use crate::browser::views::{TraceConfig, TraceResult};
use crate::error::Result;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct TracingManager {
client: Arc<crate::browser::cdp::CdpClient>,
events: Arc<Mutex<Vec<serde_json::Value>>>,
start_time: Arc<Mutex<Option<Instant>>>,
}
impl TracingManager {
pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
Self {
client,
events: Arc::new(Mutex::new(Vec::new())),
start_time: Arc::new(Mutex::new(None)),
}
}
pub async fn start(&self, config: Option<TraceConfig>) -> Result<()> {
let config = config.unwrap_or_else(|| TraceConfig {
categories: vec![
"devtools.timeline".to_string(),
"disabled-by-default-devtools.timeline".to_string(),
"v8.execute".to_string(),
"disabled-by-default-v8.cpu_profiler".to_string(),
],
streaming: Some(true),
buffer_size: None,
});
let mut params = serde_json::json!({
"categories": config.categories.join(","),
"transferMode": "ReturnAsStream",
});
if let Some(true) = config.streaming {
params["transferMode"] = serde_json::json!("ReturnAsStream");
}
if let Some(size) = config.buffer_size {
params["bufferUsageReportingInterval"] = serde_json::json!(size);
}
self.client
.send_command("Tracing.start", params)
.await?;
*self.start_time.lock().await = Some(Instant::now());
let events = Arc::clone(&self.events);
let mut rx = self.client.subscribe_events().await?;
tokio::spawn(async move {
while let Ok(event) = rx.recv().await {
match event.method.as_str() {
"Tracing.dataCollected" => {
if let Some(value) = event.params.get("value")
&& let Some(arr) = value.as_array() {
events.lock().await.extend(arr.iter().cloned());
}
}
"Tracing.tracingComplete" => break,
_ => {}
}
}
});
Ok(())
}
pub async fn end(&self) -> Result<TraceResult> {
let start = self.start_time.lock().await.take();
let elapsed = start.map(|s| s.elapsed().as_secs_f64());
self.client
.send_command("Tracing.end", serde_json::json!({}))
.await?;
tokio::time::sleep(Duration::from_millis(200)).await;
let events = self.events.lock().await.clone();
Ok(TraceResult {
success: !events.is_empty(),
events,
start_time: None,
end_time: elapsed,
})
}
pub async fn export_trace(&self, path: &std::path::Path, result: &TraceResult) -> Result<()> {
let trace_json = serde_json::json!(result.events);
let content = serde_json::to_string_pretty(&trace_json)?;
tokio::fs::write(path, content).await?;
Ok(())
}
pub async fn event_count(&self) -> usize {
self.events.lock().await.len()
}
pub async fn clear(&self) {
self.events.lock().await.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trace_config_default() {
let config = TraceConfig::default();
assert!(config.categories.is_empty());
assert_eq!(config.streaming, None);
assert_eq!(config.buffer_size, None);
}
#[test]
fn test_trace_config_custom() {
let config = TraceConfig {
categories: vec!["devtools.timeline".to_string()],
streaming: Some(true),
buffer_size: Some(1024 * 1024),
};
assert_eq!(config.categories.len(), 1);
assert!(config.streaming.unwrap());
}
#[test]
fn test_trace_result_creation() {
let result = TraceResult {
success: true,
events: vec![],
start_time: None,
end_time: Some(1.5),
};
assert!(result.success);
assert_eq!(result.end_time, Some(1.5));
}
#[test]
fn test_trace_result_serialization() {
let result = TraceResult {
success: true,
events: vec![serde_json::json!({"name": "test"})],
start_time: None,
end_time: Some(0.5),
};
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("test"));
}
}