| // 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 |