Skip to content

Shared Sync Timer Integration

The bpf-sync-timer crate hosts the reusable eBPF timer program and helper utilities. Downstream crates can depend on it to consume a shared timer tick without owning their own timer lifecycle.

Adding the dependency

[dependencies]
bpf-sync-timer = { workspace = true }

Starting the timer and requesting an ID

use bpf_sync_timer::SyncTimer;

const INTERVAL_NS: u64 = 1_000_000; // 1 ms

let mut sync_timer = SyncTimer::start(INTERVAL_NS)?;
let subscriber_id = sync_timer.assign_id()?; // returns 0..=63

The SyncTimer handle keeps the timer program alive, so hold on to it for the lifetime of your collector.

Reusing the timer bitmap in another skeleton

SyncTimer exposes the bitmap map file descriptor so that other skeletons can reuse it before they are loaded. You can pass a borrowed FD — libbpf will duplicate it internally on reuse_fd.

let mut open = skel_builder.open(obj_buf)?;
let borrowed = sync_timer.borrowed_map_fd();
open.maps.sync_timer_bitmap.reuse_fd(borrowed)?;
// Set as const before load so it's baked into the program
open.maps.rodata_data.my_timer_subscriber_id = subscriber_id as u64;
let mut skel = open.load()?;

The bitmap map is not pinned anywhere; all programs hold references directly to the shared map.

Updating the BPF program

Include the shared bitmap helper header and declare the bitmap map:

#include "sync_timer_bitmap.bpf.h"

struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, struct sync_timer_bitmap_entry);
} sync_timer_bitmap SEC(".maps");

const volatile __u64 my_sync_timer_id;

Inside the tracepoint/timer/hrtimer_expire_exit handler call the helper to check whether the current subscriber bit is set and clear it:

SEC("tracepoint/timer/hrtimer_expire_exit")
int handle_hrtimer_expire_exit(void *ctx)
{
    if (my_sync_timer_id >= 64)
        return 0;

    if (!sync_timer_check_and_reset(&sync_timer_bitmap, my_sync_timer_id))
        return 0;

    struct sync_timer_bitmap_entry *entry;
    __u32 key = 0;
    entry = bpf_map_lookup_elem(&sync_timer_bitmap, &key);
    if (!entry)
        return 0;

    __u32 cpu = bpf_get_smp_processor_id();
    if (entry->expected_cpu != cpu) {
        // emit migration event
        return 0;
    }

    // normal timer handling here
    return 0;
}

Compare the expected CPU from the bitmap entry with bpf_get_smp_processor_id() inside your tracepoint to detect timer migration.

Legacy header

The legacy sync_timer.bpf.h callback API continues to live in the bpf-sync-timer/include directory for use cases that want to manage their own map updates instead of using the shared bitmap helper.