Skip to content

Commit 4714a78

Browse files
committed
Extract trace structs
1 parent a9ccbbf commit 4714a78

File tree

4 files changed

+102
-54
lines changed

4 files changed

+102
-54
lines changed

nexus/src/app/background/tasks/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub mod region_snapshot_replacement_start;
4343
pub mod region_snapshot_replacement_step;
4444
pub mod saga_recovery;
4545
pub mod service_firewall_rules;
46+
pub mod support_bundle;
4647
pub mod support_bundle_collector;
4748
pub mod sync_service_zone_nat;
4849
pub mod sync_switch_configuration;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Support bundle related types and utilities
6+
7+
pub mod perfetto;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Perfetto Trace Event format support for visualizing support bundle collection
6+
7+
use serde::Deserialize;
8+
use serde::Serialize;
9+
10+
/// Represents a Perfetto Trace Event format JSON file for visualization.
11+
///
12+
/// This format is used by the Perfetto trace viewer (https://ui.perfetto.dev/)
13+
/// to visualize timing information for operations.
14+
#[derive(Serialize, Deserialize)]
15+
pub struct Trace {
16+
#[serde(rename = "traceEvents")]
17+
pub trace_events: Vec<TraceEvent>,
18+
/// Display unit for time values in the UI (e.g., "ms" for milliseconds)
19+
#[serde(rename = "displayTimeUnit")]
20+
pub display_time_unit: String,
21+
}
22+
23+
/// A single event in the Perfetto Trace Event format.
24+
///
25+
/// This represents a complete event (duration event) showing when an operation
26+
/// started and how long it took.
27+
#[derive(Serialize, Deserialize)]
28+
pub struct TraceEvent {
29+
/// Human-readable name of the event
30+
pub name: String,
31+
/// Category name (abbreviated as "cat" in Perfetto format).
32+
/// Used to group related events together in the trace viewer.
33+
pub cat: String,
34+
/// Phase type (abbreviated as "ph" in Perfetto format).
35+
/// "X" means a "Complete" event with both timestamp and duration.
36+
pub ph: String,
37+
/// Timestamp in microseconds (abbreviated as "ts" in Perfetto format).
38+
/// Represents when the event started, as microseconds since the epoch.
39+
pub ts: i64,
40+
/// Duration in microseconds (abbreviated as "dur" in Perfetto format).
41+
/// How long the event took to complete.
42+
pub dur: i64,
43+
/// Process ID. Used to separate events into different process lanes
44+
/// in the trace viewer.
45+
pub pid: u32,
46+
/// Thread ID. Used to separate events into different thread lanes
47+
/// within a process in the trace viewer.
48+
pub tid: usize,
49+
/// Arbitrary key-value pairs with additional event metadata
50+
pub args: serde_json::Value,
51+
}

nexus/src/app/background/tasks/support_bundle_collector.rs

Lines changed: 43 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ use zip::ZipArchive;
7878
use zip::ZipWriter;
7979
use zip::write::FullFileOptions;
8080

81+
use super::support_bundle::perfetto;
82+
8183
// We use "/var/tmp" to use Nexus' filesystem for temporary storage,
8284
// rather than "/tmp", which would keep this collected data in-memory.
8385
const TEMPDIR: &str = "/var/tmp";
@@ -1088,27 +1090,27 @@ impl BundleCollection {
10881090
.max(0);
10891091
let step_id = i + 1;
10901092

1091-
json!({
1092-
"name": step.name,
1093-
"cat": "bundle_collection",
1094-
"ph": "X", // Complete event (has duration)
1095-
"ts": start_us,
1096-
"dur": duration_us,
1097-
"pid": 1,
1098-
"tid": step_id,
1099-
"args": {
1093+
perfetto::TraceEvent {
1094+
name: step.name.clone(),
1095+
cat: "bundle_collection".to_string(),
1096+
ph: "X".to_string(),
1097+
ts: start_us,
1098+
dur: duration_us,
1099+
pid: 1,
1100+
tid: step_id,
1101+
args: json!({
11001102
"status": step.status.to_string(),
1101-
}
1102-
})
1103+
}),
1104+
}
11031105
})
11041106
.collect();
11051107

1106-
let trace_json = json!({
1107-
"traceEvents": trace_events,
1108-
"displayTimeUnit": "ms",
1109-
});
1108+
let trace = perfetto::Trace {
1109+
trace_events,
1110+
display_time_unit: "ms".to_string(),
1111+
};
11101112

1111-
let trace_content = serde_json::to_string_pretty(&trace_json)
1113+
let trace_content = serde_json::to_string_pretty(&trace)
11121114
.context("Failed to serialize trace JSON")?;
11131115

11141116
tokio::fs::write(&trace_path, trace_content).await.with_context(
@@ -1119,7 +1121,7 @@ impl BundleCollection {
11191121
self.log,
11201122
"Wrote trace file";
11211123
"path" => %trace_path,
1122-
"num_events" => trace_events.len()
1124+
"num_events" => trace.trace_events.len()
11231125
);
11241126

11251127
Ok(())
@@ -2669,64 +2671,51 @@ mod test {
26692671
.await
26702672
.expect("Should be able to download trace file");
26712673

2672-
// Parse the trace file as JSON
2674+
// Parse the trace file using our Perfetto structs
26732675
let body_bytes =
26742676
response.into_body().collect().await.unwrap().to_bytes();
2675-
let trace_json: serde_json::Value = serde_json::from_slice(&body_bytes)
2676-
.expect("Trace file should be valid JSON");
2677+
let trace: perfetto::Trace = serde_json::from_slice(&body_bytes)
2678+
.expect("Trace file should be valid Perfetto JSON");
26772679

2678-
// Verify the structure matches Perfetto Trace Event format
2679-
let trace_events = trace_json
2680-
.get("traceEvents")
2681-
.expect("Should have traceEvents field")
2682-
.as_array()
2683-
.expect("traceEvents should be an array");
2680+
// Verify display time unit
2681+
assert_eq!(
2682+
trace.display_time_unit, "ms",
2683+
"Display time unit should be milliseconds"
2684+
);
26842685

26852686
// We should have at least the main collection steps
26862687
assert!(
2687-
!trace_events.is_empty(),
2688+
!trace.trace_events.is_empty(),
26882689
"Should have at least one trace event"
26892690
);
26902691

2691-
// Verify each event has the expected fields
2692-
for event in trace_events {
2693-
assert!(event.get("name").is_some(), "Event should have name");
2692+
// Verify each event has the expected structure
2693+
for event in &trace.trace_events {
2694+
// Verify category
26942695
assert_eq!(
2695-
event.get("cat").and_then(|v| v.as_str()),
2696-
Some("bundle_collection"),
2696+
event.cat, "bundle_collection",
26972697
"Event should have category 'bundle_collection'"
26982698
);
2699-
assert_eq!(
2700-
event.get("ph").and_then(|v| v.as_str()),
2701-
Some("X"),
2702-
"Event should be Complete event type"
2703-
);
2704-
assert!(
2705-
event.get("ts").and_then(|v| v.as_i64()).is_some(),
2706-
"Event should have timestamp"
2707-
);
2708-
assert!(
2709-
event.get("dur").and_then(|v| v.as_i64()).is_some(),
2710-
"Event should have duration"
2711-
);
2712-
assert!(
2713-
event.get("args").is_some(),
2714-
"Event should have args field"
2715-
);
2699+
// Verify phase type
2700+
assert_eq!(event.ph, "X", "Event should be Complete event type");
2701+
// Verify timestamps are positive
2702+
assert!(event.ts >= 0, "Event timestamp should be non-negative");
2703+
assert!(event.dur >= 0, "Event duration should be non-negative");
2704+
// Verify process and thread IDs are set
2705+
assert_eq!(event.pid, 1, "All events should have pid=1");
2706+
assert!(event.tid > 0, "Event thread ID should be positive");
27162707
}
27172708

27182709
// Verify we have the same number of events as steps in the report
27192710
assert_eq!(
2720-
trace_events.len(),
2711+
trace.trace_events.len(),
27212712
report.steps.len(),
27222713
"Number of events should match number of steps"
27232714
);
27242715

27252716
// Verify step names match between report and trace
2726-
let trace_names: std::collections::HashSet<_> = trace_events
2727-
.iter()
2728-
.filter_map(|e| e.get("name").and_then(|v| v.as_str()))
2729-
.collect();
2717+
let trace_names: std::collections::HashSet<_> =
2718+
trace.trace_events.iter().map(|e| e.name.as_str()).collect();
27302719
let report_names: std::collections::HashSet<_> =
27312720
report.steps.iter().map(|s| s.name.as_str()).collect();
27322721
assert_eq!(

0 commit comments

Comments
 (0)