diff --git a/src/cc/BPF.cc b/src/cc/BPF.cc index 4a7ca2ccea11..292e35177237 100644 --- a/src/cc/BPF.cc +++ b/src/cc/BPF.cc @@ -30,6 +30,7 @@ #include "bpf_module.h" #include "libbpf.h" #include "perf_reader.h" +#include "common.h" #include "usdt.h" #include "BPF.h" @@ -297,11 +298,12 @@ StatusTuple BPF::attach_perf_event(uint32_t ev_type, uint32_t ev_config, TRY2(load_func(probe_func, BPF_PROG_TYPE_PERF_EVENT, probe_fd)); auto fds = new std::map(); - int cpu_st = 0; - int cpu_en = sysconf(_SC_NPROCESSORS_ONLN) - 1; + std::vector cpus; if (cpu >= 0) - cpu_st = cpu_en = cpu; - for (int i = cpu_st; i <= cpu_en; i++) { + cpus.push_back(cpu); + else + cpus = get_online_cpus(); + for (int i: cpus) { int fd = bpf_attach_perf_event(probe_fd, ev_type, ev_config, sample_period, sample_freq, pid, i, group_fd); if (fd < 0) { diff --git a/src/cc/BPFTable.cc b/src/cc/BPFTable.cc index 994e51fb6856..837d5bd0b649 100644 --- a/src/cc/BPFTable.cc +++ b/src/cc/BPFTable.cc @@ -25,6 +25,7 @@ #include "bcc_syms.h" #include "libbpf.h" #include "perf_reader.h" +#include "common.h" namespace ebpf { @@ -89,7 +90,7 @@ StatusTuple BPFPerfBuffer::open_all_cpu(perf_reader_raw_cb cb, if (cpu_readers_.size() != 0 || readers_.size() != 0) return StatusTuple(-1, "Previously opened perf buffer not cleaned"); - for (int i = 0; i < sysconf(_SC_NPROCESSORS_ONLN); i++) { + for (int i: get_online_cpus()) { auto res = open_on_cpu(cb, i, cb_cookie); if (res.code() != 0) { TRY2(close_all_cpu()); @@ -113,7 +114,7 @@ StatusTuple BPFPerfBuffer::close_on_cpu(int cpu) { StatusTuple BPFPerfBuffer::close_all_cpu() { std::string errors; bool has_error = false; - for (int i = 0; i < sysconf(_SC_NPROCESSORS_ONLN); i++) { + for (int i: get_online_cpus()) { auto res = close_on_cpu(i); if (res.code() != 0) { errors += "Failed to close CPU" + std::to_string(i) + " perf buffer: "; diff --git a/src/cc/CMakeLists.txt b/src/cc/CMakeLists.txt index fed6d3a89b27..2d1fdca54c7c 100644 --- a/src/cc/CMakeLists.txt +++ b/src/cc/CMakeLists.txt @@ -35,12 +35,12 @@ if (CMAKE_COMPILER_IS_GNUCC AND LIBCLANG_ISSTATIC) endif() endif() -add_library(bcc-shared SHARED bpf_common.cc bpf_module.cc libbpf.c perf_reader.c shared_table.cc exported_files.cc bcc_elf.c bcc_perf_map.c bcc_proc.c bcc_syms.cc usdt_args.cc usdt.cc BPF.cc BPFTable.cc) +add_library(bcc-shared SHARED bpf_common.cc bpf_module.cc libbpf.c perf_reader.c shared_table.cc exported_files.cc bcc_elf.c bcc_perf_map.c bcc_proc.c bcc_syms.cc usdt_args.cc usdt.cc common.cc BPF.cc BPFTable.cc) set_target_properties(bcc-shared PROPERTIES VERSION ${REVISION_LAST} SOVERSION 0) set_target_properties(bcc-shared PROPERTIES OUTPUT_NAME bcc) add_library(bcc-loader-static libbpf.c perf_reader.c bcc_elf.c bcc_perf_map.c bcc_proc.c) -add_library(bcc-static STATIC bpf_common.cc bpf_module.cc shared_table.cc exported_files.cc bcc_syms.cc usdt_args.cc usdt.cc BPF.cc BPFTable.cc) +add_library(bcc-static STATIC bpf_common.cc bpf_module.cc shared_table.cc exported_files.cc bcc_syms.cc usdt_args.cc usdt.cc common.cc BPF.cc BPFTable.cc) set_target_properties(bcc-static PROPERTIES OUTPUT_NAME bcc) set(llvm_raw_libs bitwriter bpfcodegen irreader linker diff --git a/src/cc/common.cc b/src/cc/common.cc new file mode 100644 index 000000000000..78bdb862677c --- /dev/null +++ b/src/cc/common.cc @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016 Catalysts GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include "common.h" + +namespace ebpf { + +std::vector read_cpu_range(std::string path) { + std::ifstream cpus_range_stream { path }; + std::vector cpus; + std::string cpu_range; + + while (std::getline(cpus_range_stream, cpu_range, ',')) { + std::size_t rangeop = cpu_range.find('-'); + if (rangeop == std::string::npos) { + cpus.push_back(std::stoi(cpu_range)); + } + else { + int start = std::stoi(cpu_range.substr(0, rangeop)); + int end = std::stoi(cpu_range.substr(rangeop + 1)); + for (int i = start; i <= end; i++) + cpus.push_back(i); + } + } + return cpus; +} + +std::vector get_online_cpus() { + return read_cpu_range("/sys/devices/system/cpu/online"); +} + +std::vector get_possible_cpus() { + return read_cpu_range("/sys/devices/system/cpu/possible"); +} + + +} // namespace ebpf diff --git a/src/cc/common.h b/src/cc/common.h index b908f25495f9..377ddfdcb298 100644 --- a/src/cc/common.h +++ b/src/cc/common.h @@ -19,6 +19,7 @@ #include #include #include +#include namespace ebpf { @@ -28,4 +29,8 @@ make_unique(Args &&... args) { return std::unique_ptr(new T(std::forward(args)...)); } +std::vector get_online_cpus(); + +std::vector get_possible_cpus(); + } // namespace ebpf diff --git a/src/cc/frontends/clang/CMakeLists.txt b/src/cc/frontends/clang/CMakeLists.txt index f1ad50f2fa73..5f6f6391e942 100644 --- a/src/cc/frontends/clang/CMakeLists.txt +++ b/src/cc/frontends/clang/CMakeLists.txt @@ -2,4 +2,4 @@ # Licensed under the Apache License, Version 2.0 (the "License") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DKERNEL_MODULES_DIR='\"${BCC_KERNEL_MODULES_DIR}\"'") -add_library(clang_frontend loader.cc b_frontend_action.cc tp_frontend_action.cc kbuild_helper.cc) +add_library(clang_frontend loader.cc b_frontend_action.cc tp_frontend_action.cc kbuild_helper.cc ../../common.cc) diff --git a/src/cc/frontends/clang/b_frontend_action.cc b/src/cc/frontends/clang/b_frontend_action.cc index 397ecc682211..3b633670bf2d 100644 --- a/src/cc/frontends/clang/b_frontend_action.cc +++ b/src/cc/frontends/clang/b_frontend_action.cc @@ -27,6 +27,7 @@ #include "b_frontend_action.h" #include "shared_table.h" +#include "common.h" #include "libbpf.h" @@ -656,7 +657,7 @@ bool BTypeVisitor::VisitVarDecl(VarDecl *Decl) { map_type = BPF_MAP_TYPE_PROG_ARRAY; } else if (A->getName() == "maps/perf_output") { map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY; - int numcpu = sysconf(_SC_NPROCESSORS_ONLN); + int numcpu = get_possible_cpus().size(); if (numcpu <= 0) numcpu = 1; table.max_entries = numcpu; diff --git a/src/python/bcc/__init__.py b/src/python/bcc/__init__.py index 0304da180b2e..454fe20d70cf 100644 --- a/src/python/bcc/__init__.py +++ b/src/python/bcc/__init__.py @@ -17,7 +17,6 @@ import ctypes as ct import fcntl import json -import multiprocessing import os import re import struct @@ -29,6 +28,7 @@ from .table import Table from .perf import Perf from .usyms import ProcessSymbols +from .utils import get_online_cpus _kprobe_limit = 1000 _num_open_probes = 0 @@ -660,7 +660,7 @@ def attach_perf_event(self, ev_type=-1, ev_config=-1, fn_name="", res[cpu] = self._attach_perf_event(fn.fd, ev_type, ev_config, sample_period, sample_freq, pid, cpu, group_fd) else: - for i in range(0, multiprocessing.cpu_count()): + for i in get_online_cpus(): res[i] = self._attach_perf_event(fn.fd, ev_type, ev_config, sample_period, sample_freq, pid, i, group_fd) self.open_perf_events[(ev_type, ev_config)] = res diff --git a/src/python/bcc/perf.py b/src/python/bcc/perf.py index ea155915ca4c..44b0128df808 100644 --- a/src/python/bcc/perf.py +++ b/src/python/bcc/perf.py @@ -13,8 +13,8 @@ # limitations under the License. import ctypes as ct -import multiprocessing import os +from .utils import get_online_cpus class Perf(object): class perf_event_attr(ct.Structure): @@ -105,5 +105,5 @@ def perf_event_open(tpoint_id, pid=-1, ptype=PERF_TYPE_TRACEPOINT, attr.sample_period = 1 attr.wakeup_events = 9999999 # don't wake up - for cpu in range(0, multiprocessing.cpu_count()): + for cpu in get_online_cpus(): Perf._open_for_cpu(cpu, attr) diff --git a/src/python/bcc/table.py b/src/python/bcc/table.py index 1a4a32467d49..74358e60ab96 100644 --- a/src/python/bcc/table.py +++ b/src/python/bcc/table.py @@ -19,6 +19,7 @@ from .libbcc import lib, _RAW_CB_TYPE from .perf import Perf +from .utils import get_online_cpus from subprocess import check_output BPF_MAP_TYPE_HASH = 1 @@ -509,7 +510,7 @@ def open_perf_buffer(self, callback): event submitted from the kernel, up to millions per second. """ - for i in range(0, multiprocessing.cpu_count()): + for i in get_online_cpus(): self._open_perf_buffer(i, callback) def _open_perf_buffer(self, cpu, callback): @@ -550,7 +551,7 @@ def open_perf_event(self, ev): if not isinstance(ev, self.Event): raise Exception("argument must be an Event, got %s", type(ev)) - for i in range(0, multiprocessing.cpu_count()): + for i in get_online_cpus(): self._open_perf_event(i, ev.typ, ev.config) diff --git a/src/python/bcc/utils.py b/src/python/bcc/utils.py new file mode 100644 index 000000000000..348ab9d5257a --- /dev/null +++ b/src/python/bcc/utils.py @@ -0,0 +1,33 @@ +# Copyright 2016 Catalysts GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def _read_cpu_range(path): + cpus = [] + with open(path, 'r') as f: + cpus_range_str = f.read() + for cpu_range in cpus_range_str.split(','): + rangeop = cpu_range.find('-') + if rangeop == -1: + cpus.append(int(cpu_range)) + else: + start = int(cpu_range[:rangeop]) + end = int(cpu_range[rangeop+1:]) + cpus.extend(range(start, end+1)) + return cpus + +def get_online_cpus(): + return _read_cpu_range('/sys/devices/system/cpu/online') + +def get_possible_cpus(): + return _read_cpu_range('/sys/devices/system/cpu/possible') diff --git a/tests/cc/test_c_api.cc b/tests/cc/test_c_api.cc index af0bd3acf31a..7a5e8dcffaa0 100644 --- a/tests/cc/test_c_api.cc +++ b/tests/cc/test_c_api.cc @@ -23,6 +23,7 @@ #include "bcc_perf_map.h" #include "bcc_proc.h" #include "bcc_syms.h" +#include "common.h" #include "vendor/tinyformat.hpp" #include "catch.hpp" @@ -196,3 +197,10 @@ TEST_CASE("resolve symbols using /tmp/perf-pid.map", "[c_api]") { munmap(map_addr, map_sz); } + + +TEST_CASE("get online CPUs", "[c_api]") { + std::vector cpus = ebpf::get_online_cpus(); + int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); + REQUIRE(cpus.size() == num_cpus); +} diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index fb0eedbda343..1da2a673df49 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -56,6 +56,8 @@ add_test(NAME py_test_tracepoint WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_tracepoint sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_tracepoint.py) add_test(NAME py_test_perf_event WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_test_perf_event sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_perf_event.py) +add_test(NAME py_test_utils WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${TEST_WRAPPER} py_test_utils sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_utils.py) add_test(NAME py_test_dump_func WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${TEST_WRAPPER} py_dump_func simple ${CMAKE_CURRENT_SOURCE_DIR}/test_dump_func.py) diff --git a/tests/python/test_array.py b/tests/python/test_array.py index 3fe1dceaf3b7..a7c82a4f6d70 100755 --- a/tests/python/test_array.py +++ b/tests/python/test_array.py @@ -6,6 +6,8 @@ import ctypes as ct import random import time +import subprocess +from bcc.utils import get_online_cpus from unittest import main, TestCase class TestArray(TestCase): @@ -62,6 +64,37 @@ def cb(cpu, data, size): time.sleep(0.1) b.kprobe_poll() self.assertGreater(self.counter, 0) + b.cleanup() + + def test_perf_buffer_for_each_cpu(self): + self.events = [] + + class Data(ct.Structure): + _fields_ = [("cpu", ct.c_ulonglong)] + + def cb(cpu, data, size): + self.assertGreater(size, ct.sizeof(Data)) + event = ct.cast(data, ct.POINTER(Data)).contents + self.events.append(event) + + text = """ +BPF_PERF_OUTPUT(events); +int kprobe__sys_nanosleep(void *ctx) { + struct { + u64 cpu; + } data = {bpf_get_smp_processor_id()}; + events.perf_submit(ctx, &data, sizeof(data)); + return 0; +} +""" + b = BPF(text=text) + b["events"].open_perf_buffer(cb) + online_cpus = get_online_cpus() + for cpu in online_cpus: + subprocess.call(['taskset', '-c', str(cpu), 'sleep', '0.1']) + b.kprobe_poll() + b.cleanup() + self.assertGreaterEqual(len(self.events), len(online_cpus), 'Received only {}/{} events'.format(len(self.events), len(online_cpus))) if __name__ == "__main__": main() diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py new file mode 100755 index 000000000000..08862e74074d --- /dev/null +++ b/tests/python/test_utils.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +# Copyright (c) Catalysts GmbH +# Licensed under the Apache License, Version 2.0 (the "License") + +from bcc.utils import get_online_cpus +import multiprocessing +import unittest + +class TestUtils(unittest.TestCase): + def test_get_online_cpus(self): + online_cpus = get_online_cpus() + num_cores = multiprocessing.cpu_count() + + self.assertEqual(len(online_cpus), num_cores) + + +if __name__ == "__main__": + unittest.main()