diff options
author | Yabin Cui <yabinc@google.com> | 2020-08-24 14:21:55 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2020-08-24 14:26:24 -0700 |
commit | 15dd5f794f58ff778fab1a8f29e0aba7cca322c7 (patch) | |
tree | b93923087a5c108d6ee88b2479cf28a5b2a5494c | |
parent | 5e346d444d2311bf0e676a1a1a492f7a96b167d2 (diff) | |
download | extras-15dd5f794f58ff778fab1a8f29e0aba7cca322c7.tar.gz |
simpleperf: Support parsing dynamic string field of tracepoint events.
Also disable hardware counter testing for cf_x86_64 targets.
Bug: 165708389
Test: run simpleperf_unit_test
Change-Id: Ie2f5c4c96239077d5d55023c8f2d0ffd9b838653
-rw-r--r-- | simpleperf/cmd_dumprecord.cpp | 69 | ||||
-rw-r--r-- | simpleperf/cmd_dumprecord_test.cpp | 7 | ||||
-rw-r--r-- | simpleperf/cmd_record_test.cpp | 4 | ||||
-rw-r--r-- | simpleperf/report_lib_interface.cpp | 2 | ||||
-rw-r--r-- | simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data | bin | 0 -> 13419 bytes | |||
-rw-r--r-- | simpleperf/tracing.cpp | 111 | ||||
-rw-r--r-- | simpleperf/tracing.h | 20 | ||||
-rw-r--r-- | simpleperf/tracing_test.cpp | 45 |
8 files changed, 190 insertions, 68 deletions
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp index c996832c..554d4ea6 100644 --- a/simpleperf/cmd_dumprecord.cpp +++ b/simpleperf/cmd_dumprecord.cpp @@ -47,7 +47,8 @@ struct SymbolInfo { uint64_t vaddr_in_file; }; -using ExtractFieldFn = std::function<std::string(const TracingField&, const char*)>; +using ExtractFieldFn = + std::function<std::string(const TracingField&, const PerfSampleRawType&)>; struct EventInfo { size_t tp_data_size = 0; @@ -55,22 +56,43 @@ struct EventInfo { std::vector<ExtractFieldFn> extract_field_functions; }; -std::string ExtractStringField(const TracingField& field, const char* data) { +std::string ExtractStringField(const TracingField& field, const PerfSampleRawType& data) { std::string s; // data points to a char [field.elem_count] array. It is not guaranteed to be ended // with '\0'. So need to copy from data like strncpy. - for (size_t i = 0; i < field.elem_count && data[i] != '\0'; i++) { - s.push_back(data[i]); + size_t max_len = std::min(data.size - field.offset, field.elem_count); + const char* p = data.data + field.offset; + for (size_t i = 0; i < max_len && *p != '\0'; i++) { + s.push_back(*p++); + } + return s; +} + +std::string ExtractDynamicStringField(const TracingField& field, const PerfSampleRawType& data) { + std::string s; + const char* p = data.data + field.offset; + if (field.elem_size != 4 || field.offset + field.elem_size > data.size) { + return s; + } + uint32_t location; + MoveFromBinaryFormat(location, p); + // Parse location: (max_len << 16) | off. + uint32_t offset = location & 0xffff; + uint32_t max_len = location >> 16; + if (offset + max_len <= data.size) { + p = data.data + offset; + for (size_t i = 0; i < max_len && *p != '\0'; i++) { + s.push_back(*p++); + } } return s; } template <typename T, typename UT = typename std::make_unsigned<T>::type> -std::string ExtractIntField(const TracingField& field, const char* data) { +std::string ExtractIntFieldFromPointer(const TracingField& field, const char* p) { static_assert(std::is_signed<T>::value); - T value; - MoveFromBinaryFormat(value, data); + MoveFromBinaryFormat(value, p); if (field.is_signed) { return android::base::StringPrintf("%" PRId64, static_cast<int64_t>(value)); @@ -79,33 +101,52 @@ std::string ExtractIntField(const TracingField& field, const char* data) { } template <typename T> -std::string ExtractIntArrayField(const TracingField& field, const char* data) { +std::string ExtractIntField(const TracingField& field, const PerfSampleRawType& data) { + if (field.offset + sizeof(T) > data.size) { + return ""; + } + return ExtractIntFieldFromPointer<T>(field, data.data + field.offset); +} + +template <typename T> +std::string ExtractIntArrayField(const TracingField& field, const PerfSampleRawType& data) { + if (field.offset + field.elem_size * field.elem_count > data.size) { + return ""; + } std::string s; + const char* p = data.data + field.offset; for (size_t i = 0; i < field.elem_count; i++) { if (i != 0) { s.push_back(' '); } - s += ExtractIntField<T>(field, data); - data += field.elem_size; + ExtractIntFieldFromPointer<T>(field, p); + p += field.elem_size; } return s; } -std::string ExtractUnknownField(const TracingField& field, const char* data) { +std::string ExtractUnknownField(const TracingField& field, const PerfSampleRawType& data) { size_t total = field.elem_size * field.elem_count; + if (field.offset + total > data.size) { + return ""; + } uint32_t value; std::string s; + const char* p = data.data + field.offset; for (size_t i = 0; i + sizeof(value) <= total; i += sizeof(value)) { if (i != 0) { s.push_back(' '); } - MoveFromBinaryFormat(value, data); + MoveFromBinaryFormat(value, p); s += android::base::StringPrintf("0x%08x", value); } return s; } ExtractFieldFn GetExtractFieldFunction(const TracingField& field) { + if (field.is_dynamic) { + return ExtractDynamicStringField; + } if (field.elem_count > 1 && field.elem_size == 1) { // Probably the field is a string. // Don't use field.is_signed, which has different values on x86 and arm. @@ -336,13 +377,11 @@ void DumpRecordCommand::ProcessSampleRecord(const SampleRecord& sr) { size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&sr); auto& event = events_[attr_index]; if (event.tp_data_size > 0 && sr.raw_data.size >= event.tp_data_size) { - const char* p = sr.raw_data.data; PrintIndented(1, "tracepoint fields:\n"); for (size_t i = 0; i < event.tp_fields.size(); i++) { auto& field = event.tp_fields[i]; - std::string s = event.extract_field_functions[i](field, p); + std::string s = event.extract_field_functions[i](field, sr.raw_data); PrintIndented(2, "%s: %s\n", field.name.c_str(), s.c_str()); - p += field.elem_count * field.elem_size; } } } diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp index dc33e719..7309424b 100644 --- a/simpleperf/cmd_dumprecord_test.cpp +++ b/simpleperf/cmd_dumprecord_test.cpp @@ -53,6 +53,13 @@ TEST(cmd_dump, dump_tracepoint_fields_of_sample_records) { ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf_with_tracepoint_event.data")})); std::string data = capture.Finish(); ASSERT_NE(data.find("prev_comm: sleep"), std::string::npos); + + // dump dynamic field of tracepoint events. + ASSERT_TRUE(capture.Start()); + ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf_with_tracepoint_event_dynamic_field.data")})); + data = capture.Finish(); + ASSERT_NE(data.find("name: /sys/kernel/debug/tracing/events/kprobes/myopen/format"), + std::string::npos); } TEST(cmd_dump, etm_data) { diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index 097f94b0..3a327a3a 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -277,7 +277,9 @@ static bool InCloudAndroid() { #if defined(__ANDROID__) std::string prop_value = android::base::GetProperty("ro.build.flavor", ""); if (android::base::StartsWith(prop_value, "cf_x86_phone") || - android::base::StartsWith(prop_value, "aosp_cf_x86_phone")) { + android::base::StartsWith(prop_value, "aosp_cf_x86_phone") || + android::base::StartsWith(prop_value, "cf_x86_64_phone") || + android::base::StartsWith(prop_value, "aosp_cf_x86_64_phone")) { return true; } #endif diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp index c73f5a05..c627afc6 100644 --- a/simpleperf/report_lib_interface.cpp +++ b/simpleperf/report_lib_interface.cpp @@ -29,6 +29,8 @@ #include "tracing.h" #include "utils.h" +using namespace simpleperf; + class ReportLib; extern "C" { diff --git a/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data b/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data Binary files differnew file mode 100644 index 00000000..24e3d902 --- /dev/null +++ b/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp index 303704da..c5a45587 100644 --- a/simpleperf/tracing.cpp +++ b/simpleperf/tracing.cpp @@ -21,6 +21,7 @@ #include <map> #include <optional> +#include <regex> #include <string> #include <vector> @@ -34,6 +35,20 @@ #include "perf_event.h" #include "utils.h" +using android::base::Split; +using android::base::StartsWith; + +template <> +void MoveFromBinaryFormat(std::string& data, const char*& p) { + data.clear(); + while (*p != '\0') { + data.push_back(*p++); + } + p++; +} + +namespace simpleperf { + const char TRACING_INFO_MAGIC[10] = {23, 8, 68, 't', 'r', 'a', 'c', 'i', 'n', 'g'}; @@ -52,15 +67,6 @@ void AppendData(std::vector<char>& data, const std::string& s) { data.insert(data.end(), s.c_str(), s.c_str() + s.size() + 1); } -template <> -void MoveFromBinaryFormat(std::string& data, const char*& p) { - data.clear(); - while (*p != '\0') { - data.push_back(*p++); - } - p++; -} - static void AppendFile(std::vector<char>& data, const std::string& file, uint32_t file_size_bytes = 8) { if (file_size_bytes == 8) { @@ -281,55 +287,62 @@ enum class FormatParsingState { // Parse lines like: field:char comm[16]; offset:8; size:16; signed:1; static TracingField ParseTracingField(const std::string& s) { TracingField field; - size_t start = 0; std::string name; std::string value; - for (size_t i = 0; i < s.size(); ++i) { - if (!isspace(s[i]) && (i == 0 || isspace(s[i - 1]))) { - start = i; - } else if (s[i] == ':') { - name = s.substr(start, i - start); - start = i + 1; - } else if (s[i] == ';') { - value = s.substr(start, i - start); - if (name == "field") { - // Parse value with brackets like "comm[16]", or just a field name. - size_t left_bracket_pos = value.find('['); - if (left_bracket_pos == std::string::npos) { - field.name = value; - field.elem_count = 1; - } else { - field.name = value.substr(0, left_bracket_pos); - field.elem_count = 1; - size_t right_bracket_pos = value.find(']', left_bracket_pos); - if (right_bracket_pos != std::string::npos) { - size_t len = right_bracket_pos - left_bracket_pos - 1; - size_t elem_count; - // Array size may not be a number, like field:u32 rates[IEEE80211_NUM_BANDS]. - if (android::base::ParseUint(value.substr(left_bracket_pos + 1, len), &elem_count)) { - field.elem_count = elem_count; - } + std::regex re(R"((\w+):(.+?);)"); + + std::sregex_iterator match_it(s.begin(), s.end(), re); + std::sregex_iterator match_end; + while (match_it != match_end) { + std::smatch match = *match_it++; + std::string name = match.str(1); + std::string value = match.str(2); + + if (name == "field") { + std::string last_value_part = Split(value, " \t").back(); + + if (StartsWith(value, "__data_loc char[]")) { + // Parse value like "__data_loc char[] name". + field.name = last_value_part; + field.elem_count = 1; + field.is_dynamic = true; + } else if (auto left_bracket_pos = last_value_part.find('['); + left_bracket_pos != std::string::npos) { + // Parse value with brackets like "char comm[16]". + field.name = last_value_part.substr(0, left_bracket_pos); + field.elem_count = 1; + if (size_t right_bracket_pos = last_value_part.find(']', left_bracket_pos); + right_bracket_pos != std::string::npos) { + size_t len = right_bracket_pos - left_bracket_pos - 1; + size_t elem_count; + // Array size may not be a number, like field:u32 rates[IEEE80211_NUM_BANDS]. + if (android::base::ParseUint(last_value_part.substr(left_bracket_pos + 1, len), + &elem_count)) { + field.elem_count = elem_count; } } - } else if (name == "offset") { - field.offset = - static_cast<size_t>(strtoull(value.c_str(), nullptr, 10)); - } else if (name == "size") { - size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10)); - CHECK_EQ(size % field.elem_count, 0u); - field.elem_size = size / field.elem_count; - } else if (name == "signed") { - int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10)); - field.is_signed = (is_signed == 1); + } else { + // Parse value like "int common_pid". + field.name = last_value_part; + field.elem_count = 1; } + } else if (name == "offset") { + field.offset = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10)); + } else if (name == "size") { + size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10)); + CHECK_EQ(size % field.elem_count, 0u); + field.elem_size = size / field.elem_count; + } else if (name == "signed") { + int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10)); + field.is_signed = (is_signed == 1); } } return field; } -static TracingFormat ParseTracingFormat(const std::string& data) { +TracingFormat ParseTracingFormat(const std::string& data) { TracingFormat format; - std::vector<std::string> strs = android::base::Split(data, "\n"); + std::vector<std::string> strs = Split(data, "\n"); FormatParsingState state = FormatParsingState::READ_NAME; for (const auto& s : strs) { if (state == FormatParsingState::READ_NAME) { @@ -608,7 +621,7 @@ std::optional<std::string> AdjustTracepointFilter(const std::string& filter, boo } std::optional<FieldNameSet> GetFieldNamesForTracepointEvent(const EventType& event) { - std::vector<std::string> strs = android::base::Split(event.name, ":"); + std::vector<std::string> strs = Split(event.name, ":"); if (strs.size() != 2) { return {}; } @@ -623,3 +636,5 @@ std::optional<FieldNameSet> GetFieldNamesForTracepointEvent(const EventType& eve } return names; } + +} // namespace simpleperf diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h index f8c88adb..60d99dde 100644 --- a/simpleperf/tracing.h +++ b/simpleperf/tracing.h @@ -26,12 +26,21 @@ #include "event_type.h" #include "utils.h" +namespace simpleperf { + struct TracingField { std::string name; - size_t offset; - size_t elem_size; - size_t elem_count; - bool is_signed; + size_t offset = 0; + size_t elem_size = 0; + size_t elem_count = 1; + bool is_signed = false; + bool is_dynamic = false; + + bool operator==(const TracingField& other) const { + return name == other.name && offset == other.offset && elem_size == other.elem_size && + elem_count == other.elem_count && is_signed == other.is_signed && + is_dynamic == other.is_dynamic; + } }; struct TracingFieldPlace { @@ -113,5 +122,8 @@ using FieldNameSet = std::set<std::string>; std::optional<std::string> AdjustTracepointFilter(const std::string& filter, bool use_quote, FieldNameSet* used_fields); std::optional<FieldNameSet> GetFieldNamesForTracepointEvent(const EventType& event); +TracingFormat ParseTracingFormat(const std::string& data); + +} // namespace simpleperf #endif // SIMPLE_PERF_TRACING_H_ diff --git a/simpleperf/tracing_test.cpp b/simpleperf/tracing_test.cpp index 26cbeebe..698fd446 100644 --- a/simpleperf/tracing_test.cpp +++ b/simpleperf/tracing_test.cpp @@ -20,6 +20,8 @@ #include <android-base/strings.h> +using namespace simpleperf; + static void CheckAdjustFilter(const std::string& filter, bool use_quote, const std::string& adjusted_filter, const std::string used_field_str) { @@ -55,3 +57,46 @@ TEST(tracing, adjust_tracepoint_filter) { // unknown operator ASSERT_FALSE(AdjustTracepointFilter("pid ^ 3", true, &used_fields).has_value()); } + +namespace simpleperf { +std::ostream& operator<<(std::ostream& os, const TracingField& field){ + os << "field (" << field.name << ", off " << field.offset << ", elem size " << field.elem_size + << ", elem_count " << field.elem_count << ", is_signed " << field.is_signed << ", is_dynamic " + << field.is_dynamic << ")"; + return os; +} +} // namespace simpleperf + +TEST(tracing, ParseTracingFormat) { + std::string data = + "name: sched_wakeup_new\n" + "ID: 94\n" + "format:\n" + "\tfield:unsigned short common_type; offset:0; size:2; signed:0;\n" + "\tfield:unsigned char common_flags; offset:2; size:1; signed:0;\n" + "\tfield:unsigned char common_preempt_count; offset:3; size:1; signed:0;\n" + "\tfield:int common_pid; offset:4; size:4; signed:1;\n" + "\n" + "\tfield:char comm[16]; offset:8; size:16; signed:1;\n" + "\tfield:__data_loc char[] name; offset:24; size:4; signed:1;\n"; + TracingFormat format = ParseTracingFormat(data); + ASSERT_EQ(format.name, "sched_wakeup_new"); + ASSERT_EQ(format.id, 94); + ASSERT_EQ(format.fields.size(), 6); + ASSERT_EQ(format.fields[0], TracingField({.name = "common_type", .offset = 0, .elem_size = 2})); + ASSERT_EQ(format.fields[1], TracingField({.name = "common_flags", .offset = 2, .elem_size = 1})); + ASSERT_EQ(format.fields[2], + TracingField({.name = "common_preempt_count", .offset = 3, .elem_size = 1})); + ASSERT_EQ(format.fields[3], + TracingField({.name = "common_pid", .offset = 4, .elem_size = 4, .is_signed = true})); + ASSERT_EQ( + format.fields[4], + TracingField( + {.name = "comm", .offset = 8, .elem_size = 1, .elem_count = 16, .is_signed = true})); + ASSERT_EQ(format.fields[5], TracingField({.name = "name", + .offset = 24, + .elem_size = 4, + .elem_count = 1, + .is_signed = true, + .is_dynamic = true})); +} |