Skip to content

Commit a0133a4

Browse files
committed
Add @perf_event program type with full attach/detach/count support
## Language changes - New `@perf_event` program attribute; context type `*bpf_perf_event_data` - New `perf_event_attr` struct literal with counter, pid, cpu, period, wakeup, inherit, exclude_kernel, exclude_user fields - New `perf_counter` enum: cpu_cycles, instructions, cache_references, cache_misses, branch_instructions, branch_misses, page_faults, context_switches, cpu_migrations - `attach(prog, attr)` two-argument form for perf_event programs - `detach(prog)` cleans up BPF link, disables and closes perf fd ## Compiler / codegen - AST: PerfEvent program type, perf_event_attr struct, perf_counter enum - Type checker: validates @perf_event function signatures and attr fields - IR generator: recognises PerfEvent program type - ebpf_c_codegen: emits SEC("perf_event") and bpf_perf_event_data context - userspace_codegen: - ks_open_perf_event(): maps perf_counter enum to PERF_TYPE/PERF_COUNT constants, validates pid/cpu rules, calls perf_event_open(2) - attach sequence: disabled=1 → IOC_RESET → attach_perf_event → IOC_ENABLE - detach sequence: IOC_DISABLE → bpf_link__destroy → close(perf_fd) - ks_read_perf_count(): reads raw 64-bit counter via read() - ks_print_perf_count(): prints "[perf] <name>: <count>" with PRId64 ## Tests - tests/test_perf_event_attach.ml (6 test cases): - pid/cpu validation rules enforced - counting startup ordering (RESET before ENABLE, attach before ENABLE) - period/wakeup default values when 0 - custom period/wakeup runtime expressions - ks_read/print_perf_count helpers generated with correct logic - standard attach branches use libbpf_get_error ## Example - examples/perf_branch_miss.ks: minimal @perf_event example (branch misses) - examples/perf_branch_miss/: pre-built reference C output ## Docs - README.md: @perf_event in program types overview; perf_counter table; Hardware Performance Counter Programs section with full lifecycle example - SPEC.md: section 3.1.3 Perf Event Programs — syntax, pid/cpu rules, perf_counter enum, generated C helpers, attach/detach sequence steps
1 parent 1757eb9 commit a0133a4

21 files changed

Lines changed: 1032 additions & 64 deletions

