• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / bpf_performance / 18855842857

20 Oct 2025 03:07PM UTC coverage: 52.878%. Remained the same
18855842857

push

github

web-flow
Merge pull request #221 from Alan-Jowett/fix_linux

Fix issues from Linux kernel changes

3 of 3 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

147 of 278 relevant lines covered (52.88%)

12.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.76
/runner/runner.cc
1
// Copyright (c) Microsoft Corporation
2
// SPDX-License-Identifier: MIT
3

4
#include "options.h"
5
#include <bpf/bpf.h>
6
#include <bpf/libbpf.h>
7
#include <chrono>
8
#include <iomanip>
9
#include <iostream>
10
#include <optional>
11
#include <regex>
12
#include <sstream>
13
#include <thread>
14
#include <vector>
15
#include <yaml-cpp/yaml.h>
16

17
// Define unique_ptr to call bpf_object__close on destruction
18
struct bpf_object_deleter
19
{
20
    void
21
    operator()(struct bpf_object* obj) const
5✔
22
    {
23
        if (obj) {
5✔
24
            bpf_object__close(obj);
5✔
25
        }
26
    }
5✔
27
};
28

29
typedef std::unique_ptr<struct bpf_object, bpf_object_deleter> bpf_object_ptr;
30

31
// Set string runner_platform to "linux" to indicate that this is a Linux runner.
32
#if defined(__linux__)
33
const std::string runner_platform = "Linux";
34
#define time_t_to_utc_tm(TM, TIME) gmtime_r(TIME, TM)
35
#define DEFAULT_PROG_TYPE BPF_PROG_TYPE_XDP
36
#define DEFAULT_ATTACH_TYPE BPF_XDP
37
#define DEFAULT_PASS_DATA true
38
#define DEFAULT_PASS_CONTEXT false
39
#define DEFAULT_BATCH_SIZE 0
40
#else
41
const std::string runner_platform = "Windows";
42
#define popen _popen
43
#define pclose _pclose
44
#define time_t_to_utc_tm(TM, TIME) gmtime_s(TM, TIME)
45
#define DEFAULT_PROG_TYPE BPF_PROG_TYPE_SOCK_OPS
46
#define DEFAULT_ATTACH_TYPE BPF_CGROUP_SOCK_OPS
47
#define DEFAULT_PASS_DATA false
48
#define DEFAULT_PASS_CONTEXT true
49
#define DEFAULT_BATCH_SIZE 64
50
#endif
51

52
int run_command_and_capture_output(const std::string& command, std::string& command_output)
×
53
{
54
    FILE* pipe = popen(command.c_str(), "r");
×
55

56
    // Read until end of file.
57
    while (!feof(pipe)) {
×
58
        char buffer[1024];
59
        if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
×
60
            command_output += buffer;
61
        }
62
    }
63

64
    // Return the exit code.
65
    return pclose(pipe);
×
66
}
67

68
// Function to convert std::chrono time point to ISO 8601 UTC string.
69
std::string to_iso8601(const std::chrono::system_clock::time_point tp)
×
70
{
71
    std::time_t t = std::chrono::system_clock::to_time_t(tp);
×
72
    std::tm tm;
73
    time_t_to_utc_tm(&tm, &t);
×
74
    std::stringstream ss;
×
75
    ss << std::put_time(&tm, "%FT%T%z");
×
76
    return ss.str();
×
77
}
×
78

