Skip to content

Commit 0f41619

Browse files
committed
Optimize perf attachment reads
Read perf counters directly from PerfAttachment.perf_fd instead of walking the global attachment list on every read. Have perf attach return the inserted attachment id/generation directly from add_attachment(), removing the post-insert scan as well. Add an internal generation token and fd-indexed state guard so copied handles used after detach are rejected without reintroducing the global list walk. Detach invalidates the token before closing the perf fd, and in-flight reads are guarded before close.
1 parent 5bcf0cb commit 0f41619

5 files changed

Lines changed: 192 additions & 53 deletions

File tree

BUILTINS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ fn main() -> i32 {
103103

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

108108
**Examples:**
109109
```kernelscript
@@ -170,7 +170,8 @@ detach(prog) // Clean up
170170

171171
**Return Value:**
172172
- Returns the raw 64-bit counter value on success
173-
- Returns `-1` on error
173+
- Returns `-1` on invalid/stale attachment or read failure
174+
- Reads use the attachment's `perf_fd` directly; the internal token detects copied handles used after detach.
174175

175176
---
176177

SPEC.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ For event families with a richer config space, such as `perf_type_hw_cache`, pro
539539
| `ks_open_perf_event` | `int (ks_perf_options)` | Calls `perf_event_open(2)`, returns fd |
540540
| `ks_attach_perf_event` | `PerfAttachment (int prog_fd, ks_perf_options, int flags)` | Full open-reset-attach-enable lifecycle |
541541
| `ks_read_perf_count` | `int64_t (int perf_fd)` | Reads current 64-bit counter via `read()` |
542-
| `ks_perf_attachment_read` | `int64_t (PerfAttachment)` | High-level read via attachment value |
542+
| `ks_perf_attachment_read` | `int64_t (PerfAttachment)` | Direct fd read through the attachment value with stale-handle detection |
543543

544544
**Attach sequence (compiler-generated, inside `ks_attach_perf_event`):**
545545
1. `ks_attr.attr.disabled = 1` — open counter without starting it
@@ -556,6 +556,7 @@ For event families with a richer config space, such as `perf_type_hw_cache`, pro
556556
**Compiler implementation:**
557557
- Detects `attach(prog, perf_options_value, flags)` (three-argument form with `perf_options` second arg) and routes to `ks_attach_perf_event`
558558
- Returns a first-class `PerfAttachment` value for perf attaches so one program can hold multiple live counters
559+
- `PerfAttachment` carries `perf_fd` plus an internal generation token; `read(attachment)` avoids global attachment-list scans and rejects copied handles after detach
559560
- Exposes omitted `perf_options` fields as language-level defaults (partial struct literal)
560561
- Validates `pid ≥ -1`, `cpu ≥ -1`, and rejects `pid == -1 && cpu == -1` at runtime
561562
- Emits `PERF_FLAG_FD_CLOEXEC` for safe fd inheritance

src/stdlib.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ let builtin_types = [
361361
("perf_fd", I32);
362362
("link_id", I32);
363363
("prog_fd", I32);
364+
("generation", U64);
364365
], builtin_pos));
365366
]
366367

src/userspace_codegen.ml

Lines changed: 165 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3830,7 +3830,9 @@ let generate_complete_userspace_program_from_ir ?(config_declarations = []) ?(ta
38303830
#include <getopt.h>
38313831
#include <fcntl.h>
38323832
#include <net/if.h>
3833+
#include <sched.h>
38333834
#include <setjmp.h>
3835+
#include <stdatomic.h>
38343836
#include <linux/bpf.h>
38353837
#include <sys/resource.h>
38363838
#include <pthread.h>
@@ -4109,6 +4111,7 @@ typedef struct PerfAttachment {
41094111
int perf_fd;
41104112
int link_id;
41114113
int prog_fd;
4114+
uint64_t generation;
41124115
} PerfAttachment;
41134116

41144117
struct attachment_entry {
@@ -4120,19 +4123,137 @@ typedef struct PerfAttachment {
41204123
int ifindex; // For XDP programs (0 for kprobe/tracepoint)
41214124
int perf_fd; // For perf_event programs (-1 otherwise)
41224125
int detaching; // Non-zero while teardown is in progress
4126+
uint64_t generation; // PerfAttachment stale-handle token
41234127
enum bpf_prog_type type;
41244128
struct attachment_entry *next;
41254129
};
41264130

4131+
struct perf_attachment_state {
4132+
_Atomic uint64_t generation;
4133+
_Atomic int perf_fd;
4134+
_Atomic unsigned int readers;
4135+
};
4136+
41274137
static struct attachment_entry *attached_programs = NULL;
4138+
static _Atomic(struct perf_attachment_state *) perf_attachment_states = NULL;
4139+
static _Atomic size_t perf_attachment_state_capacity = 0;
41284140
static pthread_mutex_t attachment_mutex = PTHREAD_MUTEX_INITIALIZER;
41294141
static int next_attachment_id = 1;
4142+
static uint64_t next_perf_attachment_generation = 1;
4143+
4144+
static int ensure_perf_attachment_state_capacity_locked(int perf_fd) {
4145+
if (perf_fd < 0) {
4146+
return -1;
4147+
}
4148+
4149+
size_t capacity = atomic_load_explicit(&perf_attachment_state_capacity, memory_order_acquire);
4150+
if ((size_t)perf_fd < capacity) {
4151+
return 0;
4152+
}
4153+
4154+
if (capacity > 0) {
4155+
fprintf(stderr, "perf fd %d exceeds perf attachment state table capacity %zu\n",
4156+
perf_fd, capacity);
4157+
return -1;
4158+
}
4159+
4160+
struct rlimit limit;
4161+
capacity = 1024;
4162+
if (getrlimit(RLIMIT_NOFILE, &limit) == 0 &&
4163+
limit.rlim_cur != RLIM_INFINITY &&
4164+
limit.rlim_cur > 0) {
4165+
capacity = (size_t)limit.rlim_cur;
4166+
} else {
4167+
long open_max = sysconf(_SC_OPEN_MAX);
4168+
if (open_max > 0) {
4169+
capacity = (size_t)open_max;
4170+
}
4171+
}
4172+
if ((size_t)perf_fd >= capacity) {
4173+
capacity = (size_t)perf_fd + 1;
4174+
}
4175+
4176+
struct perf_attachment_state *states =
4177+
malloc(capacity * sizeof(struct perf_attachment_state));
4178+
if (!states) {
4179+
fprintf(stderr, "Failed to allocate perf attachment state table\n");
4180+
return -1;
4181+
}
4182+
4183+
for (size_t i = 0; i < capacity; i++) {
4184+
atomic_init(&states[i].generation, 0);
4185+
atomic_init(&states[i].perf_fd, -1);
4186+
atomic_init(&states[i].readers, 0);
4187+
}
4188+
4189+
atomic_store_explicit(&perf_attachment_states, states, memory_order_release);
4190+
atomic_store_explicit(&perf_attachment_state_capacity, capacity, memory_order_release);
4191+
return 0;
4192+
}
4193+
4194+
static void invalidate_perf_attachment_state_locked(struct attachment_entry *entry) {
4195+
if (!entry ||
4196+
entry->type != BPF_PROG_TYPE_PERF_EVENT ||
4197+
entry->perf_fd < 0 ||
4198+
entry->generation == 0) {
4199+
return;
4200+
}
4201+
4202+
size_t capacity = atomic_load_explicit(&perf_attachment_state_capacity, memory_order_acquire);
4203+
struct perf_attachment_state *states =
4204+
atomic_load_explicit(&perf_attachment_states, memory_order_acquire);
4205+
if ((size_t)entry->perf_fd < capacity && states) {
4206+
struct perf_attachment_state *state = &states[entry->perf_fd];
4207+
atomic_store_explicit(&state->perf_fd, -1, memory_order_release);
4208+
atomic_store_explicit(&state->generation, 0, memory_order_release);
4209+
while (atomic_load_explicit(&state->readers, memory_order_acquire) != 0) {
4210+
sched_yield();
4211+
}
4212+
}
4213+
entry->generation = 0;
4214+
}
4215+
4216+
static struct perf_attachment_state *perf_attachment_begin_read(PerfAttachment attachment) {
4217+
if (attachment.perf_fd < 0 || attachment.link_id <= 0 || attachment.generation == 0) {
4218+
return NULL;
4219+
}
4220+
4221+
size_t capacity = atomic_load_explicit(&perf_attachment_state_capacity, memory_order_acquire);
4222+
struct perf_attachment_state *states =
4223+
atomic_load_explicit(&perf_attachment_states, memory_order_acquire);
4224+
if (!states || (size_t)attachment.perf_fd >= capacity) {
4225+
return NULL;
4226+
}
4227+
4228+
struct perf_attachment_state *state = &states[attachment.perf_fd];
4229+
uint64_t generation =
4230+
atomic_load_explicit(&state->generation, memory_order_acquire);
4231+
int perf_fd =
4232+
atomic_load_explicit(&state->perf_fd, memory_order_acquire);
4233+
if (generation != attachment.generation || perf_fd != attachment.perf_fd) {
4234+
return NULL;
4235+
}
4236+
4237+
atomic_fetch_add_explicit(&state->readers, 1, memory_order_acquire);
4238+
generation = atomic_load_explicit(&state->generation, memory_order_acquire);
4239+
perf_fd = atomic_load_explicit(&state->perf_fd, memory_order_acquire);
4240+
if (generation != attachment.generation || perf_fd != attachment.perf_fd) {
4241+
atomic_fetch_sub_explicit(&state->readers, 1, memory_order_release);
4242+
return NULL;
4243+
}
4244+
return state;
4245+
}
4246+
4247+
static void perf_attachment_end_read(struct perf_attachment_state *state) {
4248+
atomic_fetch_sub_explicit(&state->readers, 1, memory_order_release);
4249+
}
41304250

41314251
// Helper function to add attachment entry.
41324252
// Duplicate check is performed atomically under the same lock as insertion.
41334253
static int add_attachment(int prog_fd, const char *target, uint32_t flags,
41344254
struct bpf_link *link, int ifindex, int perf_fd,
4135-
enum bpf_prog_type type) {
4255+
enum bpf_prog_type type, int *attachment_id_out,
4256+
uint64_t *generation_out) {
41364257
struct attachment_entry *entry = malloc(sizeof(struct attachment_entry));
41374258
if (!entry) {
41384259
fprintf(stderr, "Failed to allocate memory for attachment entry\n");
@@ -4150,6 +4271,7 @@ typedef struct PerfAttachment {
41504271
entry->type = type;
41514272

41524273
entry->detaching = 0;
4274+
entry->generation = 0;
41534275
pthread_mutex_lock(&attachment_mutex);
41544276
/* Reject duplicate insertions atomically.
41554277
* Skip entries that are currently being torn down (detaching != 0) so that
@@ -4167,8 +4289,29 @@ typedef struct PerfAttachment {
41674289
existing = existing->next;
41684290
}
41694291
entry->attachment_id = next_attachment_id++;
4292+
if (type == BPF_PROG_TYPE_PERF_EVENT && perf_fd >= 0) {
4293+
if (ensure_perf_attachment_state_capacity_locked(perf_fd) != 0) {
4294+
pthread_mutex_unlock(&attachment_mutex);
4295+
free(entry);
4296+
return -1;
4297+
}
4298+
entry->generation = next_perf_attachment_generation++;
4299+
if (next_perf_attachment_generation == 0) {
4300+
next_perf_attachment_generation = 1;
4301+
}
4302+
struct perf_attachment_state *states =
4303+
atomic_load_explicit(&perf_attachment_states, memory_order_acquire);
4304+
atomic_store_explicit(&states[perf_fd].perf_fd, perf_fd, memory_order_release);
4305+
atomic_store_explicit(&states[perf_fd].generation, entry->generation, memory_order_release);
4306+
}
41704307
entry->next = attached_programs;
41714308
attached_programs = entry;
4309+
if (attachment_id_out) {
4310+
*attachment_id_out = entry->attachment_id;
4311+
}
4312+
if (generation_out) {
4313+
*generation_out = entry->generation;
4314+
}
41724315
pthread_mutex_unlock(&attachment_mutex);
41734316

41744317
return 0;
@@ -4232,7 +4375,7 @@ typedef struct PerfAttachment {
42324375
}
42334376

42344377
// Store XDP attachment (no bpf_link for XDP)
4235-
if (add_attachment(prog_fd, target, flags, NULL, ifindex, -1, BPF_PROG_TYPE_XDP) != 0) {
4378+
if (add_attachment(prog_fd, target, flags, NULL, ifindex, -1, BPF_PROG_TYPE_XDP, NULL, NULL) != 0) {
42364379
// If storage fails, detach and return error
42374380
bpf_xdp_detach(ifindex, flags, NULL);
42384381
return -1;
@@ -4262,7 +4405,7 @@ typedef struct PerfAttachment {
42624405
printf("Kprobe attached to function: %s\n", target);
42634406

42644407
// Store probe attachment for later cleanup
4265-
if (add_attachment(prog_fd, target, flags, link, 0, -1, BPF_PROG_TYPE_KPROBE) != 0) {
4408+
if (add_attachment(prog_fd, target, flags, link, 0, -1, BPF_PROG_TYPE_KPROBE, NULL, NULL) != 0) {
42664409
// If storage fails, destroy link and return error
42674410
bpf_link__destroy(link);
42684411
return -1;
@@ -4291,7 +4434,7 @@ typedef struct PerfAttachment {
42914434
printf("Fentry/fexit program attached to function: %s\n", target);
42924435

42934436
// Store tracing attachment for later cleanup
4294-
if (add_attachment(prog_fd, target, flags, link, 0, -1, BPF_PROG_TYPE_TRACING) != 0) {
4437+
if (add_attachment(prog_fd, target, flags, link, 0, -1, BPF_PROG_TYPE_TRACING, NULL, NULL) != 0) {
42954438
// If storage fails, destroy link and return error
42964439
bpf_link__destroy(link);
42974440
return -1;
@@ -4335,7 +4478,7 @@ typedef struct PerfAttachment {
43354478
}
43364479

43374480
// Store tracepoint attachment for later cleanup
4338-
if (add_attachment(prog_fd, target, flags, link, 0, -1, BPF_PROG_TYPE_TRACEPOINT) != 0) {
4481+
if (add_attachment(prog_fd, target, flags, link, 0, -1, BPF_PROG_TYPE_TRACEPOINT, NULL, NULL) != 0) {
43394482
// If storage fails, destroy link and return error
43404483
bpf_link__destroy(link);
43414484
return -1;
@@ -4372,7 +4515,7 @@ typedef struct PerfAttachment {
43724515
}
43734516

43744517
// Store TC attachment for later cleanup (flags no longer needed for direction)
4375-
if (add_attachment(prog_fd, target, 0, link, ifindex, -1, BPF_PROG_TYPE_SCHED_CLS) != 0) {
4518+
if (add_attachment(prog_fd, target, 0, link, ifindex, -1, BPF_PROG_TYPE_SCHED_CLS, NULL, NULL) != 0) {
43764519
// If storage fails, destroy link and return error
43774520
bpf_link__destroy(link);
43784521
return -1;
@@ -4480,6 +4623,7 @@ void detach_bpf_program_by_fd(int prog_fd) {
44804623
while (entry) {
44814624
if (entry->prog_fd == prog_fd && !entry->detaching) {
44824625
entry->detaching = 1;
4626+
invalidate_perf_attachment_state_locked(entry);
44834627
break;
44844628
}
44854629
entry = entry->next;
@@ -4517,6 +4661,7 @@ void ks_detach_perf_attachment(PerfAttachment attachment) {
45174661
struct attachment_entry *entry = find_attachment_by_id_locked(attachment.link_id);
45184662
if (entry && !entry->detaching) {
45194663
entry->detaching = 1;
4664+
invalidate_perf_attachment_state_locked(entry);
45204665
} else {
45214666
entry = NULL;
45224667
}
@@ -4753,36 +4898,28 @@ PerfAttachment ks_attach_perf_event(int prog_fd, ks_perf_options opts, int flags
47534898
(unsigned long long)opts.perf_config,
47544899
(unsigned long long)opts.period);
47554900
4756-
if (add_attachment(prog_fd, perf_target, (uint32_t)flags, link, 0, perf_fd, BPF_PROG_TYPE_PERF_EVENT) != 0) {
4901+
int attachment_id = -1;
4902+
uint64_t generation = 0;
4903+
if (add_attachment(prog_fd, perf_target, (uint32_t)flags, link, 0, perf_fd,
4904+
BPF_PROG_TYPE_PERF_EVENT, &attachment_id, &generation) != 0) {
47574905
ioctl(perf_fd, PERF_EVENT_IOC_DISABLE, 0);
47584906
bpf_link__destroy(link);
47594907
close(perf_fd);
47604908
return attachment;
47614909
}
47624910
4763-
pthread_mutex_lock(&attachment_mutex);
4764-
struct attachment_entry *entry = attached_programs;
4765-
while (entry) {
4766-
if (entry->prog_fd == prog_fd &&
4767-
entry->perf_fd == perf_fd &&
4768-
entry->type == BPF_PROG_TYPE_PERF_EVENT &&
4769-
!entry->detaching) {
4770-
attachment.perf_fd = perf_fd;
4771-
attachment.link_id = entry->attachment_id;
4772-
break;
4773-
}
4774-
entry = entry->next;
4775-
}
4776-
pthread_mutex_unlock(&attachment_mutex);
4777-
4778-
if (attachment.link_id <= 0) {
4911+
if (attachment_id <= 0 || generation == 0) {
47794912
fprintf(stderr, "Failed to record perf_event attachment for program fd %d\n", prog_fd);
47804913
ioctl(perf_fd, PERF_EVENT_IOC_DISABLE, 0);
47814914
bpf_link__destroy(link);
47824915
close(perf_fd);
47834916
return attachment;
47844917
}
47854918
4919+
attachment.perf_fd = perf_fd;
4920+
attachment.link_id = attachment_id;
4921+
attachment.generation = generation;
4922+
47864923
printf("Perf event program attached: id=%d prog_fd=%d perf_fd=%d target=%s\n",
47874924
attachment.link_id, attachment.prog_fd, attachment.perf_fd, perf_target);
47884925
return attachment;
@@ -4815,28 +4952,13 @@ int64_t ks_read_perf_count(int perf_fd) {
48154952
48164953
/* Read the counter for a first-class perf attachment value. */
48174954
int64_t ks_perf_attachment_read(PerfAttachment attachment) {
4818-
pthread_mutex_lock(&attachment_mutex);
4819-
int found = 0;
4820-
int dup_fd = -1;
4821-
struct attachment_entry *cur = find_attachment_by_id_locked(attachment.link_id);
4822-
if (cur &&
4823-
!cur->detaching &&
4824-
cur->perf_fd >= 0 &&
4825-
cur->type == BPF_PROG_TYPE_PERF_EVENT) {
4826-
found = 1;
4827-
dup_fd = dup(cur->perf_fd);
4828-
}
4829-
pthread_mutex_unlock(&attachment_mutex);
4830-
if (!found) {
4831-
fprintf(stderr, "ks_perf_attachment_read: no active perf attachment for link id %d\n", attachment.link_id);
4832-
return -1;
4833-
}
4834-
if (dup_fd < 0) {
4835-
fprintf(stderr, "ks_perf_attachment_read: dup(perf_fd) failed for link id %d: %s\n", attachment.link_id, strerror(errno));
4955+
struct perf_attachment_state *state = perf_attachment_begin_read(attachment);
4956+
if (!state) {
4957+
fprintf(stderr, "ks_perf_attachment_read: invalid or stale perf attachment\n");
48364958
return -1;
48374959
}
4838-
int64_t result = ks_read_perf_count(dup_fd);
4839-
close(dup_fd);
4960+
int64_t result = ks_read_perf_count(attachment.perf_fd);
4961+
perf_attachment_end_read(state);
48404962
return result;
48414963
}
48424964
|}

0 commit comments

Comments
 (0)