@@ -35,7 +35,7 @@ var flows : hash<IpAddress, PacketStats>(1024)
3535KernelScript uses a simple and clear scoping model that eliminates ambiguity:
3636
3737- ** ` @helper ` functions** : Kernel-shared functions - accessible by all eBPF programs, compile to eBPF bytecode
38- - ** Attributed functions** (e.g., ` @xdp ` , ` @tc ` , ` @tracepoint ` ): eBPF program entry points - compile to eBPF bytecode
38+ - ** Attributed functions** (e.g., ` @xdp ` , ` @tc ` , ` @tracepoint ` , ` @perf_event ` ): eBPF program entry points - compile to eBPF bytecode
3939- ** Regular functions** : User space - functions and data structures compile to native executable
4040- ** Maps and global configs** : Shared resources accessible from both kernel and user space
4141- ** No wrapper syntax** : Direct, flat structure without unnecessary nesting
@@ -440,6 +440,98 @@ kernelscript init tracepoint/syscalls/sys_enter_read my_syscall_tracer
440440# appropriate KernelScript templates with correct context types
441441```
442442
443+ #### 3.1.3 Perf Event Programs
444+
445+ ` @perf_event ` programs attach eBPF logic to hardware or software performance counters via ` perf_event_open(2) ` . The eBPF function is invoked for every counter sample; the userspace side controls which counter to monitor through a ` perf_event_attr ` struct literal passed to ` attach() ` .
446+
447+ ** Syntax:**
448+ ``` kernelscript
449+ @perf_event
450+ fn <handler_name>(ctx: *bpf_perf_event_data) -> i32 {
451+ // runs on every sample
452+ return 0
453+ }
454+ ```
455+
456+ The context type is always ` *bpf_perf_event_data ` (from ` vmlinux.h ` ).
457+
458+ ** Userspace lifecycle:**
459+ ``` kernelscript
460+ fn main() -> i32 {
461+ var attr = perf_event_attr {
462+ counter: branch_misses, // perf_counter enum value
463+ pid: -1, // -1 = all processes; ≥0 = specific PID
464+ cpu: 0, // ≥0 = specific CPU; -1 = any CPU (pid must be ≥0)
465+ period: 1000000, // sample after this many events (0 → default 1000000)
466+ wakeup: 1, // wake userspace after N samples (0 → default 1)
467+ inherit: false, // inherit to forked children
468+ exclude_kernel: false, // exclude kernel-mode samples
469+ exclude_user: false // exclude user-mode samples
470+ }
471+
472+ var prog = load(my_handler)
473+ attach(prog, attr) // perf_event_open → IOC_RESET → attach BPF → IOC_ENABLE
474+ // ... run workload ...
475+ detach(prog) // IOC_DISABLE → bpf_link__destroy → close(perf_fd)
476+ return 0
477+ }
478+ ```
479+
480+ ** ` pid ` / ` cpu ` rules enforced at runtime:**
481+
482+ | ` pid ` | ` cpu ` | Meaning |
483+ | ---| ---| ---|
484+ | ≥ 0 | ≥ 0 | Specific process on specific CPU |
485+ | ≥ 0 | -1 | Specific process on any CPU |
486+ | -1 | ≥ 0 | All processes on specific CPU (system-wide) |
487+ | -1 | -1 | ** Invalid** — rejected with error |
488+
489+ ** ` perf_counter ` enum:**
490+
491+ | Value | Linux constant |
492+ | ---| ---|
493+ | ` cpu_cycles ` | ` PERF_COUNT_HW_CPU_CYCLES ` |
494+ | ` instructions ` | ` PERF_COUNT_HW_INSTRUCTIONS ` |
495+ | ` cache_references ` | ` PERF_COUNT_HW_CACHE_REFERENCES ` |
496+ | ` cache_misses ` | ` PERF_COUNT_HW_CACHE_MISSES ` |
497+ | ` branch_instructions ` | ` PERF_COUNT_HW_BRANCH_INSTRUCTIONS ` |
498+ | ` branch_misses ` | ` PERF_COUNT_HW_BRANCH_MISSES ` |
499+ | ` page_faults ` | ` PERF_COUNT_SW_PAGE_FAULTS ` |
500+ | ` context_switches ` | ` PERF_COUNT_SW_CONTEXT_SWITCHES ` |
501+ | ` cpu_migrations ` | ` PERF_COUNT_SW_CPU_MIGRATIONS ` |
502+
503+ ** Generated C helpers (emitted when ` attach(prog, attr) ` is used):**
504+
505+ | Function | Signature | Description |
506+ | ---| ---| ---|
507+ | ` ks_open_perf_event ` | ` int (ks_perf_event_attr) ` | Calls ` perf_event_open(2) ` , returns fd |
508+ | ` ks_read_perf_count ` | ` int64_t (int perf_fd) ` | Reads current 64-bit counter via ` read() ` |
509+ | ` ks_print_perf_count ` | ` void (int perf_fd, const char*) ` | Prints ` [perf] <name>: <count> ` to stdout |
510+
511+ ** Attach sequence (compiler-generated):**
512+ 1 . ` ks_attr.attr.disabled = 1 ` — open counter without starting it
513+ 2 . ` syscall(SYS_perf_event_open, ...) ` → ` perf_fd `
514+ 3 . ` ioctl(perf_fd, PERF_EVENT_IOC_RESET, 0) ` — zero the counter
515+ 4 . ` bpf_program__attach_perf_event(prog, perf_fd) ` — link BPF program
516+ 5 . ` ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0) ` — ** start counting**
517+
518+ ** Detach sequence (compiler-generated):**
519+ 1 . ` ioctl(perf_fd, PERF_EVENT_IOC_DISABLE, 0) ` — stop counting
520+ 2 . ` bpf_link__destroy(link) ` — unlink BPF program
521+ 3 . ` close(perf_fd) ` — release the kernel perf event
522+
523+ ** Compiler implementation:**
524+ - Detects ` attach(prog, perf_event_attr_value) ` call (two-argument form) and emits ` ks_open_perf_event ` + ` attach_bpf_program_by_fd ` sequence
525+ - Validates ` pid ≥ -1 ` , ` cpu ≥ -1 ` , and rejects ` pid == -1 && cpu == -1 ` at runtime
526+ - Emits ` PERF_FLAG_FD_CLOEXEC ` for safe fd inheritance
527+ - BPF program section is ` SEC("perf_event") `
528+
529+ ** Project Initialization:**
530+ ``` bash
531+ # Initialize a perf_event project
532+ kernelscript init perf_event my_perf_monitor
533+ ```
534+
443535### 3.2 Named Configuration Blocks
444536``` kernelscript
445537// Named configuration blocks - globally accessible
0 commit comments