Performance Profiling¶
⚡ Learning Objectives
- Profile CPU performance with eBPF
- Monitor memory usage and allocations
- Track I/O performance
- Analyze application bottlenecks
- Build custom profiling tools
Introduction¶
eBPF enables detailed performance profiling with minimal overhead, making it ideal for production performance analysis.
Profiling Advantages
- Low overhead: Typically <1% CPU
- Real-time: Immediate insights
- Comprehensive: Access to all kernel events
- Flexible: Custom profiling logic
CPU Profiling¶
Stack Trace Profiler¶
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
struct {
__uint(type, BPF_MAP_TYPE_STACK_TRACE);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u64) * 127);
} stack_traces SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, u32); // Stack ID
__type(value, u64); // Count
} stack_counts SEC(".maps");
SEC("perf_event")
int profile_cpu(struct bpf_perf_event_data *ctx) {
u32 stack_id = bpf_get_stackid(ctx, &stack_traces,
BPF_F_USER_STACK);
u64 *count, zero = 0, one = 1;
count = bpf_map_lookup_elem(&stack_counts, &stack_id);
if (!count) {
bpf_map_update_elem(&stack_counts, &stack_id, &zero, BPF_NOEXIST);
count = bpf_map_lookup_elem(&stack_counts, &stack_id);
}
if (count) {
__sync_fetch_and_add(count, 1);
}
return 0;
}
char LICENSE[] SEC("license") = "GPL";
Memory Profiling¶
Allocation Tracker¶
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32); // PID
__type(value, u64); // Total allocated
} memory_usage SEC(".maps");
SEC("kprobe/kmalloc")
int trace_kmalloc(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 size = PT_REGS_PARM1(ctx);
u64 *total, zero = 0;
total = bpf_map_lookup_elem(&memory_usage, &pid);
if (!total) {
bpf_map_update_elem(&memory_usage, &pid, &zero, BPF_NOEXIST);
total = bpf_map_lookup_elem(&memory_usage, &pid);
}
if (total) {
__sync_fetch_and_add(total, size);
}
return 0;
}
SEC("kprobe/kfree")
int trace_kfree(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
void *ptr = (void *)PT_REGS_PARM1(ctx);
u64 *total;
// Note: Getting size from kfree requires additional logic
total = bpf_map_lookup_elem(&memory_usage, &pid);
// Deallocation tracking would go here
return 0;
}
I/O Profiling¶
Disk I/O Monitor¶
struct io_event {
u32 pid;
u64 bytes;
u64 latency;
u64 timestamp;
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} io_events SEC(".maps");
SEC("kprobe/blk_start_request")
int trace_io_start(struct pt_regs *ctx) {
struct request *req = (struct request *)PT_REGS_PARM1(ctx);
u64 cookie = (u64)req;
u64 now = bpf_ktime_get_ns();
// Store start time (simplified)
return 0;
}
SEC("kprobe/blk_mq_end_request")
int trace_io_end(struct pt_regs *ctx) {
struct io_event *event;
u64 latency = bpf_ktime_get_ns();
event = bpf_ringbuf_reserve(&io_events, sizeof(*event), 0);
if (event) {
event->pid = bpf_get_current_pid_tgid() >> 32;
event->timestamp = latency;
bpf_ringbuf_submit(event, 0);
}
return 0;
}
Function Latency Profiling¶
Function Entry/Exit Tracker¶
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u64); // Function address
__type(value, u64); // Start time
} function_starts SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u64);
__type(value, struct {
u64 total_time;
u64 count;
});
} function_stats SEC(".maps");
SEC("kprobe/do_sys_openat2")
int trace_entry(struct pt_regs *ctx) {
u64 func_addr = (u64)PT_REGS_IP(ctx);
u64 now = bpf_ktime_get_ns();
bpf_map_update_elem(&function_starts, &func_addr, &now, BPF_ANY);
return 0;
}
SEC("kretprobe/do_sys_openat2")
int trace_exit(struct pt_regs *ctx) {
u64 func_addr = (u64)PT_REGS_IP(ctx);
u64 *start_time;
u64 now = bpf_ktime_get_ns();
start_time = bpf_map_lookup_elem(&function_starts, &func_addr);
if (start_time) {
u64 duration = now - *start_time;
struct {
u64 total_time;
u64 count;
} *stats, zero = {};
stats = bpf_map_lookup_elem(&function_stats, &func_addr);
if (!stats) {
bpf_map_update_elem(&function_stats, &func_addr, &zero, BPF_NOEXIST);
stats = bpf_map_lookup_elem(&function_stats, &func_addr);
}
if (stats) {
__sync_fetch_and_add(&stats->total_time, duration);
__sync_fetch_and_add(&stats->count, 1);
}
}
return 0;
}
Best Practices¶
Profiling Best Practices
- Minimize overhead: Use sampling for high-frequency events
- Aggregate in kernel: Reduce user space processing
- Use appropriate maps: Per-CPU maps for counters
- Filter early: Only profile what you need
- Handle errors: Check all map operations
Next Steps: - Learn XDP programming (Chapter 11) - Understand BPF CO-RE (Chapter 12) - Master advanced techniques (Chapter 13)
Previous: Security & Enforcement
Next: XDP Programming