From 8b745a6130745bfc3131df41caf478d03de06ce5 Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Wed, 20 May 2026 22:30:16 -0400 Subject: [PATCH 1/9] Add per sample timestamp - down to 1 ms resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add collected_at to AggregatedSample - propagate collected_at in convert.rs and emit pprof label - RawSample::hash — now includes (self.collected_at / 1_000_000) (ms bucket), so identical stacks at different milliseconds become separate aggregated entries - AggregatedSample — gains pub collected_at: u64 field (milliseconds since Unix epoch) - RawAggregatedSample::process() — sets collected_at: self.sample.collected_at / 1_000_000 (ns → ms) - new hash/bucketing tests - tests for same stack in different ms buckets stays separate; same stack within one ms bucket merges - symbolize_profile — propagates collected_at through symbolization - to_pprof — emits a collected_at numeric pprof label (value = ms, unit = milliseconds) on every sample - test: collected_at label round-trips through protobuf encode/decode --- lightswitch-proto/src/profile.rs | 35 ++++++++++++++++ src/aggregator.rs | 39 ++++++++++++++++++ src/profile/convert.rs | 11 +++++- src/profile/sample.rs | 68 +++++++++++++++++++++++++++++++- 4 files changed, 149 insertions(+), 4 deletions(-) diff --git a/lightswitch-proto/src/profile.rs b/lightswitch-proto/src/profile.rs index f9cad94a..ba17ae5f 100644 --- a/lightswitch-proto/src/profile.rs +++ b/lightswitch-proto/src/profile.rs @@ -478,6 +478,41 @@ mod tests { pprof.build(); } + #[test] + fn collected_at_label_round_trips_through_pprof() { + let mut pprof = PprofBuilder::new(SystemTime::now(), Duration::from_secs(1), 19); + let mapping_id = pprof.add_mapping(0x1000, 0x2000, 0, "test.so", "build-id-abc"); + let location_id = pprof.add_location(0x1500, mapping_id, vec![]); + + let ms_timestamp: i64 = 1_748_865_070_123; + let label = + pprof.new_label("collected_at", LabelStringOrNumber::Number(ms_timestamp, "milliseconds".into())); + pprof.add_sample(vec![location_id], 1, &[label]); + + let profile = pprof.build(); + + // Verify in memory + assert_eq!(profile.sample.len(), 1); + let sample = &profile.sample[0]; + assert_eq!(sample.label.len(), 1); + let lbl = &sample.label[0]; + assert_eq!(lbl.num, ms_timestamp); + assert_ne!(lbl.num_unit, 0, "num_unit must be set for numeric labels"); + let key_str = &profile.string_table[lbl.key as usize]; + assert_eq!(key_str, "collected_at"); + let unit_str = &profile.string_table[lbl.num_unit as usize]; + assert_eq!(unit_str, "milliseconds"); + + // Verify after encode/decode round-trip + let mut buf = bytes::BytesMut::new(); + profile.encode(&mut buf).expect("encode failed"); + let decoded = pprof::Profile::decode(buf.freeze()).expect("decode failed"); + let decoded_lbl = &decoded.sample[0].label[0]; + assert_eq!(decoded_lbl.num, ms_timestamp); + let decoded_key = &decoded.string_table[decoded_lbl.key as usize]; + assert_eq!(decoded_key, "collected_at"); + } + #[test] fn test_encode_decode() { let mut pprof = PprofBuilder::new(SystemTime::now(), Duration::from_secs(5), 27); diff --git a/src/aggregator.rs b/src/aggregator.rs index 40c6fa05..7a5d658c 100644 --- a/src/aggregator.rs +++ b/src/aggregator.rs @@ -167,6 +167,45 @@ mod tests { } } + #[test] + fn test_aggregate_same_stack_different_ms_bucket_stays_separate() { + let stack = RawSample { + pid: 1, + tid: 2, + collected_at: 1_000_000_000, // 1000 ms + ustack: vec![0xdead], + kstack: vec![0xbeef], + }; + // 2 ms later = different ms bucket: must NOT be merged + let later = RawSample { + collected_at: 1_002_000_000, + ..stack.clone() + }; + let aggregator = Aggregator::default(); + let result = aggregator.aggregate(vec![stack, later]); + assert_eq!(result.len(), 2, "samples in different ms buckets must stay separate"); + } + + #[test] + fn test_aggregate_same_stack_within_ms_bucket_merges() { + let stack = RawSample { + pid: 1, + tid: 2, + collected_at: 1_000_000_000, // 1000 ms + ustack: vec![0xdead], + kstack: vec![0xbeef], + }; + // 999 µs later = same ms bucket: must be merged (count becomes 2) + let almost_same = RawSample { + collected_at: 1_000_999_000, + ..stack.clone() + }; + let aggregator = Aggregator::default(); + let result = aggregator.aggregate(vec![stack, almost_same]); + assert_eq!(result.len(), 1, "samples in the same ms bucket must merge"); + assert_eq!(result[0].count, 2); + } + #[test] fn test_aggregate_same_stack_traces_different_pid_tid() { let ustack = vec![0xffff, 0xdeadbeef]; diff --git a/src/profile/convert.rs b/src/profile/convert.rs index b4a7c954..953dea65 100644 --- a/src/profile/convert.rs +++ b/src/profile/convert.rs @@ -199,7 +199,7 @@ pub fn to_pprof( // todo: maybe append an error frame for debugging? continue; }; - let labels = task_to_labels.entry(task_key).or_insert_with(|| { + let task_labels = task_to_labels.entry(task_key).or_insert_with(|| { let metadata = metadata_provider.lock().unwrap().get_metadata(task_key); metadata .into_iter() @@ -212,7 +212,13 @@ pub fn to_pprof( }) .collect() }); - pprof.add_sample(location_ids, sample.count as i64, labels); + let timestamp_label = pprof.new_label( + "collected_at", + LabelStringOrNumber::Number(sample.collected_at as i64, "milliseconds".into()), + ); + let mut sample_labels = task_labels.clone(); + sample_labels.push(timestamp_label); + pprof.add_sample(location_ids, sample.count as i64, &sample_labels); } pprof.build() @@ -324,6 +330,7 @@ pub fn symbolize_profile( pid: sample.pid, tid: sample.tid, count: sample.count, + collected_at: sample.collected_at, ustack: symbolize_user_stack( &addresses_per_sample, procs, diff --git a/src/profile/sample.rs b/src/profile/sample.rs index 137e93b2..0e833a06 100644 --- a/src/profile/sample.rs +++ b/src/profile/sample.rs @@ -81,10 +81,10 @@ impl std::hash::Hash for RawSample { fn hash(&self, state: &mut H) { self.pid.hash(state); self.kstack.hash(state); - // The collected_at field is excluded when hashing - // the samples for aggregation. self.tid.hash(state); self.ustack.hash(state); + // Bucket by millisecond so identical stacks at different times are distinct entries. + (self.collected_at / 1_000_000).hash(state); } } @@ -131,6 +131,7 @@ impl RawAggregatedSample { ustack: Vec::new(), kstack: Vec::new(), count: self.count, + collected_at: self.sample.collected_at / 1_000_000, }; let Some(info) = procs.get(&self.sample.pid) else { @@ -213,6 +214,8 @@ pub struct AggregatedSample { pub ustack: Vec, pub kstack: Vec, pub count: u64, + /// Milliseconds since Unix epoch when this sample was collected. + pub collected_at: u64, } impl fmt::Display for AggregatedSample { @@ -329,6 +332,65 @@ mod tests { ); } + fn hash_of(sample: &RawSample) -> u64 { + use std::hash::{Hash, Hasher}; + let mut hasher = std::hash::DefaultHasher::new(); + sample.hash(&mut hasher); + hasher.finish() + } + + #[test] + fn same_stack_different_ms_bucket_hashes_differ() { + let base = RawSample { + pid: 1, + tid: 2, + collected_at: 1_000_000_000, // 1 s in ns + ustack: vec![0xdead], + kstack: vec![], + }; + // 2 ms later = different ms bucket + let later = RawSample { collected_at: 1_002_000_000, ..base.clone() }; + assert_ne!(hash_of(&base), hash_of(&later)); + } + + #[test] + fn same_stack_same_ms_bucket_hashes_equal() { + let a = RawSample { + pid: 1, + tid: 2, + collected_at: 1_000_000_000, // 1 s + ustack: vec![0xdead], + kstack: vec![], + }; + // 999 µs later — same ms bucket + let b = RawSample { collected_at: 1_000_999_000, ..a.clone() }; + assert_eq!(hash_of(&a), hash_of(&b)); + } + + #[test] + fn different_stacks_same_timestamp_hashes_differ() { + let a = RawSample { + pid: 1, + tid: 2, + collected_at: 1_000_000_000, + ustack: vec![0xdead], + kstack: vec![], + }; + let b = RawSample { ustack: vec![0xbeef], ..a.clone() }; + assert_ne!(hash_of(&a), hash_of(&b)); + } + + #[test] + fn collected_at_divides_to_milliseconds() { + // Verify the constant used in process() is correct. + // 1_748_865_070_123_456 ns / 1_000_000 = 1_748_865_070 ms + let ns: u64 = 1_748_865_070_123_456; + assert_eq!(ns / 1_000_000, 1_748_865_070); + // Sub-ms part is discarded + let ns2: u64 = 1_748_865_070_999_999; + assert_eq!(ns2 / 1_000_000, 1_748_865_070); + } + #[test] fn display_raw_aggregated_sample() { // User stack but no kernel stack @@ -393,6 +455,7 @@ mod tests { ustack: ustack_data, kstack: kstack_data.clone(), count: 128, + collected_at: 0, }; insta::assert_yaml_snapshot!(format!("{}", sample), @r#""AggregatedSample { pid: 1234567, tid: 1234568, ustack: \"[ 0: ufunc3, 1: ufunc2, 2: ufunc1]\", kstack: \"[ 0: kfunc2, 1: kfunc1]\", count: 128 }""#); @@ -404,6 +467,7 @@ mod tests { ustack: ustack_data, kstack: kstack_data.clone(), count: 1001, + collected_at: 0, }; insta::assert_yaml_snapshot!(format!("{}", sample), @r#""AggregatedSample { pid: 98765, tid: 98766, ustack: \"[NONE]\", kstack: \"[ 0: kfunc2, 1: kfunc1]\", count: 1001 }""#); } From 7cb17920c0e5c27cbdd3b8ce0aab325a47eea49f Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Fri, 12 Jun 2026 15:07:47 -0400 Subject: [PATCH 2/9] bpf_ktime_get_boot_ns() is not yet available in RHEL 8 kernels - Replace with what is available --- src/bpf/profiler.bpf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bpf/profiler.bpf.c b/src/bpf/profiler.bpf.c index 402e9cc2..fc891ced 100644 --- a/src/bpf/profiler.bpf.c +++ b/src/bpf/profiler.bpf.c @@ -264,7 +264,7 @@ static __always_inline void add_stack(struct bpf_perf_event_data *ctx, unwind_state->sample.pid = per_process_id; unwind_state->sample.tid = per_thread_id; - unwind_state->sample.collected_at = bpf_ktime_get_boot_ns(); + unwind_state->sample.collected_at = bpf_ktime_get_ns(); u32 sample_size = sizeof(sample_t) // Remove the actual stack buffer which was doubled to appease the verifier. From 3ef6c2ae7a6f0d7d764c63d71f2c4214eb2930cc Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Mon, 22 Jun 2026 12:10:00 -0400 Subject: [PATCH 3/9] Gate per sample ts with --enable-ts-per-sample bool flag --- lightswitch-proto/src/profile.rs | 6 ++++-- src/aggregator.rs | 6 +++++- src/cli/args.rs | 5 +++++ src/cli/main.rs | 4 ++++ src/collector.rs | 8 ++++++++ src/profile/convert.rs | 13 ++++++++----- src/profile/sample.rs | 15 ++++++++++++--- src/profiler.rs | 2 ++ 8 files changed, 48 insertions(+), 11 deletions(-) diff --git a/lightswitch-proto/src/profile.rs b/lightswitch-proto/src/profile.rs index ba17ae5f..6d677f8a 100644 --- a/lightswitch-proto/src/profile.rs +++ b/lightswitch-proto/src/profile.rs @@ -485,8 +485,10 @@ mod tests { let location_id = pprof.add_location(0x1500, mapping_id, vec![]); let ms_timestamp: i64 = 1_748_865_070_123; - let label = - pprof.new_label("collected_at", LabelStringOrNumber::Number(ms_timestamp, "milliseconds".into())); + let label = pprof.new_label( + "collected_at", + LabelStringOrNumber::Number(ms_timestamp, "milliseconds".into()), + ); pprof.add_sample(vec![location_id], 1, &[label]); let profile = pprof.build(); diff --git a/src/aggregator.rs b/src/aggregator.rs index 7a5d658c..6bf62bb3 100644 --- a/src/aggregator.rs +++ b/src/aggregator.rs @@ -183,7 +183,11 @@ mod tests { }; let aggregator = Aggregator::default(); let result = aggregator.aggregate(vec![stack, later]); - assert_eq!(result.len(), 2, "samples in different ms buckets must stay separate"); + assert_eq!( + result.len(), + 2, + "samples in different ms buckets must stay separate" + ); } #[test] diff --git a/src/cli/args.rs b/src/cli/args.rs index fc883beb..7c7d1f17 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -185,6 +185,11 @@ pub(crate) struct CliArgs { help = "Read metadata for all threads when a new process is detected. This might be slow in older kernels." )] pub(crate) preload_thread_metadata: bool, + #[arg( + long, + help = "Attach a per-sample collected_at timestamp (ms) label to pprof output" + )] + pub(crate) enable_ts_per_sample: bool, #[arg(long, help = "Launch live flamegraph TUI")] pub(crate) live: bool, #[arg( diff --git a/src/cli/main.rs b/src/cli/main.rs index d42cbbe7..adb6dd06 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -272,6 +272,7 @@ fn main() -> Result<(), Box> { .available_bpf_features .has_non_prealloc_hash_maps_in_tracing, userspace_pid_ns_level: system_info.available_bpf_features.userspace_pid_ns_level, + ts_per_sample: args.enable_ts_per_sample, ..Default::default() }; @@ -350,6 +351,7 @@ fn main() -> Result<(), Box> { ProfilerConfig::default().session_duration, args.sample_freq, metadata_provider.clone(), + args.enable_ts_per_sample, )), ProfileSender::Pyroscope => Box::new(PyroscopeCollector::new( args.symbolizer == Symbolizer::Local, @@ -360,6 +362,7 @@ fn main() -> Result<(), Box> { args.sample_freq, metadata_provider.clone(), args.node_name.clone().unwrap_or_default(), + args.enable_ts_per_sample, )), ProfileSender::Firefox => Box::new(FirefoxProfilerCollector::new( "firefox-profiler.json", @@ -431,6 +434,7 @@ fn main() -> Result<(), Box> { &metadata_provider, profile_duration, args.sample_freq, + args.enable_ts_per_sample, ); pprof_profile.encode(&mut buffer).unwrap(); let profile_name = args.profile_name.unwrap_or_else(|| "profile.pb".into()); diff --git a/src/collector.rs b/src/collector.rs index c0eba217..2dad40fd 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -89,6 +89,7 @@ pub struct StreamingCollector { procs: HashMap, objs: HashMap, metadata_provider: ThreadSafeGlobalMetadataProvider, + ts_per_sample: bool, } impl StreamingCollector { @@ -99,6 +100,7 @@ impl StreamingCollector { profile_duration: Duration, profile_frequency_hz: u64, metadata_provider: ThreadSafeGlobalMetadataProvider, + ts_per_sample: bool, ) -> Self { let client_builder = reqwest::blocking::Client::builder().timeout(Duration::from_secs(30)); let client = client_builder @@ -113,6 +115,7 @@ impl StreamingCollector { profile_duration, profile_frequency_hz, metadata_provider, + ts_per_sample, ..Default::default() } } @@ -140,6 +143,7 @@ impl Collector for StreamingCollector { &self.metadata_provider, self.profile_duration, self.profile_frequency_hz, + self.ts_per_sample, ); let mut request = self @@ -182,6 +186,7 @@ pub struct PyroscopeCollector { objs: HashMap, metadata_provider: ThreadSafeGlobalMetadataProvider, node_name: String, + ts_per_sample: bool, } impl PyroscopeCollector { @@ -195,6 +200,7 @@ impl PyroscopeCollector { profile_frequency_hz: u64, metadata_provider: ThreadSafeGlobalMetadataProvider, node_name: String, + ts_per_sample: bool, ) -> Self { let push_url = format!("{}/push.v1.PusherService/Push", server_url); let client_builder = reqwest::blocking::Client::builder().timeout(Duration::from_secs(30)); @@ -209,6 +215,7 @@ impl PyroscopeCollector { profile_frequency_hz, metadata_provider, node_name, + ts_per_sample, ..Default::default() } } @@ -315,6 +322,7 @@ impl PyroscopeCollector { &self.metadata_provider, self.profile_duration, self.profile_frequency_hz, + self.ts_per_sample, ); let mut buffer = Vec::new(); diff --git a/src/profile/convert.rs b/src/profile/convert.rs index 953dea65..68ec1018 100644 --- a/src/profile/convert.rs +++ b/src/profile/convert.rs @@ -45,6 +45,7 @@ pub fn to_pprof( metadata_provider: &ThreadSafeGlobalMetadataProvider, profile_duration: Duration, profile_frequency_hz: u64, + ts_per_sample: bool, ) -> pprof::Profile { // Not exactly when the profile session really started but works for now. let profile_start = SystemTime::now(); @@ -212,12 +213,14 @@ pub fn to_pprof( }) .collect() }); - let timestamp_label = pprof.new_label( - "collected_at", - LabelStringOrNumber::Number(sample.collected_at as i64, "milliseconds".into()), - ); let mut sample_labels = task_labels.clone(); - sample_labels.push(timestamp_label); + if ts_per_sample { + let timestamp_label = pprof.new_label( + "collected_at", + LabelStringOrNumber::Number(sample.collected_at as i64, "milliseconds".into()), + ); + sample_labels.push(timestamp_label); + } pprof.add_sample(location_ids, sample.count as i64, &sample_labels); } diff --git a/src/profile/sample.rs b/src/profile/sample.rs index 0e833a06..c1302282 100644 --- a/src/profile/sample.rs +++ b/src/profile/sample.rs @@ -349,7 +349,10 @@ mod tests { kstack: vec![], }; // 2 ms later = different ms bucket - let later = RawSample { collected_at: 1_002_000_000, ..base.clone() }; + let later = RawSample { + collected_at: 1_002_000_000, + ..base.clone() + }; assert_ne!(hash_of(&base), hash_of(&later)); } @@ -363,7 +366,10 @@ mod tests { kstack: vec![], }; // 999 µs later — same ms bucket - let b = RawSample { collected_at: 1_000_999_000, ..a.clone() }; + let b = RawSample { + collected_at: 1_000_999_000, + ..a.clone() + }; assert_eq!(hash_of(&a), hash_of(&b)); } @@ -376,7 +382,10 @@ mod tests { ustack: vec![0xdead], kstack: vec![], }; - let b = RawSample { ustack: vec![0xbeef], ..a.clone() }; + let b = RawSample { + ustack: vec![0xbeef], + ..a.clone() + }; assert_ne!(hash_of(&a), hash_of(&b)); } diff --git a/src/profiler.rs b/src/profiler.rs index d9aec7dd..0f594d6b 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -147,6 +147,7 @@ pub struct ProfilerConfig { pub no_prealloc_bpf_hash_maps: bool, pub preload_thread_metadata: bool, pub userspace_pid_ns_level: u32, + pub ts_per_sample: bool, } impl Default for ProfilerConfig { @@ -170,6 +171,7 @@ impl Default for ProfilerConfig { no_prealloc_bpf_hash_maps: false, preload_thread_metadata: false, userspace_pid_ns_level: 0, // Assumes running in the root pid namespace by default + ts_per_sample: false, } } } From a686630a2519c3808c3248e256dc6fab0400c3a5 Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Tue, 30 Jun 2026 12:56:35 -0400 Subject: [PATCH 4/9] Prefer bpf_ktime_get_boot_ns whenever available --- lightswitch-capabilities/src/bpf/features.bpf.c | 4 ++++ lightswitch-capabilities/src/system_info.rs | 2 ++ src/bpf/profiler.bpf.c | 4 +++- src/bpf/profiler.h | 1 + src/bpf_objects.rs | 4 ++++ src/cli/main.rs | 1 + src/profiler.rs | 2 ++ 7 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lightswitch-capabilities/src/bpf/features.bpf.c b/lightswitch-capabilities/src/bpf/features.bpf.c index 3ab81e06..b405b792 100644 --- a/lightswitch-capabilities/src/bpf/features.bpf.c +++ b/lightswitch-capabilities/src/bpf/features.bpf.c @@ -14,6 +14,7 @@ bool has_map_of_maps = false; bool has_batch_map_operations = false; bool has_task_pt_regs_helper = false; bool has_get_current_task_btf = false; +bool has_ktime_get_boot_ns = false; unsigned int userspace_pid_ns_level = 0; SEC("tracepoint/sched/sched_switch") @@ -47,6 +48,9 @@ int detect_bpf_features(void *ctx) { has_get_current_task_btf = bpf_core_enum_value_exists( enum bpf_func_id, BPF_FUNC_get_current_task_btf ); + has_ktime_get_boot_ns = bpf_core_enum_value_exists( + enum bpf_func_id, BPF_FUNC_ktime_get_boot_ns); + userspace_pid_ns_level = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, level); feature_check_done = true; diff --git a/lightswitch-capabilities/src/system_info.rs b/lightswitch-capabilities/src/system_info.rs index ab96225b..24ea0578 100644 --- a/lightswitch-capabilities/src/system_info.rs +++ b/lightswitch-capabilities/src/system_info.rs @@ -32,6 +32,7 @@ pub struct BpfFeatures { pub has_mmapable_bpf_array: bool, pub has_task_pt_regs_helper: bool, pub has_get_current_task_btf: bool, + pub has_ktime_get_boot_ns: bool, pub has_variable_inner_map: bool, pub has_non_prealloc_hash_maps_in_tracing: bool, pub userspace_pid_ns_level: u32, @@ -297,6 +298,7 @@ fn check_bpf_features(btf_custom_path: Option) -> Result { has_mmapable_bpf_array: has_mmapable_bpf_array(), has_task_pt_regs_helper: bpf_features_bss.has_task_pt_regs_helper, has_get_current_task_btf: bpf_features_bss.has_get_current_task_btf, + has_ktime_get_boot_ns: bpf_features_bss.has_ktime_get_boot_ns, has_variable_inner_map: has_variable_inner_map(), has_non_prealloc_hash_maps_in_tracing: has_non_prealloc_hash_maps_in_tracing(), userspace_pid_ns_level: bpf_features_bss.userspace_pid_ns_level, diff --git a/src/bpf/profiler.bpf.c b/src/bpf/profiler.bpf.c index fc891ced..efd5ac96 100644 --- a/src/bpf/profiler.bpf.c +++ b/src/bpf/profiler.bpf.c @@ -264,7 +264,9 @@ static __always_inline void add_stack(struct bpf_perf_event_data *ctx, unwind_state->sample.pid = per_process_id; unwind_state->sample.tid = per_thread_id; - unwind_state->sample.collected_at = bpf_ktime_get_ns(); + unwind_state->sample.collected_at = lightswitch_config.use_ktime_get_boot_ns + ? bpf_ktime_get_boot_ns() + : bpf_ktime_get_ns(); u32 sample_size = sizeof(sample_t) // Remove the actual stack buffer which was doubled to appease the verifier. diff --git a/src/bpf/profiler.h b/src/bpf/profiler.h index 28d9dcb1..7c779af0 100644 --- a/src/bpf/profiler.h +++ b/src/bpf/profiler.h @@ -80,6 +80,7 @@ struct lightswitch_config_t { bool use_ring_buffers; bool use_task_pt_regs_helper; bool use_btf_helpers; + bool use_ktime_get_boot_ns; unsigned int userspace_pid_ns_level; }; diff --git a/src/bpf_objects.rs b/src/bpf_objects.rs index c20d8970..302e97e4 100644 --- a/src/bpf_objects.rs +++ b/src/bpf_objects.rs @@ -262,6 +262,10 @@ impl Bpf { .lightswitch_config .use_task_pt_regs_helper .write(profiler_config.use_task_pt_regs_helper); + rodata + .lightswitch_config + .use_ktime_get_boot_ns + .write(profiler_config.use_ktime_get_boot_ns); // Disable BTF helpers if a BTF custom path is selected since // it will not load in machines that don't have one. TODO add override? diff --git a/src/cli/main.rs b/src/cli/main.rs index adb6dd06..f75d293a 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -266,6 +266,7 @@ fn main() -> Result<(), Box> { use_ring_buffers, use_task_pt_regs_helper: system_info.available_bpf_features.has_task_pt_regs_helper && system_info.available_bpf_features.has_get_current_task_btf, + use_ktime_get_boot_ns: system_info.available_bpf_features.has_ktime_get_boot_ns, btf_custom_path, no_prealloc_bpf_hash_maps: args.no_prealloc_bpf_hash_maps && system_info diff --git a/src/profiler.rs b/src/profiler.rs index 0f594d6b..c49daf2c 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -143,6 +143,7 @@ pub struct ProfilerConfig { pub max_native_unwind_info_size_mb: i32, pub use_ring_buffers: bool, pub use_task_pt_regs_helper: bool, + pub use_ktime_get_boot_ns: bool, pub btf_custom_path: Option, pub no_prealloc_bpf_hash_maps: bool, pub preload_thread_metadata: bool, @@ -167,6 +168,7 @@ impl Default for ProfilerConfig { max_native_unwind_info_size_mb: i32::MAX, use_ring_buffers: true, use_task_pt_regs_helper: true, + use_ktime_get_boot_ns: false, btf_custom_path: None, no_prealloc_bpf_hash_maps: false, preload_thread_metadata: false, From 211d6e5ac92cc9a3cfa2d772d9e2095c6b03a929 Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Tue, 30 Jun 2026 13:04:53 -0400 Subject: [PATCH 5/9] Convert hash_of utility to a blanket trait implementation --- src/profile/sample.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/profile/sample.rs b/src/profile/sample.rs index c1302282..557ac0d5 100644 --- a/src/profile/sample.rs +++ b/src/profile/sample.rs @@ -332,11 +332,15 @@ mod tests { ); } - fn hash_of(sample: &RawSample) -> u64 { - use std::hash::{Hash, Hasher}; - let mut hasher = std::hash::DefaultHasher::new(); - sample.hash(&mut hasher); - hasher.finish() + trait HashValue { + fn hash_value(&self) -> u64; + } + + impl HashValue for T { + fn hash_value(&self) -> u64 { + use std::hash::{BuildHasher, BuildHasherDefault, DefaultHasher}; + BuildHasherDefault::::default().hash_one(self) + } } #[test] @@ -353,7 +357,7 @@ mod tests { collected_at: 1_002_000_000, ..base.clone() }; - assert_ne!(hash_of(&base), hash_of(&later)); + assert_ne!(base.hash_value(), later.hash_value()); } #[test] @@ -370,7 +374,7 @@ mod tests { collected_at: 1_000_999_000, ..a.clone() }; - assert_eq!(hash_of(&a), hash_of(&b)); + assert_eq!(a.hash_value(), b.hash_value()); } #[test] @@ -386,7 +390,7 @@ mod tests { ustack: vec![0xbeef], ..a.clone() }; - assert_ne!(hash_of(&a), hash_of(&b)); + assert_ne!(a.hash_value(), b.hash_value()); } #[test] From 6d0be1087c4e406cc6cfb7f348dbf13055885fd8 Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Tue, 30 Jun 2026 14:21:04 -0400 Subject: [PATCH 6/9] fix test failures due to optimistic setting of use_task_pt_regs_helper to true - The actual binary doesn't fail the way the tests do, because it actually does detection from system info query and resets the flags as needed, and the branch is pruned by the verifier - The reason the tests fail though is that use_task_pt_regs_helper defaults to true, which means the verifier ALWAYS sees a live branch calling bpf_task_pt_regs and bpf_get_current_task_btf. If either of the helpers we need to use (including the one added in this PR) isn't available on RHEL 8.10, the verifier rejects the program with EINVAL. --- src/profiler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/profiler.rs b/src/profiler.rs index c49daf2c..c1ccecff 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -167,7 +167,7 @@ impl Default for ProfilerConfig { debug_info_manager: Box::new(DebugInfoBackendNull {}), max_native_unwind_info_size_mb: i32::MAX, use_ring_buffers: true, - use_task_pt_regs_helper: true, + use_task_pt_regs_helper: false, use_ktime_get_boot_ns: false, btf_custom_path: None, no_prealloc_bpf_hash_maps: false, From 97fb1b22f36910088252eec75d2dc0cd694d912a Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Tue, 30 Jun 2026 18:26:18 +0000 Subject: [PATCH 7/9] Update cargo insta after adding new CLI option --- src/cli/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/main.rs b/src/cli/main.rs index f75d293a..9791eb74 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -515,7 +515,7 @@ mod tests { cmd.arg("--help"); cmd.assert().success(); let actual = String::from_utf8(cmd.unwrap().stdout).unwrap(); - insta::assert_yaml_snapshot!(actual, @r#""Usage: lightswitch [OPTIONS] [COMMAND]\n\nCommands:\n object-info \n show-unwind \n system-info \n server \n help Print this message or the help of the given subcommand(s)\n\nOptions:\n --pids \n Specific PIDs to profile\n\n -D, --duration \n How long this agent will run in seconds\n \n [default: 18446744073709551615]\n\n --libbpf-debug\n Enable libbpf logs. This includes the BPF verifier output\n\n --bpf-logging\n Enable BPF programs logging\n\n --btf-custom-path \n Alternative BTF file\n\n --logging \n Set lightswitch's logging level\n \n [default: info]\n [possible values: trace, debug, info, warn, error]\n\n --sample-freq \n Per-CPU Sampling Frequency in Hz\n \n [default: 19]\n\n --profile-format \n Output file for Flame Graph in SVG format\n \n [default: flame-graph]\n [possible values: none, flame-graph, pprof]\n\n --flamegraph-aggregation \n What information to show in the flamegraph. Won't do anything for other profile formats\n \n [default: function]\n [possible values: function, all]\n\n --profile-path \n Path for the generated profile\n\n --profile-name \n Name for the generated profile\n\n --sender \n Where to write the profile\n\n Possible values:\n - none: Discard the profile. Used for kernel tests\n - local-disk\n - remote\n - pyroscope\n - firefox\n \n [default: local-disk]\n\n --server-url \n \n\n --token \n \n\n --pyroscope-app-name \n Application name for Pyroscope\n \n [default: lightswitch]\n\n --pyroscope-tenant-id \n Tenant ID for multi-tenant Pyroscope (sets `X-Scope-OrgID` header)\n\n --perf-buffer-bytes \n Size of each profiler perf buffer, in bytes (must be a power of 2)\n \n [default: 524288]\n\n --mapsize-info\n Print eBPF map sizes after creation\n\n --mapsize-rate-limits \n max number of rate limit entries\n \n [default: 5000]\n\n --exclude-self\n Do not profile the profiler (myself)\n\n --symbolizer \n [default: local]\n [possible values: local, none]\n\n --debug-info-backend \n [default: none]\n [possible values: none, copy, remote]\n\n --max-native-unwind-info-size-mb \n approximate max size in megabytes used for the BPF maps that hold unwind information\n \n [default: 2147483647]\n\n --enable-deadlock-detector\n enable parking_lot's deadlock detector\n\n --cache-dir-base \n [default: /tmp]\n\n --killswitch-path-override \n Override the default path to the killswitch file (/tmp/lightswitch/killswitch) which prevents the profiler from starting\n\n --unsafe-start\n Force the profiler to start even if the system killswitch is enabled\n\n --force-perf-buffer\n force perf buffers even if ring buffers can be used\n\n --no-prealloc-bpf-hash-maps\n Do not preallocate BPF hash maps\n\n --preload-thread-metadata\n Read metadata for all threads when a new process is detected. This might be slow in older kernels.\n\n --live\n Launch live flamegraph TUI\n\n --kubernetes\n Enable Kubernetes pod-level profiling\n\n --node-name \n Node name for Kubernetes metadata (required with --kubernetes)\n\n -h, --help\n Print help (see a summary with '-h')\n\n -V, --version\n Print version\n""#); + insta::assert_yaml_snapshot!(actual, @r#""Usage: lightswitch [OPTIONS] [COMMAND]\n\nCommands:\n object-info \n show-unwind \n system-info \n server \n help Print this message or the help of the given subcommand(s)\n\nOptions:\n --pids \n Specific PIDs to profile\n\n -D, --duration \n How long this agent will run in seconds\n \n [default: 18446744073709551615]\n\n --libbpf-debug\n Enable libbpf logs. This includes the BPF verifier output\n\n --bpf-logging\n Enable BPF programs logging\n\n --btf-custom-path \n Alternative BTF file\n\n --logging \n Set lightswitch's logging level\n \n [default: info]\n [possible values: trace, debug, info, warn, error]\n\n --sample-freq \n Per-CPU Sampling Frequency in Hz\n \n [default: 19]\n\n --profile-format \n Output file for Flame Graph in SVG format\n \n [default: flame-graph]\n [possible values: none, flame-graph, pprof]\n\n --flamegraph-aggregation \n What information to show in the flamegraph. Won't do anything for other profile formats\n \n [default: function]\n [possible values: function, all]\n\n --profile-path \n Path for the generated profile\n\n --profile-name \n Name for the generated profile\n\n --sender \n Where to write the profile\n\n Possible values:\n - none: Discard the profile. Used for kernel tests\n - local-disk\n - remote\n - pyroscope\n - firefox\n \n [default: local-disk]\n\n --server-url \n \n\n --token \n \n\n --pyroscope-app-name \n Application name for Pyroscope\n \n [default: lightswitch]\n\n --pyroscope-tenant-id \n Tenant ID for multi-tenant Pyroscope (sets `X-Scope-OrgID` header)\n\n --perf-buffer-bytes \n Size of each profiler perf buffer, in bytes (must be a power of 2)\n \n [default: 524288]\n\n --mapsize-info\n Print eBPF map sizes after creation\n\n --mapsize-rate-limits \n max number of rate limit entries\n \n [default: 5000]\n\n --exclude-self\n Do not profile the profiler (myself)\n\n --symbolizer \n [default: local]\n [possible values: local, none]\n\n --debug-info-backend \n [default: none]\n [possible values: none, copy, remote]\n\n --max-native-unwind-info-size-mb \n approximate max size in megabytes used for the BPF maps that hold unwind information\n \n [default: 2147483647]\n\n --enable-deadlock-detector\n enable parking_lot's deadlock detector\n\n --cache-dir-base \n [default: /bb/data/tmp/nix-shell.b4suUj]\n\n --killswitch-path-override \n Override the default path to the killswitch file (/tmp/lightswitch/killswitch) which prevents the profiler from starting\n\n --unsafe-start\n Force the profiler to start even if the system killswitch is enabled\n\n --force-perf-buffer\n force perf buffers even if ring buffers can be used\n\n --no-prealloc-bpf-hash-maps\n Do not preallocate BPF hash maps\n\n --preload-thread-metadata\n Read metadata for all threads when a new process is detected. This might be slow in older kernels.\n\n --enable-ts-per-sample\n Attach a per-sample collected_at timestamp (ms) label to pprof output\n\n --live\n Launch live flamegraph TUI\n\n --kubernetes\n Enable Kubernetes pod-level profiling\n\n --node-name \n Node name for Kubernetes metadata (required with --kubernetes)\n\n -h, --help\n Print help (see a summary with '-h')\n\n -V, --version\n Print version\n""#); } #[rstest] From ce944b5ca68a8ddbe7e1a5e9b987ed58cb78077f Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Tue, 30 Jun 2026 19:49:55 +0000 Subject: [PATCH 8/9] Actually update the cargo insta after review --- src/cli/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/main.rs b/src/cli/main.rs index 9791eb74..873b2898 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -515,7 +515,7 @@ mod tests { cmd.arg("--help"); cmd.assert().success(); let actual = String::from_utf8(cmd.unwrap().stdout).unwrap(); - insta::assert_yaml_snapshot!(actual, @r#""Usage: lightswitch [OPTIONS] [COMMAND]\n\nCommands:\n object-info \n show-unwind \n system-info \n server \n help Print this message or the help of the given subcommand(s)\n\nOptions:\n --pids \n Specific PIDs to profile\n\n -D, --duration \n How long this agent will run in seconds\n \n [default: 18446744073709551615]\n\n --libbpf-debug\n Enable libbpf logs. This includes the BPF verifier output\n\n --bpf-logging\n Enable BPF programs logging\n\n --btf-custom-path \n Alternative BTF file\n\n --logging \n Set lightswitch's logging level\n \n [default: info]\n [possible values: trace, debug, info, warn, error]\n\n --sample-freq \n Per-CPU Sampling Frequency in Hz\n \n [default: 19]\n\n --profile-format \n Output file for Flame Graph in SVG format\n \n [default: flame-graph]\n [possible values: none, flame-graph, pprof]\n\n --flamegraph-aggregation \n What information to show in the flamegraph. Won't do anything for other profile formats\n \n [default: function]\n [possible values: function, all]\n\n --profile-path \n Path for the generated profile\n\n --profile-name \n Name for the generated profile\n\n --sender \n Where to write the profile\n\n Possible values:\n - none: Discard the profile. Used for kernel tests\n - local-disk\n - remote\n - pyroscope\n - firefox\n \n [default: local-disk]\n\n --server-url \n \n\n --token \n \n\n --pyroscope-app-name \n Application name for Pyroscope\n \n [default: lightswitch]\n\n --pyroscope-tenant-id \n Tenant ID for multi-tenant Pyroscope (sets `X-Scope-OrgID` header)\n\n --perf-buffer-bytes \n Size of each profiler perf buffer, in bytes (must be a power of 2)\n \n [default: 524288]\n\n --mapsize-info\n Print eBPF map sizes after creation\n\n --mapsize-rate-limits \n max number of rate limit entries\n \n [default: 5000]\n\n --exclude-self\n Do not profile the profiler (myself)\n\n --symbolizer \n [default: local]\n [possible values: local, none]\n\n --debug-info-backend \n [default: none]\n [possible values: none, copy, remote]\n\n --max-native-unwind-info-size-mb \n approximate max size in megabytes used for the BPF maps that hold unwind information\n \n [default: 2147483647]\n\n --enable-deadlock-detector\n enable parking_lot's deadlock detector\n\n --cache-dir-base \n [default: /bb/data/tmp/nix-shell.b4suUj]\n\n --killswitch-path-override \n Override the default path to the killswitch file (/tmp/lightswitch/killswitch) which prevents the profiler from starting\n\n --unsafe-start\n Force the profiler to start even if the system killswitch is enabled\n\n --force-perf-buffer\n force perf buffers even if ring buffers can be used\n\n --no-prealloc-bpf-hash-maps\n Do not preallocate BPF hash maps\n\n --preload-thread-metadata\n Read metadata for all threads when a new process is detected. This might be slow in older kernels.\n\n --enable-ts-per-sample\n Attach a per-sample collected_at timestamp (ms) label to pprof output\n\n --live\n Launch live flamegraph TUI\n\n --kubernetes\n Enable Kubernetes pod-level profiling\n\n --node-name \n Node name for Kubernetes metadata (required with --kubernetes)\n\n -h, --help\n Print help (see a summary with '-h')\n\n -V, --version\n Print version\n""#); + insta::assert_yaml_snapshot!(actual, @r#""Usage: lightswitch [OPTIONS] [COMMAND]\n\nCommands:\n object-info \n show-unwind \n system-info \n server \n help Print this message or the help of the given subcommand(s)\n\nOptions:\n --pids \n Specific PIDs to profile\n\n -D, --duration \n How long this agent will run in seconds\n \n [default: 18446744073709551615]\n\n --libbpf-debug\n Enable libbpf logs. This includes the BPF verifier output\n\n --bpf-logging\n Enable BPF programs logging\n\n --btf-custom-path \n Alternative BTF file\n\n --logging \n Set lightswitch's logging level\n \n [default: info]\n [possible values: trace, debug, info, warn, error]\n\n --sample-freq \n Per-CPU Sampling Frequency in Hz\n \n [default: 19]\n\n --profile-format \n Output file for Flame Graph in SVG format\n \n [default: flame-graph]\n [possible values: none, flame-graph, pprof]\n\n --flamegraph-aggregation \n What information to show in the flamegraph. Won't do anything for other profile formats\n \n [default: function]\n [possible values: function, all]\n\n --profile-path \n Path for the generated profile\n\n --profile-name \n Name for the generated profile\n\n --sender \n Where to write the profile\n\n Possible values:\n - none: Discard the profile. Used for kernel tests\n - local-disk\n - remote\n - pyroscope\n - firefox\n \n [default: local-disk]\n\n --server-url \n \n\n --token \n \n\n --pyroscope-app-name \n Application name for Pyroscope\n \n [default: lightswitch]\n\n --pyroscope-tenant-id \n Tenant ID for multi-tenant Pyroscope (sets `X-Scope-OrgID` header)\n\n --perf-buffer-bytes \n Size of each profiler perf buffer, in bytes (must be a power of 2)\n \n [default: 524288]\n\n --mapsize-info\n Print eBPF map sizes after creation\n\n --mapsize-rate-limits \n max number of rate limit entries\n \n [default: 5000]\n\n --exclude-self\n Do not profile the profiler (myself)\n\n --symbolizer \n [default: local]\n [possible values: local, none]\n\n --debug-info-backend \n [default: none]\n [possible values: none, copy, remote]\n\n --max-native-unwind-info-size-mb \n approximate max size in megabytes used for the BPF maps that hold unwind information\n \n [default: 2147483647]\n\n --enable-deadlock-detector\n enable parking_lot's deadlock detector\n\n --cache-dir-base \n [default: /bb/data/tmp]\n\n --killswitch-path-override \n Override the default path to the killswitch file (/tmp/lightswitch/killswitch) which prevents the profiler from starting\n\n --unsafe-start\n Force the profiler to start even if the system killswitch is enabled\n\n --force-perf-buffer\n force perf buffers even if ring buffers can be used\n\n --no-prealloc-bpf-hash-maps\n Do not preallocate BPF hash maps\n\n --preload-thread-metadata\n Read metadata for all threads when a new process is detected. This might be slow in older kernels.\n\n --enable-ts-per-sample\n Attach a per-sample collected_at timestamp (ms) label to pprof output\n\n --live\n Launch live flamegraph TUI\n\n --kubernetes\n Enable Kubernetes pod-level profiling\n\n --node-name \n Node name for Kubernetes metadata (required with --kubernetes)\n\n -h, --help\n Print help (see a summary with '-h')\n\n -V, --version\n Print version\n""#); } #[rstest] From b32a05b9fd1bb2257113f82a27a04f789a661174 Mon Sep 17 00:00:00 2001 From: Gordon Marler Date: Wed, 1 Jul 2026 11:58:35 -0400 Subject: [PATCH 9/9] Remove needless detection of bpf_ktime_get_boot_ns - It actually is always available in kernels of interest to us --- lightswitch-capabilities/src/bpf/features.bpf.c | 4 ---- lightswitch-capabilities/src/system_info.rs | 2 -- src/bpf/profiler.bpf.c | 4 +--- src/bpf/profiler.h | 1 - src/bpf_objects.rs | 5 ----- src/cli/main.rs | 1 - src/profiler.rs | 2 -- 7 files changed, 1 insertion(+), 18 deletions(-) diff --git a/lightswitch-capabilities/src/bpf/features.bpf.c b/lightswitch-capabilities/src/bpf/features.bpf.c index b405b792..3ab81e06 100644 --- a/lightswitch-capabilities/src/bpf/features.bpf.c +++ b/lightswitch-capabilities/src/bpf/features.bpf.c @@ -14,7 +14,6 @@ bool has_map_of_maps = false; bool has_batch_map_operations = false; bool has_task_pt_regs_helper = false; bool has_get_current_task_btf = false; -bool has_ktime_get_boot_ns = false; unsigned int userspace_pid_ns_level = 0; SEC("tracepoint/sched/sched_switch") @@ -48,9 +47,6 @@ int detect_bpf_features(void *ctx) { has_get_current_task_btf = bpf_core_enum_value_exists( enum bpf_func_id, BPF_FUNC_get_current_task_btf ); - has_ktime_get_boot_ns = bpf_core_enum_value_exists( - enum bpf_func_id, BPF_FUNC_ktime_get_boot_ns); - userspace_pid_ns_level = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, level); feature_check_done = true; diff --git a/lightswitch-capabilities/src/system_info.rs b/lightswitch-capabilities/src/system_info.rs index 24ea0578..ab96225b 100644 --- a/lightswitch-capabilities/src/system_info.rs +++ b/lightswitch-capabilities/src/system_info.rs @@ -32,7 +32,6 @@ pub struct BpfFeatures { pub has_mmapable_bpf_array: bool, pub has_task_pt_regs_helper: bool, pub has_get_current_task_btf: bool, - pub has_ktime_get_boot_ns: bool, pub has_variable_inner_map: bool, pub has_non_prealloc_hash_maps_in_tracing: bool, pub userspace_pid_ns_level: u32, @@ -298,7 +297,6 @@ fn check_bpf_features(btf_custom_path: Option) -> Result { has_mmapable_bpf_array: has_mmapable_bpf_array(), has_task_pt_regs_helper: bpf_features_bss.has_task_pt_regs_helper, has_get_current_task_btf: bpf_features_bss.has_get_current_task_btf, - has_ktime_get_boot_ns: bpf_features_bss.has_ktime_get_boot_ns, has_variable_inner_map: has_variable_inner_map(), has_non_prealloc_hash_maps_in_tracing: has_non_prealloc_hash_maps_in_tracing(), userspace_pid_ns_level: bpf_features_bss.userspace_pid_ns_level, diff --git a/src/bpf/profiler.bpf.c b/src/bpf/profiler.bpf.c index efd5ac96..402e9cc2 100644 --- a/src/bpf/profiler.bpf.c +++ b/src/bpf/profiler.bpf.c @@ -264,9 +264,7 @@ static __always_inline void add_stack(struct bpf_perf_event_data *ctx, unwind_state->sample.pid = per_process_id; unwind_state->sample.tid = per_thread_id; - unwind_state->sample.collected_at = lightswitch_config.use_ktime_get_boot_ns - ? bpf_ktime_get_boot_ns() - : bpf_ktime_get_ns(); + unwind_state->sample.collected_at = bpf_ktime_get_boot_ns(); u32 sample_size = sizeof(sample_t) // Remove the actual stack buffer which was doubled to appease the verifier. diff --git a/src/bpf/profiler.h b/src/bpf/profiler.h index 7c779af0..28d9dcb1 100644 --- a/src/bpf/profiler.h +++ b/src/bpf/profiler.h @@ -80,7 +80,6 @@ struct lightswitch_config_t { bool use_ring_buffers; bool use_task_pt_regs_helper; bool use_btf_helpers; - bool use_ktime_get_boot_ns; unsigned int userspace_pid_ns_level; }; diff --git a/src/bpf_objects.rs b/src/bpf_objects.rs index 302e97e4..5bd2f665 100644 --- a/src/bpf_objects.rs +++ b/src/bpf_objects.rs @@ -262,11 +262,6 @@ impl Bpf { .lightswitch_config .use_task_pt_regs_helper .write(profiler_config.use_task_pt_regs_helper); - rodata - .lightswitch_config - .use_ktime_get_boot_ns - .write(profiler_config.use_ktime_get_boot_ns); - // Disable BTF helpers if a BTF custom path is selected since // it will not load in machines that don't have one. TODO add override? rodata diff --git a/src/cli/main.rs b/src/cli/main.rs index 873b2898..11a83df0 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -266,7 +266,6 @@ fn main() -> Result<(), Box> { use_ring_buffers, use_task_pt_regs_helper: system_info.available_bpf_features.has_task_pt_regs_helper && system_info.available_bpf_features.has_get_current_task_btf, - use_ktime_get_boot_ns: system_info.available_bpf_features.has_ktime_get_boot_ns, btf_custom_path, no_prealloc_bpf_hash_maps: args.no_prealloc_bpf_hash_maps && system_info diff --git a/src/profiler.rs b/src/profiler.rs index c1ccecff..624c7a32 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -143,7 +143,6 @@ pub struct ProfilerConfig { pub max_native_unwind_info_size_mb: i32, pub use_ring_buffers: bool, pub use_task_pt_regs_helper: bool, - pub use_ktime_get_boot_ns: bool, pub btf_custom_path: Option, pub no_prealloc_bpf_hash_maps: bool, pub preload_thread_metadata: bool, @@ -168,7 +167,6 @@ impl Default for ProfilerConfig { max_native_unwind_info_size_mb: i32::MAX, use_ring_buffers: true, use_task_pt_regs_helper: false, - use_ktime_get_boot_ns: false, btf_custom_path: None, no_prealloc_bpf_hash_maps: false, preload_thread_metadata: false,