mirror of https://github.com/aya-rs/aya
				
				
				
			Merge 0aeb379beb into 3fedc2a706
				
					
				
			
						commit
						66b3afb73f
					
				| @ -0,0 +1,58 @@ | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| 
 | ||||
| use aya_bpf::{ | ||||
|     bindings::bpf_perf_event_value, | ||||
|     helpers::bpf_get_smp_processor_id, | ||||
|     macros::{map, perf_event}, | ||||
|     maps::PerfEventArray, | ||||
|     programs::PerfEventContext, | ||||
| }; | ||||
| 
 | ||||
| /// Data sent by the bpf program to userspace.
 | ||||
| /// This structure must be defined in the exact same way on the userspace side.
 | ||||
| #[repr(C)] | ||||
| struct EventData { | ||||
|     value: u64, | ||||
|     cpu_id: u32, | ||||
|     tag: u8, | ||||
| } | ||||
| 
 | ||||
| /// Input map: file descriptors of the perf events, obtained by calling
 | ||||
| /// `perf_event_open` in user space.
 | ||||
| #[map] | ||||
| static mut DESCRIPTORS: PerfEventArray<i32> = PerfEventArray::with_max_entries(1, 0); | ||||
| 
 | ||||
| #[map] | ||||
| static mut OUTPUT: PerfEventArray<EventData> = PerfEventArray::with_max_entries(1, 0); | ||||
| 
 | ||||
