Skip to content

Commit

Permalink
Add tracers for process exits and memory unmaps
Browse files Browse the repository at this point in the history
This change is important for longer profiling sessions (such as continuous
profiling) to be informed of processes that exit as well of memory
mappings that get unmapped.

The events are only sent for mappings and processes that are already
stored in the BPF maps.

Additional changes
==================

- Move the try_recv for the events from a busy loop to a
waiting time of 250ms, as this otherwise uses too much CPU for no good
reason;
- Change build.rs to rebuild if any header or source file under src/bpf
  is modified;
- Fixes a bug where the unwind information was being updated even if it
  wasn't dirty;

Future work
===========

- Improve the way the BPF headers and code are split;
- Actually mark the memory mappings that get unmapped as so in the
  userspace data structures;
- Use the fact that a memory mapping has been unmapped or a process has
  exited to for example clean up some of the data we store;

Test Plan
=========

Ran lightswitch several times without issues.
  • Loading branch information
javierhonduco committed Apr 19, 2024
1 parent d92afad commit e2328b4
Show file tree
Hide file tree
Showing 12 changed files with 422 additions and 119 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ rstest = "0.18.2"
[build-dependencies]
bindgen = "0.69.4"
libbpf-cargo = "0.22.1"
glob = "0.3.1"

[profile.dev.package]
insta.opt-level = 3
Expand Down
31 changes: 29 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@ extern crate bindgen;
use std::env;
use std::path::PathBuf;

use glob::glob;
use libbpf_cargo::SkeletonBuilder;
use std::path::Path;

const PROFILER_BPF_HEADER: &str = "./src/bpf/profiler.h";
const PROFILER_BPF_SOURCE: &str = "./src/bpf/profiler.bpf.c";
const PROFILER_SKELETON: &str = "./src/bpf/profiler_skel.rs";

const TRACERS_BPF_HEADER: &str = "./src/bpf/tracers.h";
const TRACERS_BPF_SOURCE: &str = "./src/bpf/tracers.bpf.c";
const TRACERS_SKELETON: &str = "./src/bpf/tracers_skel.rs";

fn main() {
// Inform cargo of when to re build
println!("cargo:rerun-if-changed={PROFILER_BPF_HEADER}");
println!("cargo:rerun-if-changed={PROFILER_BPF_SOURCE}");
for path in glob("src/bpf/*[hc]").unwrap().flatten() {
println!("cargo:rerun-if-changed={}", path.display());
}

// Main native profiler.
let bindings = bindgen::Builder::default()
.derive_default(true)
.header(PROFILER_BPF_HEADER)
Expand All @@ -27,10 +34,30 @@ fn main() {
.write_to_file(bindings_out_file)
.expect("Couldn't write bindings!");

// Tracers.
let bindings = bindgen::Builder::default()
.derive_default(true)
.header(TRACERS_BPF_HEADER)
.generate()
.expect("Unable to generate bindings");

let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let bindings_out_file = out_path.join("tracers_bindings.rs");
bindings
.write_to_file(bindings_out_file)
.expect("Couldn't write bindings!");

let skel = Path::new(PROFILER_SKELETON);
SkeletonBuilder::new()
.source(PROFILER_BPF_SOURCE)
.clang_args("-Wextra -Wall")
.build_and_generate(skel)
.expect("run skeleton builder");

let skel = Path::new(TRACERS_SKELETON);
SkeletonBuilder::new()
.source(TRACERS_BPF_SOURCE)
.clang_args("-Wextra -Wall")
.build_and_generate(skel)
.expect("run skeleton builder");
}
9 changes: 5 additions & 4 deletions src/bpf/common.h → src/bpf/constants.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#ifndef __LINUX_PAGE_CONSTANTS_HACK__
#define __LINUX_PAGE_CONSTANTS_HACK__
#ifndef __LIGHTSWITCH_LINUX_PAGE_CONSTANTS__
#define __LIGHTSWITCH_LINUX_PAGE_CONSTANTS__

// Values for x86_64 as of 6.0.18-200.
#define TOP_OF_KERNEL_STACK_PADDING 0
Expand All @@ -10,9 +10,10 @@

#endif

#ifndef __ERROR_CONSTANTS_HACK__
#define __ERROR_CONSTANTS_HACK__
#ifndef __LIGHTSWITCH_ERROR_CONSTANTS__
#define __LIGHTSWITCH_ERROR_CONSTANTS__

#define EFAULT 14
#define EEXIST 17

#endif
2 changes: 2 additions & 0 deletions src/bpf/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub mod profiler_bindings;
pub mod profiler_skel;
pub mod tracers_bindings;
pub mod tracers_skel;
78 changes: 3 additions & 75 deletions src/bpf/profiler.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,17 @@
// Copyright 2022 The Parca Authors
// Copyright 2024 The Lightswitch Authors

#include "common.h"
#include "constants.h"
#include "vmlinux.h"
#include "profiler.h"
#include "shared_maps.h"
#include "shared_helpers.h"

#include <bpf/bpf_core_read.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__type(key, struct exec_mappings_key);
__type(value, mapping_t);
__uint(map_flags, BPF_F_NO_PREALLOC);
__uint(max_entries, MAX_PROCESSES * 200);
} exec_mappings SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 100000);
Expand Down Expand Up @@ -69,48 +63,13 @@ struct {
__type(value, stack_unwind_table_t);
} unwind_tables SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, struct unwinder_stats_t);
} percpu_stats SEC(".maps");


struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_PROCESSES);
__type(key, Event);
__type(value, bool);
} rate_limits SEC(".maps");

/*=========================== HELPER FUNCTIONS ==============================*/

#define DEFINE_COUNTER(__func__name) \
static void bump_unwind_##__func__name() { \
u32 zero = 0; \
struct unwinder_stats_t *unwinder_stats = \
bpf_map_lookup_elem(&percpu_stats, &zero); \
if (unwinder_stats != NULL) { \
unwinder_stats->__func__name++; \
} \
}

DEFINE_COUNTER(total);
DEFINE_COUNTER(success_dwarf);
DEFINE_COUNTER(error_truncated);
DEFINE_COUNTER(error_unsupported_expression);
DEFINE_COUNTER(error_unsupported_frame_pointer_action);
DEFINE_COUNTER(error_unsupported_cfa_register);
DEFINE_COUNTER(error_catchall);
DEFINE_COUNTER(error_should_never_happen);
DEFINE_COUNTER(error_pc_not_covered);
DEFINE_COUNTER(error_mapping_not_found);
DEFINE_COUNTER(error_mapping_does_not_contain_pc);
DEFINE_COUNTER(error_chunk_not_found);
DEFINE_COUNTER(error_binary_search_exausted_iterations);
DEFINE_COUNTER(error_sending_new_process_event);
DEFINE_COUNTER(error_cfa_offset_did_not_fit);

// Binary search the unwind table to find the row index containing the unwind
// information for a given program counter (pc) relative to the object file.
Expand Down Expand Up @@ -146,29 +105,6 @@ static __always_inline u64 find_offset_for_pc(stack_unwind_table_t *table, u64 p
return BINARY_SEARCH_EXHAUSTED_ITERATIONS;
}

static __always_inline mapping_t* find_mapping(int per_process_id, u64 pc) {
struct exec_mappings_key key = {};
key.prefix_len = PREFIX_LEN;
key.pid = __builtin_bswap32((u32) per_process_id);
key.data = __builtin_bswap64(pc);

mapping_t *mapping = bpf_map_lookup_elem(&exec_mappings, &key);

if (mapping == NULL) {
LOG("[error] no mapping found for pc %llx", pc);
bump_unwind_error_mapping_not_found();
return NULL;
}

if (pc < mapping->begin || pc >= mapping->end) {
LOG("[error] pc %llx not contained within begin: %llx end: %llx", pc, mapping->begin, mapping->end);
bump_unwind_error_mapping_does_not_contain_pc();
return NULL;
}

return mapping;
}

