pyroscope_rbspy_oncpu/ui/
speedscope.rs1use std::collections::HashMap;
2use std::io::Write;
3use std::time::SystemTime;
4
5use crate::core::process::Pid;
6use crate::core::types::{StackFrame, StackTrace};
7
8use anyhow::Result;
9
10#[derive(Debug, Serialize, Deserialize)]
29struct SpeedscopeFile {
30 #[serde(rename = "$schema")]
31 schema: String,
32 profiles: Vec<Profile>,
33 shared: Shared,
34
35 #[serde(rename = "activeProfileIndex")]
36 active_profile_index: Option<f64>,
37
38 exporter: Option<String>,
39
40 name: Option<String>,
41}
42
43#[derive(Debug, Serialize, Deserialize)]
44struct Profile {
45 #[serde(rename = "type")]
46 profile_type: ProfileType,
47
48 name: String,
49 unit: ValueUnit,
50
51 #[serde(rename = "startValue")]
52 start_value: f64,
53
54 #[serde(rename = "endValue")]
55 end_value: f64,
56
57 samples: Vec<Vec<usize>>,
58 weights: Vec<f64>,
59}
60
61#[derive(Debug, Serialize, Deserialize)]
62struct Shared {
63 frames: Vec<Frame>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67struct Frame {
68 name: String,
69 file: Option<String>,
70 line: Option<usize>,
71 col: Option<usize>,
72}
73
74#[derive(Debug, Serialize, Deserialize)]
75enum ProfileType {
76 #[serde(rename = "evented")]
77 Evented,
78 #[serde(rename = "sampled")]
79 Sampled,
80}
81
82#[derive(Debug, Serialize, Deserialize)]
83enum ValueUnit {
84 #[serde(rename = "bytes")]
85 Bytes,
86 #[serde(rename = "microseconds")]
87 Microseconds,
88 #[serde(rename = "milliseconds")]
89 Milliseconds,
90 #[serde(rename = "nanoseconds")]
91 Nanoseconds,
92 #[serde(rename = "none")]
93 None,
94 #[serde(rename = "seconds")]
95 Seconds,
96}
97
98impl SpeedscopeFile {
99 pub fn new(
100 samples: HashMap<Option<Pid>, Vec<Vec<usize>>>,
101 frames: Vec<Frame>,
102 weights: Vec<f64>,
103 ) -> SpeedscopeFile {
104 let end_value = samples.len();
105
106 SpeedscopeFile {
107 schema: "https://www.speedscope.app/file-format-schema.json".to_string(),
109
110 active_profile_index: None,
111
112 name: Some("rbspy profile".to_string()),
113
114 exporter: Some(format!("rbspy@{}", env!("CARGO_PKG_VERSION"))),
115
116 profiles: samples
117 .iter()
118 .map(|(option_pid, samples)| Profile {
119 profile_type: ProfileType::Sampled,
120
121 name: option_pid.map_or("rbspy profile".to_string(), |pid| {
122 format!("rbspy profile - pid {}", pid)
123 }),
124
125 unit: ValueUnit::Seconds,
126
127 start_value: 0.0,
128 end_value: end_value as f64,
129
130 samples: samples.clone(),
131 weights: weights.clone(),
132 })
133 .collect(),
134
135 shared: Shared { frames },
136 }
137 }
138}
139
140impl Frame {
141 pub fn new(stack_frame: &StackFrame) -> Frame {
142 Frame {
143 name: stack_frame.name.clone(),
144 file: Some(stack_frame.relative_path.clone()),
145 line: stack_frame.lineno,
146 col: None,
147 }
148 }
149}
150
151#[derive(Default)]
152pub struct Stats {
153 samples: HashMap<Option<Pid>, Vec<Vec<usize>>>,
154 frames: Vec<Frame>,
155 frame_to_index: HashMap<StackFrame, usize>,
156 weights: Vec<f64>,
157 prev_time: Option<SystemTime>,
158}
159
160impl Stats {
161 pub fn new() -> Stats {
162 Default::default()
163 }
164
165 pub fn record(&mut self, stack: &StackTrace) -> Result<()> {
166 let mut frame_indices: Vec<usize> = stack
167 .trace
168 .iter()
169 .map(|frame| {
170 let frames = &mut self.frames;
171 *self.frame_to_index.entry(frame.clone()).or_insert_with(|| {
172 let len = frames.len();
173 frames.push(Frame::new(&frame));
174 len
175 })
176 })
177 .collect();
178 frame_indices.reverse();
179
180 self.samples
181 .entry(stack.pid)
182 .or_insert_with(|| vec![])
183 .push(frame_indices);
184
185 if let Some(time) = stack.time {
186 if let Some(prev_time) = self.prev_time {
187 let delta = time.duration_since(prev_time)?;
188 self.weights.push(delta.as_secs_f64());
189 } else {
190 self.weights.push(0.0);
192 }
193 self.prev_time = stack.time;
194 } else {
195 self.weights.push(1.0);
197 }
198
199 Ok(())
200 }
201
202 pub fn write(&self, mut w: &mut dyn Write) -> Result<()> {
203 let json = serde_json::to_string(&SpeedscopeFile::new(
204 self.samples.clone(),
205 self.frames.clone(),
206 self.weights.clone(),
207 ))?;
208 writeln!(&mut w, "{}", json)?;
209 Ok(())
210 }
211}