Skip to content

Commit 14bebdb

Browse files
SiyuanSun0736Copilot
andcommitted
Refactor perf_event handling in KernelScript
- Updated the `perf_options` structure to require `perf_type` and `perf_config` instead of the previous `counter` field. - Modified examples and tests to reflect the new `perf_type` and `perf_config` usage. - Removed the `perf_print` function and its references, as it is no longer necessary with the new `perf_read` implementation. - Enhanced the `ks_open_perf_event` and `ks_perf_read` functions to directly utilize the new `perf_type` and `perf_config` fields. - Updated documentation in `SPEC.md` to clarify the changes in the `perf_options` structure and the new enums for `perf_type` and `perf_config`. - Adjusted tests to ensure proper generation of helper functions based on the new `perf_event` API. Co-authored-by: Copilot <copilot@github.com>
1 parent c3e2167 commit 14bebdb

10 files changed

Lines changed: 363 additions & 315 deletions

File tree

BUILTINS.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ fn main() -> i32 {
9898
- `flags`: Attachment flags (context-dependent)
9999
- Perf event form:
100100
- `handle`: Program handle returned from `load()`
101-
- `opts`: `perf_options` value — only `counter` is required; all other fields have defaults
101+
- `opts`: `perf_options` value — only `perf_type` and `perf_config` are required; all other fields have defaults
102102
- `flags`: Reserved (pass `0`)
103103

104104
**Return Value:**
@@ -113,11 +113,10 @@ if (result != 0) {
113113
print("Failed to attach program")
114114
}
115115
116-
// Minimal perf attach — all non-counter fields use defaults:
116+
// Minimal perf attach — all non-perf_type/perf_config fields use defaults:
117117
// pid=-1 (all procs), cpu=0, period=1_000_000, wakeup=1, flags=false
118118
var perf_prog = load(on_branch_miss)
119-
attach(perf_prog, perf_options { counter: branch_misses }, 0)
120-
var count = perf_read(perf_prog)
119+
attach(perf_prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0)
121120
detach(perf_prog)
122121
```
123122

README.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ fn main() -> i32 {
270270

271271
### Hardware Performance Counter Programs
272272

273-
Use `@perf_event` to attach eBPF programs to hardware or software performance counters. Only `counter` is required in the `perf_options` struct; all other fields have sensible defaults. Call `attach(prog, perf_options { ... }, 0)` and read back the counter with `perf_read(prog)`:
273+
Use `@perf_event` to attach eBPF programs to hardware or software performance counters. `perf_options` keeps the kernel's tagged `perf_type + perf_config` model, so adding new perf event families does not require flattening everything into one enum. Only `perf_type` and `perf_config` are required; all other fields have sensible defaults. If you need the current count in userspace, call `perf_read(prog)` after `attach(...)`:
274274

275275
```kernelscript
276276
// eBPF program fires on every hardware branch-miss sample
@@ -284,29 +284,41 @@ fn main() -> i32 {
284284
285285
// Minimal form — defaults: pid=-1 (all procs), cpu=0,
286286
// period=1_000_000, wakeup=1, all flags=false
287-
attach(prog, perf_options { counter: branch_misses }, 0)
288-
289-
var count = perf_read(prog) // read counter via program handle
290-
print(count)
287+
attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0)
288+
var count = perf_read(prog)
289+
print("branch misses: %lld", count)
291290
292291
detach(prog) // disables counter, destroys BPF link, closes fd
293292
return 0
294293
}
295294
```
296295

297-
**Available `perf_counter` values:**
296+
**Available `perf_type` values:**
298297

299298
| Enum value | Hardware/software event |
300299
|---|---|
301-
| `cpu_cycles` | `PERF_COUNT_HW_CPU_CYCLES` |
302-
| `instructions` | `PERF_COUNT_HW_INSTRUCTIONS` |
303-
| `cache_references` | `PERF_COUNT_HW_CACHE_REFERENCES` |
304-
| `cache_misses` | `PERF_COUNT_HW_CACHE_MISSES` |
305-
| `branch_instructions` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` |
306-
| `branch_misses` | `PERF_COUNT_HW_BRANCH_MISSES` |
307-
| `page_faults` | `PERF_COUNT_SW_PAGE_FAULTS` |
308-
| `context_switches` | `PERF_COUNT_SW_CONTEXT_SWITCHES` |
309-
| `cpu_migrations` | `PERF_COUNT_SW_CPU_MIGRATIONS` |
300+
| `perf_type_hardware` | `PERF_TYPE_HARDWARE` |
301+
| `perf_type_software` | `PERF_TYPE_SOFTWARE` |
302+
| `perf_type_tracepoint` | `PERF_TYPE_TRACEPOINT` |
303+
| `perf_type_hw_cache` | `PERF_TYPE_HW_CACHE` |
304+
| `perf_type_raw` | `PERF_TYPE_RAW` |
305+
| `perf_type_breakpoint` | `PERF_TYPE_BREAKPOINT` |
306+
307+
**Common `perf_config` constants:**
308+
309+
| Constant | Intended `perf_type` | Linux config |
310+
|---|---|---|
311+
| `cpu_cycles` | `perf_type_hardware` | `PERF_COUNT_HW_CPU_CYCLES` |
312+
| `instructions` | `perf_type_hardware` | `PERF_COUNT_HW_INSTRUCTIONS` |
313+
| `cache_references` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_REFERENCES` |
314+
| `cache_misses` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_MISSES` |
315+
| `branch_instructions` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` |
316+
| `branch_misses` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_MISSES` |
317+
| `page_faults` | `perf_type_software` | `PERF_COUNT_SW_PAGE_FAULTS` |
318+
| `context_switches` | `perf_type_software` | `PERF_COUNT_SW_CONTEXT_SWITCHES` |
319+
| `cpu_migrations` | `perf_type_software` | `PERF_COUNT_SW_CPU_MIGRATIONS` |
320+
321+
For newer families such as `perf_type_hw_cache`, pass the kernel-compatible encoded `perf_config` value directly.
310322

