blob: 7b662754e5b47a591a2f0e585c22dd18ab516bf4 [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 <src/lib/fxl/logging.h>
#include <src/lib/fxl/strings/string_printf.h>
#include "garnet/lib/perfmon/events.h"
namespace cpuperf_provider {
void TraceConfig::Reset() {
model_event_manager_ = nullptr;
is_enabled_ = false;
trace_os_ = false;
trace_user_ = false;
trace_pc_ = false;
trace_last_branch_ = false;
sample_rate_ = 0;
timebase_event_ = PERFMON_EVENT_ID_NONE;
selected_categories_.clear();
}
bool TraceConfig::ProcessCategories(const CategorySpec categories[],
size_t num_categories,
CategoryData* data) {
for (size_t i = 0; i < num_categories; ++i) {
const CategorySpec& cat = categories[i];
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 (data->have_sample_rate) {
FXL_LOG(ERROR)
<< "Only one sampling mode at a time is currently supported";
return false;
}
data->have_sample_rate = true;
sample_rate_ = cat.value;
break;
case CategoryGroup::kFixedArch:
case CategoryGroup::kFixedModel:
selected_categories_.insert(&cat);
data->have_data_to_collect = true;
break;
case CategoryGroup::kProgrammableArch:
case CategoryGroup::kProgrammableModel:
if (data->have_programmable_category) {
// TODO(dje): Temporary limitation.
FXL_LOG(ERROR) << "Only one programmable category at a time is "
"currently supported";
return false;
}
data->have_programmable_category = true;
data->have_data_to_collect = true;
selected_categories_.insert(&cat);
break;
}
}
}
return true;
}
bool TraceConfig::ProcessAllCategories() {
// 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 even 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 (size_t i = 0; i < kNumCommonCategories; ++i) {
if (trace_is_category_enabled(kCommonCategories[i].name))
++num_enabled_categories;
}
for (size_t i = 0; i < kNumTargetCategories; ++i) {
if (trace_is_category_enabled(kTargetCategories[i].name))
++num_enabled_categories;
}
bool is_default_case = num_enabled_categories ==
(kNumCommonCategories + kNumTargetCategories);
// 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;
CategoryData category_data;
if (!ProcessCategories(kCommonCategories, kNumCommonCategories,
&category_data)) {
return false;
}
if (!ProcessCategories(kTargetCategories, kNumTargetCategories,
&category_data)) {
return false;
}
// If neither OS,USER are specified, track both.
if (!trace_os_ && !trace_user_) {
trace_os_ = true;
trace_user_ = true;
}
is_enabled_ = category_data.have_data_to_collect;
return true;
}
bool TraceConfig::ProcessTimebase() {
for (size_t i = 0; i < kNumTimebaseCategories; ++i) {
const TimebaseSpec& cat = kTimebaseCategories[i];
if (trace_is_category_enabled(cat.name)) {
FXL_VLOG(1) << "Category " << cat.name << " enabled";
if (timebase_event_ != PERFMON_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(perfmon::ModelEventManager* model_event_manager) {
Reset();
model_event_manager_ = model_event_manager;
if (ProcessAllCategories()) {
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(perfmon_ioctl_config_t* out_config) const {
FXL_CHECK(model_event_manager_);
perfmon_ioctl_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_ != PERFMON_EVENT_ID_NONE) {
const perfmon::EventDetails* details;
FXL_CHECK(model_event_manager_->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)) {
perfmon_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 |= PERFMON_CONFIG_FLAG_OS;
if (trace_user_)
flags |= PERFMON_CONFIG_FLAG_USER;
if (timebase_event_ == PERFMON_EVENT_ID_NONE) {
// These can only be set for events that are their own timebase.
if (trace_pc_)
flags |= PERFMON_CONFIG_FLAG_PC;
if (trace_last_branch_)
flags |= PERFMON_CONFIG_FLAG_LAST_BRANCH;
}
if (timebase_event_ != PERFMON_EVENT_ID_NONE)
flags |= PERFMON_CONFIG_FLAG_TIMEBASE0;
for (unsigned i = 0; i < num_used_events; ++i) {
cfg->rate[i] = sample_rate_;
cfg->flags[i] = flags;
}
if (timebase_event_ != PERFMON_EVENT_ID_NONE) {
if (trace_pc_)
cfg->flags[0] |= PERFMON_CONFIG_FLAG_PC;
if (trace_last_branch_)
cfg->flags[0] |= PERFMON_CONFIG_FLAG_LAST_BRANCH;
}
return true;
}
std::string TraceConfig::ToString() const {
FXL_CHECK(model_event_manager_);
std::string result;
if (!is_enabled_)
return "disabled";
if (timebase_event_ != PERFMON_EVENT_ID_NONE) {
const perfmon::EventDetails* details;
FXL_CHECK(model_event_manager_->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