79
// This program runs a set of BPF programs and reports the average execution time for each program.
80
// It reads a YAML file that contains the following fields:
81
// - tests: a list of tests to run
82
//   - name: the name of the test
83
//   - elf_file: the path to the BPF object file
84
//   - iteration_count: the number of times to run each program
85
//   - map_state_preparation: optional, a program to run before the test to prepare the map state
86
//     - program: the name of the program
87
//     - iteration_count: the number of times to run the program
88
//   - program_cpu_assignment: a map of program names to CPUs
89
//     - <program name>: the name of the program
90
//       - <cpu number>: the CPU number to run the program on
91
//       - all: run the program on all CPUs
92
//       - remaining: run the program on all remaining CPUs
93
int
94
main(int argc, char** argv)
26✔
95
{
96
    try {
97
        options cmd_options;
26✔
98
        // Test input file.
99
        std::string test_file;
13✔
100
        // Get the optional test name.
101
        std::optional<std::string> test_name;
26✔
102
        std::optional<int> batch_size_override;
26✔
103
        std::optional<std::string> ebpf_file_extension_override;
26✔
104
        std::optional<int> iteration_count_override;
26✔
105
        std::optional<int> cpu_count_override;
26✔
106
        std::optional<bool> ignore_return_code;
26✔
107
        std::optional<std::string> pre_test_command;
26✔
108
        std::optional<std::string> post_test_command;
26✔
109
        bool csv_header_printed = false;
13✔
110

111
        // Add option "-i" for test input file.
112
        cmd_options.add(
52✔
113
            "-i", 2, [&test_file](auto iter) { test_file = *iter; }, "Test input file");
24✔
114

115
        // Add option "-t" to specify a test name regex.
116
        cmd_options.add(
52✔
117
            "-t", 2, [&test_name](auto iter) { test_name = *iter; }, "Test name regex");
×
118

119
        // Add option "-b" to specify batch size override.
120
        cmd_options.add(
52✔
121
            "-b",
122
            2,
123
            [&batch_size_override](auto iter) { batch_size_override = std::stoi(*iter); },
×
124
            "Batch size override");
125

126
        // Add option "-e" to specify the path to the eBPF file extension.
127
        cmd_options.add(
52✔
128
            "-e",
129
            2,
130
            [&ebpf_file_extension_override](auto iter) { ebpf_file_extension_override = *iter; },
×
131
            "eBPF file extension override");
132

133
        // Add option "-c" to specify iteration count override.
134
        cmd_options.add(
52✔
135
            "-c",
136
            2,
137
            [&iteration_count_override](auto iter) { iteration_count_override = std::stoi(*iter); },
×
138
            "Iteration count override");
139

140
        // Add option "-p" to specify cpu count override.
141
        cmd_options.add(
52✔
142
            "-p", 2, [&cpu_count_override](auto iter) { cpu_count_override = std::stoi(*iter); }, "CPU count override");
×
143

144
        // Add option to ignore return code from BPF programs.
145
        cmd_options.add(
52✔
146
            "-r",
147
            1,
148
            [&ignore_return_code](auto iter) { ignore_return_code = {true}; },
×
149
            "Ignore return code from BPF programs");
150

151
        // Add option to run a command before each test.
152
        cmd_options.add(
52✔
153
            "--pre",
154
            2,
155
            [&pre_test_command](auto iter) { pre_test_command = *iter; },
×
156
            "Command to run before each test");
157

158
        // Add option to run a command after each test.
159
        cmd_options.add(
64✔
160
            "--post",
161
            2,
162
            [&post_test_command](auto iter) { post_test_command = *iter; },
×
163
            "Command to run after each test");
164

165
        // Parse command line options.
166
        cmd_options.parse(argc, argv);
26✔
167

168
        if (test_file.empty()) {
24✔
169
            throw std::runtime_error("Test input file is required");
×
170
        }
171

172
        YAML::Node config = YAML::LoadFile(test_file);
24✔
173
        auto tests = config["tests"];
22✔
174
        std::map<std::string, bpf_object_ptr> bpf_objects;
11✔
175

176
        // Query libbpf for cpu count if not specified on command line.
177
        int cpu_count = cpu_count_override.value_or(libbpf_num_possible_cpus());
22✔
178

179
        // Fail if tests is empty or not a sequence.
180
        if (!tests || !tests.IsSequence()) {
21✔
181
            throw std::runtime_error("Invalid config file - tests must be a sequence");
2✔
182
        }
183

184
        // Run each test.
185
        for (auto test : tests) {
30✔
186
            // Check for required fields.
187
            if (!test["name"].IsDefined()) {
30✔
188
                throw std::runtime_error("Field name is required");
2✔
189
            }
190

191
            if (!test["elf_file"].IsDefined()) {
27✔
192
                throw std::runtime_error("Field elf_file is required");
2✔
193
            }
194

195
            if (!test["iteration_count"].IsDefined()) {
24✔
196
                throw std::runtime_error("Field iteration_count is required");
2✔
197
            }
198

199
            if (!test["program_cpu_assignment"].IsDefined()) {
21✔
200
                throw std::runtime_error("Field program_cpu_assignment is required");
2✔
201
            }
202

203
            if (!test["program_cpu_assignment"].IsMap()) {
18✔
204
                throw std::runtime_error("Field program_cpu_assignment must be a map");
2✔
205
            }
206

207
            // Per test fields.
208
            std::string name = test["name"].as<std::string>();
10✔
209
            std::string elf_file = test["elf_file"].as<std::string>();
15✔
210
            int iteration_count = test["iteration_count"].as<int>();
15✔
211
            std::optional<std::string> program_type;
10✔
212
            int batch_size;
213
            bool pass_data = DEFAULT_PASS_DATA;
5✔
214
            bool pass_context = DEFAULT_PASS_CONTEXT;
5✔
215
            uint32_t expected_result = 0;
5✔
216

217
            // Check if value "platform" is defined and matches the current platform.
218
            if (test["platform"].IsDefined()) {
15✔
219
                std::string platform = test["platform"].as<std::string>();
×
220
                if (runner_platform != platform) {
×
221
                    // Don't run this test if the platform doesn't match.
222
                    continue;
223
                }
224
            }
225

226
            // Check if value "program_type" is defined and use it.
227
            if (test["program_type"].IsDefined()) {
15✔
228
                program_type = test["program_type"].as<std::string>();
×
229
            }
230

231
            // Check if value "batch_size" is defined and use it.
232
            if (test["batch_size"].IsDefined()) {
15✔
233
                batch_size = test["batch_size"].as<int>();
×
234
            } else {
235
                batch_size = DEFAULT_BATCH_SIZE;
5✔
236
            }
237

238
            // Check if pass_data is defined and use it.
239
            if (test["pass_data"].IsDefined()) {
15✔
240
                pass_data = test["pass_data"].as<bool>();
×
241
            }
242

243
            // Check if pass_context is defined and use it.
244
            if (test["pass_context"].IsDefined()) {
15✔
245
                pass_context = test["pass_context"].as<bool>();
×
246
            }
247

248
            // Check if expected_result is defined and use it.
249
            if (test["expected_result"].IsDefined()) {
15✔
250
                expected_result = test["expected_result"].as<uint32_t>();
×
251
            }
252

253
            // Override batch size if specified on command line.
254
            if (batch_size_override.has_value()) {
10✔
255
                batch_size = batch_size_override.value();
×
256
            }
257

258
            // Skip if test name is specified and doesn't match, with test name being a regex.
259
            if (test_name && !std::regex_match(name, std::regex(*test_name))) {
10✔
260
                continue;
×
261
            }
262

263
            // If eBPF file extension override is specified, use it.
264
            // Windows uses .sys instead of .o for eBPF files that are compiled into a driver.
265
            if (ebpf_file_extension_override.has_value()) {
10✔
266
                elf_file = elf_file.substr(0, elf_file.find_last_of('.')) + ebpf_file_extension_override.value();
×
267
            }
268

269
            if (bpf_objects.find(elf_file) == bpf_objects.end()) {
10✔
270
                bpf_object_ptr obj;
5✔
271

272
                obj.reset(bpf_object__open(elf_file.c_str()));
10✔
273
                if (!obj) {
10✔
274
                    throw std::runtime_error("Failed to open BPF object " + elf_file + ": " + strerror(errno) + "/" + std::to_string(errno));
×
275
                }
276

277
                bpf_program* program;
278
                bpf_object__for_each_program(program, obj.get())
50✔
279
                {
280
                    bpf_prog_type prog_type;
281
                    bpf_attach_type attach_type;
282
                    if (program_type.has_value()) {
40✔
283
                        // If program_type is specified, use it.
284
                        if (libbpf_prog_type_by_name(program_type->c_str(), &prog_type, &attach_type) < 0) {
×
285
                            throw std::runtime_error("Failed to get program type " + *program_type);
×
286
                        }
287
                    } else {
288
                        // If program_type is not specified, use DEFAULT_PROG_TYPE.
289
                        prog_type = DEFAULT_PROG_TYPE;
40✔
290
                        attach_type = DEFAULT_ATTACH_TYPE;
40✔
291
                    }
292
                    (void)bpf_program__set_type(program, prog_type);
40✔
293
                }
294

295
                if (bpf_object__load(obj.get()) < 0) {
10✔
296
                    throw std::runtime_error("Failed to load BPF object " + elf_file + ": " + strerror(errno) + "/" + std::to_string(errno));
×
297
                }
298

299
                // Insert into bpf_objects
300
                bpf_objects.insert({elf_file, std::move(obj)});
10✔
301
            }
5✔
302

303
            // Vector of CPU -> program fd.
304
            std::vector<std::optional<int>> cpu_program_assignments(cpu_count);
15✔
305

306
            bpf_object_ptr& obj = bpf_objects[elf_file];
10✔
307

308
            // Check if node map_state_preparation exits.
309
            auto map_state_preparation = test["map_state_preparation"];
10✔
310
            if (map_state_preparation) {
5✔
311
                if (!map_state_preparation["program"].IsDefined()) {
15✔
312
                    throw std::runtime_error("Field map_state_preparation.program is required");
2✔
313
                }
314

315
                if (!map_state_preparation["iteration_count"].IsDefined()) {
12✔
316
                    throw std::runtime_error("Field map_state_preparation.iteration_count is required");
2✔
317
                }
318

319
                std::string prep_program_name = map_state_preparation["program"].as<std::string>();
6✔
320
                int prep_program_iterations = map_state_preparation["iteration_count"].as<int>();
6✔
321
                auto map_state_preparation_program =
322
                    bpf_object__find_program_by_name(obj.get(), prep_program_name.c_str());
6✔
323
                if (!map_state_preparation_program) {
6✔
324
                    throw std::runtime_error("Failed to find map_state_preparation program " + prep_program_name);
3✔
325
                }
326

327
                // Run map_state_preparation program via bpf_prog_test_run_opts.
328
                std::vector<uint8_t> data_in(1024);
5✔
329
                std::vector<uint8_t> data_out(1024);
4✔
330

331
                bpf_test_run_opts opts;
332
                memset(&opts, 0, sizeof(opts));
2✔
333
                opts.sz = sizeof(opts);
4✔
334
                opts.repeat = prep_program_iterations;
4✔
335
                if (pass_data) {
4✔
336
                    opts.data_in = data_in.data();
4✔
337
                    opts.data_out = data_out.data();
4✔
338
                    opts.data_size_in = static_cast<uint32_t>(data_in.size());
4✔
339
                    opts.data_size_out = static_cast<uint32_t>(data_out.size());
4✔
340
                }
341
                if (pass_context) {
4✔
UNCOV
342
                    opts.ctx_in = data_in.data();
×
UNCOV
343
                    opts.ctx_out = data_out.data();
×
UNCOV
344
                    opts.ctx_size_in = static_cast<uint32_t>(data_in.size());
×
UNCOV
345
                    opts.ctx_size_out = static_cast<uint32_t>(data_out.size());
×
346
                }
347

348
                if (bpf_prog_test_run_opts(bpf_program__fd(map_state_preparation_program), &opts)) {
4✔
349
                    throw std::runtime_error("Failed to run map_state_preparation program " + prep_program_name);
×
350
                }
351

352
                if (opts.retval != expected_result) {
4✔
353
                    std::string message = "map_state_preparation program " + prep_program_name + " returned unexpected value " +
×
354
                                          std::to_string(opts.retval) + " expected " + std::to_string(expected_result);
×
355
                    if (ignore_return_code.value_or(false)) {
×
356
                        std::cout << message << std::endl;
357
                    } else {
358
                        throw std::runtime_error(message);
×
359
                    }
360
                }
361
            }
3✔
362

363
            auto program_cpu_assignment = test["program_cpu_assignment"];
4✔
364
            for (auto assignment : program_cpu_assignment) {
6✔
365
                // Each node is a program name and a cpu number or a list of cpu numbers.
366
                // First check if program exists and get program fd.
367

368
                auto program_name = assignment.first.as<std::string>();
4✔
369
                auto program = bpf_object__find_program_by_name(obj.get(), program_name.c_str());
4✔
370
                if (!program) {
4✔
371
                    throw std::runtime_error("Failed to find program " + program_name);
3✔
372
                }
373

374
                int program_fd = bpf_program__fd(program);
2✔
375

376
                // Check if assignment is scalar or sequence
377
                if (assignment.second.IsScalar()) {
2✔
378
                    if (assignment.second.as<std::string>() == "all") {
×
379
                        // Assign program to all CPUs.
380
                        for (size_t i = 0; i < cpu_program_assignments.size(); i++) {
×
381
                            cpu_program_assignments[i] = {program_fd};
×
382
                        }
383
                    } else if (assignment.second.as<std::string>() == "remaining") {
×
384
                        // Assign program to all remaining CPUs.
385
                        for (size_t i = 0; i < cpu_program_assignments.size(); i++) {
×
386
                            if (!cpu_program_assignments[i].has_value()) {
×
387
                                cpu_program_assignments[i] = {program_fd};
×
388
                            }
389
                        }
390
                    } else {
391
                        int cpu = assignment.second.as<int>();
×
392
                        if (cpu > cpu_count) {
×
393
                            throw std::runtime_error("Invalid CPU number " + std::to_string(cpu));
×
394
                        }
395
                        cpu_program_assignments[assignment.as<int>()] = {program_fd};
×
396
                    }
397
                } else if (assignment.second.IsSequence()) {
2✔
398
                    for (auto cpu_assignment : assignment.second) {
×
399
                        int cpu = cpu_assignment.as<int>();
×
400
                        if (cpu > cpu_count) {
×
401
                            throw std::runtime_error("Invalid CPU number " + std::to_string(cpu));
×
402
                        }
403
                        cpu_program_assignments[cpu] = {program_fd};
×
404
                    }
×
405
                } else {
406
                    throw std::runtime_error("Invalid program_cpu_assignment - must be string or sequence");
2✔
407
                }
408
            }
10✔
409

410
            // Run the pre-test command if specified.
411
            if (pre_test_command.has_value()) {
×
412
                std::string command = pre_test_command.value();
413
                std::string command_output;
414
                command = std::regex_replace(command, std::regex("%NAME%"), name);
×
415
                command = std::regex_replace(command, std::regex("%ELF_FILE%"), elf_file);
×
416
                command = std::regex_replace(command, std::regex("%ITERATION_COUNT%"), std::to_string(iteration_count));
×
417
                command = std::regex_replace(command, std::regex("%CPU_COUNT%"), std::to_string(cpu_count));
×
418
                command = std::regex_replace(command, std::regex("%BATCH_SIZE%"), std::to_string(batch_size));
×
419
                if (run_command_and_capture_output(command, command_output) != 0) {
×
420
                    std::cerr << "Pre-test command failed: " << command << std::endl;
421
                    std::cerr << command_output << std::endl;
422
                }
423
            }
424

425
            auto now = std::chrono::system_clock::now();
×
426

427
            // Run each entry point via bpf_prog_test_run_opts in a thread.
428
            std::vector<std::jthread> threads;
429
            std::vector<bpf_test_run_opts> opts(cpu_count);
×
430

431
            for (size_t i = 0; i < cpu_program_assignments.size(); i++) {
×
432
                if (!cpu_program_assignments[i].has_value()) {
×
433
                    continue;
×
434
                }
435
                auto program = cpu_program_assignments[i].value();
×
436
                auto& opt = opts[i];
437

438
                threads.emplace_back([=, &opt](std::stop_token stop_token) {
×
439
                    memset(&opt, 0, sizeof(opt));
440
                    std::vector<uint8_t> data_in(1024);
×
441
                    std::vector<uint8_t> data_out(1024);
×
442

443
                    opt.sz = sizeof(opt);
×
444
                    opt.repeat = iteration_count_override.value_or(iteration_count);
×
445
                    opt.cpu = static_cast<uint32_t>(i);
×
446
                    if (pass_data) {
×
447
                        opt.data_in = data_in.data();
×
448
                        opt.data_out = data_out.data();
×
449
                        opt.data_size_in = static_cast<uint32_t>(data_in.size());
×
450
                        opt.data_size_out = static_cast<uint32_t>(data_out.size());
×
451
                    }
452
                    if (pass_context) {
×
453
                        opt.ctx_in = data_in.data();
×
454
                        opt.ctx_out = data_out.data();
×
455
                        opt.ctx_size_in = static_cast<uint32_t>(data_in.size());
×
456
                        opt.ctx_size_out = static_cast<uint32_t>(data_out.size());
×
457
                    }
458
#if defined(HAS_BPF_TEST_RUN_OPTS_BATCH_SIZE)
459
                    opt.batch_size = batch_size;
460
#endif
461

462
                    int result = bpf_prog_test_run_opts(program, &opt);
×
463
                    if (result < 0) {
×
464
                        opt.retval = result;
×
465
                    }
466
                });
×
467
            }
468
            for (auto& thread : threads) {
×
469
                thread.join();
470
            }
471

472
            // Check if any program returned unexpected result.
473
            for (auto& opt : opts) {
×
474
                if (opt.retval != expected_result) {
×
475
                    std::string message =
476
                        "Program returned unexpected result " + std::to_string(opt.retval) + " in test " + name + " expected " + std::to_string(expected_result);
×
477
                    if (ignore_return_code.value_or(false)) {
×
478
                        std::cout << message << std::endl;
479
                    } else {
480
                        throw std::runtime_error(message);
×
481
                    }
482
                }
483
            }
484

485
            // Run the post-test command if specified.
486
            if (post_test_command.has_value()) {
×
487
                std::string command = post_test_command.value();
488
                std::string command_output;
489
                command = std::regex_replace(command, std::regex("%NAME%"), name);
×
490
                command = std::regex_replace(command, std::regex("%ELF_FILE%"), elf_file);
×
491
                command = std::regex_replace(command, std::regex("%ITERATION_COUNT%"), std::to_string(iteration_count));
×
492
                command = std::regex_replace(command, std::regex("%CPU_COUNT%"), std::to_string(cpu_count));
×
493
                command = std::regex_replace(command, std::regex("%BATCH_SIZE%"), std::to_string(batch_size));
×
494

495
                if (run_command_and_capture_output(command, command_output) != 0) {
×
496
                    std::cerr << "Post-test command failed: " << command << std::endl;
497
                    std::cerr << command_output << std::endl;
498
                }
499
            }
500

501
            // Print a CSV header if not already printed.
502
            if (!csv_header_printed) {
×
503
                std::cout << "Timestamp,";
×
504
                std::cout << "Test,";
×
505
                std::cout << "Average Duration (ns),";
×
506
                for (size_t i = 0; i < opts.size(); i++) {
×
507
                    if (!cpu_program_assignments[i].has_value()) {
×
508
                        continue;
×
509
                    }
510
                    std::cout << "CPU " << i << " Duration (ns)";
×
511
                    if (i < opts.size() - 1) {
×
512
                        std::cout << ",";
×
513
                    }
514
                }
515
                std::cout << std::endl;
516
                csv_header_printed = true;
517
            }
518

519
            // Print the average execution time for each program on each CPU.
520
            std::cout  << to_iso8601(now) << "," << name << ",";
×
521

522
            uint64_t total_duration = 0;
523
            uint64_t total_count = 0;
524
            for (auto opt : opts) {
×
525
                total_duration += opt.duration;
×
526
                total_count ++;
×
527
            }
528
            std::cout << total_duration / total_count << ",";
×
529

530
            for (size_t i = 0; i < opts.size(); i++) {
×
531
                if (!cpu_program_assignments[i].has_value()) {
×
532
                    continue;
×
533
                }
534
                auto& opt = opts[i];
535
                std::cout << opt.duration;
×
536
                if (i < opts.size() - 1) {
×
537
                    std::cout << ",";
×
538
                }
539
            }
540
            std::cout << std::endl;
541
        }
74✔
542

543
        return 0;
544
    } catch (std::exception& e) {
151✔
545
        std::cerr << "Error: " << e.what() << std::endl;
24✔
546
        return 1;
12✔
547
    }
24✔
548
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc