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 CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions ebpf/include/tob/ebpf/cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
#pragma once

#include <cstddef>
#include <string>
#include <vector>

namespace tob::ebpf {

std::size_t getPossibleProcessorCount();
std::vector<std::size_t> parseCpuList(const std::string &cpu_list_str);
std::vector<std::size_t> getOnlineProcessorList();

} // namespace tob::ebpf
2 changes: 1 addition & 1 deletion ebpf/include/tob/ebpf/perfeventarray.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class PerfEventArray final {
std::unique_ptr<PrivateData> 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
62 changes: 62 additions & 0 deletions ebpf/src/cpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@

#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>

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);
Expand Down Expand Up @@ -63,11 +67,69 @@ std::size_t getPossibleProcessorCountHelper() {

return possible_cpu_count + 1;
}

} // namespace

std::size_t getPossibleProcessorCount() {
static const auto kPossibleProcessorCount = getPossibleProcessorCountHelper();

return kPossibleProcessorCount;
}

std::vector<std::size_t> parseCpuList(const std::string &cpu_list_str) {
std::vector<std::size_t> 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<std::size_t>(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<std::size_t>(i));
}
}
}

return cpu_list;
}

std::vector<std::size_t> 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
43 changes: 28 additions & 15 deletions ebpf/src/perfeventarray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/sysinfo.h>
#include <unistd.h>

#include <tob/ebpf/cpu.h>
#include <tob/ebpf/ebpf_utils.h>
#include <tob/ebpf/perfeventarray.h>
#include <tob/ebpf/typedbpfmap.h>
Expand Down Expand Up @@ -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<std::size_t, PerfEventOutput> perf_event_output_list;
std::vector<PerfEventOutput> perf_event_output_list;
std::vector<struct pollfd> perf_event_output_pollfd;
};

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<std::size_t>(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);

Expand All @@ -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<std::uint32_t>(cpu_index), perf_event_output.fd.get());

if (!err.succeeded()) {
throw StringError::create("Failed to populate the perf event array map");
Expand All @@ -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};
Expand Down
75 changes: 75 additions & 0 deletions tests/cpu.cpp
Original file line number Diff line number Diff line change
@@ -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 <doctest/doctest.h>

#include <tob/ebpf/cpu.h>

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