1use std::collections::HashMap;
7use std::fs::OpenOptions;
8use std::io;
9use std::path::{Path, PathBuf};
10use std::time::Instant;
11
12use anyhow::{Context, Result};
13
14use crate::phase::ExecutionPhase;
15use crate::runtime::{
16 EdgeTimingObservation, OperationObservation, RunMetadata, RuntimeTraceWriter, SCHEMA_V3,
17};
18
19#[derive(Debug, Clone)]
21pub struct ProfileConfig {
22 pub entrypoint: String,
23 pub phase: ExecutionPhase,
24 pub profile: String,
25 pub cargo_features: Vec<String>,
26 pub analysis_id: Option<String>,
27 pub build_id: Option<String>,
28}
29
30impl ProfileConfig {
31 pub fn train(entrypoint: impl Into<String>) -> Self {
32 Self {
33 entrypoint: entrypoint.into(),
34 phase: ExecutionPhase::Train,
35 profile: "release".into(),
36 cargo_features: Vec::new(),
37 analysis_id: None,
38 build_id: None,
39 }
40 }
41
42 pub fn infer(entrypoint: impl Into<String>) -> Self {
43 Self {
44 entrypoint: entrypoint.into(),
45 phase: ExecutionPhase::Infer,
46 profile: "release".into(),
47 cargo_features: Vec::new(),
48 analysis_id: None,
49 build_id: None,
50 }
51 }
52}
53
54struct ActiveOp {
55 static_id: String,
56 op: String,
57 inputs: Vec<String>,
58 started: Instant,
59}
60
61pub struct ProfileSession {
63 path: PathBuf,
64 writer: RuntimeTraceWriter<io::BufWriter<std::fs::File>>,
65 phase: ExecutionPhase,
66 step: u64,
67 active: HashMap<String, ActiveOp>,
68 event_counter: u64,
69}
70
71impl ProfileSession {
72 pub fn open(path: impl AsRef<Path>, config: ProfileConfig) -> Result<Self> {
74 let path = path.as_ref().to_path_buf();
75 if let Some(parent) = path.parent() {
76 std::fs::create_dir_all(parent)
77 .with_context(|| format!("create profile dir {}", parent.display()))?;
78 }
79 let file = OpenOptions::new()
80 .create(true)
81 .write(true)
82 .truncate(true)
83 .open(&path)
84 .with_context(|| format!("open profile trace {}", path.display()))?;
85 let run = RunMetadata {
86 entrypoint: config.entrypoint,
87 profile: config.profile,
88 cargo_features: config.cargo_features,
89 cfg: Vec::new(),
90 analysis_id: config.analysis_id,
91 build_id: config.build_id,
92 phase: Some(config.phase.as_str().to_string()),
93 };
94 let writer = RuntimeTraceWriter::new_with_schema(io::BufWriter::new(file), SCHEMA_V3, run)?;
95 Ok(Self {
96 path,
97 writer,
98 phase: config.phase,
99 step: 0,
100 active: HashMap::new(),
101 event_counter: 0,
102 })
103 }
104
105 pub fn phase(&self) -> ExecutionPhase {
106 self.phase
107 }
108
109 pub fn step(&self) -> u64 {
110 self.step
111 }
112
113 pub fn set_step(&mut self, step: u64) {
114 self.step = step;
115 }
116
117 pub fn begin_operation(
119 &mut self,
120 static_id: impl Into<String>,
121 op: impl Into<String>,
122 inputs: &[String],
123 ) -> Result<String> {
124 self.event_counter += 1;
125 let event_id = format!("{}-op-{}-{}", self.phase.as_str(), self.step, self.event_counter);
126 self.active.insert(
127 event_id.clone(),
128 ActiveOp {
129 static_id: static_id.into(),
130 op: op.into(),
131 inputs: inputs.to_vec(),
132 started: Instant::now(),
133 },
134 );
135 Ok(event_id)
136 }
137
138 pub fn end_operation(&mut self, event_id: &str, output: Option<String>) -> Result<u64> {
140 let active = self
141 .active
142 .remove(event_id)
143 .with_context(|| format!("unknown profile operation `{event_id}`"))?;
144 let duration_ns = active.started.elapsed().as_nanos().min(u64::MAX as u128) as u64;
145 self.writer.operation(OperationObservation {
146 event_id: event_id.to_string(),
147 op: active.op,
148 static_id: Some(active.static_id),
149 source: None,
150 inputs: active.inputs,
151 output,
152 step: Some(self.step),
153 duration_ns: Some(duration_ns),
154 })?;
155 Ok(duration_ns)
156 }
157
158 pub fn record_edge_timing(
160 &mut self,
161 from_static_id: impl Into<String>,
162 to_static_id: impl Into<String>,
163 duration_ns: u64,
164 ) -> Result<()> {
165 self.event_counter += 1;
166 let event_id = format!(
167 "{}-edge-{}-{}",
168 self.phase.as_str(),
169 self.step,
170 self.event_counter
171 );
172 self.writer.edge_timing(EdgeTimingObservation {
173 event_id,
174 from_static_id: from_static_id.into(),
175 to_static_id: to_static_id.into(),
176 duration_ns,
177 step: Some(self.step),
178 })
179 }
180
181 pub fn flush(&mut self) -> Result<()> {
182 self.writer.flush()
183 }
184
185 pub fn finish(self) -> Result<PathBuf> {
186 self.writer.finish()?;
187 Ok(self.path)
188 }
189}
190
191pub struct TimedOperation<'a> {
193 session: &'a mut ProfileSession,
194 event_id: String,
195}
196
197impl<'a> TimedOperation<'a> {
198 pub fn begin(
199 session: &'a mut ProfileSession,
200 static_id: impl Into<String>,
201 op: impl Into<String>,
202 inputs: &[String],
203 ) -> Result<Self> {
204 let event_id = session.begin_operation(static_id, op, inputs)?;
205 Ok(Self { session, event_id })
206 }
207}
208
209impl Drop for TimedOperation<'_> {
210 fn drop(&mut self) {
211 let _ = self.session.end_operation(&self.event_id, None);
212 }
213}