authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-04 13:06:10-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 13:06:10-07:00
log163d86a78430d23414abde323ca70d8379280c0b
tree5e275b7a6f6f3556efa4f757863dfe720bc180c4
parent8e056a0e5124775b9439751667bac483d7b5320a
signature Signed by PGP key B5690EEEBB952194

perf(server): skip unused Linux process detail reads (#9768)


1 files changed, 96 insertions(+), 7 deletions(-)

native/resource-monitor/src/main.rs+96-7
......@@ -346,7 +346,7 @@ impl Collector {
346346 self.system.refresh_processes_specifics(
347347 ProcessesToUpdate::All,
348348 true,
349 process_refresh_kind(),
349 process_discovery_refresh_kind(),
350350 );
351351 self.cpu_baseline_refreshed_at = Some(Instant::now());
352352 }
......@@ -361,7 +361,7 @@ impl Collector {
361361 self.system.refresh_processes_specifics(
362362 ProcessesToUpdate::All,
363363 true,
364 process_refresh_kind(),
364 process_discovery_refresh_kind(),
365365 );
366366 self.cpu_baseline_refreshed_at = Some(Instant::now());
367367
......@@ -397,15 +397,57 @@ impl Collector {
397397 roots.insert(config.root_pid);
398398 let tracked = select_tracked_pids(&rows, &roots);
399399 let tracked_process_count = tracked.len();
400 let process_details = if cfg!(target_os = "linux") && !tracked.is_empty() {
401 let monitor_pid = Pid::from_u32(std::process::id());
402 let mut detail_pids = tracked
403 .iter()
404 .copied()
405 .map(Pid::from_u32)
406 .collect::<Vec<_>>();
407 if !tracked.contains(&monitor_pid.as_u32()) {
408 detail_pids.push(monitor_pid);
409 }
410 // Detail fields need no baseline. Drop command data and OS handles after each sample.
411 let mut details = System::new();
412 details.refresh_processes_specifics(
413 ProcessesToUpdate::Some(&detail_pids),
414 true,
415 process_refresh_kind().without_cpu(),
416 );
417 // This process cannot be replaced during collection. Its start time
418 // exposes any boot-epoch shift between the two System instances.
419 let start_time_offset = self.system.process(monitor_pid).and_then(|process| {
420 details.process(monitor_pid).map(|detail| {
421 i128::from(detail.start_time()) - i128::from(process.start_time())
422 })
423 });
424 Some((details, start_time_offset))
425 } else {
426 None
427 };
428 let sample_details = process_details
429 .as_ref()
430 .map_or(&self.system, |(details, _)| details);
431 let start_time_offset = process_details
432 .as_ref()
433 .map_or(Some(0), |(_, offset)| *offset);
400434 let mut processes = tracked
401435 .into_iter()
402436 .filter_map(|pid| {
403437 let process = self.system.process(Pid::from_u32(pid))?;
404 let disk_usage = process.disk_usage();
405 let command = if process.cmd().is_empty() {
438 let details = sample_details.process(Pid::from_u32(pid))?;
439 if !matches_process_start_time(
440 process.start_time(),
441 details.start_time(),
442 start_time_offset?,
443 ) {
444 return None;
445 }
446 let disk_usage = details.disk_usage();
447 let command = if details.cmd().is_empty() {
406448 process.name().to_string_lossy().into_owned()
407449 } else {
408 process
450 details
409451 .cmd()
410452 .iter()
411453 .map(|part| part.to_string_lossy())
......@@ -429,14 +471,15 @@ impl Collector {
429471 ),
430472 cpu_percent: process.cpu_usage(),
431473 cpu_time_ms: process.accumulated_cpu_time(),
432 resident_bytes: process.memory(),
433 virtual_bytes: process.virtual_memory(),
474 resident_bytes: details.memory(),
475 virtual_bytes: details.virtual_memory(),
434476 io_read_bytes: disk_usage.total_read_bytes,
435477 io_write_bytes: disk_usage.total_written_bytes,
436478 io_semantics: io_semantics(),
437479 })
438480 })
439481 .collect::<Vec<_>>();
482 drop(process_details);
440483 processes.sort_by_key(|process| process.pid);
441484 self.sequence = self.sequence.saturating_add(1);
442485
......@@ -459,6 +502,15 @@ impl Collector {
459502 }
460503}
461504
505// Keep CPU baselines separate. Even a metadata refresh resets Linux process times.
506fn process_discovery_refresh_kind() -> ProcessRefreshKind {
507 if cfg!(target_os = "linux") {
508 ProcessRefreshKind::nothing().with_cpu().without_tasks()
509 } else {
510 process_refresh_kind()
511 }
512}
513
462514fn process_refresh_kind() -> ProcessRefreshKind {
463515 ProcessRefreshKind::nothing()
464516 .with_memory()
......@@ -472,6 +524,10 @@ fn inaccessible_process_count(selected: usize, materialized: usize) -> usize {
472524 selected.saturating_sub(materialized)
473525}
474526
527fn matches_process_start_time(discovered: u64, detail: u64, epoch_offset: i128) -> bool {
528 i128::from(detail) - epoch_offset == i128::from(discovered)
529}
530
475531fn remaining_cpu_measurement_delay(
476532 baseline_refreshed_at: Option<Instant>,
477533 now: Instant,
......@@ -889,6 +945,39 @@ mod tests {
889945 assert!(!matches_external_identity(10_000, Some(9_999)));
890946 }
891947
948 #[test]
949 fn loads_details_when_an_existing_process_becomes_selected() {
950 let mut collector = Collector::new();
951 let mut config = CollectorConfig {
952 root_pid: u32::MAX,
953 sample_interval: None,
954 external_processes: HashMap::new(),
955 };
956 assert!(collector.sample(&config, None).processes.is_empty());
957
958 config.root_pid = std::process::id();
959 let snapshot = collector.sample(&config, None);
960 let process = snapshot
961 .processes
962 .iter()
963 .find(|process| process.pid == config.root_pid)
964 .expect("selected process");
965
966 assert!(!process.command.is_empty());
967 assert!(process.resident_bytes > 0);
968 assert!(process.cpu_percent.is_finite());
969 }
970
971 #[test]
972 fn accepts_clock_shifts_without_accepting_reused_process_starts() {
973 assert!(matches_process_start_time(10_000, 13_600, 3_600));
974 assert!(!matches_process_start_time(10_000, 13_601, 3_600));
975 assert!(matches_process_start_time(10_000, 6_400, -3_600));
976 assert!(!matches_process_start_time(10_000, 6_401, -3_600));
977 assert!(matches_process_start_time(10_000, 10_000, 0));
978 assert!(!matches_process_start_time(10_000, 10_001, 0));
979 }
980
892981 #[test]
893982 fn decodes_protocol_commands() {
894983 let configure = serde_json::from_str::<Command>(