From baf6a16c6b5494179073104b286260261fda2bce Mon Sep 17 00:00:00 2001 From: wuying39 <921169248@qq.com> Date: Thu, 10 Jul 2025 19:36:20 +0800 Subject: [PATCH] cgroup supprot v2 and SPE && add UT --- docs/C_C++_API.md | 5 + docs/Details_Usage.md | 97 +++++++++++++- docs/Go_API.md | 6 + docs/Python_API.md | 5 + go/src/libkperf_test/libkperf_test.go | 10 +- pmu/pmu.cpp | 41 ++++-- pmu/spe.cpp | 8 +- test/test_perf/test_api.cpp | 82 ++---------- test/test_perf/test_cgroup.cpp | 179 ++++++++++++++++++++++++++ util/common.cpp | 16 ++- util/common.h | 3 + util/pcerr.cpp | 1 + 12 files changed, 363 insertions(+), 90 deletions(-) create mode 100644 test/test_perf/test_cgroup.cpp diff --git a/docs/C_C++_API.md b/docs/C_C++_API.md index f14919a..57d9f67 100644 --- a/docs/C_C++_API.md +++ b/docs/C_C++_API.md @@ -81,6 +81,10 @@ branchSampleFilter对应的比特值,使用方式: attr.branchSampleFilter = KPERF_SAMPLE_BRANCH_USER | KPERF_SAMPLE_BRANCH_ANY; 仅支持SAMPLING模式, 其中KPERF_SAMPLE_BRANCH_USER, KPERF_SAMPLE_BRANCH_KERNEL, KPERF_SAMPLE_BRANCH_HV使用时必须搭配KPERF_SAMPLE_BRANCH_ANY或者KPERF_SAMPLE_BRANCH_ANY之后的值一起使用 + * char** cgroupNameList; + 采集cgroup的名称列表 + * unsigned numCgroup; + 采集cgroup的个数 * 返回值 > 0 初始化成功 返回值 = -1 初始化失败,可通过Perror()查看错误信息 @@ -150,6 +154,7 @@ * unsigned long nr branchRecords的数量 * struct BranchSampleRecord *branchRecords branch指针数组 * struct SampleRawData rawData: tracepointer数据指针 + * const char* cgroupName: cgroup名称 * 返回值 = 0 且 pmuData为空 采集时间内无可用数据 返回值 != 0 且 pmuData为空 采集时发生错误,无法读取数据 diff --git a/docs/Details_Usage.md b/docs/Details_Usage.md index 353de28..284c1c8 100644 --- a/docs/Details_Usage.md +++ b/docs/Details_Usage.md @@ -21,6 +21,9 @@ PmuAttr attr = {0}; attr.evtList = evtList; attr.numEvt = 2; int pd = PmuOpen(COUNTING, &attr); +if (pd == -1) { + printf("kperf pmuopen counting failed: ", Perror()); +} ``` ```python @@ -1435,4 +1438,96 @@ pmu_hotspot 5 1 1 1、只支持SAMPLING模式采集 -2、只支持对进程分析,不支持对系统分析 \ No newline at end of file +2、只支持对进程分析,不支持对系统分析 + +### 采集cgroup +libkperf支持对cgroup v1、v2的采集和采样,类似于perf的如下命令: +``` +perf stat -e cycles,instructions -G test_cgroup +perf record -e arm_spe_0/load_filter=1/ -G test_cgroup +``` +其中counting和sampling模式下支持多个cgroup同时采集多个事件,SPE模式仅支持指定单个事件和单个cgroup。 +对于嵌套的cgroup,请使用完整的层级路径格式(父控制组/子控制组),例如"parent_cgroup/child_cgroup"。 + +以counting模式为例,可以像这样设置PmuAttr: +```c++ +// c++代码示例 +#include + +#include "symbol.h" +#include "pmu.h" +#include "pcerrc.h" + +PmuAttr attr = {0}; +char *evtList[2] = {"cycles", "instructions"}; +attr.evtList = evtList; +attr.numEvt = 2; +char *cgroupNames[2] = {"test_cgroup", "my_cgroup"}; +attr.cgroupNameList = cgroupNames; +attr.numCgroup = 2; +int pd = PmuOpen(COUNTING, &attr); +if (pd == -1) { + printf("kperf pmuopen counting failed: %s\n", Perror()); +} +PmuEnable(pd); +sleep(1); +PmuDisable(pd); +PmuData* data = nullptr; +int len = PmuRead(pd, &data); +//counting模式下data长度为numCgroup * cpu * numEvt +for (int i = 0; i < len; i++) { + printf("evt=%s, cgroup=%s, cpu=%d, count=%d\n", data[i].evt, data[i].cgroupName, data[i].cpu, data[i].count); +} +PmuClose(pd); +``` + +```python +# python代码示例 +import kperf +import time + +evtList = ["cycles", "instructions"] +cgroupNames = ["test_cgroup", "my_cgroup"] +pmu_attr = kperf.PmuAttr(evtList=evtList, cgroupNameList=cgroupNames) +pd = kperf.open(kperf.PmuTaskType.COUNTING, pmu_attr) +if pd == -1: + print(kperf.error()) + excit(1) +kperf.enable(pd) +time.sleep(1) +kperf.disable(pd) +pmu_data = kperf.read(pd) +for item in pmu_data.iter: + print(f"evt={item.evt} cgroup={item.cgroupName} cpu={item.cpu} count={item.count}") +kperf.close(pd) +``` + +```go +// go代码示例 +import "libkperf/kperf" +import "fmt" +import "time" + +func main() { + evtList := []string{"cycles", "instructions"} + cgroupNames := []string{"test_cgroup", "my_cgroup"} + attr := kperf.PmuAttr{EvtList:evtList, CgroupNameList:cgroupNames} + pd, err := kperf.PmuOpen(kperf.COUNT, attr) + if err != nil { + fmt.Printf("kperf pmuopen counting failed, expect err is nil, but is %v\n", err) + return + } + kperf.PmuEnable(pd) + time.Sleep(time.Second) + kperf.PmuDisable(pd) + dataVo, err := kperf.PmuRead(pd) + if err != nil { + fmt.Printf("kperf pmuread failed, expect err is nil, but is %v\n", err) + return + } + for _, o := range dataVo.GoData { + fmt.Printf("evt=%v cgroup=%v cpu=%v count=%v \n", o.Evt, o.CgroupName, o.Cpu, o.Count) + } + kperf.PmuClose(pd) +} +``` \ No newline at end of file diff --git a/docs/Go_API.md b/docs/Go_API.md index 413b10e..6a573e6 100644 --- a/docs/Go_API.md +++ b/docs/Go_API.md @@ -72,6 +72,11 @@ func PmuOpen(collectType C.enum_PmuTaskType, attr PmuAttr) (int, error) branchSampleMode = kperf.BranchSampleFilter.KPERF_SAMPLE_BRANCH_ANY | kperf.BranchSampleFilter.KPERF_SAMPLE_BRANCH_USER pmu_attr = kperf.PmuAttr(sampleRate=1000, useFreq=True, pidList=pidList, evtList=evtList, branchSampleFilter=branchSampleMode) 仅支持SAMPLING模式, 其中KPERF_SAMPLE_BRANCH_USER, KPERF_SAMPLE_BRANCH_KERNEL, KPERF_SAMPLE_BRANCH_HV使用时必须搭配KPERF_SAMPLE_BRANCH_ANY或者KPERF_SAMPLE_BRANCH_ANY之后的值一起使用 + * char** cgroupNameList; + 采集cgroup的名称列表 + * int numCgroup; + 采集cgroup的个数 + * 返回值是int,error, 如果error不等于nil,则返回的int值为对应采集任务ID ```go @@ -151,6 +156,7 @@ func PmuRead(fd int) (PmuDataVo, error) * Va uint64 虚拟地址 * Event uint64 混合事件的比特位 * Lat uint16 调度操作到执行操作的周期数 + * cgroupName cgroup名称 ```go //go 代码示例 diff --git a/docs/Python_API.md b/docs/Python_API.md index 26389a2..15b8f24 100644 --- a/docs/Python_API.md +++ b/docs/Python_API.md @@ -75,6 +75,10 @@ kperf.open(collector_type: kperf.PmuTaskType, pmu_attr: kperf.PmuAttr) branchSampleMode = kperf.BranchSampleFilter.KPERF_SAMPLE_BRANCH_ANY | kperf.BranchSampleFilter.KPERF_SAMPLE_BRANCH_USER pmu_attr = kperf.PmuAttr(sampleRate=1000, useFreq=True, pidList=pidList, evtList=evtList, branchSampleFilter=branchSampleMode) 仅支持SAMPLING模式, 其中KPERF_SAMPLE_BRANCH_USER, KPERF_SAMPLE_BRANCH_KERNEL, KPERF_SAMPLE_BRANCH_HV使用时必须搭配KPERF_SAMPLE_BRANCH_ANY或者KPERF_SAMPLE_BRANCH_ANY之后的值一起使用 + * cgroupNameList; + 采集cgroup的名称列表 + * numCgroup; + 采集cgroup的个数 * 返回值是int值 fd > 0 成功初始化 fd == -1 初始化失败,可通过 kperf.error()查看错误信息 @@ -160,6 +164,7 @@ pd为kperf.open返回值 * iter brbe数据迭代器 * len brbe数据长度 * rawData: tracepointer数据指针,搭配kperf.get_field和Kperf.get_field_exp使用 + * cgroupName cgroup名称 以下为kperf.read示例 diff --git a/go/src/libkperf_test/libkperf_test.go b/go/src/libkperf_test/libkperf_test.go index 1139313..ff08429 100644 --- a/go/src/libkperf_test/libkperf_test.go +++ b/go/src/libkperf_test/libkperf_test.go @@ -327,8 +327,14 @@ func TestResolvePmuDataSymbol(t *testing.T) { func TestCgroupNameList(t *testing.T) { - groupPath := "/sys/fs/cgroup/perf_event/testGocgroup" - + cgroupV2File := "/sys/fs/cgroup/cgroup.controllers" + _, err := os.Stat(cgroupV2File) + groupPath := "/sys/fs/cgroup" + if os.IsNotExist(err) { + groupPath += "/perf_event/testGocgroup" //cgroup v1 + } else { + groupPath += "/testGocgroup" + } _, statErr := os.Stat(groupPath) if statErr != nil { err := os.Mkdir(groupPath, 0755) diff --git a/pmu/pmu.cpp b/pmu/pmu.cpp index 28a72a3..6a65845 100644 --- a/pmu/pmu.cpp +++ b/pmu/pmu.cpp @@ -38,6 +38,7 @@ static unordered_map runningStatus; static SafeHandler pdMutex; static pair uncoreEventPair; static std::set onLineCpuIds; +static string cgroupDir = "/sys/fs/cgroup/"; struct PmuTaskAttr* AssignPmuTaskParam(PmuTaskType collectType, struct PmuAttr *attr); @@ -171,12 +172,18 @@ static int CheckBranchSampleFilter(const unsigned long& branchSampleFilter, enum static int CheckCgroupNameList(unsigned numCgroup, char** cgroupName) { if (numCgroup > 0 && cgroupName == nullptr) { - New(LIBPERF_ERR_INVALID_CGROUP_LIST, "Invalid cgroup name list: numcgroup is greater than 0, but cgroupName is null"); + New(LIBPERF_ERR_INVALID_CGROUP_LIST, "Invalid cgroup name list: numCgroup is greater than 0, but cgroupNameList is null"); return LIBPERF_ERR_INVALID_CGROUP_LIST; } - + int cgroupIsV2 = CheckCgroupV2(); + if (cgroupIsV2 == -1) { + New(LIBPERF_ERR_INVALID_CGROUP_LIST, "The cgroup mount does not exist or has no permission to access"); + return LIBPERF_ERR_INVALID_CGROUP_LIST; + } else if (!cgroupIsV2) { // cgroup v1 + cgroupDir += "perf_event/"; + } for (unsigned i = 0; i < numCgroup; i++) { - std::string cgroupPath = "/sys/fs/cgroup/perf_event/" + string(cgroupName[i]); + std::string cgroupPath = cgroupDir + string(cgroupName[i]); int cgroupFd = open(cgroupPath.c_str(), O_RDONLY); if (cgroupFd < 0) { New(LIBPERF_ERR_OPEN_INVALID_FILE, "open " + cgroupPath + " failed."); @@ -231,6 +238,14 @@ static int CheckCollectTypeConfig(enum PmuTaskType collectType, struct PmuAttr * New(LIBPERF_ERR_INVALID_GROUP_SPE); return LIBPERF_ERR_INVALID_GROUP_SPE; } + if (attr->cgroupNameList != nullptr && attr->pidList != nullptr) { + New(LIBPERF_ERR_INVALID_CGROUP_LIST, "Cannot specify both cgroup and pid. Please use only one."); + return LIBPERF_ERR_INVALID_CGROUP_LIST; + } + if (collectType == SPE_SAMPLING && attr->numCgroup > 1) { + New(LIBPERF_ERR_INVALID_CGROUP_LIST, "SPE mode only support one cgroup"); + return LIBPERF_ERR_INVALID_CGROUP_LIST; + } return SUCCESS; } @@ -858,7 +873,7 @@ static void PrepareCpuList(PmuAttr *attr, PmuTaskAttr *taskParam, PmuEvt* pmuEvt } int GetCgroupFd(std::string& cgroupName) { - std::string cgroupPath = "/sys/fs/cgroup/perf_event/" + cgroupName; + std::string cgroupPath = cgroupDir + cgroupName; int cgroupFd = open(cgroupPath.c_str(), O_RDONLY); if (cgroupFd < 0) { New(LIBPERF_ERR_OPEN_INVALID_FILE, "open " + cgroupPath + " failed."); @@ -926,7 +941,7 @@ static struct PmuTaskAttr* AssignTaskParam(PmuTaskType collectType, PmuAttr *att bool InitCgroupFds(struct PmuAttr *attr, std::unordered_map& cgroupFds) { for (int i = 0; i < attr->numCgroup; ++i) { std::string cgroupName = attr->cgroupNameList[i]; - int fd = GetCgroupFd (cgroupName); + int fd = GetCgroupFd(cgroupName); if (fd == -1) { for (auto iter = cgroupFds.begin(); iter != cgroupFds.end(); iter++) { close(iter->second); @@ -941,17 +956,17 @@ bool InitCgroupFds(struct PmuAttr *attr, std::unordered_map& c 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, 0, nullptr, -1); - return taskParam; - } if (attr->numCgroup > 0) { std::unordered_map cgroupFds; if (!InitCgroupFds(attr, cgroupFds)) { return nullptr; } - + if (collectType == SPE_SAMPLING) { + std::string cgroupName(attr->cgroupNameList[0]); + // evtList is nullptr, cannot loop over evtList. + taskParam = AssignTaskParam(collectType, attr, nullptr, 0, cgroupName.c_str(), cgroupFds[cgroupName]); + return taskParam; + } for (int i = 0; i < attr->numCgroup; ++i) { std::string cgroupName(attr->cgroupNameList[i]); for (int j = 0; j < attr->numEvt; ++j) { @@ -963,6 +978,10 @@ struct PmuTaskAttr* AssignPmuTaskParam(enum PmuTaskType collectType, struct PmuA } } } else { + if (collectType == SPE_SAMPLING) { + taskParam = AssignTaskParam(collectType, attr, nullptr, 0, nullptr, -1); + return taskParam; + } for (int i = 0; i < attr->numEvt; ++i) { struct PmuTaskAttr* current = AssignTaskParam(collectType, attr, attr->evtList[i], attr->evtAttr[i].groupId, nullptr, -1); if (current == nullptr) { diff --git a/pmu/spe.cpp b/pmu/spe.cpp index a8cc7d9..a461ab2 100644 --- a/pmu/spe.cpp +++ b/pmu/spe.cpp @@ -66,7 +66,13 @@ static int OpenSpeEvent(PmuEvt *pmuAttr, int cpu) attr.exclude_kernel = pmuAttr->excludeKernel; attr.exclude_user = pmuAttr->excludeUser; - return PerfEventOpen(&attr, -1, cpu, -1, 0); + unsigned flags = 0; + pid_t pid = -1; + if (!pmuAttr->cgroupName.empty()) { + flags = PERF_FLAG_PID_CGROUP | PERF_FLAG_FD_CLOEXEC; + pid = pmuAttr->cgroupFd; + } + return PerfEventOpen(&attr, pid, cpu, -1, flags); } static int OpenDummyEvent(int cpu) diff --git a/test/test_perf/test_api.cpp b/test/test_perf/test_api.cpp index 929ff5e..504547a 100644 --- a/test/test_perf/test_api.cpp +++ b/test/test_perf/test_api.cpp @@ -698,7 +698,7 @@ TEST_F(TestAPI, InvalidCgroupNameList) } -TEST_F(TestAPI, cgroupNameNotExist) +TEST_F(TestAPI, NotExistCgroupName) { auto attr = GetPmuAttribute(); char* cgroupName[1]; @@ -709,80 +709,14 @@ TEST_F(TestAPI, cgroupNameNotExist) ASSERT_EQ(pd, -1); } -TEST_F(TestAPI, ValidCgroupName) +TEST_F(TestAPI, InvalidCgroupNameListSPE) { - const std::string cgroupPath= "/sys/fs/cgroup/perf_event/testcgroup"; - if (mkdir(cgroupPath.c_str(), 0755) != 0) { - std::cout << "make testcgroup failed" << std::endl; - return; - } auto attr = GetPmuAttribute(); - char* cgroupName[1]; - cgroupName[0] = (char*)"testcgroup"; - attr.cgroupNameList = cgroupName; - attr.numCgroup = 1; - auto pd = PmuOpen(SAMPLING, &attr); - ASSERT_GT(pd, 0); - if (rmdir(cgroupPath.c_str()) != 0) { - std::cout << "remove testcgroup failed" << std::endl; - } -} - -TEST_F(TestAPI, cgroupNameSampleNormal) -{ - // 创建cgroup - const std::string cgroupPath= "/sys/fs/cgroup/perf_event/testcgroup"; - if (mkdir(cgroupPath.c_str(), 0755) != 0) { - std::cout << "make testcgroup failed" << std::endl; - return; - } - - // 向cgroup中写入进程pid - pid_t pid = getpid(); - std::stringstream ss; - ss << pid; - const std::string cgroupProc = cgroupPath + "/cgroup.procs"; - std::ofstream ofs(cgroupProc); - if (!ofs) { - std::cout << "open cgroup proc: " << cgroupProc << " failed " << std::endl; - if (rmdir(cgroupPath.c_str()) != 0) { - std::cout << "remove testcgroup failed" << std::endl; - } - return; - } - ofs << ss.str(); - - auto attr = GetPmuAttribute(); - char* cgroupName[1]; - cgroupName[0] = (char*)"testcgroup"; + char* cgroupName[2]; + cgroupName[0] = (char*)"mygrouptest"; + cgroupName[1] = (char*)"mygrouptest1"; attr.cgroupNameList = cgroupName; - attr.numCgroup = 1; - auto pd = PmuOpen(SAMPLING, &attr); - if (pd <= 0) { - if (rmdir(cgroupPath.c_str()) != 0) { - std::cout << "remove testcgroup failed" << std::endl; - } - } - - PmuEnable(pd); - PmuData* pmuData = nullptr; - int len = PmuRead(pd, &pmuData); - if (len == -1) { - std::cerr << "error msg:" << Perror() << std::endl; - if (rmdir(cgroupPath.c_str()) != 0) { - std::cout << "remove testcgroup failed" << std::endl; - } - return; - } - - PmuData data = pmuData[0]; - ASSERT_EQ(data.cgroupName, "testcgroup"); - ASSERT_GT(data.period, 0); - PmuDataFree(pmuData); - PmuDisable(pd); - PmuClose(pd); - - if (rmdir(cgroupPath.c_str()) != 0) { - std::cout << "remove testcgroup failed" << std::endl; - } + attr.numCgroup = 2; + auto pd = PmuOpen(SPE_SAMPLING, &attr); + ASSERT_EQ(pd, -1); } \ No newline at end of file diff --git a/test/test_perf/test_cgroup.cpp b/test/test_perf/test_cgroup.cpp new file mode 100644 index 0000000..f68db17 --- /dev/null +++ b/test/test_perf/test_cgroup.cpp @@ -0,0 +1,179 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. + * libkperf licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + * Author: Ms.Wu + * Create: 2025-7-9 + * Description: Unit tests for cgroup collection. + ******************************************************************************/ + +#include "test_common.h" +#include "common.h" +#include +#include + +using namespace std; + +class TestCgroup : public testing::Test { +public: + string cgroupPath; + + void SetUp() + { + // Create a simple process. + demoPid = RunTestApp(exePath); + + // Set cgroup directory path. + cgroupPath = "/sys/fs/cgroup"; + if(!CheckCgroupV2()) { // cgroup v1 + cgroupPath += "/perf_event"; + } + } + + void TearDown() + { + KillApp(demoPid); + if (data != nullptr) { + PmuDataFree(data); + data = nullptr; + } + PmuClose(pd); + for (int i = 0; i< pdNums; ++i) { + PmuClose(pds[i]); + } + rmdir(cgroupPath.c_str()); + } + +protected: + PmuAttr GetPmuAttribute() + { + PmuAttr attr = {0}; + attr.evtList = evtList; + attr.numEvt = numEvt; + attr.symbolMode = RESOLVE_ELF_DWARF; + return attr; + } + +protected: + static const string exePath; + static pid_t demoPid; + + static const unsigned numEvt = 1; + static const string expectFilename; + static const unsigned collectInterval = 100; + + char *evtList[numEvt] = {"cycles"}; + int pd = 0; + static const int pdNums = 2; + int pds[pdNums] = {0}; + PmuData *data = nullptr; +}; + +pid_t TestCgroup::demoPid; +const string TestCgroup::exePath = "simple"; +const string TestCgroup::expectFilename = "simple.cpp"; + +TEST_F(TestCgroup, ValidCgroupName) +{ + cgroupPath += "/testcgroup"; + ASSERT_EQ(mkdir(cgroupPath.c_str(), 0755), 0); + auto attr = GetPmuAttribute(); + char* cgroupName[1]; + cgroupName[0] = (char*)"testcgroup"; + attr.cgroupNameList = cgroupName; + attr.numCgroup = 1; + auto pd = PmuOpen(SAMPLING, &attr); + ASSERT_GT(pd, 0); +} + +TEST_F(TestCgroup, TestCgroupCounting) { + cgroupPath += "/cgroupCounting"; + ASSERT_EQ(mkdir(cgroupPath.c_str(), 0755), 0); + const string cgroupProc = cgroupPath + "/cgroup.procs"; + ofstream ofs(cgroupProc); + ASSERT_TRUE(ofs.is_open()); + ofs << demoPid; + ofs.flush(); + ASSERT_FALSE(ofs.fail()); + ofs.close(); + + auto attr = GetPmuAttribute(); + char* cgroupName[1]; + cgroupName[0] = (char*)"cgroupCounting"; + attr.cgroupNameList = cgroupName; + attr.numCgroup = 1; + + pd = PmuOpen(COUNTING, &attr); + ASSERT_NE(pd, -1); + int ret = PmuCollect(pd, 1000, collectInterval); + ASSERT_EQ(ret, SUCCESS); + int len = PmuRead(pd, &data); + ASSERT_GT(len, 0); + ASSERT_STREQ(data[0].cgroupName, "cgroupCounting"); +} + +TEST_F(TestCgroup, TestCgroupSampling) +{ + cgroupPath += "/cgroupSampling"; + ASSERT_EQ(mkdir(cgroupPath.c_str(), 0755), 0); + const string cgroupProc = cgroupPath + "/cgroup.procs"; + ofstream ofs(cgroupProc); + ASSERT_TRUE(ofs.is_open()); + ofs << demoPid; + ofs.flush(); + ASSERT_FALSE(ofs.fail()); + ofs.close(); + + auto attr = GetPmuAttribute(); + attr.period = 1000; + attr.dataFilter = SPE_DATA_ALL; + attr.evFilter = SPE_EVENT_RETIRED; + attr.minLatency = 0x40; + char* cgroupName[1]; + cgroupName[0] = (char*)"cgroupSampling"; + attr.cgroupNameList = cgroupName; + attr.numCgroup = 1; + + pd = PmuOpen(SAMPLING, &attr); + ASSERT_NE(pd, -1); + int ret = PmuCollect(pd, 5000, collectInterval); + int len = PmuRead(pd, &data); + ASSERT_GT(len, 0); + ASSERT_STREQ(data[0].cgroupName, "cgroupSampling"); + ASSERT_GT(data[0].period, 0); +} + +TEST_F(TestCgroup, TestCgroupSPE) +{ + cgroupPath += "/cgroupSPE"; + ASSERT_EQ(mkdir(cgroupPath.c_str(), 0755), 0); + const string cgroupProc = cgroupPath + "/cgroup.procs"; + ofstream ofs(cgroupProc); + ASSERT_TRUE(ofs.is_open()); + ofs << demoPid; + ofs.flush(); + ASSERT_FALSE(ofs.fail()); + ofs.close(); + + auto attr = GetPmuAttribute(); + attr.dataFilter = LOAD_FILTER; + attr.period = 8192; + char* cgroupName[1]; + cgroupName[0] = (char*)"cgroupSPE"; + attr.cgroupNameList = cgroupName; + attr.numCgroup = 1; + + pd = PmuOpen(SAMPLING, &attr); + ASSERT_NE(pd, -1); + int ret = PmuCollect(pd, 5000, collectInterval); + int len = PmuRead(pd, &data); + ASSERT_GT(len, 0); + ASSERT_STREQ(data[0].cgroupName, "cgroupSPE"); + ASSERT_GT(data[0].period, 0); +} \ No newline at end of file diff --git a/util/common.cpp b/util/common.cpp index c157a5a..299ac36 100644 --- a/util/common.cpp +++ b/util/common.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "pcerrc.h" #include "pcerr.h" #include "common.h" @@ -151,9 +152,22 @@ std::string GetTraceEventDir() return ""; } -bool StartWith(const std::string& str, const std::string& prefix) { +bool StartWith(const std::string& str, const std::string& prefix) +{ if (str.size() < prefix.size()) { return false; } return str.substr(0, prefix.size()) == prefix; +} + +int CheckCgroupV2() +{ + const char *mnt = "/sys/fs/cgroup"; + struct statfs stbuf; + + if (statfs(mnt, &stbuf) < 0) { + return -1; + } + + return (stbuf.f_type == CGROUP2_SUPER_MAGIC); } \ No newline at end of file diff --git a/util/common.h b/util/common.h index caa52a3..cc59c70 100644 --- a/util/common.h +++ b/util/common.h @@ -28,6 +28,8 @@ #error "Only the x86_64 and aarch64 architecture are supported." #endif +#define CGROUP2_SUPER_MAGIC 0x63677270 + const std::string TRACE_EVENT_PATH = "/sys/kernel/tracing/events/"; const std::string TRACE_DEBUG_EVENT_PATH = "/sys/kernel/debug/tracing/events/"; @@ -48,5 +50,6 @@ int RaiseNumFd(uint64_t numFd); bool ExistPath(const std::string& filePath); std::string GetTraceEventDir(); bool StartWith(const std::string& str, const std::string& prefix); +int CheckCgroupV2(); #endif // LIBKPROF_COMMON_H diff --git a/util/pcerr.cpp b/util/pcerr.cpp index 3efc851..22b4470 100644 --- a/util/pcerr.cpp +++ b/util/pcerr.cpp @@ -55,6 +55,7 @@ namespace pcerr { {LIBPERF_ERR_RESET_FD, "failed to reset fd output"}, {LIBPERF_ERR_SET_FD_RDONLY_NONBLOCK, "failed to set fd readonly and nonbolock"}, {LIBPERF_ERR_INTERFACE_NOT_SUPPORT_X86, "the current interface does not support x86"}, + {LIBPERF_ERR_INVALID_CGROUP_LIST, "invalid cgroup name list"}, }; static std::unordered_map warnMsgs = { {LIBPERF_WARN_CTXID_LOST, "Some SPE context packets are not found in the traces."}, -- Gitee