use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io;
use std::path::{Path, PathBuf};
use std::time::Instant;
use anyhow::{Context, Result};
use crate::phase::ExecutionPhase;
use crate::runtime::{
EdgeTimingObservation, OperationObservation, RunMetadata, RuntimeTraceWriter, SCHEMA_V3,
};
#[derive(Debug, Clone)]
pub struct ProfileConfig {
pub entrypoint: String,
pub phase: ExecutionPhase,
pub profile: String,
pub cargo_features: Vec<String>,
pub analysis_id: Option<String>,
pub build_id: Option<String>,
}
impl ProfileConfig {
pub fn train(entrypoint: impl Into<String>) -> Self {
Self {
entrypoint: entrypoint.into(),
phase: ExecutionPhase::Train,
profile: "release".into(),
cargo_features: Vec::new(),
analysis_id: None,
build_id: None,
}
}
pub fn infer(entrypoint: impl Into<String>) -> Self {
Self {
entrypoint: entrypoint.into(),
phase: ExecutionPhase::Infer,
profile: "release".into(),
cargo_features: Vec::new(),
analysis_id: None,
build_id: None,
}
}
}
struct ActiveOp {
static_id: String,
op: String,
inputs: Vec<String>,
started: Instant,
}
pub struct ProfileSession {
path: PathBuf,
writer: RuntimeTraceWriter<io::BufWriter<std::fs::File>>,
phase: ExecutionPhase,
step: u64,
active: HashMap<String, ActiveOp>,
event_counter: u64,
}
impl ProfileSession {
pub fn open(path: impl AsRef<Path>, config: ProfileConfig) -> Result<Self> {
let path = path.as_ref().to_path_buf();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("create profile dir {}", parent.display()))?;
}
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&path)
.with_context(|| format!("open profile trace {}", path.display()))?;
let run = RunMetadata {
entrypoint: config.entrypoint,
profile: config.profile,
cargo_features: config.cargo_features,
cfg: Vec::new(),
analysis_id: config.analysis_id,
build_id: config.build_id,
phase: Some(config.phase.as_str().to_string()),
};
let writer = RuntimeTraceWriter::new_with_schema(io::BufWriter::new(file), SCHEMA_V3, run)?;
Ok(Self {
path,
writer,
phase: config.phase,
step: 0,
active: HashMap::new(),
event_counter: 0,
})
}
pub fn phase(&self) -> ExecutionPhase {
self.phase
}
pub fn step(&self) -> u64 {
self.step
}
pub fn set_step(&mut self, step: u64) {
self.step = step;
}
pub fn begin_operation(
&mut self,
static_id: impl Into<String>,
op: impl Into<String>,
inputs: &[String],
) -> Result<String> {
self.event_counter += 1;
let event_id = format!("{}-op-{}-{}", self.phase.as_str(), self.step, self.event_counter);
self.active.insert(
event_id.clone(),
ActiveOp {
static_id: static_id.into(),
op: op.into(),
inputs: inputs.to_vec(),
started: Instant::now(),
},
);
Ok(event_id)
}
pub fn end_operation(&mut self, event_id: &str, output: Option<String>) -> Result<u64> {
let active = self
.active
.remove(event_id)
.with_context(|| format!("unknown profile operation `{event_id}`"))?;
let duration_ns = active.started.elapsed().as_nanos().min(u64::MAX as u128) as u64;
self.writer.operation(OperationObservation {
event_id: event_id.to_string(),
op: active.op,
static_id: Some(active.static_id),
source: None,
inputs: active.inputs,
output,
step: Some(self.step),
duration_ns: Some(duration_ns),
})?;
Ok(duration_ns)
}
pub fn record_edge_timing(
&mut self,
from_static_id: impl Into<String>,
to_static_id: impl Into<String>,
duration_ns: u64,
) -> Result<()> {
self.event_counter += 1;
let event_id = format!(
"{}-edge-{}-{}",
self.phase.as_str(),
self.step,
self.event_counter
);
self.writer.edge_timing(EdgeTimingObservation {
event_id,
from_static_id: from_static_id.into(),
to_static_id: to_static_id.into(),
duration_ns,
step: Some(self.step),
})
}
pub fn flush(&mut self) -> Result<()> {
self.writer.flush()
}
pub fn finish(self) -> Result<PathBuf> {
self.writer.finish()?;
Ok(self.path)
}
}
pub struct TimedOperation<'a> {
session: &'a mut ProfileSession,
event_id: String,
}
impl<'a> TimedOperation<'a> {
pub fn begin(
session: &'a mut ProfileSession,
static_id: impl Into<String>,
op: impl Into<String>,
inputs: &[String],
) -> Result<Self> {
let event_id = session.begin_operation(static_id, op, inputs)?;
Ok(Self { session, event_id })
}
}
impl Drop for TimedOperation<'_> {
fn drop(&mut self) {
let _ = self.session.end_operation(&self.event_id, None);
}
}