diff --git a/CMakeLists.txt b/CMakeLists.txt index e4d63e6..f8ab94b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ if(EBPF_COMMON_ENABLE_TESTS) tests/main.cpp tests/bpfmap.cpp + tests/cpu.cpp tests/erroror.cpp tests/llvm_utils.cpp tests/llvmbridge.cpp diff --git a/ebpf/include/tob/ebpf/cpu.h b/ebpf/include/tob/ebpf/cpu.h index 0f0dd46..75a8725 100644 --- a/ebpf/include/tob/ebpf/cpu.h +++ b/ebpf/include/tob/ebpf/cpu.h @@ -9,7 +9,13 @@ #pragma once #include +#include +#include namespace tob::ebpf { + std::size_t getPossibleProcessorCount(); +std::vector parseCpuList(const std::string &cpu_list_str); +std::vector getOnlineProcessorList(); + } // namespace tob::ebpf \ No newline at end of file diff --git a/ebpf/include/tob/ebpf/perfeventarray.h b/ebpf/include/tob/ebpf/perfeventarray.h index a3b36ff..db6fa64 100644 --- a/ebpf/include/tob/ebpf/perfeventarray.h +++ b/ebpf/include/tob/ebpf/perfeventarray.h @@ -46,7 +46,7 @@ class PerfEventArray final { std::unique_ptr d; PerfEventArray(std::size_t perf_event_output_page_exp); - BufferList readPerfMemory(std::size_t processor_index); + BufferList readPerfMemory(std::size_t pollfd_index); }; } // namespace tob::ebpf diff --git a/ebpf/src/cpu.cpp b/ebpf/src/cpu.cpp index d346560..5e355df 100644 --- a/ebpf/src/cpu.cpp +++ b/ebpf/src/cpu.cpp @@ -10,12 +10,16 @@ #include #include +#include #include #include namespace tob::ebpf { + namespace { + const std::string kPossibleCpuPseudoFile{"/sys/devices/system/cpu/possible"}; +const std::string kOnlineCpuPseudoFile{"/sys/devices/system/cpu/online"}; std::size_t getPossibleProcessorCountHelper() { std::ifstream cpu_info_file(kPossibleCpuPseudoFile); @@ -63,6 +67,7 @@ std::size_t getPossibleProcessorCountHelper() { return possible_cpu_count + 1; } + } // namespace std::size_t getPossibleProcessorCount() { @@ -70,4 +75,61 @@ std::size_t getPossibleProcessorCount() { return kPossibleProcessorCount; } + +std::vector parseCpuList(const std::string &cpu_list_str) { + std::vector cpu_list; + std::istringstream stream(cpu_list_str); + std::string token; + + while (std::getline(stream, token, ',')) { + auto dash = token.find('-'); + if (dash == std::string::npos) { + char *end{nullptr}; + auto cpu = std::strtoull(token.c_str(), &end, 10); + if (end == nullptr || *end != '\0') { + throw std::runtime_error("Failed to parse CPU value: " + token); + } + + cpu_list.push_back(static_cast(cpu)); + } else { + auto start_str = token.substr(0, dash); + auto end_str = token.substr(dash + 1); + + char *end{nullptr}; + auto range_start = std::strtoull(start_str.c_str(), &end, 10); + if (end == nullptr || *end != '\0') { + throw std::runtime_error("Failed to parse CPU range start: " + + start_str); + } + + auto range_end = std::strtoull(end_str.c_str(), &end, 10); + if (end == nullptr || *end != '\0') { + throw std::runtime_error("Failed to parse CPU range end: " + end_str); + } + + for (auto i = range_start; i <= range_end; ++i) { + cpu_list.push_back(static_cast(i)); + } + } + } + + return cpu_list; +} + +std::vector getOnlineProcessorList() { + std::ifstream cpu_info_file(kOnlineCpuPseudoFile); + if (!cpu_info_file) { + throw std::runtime_error("Failed to open the following file: " + + kOnlineCpuPseudoFile); + } + + std::string cpu_info; + std::getline(cpu_info_file, cpu_info); + if (!cpu_info_file || cpu_info.empty()) { + throw std::runtime_error("Failed to read the following file: " + + kOnlineCpuPseudoFile); + } + + return parseCpuList(cpu_info); +} } // namespace tob::ebpf \ No newline at end of file diff --git a/ebpf/src/perfeventarray.cpp b/ebpf/src/perfeventarray.cpp index f1805ed..9e91db9 100644 --- a/ebpf/src/perfeventarray.cpp +++ b/ebpf/src/perfeventarray.cpp @@ -14,9 +14,9 @@ #include #include #include -#include #include +#include #include #include #include @@ -44,13 +44,25 @@ static const auto kPerfDataHeadOffset = } // namespace +// perf_event_output_list and perf_event_output_pollfd are parallel vectors +// indexed by a dense pollfd index (0..N-1), not by CPU id. +// +// Online CPUs may be non-contiguous (e.g. {0,1,4,7}), but these vectors are +// always dense. The actual CPU id is stored in each +// PerfEventOutput::processor_index. +// +// Example: online CPUs = {0, 1, 4, 7} +// pollfd index 0 -> perf_event_output_list[0] (cpu 0) +// pollfd index 1 -> perf_event_output_list[1] (cpu 1) +// pollfd index 2 -> perf_event_output_list[2] (cpu 4) +// pollfd index 3 -> perf_event_output_list[3] (cpu 7) struct PerfEventArray::PrivateData final { PerfEventArrayMap::Ref perf_event_array_map; std::size_t single_perf_event_output_size{}; std::size_t processor_count{}; - std::unordered_map perf_event_output_list; + std::vector perf_event_output_list; std::vector perf_event_output_pollfd; }; @@ -99,18 +111,17 @@ bool PerfEventArray::read(BufferList &buffer_list, return true; } - for (auto processor_index = 0U; - processor_index < d->perf_event_output_pollfd.size(); - ++processor_index) { + for (auto pollfd_index = 0U; + pollfd_index < d->perf_event_output_pollfd.size(); ++pollfd_index) { - auto &poll_fd = d->perf_event_output_pollfd.at(processor_index); + auto &poll_fd = d->perf_event_output_pollfd[pollfd_index]; if ((poll_fd.revents & POLLIN) == 0) { continue; } poll_fd.revents = 0; - auto perf_buffer_list = readPerfMemory(processor_index); + auto perf_buffer_list = readPerfMemory(pollfd_index); if (perf_buffer_list.empty()) { continue; } @@ -172,9 +183,11 @@ PerfEventArray::PerfEventArray(std::size_t perf_event_output_page_exp) } d->perf_event_array_map = perf_event_array_map_exp.takeValue(); - d->processor_count = static_cast(get_nprocs_conf()); - for (auto cpu_index = 0U; cpu_index < d->processor_count; ++cpu_index) { + auto online_cpus = getOnlineProcessorList(); + d->processor_count = online_cpus.size(); + + for (auto cpu_index : online_cpus) { auto perf_event_output_exp = createPerfEventOutputForCPU( cpu_index, d->single_perf_event_output_size); @@ -184,8 +197,8 @@ PerfEventArray::PerfEventArray(std::size_t perf_event_output_page_exp) auto perf_event_output = perf_event_output_exp.takeValue(); - auto err = - d->perf_event_array_map->set(cpu_index, perf_event_output.fd.get()); + auto err = d->perf_event_array_map->set( + static_cast(cpu_index), perf_event_output.fd.get()); if (!err.succeeded()) { throw StringError::create("Failed to populate the perf event array map"); @@ -195,19 +208,19 @@ PerfEventArray::PerfEventArray(std::size_t perf_event_output_page_exp) poll_fd.fd = perf_event_output.fd.get(); poll_fd.events = POLLIN; d->perf_event_output_pollfd.push_back(std::move(poll_fd)); - d->perf_event_output_list.insert({cpu_index, std::move(perf_event_output)}); + d->perf_event_output_list.push_back(std::move(perf_event_output)); } } PerfEventArray::BufferList -PerfEventArray::readPerfMemory(std::size_t processor_index) { +PerfEventArray::readPerfMemory(std::size_t pollfd_index) { BufferList buffer_list; - if (processor_index > d->perf_event_output_list.size()) { + if (pollfd_index >= d->perf_event_output_list.size()) { return buffer_list; } - auto &perf_event_output = d->perf_event_output_list.at(processor_index); + auto &perf_event_output = d->perf_event_output_list[pollfd_index]; auto perf_header_memory = perf_event_output.memory->pointer(); std::uint64_t data_size{0U}; diff --git a/tests/cpu.cpp b/tests/cpu.cpp new file mode 100644 index 0000000..37ddced --- /dev/null +++ b/tests/cpu.cpp @@ -0,0 +1,75 @@ +/* + Copyright (c) 2019-present, Trail of Bits, Inc. + All rights reserved. + + This source code is licensed in accordance with the terms specified in + the LICENSE file found in the root directory of this source tree. +*/ + +#include + +#include + +namespace tob::ebpf { +TEST_CASE("parseCpuList") { + SUBCASE("Single CPU 0") { + auto result = parseCpuList("0"); + REQUIRE(result.size() == 1); + REQUIRE(result[0] == 0); + } + + SUBCASE("Single CPU non-zero") { + auto result = parseCpuList("5"); + REQUIRE(result.size() == 1); + REQUIRE(result[0] == 5); + } + + SUBCASE("Simple range") { + auto result = parseCpuList("0-3"); + REQUIRE(result.size() == 4); + REQUIRE(result[0] == 0); + REQUIRE(result[1] == 1); + REQUIRE(result[2] == 2); + REQUIRE(result[3] == 3); + } + + SUBCASE("Multiple individual CPUs") { + auto result = parseCpuList("0,2,4"); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == 0); + REQUIRE(result[1] == 2); + REQUIRE(result[2] == 4); + } + + SUBCASE("Mixed ranges and singles") { + auto result = parseCpuList("0-3,5,7-9"); + REQUIRE(result.size() == 8); + REQUIRE(result[0] == 0); + REQUIRE(result[1] == 1); + REQUIRE(result[2] == 2); + REQUIRE(result[3] == 3); + REQUIRE(result[4] == 5); + REQUIRE(result[5] == 7); + REQUIRE(result[6] == 8); + REQUIRE(result[7] == 9); + } + + SUBCASE("Large range") { + auto result = parseCpuList("0-127"); + REQUIRE(result.size() == 128); + REQUIRE(result[0] == 0); + REQUIRE(result[127] == 127); + } + + SUBCASE("Non-contiguous ranges with gaps") { + auto result = parseCpuList("0-1,4-5,8-9"); + REQUIRE(result.size() == 6); + REQUIRE(result[0] == 0); + REQUIRE(result[1] == 1); + REQUIRE(result[2] == 4); + REQUIRE(result[3] == 5); + REQUIRE(result[4] == 8); + REQUIRE(result[5] == 9); + } +} +} // namespace tob::ebpf