BUILTINS.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,22 @@ fn main() -> i32 {
8383

8484
---
8585

86-
#### `attach(handle, target, flags)`
86+
#### `attach(handle, target, flags)` / `attach(handle, attr)`
8787
**Signature:** `attach(handle: ProgramHandle, target: str(128), flags: u32) -> u32`
88+
**Signature:** `attach(handle: ProgramHandle, attr: perf_event_attr) -> u32`
8889
**Variadic:** No
8990
**Context:** Userspace only
9091

91-
**Description:** Attach a loaded eBPF program to a target interface or attachment point.
92+
**Description:** Attach a loaded eBPF program to a target interface or attachment point, or attach it to a perf event described by `perf_event_attr`.
9293

9394
**Parameters:**
94-
- `handle`: Program handle returned from `load()`
95-
- `target`: Target interface name (e.g., "eth0", "lo") or attachment point
96-
- `flags`: Attachment flags (context-dependent)
95+
- Standard form:
96+
- `handle`: Program handle returned from `load()`
97+
- `target`: Target interface name (e.g., "eth0", "lo") or attachment point
98+
- `flags`: Attachment flags (context-dependent)
99+
- Perf event form:
100+
- `handle`: Program handle returned from `load()`
101+
- `attr`: `perf_event_attr` value describing counter, pid, cpu, period, and filter flags
97102

98103
**Return Value:**
99104
- Returns `0` on success
@@ -106,11 +111,25 @@ var result = attach(prog, "eth0", 0)
106111
if (result != 0) {
107112
print("Failed to attach program")
108113
}
114+
115+
var perf_attr = perf_event_attr {
116+
counter: branch_misses,
117+
pid: -1,
118+
cpu: 0,
119+
period: 1000000,
120+
wakeup: 1,
121+
inherit: false,
122+
exclude_kernel: false,
123+
exclude_user: false
124+
}
125+
126+
var perf_prog = load(on_branch_miss)
127+
attach(perf_prog, perf_attr)
109128
```
110129

111130
**Context-specific implementations:**
112131
- **eBPF:** Not available
113-
- **Userspace:** Uses `bpf_prog_attach` system call
132+
- **Userspace:** Uses `attach_bpf_program_by_fd` for standard targets and `ks_open_perf_event` for perf events
114133
- **Kernel Module:** Not available
115134

116135
---
@@ -340,7 +359,7 @@ fn main() -> i32 {
340359
|----------|------|-----------|---------------|-------|
341360
| `print()` |||| Different output destinations |
342361
| `load()` |||| Program management only |
343-
| `attach()` |||| Program management only |
362+
| `attach()` |||| Standard attach and perf_event_attr attach |
344363
| `detach()` |||| Program management only |
345364
| `register()` |||| struct_ops registration |
346365
| `test()` |||| Testing framework only |

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ fn traffic_shaper(ctx: *__sk_buff) -> i32 {
119119
// Trace system call entry
120120
return 0
121121
}
122+
123+
// Perf event program for hardware counter sampling
124+
@perf_event
125+
fn on_branch_miss(ctx: *bpf_perf_event_data) -> i32 {
126+
// Runs on every hardware branch-miss event
127+
return 0
128+
}
122129
```
123130

124131
### Type System
@@ -261,6 +268,50 @@ fn main() -> i32 {
261268
}
262269
```
263270

271+
### Hardware Performance Counter Programs
272+
273+
Use `@perf_event` to attach eBPF programs to hardware or software performance counters. The userspace side describes the counter via a `perf_event_attr` struct literal and calls `attach(prog, attr)`:
274+
275+
```kernelscript
276+
// eBPF program fires on every hardware branch-miss sample
277+
@perf_event
278+
fn on_branch_miss(ctx: *bpf_perf_event_data) -> i32 {
279+
return 0
280+
}
281+
282+
fn main() -> i32 {
283+
var attr = perf_event_attr {
284+
counter: branch_misses, // hardware counter (see perf_counter enum)
285+
pid: -1, // all processes
286+
cpu: 0, // CPU 0
287+
period: 1000000, // sample every 1 million events
288+
wakeup: 1,
289+
inherit: false,
290+
exclude_kernel: false,
291+
exclude_user: false
292+
}
293+
294+
var prog = load(on_branch_miss)
295+
attach(prog, attr) // opens perf_event_open fd, resets, attaches BPF, enables
296+
detach(prog) // disables counter, destroys BPF link, closes fd
297+
return 0
298+
}
299+
```
300+
301+
**Available `perf_counter` values:**
302+
303+
| Enum value | Hardware/software event |
304+
|---|---|
305+
| `cpu_cycles` | `PERF_COUNT_HW_CPU_CYCLES` |
306+
| `instructions` | `PERF_COUNT_HW_INSTRUCTIONS` |
307+
| `cache_references` | `PERF_COUNT_HW_CACHE_REFERENCES` |
308+
| `cache_misses` | `PERF_COUNT_HW_CACHE_MISSES` |
309+
| `branch_instructions` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` |
310+
| `branch_misses` | `PERF_COUNT_HW_BRANCH_MISSES` |
311+
| `page_faults` | `PERF_COUNT_SW_PAGE_FAULTS` |
312+
| `context_switches` | `PERF_COUNT_SW_CONTEXT_SWITCHES` |
313+
| `cpu_migrations` | `PERF_COUNT_SW_CPU_MIGRATIONS` |
314+
264315
📖 **For detailed language specification, syntax reference, and advanced features, please read [`SPEC.md`](SPEC.md).**
265316

266317
🔧 **For complete builtin functions reference, see [`BUILTINS.md`](BUILTINS.md).**
@@ -304,6 +355,7 @@ my_project/
304355
- `tc` - Traffic control programs
305356
- `probe` - Kernel function probing
306357
- `tracepoint` - Kernel tracepoint programs
358+
- `perf_event` - Hardware/software performance counter programs
307359

308360
**Available struct_ops:**
309361
- `tcp_congestion_ops` - TCP congestion control

SPEC.md

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var flows : hash<IpAddress, PacketStats>(1024)
3535
KernelScript 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

examples/perf_branch_miss.ks

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// perf_branch_miss.ks
2+
// Demonstrates @perf_event program type in KernelScript.
3+
// The eBPF program runs on every hardware branch-miss event.
4+
// The userspace side opens the perf event and attaches the BPF program.
5+
6+
@perf_event
7+
fn on_branch_miss(ctx: *bpf_perf_event_data) -> i32 {
8+
return 0
9+
}
10+
11+
fn main() -> i32 {
12+
var attr = perf_event_attr {
13+
counter: branch_misses,
14+
pid: -1,
15+
cpu: 0,
16+
period: 1000000,
17+
wakeup: 1,
18+
inherit: false,
19+
exclude_kernel: false,
20+
exclude_user: false
21+
}
22+
23+
var prog = load(on_branch_miss)
24+
attach(prog, attr)
25+
detach(prog)
26+
27+
return 0
28+
}

src/ast.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type probe_type =
4040

4141
(** Program types supported by KernelScript *)
4242
type program_type =
43-
| Xdp | Tc | Probe of probe_type | Tracepoint | StructOps
43+
| Xdp | Tc | Probe of probe_type | Tracepoint | StructOps | PerfEvent
4444

4545
(** Map types for eBPF maps *)
4646
type map_type =
@@ -658,6 +658,7 @@ let string_of_program_type = function
658658
| Probe Kprobe -> "kprobe"
659659
| Tracepoint -> "tracepoint"
660660
| StructOps -> "struct_ops"
661+
| PerfEvent -> "perf_event"
661662

662663
let string_of_map_type = function
663664
| Hash -> "hash"

src/btf_parser.ml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ let get_program_template prog_type btf_path =
106106
| "tc" -> ("*__sk_buff", "i32", [
107107
"__sk_buff"
108108
])
109+
| "perf_event" -> ("*bpf_perf_event_data", "i32", [
110+
"bpf_perf_event_data"
111+
])
109112
| _ -> failwith (sprintf "Unsupported program type '%s' for generic template. Use specific template functions for kprobe/tracepoint." prog_type)
110113
in
111114

@@ -364,6 +367,7 @@ let generate_kernelscript_source ?extra_param ?include_kfuncs template project_n
364367
Kernelscript_context.Kprobe_codegen.register ();
365368
Kernelscript_context.Tracepoint_codegen.register ();
366369
Kernelscript_context.Fprobe_codegen.register ();
370+
Kernelscript_context.Perf_event_codegen.register ();
367371

368372
(* Get program description from context codegen system *)
369373
let context_comment = "// " ^ (Kernelscript_context.Context_codegen.get_context_program_description template.program_type) in
@@ -502,6 +506,39 @@ let generate_kernelscript_source ?extra_param ?include_kfuncs template project_n
502506
| None -> ""
503507
in
504508

509+
(* perf_event programs use a completely different main() with attach(prog, attr) *)
510+
if template.program_type = "perf_event" then
511+
sprintf {|%s
512+
// Generated by KernelScript compiler with direct BTF parsing%s
513+
514+
%s
515+
%s {
516+
// TODO: Implement your perf_event logic here
517+
518+
return %s
519+
}
520+
521+
fn main() -> i32 {
522+
var attr = perf_event_attr {
523+
counter: branch_misses,
524+
pid: -1,
525+
cpu: 0,
526+
period: 1000000,
527+
wakeup: 1,
528+
inherit: false,
529+
exclude_kernel: false,
530+
exclude_user: false
531+
}
532+
533+
var prog = load(%s)
534+
attach(prog, attr)
535+
detach(prog)
536+
537+
return 0
538+
}
539+
|} context_comment include_line attribute_line function_definition sample_return function_name
540+
else
541+
505542
sprintf {|%s
506543
// Generated by KernelScript compiler with direct BTF parsing%s
507544
%s
@@ -549,6 +586,9 @@ let get_program_btf_types prog_type =
549586
| "tracepoint" -> [
550587
("trace_entry", "struct");
551588
]
589+
| "perf_event" -> [
590+
("bpf_perf_event_data", "struct");
591+
]
552592
| _ -> []
553593

554594
(* Program-type specific kfunc names to extract from BTF *)

src/codegen_common.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ let rec ir_type_to_c target = function
4343
| UserspaceStd -> "char") (* Base type for userspace string - size handled in declaration *)
4444
| IRPointer (inner_type, _) -> sprintf "%s*" (ir_type_to_c target inner_type)
4545
| IRArray (inner_type, size, _) -> sprintf "%s[%d]" (ir_type_to_c target inner_type) size
46+
| IRStruct ("perf_event_attr", _) -> "ks_perf_event_attr" (* Avoid conflict with linux/perf_event.h *)
4647
| IRStruct (name, _) -> sprintf "struct %s" name
4748
| IREnum (name, _) -> sprintf "enum %s" name
4849
| IRResult (ok_type, _err_type) -> ir_type_to_c target ok_type (* simplified to ok type *)

src/context/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(library
22
(public_name kernelscript.context)
33
(name kernelscript_context)
4-
(modules context_codegen xdp_codegen tc_codegen kprobe_codegen tracepoint_codegen fprobe_codegen)
4+
(modules context_codegen xdp_codegen tc_codegen kprobe_codegen tracepoint_codegen fprobe_codegen perf_event_codegen)
55
(libraries unix str))

0 commit comments

Comments
 (0)