// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) // Copyright (c) 2022 Google #include "vmlinux.h" #include #include #include #include "lock_data.h" /* default buffer size */ #define MAX_ENTRIES 10240 struct tstamp_data { __u64 timestamp; __u64 lock; __u32 flags; __s32 stack_id; }; /* callstack storage */ struct { __uint(type, BPF_MAP_TYPE_STACK_TRACE); __uint(key_size, sizeof(__u32)); __uint(value_size, sizeof(__u64)); __uint(max_entries, MAX_ENTRIES); } stacks SEC(".maps"); /* maintain timestamp at the beginning of contention */ struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, int); __type(value, struct tstamp_data); __uint(max_entries, MAX_ENTRIES); } tstamp SEC(".maps"); /* actual lock contention statistics */ struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, sizeof(struct contention_key)); __uint(value_size, sizeof(struct contention_data)); __uint(max_entries, MAX_ENTRIES); } lock_stat SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, sizeof(__u32)); __uint(value_size, sizeof(struct contention_task_data)); __uint(max_entries, MAX_ENTRIES); } task_data SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, sizeof(__u32)); __uint(value_size, sizeof(__u8)); __uint(max_entries, 1); } cpu_filter SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, sizeof(__u32)); __uint(value_size, sizeof(__u8)); __uint(max_entries, 1); } task_filter SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, sizeof(__u32)); __uint(value_size, sizeof(__u8)); __uint(max_entries, 1); } type_filter SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(key_size, sizeof(__u64)); __uint(value_size, sizeof(__u8)); __uint(max_entries, 1); } addr_filter SEC(".maps"); /* control flags */ int enabled; int has_cpu; int has_task; int has_type; int has_addr; int stack_skip; /* determine the key of lock stat */ int aggr_mode; /* error stat */ int lost; static inline int can_record(u64 *ctx) { if (has_cpu) { __u32 cpu = bpf_get_smp_processor_id(); __u8 *ok; ok = bpf_map_lookup_elem(&cpu_filter, &cpu); if (!ok) return 0; } if (has_task) { __u8 *ok; __u32 pid = bpf_get_current_pid_tgid(); ok = bpf_map_lookup_elem(&task_filter, &pid); if (!ok) return 0; } if (has_type) { __u8 *ok; __u32 flags = (__u32)ctx[1]; ok = bpf_map_lookup_elem(&type_filter, &flags); if (!ok) return 0; } if (has_addr) { __u8 *ok; __u64 addr = ctx[0]; ok = bpf_map_lookup_elem(&addr_filter, &addr); if (!ok) return 0; } return 1; } static inline void update_task_data(__u32 pid) { struct contention_task_data *p; p = bpf_map_lookup_elem(&task_data, &pid); if (p == NULL) { struct contention_task_data data; bpf_get_current_comm(data.comm, sizeof(data.comm)); bpf_map_update_elem(&task_data, &pid, &data, BPF_NOEXIST); } } SEC("tp_btf/contention_begin") int contention_begin(u64 *ctx) { __u32 pid; struct tstamp_data *pelem; if (!enabled || !can_record(ctx)) return 0; pid = bpf_get_current_pid_tgid(); pelem = bpf_map_lookup_elem(&tstamp, &pid); if (pelem && pelem->lock) return 0; if (pelem == NULL) { struct tstamp_data zero = {}; bpf_map_update_elem(&tstamp, &pid, &zero, BPF_ANY); pelem = bpf_map_lookup_elem(&tstamp, &pid); if (pelem == NULL) { lost++; return 0; } } pelem->timestamp = bpf_ktime_get_ns(); pelem->lock = (__u64)ctx[0]; pelem->flags = (__u32)ctx[1]; if (aggr_mode == LOCK_AGGR_CALLER) { pelem->stack_id = bpf_get_stackid(ctx, &stacks, BPF_F_FAST_STACK_CMP | stack_skip); if (pelem->stack_id < 0) lost++; } return 0; } SEC("tp_btf/contention_end") int contention_end(u64 *ctx) { __u32 pid; struct tstamp_data *pelem; struct contention_key key; struct contention_data *data; __u64 duration; if (!enabled) return 0; pid = bpf_get_current_pid_tgid(); pelem = bpf_map_lookup_elem(&tstamp, &pid); if (!pelem || pelem->lock != ctx[0]) return 0; duration = bpf_ktime_get_ns() - pelem->timestamp; switch (aggr_mode) { case LOCK_AGGR_CALLER: key.aggr_key = pelem->stack_id; break; case LOCK_AGGR_TASK: key.aggr_key = pid; update_task_data(pid); break; case LOCK_AGGR_ADDR: key.aggr_key = pelem->lock; break; default: /* should not happen */ return 0; } data = bpf_map_lookup_elem(&lock_stat, &key); if (!data) { struct contention_data first = { .total_time = duration, .max_time = duration, .min_time = duration, .count = 1, .flags = pelem->flags, }; bpf_map_update_elem(&lock_stat, &key, &first, BPF_NOEXIST); bpf_map_delete_elem(&tstamp, &pid); return 0; } __sync_fetch_and_add(&data->total_time, duration); __sync_fetch_and_add(&data->count, 1); /* FIXME: need atomic operations */ if (data->max_time < duration) data->max_time = duration; if (data->min_time > duration) data->min_time = duration; bpf_map_delete_elem(&tstamp, &pid); return 0; } char LICENSE[] SEC("license") = "Dual BSD/GPL";