blob: f77dae9c3b4ff68792a7bca396a06d838cc3ac09 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/cpuperf_provider/categories.h"
#include <trace-engine/instrumentation.h>
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
#include "garnet/lib/cpuperf/events.h"
namespace cpuperf_provider {
enum EventId {
#define DEF_FIXED_EVENT(symbol, event_name, id, regnum, flags, \
readable_name, description) \
symbol = CPUPERF_MAKE_EVENT_ID(CPUPERF_GROUP_FIXED, id),
#define DEF_ARCH_EVENT(symbol, event_name, id, ebx_bit, event, \
umask, flags, readable_name, description) \
symbol = CPUPERF_MAKE_EVENT_ID(CPUPERF_GROUP_ARCH, id),
#include <lib/zircon-internal/device/cpu-trace/intel-pm-events.inc>
#define DEF_SKL_EVENT(symbol, event_name, id, event, umask, \
flags, readable_name, description) \
symbol = CPUPERF_MAKE_EVENT_ID(CPUPERF_GROUP_MODEL, id),
#include <lib/zircon-internal/device/cpu-trace/skylake-pm-events.inc>
#define DEF_MISC_SKL_EVENT(symbol, event_name, id, offset, size, \
flags, readable_name, description) \
symbol = CPUPERF_MAKE_EVENT_ID(CPUPERF_GROUP_MISC, id),
#include <lib/zircon-internal/device/cpu-trace/skylake-misc-events.inc>
};
#define DEF_FIXED_CATEGORY(symbol, name, events...) \
static const cpuperf_event_id_t symbol##_events[] = {events};
#define DEF_ARCH_CATEGORY(symbol, name, events...) \
static const cpuperf_event_id_t symbol##_events[] = {events};
#include "intel-pm-categories.inc"
#define DEF_SKL_CATEGORY(symbol, name, events...) \
static const cpuperf_event_id_t symbol##_events[] = {events};
#include "skylake-pm-categories.inc"
#define DEF_MISC_SKL_CATEGORY(symbol, name, events...) \
static const cpuperf_event_id_t symbol##_events[] = {events};
#include "skylake-misc-categories.inc"
static const CategorySpec kCategories[] = {
// Options
{"cpu:os", CategoryGroup::kOption,
static_cast<CategoryValue>(TraceOption::kOs), 0, nullptr},
{"cpu:user", CategoryGroup::kOption,
static_cast<CategoryValue>(TraceOption::kUser), 0, nullptr},
{"cpu:pc", CategoryGroup::kOption,
static_cast<CategoryValue>(TraceOption::kPc), 0, nullptr},
{"cpu:last_branch", CategoryGroup::kOption,
static_cast<CategoryValue>(TraceOption::kLastBranch), 0, nullptr},
// Sampling rates.
// Only one of the following is allowed.
#define DEF_SAMPLE(name, value) \
{ "cpu:" name, CategoryGroup::kSample, value, 0, nullptr }
DEF_SAMPLE("tally", 0),
DEF_SAMPLE("sample:100", 100),
DEF_SAMPLE("sample:500", 500),
DEF_SAMPLE("sample:1000", 1000),
DEF_SAMPLE("sample:5000", 5000),
DEF_SAMPLE("sample:10000", 10000),
DEF_SAMPLE("sample:50000", 50000),
DEF_SAMPLE("sample:100000", 100000),
DEF_SAMPLE("sample:500000", 500000),
DEF_SAMPLE("sample:1000000", 1000000),
#undef DEF_SAMPLE
// TODO(dje): Reorganize fixed,arch,skl(model),misc vs
// fixed/programmable+arch/model.
// Fixed events.
#define DEF_FIXED_CATEGORY(symbol, name, events...) \
{"cpu:" name, CategoryGroup::kFixedArch, 0, countof(symbol##_events), \
&symbol##_events[0]},
#include "intel-pm-categories.inc"
// Architecturally specified programmable events.
#define DEF_ARCH_CATEGORY(symbol, name, events...) \
{"cpu:" name, CategoryGroup::kProgrammableArch, 0, countof(symbol##_events), \
&symbol##_events[0]},
#include "intel-pm-categories.inc"
// Model-specific misc events
#define DEF_MISC_SKL_CATEGORY(symbol, name, events...) \
{"cpu:" name, CategoryGroup::kFixedModel, 0, countof(symbol##_events), \
&symbol##_events[0]},
#include "skylake-misc-categories.inc"
// Model-specific programmable events.
#define DEF_SKL_CATEGORY(symbol, name, events...) \
{"cpu:" name, CategoryGroup::kProgrammableModel, 0, \
countof(symbol##_events), &symbol##_events[0]},
#include "skylake-pm-categories.inc"
};
static const TimebaseSpec kTimebaseCategories[] = {
#define DEF_TIMEBASE_CATEGORY(symbol, name, event) {"cpu:" name, event},
#include "intel-timebase-categories.inc"
};
void TraceConfig::Reset() {
is_enabled_ = false;
trace_os_ = false;
trace_user_ = false;
trace_pc_ = false;
trace_last_branch_ = false;
sample_rate_ = 0;
timebase_event_ = CPUPERF_EVENT_ID_NONE;
selected_categories_.clear();
}
bool TraceConfig::ProcessCategories() {
// The default, if the user doesn't specify any categories, is that every
// trace category is enabled. This doesn't work for us as the h/w doesn't
// support enabling all events at once. And event when multiplexing support
// is added it may not support multiplexing everything. So watch for the
// default case, which we have to explicitly do as the only API we have is
// trace_is_category_enabled(), and if present apply our own default.
size_t num_enabled_categories = 0;
for (const auto& cat : kCategories) {
if (trace_is_category_enabled(cat.name))
++num_enabled_categories;
}
bool is_default_case = num_enabled_categories == countof(kCategories);
// Our default is to not trace anything: This is fairly specialized tracing
// so we only provide it if the user explicitly requests it.
if (is_default_case)
return false;
bool have_something = false;
bool have_sample_rate = false;
bool have_programmable_category = false;
for (const auto& cat : kCategories) {
if (trace_is_category_enabled(cat.name)) {
FXL_VLOG(1) << "Category " << cat.name << " enabled";
switch (cat.group) {
case CategoryGroup::kOption:
switch (static_cast<TraceOption>(cat.value)) {
case TraceOption::kOs:
trace_os_ = true;
break;
case TraceOption::kUser:
trace_user_ = true;
break;
case TraceOption::kPc:
trace_pc_ = true;
break;
case TraceOption::kLastBranch:
trace_last_branch_ = true;
break;
}
break;
case CategoryGroup::kSample:
if (have_sample_rate) {
FXL_LOG(ERROR)
<< "Only one sampling mode at a time is currenty supported";
return false;
}
have_sample_rate = true;
sample_rate_ = cat.value;
break;
case CategoryGroup::kFixedArch:
case CategoryGroup::kFixedModel:
selected_categories_.insert(&cat);
have_something = true;
break;
case CategoryGroup::kProgrammableArch:
case CategoryGroup::kProgrammableModel:
if (have_programmable_category) {
// TODO(dje): Temporary limitation.
FXL_LOG(ERROR) << "Only one programmable category at a time is "
"currenty supported";
return false;
}
have_programmable_category = true;
have_something = true;
selected_categories_.insert(&cat);
break;
}
}
}
// If neither OS,USER are specified, track both.
if (!trace_os_ && !trace_user_) {
trace_os_ = true;
trace_user_ = true;
}
is_enabled_ = have_something;
return true;
}
bool TraceConfig::ProcessTimebase() {
for (const auto& cat : kTimebaseCategories) {
if (trace_is_category_enabled(cat.name)) {
FXL_VLOG(1) << "Category " << cat.name << " enabled";
if (timebase_event_ != CPUPERF_EVENT_ID_NONE) {
FXL_LOG(ERROR) << "Timebase already specified";
return false;
}
if (sample_rate_ == 0) {
FXL_LOG(ERROR) << "Timebase cannot be used in tally mode";
return false;
}
timebase_event_ = cat.event;
}
}
return true;
}
void TraceConfig::Update() {
Reset();
if (ProcessCategories()) {
if (ProcessTimebase()) {
return;
}
}
// Some error occurred while parsing the selected categories.
Reset();
}
bool TraceConfig::Changed(const TraceConfig& old) const {
if (is_enabled_ != old.is_enabled_)
return true;
if (trace_os_ != old.trace_os_)
return true;
if (trace_user_ != old.trace_user_)
return true;
if (trace_pc_ != old.trace_pc_)
return true;
if (trace_last_branch_ != old.trace_last_branch_)
return true;
if (sample_rate_ != old.sample_rate_)
return true;
if (timebase_event_ != old.timebase_event_)
return true;
if (selected_categories_ != old.selected_categories_)
return true;
return false;
}
bool TraceConfig::TranslateToDeviceConfig(cpuperf_config_t* out_config) const {
cpuperf_config_t* cfg = out_config;
memset(cfg, 0, sizeof(*cfg));
unsigned ctr = 0;
// If a timebase is requested, it is the first event.
if (timebase_event_ != CPUPERF_EVENT_ID_NONE) {
const cpuperf::EventDetails* details;
FXL_CHECK(cpuperf::EventIdToEventDetails(timebase_event_, &details));
FXL_VLOG(2) << fxl::StringPrintf("Using timebase %s", details->name);
cfg->events[ctr++] = timebase_event_;
}
for (const auto& cat : selected_categories_) {
const char* group_name;
switch (cat->group) {
case CategoryGroup::kFixedArch:
group_name = "fixed-arch";
break;
case CategoryGroup::kFixedModel:
group_name = "fixed-model";
break;
case CategoryGroup::kProgrammableArch:
group_name = "programmable-arch";
break;
case CategoryGroup::kProgrammableModel:
group_name = "programmable-model";
break;
default:
FXL_NOTREACHED();
}
for (size_t i = 0; i < cat->count; ++i) {
if (ctr < countof(cfg->events)) {
cpuperf_event_id_t id = cat->events[i];
FXL_VLOG(2) << fxl::StringPrintf("Adding %s event id %u to trace",
group_name, id);
cfg->events[ctr++] = id;
} else {
FXL_LOG(ERROR) << "Maximum number of events exceeded";
return false;
}
}
}
unsigned num_used_events = ctr;
uint32_t flags = 0;
if (trace_os_)
flags |= CPUPERF_CONFIG_FLAG_OS;
if (trace_user_)
flags |= CPUPERF_CONFIG_FLAG_USER;
if (timebase_event_ == CPUPERF_EVENT_ID_NONE) {
// These can only be set for events that are their own timebase.
if (trace_pc_)
flags |= CPUPERF_CONFIG_FLAG_PC;
if (trace_last_branch_)
flags |= CPUPERF_CONFIG_FLAG_LAST_BRANCH;
}
if (timebase_event_ != CPUPERF_EVENT_ID_NONE)
flags |= CPUPERF_CONFIG_FLAG_TIMEBASE0;
for (unsigned i = 0; i < num_used_events; ++i) {
cfg->rate[i] = sample_rate_;
cfg->flags[i] = flags;
}
if (timebase_event_ != CPUPERF_EVENT_ID_NONE) {
if (trace_pc_)
cfg->flags[0] |= CPUPERF_CONFIG_FLAG_PC;
if (trace_last_branch_)
cfg->flags[0] |= CPUPERF_CONFIG_FLAG_LAST_BRANCH;
}
return true;
}
std::string TraceConfig::ToString() const {
std::string result;
if (!is_enabled_)
return "disabled";
if (timebase_event_ != CPUPERF_EVENT_ID_NONE) {
const cpuperf::EventDetails* details;
FXL_CHECK(cpuperf::EventIdToEventDetails(timebase_event_, &details));
result +=
fxl::StringPrintf("Timebase 0x%x(%s)", timebase_event_, details->name);
}
if (sample_rate_ > 0) {
result += fxl::StringPrintf("@%u", sample_rate_);
} else {
result += "tally";
}
if (trace_os_)
result += ",os";
if (trace_user_)
result += ",user";
if (trace_pc_)
result += ",pc";
if (trace_last_branch_)
result += ",last_branch";
for (const auto& cat : selected_categories_) {
result += ",";
result += cat->name;
}
return result;
}
} // namespace cpuperf_provider