From 7e603942bb27529d345f561b427ce2323f87ea2d Mon Sep 17 00:00:00 2001 From: "lijindong (C)" <2220386943@qq.com> Date: Thu, 4 Jul 2024 15:40:26 +0800 Subject: [PATCH] support read trace pointer data. --- .gitignore | 1 + include/pcerrc.h | 2 + include/pmu.h | 15 ++ pmu/pmu.cpp | 11 +- pmu/pmu_event.h | 5 + pmu/pmu_list.cpp | 8 + pmu/sampler.cpp | 4 +- pmu/trace_pointer_parser.cpp | 202 ++++++++++++++++++++++++++ pmu/trace_pointer_parser.h | 77 ++++++++++ test/test_perf/test_trace_pointer.cpp | 119 +++++++++++++++ 10 files changed, 442 insertions(+), 2 deletions(-) create mode 100644 pmu/trace_pointer_parser.cpp create mode 100644 pmu/trace_pointer_parser.h create mode 100644 test/test_perf/test_trace_pointer.cpp diff --git a/.gitignore b/.gitignore index 485dee6..307dfa2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea +cmake-build* diff --git a/include/pcerrc.h b/include/pcerrc.h index f5c6c02..1158dcc 100644 --- a/include/pcerrc.h +++ b/include/pcerrc.h @@ -74,6 +74,8 @@ extern "C" { #define LIBPERF_ERR_QUERY_EVENT_LIST_FAILED 1030 #define LIBPERF_ERR_PATH_INACCESSIBLE 1031 #define LIBPERF_ERR_INVALID_SAMPLE_RATE 1032 +#define LIBPERF_ERR_INVALID_FIELD_ARGS 1033 +#define LIBPERF_ERR_FIND_FIELD_LOSS 1034 #define UNKNOWN_ERROR 9999 diff --git a/include/pmu.h b/include/pmu.h index 2a08332..98fd691 100644 --- a/include/pmu.h +++ b/include/pmu.h @@ -150,6 +150,10 @@ struct PmuDataExt { unsigned long event; // event id, which is a bit map of mixed events, event bit is defined in SPE_EVENTS. }; +struct SampleRawData { + char *data; +}; + struct PmuData { struct Stack* stack; // call stack const char *evt; // event name @@ -162,6 +166,7 @@ struct PmuData { uint64_t period; // sample period uint64_t count; // event count. Only available for Counting. struct PmuDataExt *ext; // extension. Only available for Spe. + struct SampleRawData *rawData; // trace pointer collect data. }; /** @@ -283,6 +288,16 @@ void PmuClose(int pd); */ void PmuDataFree(struct PmuData* pmuData); +/** + * @brief Get the pointer trace event raw field. + * @param rawData the raw data. + * @param fieldName the filed name of one field. + * @param value the pointer of value. + * @param vSize the memory size of value. + * @return 0 success other failed. + */ +int PmuObtainPointerField(struct SampleRawData *rawData, const char *fieldName, void *value, uint32_t vSize); + #pragma GCC visibility pop #ifdef __cplusplus } diff --git a/pmu/pmu.cpp b/pmu/pmu.cpp index e3c3a4e..d7b58e7 100644 --- a/pmu/pmu.cpp +++ b/pmu/pmu.cpp @@ -26,6 +26,7 @@ #include "linked_list.h" #include "pcerr.h" #include "safe_handler.h" +#include "trace_pointer_parser.h" #include "pmu.h" using namespace pcerr; @@ -745,4 +746,12 @@ int PmuDumpData(struct PmuData *pmuData, unsigned len, char *filepath, int dumpD } New(SUCCESS); return 0; -} \ No newline at end of file +} + +int PmuObtainPointerField(struct SampleRawData *rawData, const char *fieldName, void *value, uint32_t vSize) { + if (rawData == nullptr) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, "rawData cannot be nullptr."); + return LIBPERF_ERR_INVALID_FIELD_ARGS; + } + return TracePointerParser::PointerPasser::ParsePointer(rawData->data, fieldName, value, vSize); +} diff --git a/pmu/pmu_event.h b/pmu/pmu_event.h index bc0fead..d8ded6c 100644 --- a/pmu/pmu_event.h +++ b/pmu/pmu_event.h @@ -63,6 +63,11 @@ struct PerfRawSample { unsigned long ips[]; }; +struct TraceRawData { + __u32 size; + char data[]; +}; + struct PerfTraceSample { __u64 sampleId; __u32 tid, pid; diff --git a/pmu/pmu_list.cpp b/pmu/pmu_list.cpp index 7f06549..f81686c 100644 --- a/pmu/pmu_list.cpp +++ b/pmu/pmu_list.cpp @@ -23,6 +23,7 @@ #include "pcerr.h" #include "util_time.h" #include "log.h" +#include "trace_pointer_parser.h" #include "pmu_event_list.h" #include "pmu_list.h" #include "common.h" @@ -466,6 +467,13 @@ namespace KUNPENG_PMU { for (auto &extMem : findData->second.extPool) { delete[] extMem; } + for (auto pd: findData->second.data) { + if (pd.rawData != nullptr) { + TracePointerParser::PointerPasser::FreePointerData(pd.rawData->data); + free(pd.rawData); + pd.rawData = nullptr; + } + } userDataList.erase(pmuData); } diff --git a/pmu/sampler.cpp b/pmu/sampler.cpp index 10bd5bf..1d7f549 100644 --- a/pmu/sampler.cpp +++ b/pmu/sampler.cpp @@ -31,6 +31,7 @@ #include "process_map.h" #include "log.h" #include "sampler.h" +#include "trace_pointer_parser.h" #include "common.h" using namespace std; @@ -53,7 +54,7 @@ int KUNPENG_PMU::PerfSampler::MapPerfAttr() attr.config = this->evt->config; attr.size = sizeof(struct perf_event_attr); attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_ID | - PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | PERF_SAMPLE_IDENTIFIER; + PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_RAW; attr.freq = this->evt->useFreq; attr.sample_period = this->evt->period; attr.read_format = PERF_FORMAT_ID; @@ -166,6 +167,7 @@ void KUNPENG_PMU::PerfSampler::RawSampleProcess( current->pid = static_cast(sample->pid); current->tid = static_cast(sample->tid); current->period = static_cast(sample->period); + TracePointerParser::PointerPasser::ParserRawFormatData(current, sample, event, this->evt->name); } void KUNPENG_PMU::PerfSampler::ReadRingBuffer(vector &data, vector &sampleIps) diff --git a/pmu/trace_pointer_parser.cpp b/pmu/trace_pointer_parser.cpp new file mode 100644 index 0000000..0efdbe1 --- /dev/null +++ b/pmu/trace_pointer_parser.cpp @@ -0,0 +1,202 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * libkperf licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + * Author: Mr.Li + * Create: 2024-07-04 + * Description: Provides the capability of parsing pointer events. + ******************************************************************************/ + +#include "trace_pointer_parser.h" + +#define offset(type, v) (&(((type *)0)->v)) + +using namespace TracePointerParser; +using namespace pcerr; + +const char *POINTER_FIELD_STR = "field:"; +const char *POINTER_OFFSET_REGEX = "%*[^0-9]%i%*[;] %*[^0-9]%i%*[;]"; + +static std::unordered_map> efMap; //the key is event name, value is field and ths field name map. +static std::unordered_map dEvtMap; //The key is the data pointer, value is event name. + +bool TracePointerParser::PointerPasser::IsNeedFormat(std::ifstream &file, const std::string &evtName) { + auto colonId = evtName.find(':'); + if (colonId == string::npos) { + return false; + } + string eventName = evtName.substr(colonId + 1); + string systemName = evtName.substr(0, colonId); + string formatPath = "/sys/kernel/tracing/events/" + systemName + "/" + eventName + "/format"; + string realPath = GetRealPath(formatPath); + if (realPath.empty()) { + return false; + } + if (!IsValidPath(realPath)) { + return false; + } + file.open(realPath); + if (!file.is_open()) { + return false; + } + return true; +} + +void ParseFormatFile(ifstream &file, const std::string &evtName) { + // parse the format file. + string line; + std::unordered_map fnMap; + + while (getline(file, line)) { + if (line.find(POINTER_FIELD_STR) == string::npos) { + continue; + } + auto sealId = line.find(';'); + if (sealId == string::npos) { + continue; + } + Field field; + if (!sscanf(line.substr(sealId + 1).c_str(), POINTER_OFFSET_REGEX, &field.offset, &field.size)) { + continue; + } + field.fieldStr = line.substr(strlen(POINTER_FIELD_STR) + 1, sealId - strlen(POINTER_FIELD_STR) - 1); + auto spaceId = field.fieldStr.find_last_of(' '); + auto braceId = field.fieldStr.find('['); + field.fieldName = + braceId == string::npos ? field.fieldStr.substr(spaceId + 1) : field.fieldStr.substr(spaceId + 1, + braceId - spaceId - + 1); + fnMap.insert({field.fieldName, field}); + } + // add the evtName and its fields map. + efMap.insert({evtName, fnMap}); +} + +void TracePointerParser::PointerPasser::ParserRawFormatData(struct PmuData *pd, KUNPENG_PMU::PerfRawSample *sample, + union KUNPENG_PMU::PerfEvent *event, + const std::string &evtName) { + ifstream file; + if (!IsNeedFormat(file, evtName)) { + pd->rawData = nullptr; + return; + } + // Get the perf sample raw data. + auto ipsOffset = (unsigned long) offset(struct PerfRawSample, ips); // ips offset of PerfRawSample. + auto traceOffset = ipsOffset + sample->nr * (sizeof(unsigned long)); // TraceRawData offset of sample.array. + TraceRawData *trace = (TraceRawData *) ((char *) &event->sample.array + traceOffset); + + if (trace->size == 0) { + pd->rawData = nullptr; + return; + } + + // copy the perf sample raw data to our rawData->data. + pd->rawData = (SampleRawData *) malloc(sizeof(struct SampleRawData)); + if (!pd->rawData) { + return; + } + pd->rawData->data = (char *) malloc(trace->size); + if (!pd->rawData->data) { + free(pd->rawData); + pd->rawData = nullptr; + return; + } + memcpy(pd->rawData->data, trace->data, trace->size); + dEvtMap.insert({pd->rawData->data, evtName}); + + if (efMap.find(evtName) != efMap.end()) { + return; + } + ParseFormatFile(file, evtName); +} + +template +int CheckFieldArgs(char *data, const string &fieldName, T *value, uint32_t vSize) { + if (data == nullptr) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, "data cannot be nullptr."); + return LIBPERF_ERR_INVALID_FIELD_ARGS; + } + if (fieldName.empty()) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, "fieldName cannot be empty."); + return LIBPERF_ERR_INVALID_FIELD_ARGS; + } + + if (value == nullptr) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, "value cannot be nullptr."); + return LIBPERF_ERR_INVALID_FIELD_ARGS; + } + + if (vSize == 0) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, "vSize cannot be zero."); + return LIBPERF_ERR_INVALID_FIELD_ARGS; + } + auto evtIt = dEvtMap.find(data); + if (evtIt == dEvtMap.end()) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, + "The args data maybe have changed, can't find the event name which it si associated with."); + return LIBPERF_ERR_INVALID_FIELD_ARGS; + } + auto fieldIt = efMap.find(evtIt->second); + if (fieldIt == efMap.end()) { + New(LIBPERF_ERR_FIND_FIELD_LOSS, "unknown error, can't find the filed map."); + return LIBPERF_ERR_FIND_FIELD_LOSS; + } + if (fieldIt->second.find(fieldName) == fieldIt->second.end()) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, "invalid fieldName, can't find it in format data."); + return LIBPERF_ERR_INVALID_FIELD_ARGS;; + } + return SUCCESS; +} + +template +int TracePointerParser::PointerPasser::ParseField(char *data, const std::string &fieldName, T *value, uint32_t vSize) { + int rt = CheckFieldArgs(data, fieldName, value, vSize); + if (rt != SUCCESS) { + return rt; + } + Field field = efMap.at(dEvtMap.at(data)).at(fieldName); + if (field.IsDataLoc()) { + int dataLoc; + memcpy(&dataLoc, data + field.offset, field.size); + char *dataName = data + (unsigned short) (dataLoc & 0xffff); + if (strlen(dataName) > vSize) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, + "__dta_loc data requires " + std::to_string(strlen(dataName)) + + " bytes of memory, the value needs larger memory."); + return LIBPERF_ERR_INVALID_FIELD_ARGS; + } + memcpy(value, dataName, strlen(dataName) + 1); + return SUCCESS; + } + if (field.size > vSize) { + New(LIBPERF_ERR_INVALID_FIELD_ARGS, + "value requires " + std::to_string(field.size) + + " bytes of memory, the value needs larger memory."); + return LIBPERF_ERR_INVALID_FIELD_ARGS; + } + memcpy(value, data + field.offset, field.size); + New(SUCCESS); + return SUCCESS; +} + +int TracePointerParser::PointerPasser::ParsePointer(char *data, const std::string &fieldName, void *value, + uint32_t vSize) { + return ParseField(data, fieldName, value, vSize); +} + +void TracePointerParser::PointerPasser::FreePointerData(char *data) { + if (data == nullptr) { + return; + } + if (dEvtMap.find(data) != dEvtMap.end()) { + dEvtMap.erase(data); + } + free(data); + data = nullptr; +} diff --git a/pmu/trace_pointer_parser.h b/pmu/trace_pointer_parser.h new file mode 100644 index 0000000..9081d8f --- /dev/null +++ b/pmu/trace_pointer_parser.h @@ -0,0 +1,77 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * libkperf licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + * Author: Mr.Li + * Create: 2024-07-04 + * Description: Provides the capability of parsing pointer events. + ******************************************************************************/ +#ifndef LIBKPERF_TRACE_POINTER_PARSER_H +#define LIBKPERF_TRACE_POINTER_PARSER_H + +#include +#include +#include +#include +#include +#include + +#include "pcerr.h" +#include "common.h" +#include "pmu_event.h" + +using namespace std; +using namespace KUNPENG_PMU; + +namespace TracePointerParser { + + struct Field { + int offset; //the data offset. + int size; //the field size. + std::string fieldName; //the field name of this field. + std::string fieldStr; //the field line. + + bool IsDataLoc() const { + return fieldStr.find("__data_loc") != string::npos; + } + }; + + class PointerPasser { + public: + /** + * @brief determine whether the event is a pointer event. + */ + static bool IsNeedFormat(ifstream &file, const std::string &evtName); + + /** + * @brief if this event is pointer event, should parse it. + */ + static void + ParserRawFormatData(struct PmuData *pd, PerfRawSample *sample, union PerfEvent *event, const string &evtName); + + /** + * @brief the method of parsing field. + */ + template + static int ParseField(char *data, const string &fieldName, T *value, uint32_t vSize); + + /** + * @brief the method of parsing field. + */ + static int ParsePointer(char *data, const string &fieldName, void *value, uint32_t vSize); + + /** + * @brief free the data. + */ + static void FreePointerData(char *data); + }; +} + + +#endif //LIBKPERF_TRACE_POINTER_PARSER_H diff --git a/test/test_perf/test_trace_pointer.cpp b/test/test_perf/test_trace_pointer.cpp new file mode 100644 index 0000000..4f360d2 --- /dev/null +++ b/test/test_perf/test_trace_pointer.cpp @@ -0,0 +1,119 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * libkperf licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + * Author: Mr.Li + * Create: 2024-07-4 + * Description: test for trace pointer event. + ******************************************************************************/ +#include "test_common.h" + +using namespace std; + +class TestTraceRaw : public testing::Test { +public: + void TearDown() { + if (appPid != 0) { + KillApp(appPid); + appPid = 0; + } + if (pmuData != nullptr) { + PmuDataFree(pmuData); + pmuData = nullptr; + } + PmuClose(pd); + } + +protected: + void OpenPointerEvent(char* eventName) { + PmuAttr attr = {0}; + char *evtList[1]; + evtList[0] = eventName; + attr.evtList = evtList; + attr.numEvt = 1; + int pidList[1]; + attr.pidList = pidList; + attr.numPid = 0; + attr.cpuList = nullptr; + attr.numCpu = 0; + attr.freq = 99; + attr.useFreq = 1; + attr.symbolMode = RESOLVE_ELF; + pd = PmuOpen(SAMPLING, &attr); + ASSERT_NE(pd, -1); + } + + void EnablePointer(unsigned int second) + { + PmuEnable(pd); + sleep(second); + PmuDisable(pd); + } + + int pd; + pid_t appPid = 0; + PmuData *pmuData = nullptr; +}; + +/** + * @brief test for sched_switch event. and all of exceptions maybe happened. + */ +TEST_F(TestTraceRaw, trace_pointer_sched_switch) { + OpenPointerEvent("sched:sched_switch"); + EnablePointer(1); + int len = PmuRead(pd, &pmuData); + if (len > 0) { + auto pPmuData = &pmuData[0]; + auto rawData = pPmuData->rawData; + ASSERT_NE(rawData, nullptr); + ASSERT_NE(rawData->data, nullptr); + char prev_comm[16]; + int rt = PmuObtainPointerField(rawData, "prev_comm", &prev_comm, sizeof(prev_comm)); + ASSERT_EQ(rt, SUCCESS); + int prev_pid; + rt = PmuObtainPointerField(rawData, "prev_pid", &prev_pid, sizeof(prev_pid)); + ASSERT_EQ(rt, SUCCESS); + char next_comm[1]; + rt = PmuObtainPointerField(rawData, "next_comm", &next_comm, sizeof(next_comm)); + ASSERT_NE(rt, SUCCESS); + char next_comm_error[16]; + rt = PmuObtainPointerField(nullptr, "next_comm", next_comm_error, sizeof(next_comm_error)); + ASSERT_NE(rt, SUCCESS); + rt = PmuObtainPointerField(rawData, "", next_comm_error, sizeof(next_comm_error)); + ASSERT_NE(rt, SUCCESS); + rt = PmuObtainPointerField(rawData, "next_comm", nullptr, sizeof(next_comm_error)); + ASSERT_NE(rt, SUCCESS); + rt = PmuObtainPointerField(rawData, "next_comm", next_comm_error, 0); + ASSERT_NE(rt, SUCCESS); + rt = PmuObtainPointerField(rawData, "next_comm_error", next_comm_error, sizeof(next_comm_error)); + ASSERT_NE(rt, SUCCESS); + } +} + +/** + * @brief test for format contain __data_loc data. + */ +TEST_F(TestTraceRaw, trace_pointer_sched_process_exec) { + OpenPointerEvent("sched:sched_process_exec"); + EnablePointer(10); + int len = PmuRead(pd, &pmuData); + if (len > 0) { + auto pPmuData = &pmuData[0]; + auto rawData = pPmuData->rawData; + ASSERT_NE(rawData, nullptr); + ASSERT_NE(rawData->data, nullptr); + char fileName[256]; + int rt = PmuObtainPointerField(rawData, "filename", &fileName, sizeof(fileName)); + ASSERT_EQ(rt, SUCCESS); + // the filename size must be logger than 4. + char file[4]; + rt = PmuObtainPointerField(rawData, "filename", &file, sizeof(file)); + ASSERT_NE(rt, SUCCESS); + } +} \ No newline at end of file -- Gitee