use std::collections::HashMap;
use std::io::Write as _;
use flate2::write::GzEncoder;
use flate2::Compression;
use prost::Message as _;
use crate::profile::pprof::{Function, Label, Line, Location, Profile, Sample, ValueType};
use crate::profile::SessionResult;
#[derive(Debug, Default)]
struct StringTable {
strings: Vec<String>,
index: HashMap<String, i64>,
}
impl StringTable {
fn new() -> Self {
let mut table = Self::default();
table.intern("");
table
}
fn intern(&mut self, value: &str) -> i64 {
if let Some(existing) = self.index.get(value) {
return *existing;
}
let position = self.strings.len() as i64;
self.strings.push(value.to_string());
self.index.insert(value.to_string(), position);
position
}
}
pub fn to_pprof_bytes(result: &SessionResult) -> Vec<u8> {
build(result).encode_to_vec()
}
pub fn to_pprof_gzip(result: &SessionResult) -> std::io::Result<Vec<u8>> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&to_pprof_bytes(result))?;
encoder.finish()
}
pub fn build(result: &SessionResult) -> Profile {
let mut strings = StringTable::new();
let samples_type = ValueType {
r#type: strings.intern("samples"),
unit: strings.intern("count"),
};
let cpu_type = ValueType {
r#type: strings.intern("cpu"),
unit: strings.intern("nanoseconds"),
};
let mut functions: Vec<Function> = Vec::new();
let mut function_ids: HashMap<String, u64> = HashMap::new();
let mut locations: Vec<Location> = Vec::new();
let mut location_ids: HashMap<String, u64> = HashMap::new();
let mut samples: Vec<Sample> = Vec::new();
let period = result.period_nanos as i64;
for (stack, count) in result.folded() {
let mut location_id: Vec<u64> = Vec::with_capacity(stack.len());
for frame in stack.iter().rev() {
let function_id = *function_ids.entry(frame.clone()).or_insert_with(|| {
let id = functions.len() as u64 + 1;
let name = strings.intern(frame);
functions.push(Function {
id,
name,
system_name: name,
filename: 0,
start_line: 0,
});
id
});
let id = *location_ids.entry(frame.clone()).or_insert_with(|| {
let id = locations.len() as u64 + 1;
locations.push(Location {
id,
mapping_id: 0,
address: 0,
line: vec![Line {
function_id,
line: 0,
}],
is_folded: false,
});
id
});
location_id.push(id);
}
samples.push(Sample {
location_id,
value: vec![count as i64, count as i64 * period],
label: Vec::new(),
});
}
Profile {
sample_type: vec![samples_type, cpu_type],
sample: samples,
mapping: Vec::new(),
location: locations,
function: functions,
string_table: strings.strings,
drop_frames: 0,
keep_frames: 0,
time_nanos: result.start_unix_nanos,
duration_nanos: result.metrics.duration_nanos as i64,
period_type: Some(cpu_type),
period,
comment: Vec::new(),
default_sample_type: 0,
}
}
#[derive(Debug, Default)]
pub struct AsyncProfileBuilder {
strings: StringTable,
functions: Vec<Function>,
function_ids: HashMap<String, u64>,
locations: Vec<Location>,
location_ids: HashMap<String, u64>,
samples: Vec<Sample>,
}
impl AsyncProfileBuilder {
pub fn new() -> Self {
Self {
strings: StringTable::new(),
..Self::default()
}
}
pub fn add_sample(&mut self, stack: &[String], values: [i64; 5], task_name: &str) {
let mut location_id = Vec::with_capacity(stack.len());
for frame in stack.iter().rev() {
let function_id = match self.function_ids.get(frame) {
Some(id) => *id,
None => {
let id = self.functions.len() as u64 + 1;
let name = self.strings.intern(frame);
self.functions.push(Function {
id,
name,
system_name: name,
filename: 0,
start_line: 0,
});
self.function_ids.insert(frame.clone(), id);
id
}
};
let id = match self.location_ids.get(frame) {
Some(id) => *id,
None => {
let id = self.locations.len() as u64 + 1;
self.locations.push(Location {
id,
mapping_id: 0,
address: 0,
line: vec![Line {
function_id,
line: 0,
}],
is_folded: false,
});
self.location_ids.insert(frame.clone(), id);
id
}
};
location_id.push(id);
}
let label = if task_name.is_empty() {
Vec::new()
} else {
vec![Label {
key: self.strings.intern("task"),
str: self.strings.intern(task_name),
num: 0,
num_unit: 0,
}]
};
self.samples.push(Sample {
location_id,
value: values.to_vec(),
label,
});
}
pub fn finish(mut self) -> Vec<u8> {
let nanoseconds = self.strings.intern("nanoseconds");
let count = self.strings.intern("count");
let sample_type = vec![
ValueType {
r#type: self.strings.intern("idle"),
unit: nanoseconds,
},
ValueType {
r#type: self.strings.intern("busy"),
unit: nanoseconds,
},
ValueType {
r#type: self.strings.intern("scheduled"),
unit: nanoseconds,
},
ValueType {
r#type: self.strings.intern("polls"),
unit: count,
},
ValueType {
r#type: self.strings.intern("wakes"),
unit: count,
},
];
let default_sample_type = sample_type[0].r#type;
Profile {
sample_type,
sample: self.samples,
mapping: Vec::new(),
location: self.locations,
function: self.functions,
string_table: self.strings.strings,
drop_frames: 0,
keep_frames: 0,
time_nanos: 0,
duration_nanos: 0,
period_type: None,
period: 0,
comment: Vec::new(),
default_sample_type,
}
.encode_to_vec()
}
}