311323
📖 **For detailed language specification, syntax reference, and advanced features, please read [`SPEC.md`](SPEC.md).**
312324

SPEC.md

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -460,20 +460,21 @@ The context type is always `*bpf_perf_event_data` (from `vmlinux.h`).
460460
fn main() -> i32 {
461461
var prog = load(my_handler)
462462
463-
// Only counter is required; all other fields use language-level defaults:
463+
// Only perf_type + perf_config are required; all other fields use language-level defaults:
464464
// pid=-1, cpu=0, period=1_000_000, wakeup=1, inherit/exclude_*=false
465-
attach(prog, perf_options { counter: branch_misses }, 0)
465+
attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0)
466466
467467
// Override specific fields as needed:
468468
attach(prog, perf_options {
469-
counter: cache_misses,
469+
perf_type: perf_type_hardware,
470+
perf_config: cache_misses,
470471
cpu: 2,
471472
period: 500000,
472473
exclude_kernel: true,
473474
}, 0)
474475
475-
var count = perf_read(prog) // read counter value via program handle
476-
print(count)
476+
var count = perf_read(prog)
477+
print("count: %lld", count)
477478
478479
detach(prog) // IOC_DISABLE → bpf_link__destroy → close(perf_fd)
479480
return 0
@@ -484,7 +485,8 @@ fn main() -> i32 {
484485

485486
| Field | Type | Default | Description |
486487
|---|---|---|---|
487-
| `counter` | `perf_counter` | *(required)* | Hardware/software counter |
488+
| `perf_type` | `perf_type` | *(required)* | `perf_event_attr.type` tag |
489+
| `perf_config` | `u64` | *(required)* | `perf_event_attr.config` value for that type |
488490
| `pid` | `i32` | `-1` | -1 = all processes; ≥0 = specific PID |
489491
| `cpu` | `i32` | `0` | ≥0 = specific CPU; -1 = any CPU (pid must be ≥0) |
490492
| `period` | `u64` | `1000000` | Sample after this many events |
@@ -502,19 +504,32 @@ fn main() -> i32 {
502504
| -1 | ≥ 0 | All processes on specific CPU (system-wide) |
503505
| -1 | -1 | **Invalid** — rejected with error |
504506

505-
**`perf_counter` enum:**
507+
**`perf_type` enum:**
506508

507509
| Value | Linux constant |
508510
|---|---|
509-
| `cpu_cycles` | `PERF_COUNT_HW_CPU_CYCLES` |
510-
| `instructions` | `PERF_COUNT_HW_INSTRUCTIONS` |
511-
| `cache_references` | `PERF_COUNT_HW_CACHE_REFERENCES` |
512-
| `cache_misses` | `PERF_COUNT_HW_CACHE_MISSES` |
513-
| `branch_instructions` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` |
514-
| `branch_misses` | `PERF_COUNT_HW_BRANCH_MISSES` |
515-
| `page_faults` | `PERF_COUNT_SW_PAGE_FAULTS` |
516-
| `context_switches` | `PERF_COUNT_SW_CONTEXT_SWITCHES` |
517-
| `cpu_migrations` | `PERF_COUNT_SW_CPU_MIGRATIONS` |
511+
| `perf_type_hardware` | `PERF_TYPE_HARDWARE` |
512+
| `perf_type_software` | `PERF_TYPE_SOFTWARE` |
513+
| `perf_type_tracepoint` | `PERF_TYPE_TRACEPOINT` |
514+
| `perf_type_hw_cache` | `PERF_TYPE_HW_CACHE` |
515+
| `perf_type_raw` | `PERF_TYPE_RAW` |
516+
| `perf_type_breakpoint` | `PERF_TYPE_BREAKPOINT` |
517+
518+
**Common `perf_config` constants:**
519+
520+
| Value | Intended `perf_type` | Linux constant |
521+
|---|---|---|
522+
| `cpu_cycles` | `perf_type_hardware` | `PERF_COUNT_HW_CPU_CYCLES` |
523+
| `instructions` | `perf_type_hardware` | `PERF_COUNT_HW_INSTRUCTIONS` |
524+
| `cache_references` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_REFERENCES` |
525+
| `cache_misses` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_MISSES` |
526+
| `branch_instructions` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` |
527+
| `branch_misses` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_MISSES` |
528+
| `page_faults` | `perf_type_software` | `PERF_COUNT_SW_PAGE_FAULTS` |
529+
| `context_switches` | `perf_type_software` | `PERF_COUNT_SW_CONTEXT_SWITCHES` |
530+
| `cpu_migrations` | `perf_type_software` | `PERF_COUNT_SW_CPU_MIGRATIONS` |
531+
532+
For event families with a richer config space, such as `perf_type_hw_cache`, provide the encoded kernel `perf_config` value directly instead of relying on a flattened enum.
518533

519534
**Generated C helpers (emitted when `attach(prog, perf_options{...}, flags)` is used):**
520535

@@ -524,7 +539,6 @@ fn main() -> i32 {
524539
| `ks_attach_perf_event` | `int (int prog_fd, ks_perf_options, int flags)` | Full open-reset-attach-enable lifecycle |
525540
| `ks_read_perf_count` | `int64_t (int perf_fd)` | Reads current 64-bit counter via `read()` |
526541
| `ks_perf_read` | `int64_t (int prog_fd)` | High-level read via program handle |
527-
| `ks_perf_print` | `void (int prog_fd, const char*)` | Prints `[perf] <name>: <count>` to stdout |
528542

529543
**Attach sequence (compiler-generated, inside `ks_attach_perf_event`):**
530544
1. `ks_attr.attr.disabled = 1` — open counter without starting it

examples/perf_branch_miss.ks

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ fn on_branch_miss(ctx: *bpf_perf_event_data) -> i32 {
1111
fn main() -> i32 {
1212
var prog = load(on_branch_miss)
1313

14-
// Only counter is required; pid, cpu, period, wakeup and flag fields
14+
// Only perf_type + perf_config are required; pid, cpu, period, wakeup and flag fields
1515
// default to: pid=-1 (all procs), cpu=0, period=1_000_000, wakeup=1,
1616
// inherit/exclude_kernel/exclude_user=false.
17-
attach(prog, perf_options { counter: branch_misses }, 0)
18-
19-
perf_print(prog, "branch_misses")
17+
attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0)
18+
print("Branch-miss perf_event demo attached")
2019

2120
detach(prog)
21+
print("Branch-miss perf_event demo detached")
2222
return 0
2323
}

examples/perf_cache_miss.ks

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ fn on_cache_miss(ctx: *bpf_perf_event_data) -> i32 {
1111
fn main() -> i32 {
1212
var prog = load(on_cache_miss)
1313

14-
// Only counter is required; pid, cpu, period, wakeup and flag fields
14+
// Only perf_type + perf_config are required; pid, cpu, period, wakeup and flag fields
1515
// default to: pid=-1 (all procs), cpu=0, period=1_000_000, wakeup=1,
1616
// inherit/exclude_kernel/exclude_user=false.
17-
attach(prog, perf_options { counter: cache_misses,period: 10000000, inherit: true }, 0)
18-
19-
perf_print(prog, "cache_misses")
17+
attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: cache_misses, period: 10000000, inherit: true }, 0)
18+
print("Cache-miss perf_event demo attached")
19+
var count = perf_read(prog)
20+
print("Cache-miss count: %lld", count)
2021

2122
detach(prog)
23+
print("Cache-miss perf_event demo detached")
2224
return 0
2325
}

src/btf_parser.ml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -521,11 +521,8 @@ let generate_kernelscript_source ?extra_param ?include_kfuncs template project_n
521521
fn main() -> i32 {
522522
var prog = load(%s)
523523

524-
// Only counter is required; all other fields default to sensible values.
525-
attach(prog, perf_options { counter: branch_misses }, 0)
526-
527-
var count = perf_read(prog)
528-
print(count)
524+
// perf_type + perf_config are required; all other fields default to sensible values.
525+
attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0)
529526

530527
detach(prog)
531528

src/context/perf_event_codegen.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(*
2-
* Copyright 2025 Multikernel Technologies, Inc.
2+
* Copyright 2026 Siyuan Sun
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

src/stdlib.ml

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -233,18 +233,6 @@ let builtin_functions = [
233233
kernel_impl = "";
234234
validate = None;
235235
};
236-
{
237-
name = "perf_print";
238-
param_types = [ProgramHandle; Str 128];
239-
return_type = Void;
240-
description = "Print the current counter value for a perf_event program with a label";
241-
is_variadic = false;
242-
ebpf_impl = ""; (* Not available in eBPF context *)
243-
userspace_impl = "ks_perf_print";
244-
kernel_impl = "";
245-
validate = None;
246-
};
247-
248236
]
249237

250238
(** Get built-in function definition by name *)
@@ -309,23 +297,38 @@ let builtin_types = [
309297
("TC_ACT_TRAP", Some (Ast.Signed64 8L));
310298
], builtin_pos));
311299

312-
(* perf_counter enum: KernelScript abstraction for hardware/software performance counters *)
313-
TypeDef (EnumDef ("perf_counter", [
300+
(* perf_type mirrors perf_event_attr.type so config stays a tagged 2D space. *)
301+
TypeDef (EnumDef ("perf_type", [
302+
("perf_type_hardware", Some (Ast.Signed64 0L));
303+
("perf_type_software", Some (Ast.Signed64 1L));
304+
("perf_type_tracepoint", Some (Ast.Signed64 2L));
305+
("perf_type_hw_cache", Some (Ast.Signed64 3L));
306+
("perf_type_raw", Some (Ast.Signed64 4L));
307+
("perf_type_breakpoint", Some (Ast.Signed64 5L));
308+
], builtin_pos));
309+
310+
(* Common config values for PERF_TYPE_HARDWARE. *)
311+
TypeDef (EnumDef ("perf_hw_config", [
314312
("cpu_cycles", Some (Ast.Signed64 0L));
315313
("instructions", Some (Ast.Signed64 1L));
316314
("cache_references", Some (Ast.Signed64 2L));
317315
("cache_misses", Some (Ast.Signed64 3L));
318316
("branch_instructions", Some (Ast.Signed64 4L));
319317
("branch_misses", Some (Ast.Signed64 5L));
320-
("page_faults", Some (Ast.Signed64 6L));
321-
("context_switches", Some (Ast.Signed64 7L));
322-
("cpu_migrations", Some (Ast.Signed64 8L));
318+
], builtin_pos));
319+
320+
(* Common config values for PERF_TYPE_SOFTWARE. *)
321+
TypeDef (EnumDef ("perf_sw_config", [
322+
("page_faults", Some (Ast.Signed64 2L));
323+
("context_switches", Some (Ast.Signed64 3L));
324+
("cpu_migrations", Some (Ast.Signed64 4L));
323325
], builtin_pos));
324326

325327
(* perf_options: configuration bag for @perf_event programs.
326-
Only 'counter' is required; all other fields have language-level defaults. *)
328+
Only 'perf_type' and 'perf_config' are required; all other fields have language-level defaults. *)
327329
TypeDef (StructDef ("perf_options", [
328-
("counter", Enum "perf_counter");
330+
("perf_type", Enum "perf_type");
331+
("perf_config", U64);
329332
("pid", I32);
330333
("cpu", I32);
331334
("period", U64);
@@ -338,7 +341,7 @@ let builtin_types = [
338341

339342
(** Default field values for structs that support partial initialisation.
340343
Returns [(field_name, default_literal)] for optional fields only.
341-
Required fields (e.g. counter in perf_options) are absent from the list,
344+
Required fields (e.g. perf_type/perf_config in perf_options) are absent from the list,
342345
so the type checker will still error if they are omitted. *)
343346
let get_struct_field_defaults = function
344347
| "perf_options" ->

0 commit comments

Comments
 (0)