diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..fa50f14ad04b9b0533ab72d4bdccdcc903fa4d1e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "elfin-parser"] + path = third_party/elfin-parser + url = https://gitee.com/openeuler/elfin-parser.git \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..52c7be2b4cb5084a02a2dd474bdd659b0c0616f3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# gala-gopher 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.Dai +# Create: 2024-04-03 +# Description: Define the overall structure and behavior of the project. + +if (POLICY CMP0048) + cmake_policy(SET CMP0048 NEW) +endif (POLICY CMP0048) +set(PROJECT_TOP_DIR ${CMAKE_CURRENT_LIST_DIR}) +project(libkprof) +set(CMAKE_INSTALL_PREFIX "${PROJECT_TOP_DIR}/output/" CACHE PATH "Installation directory" FORCE) + +cmake_minimum_required (VERSION 3.12.0) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_C_STANDARD 11) +if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8.5) + message(FATAL_ERROR "GCC 4.8.5 or newer required") +endif() +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(TOP_DIR ${PROJECT_SOURCE_DIR}) + +message("TOP_DIR is ${TOP_DIR}") +include(${CMAKE_CURRENT_LIST_DIR}/Common.cmake) +add_subdirectory(symbol) +add_subdirectory(pmu) + +set(CMAKE_EXPORT_COMPILE_COMMANDS True) \ No newline at end of file diff --git a/Common.cmake b/Common.cmake new file mode 100644 index 0000000000000000000000000000000000000000..030dac19e57a27c8ed557790b35dee19a37d2979 --- /dev/null +++ b/Common.cmake @@ -0,0 +1,34 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# gala-gopher 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.Dai +# Create: 2024-04-03 +# Description: Set general configuration information. + + +set(CMAKE_CXX_FLAGS_DEBUG " -O0 -g -Wall -pg -fno-gnu-unique -DDEBUG") +set(CMAKE_CXX_FLAGS_RELEASE " -O3 -DNDEBUG -fstack-protector-all -Wl,-z,relro,-z,now -Wall -fPIE -s -fno-gnu-unique") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g") +set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") +set(CMAKE_C_FLAGS_DEBUG " -O0 -g -Wall -pg -fno-gnu-unique -DDEBUG") +set(CMAKE_C_FLAGS_RELEASE " -O3 -DNDEBUG -fstack-protector-all -Wl,-z,relro,-z,now -Wall -fPIE -s -fno-gnu-unique") +set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g") +set(CMAKE_C_FLAGS_MINSIZEREL "-Os -DNDEBUG") + +set(CMAKE_EXE_LINKER_FLAGS_RELEASE " -Wl,-z,relro,-z,now,-z,noexecstack -pie -s") +set(THIRD_PARTY ${PROJECT_TOP_DIR}/third_party) +add_compile_options(-w) # 调试阶段先去除告警 + +add_library(elf_static STATIC IMPORTED) +set_property(TARGET elf_static PROPERTY IMPORTED_LOCATION ${THIRD_PARTY}/local/elfin-parser/libelf++.a) + +add_library(dwarf_static STATIC IMPORTED) +set_property(TARGET dwarf_static PROPERTY IMPORTED_LOCATION ${THIRD_PARTY}/local/elfin-parser/libdwarf++.a) +include_directories(${THIRD_PARTY}/elfin-parser/dwarf) +include_directories(${THIRD_PARTY}/elfin-parser/elf) diff --git a/build.sh b/build.sh new file mode 100644 index 0000000000000000000000000000000000000000..0d56a7def05a20550bf522d89338e78639a914cb --- /dev/null +++ b/build.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# gala-gopher 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.Dai +# Create: 2024-04-03 +# Description: Building mainstream processes. + +set -e +set -x +CURRENT_DIR=$(cd $(dirname "$0"); pwd) +PROJECT_DIR=$(realpath "${CURRENT_DIR}") + +BUILD_DIR=${PROJECT_DIR}/_build +THIRD_PARTY=${PROJECT_DIR}/third_party/ +source ${PROJECT_DIR}/build/common.sh + +creat_dir "${BUILD_DIR}" +# 指定所有依赖使用的gcc +export CC=gcc +export CXX=g++ +if [ -d "${THIRD_PARTY}/local" ];then + echo ${THIRD_PARTY}/local "is exist" +else + echo ${THIRD_PARTY}local "is not exist" + creat_dir ${THIRD_PARTY}/local +fi + +# build libprof.so libraries including libprocfs.so libprocfs.a libpmu.so libpmu.a libtrace.so libtrace.so +function build_elfin() { + local cmake_target_dir=$THIRD_PARTY/local/elfin-parser + rm -rf ${cmake_target_dir} + if [ -d "${cmake_target_dir}" ];then + echo ${cmake_target_dir} "is exist" + return + else + echo ${cmake_target_dir} "is not exist" + mkdir ${cmake_target_dir} + fi + cd "$THIRD_PARTY/elfin-parser" + rm -rf build + sed -i 's/-mcpu=tsv110//g' Common.cmake + sed -i 's/-mno-outline-atomics//g' Common.cmake + sed -i 's/-march=armv8.2-a//g' Common.cmake + if ! grep -q "^add_compile_options(-Wno-error=switch-enum)" CMakeLists.txt; then + sed -i '1i\add_compile_options(-Wno-error=switch-enum)' CMakeLists.txt + fi + mkdir build + cd build + cmake -DCMAKE_INSTALL_PREFIX=${cmake_target_dir} -DCMAKE_CXX_FLAGS="-fPIC" .. + make --silent -j 64 + cp ./lib64/libdwarf++.a ./lib64/libelf++.a ${cmake_target_dir} + echo "install log path: $cmake_target_dir" +} + +build_libprof() +{ + cd $BUILD_DIR + cmake .. + make -j ${cpu_core_num} + make install + echo "build_libprof success" +} + +main() { + build_elfin + build_libprof +} + +main $@ \ No newline at end of file diff --git a/build/common.sh b/build/common.sh new file mode 100644 index 0000000000000000000000000000000000000000..e19365e4380a87ad709c2e488752f91d705854b1 --- /dev/null +++ b/build/common.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. +# gala-gopher 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.Dai +# Create: 2024-04-03 +# Description: Partial methods for building scripts. +set -e + +cpu_core_num=$(($(nproc)-1)) + +creat_dir(){ + local target_dir="$1" + if [ -d "${target_dir}" ];then + rm -rf ${target_dir} + fi + mkdir -p ${target_dir} +} + +function build_googletest(){ + local open_source_dir=$1 + local cmake_target_dir=$1/local/googletest + if [ -d "${cmake_target_dir}" ];then + echo ${cmake_target_dir} "is exist" + return + else + echo ${cmake_target_dir} "is not exist" + fi + pushd "$open_source_dir/googletest" + mkdir -p build + pushd build + cmake -DCMAKE_INSTALL_PREFIX=$cmake_target_dir -DCMAKE_CXX_FLAGS="-fPIC" .. + make -j ${cpu_core_num} + make install + echo "install log path: $cmake_target_dir" +} + +function build_sqlite3(){ + local open_source_dir=$1 + local cmake_target_dir=$1/local/sqlite3 + if [ -d "${cmake_target_dir}" ];then + echo ${cmake_target_dir} "is exist" + return + else + echo ${cmake_target_dir} "is not exist" + fi + pushd "$open_source_dir/sqlite_src" + rm -rf sqlite-src-3370200 + unzip sqlite-src-3370200.zip + cd sqlite-src-3370200 + + patch -p1 < ../0001-sqlite-no-malloc-usable-size.patch + patch -p1 < ../0002-remove-fail-testcase-in-no-free-fd-situation.patch + patch -p1 < ../0003-CVE-2022-35737.patch + patch -p1 < ../0004-fix-memory-problem-in-the-rtree-test-suite.patch + patch -p1 < ../0005-fix-integer-overflow-on-gigabyte-string.patch + patch -p1 < ../0006-CVE-2022-46908.patch + patch -p1 < ../backport-CVE-2023-36191.patch + patch -p1 < ../backport-CVE-2023-7104.patch + + bash ./configure --prefix=${cmake_target_dir} --disable-threadsafe --disable-tcl --enable-fts5 --enable-json1 CFLAGS="-O2 -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_FTS4=1 -DSQLITE_ENABLE_RTREE=1 -fstack-protector-all -Wl,-z,relro,-z,now -s -fPIC -ftrapv" LDFLAGS="-pie -s -Wl,-z,relro,-z,now" + make -j ${cpu_core_num} + make install + + popd + rm -rf $open_source_dir/sqlite_src/sqlite-src-3370200 + echo "install sqlite3 path: $cmake_target_dir" +} \ No newline at end of file diff --git a/include/pcerrc.h b/include/pcerrc.h new file mode 100644 index 0000000000000000000000000000000000000000..bcdf187283d4f0d3b37f58619c9ae842d73aa045 --- /dev/null +++ b/include/pcerrc.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Gan + * Create: 2024-04-03 + * Description: global error codes of perf.so + ******************************************************************************/ +#ifndef PCERRC_H +#define PCERRC_H +#ifdef __cplusplus +extern "C" { +#endif +// default code +#define SUCCESS 0 +#define COMMON_ERR_NOMEM 1 // not enough memory + +// libsym 100-1000 +#define LIBSYM_ERR_BASE 100 +#define LIBSYM_ERR_KALLSYMS_INVALID LIBSYM_ERR_BASE +#define LIBSYM_ERR_DWARF_FORMAT_FAILED 101 +#define LIBSYM_ERR_ELFIN_FOMAT_FAILED 102 +#define LIBSYM_ERR_OPEN_FILE_FAILED 103 +#define LIBSYM_ERR_NOT_FIND_PID 104 +#define LIBSYM_ERR_MAP_ADDR_MODULE_FAILED 105 +#define LIBSYM_ERR_MAP_KERNAL_ADDR_FAILED 106 +#define LIBSYM_ERR_PARAM_PID_INVALID 107 +#define LIBSYM_ERR_STRCPY_OPERATE_FAILED 108 +#define LIBSYM_ERR_SNPRINF_OPERATE_FAILED 109 +#define LIBSYM_ERR_MAP_CODE_KERNEL_NOT_SUPPORT 110 +#define LIBSYM_ERR_MAP_CODE_FIND_ELF_FAILED 111 +#define LIBSYM_ERR_CMD_OPERATE_FAILED 112 +#define LIBSYM_ERR_FILE_NOT_RGE 113 +#define LIBSYM_ERR_START_SMALLER_END 114 +#define LIBSYM_ERR_STOUL_OPERATE_FAILED 115 +#define LIBSYM_ERR_FILE_INVALID 116 +// libperf 1000-3000 +#define LIBPERF_ERR_NO_AVAIL_PD 1000 +#define LIBPERF_ERR_CHIP_TYPE_INVALID 1001 +#define LIBPERF_ERR_FAIL_LISTEN_PROC 1002 +#define LIBPERF_ERR_INVALID_CPULIST 1003 +#define LIBPERF_ERR_INVALID_PIDLIST 1004 +#define LIBPERF_ERR_INVALID_EVTLIST 1005 +#define LIBPERF_ERR_INVALID_PD 1006 +#define LIBPERF_ERR_INVALID_EVENT 1007 +#define LIBPERF_ERR_SPE_UNAVAIL 1008 +#define LIBPERF_ERR_FAIL_GET_CPU 1009 +#define LIBPERF_ERR_FAIL_GET_PROC 1010 +#define LIBPERF_ERR_NO_PERMISSION 1011 +#define LIBPERF_ERR_DEVICE_BUSY 1012 +#define LIBPERF_ERR_DEVICE_INVAL 1013 +#define LIBPERF_ERR_FAIL_MMAP 1014 +#define LIBPERF_ERR_FAIL_RESOLVE_MODULE 1015 +#define LIBPERF_ERR_KERNEL_NOT_SUPPORT 1016 +#define LIBPERF_ERR_INVALID_METRIC_TYPE 1017 +#define LIBPERF_ERR_INVALID_PID 1018 +#define LIBPERF_ERR_INVALID_TASK_TYPE 1019 +#define LIBPERF_ERR_INVALID_TIME 1020 +#define LIBPERF_ERR_NO_PROC 1021 +#define LIBPERF_ERR_TOO_MANY_FD 1022 +#define LIBPERF_ERR_RAISE_FD 1023 +#define LIBPERF_ERR_INVALID_PMU_DATA 1024 +#define LIBPERF_ERR_FAILED_PMU_ENABLE 1025 +#define LIBPERF_ERR_FAILED_PMU_DISABLE 1026 +#define LIBPERF_ERR_FAILED_PMU_RESET 1027 +#define LIBPERF_ERR_NOT_OPENED 1028 + +#define UNKNOWN_ERROR 9999 + +/** +* @brief Obtaining error codes +*/ +int Perrorno(); + +/** +* @brief Obtaining Error Information +*/ +const char* Perror(); +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/pmu.h b/include/pmu.h new file mode 100644 index 0000000000000000000000000000000000000000..f8a79ba33520ba9fde1818ecb27c8044c66048a4 --- /dev/null +++ b/include/pmu.h @@ -0,0 +1,189 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Gan + * Create: 2024-04-03 + * Description: declarations and definitions of interfaces and data structures exposed by perf.so + ******************************************************************************/ +#ifndef PMU_DATA_STRUCT_H +#define PMU_DATA_STRUCT_H +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +#pragma GCC visibility push(default) + +enum PmuTaskType { + COUNTING = 0, // pmu counting task + SAMPLING = 1, // pmu sampling task + SPE_SAMPLING = 2, // spe sampling task + MAX_TASK_TYPE +}; + +enum AggregateType { + PER_SYSTEM, + PER_CORE, + PER_NUMA, + PER_SOCKET, + PER_THREAD, +}; + +enum SpeFilter { + SPE_FILTER_NONE = 0, + TS_ENABLE = 1UL << 0, // enable timestamping with value of generic timer + PA_ENABLE = 1UL << 1, // collect physical address (as well as VA) of loads/stores + PCT_ENABLE = 1UL << 2, // collect physical timestamp instead of virtual timestamp + JITTER = 1UL << 16, // use jitter to avoid resonance when sampling + BRANCH_FILTER = 1UL << 32, // collect branches only + LOAD_FILTER = 1UL << 33, // collect loads only + STORE_FILTER = 1UL << 34, // collect stores only + SPE_DATA_ALL = TS_ENABLE | PA_ENABLE | PCT_ENABLE | JITTER | BRANCH_FILTER | LOAD_FILTER | STORE_FILTER +}; + +enum SpeEventFilter { + SPE_EVENT_NONE = 0, + SPE_EVENT_RETIRED = 0x2, // instruction retired + SPE_EVENT_L1DMISS = 0x8, // L1D refill + SPE_EVENT_TLB_WALK = 0x20, // TLB refill + SPE_EVENT_MISPREDICTED = 0x80, // mispredict +}; + +struct PmuAttr { + char** evtList; // event list + unsigned numEvt; // length of event list + int* pidList; // pid list + unsigned numPid; // length of pid list + int* cpuList; // cpu id list + unsigned numCpu; // length of cpu id list + + union { + unsigned period; // sample period + unsigned freq; // sample frequency + }; + unsigned useFreq : 1; + unsigned excludeUser : 1; // don't count user + unsigned excludeKernel : 1; // don't count kernel + + // SPE related fields. + enum SpeFilter dataFilter; // spe data filter + enum SpeEventFilter evFilter; // spe event filter + unsigned long minLatency; // collect only samples with latency or higher +}; + +struct PmuDataExt { + unsigned long pa; // physical address + unsigned long va; // virtual address + unsigned long event; // event id +}; + +struct PmuData { + struct Stack* stack; // call stack + const char *evt; // event name + int64_t ts; // time stamp + pid_t pid; // process id + int tid; // thread id + unsigned cpu; // cpu id + struct CpuTopology *cpuTopo; // cpu topology + const char *comm; // process command + int period; // number of Samples + union { + uint64_t count; // event count. Only available for Counting. + struct PmuDataExt *ext; // extension. Only available for Spe. + }; +}; + +/** + * @brief + * Initialize the collection target. + * @param collectType collection typr. + * @param evtList array of event IDs + * @param numEvt length of evtList. + * @param pidList list of PIDs to be collected. Information about subprocess and subthreads of PIDs is collected. If + * the value is NULL, all process/threads are collected + * @param numPid length of pidList. + * @param cpuList CPU ID list. If the value is NULL, all CPUs are collected. + * @param numCpu cpuList length. + * @return int + */ +int PmuOpen(enum PmuTaskType collectType, struct PmuAttr *attr); + +/** + * @brief + * Enable counting or sampling of task . + * On error, -1 is returned. + */ +int PmuEnable(int pd); + +/** + * @brief + * Disable counting or sampling of task . + * On error, -1 is returned. + */ +int PmuDisable(int pd); + +/** + * @brief + * Collect milliseconds. If is equal to - 1 and the PID list is not empty, the collection + * is performed until all processes are complete. + * @param milliseconds + * @return int + */ +int PmuCollect(int pd, int milliseconds); + +/** + * @brief + * Similar to , and accepts multiple pds. + * @param milliseconds + * @return int + */ +int PmuCollectV(int *pd, unsigned len, int milliseconds); + +/** + * @brief stop a sampling task in asynchronous mode + * @param pd pmu descriptor. + */ +void PmuStop(int pd); + +/** + * @brief + * Collect data. If the value is NULL and the error code is 0, no data is available in the current collection time. If + * the value is NULL and the error code is not 0, an error occurs in the collection process and data cannot be read. + * @param struct PmuData* + */ +int PmuRead(int pd, struct PmuData** pmuData); + +/** + * @brief + * Append data list to another data list <*toData>. + * The pointer of data list <*toData> will be refreshed after this function is called. + * On success, length of <*toData> is returned. + * On error, -1 is returned. + * @param fromData data list which will be copied to <*toData> + * @param toData pointer to target data list. If data list <*toData> is NULL, a new list will be created. + */ +int PmuAppendData(struct PmuData *fromData, struct PmuData **toData); + +/** + * @brief Close all the file descriptor opened during collecting process + */ +void PmuClose(int pd); + +/** + * @brief Free PmuData pointer. + * @param pmuData + */ +void PmuDataFree(struct PmuData* pmuData); + +#pragma GCC visibility pop +#ifdef __cplusplus +} +#endif +#endif diff --git a/pmu/CMakeLists.txt b/pmu/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d3440095412eb1e8c339d82a791ef9f7f570ef6e --- /dev/null +++ b/pmu/CMakeLists.txt @@ -0,0 +1,38 @@ +# Description: Compile Devikit rpc framework. +# Copyright: Copyright © Huawei Technologies Co., Ltd. 2023. All rights reserved. +# History: 2023-05-10 created + +if (POLICY CMP0048) + cmake_policy(SET CMP0048 NEW) +endif (POLICY CMP0048) +project(libkprof) +cmake_minimum_required (VERSION 3.12.0) +# Related directory settings # +set(UTIL_FILE_DIR ${PROJECT_TOP_DIR}/util) +set(SYMBOL_FILE_DIR ${PROJECT_TOP_DIR}/symbol) +set(PMU_FILE_DIR ${PROJECT_TOP_DIR}/pmu) +set(PFM_FILE_DIR ${PROJECT_TOP_DIR}/pmu/pfm) +set(PMU_DECODER_DIR ${PMU_FILE_DIR}/decoder) + +# Source files # +file(GLOB UTIL_SRC ${UTIL_FILE_DIR}/*.cpp) +file(GLOB PMU_SRC ${PMU_FILE_DIR}/*c ${PMU_FILE_DIR}/*cpp) +file(GLOB PMU_DECODER_SRC ${PMU_DECODER_DIR}/*.cpp) +file(GLOB SYMBOL_SRC ${SYMBOL_FILE_DIR}/*c ${SYMBOL_FILE_DIR}/*cpp) +file(GLOB PFM_SRC ${PFM_FILE_DIR}/*c ${PFM_FILE_DIR}/*cpp) + +include_directories(${PROJECT_TOP_DIR}/include) +include_directories(${PMU_FILE_DIR}/) +include_directories(${PFM_FILE_DIR}) + +# directories for ultilities and symbol resolving +include_directories(${UTIL_FILE_DIR}) +include_directories(${SYMBOL_FILE_DIR}) +include_directories(${PMU_DECODER_DIR}) + +ADD_LIBRARY(kperf SHARED ${PMU_SRC} ${UTIL_SRC} ${PFM_SRC} ${PMU_DECODER_SRC}) +target_link_libraries(kperf numa sym) +target_compile_options(kperf PRIVATE -fPIC) +install(TARGETS kperf DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +file(GLOB HEADER_FILES ${PROJECT_TOP_DIR}/include/*.h) +install(FILES ${HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include) diff --git a/pmu/decoder/arm_spe_decoder.cpp b/pmu/decoder/arm_spe_decoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..466f016bb3937d81c85ec53c7b23f3a4f91cf3f8 --- /dev/null +++ b/pmu/decoder/arm_spe_decoder.cpp @@ -0,0 +1,261 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Jin + * Create: 2024-04-03 + * Description: parses the SPE data in the AUX buffer + ******************************************************************************/ +#include +#include +#include +#include "spe.h" +#include "arm_spe_decoder.h" + +static void SetPktPayload(struct SpePacket *pkt, uint8_t *buf) +{ + switch (pkt->payloadSize) { + case 1: + pkt->payload = *buf; + break; + case 2: + pkt->payload = *(uint16_t *)(buf); + break; + case 4: + pkt->payload = *(uint32_t *)(buf); + break; + case 8: + pkt->payload = *(uint64_t *)(buf); + break; + default: + break; + } +} + +static inline void* SpePacketPad(struct SpePacket *pkt, uint8_t *buf) +{ + pkt->type = SpePacketType::SPE_PACKET_PAD; + buf += sizeof(uint8_t); + return buf; +} + +static inline void* SpePacketEnd(struct SpePacket *pkt, uint8_t *buf) +{ + pkt->type = SpePacketType::SPE_PACKET_END; + buf += sizeof(uint8_t); + return buf; +} + +static inline void* SpePacketTs(uint16_t header, struct SpePacket *pkt, uint8_t *buf) +{ + pkt->type = SpePacketType::SPE_PACKET_TIMESTAMP; + buf += sizeof(uint8_t); + pkt->payloadSize = 1 << ((header & 0b110000) >> 4); + SetPktPayload(pkt, buf); + buf += pkt->payloadSize; + return buf; +} + +static uint8_t* Get0B01Pkt(uint16_t header, struct SpePacket *pkt, uint8_t *buf) +{ + if ((header & 0b1111) == 0b0010) { + pkt->type = SpePacketType::SPE_PACKET_EVENTS; + buf += sizeof(uint8_t); + pkt->payloadSize = 1 << ((header & 0b110000) >> 4); + SetPktPayload(pkt, buf); + buf += pkt->payloadSize; + } else if ((header & 0b1111) == 0b0011) { + pkt->type = SpePacketType::SPE_PACKET_DATA_SOURCE; + buf += sizeof(uint8_t); + pkt->payloadSize = 1 << ((header & 0b110000) >> 4); + buf += pkt->payloadSize; + } else if ((header >> 2) == 0b011001) { + pkt->type = SpePacketType::SPE_PACKET_CONTEXT; + buf += sizeof(uint8_t); + pkt->payloadSize = 1 << ((header & 0b110000) >> 4); + SetPktPayload(pkt, buf); + buf += pkt->payloadSize; + } else if ((header >> 2) == 0b010010) { + pkt->type = SpePacketType::SPE_PACKET_OP_TYPE; + buf += sizeof(uint8_t); + buf += sizeof(uint8_t); + } else { + pkt->type = SpePacketType::SPE_PACKET_BAD; + buf += sizeof(uint8_t); + } + + return buf; +} + +static uint8_t *GetPkt(struct SpePacket *pkt, uint8_t *buf) +{ + uint16_t header = *static_cast(buf); + + switch (header) { + case (0): + return static_cast(SpePacketPad(pkt, buf)); + case (1): + return static_cast(SpePacketEnd(pkt, buf)); + case (0b01110001): + return static_cast(SpePacketTs(header, pkt, buf)); + default: + break; + } + + if (!((header >> 6) ^ 0b01)) { + buf = Get0B01Pkt(header, pkt, buf); + } else if ((header >> 3) == 0b10110) { + pkt->type = SpePacketType::SPE_PACKET_ADDRESS; + buf += sizeof(uint8_t); + pkt->payloadSize = 1 << ((header & 0b110000) >> 4); + SetPktPayload(pkt, buf); + buf += pkt->payloadSize; + } else if ((header >> 3) == 0b10011) { + pkt->type = SpePacketType::SPE_PACKET_COUNTER; + buf += sizeof(uint8_t); + buf += sizeof(uint16_t); + } else if ((header >> 10) == 0b001000) { + header = *(uint16_t *)(buf); + if ((header & 0b11111000) == 0b10110000) { + pkt->type = SpePacketType::SPE_PACKET_ADDRESS; + } else if ((header & 0b11111000) == 0b10011000) { + pkt->type = SpePacketType::SPE_PACKET_COUNTER; + } else { + pkt->type = SpePacketType::SPE_PACKET_BAD; + } + buf += sizeof(uint16_t); + pkt->payloadSize = 1 << ((header & 0b110000) >> 4); + SetPktPayload(pkt, buf); + buf += pkt->payloadSize; + } else { + pkt->type = SpePacketType::SPE_PACKET_BAD; + buf += sizeof(uint8_t); + } + + pkt->header = header; + return buf; +} + +static uint64_t FixupTopByte(uint64_t va) +{ + uint64_t fixup = (va & 0xff000000000000) >> 48; + + /* + * Armv8 ARM (ARM DDI 0487F.c), chapter "D10.2.1 Address packet" + * defines the data virtual address payload format, the top byte + * (bits [63:56]) is assigned as top-byte tag; so we only can + * retrieve address value from bits [55:0]. + * + * According to Documentation/arm64/memory.rst, if detects the + * specific pattern in bits [55:52] of payload which falls in + * the kernel space, should fixup the top byte. + * + * For this reason, if detects the bits [55:52] is 0xf, will + * fill 0xff into the top byte. + */ + if ((fixup & 0xf0ULL) == 0xf0ULL) { + va |= 0xffULL << 56; + } + + return va; +} + +static void DecodeAddressPkt(struct SpePacket *pkt, struct SpeRecord *record) +{ + uint16_t index = (pkt->header & 0b111) | (((pkt->header >> 8) & 0b11) << 3); + + switch (index) { + case 0: // PC + record->pc = pkt->payload & 0xffffffffffffff; + record->pc = FixupTopByte(record->pc); + break; + case 1: // Branch target address + break; + case 2: // Data access virtual address + record->va = pkt->payload & 0xffffffffffffff; + record->va = FixupTopByte(record->va); + break; + case 3: // Data access physical address + record->pa = pkt->payload & 0xffffffffffffff; + break; + case 4: // Previous branch target address + break; + default: + break; + } +} + +static void DecodeEventPkt(struct SpePacket *pkt, struct SpeRecord *record) +{ + record->event = pkt->payload; +} + +static void DecodeContextPkt(struct SpePacket *pkt, struct SpeRecord *record) +{ + record->tid = pkt->payload; +} + +static void DecodeTimestampPkt(struct SpePacket *pkt, struct SpeRecord *record) +{ + record->timestamp = pkt->payload; +} + +static void DecodePkt(struct SpePacket *pkt, struct SpeRecord *record) +{ + switch (pkt->type) { + case SpePacketType::SPE_PACKET_ADDRESS: + DecodeAddressPkt(pkt, record); + break; + case SpePacketType::SPE_PACKET_CONTEXT: + DecodeContextPkt(pkt, record); + break; + case SpePacketType::SPE_PACKET_COUNTER: + break; + case SpePacketType::SPE_PACKET_DATA_SOURCE: + break; + case SpePacketType::SPE_PACKET_END: + break; + case SpePacketType::SPE_PACKET_EVENTS: + DecodeEventPkt(pkt, record); + break; + case SpePacketType::SPE_PACKET_OP_TYPE: + break; + case SpePacketType::SPE_PACKET_PAD: + break; + case SpePacketType::SPE_PACKET_TIMESTAMP: + DecodeTimestampPkt(pkt, record); + break; + default: + break; + } +} + +SpeRecord *SpeGetRecord(uint8_t *buf, uint8_t *end, struct SpeRecord *rec, int *remainSize) +{ + struct SpePacket pkt; + + rec->pid = -1; + rec->tid = -1; + while (buf < end) { + if (*remainSize < 1) { + break; + } + + buf = GetPkt(&pkt, buf); + DecodePkt(&pkt, rec); + if (pkt.type == SpePacketType::SPE_PACKET_END || pkt.type == SpePacketType::SPE_PACKET_TIMESTAMP) { + rec++; + *remainSize -= 1; + rec->pid = -1; + rec->tid = -1; + } + } + + return rec; +} diff --git a/pmu/decoder/arm_spe_decoder.h b/pmu/decoder/arm_spe_decoder.h new file mode 100644 index 0000000000000000000000000000000000000000..65d247e39b8d0adbb760c07f02e5694b636d284e --- /dev/null +++ b/pmu/decoder/arm_spe_decoder.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Jin + * Create: 2024-04-03 + * Description: get-spe-data interface and spe-packet definitions + ******************************************************************************/ +#ifndef __SPE__DECODER_HH__ +#define __SPE__DECODER_HH__ + +#include +#include +#include +#include +#include + +enum class SpePacketType { + SPE_PACKET_BAD, + SPE_PACKET_ADDRESS, + SPE_PACKET_CONTEXT, + SPE_PACKET_COUNTER, + SPE_PACKET_DATA_SOURCE, + SPE_PACKET_END, + SPE_PACKET_EVENTS, + SPE_PACKET_OP_TYPE, + SPE_PACKET_PAD, + SPE_PACKET_TIMESTAMP, +}; + +struct SpePacket { + enum SpePacketType type; + uint64_t payload; + uint16_t header; + uint16_t payloadSize; +}; + +struct SpeRecord; + +SpeRecord *SpeGetRecord(uint8_t *buf, uint8_t *end, struct SpeRecord *rec, int *remainSize); + +#endif diff --git a/pmu/evt.cpp b/pmu/evt.cpp new file mode 100644 index 0000000000000000000000000000000000000000..84fdeea2f4bf172eaf58f252e5bd12373e37c2c2 --- /dev/null +++ b/pmu/evt.cpp @@ -0,0 +1,125 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: functions for managing and interacting with performance events using system calls and inline assembly + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include "pcerrc.h" +#include "evt.h" + +enum class HEAD_SIZE { + HEAD_SIZE_ONE = 1, + HEAD_SIZE_TWO = 2, + HEAD_SIZE_FOUR = 4, + HEAD_SIZE_EIGHT = 8, +}; + +static constexpr double CUT_OFF_PERCENT = 0.05; +int KUNPENG_PMU::PerfEventOpen(struct perf_event_attr *attr, pid_t pid, int cpu, int groupFd, unsigned long flags) +{ + return syscall(__NR_perf_event_open, attr, pid, cpu, groupFd, flags); +} + +int KUNPENG_PMU::PerfEvt::Enable() +{ + if (ioctl(this->fd, PERF_EVENT_IOC_ENABLE, 0) == 0) { + return SUCCESS; + } + return LIBPERF_ERR_FAILED_PMU_ENABLE; +} + +int KUNPENG_PMU::PerfEvt::Reset() +{ + if (ioctl(this->fd, PERF_EVENT_IOC_RESET, 0) == 0) { + return SUCCESS; + } + return LIBPERF_ERR_FAILED_PMU_RESET; +} + +int KUNPENG_PMU::PerfEvt::Disable() +{ + if (ioctl(this->fd, PERF_EVENT_IOC_DISABLE, 0)) { + return SUCCESS; + } + return LIBPERF_ERR_FAILED_PMU_DISABLE; +} + +int KUNPENG_PMU::PerfEvt::Close() +{ + close(this->fd); + return SUCCESS; +} + +int KUNPENG_PMU::PerfEvt::BeginRead() +{ + return SUCCESS; +} + +int KUNPENG_PMU::PerfEvt::EndRead() +{ + return SUCCESS; +} + +int KUNPENG_PMU::PerfEvt::Start() +{ + this->Reset(); + return this->Enable(); +} + +int KUNPENG_PMU::PerfEvt::Pause() +{ + return this->Disable(); +} + +__u64 KUNPENG_PMU::ReadOnce(__u64 *head) +{ + union { + typeof(*head) val; + char charHead[1]; + } pointerUnion = {.charHead = {0}}; + + switch (static_cast(sizeof(*head))) { + case HEAD_SIZE::HEAD_SIZE_ONE: + asm volatile("ldarb %w0, %1" + : "=r"(*(__u8 __attribute__((__may_alias__)) *)pointerUnion.charHead) + : "Q"(*head) + : "memory"); + break; + case HEAD_SIZE::HEAD_SIZE_TWO: + asm volatile("ldarh %w0, %1" + : "=r"(*(__u16 __attribute__((__may_alias__)) *)pointerUnion.charHead) + : "Q"(*head) + : "memory"); + break; + case HEAD_SIZE::HEAD_SIZE_FOUR: + asm volatile("ldar %w0, %1" + : "=r"(*(__u32 __attribute__((__may_alias__)) *)pointerUnion.charHead) + : "Q"(*head) + : "memory"); + break; + case HEAD_SIZE::HEAD_SIZE_EIGHT: + asm volatile("ldar %0, %1" + : "=r"(*(__u64 __attribute__((__may_alias__)) *)pointerUnion.charHead) + : "Q"(*head) + : "memory"); + break; + default: + break; + } + return pointerUnion.val; +} diff --git a/pmu/evt.h b/pmu/evt.h new file mode 100644 index 0000000000000000000000000000000000000000..65411366de8c5c9765ed566be73a415dba6ed6eb --- /dev/null +++ b/pmu/evt.h @@ -0,0 +1,67 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: declaration of class PerfEvt and related functions for managing performance events in + * the KUNPENG_PMU namespace + ******************************************************************************/ +#ifndef PMU_EVT_H +#define PMU_EVT_H +#include +#include +#include +#include +#include +#include "symbol.h" +#include "pmu_event.h" + +namespace KUNPENG_PMU { +class PerfEvt { +public: + using ProcPtr = std::shared_ptr; + using ProcMap = std::unordered_map; + + PerfEvt(int cpu, int pid, struct PmuEvt* evt, ProcMap& procMap) : cpu(cpu), pid(pid), evt(evt), procMap(procMap) + {} + ~PerfEvt() + {} + virtual int Start(); + virtual int Pause(); + virtual int Disable(); + virtual int Enable(); + virtual int Reset(); + virtual int Close(); + virtual int BeginRead(); + virtual int EndRead(); + + virtual int Init() = 0; + + virtual int Read(std::vector &data, std::vector &sampleIps) = 0; + + virtual int MapPerfAttr() = 0; + + int GetFd() const + { + return fd; + } + +protected: + __u64 count; + int fd; + int cpu; + pid_t pid; + struct PmuEvt* evt; + ProcMap &procMap; +}; +int PerfEventOpen(struct perf_event_attr* attr, pid_t pid, int cpu, int groupFd, unsigned long flags); +__u64 ReadOnce(__u64 *head); +} // namespace KUNPENG_PMU +#endif diff --git a/pmu/evt_list.cpp b/pmu/evt_list.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5302a140398baba83f3e4d74a341ae3fbde34275 --- /dev/null +++ b/pmu/evt_list.cpp @@ -0,0 +1,211 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: implementations for managing and interacting with performance events in the KUNPENG_PMU namespace + ******************************************************************************/ +#include +#include +#include +#include +#include +#include "cpu_map.h" +#include "linked_list.h" +#include "pmu_event.h" +#include "pcerrc.h" +#include "log.h" +#include "evt_list.h" + +using namespace std; + +enum PmuTask { + START = 0, + PAUSE = 1, + DISABLE = 2, + ENABLE = 3, + RESET = 4, + OPEN = 5, + CLOSE = 6, + INIT = 7, + READ = 8, + STOP = 9, +}; + +int KUNPENG_PMU::EvtList::CollectorDoTask(PerfEvtPtr collector, int task) +{ + switch (task) { + case START: + return collector->Start(); + case PAUSE: + return collector->Pause(); + case DISABLE: + return collector->Disable(); + case ENABLE: + return collector->Enable(); + case RESET: + return collector->Reset(); + case CLOSE: { + auto ret = collector->Close(); + if (ret == SUCCESS) { + fdList.erase(collector->GetFd()); + } + return ret; + } + case INIT: + return collector->Init(); + default: + return UNKNOWN_ERROR; + } +} +int KUNPENG_PMU::EvtList::CollectorXYArrayDoTask(int cpuCnt, int pidCnt, std::vector>& xyArray, int task) +{ + for (int row = 0; row < cpuCnt; row++) { + for (int col = 0; col < pidCnt; col++) { + auto err = CollectorDoTask(xyArray[row][col], task); + if (err != SUCCESS) { + return err; + } + } + } + return SUCCESS; +} + +int KUNPENG_PMU::EvtList::Init() +{ + // Init process map. + for (auto &proc : pidList) { + if (proc->tid > 0) { + procMap[proc->tid] = proc; + } + } + + for (unsigned int row = 0; row < numCpu; row++) { + std::vector evtVec{}; + for (unsigned int col = 0; col < numPid; col++) { + PerfEvtPtr perfEvt = + this->MapPmuAttr(this->cpuList[row]->coreId, this->pidList[col]->tid, this->pmuEvt.get()); + if (perfEvt == nullptr) { + continue; + } + auto err = perfEvt->Init(); + if (err != SUCCESS) { + return err; + } + fdList.insert(perfEvt->GetFd()); + evtVec.emplace_back(perfEvt); + } + this->xyCounterArray.emplace_back(evtVec); + } + return SUCCESS; +} + +int KUNPENG_PMU::EvtList::Start() +{ + return CollectorXYArrayDoTask(this->numCpu, this->numPid, this->xyCounterArray, START); +} + +int KUNPENG_PMU::EvtList::Enable() +{ + return CollectorXYArrayDoTask(this->numCpu, this->numPid, this->xyCounterArray, ENABLE); +} + +int KUNPENG_PMU::EvtList::Stop() +{ + return CollectorXYArrayDoTask(this->numCpu, this->numPid, this->xyCounterArray, STOP); +} + +int KUNPENG_PMU::EvtList::Close() +{ + auto ret = CollectorXYArrayDoTask(this->numCpu, this->numPid, this->xyCounterArray, CLOSE); + if (ret != SUCCESS) { + return ret; + } + + procMap.clear(); + return SUCCESS; +} + +int KUNPENG_PMU::EvtList::Reset() +{ + return CollectorXYArrayDoTask(this->numCpu, this->numPid, this->xyCounterArray, RESET); +} + +void KUNPENG_PMU::EvtList::FillFields( + const size_t &start, const size_t &end, CpuTopology *cpuTopo, ProcTopology *procTopo, vector &data) +{ + for (auto i = start; i < end; ++i) { + data[i].cpuTopo = cpuTopo; + data[i].evt = this->pmuEvt->name.c_str(); + if (data[i].comm == nullptr) { + data[i].comm = procTopo->comm; + } + data[i].ts = this->ts; + } +} + +int KUNPENG_PMU::EvtList::Read(vector &data, std::vector &sampleIps) +{ + for (unsigned int row = 0; row < numCpu; row++) { + for (unsigned int col = 0; col < numPid; col++) { + int err = this->xyCounterArray[row][col]->BeginRead(); + if (err != SUCCESS) { + return err; + } + } + } + + struct PmuEvtData* head = nullptr; + for (unsigned int row = 0; row < numCpu; row++) { + auto cpuTopo = this->cpuList[row].get(); + for (unsigned int col = 0; col < numPid; col++) { + auto cnt = data.size(); + int err = this->xyCounterArray[row][col]->Read(data, sampleIps); + if (err != SUCCESS) { + return err; + } + if (data.size() - cnt) { + DBG_PRINT("evt: %s pid: %d cpu: %d samples num: %d\n", pmuEvt->name.c_str(), pidList[col]->pid, + cpuTopo->coreId, data.size() - cnt); + } + // Fill event name and cpu topology. + FillFields(cnt, data.size(), cpuTopo, pidList[col].get(), data); + } + } + + for (unsigned int row = 0; row < numCpu; row++) { + for (unsigned int col = 0; col < numPid; col++) { + int err = this->xyCounterArray[row][col]->EndRead(); + if (err != SUCCESS) { + return err; + } + } + } + return SUCCESS; +} + +int KUNPENG_PMU::EvtList::Pause() +{ + return CollectorXYArrayDoTask(this->numCpu, this->numPid, this->xyCounterArray, PAUSE); +} + +std::shared_ptr KUNPENG_PMU::EvtList::MapPmuAttr(int cpu, int pid, PmuEvt* pmuEvent) +{ + switch (pmuEvent->collectType) { + case (COUNTING): + return std::make_shared(cpu, pid, pmuEvent, procMap); + case (SAMPLING): + return std::make_shared(cpu, pid, pmuEvent, procMap); + case (SPE_SAMPLING): + return std::make_shared(cpu, pid, pmuEvent, procMap); + default: + return nullptr; + }; +} diff --git a/pmu/evt_list.h b/pmu/evt_list.h new file mode 100644 index 0000000000000000000000000000000000000000..a431bb79ed57e8e38a1c546e71643385afd87fac --- /dev/null +++ b/pmu/evt_list.h @@ -0,0 +1,85 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: declaration of class EvtList with functions for managing and interacting with a list + * of performance events in the KUNPENG_PMU namespace + ******************************************************************************/ +#ifndef PMU_EVTLIST_H +#define PMU_EVTLIST_H +#include +#include +#include +#include +#include +#include "cpu_map.h" +#include "perf_counter.h" +#include "pmu.h" +#include "process_map.h" +#include "sampler.h" +#include "spe_sampler.h" + +namespace KUNPENG_PMU { +class EvtList { +public: + using ProcPtr = std::shared_ptr; + using CpuPtr = std::shared_ptr; + EvtList(std::vector &cpuList, std::vector &pidList, std::shared_ptr pmuEvt) + : cpuList(cpuList), pidList(pidList), pmuEvt(pmuEvt) + { + this->numCpu = this->cpuList.size(); + this->numPid = this->pidList.size(); + } + int Init(); + int Pause(); + int Close(); + int Start(); + int Enable(); + int Stop(); + int Reset(); + int Read(std::vector &pmuData, std::vector &sampleIps); + + void SetTimeStamp(const int64_t ×tamp) + { + this->ts = timestamp; + } + + std::set GetFdList() const + { + return fdList; + } + + int GetEvtType() const + { + return pmuEvt->collectType; + } + +private: + using PerfEvtPtr = std::shared_ptr; + + int CollectorDoTask(PerfEvtPtr collector, int task); + int CollectorXYArrayDoTask(int numCpu, int numPid, std::vector>& xyArray, int task); + void FillFields(const size_t &start, const size_t &end, CpuTopology *cpuTopo, ProcTopology *procTopo, + std::vector &pmuData); + + std::vector cpuList; + std::vector pidList; + std::shared_ptr pmuEvt; + std::vector>> xyCounterArray; + std::shared_ptr MapPmuAttr(int cpu, int pid, PmuEvt* pmuEvent); + unsigned int numCpu = 0; + unsigned int numPid = 0; + std::set fdList; + int64_t ts = 0; + std::unordered_map procMap; +}; +} // namespace KUNPENG_PMU +#endif diff --git a/pmu/perf_counter.cpp b/pmu/perf_counter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6262003f699b7ecca854fb58be7f21ff88d6cb7d --- /dev/null +++ b/pmu/perf_counter.cpp @@ -0,0 +1,112 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Gan + * Create: 2024-04-03 + * Description: implementations for reading performance counters and initializing counting logic in + * the KUNPENG_PMU namespace. + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmu.h" +#include "linked_list.h" +#include "pmu_event.h" +#include "pcerr.h" +#include "log.h" +#include "perf_counter.h" + +using namespace std; + +static constexpr int MAX_ATTR_SIZE = 120; +/** + * Read pmu counter and deal with pmu multiplexing + * Right now we do not implement grouping logic, thus we ignore the + * PERF_FORMAT_ID section for now + */ +int KUNPENG_PMU::PerfCounter::Read(vector &data, std::vector &sampleIps) +{ + struct ReadFormat perfCountValue; + + /** + * If some how the file descriptor is less than 0, + * we make the count to be 0 and return + */ + if (__glibc_unlikely(this->fd < 0)) { + this->count = 0; + return UNKNOWN_ERROR; + } + read(this->fd, &perfCountValue, sizeof(perfCountValue)); + + /** + * In case of multiplexing, we follow the linux documentation for calculating the estimated + * counting value (https://perf.wiki.kernel.org/index.php/Tutorial) + */ + + /** + * For now we assume PMU register was reset before each collection, so we assign the counting value to the + * count section in data. We will implement the aggregating logic soon + */ + this->count = perfCountValue.value * static_cast(perfCountValue.timeEnabled) / + static_cast(perfCountValue.timeRunning); + data.emplace_back(PmuData{0}); + auto& current = data.back(); + current.count = this->count; + current.cpu = static_cast(this->cpu); + current.tid = this->pid; + auto findProc = procMap.find(current.tid); + if (findProc != procMap.end()) { + current.pid = findProc->second->pid; + } + return SUCCESS; +} + +/** + * Initialize counting + */ +int KUNPENG_PMU::PerfCounter::Init() +{ + return this->MapPerfAttr(); +} + +int KUNPENG_PMU::PerfCounter::MapPerfAttr() +{ + /** + * For now, we only implemented the logic for CORE type events. Support for UNCORE PMU events will be + * added soon + */ + struct perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(struct perf_event_attr); + attr.type = this->evt->type; + attr.config = this->evt->config; + + /** + * We want to set the disabled and inherit bit to collect child processes + */ + attr.disabled = 1; + attr.inherit = 1; + + /** + * For now we set the format id bit to implement grouping logic in the future + */ + attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID; + this->fd = PerfEventOpen(&attr, this->pid, this->cpu, -1, 0); + DBG_PRINT("type: %d cpu: %d config: %X\n", attr.type, cpu, attr.config); + if (__glibc_unlikely(this->fd < 0)) { + return MapErrno(errno); + } + return SUCCESS; +} diff --git a/pmu/perf_counter.h b/pmu/perf_counter.h new file mode 100644 index 0000000000000000000000000000000000000000..19628fce937d1869ba4894efccd96367d6c2555d --- /dev/null +++ b/pmu/perf_counter.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Gan + * Create: 2024-04-03 + * Description: declaration of class PerfCounter that inherits from PerfEvt and provides implementations + * for initializing, reading, and mapping performance counter attributes in the KUNPENG_PMU namespace + ******************************************************************************/ +#ifndef PMU_COUNTER_H +#define PMU_COUNTER_H + +#include +#include +#include +#include "evt.h" +#include "pmu_event.h" + +struct ReadFormat { + __u64 value; /* The value of the event */ + __u64 timeEnabled; /* if PERF_FORMAT_TOTAL_timeEnabled */ + __u64 timeRunning; /* if PERF_FORMAT_TOTAL_timeRunning */ + __u64 id; /* if PERF_FORMAT_ID */ +}; + +namespace KUNPENG_PMU { + class PerfCounter : public PerfEvt { + public: + using PerfEvt::PerfEvt; + ~PerfCounter() + {} + int Init() override; + int Read(std::vector &data, std::vector &sampleIps) override; + int MapPerfAttr() override; + }; +} // namespace KUNPENG_PMU +#endif diff --git a/pmu/pfm/CMakeLists.txt b/pmu/pfm/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..6a84de0d787a061403488b2ebdff15679d1e290d --- /dev/null +++ b/pmu/pfm/CMakeLists.txt @@ -0,0 +1,15 @@ +if (POLICY CMP0048) + cmake_policy(SET CMP0048 NEW) +endif (POLICY CMP0048) +project(libpfm) +cmake_minimum_required (VERSION 3.12.0) + +set(PMU_FILE_DIR ${PROJECT_TOP_DIR}/pmu) +set(PFM_FILE_DIR ${PROJECT_TOP_DIR}/pmu/pfm) +file(GLOB PFM_SRC ${PFM_FILE_DIR}/*c ${PFM_FILE_DIR}/*cpp) +include_directories(${PROJECT_TOP_DIR}/include) +include_directories(${PMU_FILE_DIR}/) +include_directories(PFM_FILE_DIR) + +ADD_LIBRARY(pfm SHARED ${PFM_SRC}) +target_compile_options(pfm PRIVATE -fPIC) \ No newline at end of file diff --git a/pmu/pfm/core.cpp b/pmu/pfm/core.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aa7dd54354d26fcdd797017dd1ed07652acc5254 --- /dev/null +++ b/pmu/pfm/core.cpp @@ -0,0 +1,606 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: core event config + ******************************************************************************/ +#include +#include +#include +#include "pmu.h" +#include "core.h" + +const std::unordered_map HIP_A_CORE_PMU_MAP{ + { + KUNPENG_PMU::HIP_A::CORE::BRANCH_MISSES, + { + PERF_TYPE_RAW, + 0x5, + KUNPENG_PMU::HIP_A::CORE::BRANCH_MISSES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::BUS_CYCLES, + { + PERF_TYPE_RAW, + 0x6, + KUNPENG_PMU::HIP_A::CORE::BUS_CYCLES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::CACHE_MISSES, + { + PERF_TYPE_RAW, + 0x3, + KUNPENG_PMU::HIP_A::CORE::CACHE_MISSES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::CACHE_REFERENCES, + { + PERF_TYPE_RAW, + 0x2, + KUNPENG_PMU::HIP_A::CORE::CACHE_REFERENCES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::CPU_CYCLES, + { + PERF_TYPE_RAW, + 0x11, + KUNPENG_PMU::HIP_A::CORE::CPU_CYCLES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::CYCLES, + { + PERF_TYPE_RAW, + 0x11, + KUNPENG_PMU::HIP_A::CORE::CYCLES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::INSTRUCTIONS, + { + PERF_TYPE_RAW, + 0x1, + KUNPENG_PMU::HIP_A::CORE::INSTRUCTIONS + } + }, + { + KUNPENG_PMU::HIP_A::CORE::STALLED_CYCLES_BACKEND, + { + PERF_TYPE_RAW, + 0x8, + KUNPENG_PMU::HIP_A::CORE::STALLED_CYCLES_BACKEND + } + }, + { + KUNPENG_PMU::HIP_A::CORE::STALLED_CYCLES_FRONTEND, + { + PERF_TYPE_RAW, + 0x7, + KUNPENG_PMU::HIP_A::CORE::STALLED_CYCLES_FRONTEND + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1_DCACHE_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10000, + KUNPENG_PMU::HIP_A::CORE::L1_DCACHE_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::IDLE_CYCLES_BACKEND, + { + PERF_TYPE_RAW, + 0x8, + KUNPENG_PMU::HIP_A::CORE::IDLE_CYCLES_BACKEND + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1_ICACHE_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10001, + KUNPENG_PMU::HIP_A::CORE::L1_ICACHE_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::IDLE_CYCLES_FRONTEND, + { + PERF_TYPE_RAW, + 0x7, + KUNPENG_PMU::HIP_A::CORE::IDLE_CYCLES_FRONTEND + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1_ICACHE_LOADS, + { + PERF_TYPE_RAW, + 0x1, + KUNPENG_PMU::HIP_A::CORE::L1_ICACHE_LOADS + } + }, + { + KUNPENG_PMU::HIP_A::CORE::LLC_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10002, + KUNPENG_PMU::HIP_A::CORE::LLC_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::LLC_LOADS, + { + PERF_TYPE_RAW, + 0x2, + KUNPENG_PMU::HIP_A::CORE::LLC_LOADS + } + }, + { + KUNPENG_PMU::HIP_A::CORE::BRANCH_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10005, + KUNPENG_PMU::HIP_A::CORE::BRANCH_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::BRANCH_LOADS, + { + PERF_TYPE_RAW, + 0x5, + KUNPENG_PMU::HIP_A::CORE::BRANCH_LOADS + } + }, + { + KUNPENG_PMU::HIP_A::CORE::DTLB_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10003, + KUNPENG_PMU::HIP_A::CORE::DTLB_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::DTLB_LOADS, + { + PERF_TYPE_RAW, + 0x3, + KUNPENG_PMU::HIP_A::CORE::DTLB_LOADS + } + }, + { + KUNPENG_PMU::HIP_A::CORE::ITLB_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10004, + KUNPENG_PMU::HIP_A::CORE::ITLB_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_A::CORE::ITLB_LOADS, + { + PERF_TYPE_RAW, + 0x4, + KUNPENG_PMU::HIP_A::CORE::ITLB_LOADS + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_RD, + { + PERF_TYPE_RAW, + 0x40, + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_RD + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WR, + { + PERF_TYPE_RAW, + 0x41, + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WR + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_REFILL_RD, + { + PERF_TYPE_RAW, + 0x42, + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_REFILL_RD + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_REFILL_WR, + { + PERF_TYPE_RAW, + 0x43, + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_REFILL_WR + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WB_VICTIM, + { + PERF_TYPE_RAW, + 0x46, + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WB_VICTIM + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WB_CLEAN, + { + PERF_TYPE_RAW, + 0x47, + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WB_CLEAN + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_INVAL, + { + PERF_TYPE_RAW, + 0x48, + KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_INVAL + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_TLB_REFILL_RD, + { + PERF_TYPE_RAW, + 0x4c, + KUNPENG_PMU::HIP_A::CORE::L1D_TLB_REFILL_RD + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_TLB_REFILL_WR, + { + PERF_TYPE_RAW, + 0x4d, + KUNPENG_PMU::HIP_A::CORE::L1D_TLB_REFILL_WR + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_TLB_RD, + { + PERF_TYPE_RAW, + 0x4e, + KUNPENG_PMU::HIP_A::CORE::L1D_TLB_RD + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1D_TLB_WR, + { + PERF_TYPE_RAW, + 0x4f, + KUNPENG_PMU::HIP_A::CORE::L1D_TLB_WR + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_RD, + { + PERF_TYPE_RAW, + 0x50, + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_RD + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WR, + { + PERF_TYPE_RAW, + 0x51, + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WR + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_REFILL_RD, + { + PERF_TYPE_RAW, + 0x52, + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_REFILL_RD + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_REFILL_WR, + { + PERF_TYPE_RAW, + 0x53, + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_REFILL_WR + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WB_VICTIM, + { + PERF_TYPE_RAW, + 0x56, + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WB_VICTIM + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WB_CLEAN, + { + PERF_TYPE_RAW, + 0x57, + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WB_CLEAN + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_INVAL, + { + PERF_TYPE_RAW, + 0x58, + KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_INVAL + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1I_CACHE_PRF, + { + PERF_TYPE_RAW, + 0x102e, + KUNPENG_PMU::HIP_A::CORE::L1I_CACHE_PRF + } + }, + { + KUNPENG_PMU::HIP_A::CORE::L1I_CACHE_PRF_REFILL, + { + PERF_TYPE_RAW, + 0x102f, + KUNPENG_PMU::HIP_A::CORE::L1I_CACHE_PRF_REFILL + } + }, + { + KUNPENG_PMU::HIP_A::CORE::IQ_IS_EMPTY, + { + PERF_TYPE_RAW, + 0x1043, + KUNPENG_PMU::HIP_A::CORE::IQ_IS_EMPTY + } + }, + { + KUNPENG_PMU::HIP_A::CORE::IF_IS_STALL, + { + PERF_TYPE_RAW, + 0x1044, + KUNPENG_PMU::HIP_A::CORE::IF_IS_STALL + } + }, + { + KUNPENG_PMU::HIP_A::CORE::FETCH_BUBBLE, + { + PERF_TYPE_RAW, + 0x2014, + KUNPENG_PMU::HIP_A::CORE::FETCH_BUBBLE + } + }, + { + KUNPENG_PMU::HIP_A::CORE::PRF_REQ, + { + PERF_TYPE_RAW, + 0x6013, + KUNPENG_PMU::HIP_A::CORE::PRF_REQ + } + }, + { + KUNPENG_PMU::HIP_A::CORE::HIT_ON_PRF, + { + PERF_TYPE_RAW, + 0x6014, + KUNPENG_PMU::HIP_A::CORE::HIT_ON_PRF + } + }, + { + KUNPENG_PMU::HIP_A::CORE::EXE_STALL_CYCLE, + { + PERF_TYPE_RAW, + 0x7001, + KUNPENG_PMU::HIP_A::CORE::EXE_STALL_CYCLE + } + }, + { + KUNPENG_PMU::HIP_A::CORE::MEM_STALL_ANYLOAD, + { + PERF_TYPE_RAW, + 0x7004, + KUNPENG_PMU::HIP_A::CORE::MEM_STALL_ANYLOAD + } + }, + { + KUNPENG_PMU::HIP_A::CORE::MEM_STALL_L1MISS, + { + PERF_TYPE_RAW, + 0x7006, + KUNPENG_PMU::HIP_A::CORE::MEM_STALL_L1MISS + } + }, + { + KUNPENG_PMU::HIP_A::CORE::MEM_STALL_L2MISS, + { + PERF_TYPE_RAW, + 0x7007, + KUNPENG_PMU::HIP_A::CORE::MEM_STALL_L2MISS + } + }, +}; + +const std::unordered_map HIP_B_CORE_PMU_MAP{ + { + KUNPENG_PMU::HIP_B::CORE::BRANCH_MISSES, + { + PERF_TYPE_RAW, + 0x5, + KUNPENG_PMU::HIP_B::CORE::BRANCH_MISSES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::CACHE_MISSES, + { + PERF_TYPE_RAW, + 0x3, + KUNPENG_PMU::HIP_B::CORE::CACHE_MISSES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::CACHE_REFERENCES, + { + PERF_TYPE_RAW, + 0x2, + KUNPENG_PMU::HIP_B::CORE::CACHE_REFERENCES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::CPU_CYCLES, + { + PERF_TYPE_RAW, + 0x11, + KUNPENG_PMU::HIP_B::CORE::CPU_CYCLES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::CYCLES, + { + PERF_TYPE_RAW, + 0x11, + KUNPENG_PMU::HIP_B::CORE::CYCLES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::INSTRUCTIONS, + { + PERF_TYPE_RAW, + 0x1, + KUNPENG_PMU::HIP_B::CORE::INSTRUCTIONS + } + }, + { + KUNPENG_PMU::HIP_B::CORE::STALLED_CYCLES_BACKEND, + { + PERF_TYPE_RAW, + 0x8, + KUNPENG_PMU::HIP_B::CORE::STALLED_CYCLES_BACKEND + } + }, + { + KUNPENG_PMU::HIP_B::CORE::STALLED_CYCLES_FRONTEND, + { + PERF_TYPE_RAW, + 0x7, + KUNPENG_PMU::HIP_B::CORE::STALLED_CYCLES_FRONTEND + } + }, + { + KUNPENG_PMU::HIP_B::CORE::L1_DCACHE_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10000, + KUNPENG_PMU::HIP_B::CORE::L1_DCACHE_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::IDLE_CYCLES_BACKEND, + { + PERF_TYPE_RAW, + 0x8, + KUNPENG_PMU::HIP_B::CORE::IDLE_CYCLES_BACKEND + } + }, + { + KUNPENG_PMU::HIP_B::CORE::L1_ICACHE_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10001, + KUNPENG_PMU::HIP_B::CORE::L1_ICACHE_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::IDLE_CYCLES_FRONTEND, + { + PERF_TYPE_RAW, + 0x7, + KUNPENG_PMU::HIP_B::CORE::IDLE_CYCLES_FRONTEND + } + }, + { + KUNPENG_PMU::HIP_B::CORE::L1_ICACHE_LOADS, + { + PERF_TYPE_RAW, + 0x1, + KUNPENG_PMU::HIP_B::CORE::L1_ICACHE_LOADS + } + }, + { + KUNPENG_PMU::HIP_B::CORE::LLC_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10002, + KUNPENG_PMU::HIP_B::CORE::LLC_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::LLC_LOADS, + { + PERF_TYPE_RAW, + 0x2, + KUNPENG_PMU::HIP_B::CORE::LLC_LOADS + } + }, + { + KUNPENG_PMU::HIP_B::CORE::BRANCH_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10005, + KUNPENG_PMU::HIP_B::CORE::BRANCH_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::BRANCH_LOADS, + { + PERF_TYPE_RAW, + 0x5, + KUNPENG_PMU::HIP_B::CORE::BRANCH_LOADS + } + }, + { + KUNPENG_PMU::HIP_B::CORE::DTLB_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10003, + KUNPENG_PMU::HIP_B::CORE::DTLB_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::DTLB_LOADS, + { + PERF_TYPE_RAW, + 0x3, + KUNPENG_PMU::HIP_B::CORE::DTLB_LOADS + } + }, + { + KUNPENG_PMU::HIP_B::CORE::ITLB_LOAD_MISSES, + { + PERF_TYPE_RAW, + 0x10004, + KUNPENG_PMU::HIP_B::CORE::ITLB_LOAD_MISSES + } + }, + { + KUNPENG_PMU::HIP_B::CORE::ITLB_LOADS, + { + PERF_TYPE_RAW, + 0x4, + KUNPENG_PMU::HIP_B::CORE::ITLB_LOADS + } + }, +}; + +const KUNPENG_PMU::CORE_EVT_MAP KUNPENG_PMU::CORE_EVENT_MAP = { + {CHIP_TYPE::HIPA, HIP_A_CORE_PMU_MAP}, + {CHIP_TYPE::HIPB, HIP_B_CORE_PMU_MAP}, +}; \ No newline at end of file diff --git a/pmu/pfm/core.h b/pmu/pfm/core.h new file mode 100644 index 0000000000000000000000000000000000000000..1e0ca229310c91d42d913269cd1325bae964632b --- /dev/null +++ b/pmu/pfm/core.h @@ -0,0 +1,26 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: core event map declaration + ******************************************************************************/ +#ifndef CORE_H +#define CORE_H +#include +#include "pfm_event.h" +#include "pfm_name.h" + +namespace KUNPENG_PMU { + extern const CORE_EVT_MAP CORE_EVENT_MAP; +} + +#endif + diff --git a/pmu/pfm/pfm.cpp b/pmu/pfm/pfm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1f8ea3f820c8b82fde00658b98603627537ff352 --- /dev/null +++ b/pmu/pfm/pfm.cpp @@ -0,0 +1,171 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: event configuration query + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "trace.h" +#include "common.h" +#include "cpu_map.h" +#include "pfm_event.h" +#include "pmu_event.h" +#include "pmu.h" +#include "pcerr.h" +#include "pfm.h" + +#include "uncore.h" +#include "core.h" + +using namespace std; +using namespace pcerr; +using namespace KUNPENG_PMU; + +static constexpr int MAX_STRING_LEN = 2048; +static CHIP_TYPE g_chipType = UNDEFINED_TYPE; + +static struct PmuEvt* ConstructPmuEvtFromCore(KUNPENG_PMU::CoreConfig config, int collectType) +{ + struct PmuEvt* pmuEvtPtr = new PmuEvt; + pmuEvtPtr->config = config.config; + pmuEvtPtr->name = config.eventName; + pmuEvtPtr->type = config.type; + pmuEvtPtr->pmuType = CORE_TYPE; + pmuEvtPtr->collectType = collectType; + pmuEvtPtr->cpumask = -1; + return std::move(pmuEvtPtr); +} + +static struct PmuEvt* GetCoreEvent(const char* pmuName, int collectType) +{ + return KUNPENG_PMU::CORE_EVENT_MAP.at(g_chipType).find(pmuName) != + KUNPENG_PMU::CORE_EVENT_MAP.at(g_chipType).end() + ? ConstructPmuEvtFromCore( + KUNPENG_PMU::CORE_EVENT_MAP.at(g_chipType).at(pmuName), collectType) + : nullptr; +} + +static int GetSpeType(void) +{ + constexpr char* speTypePath = "/sys/devices/arm_spe_0/type"; + FILE *fp = fopen(speTypePath, "r"); + int type; + + if (!fp) { + return -1; + } + + if (fscanf(fp, "%d", &type) != 1) { + if (fclose(fp) == EOF) { + return -1; + } + return -1; + } + + if (fclose(fp) == EOF) { + return -1; + } + return type; +} + +using EvtRetriever = std::function; + +static const std::unordered_map EvtMap{ + {KUNPENG_PMU::CORE_TYPE, GetCoreEvent}, + {KUNPENG_PMU::UNCORE_TYPE, GetUncoreEvent}, + {KUNPENG_PMU::TRACE_TYPE, GetKernelTraceEvent}, +}; + +static int GetEventType(const char *pmuName, string &evtName) +{ + auto coreMap = CORE_EVENT_MAP.at(g_chipType); + auto findCoreEvent = coreMap.find(pmuName); + if (findCoreEvent != coreMap.end()) { + evtName = pmuName; + return CORE_TYPE; + } + std::string strName(pmuName); + + // Kernel trace point event name like 'block:block_bio_complete' + if (strName.find(':') != string::npos) { + evtName = pmuName; + return TRACE_TYPE; + } + + // Parse uncore event name like 'hisi_sccl3_ddrc0/flux_rd/' + auto findSlash = strName.find('/'); + if (findSlash == string::npos) { + return -1; + } + findSlash = strName.find('/', findSlash); + if (findSlash == string::npos) { + return -1; + } + evtName = pmuName; + return UNCORE_TYPE; +} + +struct PmuEvt* PfmGetPmuEvent(const char* pmuName, int collectType) +{ + if (pmuName == nullptr) { + auto* evt = new PmuEvt {0}; + evt->collectType = collectType; + return evt; + } + string evtName; + g_chipType = GetCpuType(); + if (g_chipType == UNDEFINED_TYPE) { + return nullptr; + } + auto type = GetEventType(pmuName, evtName); + if (type == -1) { + return nullptr; + } + struct PmuEvt* evt = (EvtMap.find(type) != EvtMap.end()) ? + EvtMap.at(type)(evtName.c_str(), collectType) : nullptr; + if (evt == nullptr) { + return evt; + } + return evt; +} + +struct PmuEvt* PfmGetSpeEvent( + unsigned long dataFilter, unsigned long eventFilter, unsigned long minLatency, int collectType) +{ + PmuEvt* evt = new PmuEvt{0}; + evt->collectType = collectType; + int type = GetSpeType(); + if (type == -1) { + return nullptr; + } + evt->type = static_cast(type); + evt->config = dataFilter; + evt->config1 = eventFilter; + evt->config2 = minLatency; + evt->cpumask = -1; + + return evt; +} + +void PmuEvtFree(PmuEvt *evt) +{ + if (evt != nullptr) { + delete evt; + } +} diff --git a/pmu/pfm/pfm.h b/pmu/pfm/pfm.h new file mode 100644 index 0000000000000000000000000000000000000000..78bd679d0fda71a0c79f407a5df9faf22f52a26b --- /dev/null +++ b/pmu/pfm/pfm.h @@ -0,0 +1,29 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: event query interface, release interface + ******************************************************************************/ +#ifndef PFM_H +#define PFM_H +#ifdef __cplusplus +extern "C" { +#endif +struct PmuEvt* PfmGetPmuEvent(const char* pmuName, int collectType); +struct PmuEvt* PfmGetSpeEvent( + unsigned long dataFilter, unsigned long eventFilter, unsigned long minLatency, int collectType); +void PmuEvtFree(PmuEvt *evt); +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/pmu/pfm/pfm_event.h b/pmu/pfm/pfm_event.h new file mode 100644 index 0000000000000000000000000000000000000000..b416baf395abc0c771a7678314683abf2a2ef2a3 --- /dev/null +++ b/pmu/pfm/pfm_event.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: event type definitions, declarations, prototypes + ******************************************************************************/ +#ifndef PFM_EVENT_H +#define PFM_EVENT_H +#include +#include +#include +#include "cpu_map.h" +#include "pmu.h" + +namespace KUNPENG_PMU { + enum PMU_TYPE { + CORE_TYPE, + UNCORE_TYPE, + TRACE_TYPE, + }; + + struct UncoreConfig { + __u64 type; + __u64 config; + __u64 config1; + __u64 config2; + enum PMU_TYPE pmuType; + int cpuMask; + const std::string eventName; + const std::string desc; + }; + + struct CoreConfig { + __u64 type; + __u64 config; + const std::string eventName; + const std::string desc; + }; + using UNCORE_EVT_MAP = + std::unordered_map&>; + using CORE_EVT_MAP = + std::unordered_map&>; +} // namespace KUNPENG_PMU +#endif diff --git a/pmu/pfm/pfm_name.cpp b/pmu/pfm/pfm_name.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a587dc4634b8b1831bdc7be0467852a16d63599d --- /dev/null +++ b/pmu/pfm/pfm_name.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: core event name definition + ******************************************************************************/ +#include +#include +#include +#include +#include "pfm_name.h" + +/** + * CORE events for HIP_A + */ +const char* KUNPENG_PMU::HIP_A::CORE::BRANCH_MISSES = "branch-misses"; +const char* KUNPENG_PMU::HIP_A::CORE::BUS_CYCLES = "bus-cycles"; +const char* KUNPENG_PMU::HIP_A::CORE::CACHE_MISSES = "cache-misses"; +const char* KUNPENG_PMU::HIP_A::CORE::CACHE_REFERENCES = "cache-references"; +const char* KUNPENG_PMU::HIP_A::CORE::CPU_CYCLES = "cpu-cycles"; +const char* KUNPENG_PMU::HIP_A::CORE::CYCLES = "cycles"; +const char* KUNPENG_PMU::HIP_A::CORE::INSTRUCTIONS = "instructions"; +const char* KUNPENG_PMU::HIP_A::CORE::STALLED_CYCLES_BACKEND = "stalled-cycles-backend"; +const char* KUNPENG_PMU::HIP_A::CORE::STALLED_CYCLES_FRONTEND = "stalled-cycles-frontend"; +const char* KUNPENG_PMU::HIP_A::CORE::L1_DCACHE_LOAD_MISSES = "l1-dcache-load-misses"; +const char* KUNPENG_PMU::HIP_A::CORE::IDLE_CYCLES_BACKEND = "idle-cycles-backend"; +const char* KUNPENG_PMU::HIP_A::CORE::L1_ICACHE_LOAD_MISSES = "l1-icache-load-misses"; +const char* KUNPENG_PMU::HIP_A::CORE::IDLE_CYCLES_FRONTEND = "idle-cycles-frontend"; +const char* KUNPENG_PMU::HIP_A::CORE::L1_ICACHE_LOADS = "l1-icache-loads"; +const char* KUNPENG_PMU::HIP_A::CORE::LLC_LOAD_MISSES = "llc-load-misses"; +const char* KUNPENG_PMU::HIP_A::CORE::LLC_LOADS = "llc-loads"; +const char* KUNPENG_PMU::HIP_A::CORE::BRANCH_LOAD_MISSES = "branch-load-misses"; +const char* KUNPENG_PMU::HIP_A::CORE::BRANCH_LOADS = "branch-loads"; +const char* KUNPENG_PMU::HIP_A::CORE::DTLB_LOAD_MISSES = "dtlb-load-misses"; +const char* KUNPENG_PMU::HIP_A::CORE::DTLB_LOADS = "dtlb-loads"; +const char* KUNPENG_PMU::HIP_A::CORE::ITLB_LOAD_MISSES = "itlb-load-misses"; +const char* KUNPENG_PMU::HIP_A::CORE::ITLB_LOADS = "itlb-loads"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_RD = "l1d-cache-rd"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WR = "l1d-cache-wr"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_REFILL_RD = "l1d-cache-refill-rd"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_REFILL_WR = "l1d-cache-refill-rd"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WB_VICTIM = "l1d-cache-wb-victim"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_WB_CLEAN = "l1d-cache-wb-clean"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_CACHE_INVAL = "l1d-cache-inval"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_TLB_REFILL_RD = "l1d-tlb-refill-rd"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_TLB_REFILL_WR = "l1d-tlb-refill-wr"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_TLB_RD = "l1d-tlb-rd"; +const char* KUNPENG_PMU::HIP_A::CORE::L1D_TLB_WR = "l1d-tlb-wr"; +const char* KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_RD = "l2d-cache-rd"; +const char* KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WR = "l2d-cache-wr"; +const char* KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_REFILL_RD = "l2d-cache-refill-rd"; +const char* KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_REFILL_WR = "l2d-cache-refill-rd"; +const char* KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WB_VICTIM = "l2d-cache-wb-victim"; +const char* KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_WB_CLEAN = "l2d-cache-wb-clean"; +const char* KUNPENG_PMU::HIP_A::CORE::L2D_CACHE_INVAL = "l2d-cache-inval"; +const char* KUNPENG_PMU::HIP_A::CORE::L1I_CACHE_PRF = "l1i-cache-prf"; +const char* KUNPENG_PMU::HIP_A::CORE::L1I_CACHE_PRF_REFILL = "l1i-cache-prf-refill"; +const char* KUNPENG_PMU::HIP_A::CORE::IQ_IS_EMPTY = "iq-is-empty"; +const char* KUNPENG_PMU::HIP_A::CORE::IF_IS_STALL = "if-is-stall"; +const char* KUNPENG_PMU::HIP_A::CORE::FETCH_BUBBLE = "fetch-bubble"; +const char* KUNPENG_PMU::HIP_A::CORE::PRF_REQ = "prf-req"; +const char* KUNPENG_PMU::HIP_A::CORE::HIT_ON_PRF = "hit-on-prf"; +const char* KUNPENG_PMU::HIP_A::CORE::EXE_STALL_CYCLE = "exe-stall-cycle"; +const char* KUNPENG_PMU::HIP_A::CORE::MEM_STALL_ANYLOAD = "mem-stall-anyload"; +const char* KUNPENG_PMU::HIP_A::CORE::MEM_STALL_L1MISS = "mem-stall-l1miss"; +const char* KUNPENG_PMU::HIP_A::CORE::MEM_STALL_L2MISS = "mem-stall-l2miss"; + +/** + * CORE events for HIP_B + */ +const char* KUNPENG_PMU::HIP_B::CORE::BRANCH_MISSES = "branch-misses"; +const char* KUNPENG_PMU::HIP_B::CORE::CACHE_MISSES = "cache-misses"; +const char* KUNPENG_PMU::HIP_B::CORE::CACHE_REFERENCES = "cache-references"; +const char* KUNPENG_PMU::HIP_B::CORE::CPU_CYCLES = "cpu-cycles"; +const char* KUNPENG_PMU::HIP_B::CORE::CYCLES = "cycles"; +const char* KUNPENG_PMU::HIP_B::CORE::INSTRUCTIONS = "instructions"; +const char* KUNPENG_PMU::HIP_B::CORE::STALLED_CYCLES_BACKEND = "stalled-cycles-backend"; +const char* KUNPENG_PMU::HIP_B::CORE::STALLED_CYCLES_FRONTEND = "stalled-cycles-frontend"; +const char* KUNPENG_PMU::HIP_B::CORE::L1_DCACHE_LOAD_MISSES = "l1-dcache-load-misses"; +const char* KUNPENG_PMU::HIP_B::CORE::IDLE_CYCLES_BACKEND = "idle-cycles-backend"; +const char* KUNPENG_PMU::HIP_B::CORE::L1_ICACHE_LOAD_MISSES = "l1-icache-load-misses"; +const char* KUNPENG_PMU::HIP_B::CORE::IDLE_CYCLES_FRONTEND = "idle-cycles-frontend"; +const char* KUNPENG_PMU::HIP_B::CORE::L1_ICACHE_LOADS = "l1-icache-loads"; +const char* KUNPENG_PMU::HIP_B::CORE::LLC_LOAD_MISSES = "llc-load-misses"; +const char* KUNPENG_PMU::HIP_B::CORE::LLC_LOADS = "llc-loads"; +const char* KUNPENG_PMU::HIP_B::CORE::BRANCH_LOAD_MISSES = "branch-load-misses"; +const char* KUNPENG_PMU::HIP_B::CORE::BRANCH_LOADS = "branch-loads"; +const char* KUNPENG_PMU::HIP_B::CORE::DTLB_LOAD_MISSES = "dtlb-load-misses"; +const char* KUNPENG_PMU::HIP_B::CORE::DTLB_LOADS = "dtlb-loads"; +const char* KUNPENG_PMU::HIP_B::CORE::ITLB_LOAD_MISSES = "itlb-load-misses"; +const char* KUNPENG_PMU::HIP_B::CORE::ITLB_LOADS = "itlb-loads"; \ No newline at end of file diff --git a/pmu/pfm/pfm_name.h b/pmu/pfm/pfm_name.h new file mode 100644 index 0000000000000000000000000000000000000000..dab16b4fe0b1944bf2f58094a7cc9390d44ebccb --- /dev/null +++ b/pmu/pfm/pfm_name.h @@ -0,0 +1,108 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: core event name declination + ******************************************************************************/ +#ifndef PFM_NAME_H +#define PFM_NAME_H +#include +#include +#include +#include + +namespace KUNPENG_PMU { +namespace HIP_A { +namespace CORE { +extern const char* BRANCH_MISSES; +extern const char* BUS_CYCLES; +extern const char* CACHE_MISSES; +extern const char* CACHE_REFERENCES; +extern const char* CPU_CYCLES; +extern const char* CYCLES; +extern const char* INSTRUCTIONS; +extern const char* STALLED_CYCLES_BACKEND; +extern const char* STALLED_CYCLES_FRONTEND; +extern const char* L1_DCACHE_LOAD_MISSES; +extern const char* IDLE_CYCLES_BACKEND; +extern const char* L1_ICACHE_LOAD_MISSES; +extern const char* IDLE_CYCLES_FRONTEND; +extern const char* L1_ICACHE_LOADS; +extern const char* LLC_LOAD_MISSES; +extern const char* LLC_LOADS; +extern const char* BRANCH_LOAD_MISSES; +extern const char* BRANCH_LOADS; +extern const char* DTLB_LOAD_MISSES; +extern const char* DTLB_LOADS; +extern const char* ITLB_LOAD_MISSES; +extern const char* ITLB_LOADS; +extern const char* L1D_CACHE_RD; +extern const char* L1D_CACHE_WR; +extern const char* L1D_CACHE_REFILL_RD; +extern const char* L1D_CACHE_REFILL_WR; +extern const char* L1D_CACHE_WB_VICTIM; +extern const char* L1D_CACHE_WB_CLEAN; +extern const char* L1D_CACHE_INVAL; +extern const char* L1D_TLB_REFILL_RD; +extern const char* L1D_TLB_REFILL_WR; +extern const char* L1D_TLB_RD; +extern const char* L1D_TLB_WR; +extern const char* L2D_CACHE_RD; +extern const char* L2D_CACHE_WR; +extern const char* L2D_CACHE_REFILL_RD; +extern const char* L2D_CACHE_REFILL_WR; +extern const char* L2D_CACHE_WB_VICTIM; +extern const char* L2D_CACHE_WB_CLEAN; +extern const char* L2D_CACHE_INVAL; +extern const char* L1I_CACHE_PRF; +extern const char* L1I_CACHE_PRF_REFILL; +extern const char* IQ_IS_EMPTY; +extern const char* IF_IS_STALL; +extern const char* FETCH_BUBBLE; +extern const char* PRF_REQ; +extern const char* HIT_ON_PRF; +extern const char* EXE_STALL_CYCLE; +extern const char* MEM_STALL_ANYLOAD; +extern const char* MEM_STALL_L1MISS; +extern const char* MEM_STALL_L2MISS; +} // namespace CORE + +} // namespace HIP_A + +namespace HIP_B { +namespace CORE { +extern const char* BRANCH_MISSES; +extern const char* CACHE_MISSES; +extern const char* CACHE_REFERENCES; +extern const char* CPU_CYCLES; +extern const char* CYCLES; +extern const char* INSTRUCTIONS; +extern const char* STALLED_CYCLES_BACKEND; +extern const char* STALLED_CYCLES_FRONTEND; +extern const char* L1_DCACHE_LOAD_MISSES; +extern const char* IDLE_CYCLES_BACKEND; +extern const char* L1_ICACHE_LOAD_MISSES; +extern const char* IDLE_CYCLES_FRONTEND; +extern const char* L1_ICACHE_LOADS; +extern const char* LLC_LOAD_MISSES; +extern const char* LLC_LOADS; +extern const char* BRANCH_LOAD_MISSES; +extern const char* BRANCH_LOADS; +extern const char* DTLB_LOAD_MISSES; +extern const char* DTLB_LOADS; +extern const char* ITLB_LOAD_MISSES; +extern const char* ITLB_LOADS; +} // namespace CORE + +} // namespace HIP_B + +} // namespace KUNPENG_PMU +#endif \ No newline at end of file diff --git a/pmu/pfm/trace.cpp b/pmu/pfm/trace.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9e2c77addb50ee5675a8ba82bcfacc3200c13a97 --- /dev/null +++ b/pmu/pfm/trace.cpp @@ -0,0 +1,57 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: trace point event configuration query + ******************************************************************************/ +#include +#include "common.h" +#include "pmu_event.h" +#include "trace.h" + +using namespace std; +using namespace KUNPENG_PMU; + +static int64_t GetTraceEventConfig(const std::string &name) +{ + size_t colon = name.find(':'); + string systemName = name.substr(0, colon); + string eventName = name.substr(colon + 1); + + string eventPath = "/sys/kernel/tracing/events/" + systemName + "/" + eventName + "/id"; + string realPath = GetRealPath(eventPath); + if (!IsValidPath(realPath)) { + return -1; + } + ifstream typeIn(realPath); + if (!typeIn.is_open()) { + return -1; + } + string typeStr; + typeIn >> typeStr; + + return stoi(typeStr); +} + +struct PmuEvt* GetKernelTraceEvent(const char* pmuName, int collectType) +{ + int64_t config = GetTraceEventConfig(pmuName); + if (config == -1) { + return nullptr; + } + auto* pmuEvtPtr = new PmuEvt; + pmuEvtPtr->config = config; + pmuEvtPtr->name = pmuName; + pmuEvtPtr->type = PERF_TYPE_TRACEPOINT; + pmuEvtPtr->collectType = collectType; + pmuEvtPtr->cpumask = -1; + return pmuEvtPtr; +} diff --git a/pmu/pfm/trace.h b/pmu/pfm/trace.h new file mode 100644 index 0000000000000000000000000000000000000000..55b5fd4c35c30c21f286dcd29844e242deefb4f4 --- /dev/null +++ b/pmu/pfm/trace.h @@ -0,0 +1,20 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: trace point event query interface + ******************************************************************************/ +#ifndef TRACE_H +#define TRACE_H + +struct PmuEvt* GetKernelTraceEvent(const char* pmuName, int collectType); + +#endif diff --git a/pmu/pfm/uncore.cpp b/pmu/pfm/uncore.cpp new file mode 100644 index 0000000000000000000000000000000000000000..16257f32292a0233abe6e36989e015b9d3794e31 --- /dev/null +++ b/pmu/pfm/uncore.cpp @@ -0,0 +1,132 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: uncore event configuration query + ******************************************************************************/ +#include +#include "common.h" +#include "pcerr.h" +#include "pfm_event.h" +#include "pmu_event.h" +#include "uncore.h" + +using namespace std; +using namespace KUNPENG_PMU; + +static int GetDeviceType(const string &devName) +{ + string typePath = "/sys/devices/" + devName + "/type"; + std::string realPath = GetRealPath(typePath); + if (!IsValidPath(realPath)) { + return -1; + } + ifstream typeIn(realPath); + if (!typeIn.is_open()) { + return -1; + } + string typeStr; + typeIn >> typeStr; + + return stoi(typeStr); +} + +static int GetCpuMask(const string &devName) +{ + string maskPath = "/sys/devices/" + devName + "/cpumask"; + std::string realPath = GetRealPath(maskPath); + if (!IsValidPath(realPath)) { + return -1; + } + ifstream maskIn(realPath); + if (!maskIn.is_open()) { + return -1; + } + // Cpumask is a comma-separated list of integers, + // but now make it simple for ddrc event. + string maskStr; + maskIn >> maskStr; + + return stoi(maskStr); +} + +static int64_t GetUncoreEventConfig(const char* pmuName) +{ + int64_t config; + string strName(pmuName); + auto findSlash = strName.find('/'); + string devName = strName.substr(0, findSlash); + string evtName = strName.substr(devName.size() + 1, strName.size() - 1 - (devName.size() + 1)); + string evtPath = "/sys/devices/" + devName + "/events/" + evtName; + std::string realPath = GetRealPath(evtPath); + if (!IsValidPath(realPath)) { + return -1; + } + ifstream evtIn(realPath); + if (!evtIn.is_open()) { + return -1; + } + string configStr; + evtIn >> configStr; + auto findEq = configStr.find("="); + if (findEq == string::npos) { + return -1; + } + auto subStr = configStr.substr(findEq + 1, configStr.size() - findEq); + std::istringstream iss(subStr); + iss >> std::hex >> config; + + return config; +} + +int FillUncoreFields(const char* pmuName, PmuEvt *evt) +{ + string strName(pmuName); + auto findSlash = strName.find('/'); + string devName = strName.substr(0, findSlash); + string evtName = strName.substr(devName.size() + 1, strName.size() - 1 - (devName.size() + 1)); + int devType = GetDeviceType(devName); + if (devType == -1) { + return UNKNOWN_ERROR; + } + evt->type = devType; + int cpuMask = GetCpuMask(devName); + if (cpuMask == -1) { + return UNKNOWN_ERROR; + } + if (GetUncoreEventConfig(pmuName) == -1) { + return LIBPERF_ERR_INVALID_EVENT; + } + + evt->cpumask = cpuMask; + evt->name = pmuName; + return SUCCESS; +} + +struct PmuEvt* GetUncoreEvent(const char* pmuName, int collectType) +{ + int64_t config = GetUncoreEventConfig(pmuName); + if (config == -1) { + return nullptr; + } + auto* pmuEvtPtr = new PmuEvt; + pmuEvtPtr->config = config; + pmuEvtPtr->name = pmuName; + pmuEvtPtr->pmuType = UNCORE_TYPE; + pmuEvtPtr->collectType = collectType; + + // Fill fields for uncore devices. + auto err = FillUncoreFields(pmuName, pmuEvtPtr); + if (err != SUCCESS) { + return nullptr; + } + return pmuEvtPtr; +} \ No newline at end of file diff --git a/pmu/pfm/uncore.h b/pmu/pfm/uncore.h new file mode 100644 index 0000000000000000000000000000000000000000..3599239e47d86e9b64ccaa9dbf4e948ef68fc671 --- /dev/null +++ b/pmu/pfm/uncore.h @@ -0,0 +1,21 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Ye + * Create: 2024-04-03 + * Description: uncore event query interface + ******************************************************************************/ +#ifndef UNCORE_H +#define UNCORE_H +#include "pmu_event.h" + +struct PmuEvt* GetUncoreEvent(const char* pmuName, int collectType); + +#endif \ No newline at end of file diff --git a/pmu/pmu.cpp b/pmu/pmu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a88470f52f4b235ac55f88492dc247fd2093f63d --- /dev/null +++ b/pmu/pmu.cpp @@ -0,0 +1,547 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: implementations for managing performance monitoring tasks, collecting data, + * and handling performance counters in the KUNPENG_PMU namespace + ******************************************************************************/ +#include +#include +#include +#include "pfm.h" +#include "pfm_event.h" +#include "pmu_event.h" +#include "pmu_list.h" +#include "linked_list.h" +#include "pcerr.h" +#include "safe_handler.h" +#include "pmu.h" + +using namespace pcerr; +using namespace KUNPENG_PMU; +using namespace std; + +#define MAX_CPU_NUM sysconf(_SC_NPROCESSORS_ONLN) + +static unordered_map runningStatus; +static SafeHandler pdMutex; + +struct PmuTaskAttr* AssignPmuTaskParam(PmuTaskType collectType, struct PmuAttr *attr); + +static int PmuCollectStart(const int pd) +{ + return KUNPENG_PMU::PmuList::GetInstance()->Start(pd); +} + +static int PmuCollectPause(const int pd) +{ + return KUNPENG_PMU::PmuList::GetInstance()->Pause(pd); +} + +static int CheckCpuList(unsigned numCpu, int* cpuList) +{ + if (numCpu > MAX_CPU_NUM) { + string errMsg = "Invalid numCpu: " + to_string(numCpu); + New(LIBPERF_ERR_INVALID_CPULIST, errMsg); + return LIBPERF_ERR_INVALID_CPULIST; + } + if (numCpu > 0 && cpuList == nullptr) { + New(LIBPERF_ERR_INVALID_CPULIST); + return LIBPERF_ERR_INVALID_CPULIST; + } + for (int i = 0; i < numCpu; i++) { + if (cpuList[i] < 0 || cpuList[i] >= MAX_CPU_NUM) { + string errMsg = "Invalid cpu id: " + to_string(cpuList[i]); + New(LIBPERF_ERR_INVALID_CPULIST, errMsg); + return LIBPERF_ERR_INVALID_CPULIST; + } + } + return SUCCESS; +} + +static int CheckPidList(unsigned numPid, int* pidList) +{ + if (numPid > 0 && pidList == nullptr) { + New(LIBPERF_ERR_INVALID_PIDLIST); + return LIBPERF_ERR_INVALID_PIDLIST; + } + for (int i = 0; i < numPid; i++) { + if (pidList[i] < 0) { + string errMsg = "Invalid pid: " + to_string(pidList[i]); + New(LIBPERF_ERR_INVALID_PIDLIST, errMsg); + return LIBPERF_ERR_INVALID_PIDLIST; + } + } + return SUCCESS; +} + +static int CheckEvtList(unsigned numEvt, char** evtList) +{ + if (numEvt > 0 && evtList == nullptr) { + New(LIBPERF_ERR_INVALID_EVTLIST); + return LIBPERF_ERR_INVALID_EVTLIST; + } + return SUCCESS; +} + +static int CheckAttr(enum PmuTaskType collectType, struct PmuAttr *attr) +{ + auto err = CheckCpuList(attr->numCpu, attr->cpuList); + if (err != SUCCESS) { + return err; + } + err = CheckPidList(attr->numPid, attr->pidList); + if (err != SUCCESS) { + return err; + } + err = CheckEvtList(attr->numEvt, attr->evtList); + if (err != SUCCESS) { + return err; + } + if (collectType < 0 || collectType >= MAX_TASK_TYPE) { + New(LIBPERF_ERR_INVALID_TASK_TYPE); + return LIBPERF_ERR_INVALID_TASK_TYPE; + } + if ((collectType == SAMPLING || collectType == COUNTING) && attr->evtList == nullptr) { + New(LIBPERF_ERR_INVALID_EVTLIST); + return LIBPERF_ERR_INVALID_EVTLIST; + } + + return SUCCESS; +} + +static bool PdValid(const int &pd) +{ + return PmuList::GetInstance()->IsPdAlive(pd); +} + +static void PmuTaskAttrFree(PmuTaskAttr *taskAttr) +{ + auto node = taskAttr; + while (node) { + delete[] node->pidList; + delete[] node->cpuList; + auto current = node; + node = node->next; + current->pmuEvt = nullptr; + free(current); + } +} + +int PmuOpen(enum PmuTaskType collectType, struct PmuAttr *attr) +{ + try { + auto err = CheckAttr(collectType, attr); + if (err != SUCCESS) { + return -1; + } + + auto pTaskAttr = AssignPmuTaskParam(collectType, attr); + if (pTaskAttr == nullptr) { + return -1; + } + unique_ptr taskAttr(pTaskAttr, PmuTaskAttrFree); + + auto pd = KUNPENG_PMU::PmuList::GetInstance()->NewPd(); + if (pd == -1) { + New(LIBPERF_ERR_NO_AVAIL_PD); + return -1; + } + + err = KUNPENG_PMU::PmuList::GetInstance()->Register(pd, taskAttr.get()); + if (err != SUCCESS) { + PmuList::GetInstance()->Close(pd); + pd = -1; + } + New(err); + return pd; + } catch (std::bad_alloc&) { + New(COMMON_ERR_NOMEM); + return -1; + } catch (exception& ex) { + New(UNKNOWN_ERROR, ex.what()); + return -1; + } +} + +int PmuEnable(int pd) +{ + return PmuCollectStart(pd); +} + +int PmuDisable(int pd) +{ + return PmuCollectPause(pd); +} + +int PmuAppendData(struct PmuData *fromData, struct PmuData **toData) +{ + int toLen = 0; + PmuList::GetInstance()->AppendData(fromData, toData, toLen); + return toLen; +} + +static int DoCollectCounting(int pd, int milliseconds) +{ + constexpr int collectInterval = 100; + constexpr int usecPerMilli = 1000; + // Collect every milliseconds, + // and read data from ring buffer. + int remained = milliseconds; + bool unlimited = milliseconds == -1; + PmuCollectStart(pd); + while (remained > 0 || unlimited) { + int interval = collectInterval; + if (!unlimited && remained < collectInterval) { + interval = remained; + } + usleep(usecPerMilli * interval); + + pdMutex.tryLock(pd); + if (!runningStatus[pd]) { + pdMutex.releaseLock(pd); + break; + } + pdMutex.releaseLock(pd); + + remained -= interval; + } + PmuCollectPause(pd); + // Read data from ring buffer and store data to somewhere. + auto err = PmuList::GetInstance()->ReadDataToBuffer(pd); + if (err != SUCCESS) { + New(err); + return err; + } + return SUCCESS; +} + +static int DoCollectNonCounting(int pd, int milliseconds) +{ + constexpr int collectInterval = 100; + constexpr int usecPerMilli = 1000; + // Collect every milliseconds, + // and read data from ring buffer. + int remained = milliseconds; + bool unlimited = milliseconds == -1; + while (remained > 0 || unlimited) { + int interval = collectInterval; + if (!unlimited && remained < collectInterval) { + interval = remained; + } + + PmuCollectStart(pd); + usleep(usecPerMilli * interval); + PmuCollectPause(pd); + + // Read data from ring buffer and store data to somewhere. + auto err = PmuList::GetInstance()->ReadDataToBuffer(pd); + if (err != SUCCESS) { + New(err); + return err; + } + + // Check if all processes exit. + if (PmuList::GetInstance()->AllPmuDead(pd)) { + break; + } + pdMutex.tryLock(pd); + if (!runningStatus[pd]) { + pdMutex.releaseLock(pd); + break; + } + pdMutex.releaseLock(pd); + + remained -= interval; + } + return SUCCESS; +} + +static int DoCollect(int pd, int milliseconds) +{ + if (PmuList::GetInstance()->GetTaskType(pd) == COUNTING) { + return DoCollectCounting(pd, milliseconds); + } + return DoCollectNonCounting(pd, milliseconds); +} + +int PmuCollect(int pd, int milliseconds) +{ + int err = SUCCESS; + string errMsg = ""; + try { + if (!PdValid(pd)) { + New(LIBPERF_ERR_INVALID_PD); + return -1; + } + if (milliseconds != -1 && milliseconds < 0) { + New(LIBPERF_ERR_INVALID_TIME); + return -1; + } + + pdMutex.tryLock(pd); + runningStatus[pd] = true; + pdMutex.releaseLock(pd); + err = DoCollect(pd, milliseconds); + } catch (std::bad_alloc&) { + err = COMMON_ERR_NOMEM; + } catch (exception& ex) { + err = UNKNOWN_ERROR; + errMsg = ex.what(); + } + pdMutex.tryLock(pd); + runningStatus[pd] = false; + pdMutex.releaseLock(pd); + if (!errMsg.empty()) { + New(err, errMsg); + } else { + New(err); + } + if (err != SUCCESS) { + return -1; + } + return err; +} + +static int InnerCollect(int *pds, unsigned len, size_t collectTime, bool &stop) +{ + for (unsigned i = 0; i < len; ++i) { + PmuCollectStart(pds[i]); + } + usleep(collectTime); + for (unsigned i = 0; i < len; ++i) { + PmuCollectPause(pds[i]); + } + + for (unsigned i = 0; i < len; ++i) { + // Read data from ring buffer and store data to somewhere. + auto err = PmuList::GetInstance()->ReadDataToBuffer(pds[i]); + if (err != SUCCESS) { + return err; + } + } + + // Check if all processes exit. + bool allDead = true; + for (unsigned i = 0; i < len; ++i) { + auto taskType = PmuList::GetInstance()->GetTaskType(pds[i]); + if (taskType == COUNTING) { + allDead = false; + break; + } + if (!PmuList::GetInstance()->AllPmuDead(pds[i])) { + allDead = false; + break; + } + } + if (allDead) { + stop = true; + return SUCCESS; + } + + // Check if all processes are stopped. + bool allStopped = true; + for (unsigned i = 0; i < len; ++i) { + pdMutex.tryLock(pds[i]); + if (runningStatus[pds[i]]) { + allStopped = false; + pdMutex.releaseLock(pds[i]); + break; + } + pdMutex.releaseLock(pds[i]); + } + if (allStopped) { + stop = true; + } + + return SUCCESS; +} + +int PmuCollectV(int *pds, unsigned len, int milliseconds) +{ + constexpr int collectInterval = 100; + constexpr int usecPerMilli = 1000; + // Collect every milliseconds, + // and read data from ring buffer. + int remained = milliseconds; + bool unlimited = milliseconds == -1; + for (int i = 0; i < len; ++i) { + pdMutex.tryLock(pds[i]); + runningStatus[pds[i]] = true; + pdMutex.releaseLock(pds[i]); + } + while (remained > 0 || unlimited) { + int interval = collectInterval; + if (!unlimited && remained < collectInterval) { + interval = remained; + } + bool stop = false; + auto err = InnerCollect(pds, len, static_cast(usecPerMilli * interval), stop); + if (err != SUCCESS) { + New(err); + return err; + } + if (stop) { + break; + } + remained -= interval; + } + return SUCCESS; +} + +void PmuStop(int pd) +{ + if (!PdValid(pd)) { + New(LIBPERF_ERR_INVALID_PD); + return; + } + + pdMutex.tryLock(pd); + runningStatus[pd] = false; + pdMutex.releaseLock(pd); + New(SUCCESS); +} + +int PmuRead(int pd, struct PmuData** pmuData) +{ + try { + if (!PdValid(pd)) { + New(LIBPERF_ERR_INVALID_PD); + return LIBPERF_ERR_INVALID_PD; + } + + auto& retData = KUNPENG_PMU::PmuList::GetInstance()->Read(pd); + New(SUCCESS); + if (!retData.empty()) { + *pmuData = retData.data(); + return retData.size(); + } else { + *pmuData = nullptr; + return 0; + } + } catch (std::bad_alloc&) { + New(COMMON_ERR_NOMEM); + return -1; + } catch (exception& ex) { + New(UNKNOWN_ERROR, ex.what()); + return -1; + } +} + +void PmuClose(int pd) +{ + if (!PdValid(pd)) { + New(LIBPERF_ERR_INVALID_PD); + return; + } + try { + KUNPENG_PMU::PmuList::GetInstance()->Close(pd); + New(SUCCESS); + } catch (std::bad_alloc&) { + New(COMMON_ERR_NOMEM); + } catch (exception& ex) { + New(UNKNOWN_ERROR, ex.what()); + } +} + +static struct PmuEvt* GetPmuEvent(const char* pmuName, int collectType) +{ + return PfmGetPmuEvent(pmuName, collectType); +} + +static void PrepareCpuList(PmuAttr *attr, PmuTaskAttr *taskParam, PmuEvt* pmuEvt) +{ + if (pmuEvt->cpumask >= 0) { + taskParam->numCpu = 1; + taskParam->cpuList = new int[1]; + taskParam->cpuList[0] = pmuEvt->cpumask; + } else if (attr->cpuList == nullptr && attr->pidList != nullptr && pmuEvt->collectType == COUNTING) { + // For counting with pid list for system wide, open fd with cpu -1 and specific pid. + taskParam->numCpu = 1; + taskParam->cpuList = new int[taskParam->numCpu]; + taskParam->cpuList[0] = -1; + } else if (attr->cpuList == nullptr) { + // For null cpulist, open fd with cpu 0,1,2...max_cpu + taskParam->numCpu = MAX_CPU_NUM; + taskParam->cpuList = new int[taskParam->numCpu]; + for (int i = 0; i < taskParam->numCpu; i++) { + taskParam->cpuList[i] = i; + } + } else { + taskParam->numCpu = attr->numCpu; + taskParam->cpuList = new int[attr->numCpu]; + for (int i = 0; i < attr->numCpu; i++) { + taskParam->cpuList[i] = attr->cpuList[i]; + } + } +} + +static struct PmuTaskAttr* AssignTaskParam(PmuTaskType collectType, PmuAttr *attr, const char* name) +{ + unique_ptr taskParam(CreateNode(), PmuTaskAttrFree); + /** + * Assign pids to collect + */ + taskParam->numPid = attr->numPid; + taskParam->pidList = new int[attr->numPid]; + for (int i = 0; i < attr->numPid; i++) { + taskParam->pidList[i] = attr->pidList[i]; + } + PmuEvt* pmuEvt = nullptr; + if (collectType == SPE_SAMPLING) { + pmuEvt = PfmGetSpeEvent(attr->dataFilter, attr->evFilter, attr->minLatency, collectType); + if (pmuEvt == nullptr) { + New(LIBPERF_ERR_SPE_UNAVAIL); + return nullptr; + } + } else { + pmuEvt = GetPmuEvent(name, collectType); + if (pmuEvt == nullptr) { + New(LIBPERF_ERR_INVALID_EVENT, "Invalid event: " + string(name)); + return nullptr; + } + } + /** + * Assign cpus to collect + */ + PrepareCpuList(attr, taskParam.get(), pmuEvt); + + taskParam->pmuEvt = shared_ptr(pmuEvt, PmuEvtFree); + taskParam->pmuEvt->useFreq = attr->useFreq; + taskParam->pmuEvt->period = attr->period; + taskParam->pmuEvt->excludeKernel = attr->excludeKernel; + taskParam->pmuEvt->excludeUser = attr->excludeUser; + return taskParam.release(); +} + +struct PmuTaskAttr* AssignPmuTaskParam(enum PmuTaskType collectType, struct PmuAttr *attr) +{ + struct PmuTaskAttr* taskParam = nullptr; + if (collectType == SPE_SAMPLING) { + // evtList is nullptr, cannot loop over evtList. + taskParam = AssignTaskParam(collectType, attr, nullptr); + return taskParam; + } + for (int i = 0; i < attr->numEvt; i++) { + struct PmuTaskAttr* current = AssignTaskParam(collectType, attr, attr->evtList[i]); + if (current == nullptr) { + return nullptr; + } + AddTail(&taskParam, ¤t); + } + return taskParam; +} + +void PmuDataFree(struct PmuData* pmuData) +{ + PmuList::GetInstance()->FreeData(pmuData); + New(SUCCESS); +} diff --git a/pmu/pmu_event.cpp b/pmu/pmu_event.cpp new file mode 100644 index 0000000000000000000000000000000000000000..19f027ea07a6fb9df261a5cce10bc20f93d857d3 --- /dev/null +++ b/pmu/pmu_event.cpp @@ -0,0 +1,37 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: function for mapping system errors to custom error codes in the KUNPENG_PMU namespace + ******************************************************************************/ +#include "pcerrc.h" +#include "pmu_event.h" + +namespace KUNPENG_PMU { + int MapErrno(int sysErr) + { + switch (sysErr) { + case EPERM: + case EACCES: + return LIBPERF_ERR_NO_PERMISSION; + case EBUSY: + return LIBPERF_ERR_DEVICE_BUSY; + case EINVAL: + return LIBPERF_ERR_DEVICE_INVAL; + case ESRCH: + return LIBPERF_ERR_NO_PROC; + case EMFILE: + return LIBPERF_ERR_TOO_MANY_FD; + default: + return UNKNOWN_ERROR; + } + } +} // namespace KUNPENG_PMU \ No newline at end of file diff --git a/pmu/pmu_event.h b/pmu/pmu_event.h new file mode 100644 index 0000000000000000000000000000000000000000..992fa3a3ebad19556a2695ff63d225dde08ea21d --- /dev/null +++ b/pmu/pmu_event.h @@ -0,0 +1,168 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: definition of structures related to performance event sampling and recording in + * the KUNPENG_PMU namespace + ******************************************************************************/ +#ifndef PMU_EVENT_H +#define PMU_EVENT_H +#include +#include +#include +#include +#include +#include +#include "pmu.h" + +#ifndef PMU_SAMPLE_STRUCT_H +#define PMU_SAMPLE_STRUCT_H +#include +#include + +struct PmuEvt { + __u64 type; // pmu event type defined in linux/perf_event.h + __u64 config; // event configuration + __u64 config1; // event filter if applicable + __u64 config2; // further filter if necessary on top of config1 + int pmuType; // if pmu is CORE/UNCORE/SPE and etc (to be implemented) + int collectType; + std::string name; // string name of this pmu event + int cpumask; // a representative CPU number for each socket (package) in the motherboard. + unsigned excludeUser : 1; // don't count user + unsigned excludeKernel : 1; // don't count kernel + union { + unsigned period; // sample period + unsigned freq; // sample frequency + }; + unsigned useFreq : 1; +}; + +namespace KUNPENG_PMU { + +const int PERF_SAMPLE_MAX_SIZE (1 << 16); + +struct PerfRawSample { + __u64 sampleid; + __u64 ip; + __u32 pid, tid; + __u64 time; + __u64 id; + int cpu; + __u64 period; + __u64 nr; + unsigned long ips[]; +}; + +struct PerfTraceSample { + __u64 sampleId; + __u32 tid, pid; + __u64 time; + __u32 cpu; + __u32 size; + char data[]; +}; + +struct sampleId { + __u32 pid, tid; /* if PERF_SAMPLE_TID set */ + __u64 time; /* if PERF_SAMPLE_TIME set */ + __u64 id; /* if PERF_SAMPLE_ID set */ + __u32 cpu, res; /* if PERF_SAMPLE_CPU set */ + __u64 identifier; /* if PERF_SAMPLE_IDENTIFIER set */ +}; + +struct PerfRawMmap { + __u32 pid, tid; + __u64 addr; + __u64 len; + __u64 pgoff; + char filename[]; +}; + +struct PerfRecordMmap { + struct perf_event_header header; + __u32 pid, tid; + __u64 addr; + __u64 len; + __u64 pgoff; + char filename[PATH_MAX]; + struct sampleId sampleId; +}; + +struct PerfRecordMmap2 { + struct perf_event_header header; + __u32 pid, tid; + __u64 addr; + __u64 len; + __u64 pgoff; + __u32 maj; + __u32 min; + __u64 ino; + __u64 ino_generation; + __u32 prot, flags; + char filename[]; +}; + +struct PerfRecordComm { + struct perf_event_header header; + __u32 pid, tid; + char comm[]; +}; + +struct PerfRecordSample { + struct perf_event_header header; + __u64 array[]; +}; + +struct PerfRecordFork { + struct perf_event_header header; + __u32 pid, ppid; + __u32 tid, ptid; + __u64 time; +}; + +struct PerfRecordExit { + struct perf_event_header header; + __u32 pid, ppid; + __u32 tid, ptid; + __u64 time; +}; + +struct PerfMmap { + struct perf_event_mmap_page* base; + __u64 mask; + int fd; + __u64 prev; + __u64 start; + __u64 end; + bool overwrite; + __u64 flush; + char copiedEvent[PERF_SAMPLE_MAX_SIZE]; +}; + +struct PerfSampleIps { + std::vector ips; +}; + +union PerfEvent { + struct perf_event_header header; + struct PerfRecordMmap mmap; + struct PerfRecordComm comm; + struct PerfRecordFork fork; + struct PerfRecordSample sample; + struct PerfRecordExit exit; + struct PerfRecordMmap2 mmap2; +}; + +int MapErrno(int sysErr); +} // namespace KUNPENG_PMU +#endif +#endif diff --git a/pmu/pmu_list.cpp b/pmu/pmu_list.cpp new file mode 100644 index 0000000000000000000000000000000000000000..691ecfb8b2ba42266bda54cde46d38242b4cd6f7 --- /dev/null +++ b/pmu/pmu_list.cpp @@ -0,0 +1,571 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: functions for managing performance monitoring tasks, collecting data, and handling + * performance counters in the KUNPENG_PMU namespace + ******************************************************************************/ +#include +#include +#include "linked_list.h" +#include "cpu_map.h" +#include "process_map.h" +#include "pcerrc.h" +#include "pcerr.h" +#include "util_time.h" +#include "log.h" +#include "pmu_list.h" +#include "common.h" + +using namespace std; +using namespace pcerr; + +namespace KUNPENG_PMU { +// Initializing pmu list singleton instance and global lock + std::mutex PmuList::pmuListMtx; + std::mutex PmuList::dataListMtx; + + int PmuList::CheckRlimit(const unsigned fdNum) + { + return RaiseNumFd(fdNum); + } + + int PmuList::Register(const int pd, PmuTaskAttr* taskParam) + { + if (taskParam->pmuEvt->collectType != COUNTING) { + SymResolverInit(); + SymResolverRecordKernel(); + } + /* Use libpfm to get the basic config for this pmu event */ + struct PmuTaskAttr* pmuTaskAttrHead = taskParam; + // Init collect type for pmu data, + // because different type has different free strategy. + auto &evtData = GetDataList(pd); + if (pmuTaskAttrHead != nullptr) { + evtData.collectType = static_cast(pmuTaskAttrHead->pmuEvt->collectType); + evtData.pd = pd; + } + + unsigned fdNum = 0; + while (pmuTaskAttrHead) { + /** + * Create cpu topology list + */ + std::vector cpuTopoList; + auto err = PrepareCpuTopoList(pd, pmuTaskAttrHead, cpuTopoList); + if (err != SUCCESS) { + return err; + } + + /** + * Create process topology list + */ + std::vector procTopoList; + err = PrepareProcTopoList(pmuTaskAttrHead, procTopoList); + if (err != SUCCESS) { + return err; + } + fdNum += cpuTopoList.size(); + procTopoList.size(); + std::shared_ptr evtList = + std::make_shared(cpuTopoList, procTopoList, pmuTaskAttrHead->pmuEvt); + InsertEvtList(pd, evtList); + pmuTaskAttrHead = pmuTaskAttrHead->next; + } + + for (auto evtList : GetEvtList(pd)) { + auto err = evtList->Init(); + if (err != SUCCESS) { + return err; + } + err = AddToEpollFd(pd, evtList); + if (err != SUCCESS) { + return err; + } + } + + auto err = CheckRlimit(fdNum); + if (err != SUCCESS) { + return err; + } + return SUCCESS; + } + + int PmuList::Start(const int pd) + { + auto pmuList = GetEvtList(pd); + for (auto item : pmuList) { + auto err = item->Start(); + if (err != SUCCESS) { + return err; + } + } + return SUCCESS; + } + + int PmuList::Pause(const int pd) + { + auto pmuList = GetEvtList(pd); + for (auto item : pmuList) { + auto err = item->Pause(); + if (err != SUCCESS) { + return err; + } + } + return SUCCESS; + } + + std::vector& PmuList::Read(const int pd) + { + // Exchange data in to . + // Return a pointer to data. + + auto &evtData = GetDataList(pd); + if (evtData.data.empty()) { + // Have not read ring buffer yet. + // Mostly caller is using PmuEnable and PmuDisable mode. + auto err = ReadDataToBuffer(pd); + if (err != SUCCESS) { + return userDataList[nullptr].data; + } + } + auto& userData = ExchangeToUserData(pd); + if (GetTaskType(pd) == COUNTING) { + // Reset counter after Read is called, + // thus count in PmuData is pmu count in last epoch. + auto evtList = GetEvtList(pd); + for (auto item : evtList) { + item->Reset(); + } + } + return userData; + } + + int PmuList::ReadDataToBuffer(const int pd) + { + // Read data from prev sampling, + // and store data in . + auto &evtData = GetDataList(pd); + auto ts = GetCurrentTime(); + auto eventList = GetEvtList(pd); + for (auto item : eventList) { + item->SetTimeStamp(ts); + auto err = item->Read(evtData.data, evtData.sampleIps); + if (err != SUCCESS) { + return err; + } + } + + return SUCCESS; + } + + int PmuList::AppendData(PmuData *fromData, PmuData **toData, int &len) + { + if (toData == nullptr || fromData == nullptr) { + return LIBPERF_ERR_INVALID_PMU_DATA; + } + + lock_guard lg(dataListMtx); + auto findFromData = userDataList.find(fromData); + if (findFromData == userDataList.end()) { + return LIBPERF_ERR_INVALID_PMU_DATA; + } + + if (*toData == nullptr) { + // For null target data list, copy source list to target. + // A new pointer to data list is created and is assigned to <*toData>. + EventData newData = findFromData->second; + len = newData.data.size(); + auto pData = newData.data.data(); + userDataList[pData] = move(newData); + *toData = pData; + return SUCCESS; + } + + auto findToData = userDataList.find(*toData); + if (findFromData == userDataList.end()) { + return LIBPERF_ERR_INVALID_PMU_DATA; + } + // For non-null target data list, append source list to end of target vector. + auto &dataVec = findToData->second.data; + dataVec.insert(dataVec.end(), findFromData->second.data.begin(), findFromData->second.data.end()); + len = dataVec.size(); + + if (*toData != dataVec.data()) { + // As target vector grows, pointer to list may change. + // Update the pointer and assign to <*toData>. + auto newDataPtr = dataVec.data(); + userDataList[newDataPtr] = move(findToData->second); + userDataList.erase(*toData); + *toData = newDataPtr; + } + return SUCCESS; + } + + void PmuList::Close(const int pd) + { + auto evtList = GetEvtList(pd); + for (auto item : evtList) { + item->Close(); + } + EraseEvtList(pd); + EraseDataList(pd); + RemoveEpollFd(pd); + EraseSpeCpu(pd); + SymResolverDestroy(); + } + + int PmuList::NewPd() + { + lock_guard lg(pmuListMtx); + if (maxPd == std::numeric_limits::max()) { + // Search available pd, by search available key in pmuList. + unsigned availPd = 0; + auto findPd = pmuList.find(availPd); + while (findPd != pmuList.end()) { + ++availPd; + findPd = pmuList.find(availPd); + if (availPd == std::numeric_limits::max()) { + return -1; + } + } + maxPd = availPd; + } else { + maxPd++; + } + + return maxPd; + } + + bool PmuList::AllPmuDead(const int pd) + { + auto epollFd = GetEpollFd(pd); + if (epollFd == -1) { + return true; + } + // Check if all fds are EPOLLHUP, which represents all processes exit. + auto epollEvents = GetEpollEvents(epollFd); + epoll_wait(epollFd, epollEvents.data(), epollEvents.size(), 0); + for (auto& evt : epollEvents) { + if (!(evt.events & EPOLLHUP)) { + return false; + } + } + + return true; + } + + bool PmuList::IsPdAlive(const int pd) const + { + lock_guard lg(pmuListMtx); + return pmuList.find(pd) != pmuList.end(); + } + + void PmuList::FreeData(PmuData* pmuData) + { + EraseUserData(pmuData); + } + + int PmuList::GetTaskType(const int pd) const + { + lock_guard lg(pmuListMtx); + auto findEvtList = pmuList.find(pd); + if (findEvtList == pmuList.end()) { + return -1; + } + if (findEvtList->second.empty()) { + return -1; + } + return findEvtList->second[0]->GetEvtType(); + } + + void PmuList::InsertEvtList(const unsigned pd, std::shared_ptr evtList) + { + lock_guard lg(pmuListMtx); + pmuList[pd].push_back(evtList); + } + + std::vector>& PmuList::GetEvtList(const unsigned pd) + { + lock_guard lg(pmuListMtx); + return pmuList[pd]; + } + + void PmuList::EraseEvtList(const unsigned pd) + { + lock_guard lg(pmuListMtx); + pmuList.erase(pd); + } + + PmuList::EventData& PmuList::GetDataList(const unsigned pd) + { + lock_guard lg(dataListMtx); + return dataList[pd]; + } + + void PmuList::EraseDataList(const unsigned pd) + { + lock_guard lg(dataListMtx); + dataList.erase(pd); + for (auto iter = userDataList.begin();iter != userDataList.end();) { + if (iter->second.pd == pd) { + iter = userDataList.erase(iter); + } else { + ++iter; + } + } + } + + void PmuList::FillStackInfo(EventData &eventData) + { + // Parse dwarf and elf info of each pid and get stack trace for each pmu data. + for (size_t i = 0; i < eventData.data.size(); ++i) { + auto &pmuData = eventData.data[i]; + auto &ipsData = eventData.sampleIps[i]; + if (eventData.collectType == SPE_SAMPLING) { + SymResolverRecordModuleNoDwarf(pmuData.pid); + } else { + SymResolverRecordModule(pmuData.pid); + } + if (pmuData.stack == nullptr) { + pmuData.stack = StackToHash(pmuData.pid, ipsData.ips.data(), ipsData.ips.size()); + } + } + } + + void PmuList::AggregateData(const std::vector& evData, std::vector& newEvData) + { + // Acccumulate stat data in previous PmuCollect for convenient use. + // One count for same event + tid + cpu. + map, PmuData> mergedMap; + for (auto& data : evData) { + auto key = std::make_tuple( + data.evt, data.tid, data.cpu); + if (mergedMap.find(key) == mergedMap.end()) { + mergedMap[key] = data; + } else { + mergedMap[key].count += data.count; + } + } + for (auto &evtData: mergedMap) { + newEvData.push_back(evtData.second); + } + } + + std::vector& PmuList::ExchangeToUserData(const unsigned pd) + { + lock_guard lg(dataListMtx); + if (dataList.count(pd) == 0) { + return GetPreviousData(pd); + } + + auto& evData = dataList[pd]; + auto pData = evData.data.data(); + if (GetTaskType(pd) == COUNTING) { + std::vector newPmuData; + AggregateData(evData.data, newPmuData); + EventData newEvData = { + .pd = pd, + .collectType = COUNTING, + .data = newPmuData, + }; + + auto inserted = userDataList.emplace(newEvData.data.data(), move(newEvData)); + dataList.erase(pd); + return inserted.first->second.data; + } else { + auto inserted = userDataList.emplace(pData, move(evData)); + dataList.erase(pd); + FillStackInfo(inserted.first->second); + return inserted.first->second.data; + } + } + + void PmuList::EraseUserData(PmuData* pmuData) + { + lock_guard lg(dataListMtx); + auto findData = userDataList.find(pmuData); + if (findData == userDataList.end()) { + return; + } + userDataList.erase(pmuData); + } + + int PmuList::GetHistoryData(const int pd, std::vector& aggregatedData) + { + lock_guard lg(dataListMtx); + std::vector mergedData; + for (const auto& pair : userDataList) { + if (pair.second.pd == pd && pair.second.collectType == COUNTING) { + mergedData.insert(mergedData.end(), pair.second.data.begin(), pair.second.data.end()); + } + } + AggregateData(mergedData, aggregatedData); + return aggregatedData.size(); + } + + std::vector& PmuList::GetPreviousData(const unsigned pd) + { + std::vector* lastData = nullptr; + int64_t maxTs = 0; + + for (auto& pair : userDataList) { + if (pair.second.pd == pd && !pair.second.data.empty() && pair.second.data[0].ts > maxTs) { + maxTs = pair.second.data[0].ts; + lastData = &pair.second.data; + } + } + if (lastData != nullptr) { + return *lastData; + } + throw runtime_error(""); + } + + int PmuList::AddToEpollFd(const int pd, const std::shared_ptr &evtList) + { + lock_guard lg(pmuListMtx); + // Try to create a epoll fd for current pd. + int epollFd = 0; + auto findFd = epollList.find(pd); + if (findFd == epollList.end()) { + epollFd = epoll_create1(0); + if (epollFd < 0) { + return LIBPERF_ERR_FAIL_LISTEN_PROC; + } + epollList[pd] = epollFd; + } else { + epollFd = findFd->second; + } + + // Add ring buffer fd list to epoll fd. + auto& epollEvtList = epollEvents[epollFd]; + for (auto fd : evtList->GetFdList()) { + epollEvtList.emplace_back(epoll_event{0}); + auto& epollEvt = epollEvtList.back(); + epollEvt.events = EPOLLIN | EPOLLRDHUP; + epollEvt.data.fd = fd; + auto ret = epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &epollEvt); + if (ret != 0) { + return LIBPERF_ERR_FAIL_LISTEN_PROC; + } + } + + return SUCCESS; + } + + void PmuList::RemoveEpollFd(const int pd) + { + lock_guard lg(pmuListMtx); + auto findFd = epollList.find(pd); + if (findFd != epollList.end()) { + close(findFd->second); + epollEvents.erase(findFd->second); + epollList.erase(pd); + } + } + + int PmuList::GetEpollFd(const int pd) + { + lock_guard lg(pmuListMtx); + auto findFd = epollList.find(pd); + if (findFd != epollList.end()) { + return findFd->second; + } + return -1; + } + + std::vector& PmuList::GetEpollEvents(const int epollFd) + { + lock_guard lg(pmuListMtx); + auto findEvts = epollEvents.find(epollFd); + if (findEvts != epollEvents.end()) { + return findEvts->second; + } + + // Cannot reach here. + throw runtime_error("cannot find epoll events."); + } + + bool PmuList::IsCpuInList(const int &cpu) const + { + lock_guard lg(pmuListMtx); + for (auto cpuList: speCpuList) { + if (cpuList.second.find(cpu) != cpuList.second.end()) { + return true; + } + } + return false; + } + + void PmuList::AddSpeCpu(const unsigned &pd, const int &cpu) + { + lock_guard lg(pmuListMtx); + speCpuList[pd].insert(cpu); + } + + void PmuList::EraseSpeCpu(const unsigned &pd) + { + lock_guard lg(pmuListMtx); + speCpuList.erase(pd); + } + + int PmuList::PrepareCpuTopoList( + const unsigned &pd, PmuTaskAttr* pmuTaskAttrHead, std::vector& cpuTopoList) + { + for (int i = 0; i < pmuTaskAttrHead->numCpu; i++) { + if (pmuTaskAttrHead->pmuEvt->collectType == SPE_SAMPLING && IsCpuInList(pmuTaskAttrHead->cpuList[i])) { + // For SPE sampling, one core can only be used by one pd. + // Therefore, check if core is in sampling. + return LIBPERF_ERR_DEVICE_BUSY; + } + struct CpuTopology* cpuTopo = GetCpuTopology(pmuTaskAttrHead->cpuList[i]); + if (cpuTopo == nullptr) { + New(LIBPERF_ERR_FAIL_GET_CPU); + return LIBPERF_ERR_FAIL_GET_CPU; + } + if (pmuTaskAttrHead->pmuEvt->collectType == SPE_SAMPLING) { + AddSpeCpu(pd, pmuTaskAttrHead->cpuList[i]); + } + cpuTopoList.emplace_back(shared_ptr(cpuTopo)); + } + return SUCCESS; + } + + int PmuList::PrepareProcTopoList(PmuTaskAttr* pmuTaskAttrHead, std::vector& procTopoList) const + { + if (pmuTaskAttrHead->numPid == 0) { + struct ProcTopology* procTopo = GetProcTopology(-1); + if (procTopo == nullptr) { + New(LIBPERF_ERR_FAIL_GET_PROC); + return LIBPERF_ERR_FAIL_GET_PROC; + } + procTopoList.emplace_back(unique_ptr(procTopo, FreeProcTopo)); + } + for (int i = 0; i < pmuTaskAttrHead->numPid; i++) { + int numChild = 0; + int* childTidList = GetChildTid(pmuTaskAttrHead->pidList[i], &numChild); + if (childTidList == nullptr) { + return LIBPERF_ERR_INVALID_PID; + } + for (int j = 0; j < numChild; j++) { + struct ProcTopology* procTopo = GetProcTopology(childTidList[j]); + if (procTopo == nullptr) { + New(LIBPERF_ERR_FAIL_GET_PROC); + return LIBPERF_ERR_FAIL_GET_PROC; + } + DBG_PRINT("Add to proc map: %d\n", childTidList[j]); + procTopoList.emplace_back(shared_ptr(procTopo, FreeProcTopo)); + } + delete[] childTidList; + } + return SUCCESS; + } + +} \ No newline at end of file diff --git a/pmu/pmu_list.h b/pmu/pmu_list.h new file mode 100644 index 0000000000000000000000000000000000000000..67b8bd23231101b973d84f779bd13e10b75c8436 --- /dev/null +++ b/pmu/pmu_list.h @@ -0,0 +1,138 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: definition of singleton class PmuList for managing performance monitoring tasks, + * collecting data, and handling performance counters in the KUNPENG_PMU namespace + ******************************************************************************/ +#ifndef PMU_LIST_H +#define PMU_LIST_H +#include +#include +#include +#include +#include "evt_list.h" +#include "pmu_event.h" + +namespace KUNPENG_PMU { + +struct PmuTaskAttr { + int numCpu; // number of cpu to be collected + int* cpuList; // list of core ids to be collected + // list length has to be as the same as numCpu + int numPid; // number of (parent) processes to be collected + int* pidList; // list of pids(tids) to be collected + // list length has to be as the same as numPid + std::shared_ptr pmuEvt; // which pmu to be collected + struct PmuTaskAttr* next; // next task attribute +}; + +class PmuList { +public: + static PmuList* GetInstance() + { + static PmuList instance; + return &instance; + } + int Register(const int pd, PmuTaskAttr* taskParam); + /** + * @brief Read all pmu data of event list, and store data to internal buffer. + * @param pd + */ + int ReadDataToBuffer(const int pd); + /** + * @brief Read pmu data from internal buffer and return ref. + * @param pd + * @return std::vector& + */ + std::vector& Read(const int pd); + int AppendData(PmuData *fromData, PmuData **toData, int &len); + int Start(const int pd); + int Pause(const int pd); + void Close(const int pd); + bool AllPmuDead(const int pd); + bool IsPdAlive(const int pd) const; + void FreeData(PmuData* pmuData); + int GetTaskType(const int pd) const; + + int NewPd(); + + int GetHistoryData(const int pd, std::vector& pmuData); + +private: + using ProcPtr = std::shared_ptr; + using CpuPtr = std::shared_ptr; + PmuList() + {} + PmuList(const PmuList&) = delete; + PmuList& operator=(const PmuList&) = delete; + ~PmuList() = default; + + struct EventData { + unsigned pd; + PmuTaskType collectType; + std::vector data; + std::vector sampleIps; + }; + + void InsertEvtList(const unsigned pd, std::shared_ptr evtList); + std::vector>& GetEvtList(const unsigned pd); + void EraseEvtList(const unsigned pd); + + EventData& GetDataList(const unsigned pd); + void EraseDataList(const unsigned pd); + // Move pmu data from dataList to userDataList, + // and return ref of dataList in userDataList. + std::vector& ExchangeToUserData(const unsigned pd); + void FillStackInfo(EventData &eventData); + void EraseUserData(PmuData* pmuData); + + int AddToEpollFd(const int pd, const std::shared_ptr &evtList); + void RemoveEpollFd(const int pd); + int GetEpollFd(const int pd); + std::vector& GetEpollEvents(const int epollFd); + + bool IsCpuInList(const int &cpu) const; + void AddSpeCpu(const unsigned &pd, const int &cpu); + void EraseSpeCpu(const unsigned &pd); + int PrepareCpuTopoList( + const unsigned& pd, PmuTaskAttr* pmuTaskAttrHead, std::vector& cpuTopoList); + int PrepareProcTopoList(PmuTaskAttr* pmuTaskAttrHead, std::vector& procTopoList) const; + int CheckRlimit(const unsigned fdNum); + static void AggregateData(const std::vector& evData, std::vector& newEvData); + std::vector& GetPreviousData(const unsigned pd); + + static std::mutex pmuListMtx; + static std::mutex dataListMtx; + std::unordered_map>> pmuList; + // Key: pd + // Value: PmuData List. + // PmuData is stored here before user call . + std::unordered_map dataList; + // Key: PmuData raw pointer + // Value: PmuData vector for raw pointer. + // PmuData is stored here after user call . + std::unordered_map userDataList; + + // Key: pd + // Value: epoll fd + std::unordered_map epollList; + // Key: epoll fd + // Value: epoll event list + std::unordered_map> epollEvents; + + // Key: pd + // Value: spe sampling cpu list. + std::unordered_map> speCpuList; + unsigned maxPd = 0; +}; +} // namespace KUNPENG_PMU +#endif diff --git a/pmu/sample_process.cpp b/pmu/sample_process.cpp new file mode 100644 index 0000000000000000000000000000000000000000..79a20e76d489c934aa623c50375e55548fa34ca0 --- /dev/null +++ b/pmu/sample_process.cpp @@ -0,0 +1,160 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Zhang + * Create: 2024-04-03 + * Description: functions for reading and managing data from a ring buffer in the KUNPENG_PMU namespace + ******************************************************************************/ +#include +#include +#include +#include "pcerrc.h" +#include "evt.h" +#include "sample_process.h" + +#define PAGE_SIZE (sysconf(_SC_PAGESIZE)) +#define MB() asm volatile("dmb ish" ::: "memory") +static constexpr int MAX_DATA_SIZE = 8192; +#define PerfRingbufferSmpStoreRelease(p, v) \ + ({ \ + union { \ + typeof(*p) val; \ + char charHead[1]; \ + } pointerUnion = {.val = (v)}; \ + asm volatile("stlr %1, %0" : "=Q"(*p) : "r"(*(__u64 *)pointerUnion.charHead) : "memory"); \ + }) + +void KUNPENG_PMU::PerfMmapConsume(PerfMmap &map) +{ + __u64 prev = map.prev; + struct perf_event_mmap_page *base = (struct perf_event_mmap_page *)map.base; + PerfRingbufferSmpStoreRelease(&base->data_tail, prev); +} + +void KUNPENG_PMU::PerfMmapReadDone(PerfMmap &map) +{ + struct perf_event_mmap_page *base = (struct perf_event_mmap_page *)map.base; + map.prev = ReadOnce(&base->data_head); +} + +int KUNPENG_PMU::MmapInit(PerfMmap &sampleMmap) +{ + struct perf_event_mmap_page *base = (struct perf_event_mmap_page *)sampleMmap.base; + __u64 head = ReadOnce(&base->data_head); + __u64 prev = sampleMmap.prev; + unsigned long size; + + sampleMmap.start = sampleMmap.overwrite ? head : prev; + sampleMmap.end = sampleMmap.overwrite ? prev : head; + + if (__glibc_unlikely((sampleMmap.end - sampleMmap.start) < sampleMmap.flush)) { + return -EAGAIN; + } + + size = sampleMmap.end - sampleMmap.start; + if (size > (unsigned long)(sampleMmap.mask) + 1) { + if (!sampleMmap.overwrite) { + sampleMmap.prev = head; + PerfMmapConsume(sampleMmap); + } + } + return 0; +} + +void CopyDataInWhileLoop(KUNPENG_PMU::PerfMmap& map, __u64 offset, unsigned char* data, __u64 len) +{ + /* + * Here, as long as we still have data inside the mmap, we continue to wrap around + * the head and tail pointer to a point that + */ + char *tmpDataPtr = map.copiedEvent; + __u64 copiedData = 0; + while (len) { + __u64 restSize = offset & map.mask; + copiedData = map.mask + 1 - restSize < len ? map.mask + 1 - restSize : len; + + memcpy(tmpDataPtr, &data[restSize], copiedData); + + offset += copiedData; + tmpDataPtr += copiedData; + len -= copiedData; + } +} + +static inline union KUNPENG_PMU::PerfEvent *PerfMmapRead(KUNPENG_PMU::PerfMmap &map, __u64 *startPointer, __u64 end) +{ + /* + * Logic for reading ringbuffer + */ + unsigned char *data = (unsigned char *)map.base + PAGE_SIZE; + union KUNPENG_PMU::PerfEvent *event = nullptr; + __u64 diff = end - *startPointer; + if (diff >= sizeof(event->header)) { + size_t size; + + event = (union KUNPENG_PMU::PerfEvent *)&data[*startPointer & map.mask]; + size = event->header.size; + + if (__glibc_unlikely(size < sizeof(event->header) || diff < size)) { + return nullptr; + } + + size_t event_size = sizeof(*event); + if ((*startPointer & map.mask) + size != ((*startPointer + size) & map.mask)) { + __u64 offset = *startPointer; + __u64 len = event_size < size ? event_size : size; + CopyDataInWhileLoop(map, offset, data, len); + event = (union KUNPENG_PMU::PerfEvent *)map.copiedEvent; + } + + *startPointer += size; + } + + return event; +} + +union KUNPENG_PMU::PerfEvent *KUNPENG_PMU::ReadEvent(PerfMmap &map) +{ + if (!map.overwrite) { + struct perf_event_mmap_page *base = (struct perf_event_mmap_page *)map.base; + map.end = ReadOnce(&base->data_head); + } + + union KUNPENG_PMU::PerfEvent *event = PerfMmapRead(map, &map.start, map.end); + + if (!map.overwrite) { + map.prev = map.start; + } + + return event; +} + +int KUNPENG_PMU::RingbufferReadInit(PerfMmap &map) +{ + __u64 head = ReadOnce(&map.base->data_head); + __u64 prev = map.prev; + unsigned long size; + + map.start = map.overwrite ? head : prev; + map.end = map.overwrite ? prev : head; + + if (__glibc_unlikely((map.end - map.start) < map.flush)) { + return UNKNOWN_ERROR; + } + + size = map.end - map.start; + if (size > (unsigned long)(map.mask) + 1) { + if (!map.overwrite) { + map.prev = head; + PerfMmapConsume(map); + } + } + return SUCCESS; +} diff --git a/pmu/sample_process.h b/pmu/sample_process.h new file mode 100644 index 0000000000000000000000000000000000000000..0878845d2c6fa8873a78a543510296dd13403d34 --- /dev/null +++ b/pmu/sample_process.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Gan + * Create: 2024-04-03 + * Description: definition of functions for handling performance event sampling processes in + * the KUNPENG_PMU namespace + ******************************************************************************/ +#ifndef PMU_SAMPLE_PROCESS_H +#define PMU_SAMPLE_PROCESS_H +#include +#include "pmu_event.h" + +namespace KUNPENG_PMU { + + int MmapInit(PerfMmap& sampleMmap); + union PerfEvent* ReadEvent(PerfMmap& map); + int RingbufferReadInit(PerfMmap& map); + void PerfMmapConsume(PerfMmap& map); + void PerfMmapReadDone(PerfMmap& map); + int CreateRingbuffer(PerfMmap& map, int mask, int prot, int fd); + +} // namespace KUNPENG_PMU + +#endif diff --git a/pmu/sampler.cpp b/pmu/sampler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bbaa9914380955b7cc15a2eb9ac52cfc8267d92c --- /dev/null +++ b/pmu/sampler.cpp @@ -0,0 +1,220 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Gan + * Create: 2024-04-03 + * Description: implementations for sampling and processing performance data using ring buffers in + * the KUNPENG_PMU namespace + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linked_list.h" +#include "symbol_resolve.h" +#include "util_time.h" +#include "sample_process.h" +#include "pcerrc.h" +#include "process_map.h" +#include "log.h" +#include "sampler.h" + +using namespace std; + +static constexpr int PAGE_SIZE = 4096; +static constexpr int MAX_ATTR_SIZE = 120; +int KUNPENG_PMU::PerfSampler::pages = 128; + +template +static inline bool IsPowerOfTwo(T x) +{ + return (x) != 0 && (((x) & ((x) - 1)) == 0); +} + +int KUNPENG_PMU::PerfSampler::MapPerfAttr() +{ + struct perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = this->evt->type; + 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; + attr.freq = this->evt->useFreq; + attr.sample_period = this->evt->period; + attr.read_format = PERF_FORMAT_ID; + attr.exclude_kernel = this->evt->excludeKernel; + attr.exclude_user = this->evt->excludeUser; + attr.pinned = 1; + attr.disabled = 1; + attr.inherit = 1; + attr.mmap = 1; + attr.comm = 1; + attr.mmap2 = 1; + attr.task = 1; + attr.sample_id_all = 1; + attr.exclude_guest = 1; + + this->fd = PerfEventOpen(&attr, this->pid, this->cpu, -1, 0); + DBG_PRINT("pid: %d type: %d cpu: %d config: %X\n", this->pid, attr.type, cpu, attr.config); + if (__glibc_unlikely(this->fd < 0)) { + return MapErrno(errno); + } + return SUCCESS; +} + +union KUNPENG_PMU::PerfEvent *KUNPENG_PMU::PerfSampler::SampleReadEvent() +{ + return ReadEvent(*this->sampleMmap); +} + +int KUNPENG_PMU::PerfSampler::Mmap() +{ + int mmapLen = (this->pages + 1) * PAGE_SIZE; + auto mask = mmapLen - PAGE_SIZE - 1; + if (mask < 0) { + return UNKNOWN_ERROR; + } + + this->sampleMmap->prev = 0; + this->sampleMmap->mask = static_cast<__u64>(mask); + void *currentMap = + mmap(NULL, this->sampleMmap->mask + 1 + PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (__glibc_unlikely(currentMap == MAP_FAILED)) { + this->sampleMmap->base = nullptr; + return UNKNOWN_ERROR; + } + this->sampleMmap->base = static_cast(currentMap); + this->sampleMmap->fd = fd; + return SUCCESS; +} + +int KUNPENG_PMU::PerfSampler::ReadInit() +{ + if (!this->sampleMmap->base) { + return UNKNOWN_ERROR; + } + return RingbufferReadInit(*this->sampleMmap.get()); +} + +void KUNPENG_PMU::PerfSampler::UpdatePidInfo(const pid_t &pid, const int &tid) +{ + auto findProc = procMap.find(tid); + if (findProc == procMap.end()) { + auto procTopo = GetProcTopology(tid); + if (procTopo != nullptr) { + DBG_PRINT("Add to proc map: %d\n", tid); + procMap[tid] = shared_ptr(procTopo, FreeProcTopo); + } + } +} + +void KUNPENG_PMU::PerfSampler::RawSampleProcess( + struct PmuData *current, PerfSampleIps *ips, union KUNPENG_PMU::PerfEvent *event) +{ + if (current == nullptr) { + return; + } + KUNPENG_PMU::PerfRawSample *sample = (KUNPENG_PMU::PerfRawSample *)event->sample.array; + // Copy ips from ring buffer and get stack info later. + for (__u64 i = 0; i < sample->nr; ++i) { + ips->ips.push_back(sample->ips[i]); + } + current->cpu = static_cast(sample->cpu); + current->pid = static_cast(sample->pid); + current->tid = static_cast(sample->tid); + current->period = static_cast(sample->period); +} + +void KUNPENG_PMU::PerfSampler::ReadRingBuffer(vector &data, vector &sampleIps) +{ + union KUNPENG_PMU::PerfEvent *event; + while (true) { + event = this->SampleReadEvent(); + if (__glibc_unlikely(event == nullptr)) { + break; + } + __u32 sampleType = event->header.type; + switch (sampleType) { + case PERF_RECORD_SAMPLE: { + data.emplace_back(PmuData{0}); + auto& current = data.back(); + sampleIps.emplace_back(PerfSampleIps()); + auto& ips = sampleIps.back(); + this->RawSampleProcess(¤t, &ips, event); + break; + } + case PERF_RECORD_MMAP: { + SymResolverUpdateModule(event->mmap.tid, event->mmap.filename, event->mmap.addr); + break; + } + case PERF_RECORD_MMAP2: { + SymResolverUpdateModule(event->mmap2.tid, event->mmap2.filename, event->mmap2.addr); + break; + } + case PERF_RECORD_FORK: { + DBG_PRINT("Fork ptid: %d tid: %d\n", event->fork.pid, event->fork.tid); + UpdatePidInfo(event->fork.pid, event->fork.tid); + break; + } + default: + break; + } + PerfMmapConsume(*this->sampleMmap); + } + PerfMmapReadDone(*this->sampleMmap); +} + +void KUNPENG_PMU::PerfSampler::FillComm(const size_t &start, const size_t &end, vector &data) +{ + for (size_t i = start; i < end; ++i) { + auto& pmuData = data[i]; + auto findProc = procMap.find(pmuData.tid); + if (findProc == procMap.end()) { + continue; + } + pmuData.comm = findProc->second->comm; + } +} + +int KUNPENG_PMU::PerfSampler::Read(vector &data, std::vector &sampleIps) +{ + auto err = this->ReadInit(); + if (__glibc_unlikely(err != SUCCESS)) { + return err; + } + auto cnt = data.size(); + this->ReadRingBuffer(data, sampleIps); + if (this->pid == -1) { + FillComm(cnt, data.size(), data); + } + + return SUCCESS; +} + +int KUNPENG_PMU::PerfSampler::Init() +{ + auto err = this->MapPerfAttr(); + if (err != SUCCESS) { + return err; + } + err = this->Mmap(); + if (__glibc_unlikely(err != SUCCESS)) { + close(this->fd); + return LIBPERF_ERR_FAIL_MMAP; + } + return SUCCESS; +} diff --git a/pmu/sampler.h b/pmu/sampler.h new file mode 100644 index 0000000000000000000000000000000000000000..e9ab432c6b15796345c095aecb5af88f3570a9ff --- /dev/null +++ b/pmu/sampler.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Gan + * Create: 2024-04-03 + * Description: definition of class PerfSampler for sampling and processing performance data in + * the KUNPENG_PMU namespace + ******************************************************************************/ +#ifndef PMU_SAMPLE_H +#define PMU_SAMPLE_H + +#include +#include +#include +#include +#include +#include +#include +#include "pmu_event.h" +#include "evt.h" +#include "symbol.h" + +namespace KUNPENG_PMU { + struct MmapParam { + int prot; + __u64 mask; + }; + + class PerfSampler : public PerfEvt { + public: + using PerfEvt::PerfEvt; + ~PerfSampler() + {} + + int Init() override; + int Read(std::vector &data, std::vector &sampleIps) override; + + int MapPerfAttr() override; + + private: + int ReadInit(); + int Mmap(); + union PerfEvent *SampleReadEvent(); + void RawSampleProcess(struct PmuData *sampleHead, PerfSampleIps *ips, union KUNPENG_PMU::PerfEvent *event); + void MmapSampleProcess(); + void Mmap2SampleProcess(); + void ForkSampleProcess(); + void ReadRingBuffer(std::vector &data, std::vector &sampleIps); + void FillComm(const size_t &start, const size_t &end, std::vector &data); + void UpdatePidInfo(const pid_t &pid, const int &tid); + + static int pages; + std::shared_ptr sampleMmap = std::make_shared(); + }; +} // namespace KUNPENG_PMU +#endif diff --git a/pmu/spe.cpp b/pmu/spe.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eeb77e1431835229733b7300cf1ed8a09f2bb24b --- /dev/null +++ b/pmu/spe.cpp @@ -0,0 +1,633 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Jin + * Create: 2024-04-03 + * Description: implements functionalities for reading and processing System Performance Events (SPE) + * data in the KUNPENG_PMU namespace + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include "pmu_event.h" +#include "arm_spe_decoder.h" +#include "process_map.h" +#include "log.h" +#include "pcerr.h" +#include "evt.h" +#include "spe.h" + +using namespace std; +using namespace KUNPENG_PMU; + +constexpr unsigned SPE_RECORD_MAX = 100000; +constexpr unsigned BUFF_SIZE = 64; +/* Should align to 2^n size in pages */ +constexpr unsigned RING_BUF_SIZE = 64 * 1024; +constexpr unsigned AUX_BUF_SIZE = 256 * 1024; + + +struct AuxContext { + struct ContextSwitchData *dummyData; + int *dummyIdx; + int cpu; + size_t auxOffset; + size_t auxSize; +}; + +static int OpenSpeEvent(PmuEvt *pmuAttr, int cpu) +{ + struct perf_event_attr attr = {0}; + + attr.size = sizeof(attr); + attr.type = pmuAttr->type; + attr.config = pmuAttr->config; /* pa_enable | load_filter | store_filter | ts_enable */ + attr.config1 = pmuAttr->config1; /* event_filter */ + attr.config2 = pmuAttr->config2; /* min_latency */ + attr.exclude_guest = 1; + attr.disabled = 1; + attr.freq = pmuAttr->useFreq; + attr.sample_period = pmuAttr->period; + attr.sample_type = PERF_SAMPLE_TID; + attr.sample_id_all = 1; + attr.read_format = PERF_FORMAT_ID; + attr.exclude_kernel = pmuAttr->excludeKernel; + attr.exclude_user = pmuAttr->excludeUser; + + return PerfEventOpen(&attr, -1, cpu, -1, 0); +} + +static int OpenDummyEvent(int cpu) +{ + struct perf_event_attr attr = {0}; + + attr.size = sizeof(attr); + attr.type = PERF_TYPE_SOFTWARE; + attr.config = PERF_COUNT_SW_DUMMY; + attr.exclude_kernel = 1; + attr.disabled = 1; + attr.sample_period = 1; + attr.sample_type = PERF_SAMPLE_TIME; + attr.sample_id_all = 1; + attr.read_format = PERF_FORMAT_ID; + attr.context_switch = 1; + attr.mmap = 1; + attr.task = 1; + attr.inherit = 1; + attr.exclude_guest = 1; + + return PerfEventOpen(&attr, -1, cpu, -1, 0); +} + +static int PerfReadTscConversion(const struct perf_event_mmap_page *pc, struct PerfTscConversion *tc) +{ + uint32_t seq; + int i = 0; + + while (1) { + seq = pc->lock; + RMB(); + tc->timeMult = pc->time_mult; + tc->timeShift = pc->time_shift; + tc->timeZero = pc->time_zero; + tc->capUserTimeZero = pc->cap_user_time_zero; + RMB(); + + if (pc->lock == seq && !(seq & 1)) { + break; + } + if (++i > 10000) { + return LIBPERF_ERR_KERNEL_NOT_SUPPORT; + } + } + + if (!tc->capUserTimeZero) { + return LIBPERF_ERR_KERNEL_NOT_SUPPORT; + } + + return SUCCESS; +} + +static uint64_t TscToPerfTime(uint64_t cyc, struct PerfTscConversion *tc) +{ + uint64_t quot = cyc >> tc->timeShift; + uint64_t rem = cyc & ((static_cast(1) << tc->timeShift) - 1); + + return tc->timeZero + quot * tc->timeMult + ((rem * tc->timeMult) >> tc->timeShift); +} + +static void CoreSpeClose(struct SpeCoreContext *ctx, struct SpeContext *speCtx) +{ + if (ctx->speFd > 0) { + close(ctx->speFd); + } + + if (ctx->dummyFd > 0) { + close(ctx->dummyFd); + } + + if (ctx->speMpage && ctx->speMpage != MAP_FAILED) { + munmap(ctx->speMpage, speCtx->speMmapSize); + } + + if (ctx->auxMpage && ctx->auxMpage != MAP_FAILED) { + munmap(ctx->auxMpage, speCtx->auxMmapSize); + } + + if (ctx->dummyMpage && ctx->dummyMpage != MAP_FAILED) { + munmap(ctx->dummyMpage, speCtx->dummyMmapSize); + } + + memset(ctx, 0, sizeof(*ctx)); +} + +static int CoreSpeOpenFailed(struct SpeCoreContext **ctx, struct SpeContext *speCtx) +{ + CoreSpeClose(*ctx, speCtx); + return LIBPERF_ERR_SPE_UNAVAIL; +} + +static int CoreSpeOpen(struct SpeCoreContext **ctx, struct SpeContext *speCtx, PmuEvt *attr, int cpu) +{ + int ret = -1; + struct perf_event_mmap_page *mp = nullptr; + + (*ctx)->cpu = cpu; + (*ctx)->speFd = OpenSpeEvent(attr, cpu); + if ((*ctx)->speFd < 0) { + auto err = MapErrno(errno); + ERR_PRINT("failed to open spe\n"); + CoreSpeClose(*ctx, speCtx); + return err; + } + DBG_PRINT("perf_event_open, cpu: %d fd: %d\n", cpu, (*ctx)->speFd); + + (*ctx)->speMpage = mmap(nullptr, speCtx->speMmapSize, PROT_READ | PROT_WRITE, MAP_SHARED, (*ctx)->speFd, 0); + if ((*ctx)->speMpage == MAP_FAILED) { + ERR_PRINT("failed to mmap for spe event\n"); + return CoreSpeOpenFailed(ctx, speCtx); + } + + mp = static_cast((*ctx)->speMpage); + mp->aux_offset = speCtx->speMmapSize; + mp->aux_size = speCtx->auxMmapSize; + + (*ctx)->auxMpage = mmap(nullptr, mp->aux_size, PROT_READ | PROT_WRITE, MAP_SHARED, (*ctx)->speFd, mp->aux_offset); + if ((*ctx)->auxMpage == MAP_FAILED) { + ERR_PRINT("failed to mmap for aux event\n"); + return CoreSpeOpenFailed(ctx, speCtx); + } + + (*ctx)->dummyFd = OpenDummyEvent(cpu); + if ((*ctx)->dummyFd < 0) { + ERR_PRINT("failed to open dummy event fd\n"); + return CoreSpeOpenFailed(ctx, speCtx); + } + + (*ctx)->dummyMpage = mmap(nullptr, speCtx->dummyMmapSize, PROT_READ | PROT_WRITE, MAP_SHARED, (*ctx)->dummyFd, 0); + if ((*ctx)->dummyMpage == MAP_FAILED) { + ERR_PRINT("failed to mmap for dummy event\n"); + return CoreSpeOpenFailed(ctx, speCtx); + } + + return SUCCESS; +} + +int SpeOpen(PmuEvt *attr, int cpu, SpeContext *ctx) +{ + int pageSize = sysconf(_SC_PAGESIZE); + + if (attr->type == -1) { + free(ctx); + return LIBPERF_ERR_SPE_UNAVAIL; + } + + ctx->cpuNum = 1; + ctx->pageSize = pageSize; + + /* Should align to 2^n size in pages */ + ctx->speMmapSize = RING_BUF_SIZE + static_cast(pageSize); + ctx->auxMmapSize = AUX_BUF_SIZE; + ctx->dummyMmapSize = RING_BUF_SIZE + pageSize; + + ctx->coreCtxes = (struct SpeCoreContext *)malloc(sizeof(struct SpeCoreContext)); + ctx->coreCtxes->mask = ctx->auxMmapSize - 1; + ctx->coreCtxes->prev = 0; + if (!ctx->coreCtxes) { + free(ctx); + return COMMON_ERR_NOMEM; + } + + auto err = CoreSpeOpen(&ctx->coreCtxes, ctx, attr, cpu); + if (err != 0) { + free(ctx->coreCtxes); + free(ctx); + return err; + } + return SUCCESS; +} + +static int CoreSpeEnable(struct SpeCoreContext *ctx) +{ + if (ctx->dummyFd <= 0 || ctx->speFd <= 0) { + return LIBPERF_ERR_FAILED_PMU_ENABLE; + } + + auto err = ioctl(ctx->dummyFd, PERF_EVENT_IOC_ENABLE, 0); + if (err != SUCCESS) { + return LIBPERF_ERR_FAILED_PMU_ENABLE; + } + err = ioctl(ctx->speFd, PERF_EVENT_IOC_ENABLE, 0); + if (err != SUCCESS) { + return LIBPERF_ERR_FAILED_PMU_ENABLE; + } + return SUCCESS; +} + +int SpeEnable(struct SpeContext *ctx) +{ + for (int i = 0; i < ctx->cpuNum; i++) { + auto err = CoreSpeEnable(&ctx->coreCtxes[i]); + if (err != SUCCESS) { + return err; + } + } + + return SUCCESS; +} + +static int CoreSpeDisable(struct SpeCoreContext *ctx) +{ + if (ctx->dummyFd <= 0 || ctx->speFd <= 0) { + return LIBPERF_ERR_FAILED_PMU_DISABLE; + } + + auto err = ioctl(ctx->speFd, PERF_EVENT_IOC_DISABLE, 0); + if (err != SUCCESS) { + return LIBPERF_ERR_FAILED_PMU_DISABLE; + } + err = ioctl(ctx->dummyFd, PERF_EVENT_IOC_DISABLE, 0); + if (err != SUCCESS) { + return LIBPERF_ERR_FAILED_PMU_DISABLE; + } + return SUCCESS; +} + +int SpeDisable(struct SpeContext *ctx) +{ + for (int i = 0; i < ctx->cpuNum; i++) { + auto err = CoreSpeDisable(&ctx->coreCtxes[i]); + if (err != SUCCESS) { + return err; + } + } + + return SUCCESS; +} + +void SpeClose(struct SpeContext *ctx) +{ + for (int i = 0; i < ctx->cpuNum; i++) { + CoreSpeClose(&ctx->coreCtxes[i], ctx); + } + + free(ctx->coreCtxes); + free(ctx); + return; +} + +void Spe::UpdateProcMap(__u32 ppid, __u32 pid) +{ + auto findParent = procMap.find(ppid); + if (findParent != procMap.end()) { + auto procTopo = GetProcTopology(pid); + if (procTopo != nullptr) { + if (procMap.find(pid) == procMap.end()) { + DBG_PRINT("Add to proc map: %d\n", pid); + procMap[pid] = shared_ptr(procTopo, FreeProcTopo); + } else { + FreeProcTopo(procTopo); + } + } + } +} + +static void ParseContextSwitch(PerfEventSampleContextSwitch *contextSample, ContextSwitchData *data, uint64_t *num, + ContextSwitchData *lastSwitchOut) +{ + if (contextSample->header.misc == 0 && contextSample->time < 1e18) { + // Context switch for Switch-In, nextPrevPid> is active pid before time>. + // Use switch-in data for spe timestamp analysis, because switch-out data miss some context switch info. + data[*num].nextPrevPid = contextSample->nextPrevPid; + data[*num].nextPrevTid = contextSample->nextPrevTid; + data[*num].time = contextSample->time; + (*num)++; + } + if (contextSample->header.misc == PERF_RECORD_MISC_SWITCH_OUT && contextSample->time < 1e18) { + // keep track of the last switch-out data, which will be used for last time slice. + lastSwitchOut->nextPrevPid = contextSample->nextPrevPid; + lastSwitchOut->nextPrevTid = contextSample->nextPrevTid; + lastSwitchOut->time = contextSample->time; + (*num)++; + } +} + +void Spe::CoreDummyData(struct SpeCoreContext *context, struct ContextSwitchData *data, int size, int pageSize) +{ + uint64_t maxNum = size / sizeof(struct ContextSwitchData); + uint64_t num = 1; + struct perf_event_mmap_page *mpage = (struct perf_event_mmap_page *)context->dummyMpage; + uint8_t *ringBuf = (uint8_t *)(mpage) + pageSize; + uint64_t dataHead = mpage->data_head; + uint64_t dataTail = mpage->data_tail; + + RMB(); + ContextSwitchData lastSwitchOut; + while ((dataTail < dataHead) && (num < maxNum)) { + uint64_t off = dataTail % mpage->data_size; + struct perf_event_header *header = (struct perf_event_header *)(ringBuf + off); + + if (header->type == PERF_RECORD_MMAP) { + struct PerfRecordMmap *sample = (struct PerfRecordMmap *)header; + SymResolverUpdateModule(sample->tid, sample->filename, sample->addr); + dataTail += header->size; + continue; + } + if (header->type == PERF_RECORD_FORK) { + struct PerfRecordFork *sample = (struct PerfRecordFork *)header; + DBG_PRINT("Fork pid: %d tid: %d\n", sample->pid, sample->tid); + UpdateProcMap(sample->pid, sample->tid); + dataTail += header->size; + continue; + } + + if ((off + header->size) > mpage->data_size || header->type != PERF_RECORD_SWITCH_CPU_WIDE) { + /* skip the wrap record or invalid record */ + dataTail += header->size; + continue; + } + + struct PerfEventSampleContextSwitch *contextSample = (struct PerfEventSampleContextSwitch *)header; + ParseContextSwitch(contextSample, data, &num, &lastSwitchOut); + dataTail += header->size; + } + + // Put last switch out to the end of dummy data. + data[num].nextPrevPid = lastSwitchOut.nextPrevPid; + data[num].nextPrevTid = lastSwitchOut.nextPrevTid; + data[num].time = lastSwitchOut.time; + ++num; + data[0].num = num; + data[0].nextPrevPid = -1; + data[0].nextPrevTid = -1; + + mpage->data_tail = mpage->data_head; + MB(); +} + +static void SetTidByTimestamp(struct ContextSwitchData *dummyData, int *dummyIdx, struct SpeRecord *buf, + struct SpeRecord *bufEnd, int cpu, struct PerfTscConversion *tc) +{ + for (struct SpeRecord *start = buf; start < bufEnd; start++) { + uint64_t recordTime = TscToPerfTime(start->timestamp, tc); + + start->cpu = cpu; + start->timestamp = recordTime; + + if (*dummyIdx >= dummyData[0].num - 1) { + // Now, all spe records locate after the last switch-in data. + // We have to use switch-out data to get pid of the last time slice. + start->pid = dummyData[dummyData[0].num - 1].nextPrevPid; + start->tid = dummyData[dummyData[0].num - 1].nextPrevTid; + continue; + } + + for (; *dummyIdx < dummyData[0].num - 1; (*dummyIdx)++) { + if (dummyData[*dummyIdx].time > recordTime) { + // is located between dummyData[*dummyIdx-1].time and dummyData[*dummyIdx].time. + // Then pid is the prev pid of dummyData[*dummyIdx]. + start->pid = dummyData[*dummyIdx].nextPrevPid; + start->tid = dummyData[*dummyIdx].nextPrevTid; + break; + } + } + } + + return; +} + +static struct SpeRecord *CoreAuxData(struct SpeCoreContext *ctx, AuxContext *auxCtx, + struct SpeRecord *buf, int *remainSize) +{ + struct perf_event_mmap_page *mpage = (struct perf_event_mmap_page *)ctx->speMpage; + uint8_t *auxBuf = static_cast(ctx->auxMpage); + uint8_t *auxStart = auxBuf + auxCtx->auxOffset % mpage->aux_size; + uint8_t *auxEnd = auxStart + auxCtx->auxSize; + SpeRecord *bufEnd = SpeGetRecord(auxStart, auxEnd, buf, remainSize); + + struct PerfTscConversion tc; + auto err = PerfReadTscConversion(mpage, &tc); + if (err != SUCCESS) { + pcerr::New(err); + return nullptr; + } + SetTidByTimestamp(auxCtx->dummyData, auxCtx->dummyIdx, buf, bufEnd, auxCtx->cpu, &tc); + + return bufEnd; +} + +static size_t ComputeAuxSize(size_t auxMapLen, size_t headOff, size_t oldOff, int pageSize) +{ + // Compute current aux buffer size by current offset and previous offset. + size_t size = 0; + if (headOff > oldOff) { + // Normal case, just diff of two offsets. + size = headOff - oldOff; + } else { + // Wraparound, size equals sum of two segment. + size = auxMapLen - (oldOff - headOff); + } + return size; +} + +static struct SpeRecord *CoreSpeData(struct SpeCoreContext *ctx, struct ContextSwitchData *dummyData, + struct SpeRecord *buf, int *remainSize, int pageSize, int cpu) +{ + int dummyIdx = 1; + struct perf_event_mmap_page *mpage = (struct perf_event_mmap_page *)ctx->speMpage; + RMB(); + __u64 old = ctx->prev; + __u64 head = ReadOnce(&mpage->aux_head); + if (old == head) { + return buf; + } + size_t headOff = head & ctx->mask; + size_t oldOff = old & ctx->mask; + size_t size = ComputeAuxSize(mpage->aux_size, headOff, oldOff, pageSize); + + size_t auxOffset = 0; + struct SpeRecord *bufEnd = nullptr; + AuxContext auxCtx = {.dummyData = dummyData, + .dummyIdx = &dummyIdx, + .cpu = cpu}; + if (size > headOff) { + // Wraparound, read two data segments. + // Read the tail segment. + auxCtx.auxSize = size - headOff; + auxCtx.auxOffset = mpage->aux_size - auxCtx.auxSize; + buf = CoreAuxData(ctx, &auxCtx, buf, remainSize); + // Read the head segment. + auxCtx.auxOffset = 0; + auxCtx.auxSize = headOff; + buf = CoreAuxData(ctx, &auxCtx, buf, remainSize); + } else { + auxCtx.auxOffset = oldOff; + auxCtx.auxSize = size; + buf = CoreAuxData(ctx, &auxCtx, buf, remainSize); + } + ctx->prev = head; + + mpage->data_tail = mpage->data_head; + mpage->aux_tail = mpage->aux_head; + MB(); + + return buf; +} + +/* + * For the initial implementation, caller should allocate a big enough buffer to + * contain all of spe records. It's not pretty frankly, will be improved later. + */ +int Spe::SpeReadData(struct SpeContext *context, struct SpeRecord *buf, int size) +{ + int remainSize = size; + int dummySize = context->dummyMmapSize; + CoreDummyData(context->coreCtxes, dummyData, dummySize, context->pageSize); + buf = CoreSpeData(context->coreCtxes, dummyData, buf, &remainSize, context->pageSize, cpu); + return size - remainSize; +} + +int Spe::Open(PmuEvt *attr) +{ + if (status == NONE) { + ctx = (struct SpeContext *)malloc(sizeof(struct SpeContext)); + if (!ctx) { + return COMMON_ERR_NOMEM; + } + auto err = SpeOpen(attr, cpu, ctx); + if (err != SUCCESS) { + return err; + } + status |= OPENED; + this->dummyFd = this->ctx->coreCtxes->dummyFd; + this->fd = this->ctx->coreCtxes->speFd; + + if (records == nullptr) { + records = new SpeRecord[SPE_RECORD_MAX]; + } + if (dummyData == nullptr) { + dummyData = new ContextSwitchData[ctx->dummyMmapSize]; + } + } + + return SUCCESS; +} +int Spe::Enable(bool clearPrevRecords) +{ + if (clearPrevRecords) { + pidRecords.clear(); + } + + if (!(status & OPENED)) { + return LIBPERF_ERR_FAILED_PMU_ENABLE; + } + if (status & ENABLED) { + return SUCCESS; + } + auto err = SpeEnable(ctx); + if (err != SUCCESS) { + return err; + } + status &= ~DISABLED; + status &= ~READ; + status |= ENABLED; + return SUCCESS; +} +int Spe::Disable() +{ + if (!(status & OPENED)) { + return LIBPERF_ERR_FAILED_PMU_DISABLE; + } + if (status & DISABLED) { + return SUCCESS; + } + auto err = SpeDisable(ctx); + if (err != SUCCESS) { + return err; + } + status &= ~ENABLED; + status |= DISABLED; + return SUCCESS; +} +bool Spe::Close() +{ + if (status == NONE) { + return true; + } + SpeClose(ctx); + if (records != nullptr) { + delete[] records; + records = nullptr; + } + if (dummyData != nullptr) { + delete[] dummyData; + dummyData = nullptr; + } + status = NONE; + return true; +} + +int Spe::Read() +{ + if (!(status & OPENED)) { + return UNKNOWN_ERROR; + } + if (status & READ) { + return SUCCESS; + } + int numRecord = SpeReadData(this->ctx, records, SPE_RECORD_MAX); + for (int i = 0; i < numRecord; i++) { + SpeRecord *rec = &records[i]; + pidRecords[rec->tid].push_back(rec); + } + status |= READ; + if (Perrorno() == LIBPERF_ERR_KERNEL_NOT_SUPPORT) { + return Perrorno(); + } + return SUCCESS; +} + +bool Spe::HaveRead() +{ + return status & READ; +} + +const std::vector Spe::GetPidRecords(const pid_t &pid) const +{ + auto findRecords = pidRecords.find(pid); + if (findRecords == pidRecords.end()) { + return {}; + } + return findRecords->second; +} diff --git a/pmu/spe.h b/pmu/spe.h new file mode 100644 index 0000000000000000000000000000000000000000..7e5697fd9e9453889347985f572fd386a16e1e9d --- /dev/null +++ b/pmu/spe.h @@ -0,0 +1,222 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Jin + * Create: 2024-04-03 + * Description: definition of class Spe for handling System Performance Events (SPE) data collection + * and processing for each CPU, storing the collected data for further analysis + ******************************************************************************/ +#ifndef __SPE__HH__ +#define __SPE__HH__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmu_event.h" +#include "symbol.h" + +#define MB() asm volatile("dsb sy") +#define RMB() asm volatile("dsb ld") +#define WMB() asm volatile("dsb st") + +#define EVENT_EXCEPTION_GEN 0x1 +#define EVENT_RETIRED 0x2 +#define EVENT_L1D_ACCESS 0x4 +#define EVENT_L1D_REFILL 0x8 +#define EVENT_TLB_ACCESS 0x10 +#define EVENT_TLB_REFILL 0x20 +#define EVENT_NOT_TAKEN 0x40 +#define EVENT_MISPRED 0x80 +#define EVENT_LLC_ACCESS 0x100 +#define EVENT_LLC_REFILL 0x200 +#define EVENT_REMOTE_ACCESS 0x400 + +#define SPE_SAMPLE_MAX 200000 + +struct SpeCoreContext { + int cpu; + int speFd; + int dummyFd; + void *speMpage; + void *auxMpage; + void *dummyMpage; + __u64 prev; + size_t mask; +}; + +struct SpeContext { + int cpuNum; + __u64 speMmapSize; /* size of spe event ring buffer + first page */ + __u64 auxMmapSize; /* size of aux buffer */ + int dummyMmapSize; /* size of dummy event ring buffer + first page */ + int pageSize; + struct SpeCoreContext *coreCtxes; +}; + +struct SpeRecord { + uint64_t event; + uint64_t pid; + uint64_t tid; + int cpu; + int vaNid; + uint64_t va; + uint64_t pa; + uint64_t timestamp; + uint64_t pc; +}; + +struct PerfEventSample { + struct perf_event_header header; // only keep the header +}; + +struct PerfEventSampleAux { + struct perf_event_header header; // aux buffer event header + uint64_t auxOffset; // current offset in aux buffer + uint64_t auxSize; // current aux data size + uint64_t flags; // data flag + uint32_t pid, tid; // process and thread id of spe record +}; + +struct PerfEventSampleContextSwitch { + struct perf_event_header header; // context switch record header + uint32_t nextPrevPid; // The process ID of the previous (if switching in) or + // next (if switching out) process on the CPU. + uint32_t nextPrevTid; // The thread ID of the previous (if switching in) or + // next (if switching out) thread on the CPU. + uint64_t time; // timestamp +}; + +struct PerfTscConversion { + uint16_t timeShift; + uint32_t timeMult; + uint64_t timeZero; + int capUserTimeZero; +}; + +struct ContextSwitchData { + uint32_t nextPrevPid = 0; + uint32_t nextPrevTid = 0; + union { + uint64_t time = 0; + uint64_t num; + }; +}; + +struct SampleId { + __u32 pid, tid; /* if PERF_SAMPLE_TID set */ + __u64 time; /* if PERF_SAMPLE_TIME set */ + __u64 id; /* if PERF_SAMPLE_ID set */ + __u32 cpu, res; /* if PERF_SAMPLE_CPU set */ + __u64 identifier; /* if PERF_SAMPLE_IDENTIFIER set */ +}; + +/** + * @brief SPE collector for each cpu. + */ +class Spe { +public: + explicit Spe(int cpu, std::unordered_map> &procMap) + : cpu(cpu), procMap(procMap) + {} + + ~Spe() + { + if (records != nullptr) { + delete records; + records = nullptr; + } + } + + /** + * @brief Open SPE ring buffer. + * @param attr sampling attribute. + * @return true + * @return false + */ + int Open(PmuEvt *attr); + + /** + * @brief Start collect. + * @param clearPrevRecords whether clear all records from previos collection. + */ + int Enable(bool clearPrevRecords = true); + + /** + * @brief Stop collect. + */ + int Disable(); + + /** + * @brief Free ring buffer. + * @return true + * @return false + */ + bool Close(); + + /** + * @brief Read data in ring buffer in last collection, and store data in this object. Use GetPidRecords to get data. + * @return true + * @return false + */ + int Read(); + + /** + * @brief The last collceted data have been read. + * @return true + * @return false + */ + bool HaveRead(); + + /** + * @brief Get SPE data of process with . + * @param pid + * @return const std::vector + */ + const std::vector GetPidRecords(const pid_t &pid) const; + + int GetSpeFd() const + { + return fd; + } + + const std::map>& GetAllRecords() const + { + return pidRecords; + } + +private: + int SpeReadData(struct SpeContext *context, struct SpeRecord *buf, int size); + void CoreDummyData(struct SpeCoreContext *context, struct ContextSwitchData *data, int size, int pageSize); + void UpdateProcMap(__u32 ppid, __u32 pid); + + const unsigned short NONE = 0; + const unsigned short OPENED = 1 << 0; + const unsigned short ENABLED = 1 << 1; + const unsigned short DISABLED = 1 << 2; + const unsigned short READ = 1 << 3; + + int cpu = 0; + SpeContext *ctx = nullptr; + unsigned short status = NONE; + int dummyFd = 0; + int fd = 0; + SpeRecord *records = nullptr; + ContextSwitchData *dummyData = nullptr; + std::map> pidRecords; + std::unordered_map> &procMap; +}; + +#endif diff --git a/pmu/spe_sampler.cpp b/pmu/spe_sampler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9cca5e49e38ebaf9e6ca21c1a3464eec4cd09fdc --- /dev/null +++ b/pmu/spe_sampler.cpp @@ -0,0 +1,198 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Jin + * Create: 2024-04-03 + * Description: implements functions for handling System Performance Events (SPE) data collection + * and processing for each CPU in the KUNPENG_PMU namespace + ******************************************************************************/ +#include +#include +#include +#include +#include +#include "linked_list.h" +#include "spe.h" +#include "pcerrc.h" +#include "spe_sampler.h" +#include "pcerr.h" + +#define MB() asm volatile("dsb sy"); +#define rmb() asm volatile("dsb ld"); +#define wmb() asm volatile("dsb st"); + +static constexpr int CYCLES_FREQ = 100; +static constexpr int SPE_PERIOD = 100; + +using namespace std; + +namespace KUNPENG_PMU { + + static map speSet; + + bool PerfSpe::Mmap() + { + return true; + } + + int PerfSpe::MapPerfAttr() + { + return SUCCESS; + } + + int PerfSpe::Init() + { + auto findSpe = speSet.find(this->cpu); + if (findSpe != speSet.end()) { + this->fd = findSpe->second.GetSpeFd(); + return SUCCESS; + } + + findSpe = speSet.emplace(this->cpu, Spe(this->cpu, procMap)).first; + auto err = findSpe->second.Open(evt); + if (err != SUCCESS) { + speSet.erase(this->cpu); + return err; + } + this->fd = findSpe->second.GetSpeFd(); + return SUCCESS; + } + + int PerfSpe::Read(vector &data, std::vector &sampleIps) + { + auto findSpe = speSet.find(this->cpu); + if (findSpe == speSet.end()) { + return LIBPERF_ERR_SPE_UNAVAIL; + } + + if (findSpe->second.HaveRead()) { + // Do not repeat reading data from the same core. + return SUCCESS; + } + if (findSpe->second.Read() != SUCCESS) { + return Perrorno(); + } + + // Fill pmu data from SPE collector. + auto cnt = data.size(); + if (pid == -1) { + // Loop over all tids in records and resolve module symbol for all pids. + UpdatePidList(findSpe->second); + for (auto records : findSpe->second.GetAllRecords()) { + if (records.first == -1 || records.first == 0) { + continue; + } + // Insert each spe record for each tid. + InsertSpeRecords(records.first, records.second, data, sampleIps); + } + } else { + // Loop over all tids. + for (auto &proc : procMap) { + // Get all spe records for tid. + const auto &records = findSpe->second.GetPidRecords(proc.first); + InsertSpeRecords(proc.second->tid, records, data, sampleIps); + } + } + + return SUCCESS; + } + + void PerfSpe::InsertSpeRecords( + const int &tid, const std::vector &speRecords, vector &data, vector &sampleIps) + { + ProcTopology *procTopo = nullptr; + auto findProc = procMap.find(tid); + if (findProc == procMap.end()) { + return; + } + procTopo = findProc->second.get(); + // Use large memory malloc instead of small mallocs to improve performance. + PmuDataExt *extPtrs = new PmuDataExt[speRecords.size()]; + extPool.push_back(extPtrs); + for (size_t i = 0; i < speRecords.size(); ++i) { + auto rec = speRecords[i]; + data.emplace_back(PmuData{0}); + auto ¤t = data.back(); + current.pid = static_cast(procTopo->pid); + current.tid = static_cast(rec->tid); + current.cpu = static_cast(this->cpu); + current.ext = &extPtrs[i]; + current.ext->event = rec->event; + current.ext->va = rec->va; + current.ext->pa = rec->pa; + current.comm = procTopo ? procTopo->comm : nullptr; + // Assign pc, which will be parsed to Symbol in PmuRead. + sampleIps.emplace_back(PerfSampleIps()); + auto &ips = sampleIps.back(); + ips.ips.push_back(rec->pc); + } + } + + void PerfSpe::UpdatePidList(const Spe &spe) + { + for (auto records : spe.GetAllRecords()) { + auto tid = records.first; + if (procMap.find(tid) == procMap.end()) { + auto procTopo = GetProcTopology(tid); + if (procTopo != nullptr) { + procMap[tid] = shared_ptr(procTopo, FreeProcTopo); + } + } + } + } + + int PerfSpe::BeginRead() + { + return Disable(); + } + + int PerfSpe::EndRead() + { + return Enable(); + } + + int PerfSpe::Enable() + { + auto findSpe = speSet.find(this->cpu); + if (findSpe == speSet.end()) { + return LIBPERF_ERR_NOT_OPENED; + } + + return findSpe->second.Enable(); + } + + int PerfSpe::Disable() + { + auto findSpe = speSet.find(this->cpu); + if (findSpe == speSet.end()) { + return LIBPERF_ERR_NOT_OPENED; + } + + return findSpe->second.Disable(); + } + + int PerfSpe::Close() + { + auto findSpe = speSet.find(this->cpu); + if (findSpe == speSet.end()) { + return SUCCESS; + } + + findSpe->second.Close(); + speSet.erase(this->cpu); + for (auto extPtr : extPool) { + delete[] extPtr; + } + extPool.clear(); + return SUCCESS; + } + + +} // namespace KUNPENG_PMU \ No newline at end of file diff --git a/pmu/spe_sampler.h b/pmu/spe_sampler.h new file mode 100644 index 0000000000000000000000000000000000000000..2f503ce331ecd93ca6997ad045086989484fff63 --- /dev/null +++ b/pmu/spe_sampler.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Jin + * Create: 2024-04-03 + * Description: defines a class PerfSpe for handling System Performance Events (SPE) data collection + * and processing in the KUNPENG_PMU namespace + ******************************************************************************/ +#ifndef PMU_SPE_H +#define PMU_SPE_H + +#include +#include +#include +#include +#include +#include +#include "process_map.h" +#include "pmu_event.h" +#include "evt.h" +#include "symbol.h" +#include "spe.h" +#include "arm_spe_decoder.h" + +namespace KUNPENG_PMU { + class PerfSpe : public PerfEvt { + public: + using PerfEvt::PerfEvt; + ~PerfSpe() + {} + + int Init() override; + int Read(std::vector &data, std::vector &sampleIps) override; + int MapPerfAttr() override; + bool Mmap(); + + int Disable() override; + int Enable() override; + int Close() override; + + int BeginRead() override; + int EndRead() override; + private: + bool SpeExist(int cpu) const; + void InsertSpeRecords(const int &tid, const std::vector &speRecords, std::vector &data, + std::vector &sampleIps); + void UpdatePidList(const Spe &spe); + + std::vector extPool; + }; +} // namespace KUNPENG_PMU +#endif diff --git a/symbol/CMakeLists.txt b/symbol/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e17614b6ff4f4a4c693617bfc24c977d5e97a751 --- /dev/null +++ b/symbol/CMakeLists.txt @@ -0,0 +1,21 @@ +if (POLICY CMP0048) + cmake_policy(SET CMP0048 NEW) +endif (POLICY CMP0048) +project(libkprof) +cmake_minimum_required (VERSION 3.12.0) + +set(SYMBOL_FILE_DIR ${PROJECT_TOP_DIR}/symbol) +set(INCLUDE_DIR ${PROJECT_TOP_DIR}/include) +set(UTIL_FILE_DIR ${PROJECT_TOP_DIR}/util) + +file(GLOB SYMBOL_SRC ${SYMBOL_FILE_DIR}/*c ${SYMBOL_FILE_DIR}/*cpp ${UTIL_FILE_DIR}/pcerr.cpp) + +include_directories(${UTIL_FILE_DIR}) +include_directories(${SYMBOL_FILE_DIR}) +include_directories(${INCLUDE_DIR}) + +message(${THIRD_PARTY}/elfin-parser/elf) +ADD_LIBRARY(sym SHARED ${SYMBOL_SRC}) +target_link_libraries(sym elf_static dwarf_static pthread) +install(TARGETS sym DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install(FILES ${SYMBOL_FILE_DIR}/symbol.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include) diff --git a/symbol/name_resolve.cpp b/symbol/name_resolve.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ec7b3a096a9bb2896db15cd7ff6be39508ed638d --- /dev/null +++ b/symbol/name_resolve.cpp @@ -0,0 +1,28 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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-04-03 + * Description: Reverse the symbols in the C ++ ABI specification and convert it into a readable function name. + ******************************************************************************/ +#include +#include +#include +#include + +char* CppNamedDemangle(const char* abiName) +{ + int status; + char* name = abi::__cxa_demangle(abiName, nullptr, nullptr, &status); + if (status != 0) { + return nullptr; + } + return name; +} diff --git a/symbol/name_resolve.h b/symbol/name_resolve.h new file mode 100644 index 0000000000000000000000000000000000000000..09c6bafbcff16e60f6ec06afe7de243e6af3b8a0 --- /dev/null +++ b/symbol/name_resolve.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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-04-03 + * Description: Reverse the symbols in the C ++ ABI specification and convert it into a readable function name. + ******************************************************************************/ +#ifndef CPP_NAME_RESOLVE_H +#define CPP_NAME_RESOLVE_H + +#ifdef __cpusplus +extern "C" { +#endif +/** For further implementation such as support for python, rust or java name + * demangel, APIs should be implemented here */ +char* CppNamedDemangle(const char* abiName); +#ifdef __cpusplus +} +#endif + +#endif diff --git a/symbol/symbol.cpp b/symbol/symbol.cpp new file mode 100644 index 0000000000000000000000000000000000000000..29ba621c31b29492b8c483f99079e2504b14e93e --- /dev/null +++ b/symbol/symbol.cpp @@ -0,0 +1,155 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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-04-03 + * Description: Provide the tool function and data structure of the complete symbol analysis and stack analysis. + ******************************************************************************/ +#include +#include +#include "symbol_resolve.h" +#include "pcerr.h" +#include "symbol.h" + +using namespace KUNPENG_SYM; +void SymResolverInit() +{ + SymbolResolve::GetInstance(); +} + +int SymResolverRecordKernel() +{ + try { + return SymbolResolve::GetInstance()->RecordKernel(); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return COMMON_ERR_NOMEM; + } +} + +int SymResolverRecordModule(int pid) +{ + try { + return SymbolResolve::GetInstance()->RecordModule(pid, RecordModuleType::RECORD_ALL); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return COMMON_ERR_NOMEM; + } +} + +int SymResolverRecordModuleNoDwarf(int pid) +{ + try { + return SymbolResolve::GetInstance()->RecordModule(pid, RecordModuleType::RECORD_NO_DWARF); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return COMMON_ERR_NOMEM; + } +} + +int SymResolverRecordElf(const char* fileName) +{ + try { + return SymbolResolve::GetInstance()->RecordElf(fileName); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return COMMON_ERR_NOMEM; + } +} + +int SymResolverRecordDwarf(const char* fileName) +{ + try { + return SymbolResolve::GetInstance()->RecordDwarf(fileName); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return COMMON_ERR_NOMEM; + } +} + +void SymResolverDestroy() +{ + SymbolResolve::GetInstance()->Clear(); +} + +struct Stack* StackToHash(int pid, unsigned long* stack, int nr) +{ + try { + return SymbolResolve::GetInstance()->StackToHash(pid, stack, nr); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return nullptr; + } +} + +struct Symbol* SymResolverMapAddr(int pid, unsigned long addr) +{ + try { + return SymbolResolve::GetInstance()->MapAddr(pid, addr); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return nullptr; + } +} + +int SymResolverIncrUpdateModule(int pid) +{ + try { + return SymbolResolve::GetInstance()->UpdateModule(pid); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return COMMON_ERR_NOMEM; + } +} + +int SymResolverUpdateModule(int pid, const char* moduleName, unsigned long startAddr) +{ + try { + return SymbolResolve::GetInstance()->UpdateModule(pid, moduleName, startAddr); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return COMMON_ERR_NOMEM; + } +} + +struct StackAsm* SymResolverAsmCode(const char* moduleName, unsigned long startAddr, unsigned long endAddr) +{ + try { + return SymbolResolve::GetInstance()->MapAsmCode(moduleName, startAddr, endAddr); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return nullptr; + } +} + +struct Symbol* SymResolverMapCodeAddr(const char* moduleName, unsigned long startAddr) +{ + try { + return SymbolResolve::GetInstance()->MapCodeAddr(moduleName, startAddr); + } catch (std::bad_alloc& err) { + pcerr::New(COMMON_ERR_NOMEM); + return nullptr; + } +} + +void FreeModuleData(int pid) +{ + return SymbolResolve::GetInstance()->FreeModule(pid); +} + +void FreeSymbolPtr(struct Symbol* symbol) +{ + SymbolUtils::FreeSymbol(symbol); +} + +void FreeAsmStack(struct StackAsm* stackAsm) +{ + SymbolUtils::FreeStackAsm(&stackAsm); +} diff --git a/symbol/symbol.h b/symbol/symbol.h new file mode 100644 index 0000000000000000000000000000000000000000..173310fc83816bc1c919263942c8a4f7758f7bdb --- /dev/null +++ b/symbol/symbol.h @@ -0,0 +1,134 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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-04-03 + * Description: Provide the tool function and data structure of the complete symbol analysis and stack analysis. + ******************************************************************************/ +#ifndef SYMBOL_H +#define SYMBOL_H +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +struct Symbol { + unsigned long addr; // address (dynamic allocated) of this symbol + char* module; // binary name of which the symbol belongs to + char* symbolName; // name of the symbol + char* fileName; // corresponding file of current symbol + unsigned int lineNum; // line number of a symbol in the file + unsigned long offset; + unsigned long codeMapEndAddr; // function end address + unsigned long codeMapAddr; // real srcAddr of Asm Code or + __u64 count; +}; + +struct Stack { + struct Symbol* symbol; // symbol info for current stack + struct Stack* next; // points to next position in stack + __u64 count; +} __attribute__((aligned(64))); + +struct StackAsm { + char* funcName; // function name of void + unsigned long funcStartAddr; // start address of function + unsigned long functFileOffset; // offset of function in this file + struct StackAsm* next; // points to next position in stack + struct AsmCode* asmCode; // asm code +}; + +struct AsmCode { + unsigned long addr; // address of asm file + char* code; // code of asm + char* fileName; // this source file name of this asm code + unsigned int lineNum; // the real line of this addr +}; + +void SymResolverInit(); + +int SymResolverRecordKernel(); + +int SymResolverRecordModule(int pid); + +int SymResolverRecordModuleNoDwarf(int pid); +/** + * Incremental update modules of pid, i.e. record newly loaded dynamic libraries by pid. + */ +int SymResolverIncrUpdateModule(int pid); + +int SymResolverUpdateModule(int pid, const char* moduleName, unsigned long startAddr); + +/** + * Record ELF data for a binary + */ +int SymResolverRecordElf(const char* fileName); + +/** + * Record DWARF data for a binary + */ +int SymResolverRecordDwarf(const char* fileName); + +/** + * Clean up resolver in the end after usage + */ +void SymResolverDestroy(); + +/** + * Convert a callstack to a unsigned long long hashid + */ +struct Stack* StackToHash(int pid, unsigned long* stack, int nr); + +/** + * Map a specific address to a symbol + */ +struct Symbol* SymResolverMapAddr(int pid, unsigned long addr); + +/** + * Obtain assembly code from file and start address and end address + */ +struct StackAsm* SymResolverAsmCode(const char* moduleName, unsigned long startAddr, unsigned long endAddr); + +/** + * Obtain the source code from the file and real start address. + */ +struct Symbol* SymResolverMapCodeAddr(const char* moduleName, unsigned long startAddr); + +/** + * free Symbol pointer + */ +void FreeSymbolPtr(struct Symbol* symbol); + +/** + * free pid module data + * @param pid + */ +void FreeModuleData(int pid); + +/** + * free asm stack code + */ +void FreeAsmStack(struct StackAsm* stackAsm); + +struct ProcTopology { + int pid; + int tid; + int ppid; + int numChild; + int* childPid; + char* comm; + char* exe; + bool kernel; +}; +#ifdef __cplusplus +} +#endif +#endif diff --git a/symbol/symbol_resolve.cpp b/symbol/symbol_resolve.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3a1b22d26a77e55825a49e77fc99de4a004bf271 --- /dev/null +++ b/symbol/symbol_resolve.cpp @@ -0,0 +1,1064 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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-04-03 + * Description: Provide a complete set of symbolic analysis tools, perform operations such as + * module records, address analysis and stack conversion. + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "name_resolve.h" +#include "pcerr.h" +#include "symbol_resolve.h" + +using namespace KUNPENG_SYM; +constexpr __u64 MAX_LINE_LENGTH = 1024; +constexpr __u64 MODULE_NAME_LEN = 1024; +constexpr __u64 MAX_LINUX_FILE_NAME = 1024; +constexpr __u64 MAX_LINUX_SYMBOL_LEN = 8192; +constexpr __u64 MAX_LINUX_MODULE_LEN = 1024; +constexpr int ADDR_LEN = 16; +constexpr int MODE_LEN = 5; +constexpr int MAP_LEN = 128; +constexpr int OFFSET_LEN = 16; +constexpr int DEV_LEN = 16; +constexpr int INODE_LEN = 16; +constexpr __u64 KERNEL_START_ADDR = 0xffff000000000000; +constexpr int BINARY_HALF = 2; +constexpr int KERNEL_NAME_LEN = 8; +constexpr int SHA_BIT_SHIFT_LEN = 8; +constexpr int KERNEL_MODULE_LNE = 128; +constexpr int CODE_LINE_RANGE_LEN = 10; +constexpr int HEX_LEN = 16; +constexpr int TO_TAIL_LEN = 2; + +const std::string HUGEPAGE = "/anon_hugepage"; +const std::string DEV_ZERO = "/dev/zero"; +const std::string ANON = "//anon"; +const std::string STACK = "[stack"; +const std::string SOCKET = "socket:"; +const std::string VSYSCALL = "[vsyscall]"; +const std::string HEAP = "[heap]"; +const std::string VDSO = "[vdso]"; +const std::string SYSV = "/sysv"; +const std::string VVAR = "[vvar]"; +const std::string R_XP = "r-xp"; +const std::string SLASH = "/"; +const char DASH = '-'; +const char EXE_TYPE = 'x'; + +namespace { + static inline void SetFalse(bool& flag) + { + flag = false; + } + + static inline bool CheckIfFile(std::string mapline) + { + return (!((mapline.find(HUGEPAGE) != std::string::npos) || (mapline.find(DEV_ZERO) != std::string::npos) || + (mapline.find(ANON) != std::string::npos) || (mapline.find(STACK) != std::string::npos) || + (mapline.find(SOCKET) != std::string::npos) || (mapline.find(VSYSCALL) != std::string::npos) || + (mapline.find(HEAP) != std::string::npos) || (mapline.find(VDSO) != std::string::npos) || + (mapline.find(SYSV) != std::string::npos) || (mapline.find(VVAR) != std::string::npos)) && + (mapline.find(R_XP) != std::string::npos)) + ? true + : false; + } + + static inline char* InitChar(int len) + { + char* str = new char[len + 1]; + memset(str, 0, len + 1); + if (str == nullptr) { + return nullptr; + } + return str; + } + + static void ReadProcPidMap(std::ifstream& file, std::vector>& modVec) + { + char line[MAX_LINUX_SYMBOL_LEN]; + char mode[MODE_LEN]; + char offset[OFFSET_LEN]; + char dev[DEV_LEN]; + char inode[INODE_LEN]; + while (file.getline(line, MAX_LINUX_SYMBOL_LEN)) { + if (!CheckIfFile(line)) { + continue; + } + std::shared_ptr data = std::make_shared(); + char modNameChar[MAX_LINUX_MODULE_LEN]; + if (sscanf(line, "%lx-%lx %s %s %s %s %s", + &data->start, &data->end, mode, offset,dev, + inode, modNameChar) == EOF) { + continue; + } + data->moduleName = modNameChar; + modVec.emplace_back(data); + } + } + + static inline void DwarfInfoRecord(DWARF_DATA_MAP& dwarfTable, const dwarf::line_table& lt, + SymbolVet& dwarfFileArray) + { + for (const dwarf::line_table::entry& line : lt) { + if (line.end_sequence) { + continue; + } + std::string fileName = line.file->path(); + if (fileName.empty()) { + continue; + } + DwarfMap data = {0}; + data.lineNum = line.line; + data.fileIndex = dwarfFileArray.InsertKeyForIndex(fileName); + dwarfTable.insert({line.address, data}); + } + } + + static inline void ElfInfoRecord(std::vector& elfVector, const elf::section& sec) + { + for (const auto& sym : sec.as_symtab()) { + auto& data = sym.get_data(); + if (data.type() != elf::stt::func) + continue; + ElfMap elfData; + elfData.start = data.value; + elfData.end = data.value + data.size; + elfData.symbolName = sym.get_name(); + elfVector.emplace_back(elfData); + } + } + + static StackAsm* FirstLineMatch(const std::string& line) + { + if (line[line.size() - TO_TAIL_LEN] == ':') { + struct StackAsm* stackAsm = CreateNode(); + stackAsm->funcName = InitChar(MAX_LINE_LENGTH); + stackAsm->asmCode = nullptr; + stackAsm->next = nullptr; + int ret = sscanf(line.c_str(), "%llx %s %*s %*s %llx", &stackAsm->funcStartAddr, stackAsm->funcName, + &stackAsm->functFileOffset); + if (ret == EOF) { + free(stackAsm->funcName); + stackAsm->funcName = nullptr; + free(stackAsm); + stackAsm = nullptr; + return nullptr; + } + return stackAsm; + } + return nullptr; + } + + static bool MatchFileName(const std::string& line, std::string& fileName, unsigned int& lineNum) + { + char startStr[MAX_LINE_LENGTH + 1]; + int ret = sscanf(line.c_str(), "%s", startStr); + if (ret < 0) { + return false; + } + std::string preLine = startStr; + if (preLine.find(":") + 1 >= preLine.size()) { + return false; + } + std::string lineStr = preLine.substr(preLine.find(":") + 1, preLine.size()); + if (lineStr.empty()) { + return false; + } + if (!SymbolUtils::IsNumber(lineStr)) { + return false; + } + try { + lineNum = std::atoi(lineStr.c_str()); + } catch (std::exception& err) { + return false; + } + fileName = preLine.substr(0, preLine.find(":")); + return true; + } + + static AsmCode* MatchAsmCode(const std::string& line, const std::string& srcFileName, unsigned int lineNum) + { + std::string addrStr = line.substr(0, line.find(":")); + struct AsmCode* asmCode = new AsmCode(); + asmCode->addr = SymbolUtils::SymStoul(addrStr); + if (asmCode->addr == 0) { + delete asmCode; + return nullptr; + } + size_t tailLen = line.find("\n") != std::string::npos ? line.find("\n") : strlen(line.c_str()); + char startStr[MAX_LINE_LENGTH + 1]; + int ret = sscanf(line.c_str(), "%*s %s", startStr); + if (ret == EOF) { + delete asmCode; + return nullptr; + } + std::string codeStr = line.substr(line.find(startStr) + strlen(startStr), tailLen); + if (!codeStr.empty()) { + codeStr.erase(0, codeStr.find_first_not_of(" ")); + codeStr.erase(0, codeStr.find_first_not_of("\t")); + } + asmCode->code = InitChar(MAX_LINE_LENGTH); + strcpy(asmCode->code, codeStr.c_str()); + asmCode->fileName = InitChar(MAX_LINUX_FILE_NAME); + strcpy(asmCode->fileName, srcFileName.c_str()); + asmCode->lineNum = lineNum; + return asmCode; + } + + static inline void FreeSymMap(SYMBOL_MAP& symMap) + { + for (auto& item : symMap) { + for (auto& data : item.second) { + SymbolUtils::FreeSymbol(data.second); + } + } + } + + static inline void FreeStackMap(STACK_MAP& stackMap) + { + for (auto& item : stackMap) { + for (auto& data : item.second) { + struct Stack* head = data.second; + FreeList(&head); + } + } + } + + static inline void FreeKernelVet(std::vector>& kernel) + { + for (auto symbol : kernel) { + if (symbol->module) { + delete[] symbol->module; + symbol->module = nullptr; + } + if (symbol->fileName) { + delete[] symbol->fileName; + symbol->fileName = nullptr; + } + if (symbol->symbolName) { + delete[] symbol->symbolName; + symbol->symbolName = nullptr; + } + } + } + + static inline size_t HashStr(unsigned long* stack, int nr) + { + std::string str = {}; + for (int k = 0; k < nr; k++) { + str += stack[k]; + } + std::hash h; + size_t n = h(str); + return n; + } + + static inline bool CheckColonSuffix(const std::string& str) + { + if (!strstr(str.c_str(), ":")) { + return false; + } + size_t colonIndex = str.find(":"); + if (colonIndex + 1 >= str.size()) { + return false; + } + std::string suffix = str.substr(colonIndex + 1, str.size()); + if (suffix.empty() || suffix.size() == 1) { + return false; + } + return true; + } + +} // namespace + +void SymbolUtils::FreeSymbol(struct Symbol* symbol) +{ + if (symbol->symbolName) { + delete[] symbol->symbolName; + symbol->symbolName = nullptr; + } + if (symbol->fileName) { + delete[] symbol->fileName; + symbol->fileName = nullptr; + } + if (symbol->module) { + delete[] symbol->module; + symbol->module = nullptr; + } + if (symbol) { + delete symbol; + symbol = nullptr; + } +} + +void SymbolUtils::FreeStackAsm(struct StackAsm** stackAsm) +{ + struct StackAsm* current = *stackAsm; + struct StackAsm* next; + while (current != nullptr) { + next = current->next; + if (current->asmCode) { + delete[] current->asmCode->code; + delete[] current->asmCode->fileName; + free(current->asmCode); + } + if (current->funcName) { + delete[] current->funcName; + } + free(current); + current = next; + } + *stackAsm = nullptr; +} + +bool SymbolUtils::IsFile(const char* fileName) +{ + struct stat st {}; + // 获取文件属性 + if (stat(fileName, &st) != 0) { + return false; + } + return (S_ISREG(st.st_mode)) ? true : false; +} + +unsigned long SymbolUtils::SymStoul(const std::string& addrStr) +{ + try { + return std::stoul(addrStr, nullptr, HEX_LEN); + } catch (std::invalid_argument& invalidErr) { + return 0; + } +} + +std::string SymbolUtils::RealPath(const std::string& filePath) +{ + char buff[PATH_MAX] = {0}; + if (realpath(filePath.c_str(), buff)) { + return std::string{buff}; + } else { + return std::string{}; + } +} + +bool SymbolUtils::IsValidPath(const std::string& filePath) +{ + if (filePath.empty()) { + return false; + } + return true; +} + +bool SymbolUtils::IsNumber(const std::string& str) +{ + for (int i = 0; i < str.length(); i++) { + if (!isdigit(str[i])) { + return false; + } + } + return true; +} + +int SymbolResolve::RecordModule(int pid, RecordModuleType recordModuleType) +{ + if (pid < 0) { + pcerr::New(LIBSYM_ERR_PARAM_PID_INVALID, "libsym param process ID must be greater than 0"); + return LIBSYM_ERR_PARAM_PID_INVALID; + } + SetFalse(this->isCleared); + moduleSafeHandler.tryLock(pid); + if (this->moduleMap.find(pid) != this->moduleMap.end()) { + pcerr::New(0, "success"); + moduleSafeHandler.releaseLock(pid); + return 0; + } + char mapFile[MAP_LEN]; + if (snprintf(mapFile, MAP_LEN, "/proc/%d/maps", pid) < 0) { + moduleSafeHandler.releaseLock(pid); + return LIBSYM_ERR_SNPRINF_OPERATE_FAILED; + } + std::ifstream file(mapFile); + if (!file.is_open()) { + pcerr::New(LIBSYM_ERR_OPEN_FILE_FAILED, + "libsym can't open file named " + std::string{mapFile} + " because of " + std::string{strerror(errno)}); + moduleSafeHandler.releaseLock(pid); + return LIBSYM_ERR_OPEN_FILE_FAILED; + } + std::vector> modVec; + ReadProcPidMap(file, modVec); + for (auto& item : modVec) { + this->RecordElf(item->moduleName.c_str()); + if (recordModuleType != RecordModuleType::RECORD_NO_DWARF) { + this->RecordDwarf(item->moduleName.c_str()); + } + } + this->moduleMap.insert({pid, modVec}); + pcerr::New(0, "success"); + moduleSafeHandler.releaseLock(pid); + return 0; +} + +int SymbolResolve::UpdateModule(int pid) +{ + if (pid < 0) { + pcerr::New(LIBSYM_ERR_PARAM_PID_INVALID, "libsym param process ID must be greater than 0"); + return LIBSYM_ERR_PARAM_PID_INVALID; + } + moduleSafeHandler.tryLock(pid); + if (this->moduleMap.find(pid) == this->moduleMap.end()) { + // need to use RecordModule first! + pcerr::New(SUCCESS); + moduleSafeHandler.releaseLock(pid); + return SUCCESS; + } + // Get memory maps of pid. + char mapFile[MAP_LEN]; + if (snprintf(mapFile, MAP_LEN, "/proc/%d/maps", pid) < 0) { + moduleSafeHandler.releaseLock(pid); + return LIBSYM_ERR_SNPRINF_OPERATE_FAILED; + } + std::ifstream file(mapFile); + if (!file.is_open()) { + pcerr::New(LIBSYM_ERR_OPEN_FILE_FAILED, + "libsym can't open file named " + std::string{mapFile} + " because of " + std::string{strerror(errno)}); + moduleSafeHandler.releaseLock(pid); + return LIBSYM_ERR_OPEN_FILE_FAILED; + } + std::vector> newModVec; + ReadProcPidMap(file, newModVec); + + // Find new dynamic modules. + auto &oldModVec = moduleMap[pid]; + if (newModVec.size() <= oldModVec.size()) { + pcerr::New(SUCCESS); + moduleSafeHandler.releaseLock(pid); + return SUCCESS; + } + auto diffModVec = FindDiffMaps(oldModVec, newModVec); + // Load modules. + for (auto& item : diffModVec) { + this->RecordElf(item->moduleName.c_str()); + this->RecordDwarf(item->moduleName.c_str()); + } + for (auto mod : diffModVec) { + oldModVec.push_back(mod); + } + pcerr::New(SUCCESS); + moduleSafeHandler.releaseLock(pid); + return SUCCESS; +} + +void SymbolResolve::FreeModule(int pid) +{ + if (pid < 0) { + return; + } + moduleSafeHandler.tryLock(pid); + auto it = this->moduleMap.find(pid); + if (it != this->moduleMap.end()) { + this->moduleMap.erase(it); + } + moduleSafeHandler.releaseLock(pid); + return; +} + +struct SortElf { + inline bool operator()(const ElfMap& first, const ElfMap& second) + { + return (first.start < second.start); + } +}; + +int SymbolResolve::RecordElf(const char* fileName) +{ + SetFalse(this->isCleared); + std::string file = fileName; + elfSafeHandler.tryLock(file); + + if (this->elfMap.find(fileName) != this->elfMap.end()) { + pcerr::New(0, "success"); + elfSafeHandler.releaseLock(file); + return 0; + } + + if (!SymbolUtils::IsFile(fileName)) { + pcerr::New(LIBSYM_ERR_FILE_NOT_RGE, "libsym detects that the input parameter fileName is not a file"); + elfSafeHandler.releaseLock(file); + return LIBSYM_ERR_FILE_NOT_RGE; + } + + /** symbol cache logic should be implemented after this line */ + int fd = open(fileName, O_RDONLY); + if (fd < 0) { + pcerr::New(LIBSYM_ERR_OPEN_FILE_FAILED, + "libsym can't open file named " + file + " because of " + std::string{strerror(errno)}); + elfSafeHandler.releaseLock(file); + return LIBSYM_ERR_OPEN_FILE_FAILED; + } + + std::shared_ptr efLoader = elf::create_mmap_loader(fd); + elf::elf ef(efLoader); + std::vector elfVector; + + try { + for (const auto& sec : ef.sections()) { + if (sec.get_hdr().type != elf::sht::symtab && sec.get_hdr().type != elf::sht::dynsym) { + continue; + } + ElfInfoRecord(elfVector, sec); + } + } catch (elf::format_error& error) { + pcerr::New(LIBSYM_ERR_ELFIN_FOMAT_FAILED, "libsym record elf format error: " + std::string{error.what()}); + elfSafeHandler.releaseLock(file); + return LIBSYM_ERR_ELFIN_FOMAT_FAILED; + } + + std::sort(elfVector.begin(), elfVector.end(), SortElf()); + this->elfMap.insert({file, elfVector}); + close(fd); + pcerr::New(0, "success"); + elfSafeHandler.releaseLock(file); + return 0; +} + +int SymbolResolve::RecordDwarf(const char* fileName) +{ + SetFalse(this->isCleared); + std::string file = fileName; + dwarfSafeHandler.tryLock(file); + + if (this->dwarfMap.find(fileName) != this->dwarfMap.end()) { + pcerr::New(0, "success"); + dwarfSafeHandler.releaseLock((file)); + return 0; + } + + if (!SymbolUtils::IsFile(fileName)) { + pcerr::New(LIBSYM_ERR_FILE_NOT_RGE, "libsym detects that the input parameter fileName is not a file"); + dwarfSafeHandler.releaseLock((file)); + return LIBSYM_ERR_FILE_NOT_RGE; + } + + int fd = open(fileName, O_RDONLY); + if (fd < 0) { + pcerr::New(LIBSYM_ERR_OPEN_FILE_FAILED, + "libsym can't open file named " + file + " because of " + std::string{strerror(errno)}); + dwarfSafeHandler.releaseLock((file)); + return LIBSYM_ERR_OPEN_FILE_FAILED; + } + /** symbol cache logic should be implemented after this line */ + + auto efLoader = elf::create_mmap_loader(fd); + elf::elf ef(std::move(efLoader)); + + try { + dwarf::dwarf dw(dwarf::elf::create_loader(ef)); + DWARF_DATA_MAP dwarfTable; + for (const auto& cu : dw.compilation_units()) { + const dwarf::line_table lt = cu.get_line_table(); + DwarfInfoRecord(dwarfTable, lt, dwarfFileArray); + } + std::vector addrVet; + for (auto it = dwarfTable.begin(); it != dwarfTable.end(); ++it) { + addrVet.push_back(it->first); + } + this->dwarfMap.insert({file, dwarfTable}); + this->dwarfVetMap.insert({file, addrVet}); + } catch (dwarf::format_error& error) { + dwarfSafeHandler.releaseLock((file)); + pcerr::New(LIBSYM_ERR_DWARF_FORMAT_FAILED, + "libsym record dwarf file named " + file + " format error: " + std::string{error.what()}); + return LIBSYM_ERR_DWARF_FORMAT_FAILED; + } + + efLoader.reset(); + close(fd); + pcerr::New(0, "success"); + dwarfSafeHandler.releaseLock((file)); + return 0; +} + +void SymbolResolve::Clear() +{ + std::lock_guard lock(mutex); + if (!this->instance) { + return; + } + /** + * free the memory allocated for symbol table + */ + FreeSymMap(this->symbolMap); + + /** + * free the memory allocated for stack table + */ + FreeStackMap(this->stackMap); + /** + * free the memory allocated from kernel + */ + FreeKernelVet(this->ksymArray); + this->isCleared = true; + delete this->instance; + this->instance = nullptr; +} + +void SymbolResolve::SearchElfInfo( + std::vector& elfVec, unsigned long addr, struct Symbol* symbol, unsigned long* offset) +{ + ssize_t start = 0; + ssize_t end = elfVec.size() - 1; + ssize_t mid; + unsigned long symAddr; + + while (start < end) { + mid = start + (end - start + 1) / BINARY_HALF; + symAddr = elfVec[mid].start; + if (symAddr <= addr) { + start = mid; + } else { + end = mid - 1; + } + } + + if (start == end && elfVec[start].start <= addr && elfVec[start].end >= addr) { + *offset = addr - elfVec[start].start; + symbol->codeMapEndAddr = elfVec[start].end; + char* name = CppNamedDemangle(elfVec[start].symbolName.c_str()); + if (name) { + strcpy(symbol->symbolName, name); + free(name); + name = nullptr; + return; + } + strcpy(symbol->symbolName, elfVec[start].symbolName.c_str()); + return; + } + strcpy(symbol->symbolName, "UNKNOWN"); + return; +} + +void SymbolResolve::SearchDwarfInfo( + std::vector& addrVet, DWARF_DATA_MAP& dwarfDataMap, unsigned long addr, struct Symbol* symbol) +{ + DwarfMap dwarfMap = {0}; + bool findLine = false; + if (dwarfDataMap.find(addr) != dwarfDataMap.end()) { + dwarfMap = dwarfDataMap.at(addr); + findLine = true; + } else { + auto it = std::upper_bound(addrVet.begin(), addrVet.end(), addr); + if (it > addrVet.begin() && it < addrVet.end()) { + --it; + } + if (it != addrVet.end()) { + dwarfMap = dwarfDataMap.at(*it); + findLine = true; + } + } + if (findLine) { + std::string fileName = dwarfFileArray.GetKeyByIndex(dwarfMap.fileIndex); + strcpy(symbol->fileName, fileName.c_str()); + symbol->lineNum = dwarfMap.lineNum; + } else { + strcpy(symbol->fileName, "Uknown"); + symbol->lineNum = 0; + } +} + +std::shared_ptr SymbolResolve::AddrToModule( + std::vector>& processModule, unsigned long addr) +{ + ssize_t start = 0; + ssize_t end = processModule.size() - 1; + ssize_t mid; + unsigned long symAddr; + + while (start < end) { + mid = start + (end - start + 1) / BINARY_HALF; + symAddr = processModule[mid]->start; + if (symAddr <= addr) { + start = mid; + } else { + end = mid - 1; + } + } + + if (start == end && processModule[start]->start <= addr) { + return processModule[start]; + } + + pcerr::New(LIBSYM_ERR_MAP_ADDR_MODULE_FAILED, "libsym addr can't find module"); + return nullptr; +} + +struct Stack* SymbolResolve::StackToHash(int pid, unsigned long* stack, int nr) +{ + if (this->stackMap.find(pid) == this->stackMap.end()) { + this->stackMap[pid] = {}; + } + size_t stackId = HashStr(stack, nr); + if (this->stackMap.at(pid).find(stackId) != this->stackMap.at(pid).end()) { + return this->stackMap.at(pid).at(stackId); + } + + struct Stack* head = nullptr; + for (int i = nr - 1; i >= 0; i--) { + struct Stack* current = CreateNode(); + auto symbol = this->MapAddr(pid, stack[i]); + if (symbol != nullptr) { + current->symbol = symbol; + } else { + current->symbol = nullptr; + } + AddTail(&head, ¤t); + } + + if (this->stackMap.at(pid).find(stackId) == this->stackMap.at(pid).end()) { + this->stackMap.at(pid)[stackId] = head; + } + pcerr::New(0, "success"); + return head; +} + +struct Symbol* SymbolResolve::MapKernelAddr(unsigned long addr) +{ + ssize_t start = 0; + ssize_t end = this->ksymArray.size() - 1; + ssize_t mid; + __u64 symAddr; + + while (start < end) { + mid = start + (end - start + 1) / BINARY_HALF; + symAddr = this->ksymArray[mid]->addr; + if (symAddr <= addr) { + start = mid; + } else { + end = mid - 1; + } + } + + if (start == end && this->ksymArray[start]->addr <= addr) { + return this->ksymArray[start].get(); + } + pcerr::New(LIBSYM_ERR_MAP_KERNAL_ADDR_FAILED, "libsym cannot find the corresponding kernel address"); + return nullptr; +} + +struct Symbol* SymbolResolve::MapUserAddr(int pid, unsigned long addr) +{ + if (this->moduleMap.find(pid) == this->moduleMap.end()) { + pcerr::New(LIBSYM_ERR_NOT_FIND_PID, "The libsym process ID " + std::to_string(pid) + " cannot be found."); + return nullptr; + } + + std::shared_ptr module = this->AddrToModule(this->moduleMap.at(pid), addr); + if (!module) { + return nullptr; + } + /** + * Try to search elf data first + */ + symSafeHandler.tryLock(pid); + if (this->symbolMap.find(pid) == this->symbolMap.end()) { + this->symbolMap[pid] = {}; + } + symSafeHandler.releaseLock(pid); + auto it = this->symbolMap.at(pid).find(addr); + if (it != this->symbolMap.at(pid).end()) { + return it->second; + } + struct Symbol* symbol = new struct Symbol(); + symbol->module = InitChar(MAX_LINUX_MODULE_LEN); + symbol->symbolName = InitChar(MAX_LINUX_SYMBOL_LEN); + symbol->fileName = InitChar(MAX_LINUX_FILE_NAME); + symbol->addr = addr; + symbol->offset = 0; + strcpy(symbol->module, module->moduleName.c_str()); + unsigned long addrToSearch = addr; + if (this->elfMap.find(module->moduleName) != this->elfMap.end()) { + // If the largest symbol in the elf symbol table is detected to be smaller than the searched symbol, subtraction + // is performed. + std::vector elfVet = this->elfMap.at(module->moduleName); + if (!elfVet.empty()) { + if (elfVet.back().end < addrToSearch && addrToSearch > module->start) { + addrToSearch = addrToSearch - module->start; + } + this->SearchElfInfo(elfVet, addrToSearch, symbol, &symbol->offset); + } + } + + if (this->dwarfMap.find(module->moduleName) != this->dwarfMap.end()) { + this->SearchDwarfInfo( + this->dwarfVetMap.at(module->moduleName), this->dwarfMap.at(module->moduleName), addrToSearch, symbol); + } + symbol->codeMapAddr = addrToSearch; + this->symbolMap.at(pid).insert({addr, symbol}); + pcerr::New(0, "success"); + return symbol; +} + +struct Symbol* SymbolResolve::MapAddr(int pid, unsigned long addr) +{ + struct Symbol* data = nullptr; + if (addr > KERNEL_START_ADDR) { + data = this->MapKernelAddr(addr); + if (data == nullptr) { + return nullptr; + } + data->offset = addr - data->addr; + } else { + data = this->MapUserAddr(pid, addr); + } + return data; +} + +int SymbolResolve::RecordKernel() +{ + SetFalse(this->isCleared); + if (!this->ksymArray.empty()) { + pcerr::New(0, "success"); + return 0; + } + // Prevent multiple threads from processing kernel data at the same time. + std::lock_guard guard(kernelMutex); + + FILE* kallsyms = fopen("/proc/kallsyms", "r"); + if (__glibc_unlikely(kallsyms == nullptr)) { + pcerr::New(LIBSYM_ERR_KALLSYMS_INVALID, + "libsym failed to open /proc/kallsyms, found that file /proc/kallsyms " + std::string{strerror(errno)}); + return LIBSYM_ERR_KALLSYMS_INVALID; + } + + char line[MAX_LINE_LENGTH]; + char mode; + __u64 addr; + char name[KERNEL_MODULE_LNE]; + + while (fgets(line, sizeof(line), kallsyms)) { + if (sscanf(line, "%llx %c %s%*[^\n]\n", &addr, &mode, name) == EOF) { + continue; + } + ssize_t nameLen = strlen(name); + std::shared_ptr data = std::make_shared(); + data->symbolName = InitChar(nameLen); + strcpy(data->symbolName, name); + data->addr = addr; + data->fileName = InitChar(KERNEL_NAME_LEN); + strcpy(data->fileName, "KERNEL"); + data->module = InitChar(KERNEL_NAME_LEN); + strcpy(data->module, "KERNEL"); + data->lineNum = 0; + this->ksymArray.emplace_back(data); + } + + fclose(kallsyms); + pcerr::New(0, "success"); + return 0; +} + +int SymbolResolve::UpdateModule(int pid, const char* moduleName, unsigned long startAddr) +{ + if (pid < 0) { + pcerr::New(LIBSYM_ERR_PARAM_PID_INVALID, "libsym param process ID must be greater than 0"); + return LIBSYM_ERR_PARAM_PID_INVALID; + } + SetFalse(this->isCleared); + std::shared_ptr data = std::make_shared(); + data->moduleName = moduleName; + data->start = startAddr; + int ret = this->RecordElf(data->moduleName.c_str()); + if (ret == LIBSYM_ERR_OPEN_FILE_FAILED || ret == LIBSYM_ERR_FILE_NOT_RGE) { + return ret; + } + + this->RecordDwarf(data->moduleName.c_str()); + if (ret == LIBSYM_ERR_OPEN_FILE_FAILED || ret == LIBSYM_ERR_FILE_NOT_RGE) { + return ret; + } + + if (this->moduleMap.find(pid) == this->moduleMap.end()) { + std::vector> modVec; + modVec.emplace_back(data); + this->moduleMap[pid] = modVec; + } else { + this->moduleMap[pid].emplace_back(data); + } + pcerr::New(0, "success"); + return 0; +} + +struct Symbol* SymbolResolve::MapCodeElfAddr(const std::string& moduleName, unsigned long addr) +{ + struct Symbol* data = nullptr; + if (addr > KERNEL_START_ADDR) { + pcerr::New(LIBSYM_ERR_MAP_CODE_KERNEL_NOT_SUPPORT, + "libsym The current version does not support kernel source code matching."); + return nullptr; + } else { + int ret = RecordElf(moduleName.c_str()); + if (ret != 0) { + return nullptr; + } + data = this->MapUserCodeAddr(moduleName, addr); + } + return data; +} + +struct Symbol* SymbolResolve::MapUserCodeAddr(const std::string& moduleName, unsigned long startAddr) +{ + struct Symbol* symbol = new Symbol(); + symbol->symbolName = InitChar(MAX_LINUX_SYMBOL_LEN); + symbol->fileName = InitChar(MAX_LINUX_SYMBOL_LEN); + symbol->module = InitChar(MAX_LINUX_MODULE_LEN); + symbol->addr = startAddr; + unsigned long addrToSearch = startAddr; + symbol->codeMapAddr = addrToSearch; + strcpy(symbol->module, moduleName.c_str()); + if (this->elfMap.find(moduleName) != this->elfMap.end()) { + this->SearchElfInfo(this->elfMap.at(moduleName), addrToSearch, symbol, &symbol->offset); + } + pcerr::New(0, "success"); + return symbol; +} + +struct StackAsm* SymbolResolve::MapAsmCode(const char* moduleName, unsigned long startAddr, unsigned long endAddr) +{ + struct StackAsm* stackAsm = MapAsmCodeStack(moduleName, startAddr, endAddr); + return stackAsm; +} + +struct Symbol* SymbolResolve::MapCodeAddr(const char* moduleName, unsigned long startAddr) +{ + Symbol* symbol = MapCodeElfAddr(moduleName, startAddr); + if (!symbol) { + return nullptr; + } + int ret = RecordDwarf(moduleName); + if (ret == 0) { + this->SearchDwarfInfo( + this->dwarfVetMap.at(moduleName), this->dwarfMap.at(moduleName), symbol->codeMapAddr, symbol); + } else { + symbol->fileName = nullptr; + } + return symbol; +} + +struct StackAsm* ReadAsmCodeFromPipe(FILE* pipe) +{ + struct StackAsm* head = nullptr; + struct StackAsm* last = nullptr; + int lineLen = MAX_LINE_LENGTH; + char line[lineLen]; + std::string srcFileName = "unknown"; + unsigned int srcLineNum = 0; + while (!feof(pipe)) { + memset(line, 0, lineLen); + if (!fgets(line, lineLen, pipe)) { + break; + } + if (!line || (line && line[0] == '\0') || (line && line[0] == '\n')) { + continue; + } + struct StackAsm* stackAsm = nullptr; + if (strstr(line, "File Offset") != nullptr) { + stackAsm = FirstLineMatch(line); + } + if (!stackAsm && head) { + if (!CheckColonSuffix(line)) { + continue; + } + if (MatchFileName(line, srcFileName, srcLineNum)) { + continue; + } + AsmCode* asmCode = MatchAsmCode(line, srcFileName, srcLineNum); + if (!asmCode) { + continue; + } + struct StackAsm* current = CreateNode(); + current->funcName = nullptr; + current->asmCode = asmCode; + last->next = current; + last = current; + continue; + } + if (!head && stackAsm) { + AddTail(&head, &stackAsm); + last = head; + } + if (stackAsm && head) { + last->next = stackAsm; + last = stackAsm; + } + } + pclose(pipe); + pipe = nullptr; + return head; +} + +struct StackAsm* SymbolResolve::MapAsmCodeStack( + const std::string& moduleName, unsigned long startAddr, unsigned long endAddr) +{ + char startAddrStr[ADDR_LEN]; + char endAddrStr[ADDR_LEN]; + if (startAddr >= endAddr) { + pcerr::New(LIBSYM_ERR_START_SMALLER_END, "libysm the end address must be greater than the start address"); + return nullptr; + } + if (snprintf(startAddrStr, ADDR_LEN, "0x%lx", startAddr) < 0) { + pcerr::New(LIBSYM_ERR_SNPRINF_OPERATE_FAILED, "libsym fails to execute snprintf"); + return nullptr; + } + + if (snprintf(endAddrStr, ADDR_LEN, "0x%lx", endAddr) < 0) { + pcerr::New(LIBSYM_ERR_SNPRINF_OPERATE_FAILED, "libsym fails to execute snprintf"); + return nullptr; + } + std::string cmd = "objdump -Fld " + moduleName + " --start-address=" + std::string{startAddrStr} + + " --stop-address=" + std::string{endAddrStr}; + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) { + pcerr::New(LIBSYM_ERR_CMD_OPERATE_FAILED, + "libsym fails to obtain the assembly instruction" + std::string{strerror(errno)}); + return nullptr; + } + struct StackAsm* head = ReadAsmCodeFromPipe(pipe); + pcerr::New(0, "success"); + return head; +} + +std::vector> SymbolResolve::FindDiffMaps( + const std::vector>& oldMaps, + const std::vector>& newMaps) const +{ + std::vector> diffMaps; + for (auto newMod : newMaps) { + for (auto oldMod : oldMaps) { + if (newMod->start != oldMod->start) { + diffMaps.push_back(newMod); + } + } + } + + return diffMaps; +} + +SymbolResolve* SymbolResolve::instance = nullptr; +std::mutex SymbolResolve::mutex; +std::mutex SymbolResolve::kernelMutex; diff --git a/symbol/symbol_resolve.h b/symbol/symbol_resolve.h new file mode 100644 index 0000000000000000000000000000000000000000..e87d6ba5f9083074feb415b3e806a3c4a83a81f8 --- /dev/null +++ b/symbol/symbol_resolve.h @@ -0,0 +1,167 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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-04-03 + * Description: Provide a complete set of symbolic analysis tools, perform operations such as + * module records, address analysis and stack conversion. + ******************************************************************************/ +#ifndef USER_SYMBOL_H +#define USER_SYMBOL_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "safe_handler.h" +#include "linked_list.h" +#include "symbol.h" + +namespace KUNPENG_SYM { + struct ModuleMap { + unsigned long start; + unsigned long end; + std::string moduleName; + } __attribute__((aligned(8))); + + struct ElfMap { + unsigned long start; + unsigned long end; + std::string symbolName; + } __attribute__((aligned(8))); + + struct DwarfMap { + unsigned int lineNum; + unsigned int fileIndex; + } __attribute__((aligned(8))); + + enum class RecordModuleType { RECORD_ALL = 0, RECORD_NO_DWARF = 1 }; + + using SYMBOL_MAP = std::unordered_map>; + using STACK_MAP = std::unordered_map>; + using MODULE_MAP = std::unordered_map>>; + using DWARF_DATA_MAP = std::map; + using DWARF_MAP = std::unordered_map; + using DWARF_VET_MAP = std::unordered_map>; + using ELF_MAP = std::unordered_map>; + + template + class SymbolVet : public std::vector { + public: + unsigned int InsertKeyForIndex(const Key& key) + { + std::lock_guard guard(keyMutex); + if (keyMap.find(key) != keyMap.end()) { + return keyMap.at(key); + } + this->push_back(key); + keyMap[key] = this->size() - 1; + return this->size() - 1; + } + + Key& GetKeyByIndex(const unsigned int index) + { + std::lock_guard guard(keyMutex); + if (index < this->size()) { + return this->at(index); + } + Key key = {}; + return key; + } + + private: + std::unordered_map keyMap; + std::mutex keyMutex; + }; + + class SymbolUtils final { + public: + SymbolUtils() = default; + ~SymbolUtils() = default; + static void FreeSymbol(struct Symbol* symbol); + static bool IsFile(const char* fileName); + static unsigned long SymStoul(const std::string& addrStr); + static std::string RealPath(const std::string& filePath); + static bool IsValidPath(const std::string& filePath); + static bool IsNumber(const std::string& str); + static void FreeStackAsm(struct StackAsm** stackAsm); + }; + class SymbolResolve { + public: + static SymbolResolve* GetInstance() + { + // Double-checked locking for thread safety + if (instance == nullptr) { + std::lock_guard lock(mutex); + if (instance == nullptr) { + instance = new SymbolResolve(); + } + } + return instance; + } + + int RecordModule(int pid, RecordModuleType recordModuleType); + void FreeModule(int pid); + int RecordKernel(); + int RecordElf(const char* fileName); + int RecordDwarf(const char* fileName); + int UpdateModule(int pid); + int UpdateModule(int pid, const char* moduleName, unsigned long startAddr); + void Clear(); + std::shared_ptr AddrToModule(std::vector>& processModule, unsigned long addr); + struct Stack* StackToHash(int pid, unsigned long* stack, int nr); + struct Symbol* MapAddr(int pid, unsigned long addr); + struct StackAsm* MapAsmCode(const char* moduleName, unsigned long startAddr, unsigned long endAddr); + struct Symbol* MapCodeAddr(const char* moduleName, unsigned long startAddr); + + private: + void SearchElfInfo( + std::vector& elfVec, unsigned long addr, struct Symbol* symbol, unsigned long* offset); + void SearchDwarfInfo( + std::vector& addrVet, DWARF_DATA_MAP& dwalfVec, unsigned long addr, struct Symbol* symbol); + struct Symbol* MapKernelAddr(unsigned long addr); + struct Symbol* MapUserAddr(int pid, unsigned long addr); + struct Symbol* MapUserCodeAddr(const std::string& moduleName, unsigned long addr); + struct Symbol* MapCodeElfAddr(const std::string& moduleName, unsigned long addr); + struct StackAsm* MapAsmCodeStack(const std::string& moduleName, unsigned long startAddr, unsigned long endAddr); + std::vector> FindDiffMaps(const std::vector>& oldMaps, + const std::vector>& newMaps) const; + + SYMBOL_MAP symbolMap{}; + STACK_MAP stackMap{}; + MODULE_MAP moduleMap{}; + DWARF_MAP dwarfMap{}; + DWARF_VET_MAP dwarfVetMap{}; + ELF_MAP elfMap{}; + bool isCleared = false; + std::vector> ksymArray; + SymbolVet dwarfFileArray; + SymbolResolve() + {} + + SymbolResolve(const SymbolResolve&) = delete; + SymbolResolve& operator=(const SymbolResolve&) = delete; + + ~SymbolResolve() + {} + SafeHandler moduleSafeHandler; + SafeHandler dwarfSafeHandler; + SafeHandler elfSafeHandler; + SafeHandler symSafeHandler; + static std::mutex kernelMutex; + static SymbolResolve* instance; + static std::mutex mutex; + }; +} // namespace KUNPENG_SYM +#endif \ No newline at end of file diff --git a/third_party/elfin-parser b/third_party/elfin-parser new file mode 160000 index 0000000000000000000000000000000000000000..13e57e29400a3a7bb5cb27c364b4b73468b4050f --- /dev/null +++ b/third_party/elfin-parser @@ -0,0 +1 @@ +Subproject commit 13e57e29400a3a7bb5cb27c364b4b73468b4050f diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a59ba9dbe5c83570e8541fd069c403eb4643e5cd --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,22 @@ +if (POLICY CMP0048) + cmake_policy(SET CMP0048 NEW) +endif (POLICY CMP0048) +project(libprofutil) +cmake_minimum_required (VERSION 3.12.0) + +set(UTIL_FILE_DIR ${PROJECT_TOP_DIR}/util) +set(SYMBOL_FILE_DIR ${PROJECT_TOP_DIR}/symbol) +set(INCLUDE_DIR ${PROJECT_TOP_DIR}/include) + +file(GLOB UTIL_SRC ${UTIL_FILE_DIR}/*cpp) + +include_directories(${SYMBOL_FILE_DIR}) +include_directories(${UTIL_FILE_DIR}) +include_directories(${INCLUDE_DIR}) + +include_directories(${THIRD_PARTY}/json/single_include/nlohmann) +include_directories(${THIRD_PARTY}/huawei_secure_c/include) + +add_library(profu STATIC ${UTIL_SRC}) +target_compile_options(profu PUBLIC -fPIC) +target_link_libraries(profu numa) diff --git a/util/common.cpp b/util/common.cpp new file mode 100644 index 0000000000000000000000000000000000000000..369ba9410f8cb338139116dd12ae7fcde7cde004 --- /dev/null +++ b/util/common.cpp @@ -0,0 +1,69 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Provide common file operation functions and system resource management functions. + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include "pcerrc.h" +#include "common.h" + +std::string GetRealPath(const std::string filePath) +{ + char resolvedPath[PATH_MAX]; + if (realpath(filePath.data(), resolvedPath) == nullptr) { + return std::string{}; + } + if (access(resolvedPath, R_OK) != 0) { + return std::string{}; + } + return resolvedPath; +} + +bool IsValidPath(const std::string& filePath) +{ + if (filePath.empty()) { + return false; + } + return true; +} + +int RaiseNumFd(unsigned long numFd) +{ + unsigned long extra = 50; + unsigned long setNumFd = extra + numFd; + struct rlimit currentlim; + if (getrlimit(RLIMIT_NOFILE, ¤tlim) == -1) { + return LIBPERF_ERR_RAISE_FD; + } + if (currentlim.rlim_cur > setNumFd) { + return SUCCESS; + } + if (currentlim.rlim_max < numFd) { + return LIBPERF_ERR_TOO_MANY_FD; + } + struct rlimit rlim { + .rlim_cur = currentlim.rlim_max, .rlim_max = currentlim.rlim_max, + }; + if (setNumFd < currentlim.rlim_max) { + rlim.rlim_cur = setNumFd; + } + if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) { + return LIBPERF_ERR_RAISE_FD; + } else { + return SUCCESS; + } +} \ No newline at end of file diff --git a/util/common.h b/util/common.h new file mode 100644 index 0000000000000000000000000000000000000000..a5c629a4261f0504f2dc7d32eed9503aa71967f0 --- /dev/null +++ b/util/common.h @@ -0,0 +1,24 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Provide common file operation functions and system resource management functions. + ******************************************************************************/ + +#ifndef LIBKPROF_COMMON_H +#define LIBKPROF_COMMON_H +#include + +std::string GetRealPath(const std::string filePath); +bool IsValidPath(const std::string& filePath); +int RaiseNumFd(uint64_t numFd); + +#endif // LIBKPROF_COMMON_H diff --git a/util/cpu_map.cpp b/util/cpu_map.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4443fb80b9247a042f6cc752788f2e15e93e548a --- /dev/null +++ b/util/cpu_map.cpp @@ -0,0 +1,104 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Get CPU topology and chip type. + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "pcerr.h" +#include "cpu_map.h" + +using namespace std; + +static const std::string CPU_TOPOLOGY_PACKAGE_ID = "/sys/bus/cpu/devices/cpu%d/topology/physical_package_id"; +static const std::string MIDR_EL1 = "/sys/devices/system/cpu/cpu0/regs/identification/midr_el1"; +static const std::string HIPA_ID = "0x00000000481fd010"; +static const std::string HIPB_ID = "0x00000000480fd020"; +static constexpr int PATH_LEN = 256; + +static CHIP_TYPE g_chipType = CHIP_TYPE::UNDEFINED_TYPE; + +static inline bool ReadCpuPackageId(int coreId, CpuTopology* cpuTopo) +{ + char filename[PATH_LEN]; + if (snprintf(filename, PATH_LEN, CPU_TOPOLOGY_PACKAGE_ID.c_str(), coreId) < 0) { + return false; + } + std::string realPath = GetRealPath(filename); + if (!IsValidPath(realPath)) { + return false; + } + std::ifstream packageFile(realPath); + if (!packageFile.is_open()) { + return false; + } + std::string packageId; + packageFile >> packageId; + try { + cpuTopo->socketId = std::stoi(packageId); + } catch (...) { + return false; + } + return true; +} + +struct CpuTopology* GetCpuTopology(int coreId) +{ + auto cpuTopo = std::unique_ptr(new CpuTopology()); + memset(cpuTopo.get(), 0, sizeof(CpuTopology)); + if (coreId == -1) { + cpuTopo->coreId = coreId; + cpuTopo->numaId = -1; + cpuTopo->socketId = -1; + return cpuTopo.release(); + } + + if (!ReadCpuPackageId(coreId, cpuTopo.get())) { + return nullptr; + } + + cpuTopo->coreId = coreId; + cpuTopo->numaId = numa_node_of_cpu(coreId); + return cpuTopo.release(); +} + +bool InitCpuType() +{ + static std::mutex mtx; + std::lock_guard lock(mtx); + std::ifstream cpuFile(MIDR_EL1); + std::string cpuId; + cpuFile >> cpuId; + if (cpuId.compare(HIPA_ID) == 0) { + g_chipType = CHIP_TYPE::HIPA; + } else if (cpuId.compare(HIPB_ID) == 0) { + g_chipType = CHIP_TYPE::HIPB; + } else { + pcerr::New(LIBPERF_ERR_CHIP_TYPE_INVALID, "invalid chip type"); + return false; + } + return true; +} + +CHIP_TYPE GetCpuType() +{ + if (g_chipType == UNDEFINED_TYPE && !InitCpuType()) { + return UNDEFINED_TYPE; + } + return g_chipType; +} diff --git a/util/cpu_map.h b/util/cpu_map.h new file mode 100644 index 0000000000000000000000000000000000000000..016fadb557a1ef32c8ece915385d0f845bcf0a0d --- /dev/null +++ b/util/cpu_map.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Get CPU topology and chip type. + ******************************************************************************/ +#ifndef CPU_MAP_H +#define CPU_MAP_H +#include +#include "pmu.h" +#ifdef __cplusplus +extern "C" { +#endif + +enum CHIP_TYPE { + UNDEFINED_TYPE = 0, + HIPA = 1, + HIPB = 2, +}; + +struct CpuTopology { + int coreId; + int numaId; + int socketId; +}; +struct CpuTopology* GetCpuTopology(int coreId); +CHIP_TYPE GetCpuType(); +#ifdef __cplusplus +} +#endif +#endif diff --git a/util/linked_list.h b/util/linked_list.h new file mode 100644 index 0000000000000000000000000000000000000000..2a2cd507ab36a06166bfa514a78d3472d2802b4d --- /dev/null +++ b/util/linked_list.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: A series of universal single -connection list operation functions are implemented.. + ******************************************************************************/ +#ifndef LINKED_LIST +#define LINKED_LIST +#include +#include +#include + +// Function to create a new node +template +ListNode* CreateNode() +{ + ListNode* newNode = (ListNode*)malloc(sizeof(ListNode)); + if (newNode == nullptr) { + return nullptr; + } + memset(newNode, 0, sizeof(ListNode)); + + newNode->next = nullptr; + return newNode; +} + +// Function to add a node at the head of the linked list +template +void addHead(ListNode** head, ListNode* newNode) +{ + newNode->next = *head; + *head = newNode; +} + +// Function to add a node at the tail of the linked list +template +void AddTail(ListNode** head, ListNode** newNode) +{ + if (*head == nullptr) { + *head = *newNode; + } else { + ListNode* current = *head; + while (current->next != nullptr) { + current = current->next; + } + current->next = *newNode; + } +} + +// Function to free the linked list +template +void FreeList(ListNode** head) +{ + ListNode* current = *head; + ListNode* nextNode; + + while (current != nullptr) { + nextNode = current->next; + free(current); + current = nextNode; + } + *head = nullptr; // Ensure the head is set to nullptr after freeing all nodes +} +#endif diff --git a/util/log.h b/util/log.h new file mode 100644 index 0000000000000000000000000000000000000000..2a860614dc6f371d10a1b05d8c114ffde04d064d --- /dev/null +++ b/util/log.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Used for code debugging. + ******************************************************************************/ +#ifndef PERF_LOG_H +#define PERF_LOG_H + +#ifdef NDEBUG +#define DBG_PRINT(...) +#define ERR_PRINT(...) +#else +#define DBG_PRINT(...) if (g_perfDebug) fprintf(stdout, __VA_ARGS__) +#define ERR_PRINT(...) if (g_perfDebug) fprintf(stderr, __VA_ARGS__) + +static int g_perfDebug = 0; + +__attribute__((constructor)) static void InitLog() +{ + auto debugEnv = getenv("PERF_DEBUG"); + if (debugEnv != nullptr && strcmp(debugEnv, "1") == 0) { + g_perfDebug = 1; + } +} +#endif + +#endif \ No newline at end of file diff --git a/util/pcerr.cpp b/util/pcerr.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad4fcb7f7fb3c74d9dfd9d7304db40b8b72a3bbf --- /dev/null +++ b/util/pcerr.cpp @@ -0,0 +1,86 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Error code mechanism, used to return error codes and error messages. + ******************************************************************************/ +#include +#include "pcerrc.h" +#include "pcerr.h" + +namespace pcerr { + static std::unordered_map defaultMsg = { + {SUCCESS, "success"}, + {COMMON_ERR_NOMEM, "not enough memory"}, + {LIBPERF_ERR_NO_AVAIL_PD, "no available pd for libperf"}, + {LIBPERF_ERR_CHIP_TYPE_INVALID, "invalid cpu arch"}, + {LIBPERF_ERR_FAIL_LISTEN_PROC, "failed to listen process"}, + {LIBPERF_ERR_INVALID_CPULIST, "invalid cpu list"}, + {LIBPERF_ERR_INVALID_PIDLIST, "invalid pid list"}, + {LIBPERF_ERR_INVALID_EVTLIST, "invalid event list"}, + {LIBPERF_ERR_INVALID_PD, "invalid pd"}, + {LIBPERF_ERR_INVALID_EVENT, "invalid event"}, + {LIBPERF_ERR_SPE_UNAVAIL, "spe unavailable"}, + {LIBPERF_ERR_FAIL_GET_CPU, "failed to get cpu info"}, + {LIBPERF_ERR_FAIL_GET_PROC, "failed to get process info"}, + {LIBPERF_ERR_NO_PERMISSION, "no permission to open pmu device"}, + {LIBPERF_ERR_DEVICE_BUSY, "pmu device is busy"}, + {LIBPERF_ERR_DEVICE_INVAL, "invalid event for pmu device"}, + {LIBPERF_ERR_FAIL_MMAP, "failed to mmap"}, + {LIBPERF_ERR_FAIL_RESOLVE_MODULE, "failed to resolve symbols"}, + {LIBPERF_ERR_INVALID_PID, "failed to find process by pid"}, + {LIBPERF_ERR_INVALID_TASK_TYPE, "invalid task type"}, + {LIBPERF_ERR_INVALID_TIME, "invalid sampling time"}, + {LIBPERF_ERR_NO_PROC, "no such process"}, + {LIBPERF_ERR_KERNEL_NOT_SUPPORT, "current pmu task is not supported by kernel"}, + {LIBPERF_ERR_TOO_MANY_FD, "too many open files"}, + {LIBPERF_ERR_RAISE_FD, "failed to setrlimit or getrlimit"}, + }; + + ProfErrorObj ProfErrorObj::profErrorObj; + + ProfErrorObj& ProfErrorObj::GetInstance() + { + return profErrorObj; + } + + void ProfErrorObj::SetProfError(const ProfError& profError) + { + this->profError = profError; + } + + void New(int code) + { + auto findMsg = defaultMsg.find(code); + if (findMsg != defaultMsg.end()) { + New(code, findMsg->second); + } else { + New(code, ""); + } + } + + void New(int code, const std::string& msg) + { + ProfError profError = std::make_shared(code, msg); + ProfErrorObj::GetInstance().SetProfError(profError); + } + +} // namespace pcerr + +int Perrorno() +{ + return pcerr::ProfErrorObj::GetInstance().GetProfError()->Code(); +} + +const char* Perror() +{ + return pcerr::ProfErrorObj::GetInstance().GetProfError()->Msg(); +} \ No newline at end of file diff --git a/util/pcerr.h b/util/pcerr.h new file mode 100644 index 0000000000000000000000000000000000000000..98f225c715e7dcfe3286e1e9f9ba278dd6c5006d --- /dev/null +++ b/util/pcerr.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Error code mechanism, used to return error codes and error messages. + ******************************************************************************/ +#ifndef SYMBOL_PCERR_H +#define SYMBOL_PCERR_H +#include +#include +#include +#include +#include "pcerrc.h" + +namespace pcerr { + namespace details { + struct BaseError; + } + + using ProfError = std::shared_ptr; + + namespace details { + struct BaseError : std::enable_shared_from_this { + virtual ~BaseError() = 0; + virtual const char* Msg() const + { + return nullptr; + } + virtual int Code() const + { + return 0; + } + ProfError GetThisError() + { + return shared_from_this(); + } + }; + inline BaseError::~BaseError() + {} + } // namespace details + + namespace details { + class CodeStrMsgError : public BaseError { + public: + CodeStrMsgError(int code, const std::string& msg) : m_code{code}, m_msg{msg} + {} + int Code() const override + { + return this->m_code; + }; + const char* Msg() const override + { + return this->m_msg.c_str(); + }; + + private: + int m_code; + std::string m_msg; + }; + } // namespace details + class ProfErrorObj { + public: + static ProfErrorObj& GetInstance(); + void SetProfError(const ProfError& profError); + ProfError& GetProfError() + { + if (profError == nullptr) { + profError = std::make_shared(0, "success"); + } + return profError; + } + + private: + ProfError profError; + static ProfErrorObj profErrorObj; + }; + + void [[nodiscard]] New(int code); + void [[nodiscard]] New(int code, const std::string& msg); +} // namespace pcerr + +#endif \ No newline at end of file diff --git a/util/process_map.cpp b/util/process_map.cpp new file mode 100644 index 0000000000000000000000000000000000000000..96da01f1986e55dd7ae6b2823a6fa6365c1db53b --- /dev/null +++ b/util/process_map.cpp @@ -0,0 +1,264 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Get process and thread information. + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "process_map.h" + +using namespace std; +constexpr int COMM_SIZE = 128; +constexpr int PATH_LEN = 1024; +unsigned int GetNumPid() +{ + DIR *directory = opendir("/proc"); + struct dirent *entry; + unsigned int count = 0; + if (directory == nullptr) { + perror("Error opening /proc directory"); + return -1; + } + + // Count the number of process directories (pidList) + while ((entry = readdir(directory))) { + // Check if the entry is a directory and represents a process ID + if (entry->d_type == DT_DIR && atoi(entry->d_name) != 0) { + count++; + } + } + closedir(directory); + return count; +} + +int *GetAllPids(unsigned int *count) +{ + DIR *directory; + struct dirent *entry; + int *pidList = nullptr; + directory = opendir("/proc"); + + *count = GetNumPid(); + + // Allocate memory for storing pidList + if ((*count) < SIZE_MAX / sizeof(int)) { + pidList = static_cast(malloc((*count) * sizeof(int))); + } + if (pidList == nullptr) { + perror("Memory allocation error"); + closedir(directory); + return nullptr; + } + + int index = 0; + while ((entry = readdir(directory))) { + if (entry->d_type == DT_DIR && atoi(entry->d_name) != 0) { + pidList[index++] = atoi(entry->d_name); + } + } + + closedir(directory); + return pidList; +} + +void FreeProcTopo(struct ProcTopology *procTopo) +{ + if (procTopo == nullptr) { + return; + } + if (procTopo->childPid != nullptr) { + free(procTopo->childPid); + procTopo->childPid = nullptr; + } + if (procTopo->comm != nullptr) { + free(procTopo->comm); + procTopo->comm = nullptr; + } + if (procTopo->exe != nullptr) { + free(procTopo->exe); + procTopo->exe = nullptr; + } + delete procTopo; +} + +static int GetTgid(pid_t pid) +{ + if (pid == -1) { + // for system sampling. + return -1; + } + // Get tgid from /proc//status. + std::string filePath = "/proc/" + std::to_string(pid) + "/status"; + std::string realPath = GetRealPath(filePath); + if (!IsValidPath(realPath)) { + return -1; + } + std::ifstream statusFile(realPath); + if (!statusFile.is_open()) { + return -1; + } + string token; + bool foundTgid = false; + while (!statusFile.eof()) { + if (!statusFile.is_open()) { + return -1; + } + statusFile >> token; + if (token == "Tgid:") { + foundTgid = true; + continue; + } + if (foundTgid) { + return stoi(token); + } + } + return -1; +} + +static char *GetComm(pid_t pid) +{ + std::string commName; + if (pid == -1) { + commName = "system"; + char *comm = static_cast(malloc(commName.length() + 1)); + if (comm == nullptr) { + return nullptr; + } + strcpy(comm, commName.c_str()); + return comm; + } + std::string filePath = "/proc/" + std::to_string(pid) + "/comm"; + std::string realPath = GetRealPath(filePath); + if (!IsValidPath(realPath)) { + return nullptr; + } + std::ifstream commFile(realPath); + if (!commFile.is_open()) { + return nullptr; + } + commFile >> commName; + char *comm = static_cast(malloc(commName.length() + 1)); + if (comm == nullptr) { + return nullptr; + } + strcpy(comm, commName.c_str()); + return comm; +} + +struct ProcTopology *GetProcTopology(pid_t pid) +{ + unique_ptr procTopo(new ProcTopology{0}, FreeProcTopo); + procTopo->tid = pid; + try { + // Get tgid, i.e., process id. + procTopo->pid = GetTgid(pid); + if (pid != -1 && procTopo->pid == -1) { + return nullptr; + } + // Get command name. + procTopo->comm = GetComm(procTopo->pid); + if (procTopo->comm == nullptr) { + return nullptr; + } + } catch (exception&) { + return nullptr; + } + + return procTopo.release(); +} + +// Check if a string represents a valid integer +int IsValidInt(const char *str) +{ + while (*str) { + if (!isdigit(*str)) { + return 0; + } + str++; + } + return 1; +} + +void StoreThreadId(int** childTidList, int* count, const char* entryName) +{ + (*count)++; + int* newChildTidList = new int[(*count)]; + if (newChildTidList != nullptr) { + if (*childTidList != nullptr) { + std::copy(*childTidList, *childTidList + (*count) - 1, newChildTidList); + delete[] *childTidList; + } + newChildTidList[(*count) - 1] = atoi(entryName); + *childTidList = newChildTidList; + } +} + +bool GetChildTidRecursive(const char *dirPath, int **childTidList, int *count) +{ + DIR *dir = opendir(dirPath); + if (!dir) { + return false; + } + + struct dirent *entry; + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_type == DT_DIR) { + // Skip "." and ".." directories + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // Check if the entry name is a valid thread ID + if (IsValidInt(entry->d_name)) { + // Store the thread ID in the array + StoreThreadId(childTidList, count, entry->d_name); + } + + char path[PATH_LEN]; + if (snprintf(path, sizeof(path), "%s/%s", dirPath, entry->d_name) < 0) { + continue; + } + + // Continue recursively + GetChildTidRecursive(path, childTidList, count); + } + } + + closedir(dir); + return true; +} + +int *GetChildTid(int pid, int *numChild) +{ + int *childTidList = nullptr; + char dirPath[PATH_LEN]; + if (snprintf(dirPath, sizeof(dirPath), "/proc/%d/task", pid) < 0) { + return nullptr; + } + *numChild = 0; + if (!GetChildTidRecursive(dirPath, &childTidList, numChild)) { + if (childTidList) { + delete[] childTidList; + childTidList = nullptr; + } + *numChild = 0; + } + return childTidList; +} + diff --git a/util/process_map.h b/util/process_map.h new file mode 100644 index 0000000000000000000000000000000000000000..937d9927c40d3c49aa8494e37548b43b65168b9a --- /dev/null +++ b/util/process_map.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Get process and thread information. + ******************************************************************************/ +#ifndef PROCESS_MAP_H +#define PROCESS_MAP_H +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +struct ProcTopology* GetProcTopology(pid_t pid); +void FreeProcTopo(struct ProcTopology *procTopo); +int* GetAllPids(int* count); +unsigned int GetNumPid(); +int* GetChildTid(int pid, int* numChild); +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/util/safe_handler.h b/util/safe_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..302a5eb7c87607a1bc25e43b94fb7e70a437f43e --- /dev/null +++ b/util/safe_handler.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Provide the key value processing function of thread security. + ******************************************************************************/ +#ifndef SAFE_HANDLER_H +#define SAFE_HANDLER_H +#include +#include +#include +#include + +template +class SafeHandler { +public: + SafeHandler() + {} + void tryLock(const Key& key) + { + while (true) { + if (find(key)) { + continue; + } + { + std::unique_lock lock(_mutex); + if (find(key)) { + continue; + } + insert(key); + } + break; + } + } + + void releaseLock(const Key& key) + { + std::unique_lock lock(_smutex); + _set.erase(key); + } + +private: + std::mutex _mutex; + std::mutex _smutex; + std::unordered_set _set; + + void insert(const Key& key) + { + std::unique_lock lock(_smutex); + _set.insert(key); + } + + bool find(const Key& key) + { + std::unique_lock lock(_smutex); + if (_set.find(key) != _set.end()) { + return true; + } + return false; + } +}; + +#endif + diff --git a/util/util_time.cpp b/util/util_time.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8f63e8342c804db3e270fca51a6303088642614e --- /dev/null +++ b/util/util_time.cpp @@ -0,0 +1,24 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Get current time. + ******************************************************************************/ +#include +#include "util_time.h" + +int64_t GetCurrentTime() +{ + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); +} + diff --git a/util/util_time.h b/util/util_time.h new file mode 100644 index 0000000000000000000000000000000000000000..42a3d280a79b55ec93dd185e277f4b811f7ee034 --- /dev/null +++ b/util/util_time.h @@ -0,0 +1,26 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * gala-gopher 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.Wang + * Create: 2024-04-03 + * Description: Get current time. + ******************************************************************************/ +#ifndef UTIL_TIME_H +#define UTIL_TIME_H +#ifdef __cplusplus +#include +#include +extern "C" { +#endif +int64_t GetCurrentTime(); +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file