Implement perf event array map synchronous API#4966
Implement perf event array map synchronous API#4966mikeagun wants to merge 20 commits intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request implements synchronous APIs for perf event array maps in eBPF for Windows, providing Linux-compatible libbpf APIs for consuming perf buffer events. The implementation adds synchronous mode alongside the existing asynchronous mode, with the synchronous mode now being the default to match Linux libbpf behavior.
Changes:
- Adds synchronous perf buffer consumer APIs (
perf_buffer__poll,perf_buffer__consume,perf_buffer__consume_buffer,perf_buffer__buffer_cnt) - Implements per-CPU ring buffer mapping with wait handle support for synchronous event notification
- Moves lost event counter from per-ring structure to shared producer page for user-space visibility
- Updates documentation with comprehensive examples for all consumer modes (synchronous polling, asynchronous callbacks, and wait handle consumers)
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/libbpf_test.cpp | Comprehensive unit tests for perf buffer APIs covering error cases, creation, consumption, and lost event handling |
| tests/sample/perf_event_burst.c | Test program for generating burst events to validate lost event handling |
| tests/libs/util/misc_helper.h | Adds scoped_cpu_affinity RAII helper for setting thread CPU affinity in tests |
| tests/api_test/api_test.cpp | Integration tests for synchronous perf buffer APIs with real programs and multi-threaded scenarios |
| libs/runtime/ebpf_ring_buffer.h | Adds producer page accessor functions for ring buffer and perf event arrays |
| libs/runtime/ebpf_ring_buffer.c | Implements producer page accessors with type casting for perf event array extension |
| libs/execution_context/unit/execution_context_unit_test.cpp | Adds test for synchronous per-CPU ring buffer mapping and consumption |
| libs/execution_context/ebpf_protocol.h | Adds index field to ring buffer map/unmap protocol messages for per-CPU buffer support |
| libs/execution_context/ebpf_maps.h | Updates ring buffer map/unmap APIs to include index parameter for per-CPU access |
| libs/execution_context/ebpf_maps.c | Implements per-CPU mapping/unmapping, wait handle support, and lost counter tracking in producer page |
| libs/execution_context/ebpf_core.c | Updates protocol handlers to pass index parameter for ring buffer operations |
| libs/api/libbpf_map.cpp | Main implementation of synchronous perf buffer manager with poll, consume, and per-CPU consumption APIs |
| libs/api/ebpf_api.cpp | Adds internal index-based ring buffer map/unmap functions wrapping existing APIs |
| libs/api/api_internal.h | Declares internal index-based ring buffer mapping functions |
| include/ebpf_api.h | Defines perf event array producer page structure and adds wait handle getter APIs |
| include/bpf/libbpf.h | Adds Linux-compatible perf buffer APIs (poll, consume, consume_buffer, buffer_cnt) |
| ebpfapi/Source.def | Exports new perf buffer API functions |
| docs/PerfEventArray.md | Comprehensive documentation update with examples for all consumer modes and API differences |
| /** | ||
| * @brief Get the shared producer page for the perf event array. | ||
| * | ||
| * TODO(before merge): should this be in a separate file? | ||
| * | ||
| * @param[in, out] ring_buffer Ring buffer to query. | ||
| * @retval Pointer to the producer page, or NULL if not found. | ||
| */ | ||
| ebpf_perf_event_array_producer_page_t* | ||
| ebpf_perf_event_array_get_producer_page(_Inout_ ebpf_ring_buffer_t* ring_buffer); |
There was a problem hiding this comment.
This TODO comment suggests uncertainty about API placement. Since ebpf_perf_event_array_get_producer_page is specific to perf event arrays and not general ring buffers, consider moving this to a dedicated perf_event_array header file (e.g., ebpf_perf_event_array.h) for better code organization. However, if the implementation is tightly coupled with the ring buffer internals, the current placement may be acceptable.
There was a problem hiding this comment.
I'm not sure on this.
The implementations are tightly coupled in some places -- for example the perf producer page must extend the ring buffer map producer page, and they actually point to the same page of memory (the ring producer page).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
libs/runtime/ebpf_ring_buffer.h:1
- TODO comment should be removed before merging. If the question about file organization needs to be addressed, it should be resolved now or tracked in a separate issue.
// Copyright (c) eBPF for Windows contributors
e4e6b38 to
74bc84a
Compare
| int | ||
| perf_buffer__consume_buffer(struct perf_buffer* pb, size_t cpu) | ||
| { | ||
| if (!pb || pb->is_async_mode || cpu >= pb->sync_maps.size()) { | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| // Process available data from the specific CPU buffer. | ||
| auto& map_info = pb->sync_maps[cpu]; | ||
| return _process_ring_records(map_info); // Process all records. | ||
| } |
There was a problem hiding this comment.
The perf_buffer__consume, perf_buffer__consume_buffer, and perf_buffer__poll functions return negative error codes (e.g., -EINVAL) but don't set errno. This is inconsistent with Linux libbpf behavior and with the test expectations at libbpf_test.cpp:1500 which checks errno == EINVAL after a failure. These functions should set errno before returning negative error codes to match Linux libbpf behavior.
| _Must_inspect_result_ _Success_(return == EBPF_SUCCESS) ebpf_result_t ebpf_perf_buffer_get_buffer( | ||
| _In_ struct perf_buffer* pb, | ||
| _In_ uint32_t index, | ||
| _Outptr_result_maybenull_ ebpf_ring_buffer_consumer_page_t** consumer_page, | ||
| _Outptr_result_maybenull_ const ebpf_ring_buffer_producer_page_t** producer_page, | ||
| _Outptr_result_buffer_maybenull_(*data_size) const uint8_t** data, | ||
| _Out_opt_ uint64_t* data_size) EBPF_NO_EXCEPT | ||
| { | ||
| if (!pb || !consumer_page || !producer_page || !data || !data_size) { | ||
| return EBPF_INVALID_ARGUMENT; | ||
| } | ||
|
|
||
| if (pb->is_async_mode) { | ||
| // For async mode, direct buffer access isn't currently supported. | ||
| return EBPF_INVALID_ARGUMENT; | ||
| } | ||
|
|
||
| if (index >= pb->sync_maps.size()) { | ||
| return EBPF_OBJECT_NOT_FOUND; | ||
| } | ||
|
|
||
| // Return buffer info for the specified map (CPU). | ||
| const auto& map_info = pb->sync_maps[index]; | ||
| *consumer_page = static_cast<ebpf_ring_buffer_consumer_page_t*>(map_info.consumer_page); | ||
| *producer_page = static_cast<const ebpf_ring_buffer_producer_page_t*>(map_info.producer_page); | ||
| *data = map_info.data; | ||
| *data_size = map_info.data_size; | ||
|
|
||
| return EBPF_SUCCESS; | ||
| } No newline at end of file |
There was a problem hiding this comment.
The function ebpf_perf_buffer_get_buffer is implemented but not exported in ebpfapi/Source.def and not declared in include/ebpf_api.h. This makes it inaccessible to external consumers. If this function is intended to be part of the public API, it needs to be added to both the header file and the exports list. If it's meant to be internal only, consider renaming it with a leading underscore prefix.
| ebpf_perf_event_array_producer_page_t* | ||
| ebpf_perf_event_array_get_producer_page(_Inout_ ebpf_ring_buffer_t* ring_buffer) | ||
| { | ||
| return (ebpf_perf_event_array_producer_page_t*)ring_buffer->producer_page; | ||
| } |
There was a problem hiding this comment.
The C-style cast from ebpf_ring_buffer_producer_page_t* to ebpf_perf_event_array_producer_page_t* is unsafe and relies on implicit struct layout assumptions. This cast only works correctly if ebpf_perf_event_array_producer_page_t is actually pointing to memory that was allocated with the perf event array layout. Consider adding a runtime check or using static_assert to verify that ebpf_perf_event_array_producer_page_t's first field matches ebpf_ring_buffer_producer_page_t layout, or add documentation clearly stating the memory layout requirement.
5c9aa13 to
cf90253
Compare
Description
Implements libbpf synchronous perf event array map API.
Closes #4811
Testing
Adds tests for the new synchronous consumer API to
api_test.cppandlibbpf_test.cppDocumentation
Updates docs to reflect current state of the API.
Installation
N/A