use super::Adapter;
use crate::span::{Category, Span, Status};
use serde::Deserialize;
use serde_json::Value;
use std::collections::BTreeMap;
pub struct Generic;
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum TraceInput {
Wrapped {
#[serde(rename = "traceEvents")]
trace_events: Vec<RawEvent>,
},
Bare(Vec<RawEvent>),
}
#[derive(Debug, Deserialize, Clone)]
struct RawEvent {
ph: String,
#[serde(default)]
name: String,
#[serde(default)]
cat: Option<String>,
#[serde(default)]
ts: f64,
#[serde(default)]
dur: Option<f64>,
#[serde(default)]
pid: i64,
#[serde(default)]
tid: i64,
#[serde(default)]
args: Option<Value>,
}
impl Adapter for Generic {
fn name(&self) -> &'static str {
"generic"
}
fn parse(&self, input: &[u8]) -> anyhow::Result<Vec<Span>> {
let parsed: TraceInput = serde_json::from_slice(input)?;
let events = match parsed {
TraceInput::Wrapped { trace_events } => trace_events,
TraceInput::Bare(events) => events,
};
let track_names = process_names(&events);
let spans = events
.iter()
.filter(|e| e.ph == "X")
.filter_map(|e| {
let dur_us = e.dur?;
Some(Span {
name: e.name.clone(),
category: classify(e),
status: Status::Success,
track: track_names
.get(&e.pid)
.cloned()
.unwrap_or_else(|| "generic".to_string()),
lane: e.tid.max(0) as u32,
start_us: e.ts.round() as i64,
dur_us: dur_us.round() as i64,
args: string_args(e.args.as_ref()),
})
})
.collect();
Ok(spans)
}
}
fn process_names(events: &[RawEvent]) -> BTreeMap<i64, String> {
events
.iter()
.filter(|e| e.ph == "M" && e.name == "process_name")
.filter_map(|e| {
let name = e.args.as_ref()?.get("name")?.as_str()?.to_string();
Some((e.pid, name))
})
.collect()
}
fn classify(e: &RawEvent) -> Category {
Category::from(e.cat.as_deref().unwrap_or(&e.name))
}
fn string_args(args: Option<&Value>) -> BTreeMap<String, String> {
let mut out = BTreeMap::new();
if let Some(Value::Object(map)) = args {
for (k, v) in map {
let s = match v {
Value::String(s) => s.clone(),
other => other.to_string(),
};
out.insert(k.clone(), s);
}
}
out
}