| #[perf_event] | ||||
| pub fn on_perf_event(ctx: PerfEventContext) -> i64 { | ||||
|     match read_event(&ctx).and_then(|res| write_output(&ctx, res)) { | ||||
|         Ok(()) => 0, | ||||
|         Err(e) => e, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn read_event(ctx: &PerfEventContext) -> Result<EventData, i64> { | ||||
|     // read the event value using the file descriptor in the DESCRIPTORS array
 | ||||
|     let event: bpf_perf_event_value = unsafe { DESCRIPTORS.read_current_cpu() }?; | ||||
| 
 | ||||
|     let cpu_id = unsafe { bpf_get_smp_processor_id() }; | ||||
|     let res = EventData { | ||||
|         value: event.counter, | ||||
|         cpu_id, | ||||
|         tag: 0xAB, | ||||
|     }; | ||||
|     Ok(res) | ||||
| } | ||||
| 
 | ||||
| fn write_output(ctx: &PerfEventContext, output: EventData) -> Result<(), i64> { | ||||
|     unsafe { OUTPUT.output_current_cpu(ctx, &output) } | ||||
| } | ||||
| 
 | ||||
| #[cfg(not(test))] | ||||
| #[panic_handler] | ||||
| fn panic(_info: &core::panic::PanicInfo) -> ! { | ||||
|     loop {} | ||||
| } | ||||
| @ -0,0 +1,162 @@ | ||||
| use std::os::fd::OwnedFd; | ||||
| use std::time::{Duration, Instant}; | ||||
| 
 | ||||
| use aya::maps::perf::Events; | ||||
| use aya::maps::{AsyncPerfEventArray, PerfEventArray}; | ||||
| use aya::programs::perf_event::{perf_event_open, PerfEventLinkId, PerfEventScope}; | ||||
| use aya::programs::{PerfEvent, PerfTypeId, ProgramError, SamplePolicy}; | ||||
| use aya::Bpf; | ||||
| use aya_obj::generated::{perf_hw_id, perf_sw_ids, perf_type_id}; | ||||
| use bytes::BytesMut; | ||||
| use test_log::test; | ||||
| 
 | ||||
| /// Data sent by the bpf program to userspace.
 | ||||
| /// This structure must be defined in the exact same way on the bpf side.
 | ||||
| #[derive(Debug)] | ||||
| #[repr(C)] | ||||
| struct EventData { | ||||
|     value: u64, | ||||
|     cpu_id: u32, | ||||
|     tag: u8, | ||||
| } | ||||
| 
 | ||||
| const CPU_ID: u32 = 0; | ||||
| const SAMPLING_FREQUENCY_HZ: u64 = 100; | ||||
| const BUF_PAGE_COUNT: usize = 2; // must be a power of two
 | ||||
| const WAIT_TIMEOUT: Duration = Duration::from_secs(1); | ||||
| 
 | ||||
| /// Opens an hardware perf_event for testing.
 | ||||
| fn open_perf_event_hw() -> Result<OwnedFd, ProgramError> { | ||||
|     perf_event_open( | ||||
|         perf_type_id::PERF_TYPE_HARDWARE as u32, | ||||
|         perf_hw_id::PERF_COUNT_HW_CPU_CYCLES as u64, | ||||
|         PerfEventScope::AllProcessesOneCpu { cpu: CPU_ID }, | ||||
|         None, | ||||
|         None, | ||||
|         0, | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| /// Attaches a PerfEvent bpf program to a software clock event.
 | ||||
| fn attach_bpf_to_clock(program: &mut PerfEvent) -> Result<PerfEventLinkId, ProgramError> { | ||||
|     program.attach( | ||||
|         PerfTypeId::Software, | ||||
|         perf_sw_ids::PERF_COUNT_SW_CPU_CLOCK as u64, | ||||
|         PerfEventScope::AllProcessesOneCpu { cpu: CPU_ID }, | ||||
|         SamplePolicy::Frequency(SAMPLING_FREQUENCY_HZ), | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn perf_event_read_from_kernel() { | ||||
|     // load bpf program
 | ||||
|     let mut bpf = Bpf::load(crate::PERF_EVENTS).expect("failed to load bpf code"); | ||||
|     let mut descriptors = PerfEventArray::try_from(bpf.take_map("DESCRIPTORS").unwrap()).unwrap(); | ||||
|     let mut bpf_output = PerfEventArray::try_from(bpf.take_map("OUTPUT").unwrap()).unwrap(); | ||||
| 
 | ||||
|     // open a perf_event
 | ||||
|     let event_fd = open_perf_event_hw().unwrap(); | ||||
| 
 | ||||
|     // pass pointer to bpf array
 | ||||
|     descriptors | ||||
|         .set(0, &event_fd) | ||||
|         .expect("failed to put event's fd into the map"); | ||||
| 
 | ||||
|     // load program
 | ||||
|     let program: &mut PerfEvent = bpf | ||||
|         .program_mut("on_perf_event") | ||||
|         .unwrap() | ||||
|         .try_into() | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     program | ||||
|         .load() | ||||
|         .expect("the bpf program should load properly"); | ||||
| 
 | ||||
|     // get buffer to poll the events
 | ||||
|     let mut buf = bpf_output | ||||
|         .open(CPU_ID, Some(BUF_PAGE_COUNT)) | ||||
|         .expect("failed to open output buffer to poll events"); | ||||
| 
 | ||||
|     // attach program
 | ||||
|     attach_bpf_to_clock(program).expect("the bpf program should attach properly"); | ||||
| 
 | ||||
|     // wait for the values to be added to the buffer
 | ||||
|     let t0 = Instant::now(); | ||||
|     while !buf.readable() { | ||||
|         std::thread::sleep(Duration::from_millis(1000 / SAMPLING_FREQUENCY_HZ)); | ||||
|         assert!( | ||||
|             t0.elapsed() < WAIT_TIMEOUT, | ||||
|             "timeout elapsed: no data in the buffer" | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     // read the events and check that the returned data is correct
 | ||||
|     let mut events_data: [BytesMut; BUF_PAGE_COUNT] = std::array::from_fn(|_| BytesMut::new()); | ||||
|     let Events { read, lost } = buf | ||||
|         .read_events(&mut events_data) | ||||
|         .expect("failed to poll events"); | ||||
| 
 | ||||
|     for data_buf in events_data.iter_mut().take(read) { | ||||
|         let ptr = data_buf.as_ptr() as *const EventData; | ||||
|         let data @ EventData { cpu_id, tag, value } = unsafe { ptr.read_unaligned() }; | ||||
|         assert!(value > 0, "unexpected data: {:?}", value); | ||||
|         assert_eq!(cpu_id, CPU_ID, "unexpected data: {:?}", data); | ||||
|         assert_eq!(tag, 0xAB, "unexpected data: {:?}", data); | ||||
|     } | ||||
|     assert_eq!(lost, 0, "lost {} events", lost); | ||||
| } | ||||
| 
 | ||||
| #[test(tokio::test)] | ||||
| async fn perf_event_read_from_kernel_async() { | ||||
|     // load bpf program
 | ||||
|     let mut bpf = Bpf::load(crate::PERF_EVENTS).expect("failed to load bpf code"); | ||||
|     let mut descriptors = | ||||
|         AsyncPerfEventArray::try_from(bpf.take_map("DESCRIPTORS").unwrap()).unwrap(); | ||||
|     let mut bpf_output = AsyncPerfEventArray::try_from(bpf.take_map("OUTPUT").unwrap()).unwrap(); | ||||
| 
 | ||||
|     // open a perf_event
 | ||||
|     let event_fd = open_perf_event_hw().unwrap(); | ||||
| 
 | ||||
|     // pass pointer to bpf array
 | ||||
|     descriptors | ||||
|         .set(0, &event_fd) | ||||
|         .expect("failed to put event's fd into the map"); | ||||
| 
 | ||||
|     // load program
 | ||||
|     let program: &mut PerfEvent = bpf | ||||
|         .program_mut("on_perf_event") | ||||
|         .unwrap() | ||||
|         .try_into() | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     program | ||||
|         .load() | ||||
|         .expect("the bpf program should load properly"); | ||||
| 
 | ||||
|     // get buffer to poll the events
 | ||||
|     let mut buf = bpf_output | ||||
|         .open(CPU_ID, Some(BUF_PAGE_COUNT)) | ||||
|         .expect("failed to open output buffer to poll events"); | ||||
| 
 | ||||
|     // attach program
 | ||||
|     attach_bpf_to_clock(program).expect("the bpf program should attach properly"); | ||||
| 
 | ||||
|     // read the events as soon as they are available
 | ||||
|     let mut events_data: [BytesMut; BUF_PAGE_COUNT] = std::array::from_fn(|_| BytesMut::new()); | ||||
|     let Events { read, lost } = | ||||
|         tokio::time::timeout(WAIT_TIMEOUT, buf.read_events(&mut events_data)) | ||||
|             .await | ||||
|             .expect("timeout elapsed: no data in the buffer") | ||||
|             .expect("failed to poll events"); | ||||
| 
 | ||||
|     // check that the returned data is correct
 | ||||
|     for data_buf in events_data.iter_mut().take(read) { | ||||
|         let ptr = data_buf.as_ptr() as *const EventData; | ||||
|         let data @ EventData { cpu_id, tag, value } = unsafe { ptr.read_unaligned() }; | ||||
|         assert!(value > 0, "unexpected data: {:?}", value); | ||||
|         assert_eq!(cpu_id, CPU_ID, "unexpected data: {:?}", data); | ||||
|         assert_eq!(tag, 0xAB, "unexpected data: {:?}", data); | ||||
|     } | ||||
|     assert_eq!(lost, 0, "lost {} events", lost) | ||||
| } | ||||
					Loading…
					
					
				
		Reference in New Issue