Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dynolog/src/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <cstdint>

#define MAX_CPU_SOCKETS 8
#define MAX_NUMA_NODES 2

namespace dynolog {

Expand Down
81 changes: 81 additions & 0 deletions hbt/src/common/System.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,87 @@ std::vector<std::vector<uint32_t>> getSocketCoreMapFromSysfs(
return socketCoreMap;
}

namespace {

std::optional<uint32_t> readUintFile(const std::filesystem::path& p) {
std::ifstream f(p);
uint32_t v = 0;
if (f.is_open() && (f >> v)) {
return v;
}
return std::nullopt;
}

// The L3 cache (CCX) id for a CPU: the cache/index* entry whose level is 3.
std::optional<uint32_t> readCpuL3CacheId(const std::filesystem::path& cpuDir) {
const std::filesystem::path cacheDir = cpuDir / "cache";
if (!std::filesystem::exists(cacheDir)) {
return std::nullopt;
}
for (const auto& idx : std::filesystem::directory_iterator(cacheDir)) {
if (idx.path().filename().string().find("index") != 0) {
continue;
}
// level for L3 cache
if (readUintFile(idx.path() / "level") == 3u) {
return readUintFile(idx.path() / "id");
}
}
return std::nullopt;
}

// The NUMA node for a CPU: the node<N> entry under the cpu directory (a symlink
// on a real host, matched here by name only).
std::optional<uint32_t> readCpuNumaNode(const std::filesystem::path& cpuDir) {
if (!std::filesystem::exists(cpuDir)) {
return std::nullopt;
}
for (const auto& entry : std::filesystem::directory_iterator(cpuDir)) {
const std::string name = entry.path().filename().string();
if (name.find("node") != 0 || name.length() <= 4) {
continue;
}
const std::string idStr = name.substr(4);
if (std::all_of(idStr.begin(), idStr.end(), ::isdigit)) {
return static_cast<uint32_t>(std::stoul(idStr));
}
}
return std::nullopt;
}
} // namespace

std::vector<AmdL3CcxNumaEntry> getAmdL3CcxToNumaNodeMapFromSysfs(
const std::string& rootdir) {
std::vector<AmdL3CcxNumaEntry> entries;
std::filesystem::path root = rootdir.empty() ? "/" : rootdir;

// One representative CPU per L3 (CCX) domain.
std::ifstream cpumaskFile(
root / "sys/bus/event_source/devices/amd_l3/cpumask");
std::string cpumask;
if (!cpumaskFile.is_open() || !std::getline(cpumaskFile, cpumask)) {
// No amd_l3 uncore PMU (non-AMD or unsupported HW).
return entries;
}

const std::filesystem::path cpuBase = root / "sys/devices/system/cpu";
for (const CpuId cpu : parseCpusListToSet(cpumask)) {
const std::filesystem::path cpuDir =
cpuBase / ("cpu" + std::to_string(cpu));
const auto ccxId = readCpuL3CacheId(cpuDir);
const auto numaNode = readCpuNumaNode(cpuDir);
if (!ccxId.has_value() || !numaNode.has_value()) {
HBT_LOG_WARNING()
<< "amd_l3 cpu " << cpu
<< ": missing L3 cache id or NUMA node in sysfs; skipping";
continue;
}
entries.push_back(
{static_cast<uint32_t>(cpu), ccxId.value(), numaNode.value()});
}
return entries;
}

std::string removeBlanks(std::string s) {
// Remove blanks.
s.erase(
Expand Down
22 changes: 22 additions & 0 deletions hbt/src/common/System.h
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,28 @@ inline int readIntFromFile(const std::string& filepath) {
std::vector<std::vector<uint32_t>> getSocketCoreMapFromSysfs(
const std::string& rootdir = "/");

// One AMD L3 (CCX) domain's representative CPU, its L3 cache (CCX) id, and the
// NUMA node it belongs to. Built from sysfs so per-CCX uncore counters can be
// attributed to the correct NUMA node.
struct AmdL3CcxNumaEntry {
uint32_t cpu;
uint32_t ccxId;
uint32_t numaNode;
};

// Discover the CPU -> CCX -> NUMA node topology for AMD L3 uncore domains:
// 1. Read <rootdir>/sys/bus/event_source/devices/amd_l3/cpumask for one
// representative CPU per L3 (CCX) domain.
// 2. For each representative CPU, read its L3 cache id (the CCX id) from the
// <rootdir>/sys/devices/system/cpu/cpu<X>/cache/index* entry whose level
// is 3, and its NUMA node from the node<N> entry under
// <rootdir>/sys/devices/system/cpu/cpu<X>/.
// Returns one entry per representative CPU whose CCX id and NUMA node both
// resolve. Empty when amd_l3 is absent (non-AMD or unsupported HW) or the
// topology could not be read.
std::vector<AmdL3CcxNumaEntry> getAmdL3CcxToNumaNodeMapFromSysfs(
const std::string& rootdir = "/");

//
// System functions
//
Expand Down
24 changes: 24 additions & 0 deletions hbt/src/common/tests/SystemTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,27 @@ TEST(SystemTest, GetSocketCoreMapFromSysfsTest) {
EXPECT_THAT(result[1], testing::ElementsAre(8, 9, 10, 11, 12, 13, 14, 15))
<< "Socket 1 should have cores 8-15 in order";
}

TEST(SystemTest, GetAmdL3CcxToNumaNodeMapFromSysfsTest) {
const char* testRootEnv = getenv("TESTROOT");
if (!testRootEnv) {
GTEST_SKIP() << "TESTROOT environment variable not set, skipping test";
}

// The mock amd_l3 cpumask lists representative CPUs 0, 4, 8. cpu0 has a
// level-1 cache index preceding its level-3 one (exercising the level scan)
// and resolves to CCX id 0 / NUMA node 0; cpu4 resolves to CCX id 1 / node 1.
// cpu8 has an L3 cache but no node<N> entry, so it must be skipped.
const auto entries = getAmdL3CcxToNumaNodeMapFromSysfs(testRootEnv);
ASSERT_EQ(entries.size(), 2u);
EXPECT_EQ(entries[0].cpu, 0u);
EXPECT_EQ(entries[0].ccxId, 0u);
EXPECT_EQ(entries[0].numaNode, 0u);
EXPECT_EQ(entries[1].cpu, 4u);
EXPECT_EQ(entries[1].ccxId, 1u);
EXPECT_EQ(entries[1].numaNode, 1u);

// No amd_l3 device (non-AMD / unsupported HW) yields an empty result.
EXPECT_TRUE(
getAmdL3CcxToNumaNodeMapFromSysfs("/nonexistent_hbt_test_root").empty());
}
5 changes: 5 additions & 0 deletions hbt/src/perf_event/PmuDevices.h
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,11 @@ class PmuDeviceManager {
rootDir_(rootDir),
cpuSocketToCores_(getSocketCoreMapFromSysfs(rootDir)) {}

// Root of the sysfs tree this manager reads from. Tests use this fun to mock.
const std::string& getRootDir() const noexcept {
return rootDir_;
}

// Sync PMUs exposed in /sys/devices with those in pmu_groups_.
void loadSysFsPmus();

Expand Down
5 changes: 5 additions & 0 deletions hbt/src/perf_event/tests/MockPerUncoreCountReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class MockPerUncoreCountReader : public PerUncoreCountReader {
readPerPerfEventsGroup,
(),
(const, override));
MOCK_METHOD(
(std::optional<std::map<int, ReadValues>>),
readPerPerfEventsGroupKeyed,
(),
(const, override));
MOCK_METHOD(
(std::map<int, ReadValues>),
readPerPerfEventsGroupOnCpu,
Expand Down
1 change: 1 addition & 0 deletions testing/root/sys/bus/event_source/devices/amd_l3/cpumask
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0,4,8
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
1 change: 1 addition & 0 deletions testing/root/sys/devices/system/cpu/cpu0/cache/index3/id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3
Empty file.
1 change: 1 addition & 0 deletions testing/root/sys/devices/system/cpu/cpu4/cache/index3/id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3
Empty file.
1 change: 1 addition & 0 deletions testing/root/sys/devices/system/cpu/cpu8/cache/index3/id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3
1 change: 1 addition & 0 deletions testing/root/sys/devices/system/node/node0/cpulist
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0-3,8-11
1 change: 1 addition & 0 deletions testing/root/sys/devices/system/node/node1/cpulist
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4-7,12-15
1 change: 1 addition & 0 deletions testing/root/sys/devices/system/node/node2/cpulist
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading