use std::collections::{HashMap};
use std::io;
use std::io::Write;
use std::fs::File;
use crate::stack_trace;
use remoteprocess::Tid;
use failure::{Error};
use serde_json;
#[derive(Debug, Serialize)]
struct SpeedscopeFile {
#[serde(rename = "$schema")]
schema: String,
profiles: Vec<Profile>,
shared: Shared,
#[serde(rename = "activeProfileIndex")]
active_profile_index: Option<f64>,
exporter: Option<String>,
name: Option<String>,
}
#[derive(Debug, Serialize)]
struct Profile {
#[serde(rename = "type")]
profile_type: ProfileType,
name: String,
unit: ValueUnit,
#[serde(rename = "startValue")]
start_value: f64,
#[serde(rename = "endValue")]
end_value: f64,
samples: Vec<Vec<usize>>,
weights: Vec<f64>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Shared {
frames: Vec<Frame>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Frame {
name: String,
file: Option<String>,
line: Option<u32>,
col: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
enum ProfileType {
#[serde(rename = "evented")]
Evented,
#[serde(rename = "sampled")]
Sampled,
}
#[derive(Debug, Serialize, Deserialize)]
enum ValueUnit {
#[serde(rename = "bytes")]
Bytes,
#[serde(rename = "microseconds")]
Microseconds,
#[serde(rename = "milliseconds")]
Milliseconds,
#[serde(rename = "nanoseconds")]
Nanoseconds,
#[serde(rename = "none")]
None,
#[serde(rename = "seconds")]
Seconds,
}
impl SpeedscopeFile {
pub fn new(samples: &HashMap<Tid, Vec<Vec<usize>>>, frames: &Vec<Frame>) -> SpeedscopeFile {
let end_value = samples.len();
SpeedscopeFile {
schema: "https://www.speedscope.app/file-format-schema.json".to_string(),
active_profile_index: None,
name: Some("py-spy profile".to_string()),
exporter: Some(format!("py-spy@{}", env!("CARGO_PKG_VERSION"))),
profiles: samples.iter().map(|(_, samples)| {
let weights: Vec<f64> = (&samples).iter().map(|_s| 1_f64).collect();
Profile {
profile_type: ProfileType::Sampled,
name: String::from("py-spy"),
unit: ValueUnit::None,
start_value: 0.0,
end_value: end_value as f64,
samples: samples.clone(),
weights
}
}).collect(),
shared: Shared {
frames: frames.clone()
}
}
}
}
impl Frame {
pub fn new(stack_frame: &stack_trace::Frame, show_line_numbers: bool) -> Frame {
Frame {
name: stack_frame.name.clone(),
file: Some(stack_frame.filename.clone()),
line: if show_line_numbers { Some(stack_frame.line as u32) } else { None },
col: None
}
}
}
pub struct Stats {
samples: HashMap<Tid, Vec<Vec<usize>>>,
frames: Vec<Frame>,
frame_to_index: HashMap<stack_trace::Frame, usize>,
show_line_numbers: bool
}
impl Stats {
pub fn new(show_line_numbers: bool) -> Stats {
Stats {
samples: HashMap::new(),
frames: vec![],
frame_to_index: HashMap::new(),
show_line_numbers
}
}
pub fn record(&mut self, stack: &stack_trace::StackTrace) -> Result<(), io::Error> {
let show_line_numbers = self.show_line_numbers;
let mut frame_indices: Vec<usize> = stack.frames.iter().map(|frame| {
let frames = &mut self.frames;
let mut key = frame.clone();
if !show_line_numbers {
key.line = 0;
}
*self.frame_to_index.entry(key).or_insert_with(|| {
let len = frames.len();
frames.push(Frame::new(&frame, show_line_numbers));
len
})
}).collect();
frame_indices.reverse();
self.samples.entry(stack.thread_id as Tid).or_insert_with(|| {
vec![]
}).push(frame_indices);
Ok(())
}
pub fn write(&self, w: &mut File) -> Result<(), Error> {
let json = serde_json::to_string(&SpeedscopeFile::new(&self.samples, &self.frames))?;
writeln!(w, "{}", json)?;
Ok(())
}
}