Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions BUILTINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fn main() -> i32 {

#### `attach(handle, target, flags)` / `attach(handle, opts, flags)`
**Signature:** `attach(handle: ProgramHandle, target: str(128), flags: u32) -> u32`
**Signature:** `attach(handle: ProgramHandle, opts: perf_options, flags: u32) -> PerfAttachment`
**Signature:** `attach(handle: ProgramHandle, opts: perf_options, flags: u32) -> perf_attachment`
**Variadic:** No
**Context:** Userspace only

Expand All @@ -98,12 +98,12 @@ fn main() -> i32 {
- `flags`: Attachment flags (context-dependent)
- Perf event form:
- `handle`: Program handle returned from `load()`
- `opts`: `perf_options` value — only `perf_type` and `perf_config` are required; all other fields have defaults, including no group (`group` invalid and `group_fd=-1`)
- `opts`: `perf_options` value — only `event` is required; all other fields have defaults, including no group (`group` defaults to an invalid attachment)
- `flags`: Must be `0` for perf attaches; nonzero values are rejected

**Return Value:**
- Standard form returns `0` on success and an error code on failure
- Perf event form returns a `PerfAttachment` value with the open counter/link identity and an internal stale-handle token
- Perf event form returns a `perf_attachment` value with the open counter/link identity and an internal stale-handle token

**Examples:**
```kernelscript
Expand All @@ -113,26 +113,25 @@ if (result != 0) {
print("Failed to attach program")
}

// Minimal perf attach — all non-perf_type/perf_config fields use defaults:
// Minimal perf attach — all non-event fields use defaults:
// pid=-1 (all procs), cpu=0, period=1_000_000, wakeup=1; perf attach flags must be 0
var perf_prog = load(on_branch_miss)
var perf_att = attach(perf_prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0)
var perf_att = attach(perf_prog, perf_options { event: branch_misses }, 0)
var count = read(perf_att).scaled
detach(perf_att)
detach(perf_prog)

// Grouped perf events: branch joins cache's leader group. Adding a member restarts the group.
var cache = attach(perf_prog, perf_options { perf_type: perf_type_hardware, perf_config: cache_misses }, 0)
var cache = attach(perf_prog, perf_options { event: cache_misses }, 0)
var branch = attach(perf_prog, perf_options {
perf_type: perf_type_hardware,
perf_config: branch_misses,
event: branch_misses,
group: cache,
}, 0)
detach(branch)
detach(cache)
```

Grouped events are scheduled as one atomic PMU unit. Separate events and separate groups may be multiplexed, but members inside one group cannot be independently multiplexed. Static groups that exceed the target PMU counter limit are rejected at compile time; override the detected/default limit with `KERNELSCRIPT_PERF_GROUP_MAX_EVENTS` when compiling for a different target. The effective limit is capped at 16 to match `PerfRead`.
Grouped events are scheduled as one atomic PMU unit. Separate events and separate groups may be multiplexed, but members inside one group cannot be independently multiplexed. A group that needs more hardware PMU counters than the running host provides is rejected by the kernel at `perf_event_open(2)`, and `attach()` reports the error at runtime. `read(att)` exposes up to 16 group entries (`perf_read`).

**Context-specific implementations:**
- **eBPF:** Not available
Expand All @@ -143,14 +142,14 @@ Grouped events are scheduled as one atomic PMU unit. Separate events and separat

#### `detach(handle)`
**Signature:** `detach(handle: ProgramHandle) -> void`
**Signature:** `detach(handle: PerfAttachment) -> void`
**Signature:** `detach(handle: perf_attachment) -> void`
**Variadic:** No
**Context:** Userspace only

**Description:** Detach a loaded eBPF program from its current attachment point, or tear down one perf attachment.

**Parameters:**
- `handle`: Program handle returned from `load()`, or a `PerfAttachment` returned from perf `attach()`
- `handle`: Program handle returned from `load()`, or a `perf_attachment` returned from perf `attach()`

**Return Value:**
- No return value (void)
Expand All @@ -171,7 +170,7 @@ detach(prog) // Clean up
---

#### `read(handle)`
**Signature:** `read(handle: PerfAttachment) -> PerfRead`
**Signature:** `read(handle: perf_attachment) -> perf_read`
**Variadic:** No
**Context:** Userspace only

Expand Down
51 changes: 19 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ fn main() -> i32 {

### Hardware Performance Counter Programs

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. Perf attaches return a first-class attachment value, so if you need the current count in userspace, call `read(att).scaled`:
Use `@perf_event` to attach eBPF programs to hardware or software performance counters. Each event is named by a single `event` value that carries both the kernel `perf_event_attr.type` tag and its config, so the type can never be mismatched with the config. Only `event` is required; all other fields have sensible defaults. Perf attaches return a first-class attachment value, so if you need the current count in userspace, call `read(att).scaled`:

```kernelscript
// eBPF program fires on every hardware branch-miss sample
Expand All @@ -308,7 +308,7 @@ fn main() -> i32 {

// Minimal form — defaults: pid=-1 (all procs), cpu=0, no group,
// period=1_000_000, wakeup=1; perf attach flags must be 0
var att = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: branch_misses }, 0)
var att = attach(prog, perf_options { event: branch_misses }, 0)
var count = read(att).scaled
print("branch misses: %lld", count)

Expand All @@ -318,48 +318,35 @@ fn main() -> i32 {
}
```

Perf events can share a kernel scheduling group by passing the leader attachment directly with `group`.
The lower-level `group_fd: cache.perf_fd` form is still supported for compatibility:
Perf events can share a kernel scheduling group by passing the leader attachment with `group`:

```kernelscript
var cache = attach(prog, perf_options { perf_type: perf_type_hardware, perf_config: cache_misses }, 0)
var cache = attach(prog, perf_options { event: cache_misses }, 0)
var branch = attach(prog, perf_options {
perf_type: perf_type_hardware,
perf_config: branch_misses,
event: branch_misses,
group: cache,
}, 0)
```

Adding a member restarts the whole group from zero. Detaching a leader cascades to any live members. A group competes for PMU counters as one atomic unit: different groups can be multiplexed over time, but members inside one group are not independently multiplexed. For statically visible groups, the compiler rejects groups that need more PMU counter slots than the target limit. The limit is read from known sysfs PMU caps when available, defaults to 4, can be overridden with `KERNELSCRIPT_PERF_GROUP_MAX_EVENTS`, and is capped at 16 to match `PerfRead`.
Adding a member restarts the whole group from zero. Detaching a leader cascades to any live members. A group competes for PMU counters as one atomic unit: different groups can be multiplexed over time, but members inside one group are not independently multiplexed. A group that needs more hardware PMU counters than the running host provides is rejected by the kernel at `perf_event_open(2)`, and `attach()` surfaces the error at runtime — on the actual deployment host, where the real counter count is known. `read(att)` exposes up to 16 group entries (`perf_read`).

`read(att)` returns a `PerfRead` snapshot with raw, multiplex-scaled, timing, and group fields. Use `read(att).scaled` for that attachment's counter value, `read(att).raw` for its unscaled value, and `read(att).values` / `read(att).ids` for a same-time group snapshot.
`read(att)` returns a `perf_read` snapshot with raw, multiplex-scaled, timing, and group fields. Use `read(att).scaled` for that attachment's counter value, `read(att).raw` for its unscaled value, and `read(att).values` / `read(att).ids` for a same-time group snapshot.

**Available `perf_type` values:**
**Available `event` constants:**

| Enum value | Hardware/software event |
|---|---|
| `perf_type_hardware` | `PERF_TYPE_HARDWARE` |
| `perf_type_software` | `PERF_TYPE_SOFTWARE` |
| `perf_type_tracepoint` | `PERF_TYPE_TRACEPOINT` |
| `perf_type_hw_cache` | `PERF_TYPE_HW_CACHE` |
| `perf_type_raw` | `PERF_TYPE_RAW` |
| `perf_type_breakpoint` | `PERF_TYPE_BREAKPOINT` |
Each constant packs its `perf_event_attr.type` tag in the high 32 bits and its config in the low 32 bits, so naming the event fixes both.

**Common `perf_config` constants:**

| Constant | Intended `perf_type` | Linux config |
| Constant | Kernel type | Linux config |
|---|---|---|
| `cpu_cycles` | `perf_type_hardware` | `PERF_COUNT_HW_CPU_CYCLES` |
| `instructions` | `perf_type_hardware` | `PERF_COUNT_HW_INSTRUCTIONS` |
| `cache_references` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_REFERENCES` |
| `cache_misses` | `perf_type_hardware` | `PERF_COUNT_HW_CACHE_MISSES` |
| `branch_instructions` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` |
| `branch_misses` | `perf_type_hardware` | `PERF_COUNT_HW_BRANCH_MISSES` |
| `page_faults` | `perf_type_software` | `PERF_COUNT_SW_PAGE_FAULTS` |
| `context_switches` | `perf_type_software` | `PERF_COUNT_SW_CONTEXT_SWITCHES` |
| `cpu_migrations` | `perf_type_software` | `PERF_COUNT_SW_CPU_MIGRATIONS` |

For newer families such as `perf_type_hw_cache`, pass the kernel-compatible encoded `perf_config` value directly.
| `cpu_cycles` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CPU_CYCLES` |
| `instructions` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_INSTRUCTIONS` |
| `cache_references` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CACHE_REFERENCES` |
| `cache_misses` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_CACHE_MISSES` |
| `branch_instructions` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_BRANCH_INSTRUCTIONS` |
| `branch_misses` | `PERF_TYPE_HARDWARE` | `PERF_COUNT_HW_BRANCH_MISSES` |
| `page_faults` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_PAGE_FAULTS` |
| `context_switches` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_CONTEXT_SWITCHES` |
| `cpu_migrations` | `PERF_TYPE_SOFTWARE` | `PERF_COUNT_SW_CPU_MIGRATIONS` |

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

Expand Down
Loading
Loading