// Finds the shard information for a given pid and program counter. Optionally,
// and offset can be passed that will be filled in with the mapping's load
// address.
Expand Down Expand Up @@ -207,14 +143,6 @@ find_chunk(mapping_t *mapping, u64 object_relative_pc) {
return NULL;
}

static __always_inline bool process_is_known(int per_process_id) {
struct exec_mappings_key key = {};
key.prefix_len = PREFIX_LEN;
key.pid = __builtin_bswap32((u32) per_process_id);
key.data = 0;

return bpf_map_lookup_elem(&exec_mappings, &key) != NULL;
}

static __always_inline void event_new_process(struct bpf_perf_event_data *ctx, int per_process_id) {
Event event = {
Expand Down
32 changes: 32 additions & 0 deletions src/bpf/shared_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

static __always_inline mapping_t* find_mapping(int per_process_id, u64 pc) {
struct exec_mappings_key key = {};
key.prefix_len = PREFIX_LEN;
key.pid = __builtin_bswap32((u32) per_process_id);
key.data = __builtin_bswap64(pc);

mapping_t *mapping = bpf_map_lookup_elem(&exec_mappings, &key);

if (mapping == NULL) {
LOG("[error] no mapping found for pc %llx", pc);
bump_unwind_error_mapping_not_found();
return NULL;
}

if (pc < mapping->begin || pc >= mapping->end) {
LOG("[error] pc %llx not contained within begin: %llx end: %llx", pc, mapping->begin, mapping->end);
bump_unwind_error_mapping_does_not_contain_pc();
return NULL;
}

return mapping;
}

static __always_inline bool process_is_known(int per_process_id) {
struct exec_mappings_key key = {};
key.prefix_len = PREFIX_LEN;
key.pid = __builtin_bswap32((u32) per_process_id);
key.data = 0;

return bpf_map_lookup_elem(&exec_mappings, &key) != NULL;
}
48 changes: 48 additions & 0 deletions src/bpf/shared_maps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef __LIGHTSWITCH_SHARED_BPF_MAPS__
#define __LIGHTSWITCH_SHARED_BPF_MAPS__

#include <bpf/bpf_tracing.h>

struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__type(key, struct exec_mappings_key);
__type(value, mapping_t);
__uint(map_flags, BPF_F_NO_PREALLOC);
__uint(max_entries, MAX_PROCESSES * 200);
} exec_mappings SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, struct unwinder_stats_t);
} percpu_stats SEC(".maps");


#define DEFINE_COUNTER(__func__name) \
static void bump_unwind_##__func__name() { \
u32 zero = 0; \
struct unwinder_stats_t *unwinder_stats = \
bpf_map_lookup_elem(&percpu_stats, &zero); \
if (unwinder_stats != NULL) { \
unwinder_stats->__func__name++; \
} \
}

DEFINE_COUNTER(total);
DEFINE_COUNTER(success_dwarf);
DEFINE_COUNTER(error_truncated);
DEFINE_COUNTER(error_unsupported_expression);
DEFINE_COUNTER(error_unsupported_frame_pointer_action);
DEFINE_COUNTER(error_unsupported_cfa_register);
DEFINE_COUNTER(error_catchall);
DEFINE_COUNTER(error_should_never_happen);
DEFINE_COUNTER(error_pc_not_covered);
DEFINE_COUNTER(error_mapping_not_found);
DEFINE_COUNTER(error_mapping_does_not_contain_pc);
DEFINE_COUNTER(error_chunk_not_found);
DEFINE_COUNTER(error_binary_search_exausted_iterations);
DEFINE_COUNTER(error_sending_new_process_event);
DEFINE_COUNTER(error_cfa_offset_did_not_fit);

#endif
Loading

0 comments on commit e2328b4

Please sign in to comment.