use crate::browser::views::PerformanceMetrics;
use crate::error::Result;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct PerformanceManager {
client: Arc<crate::browser::cdp::CdpClient>,
}
impl PerformanceManager {
pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
Self { client }
}
pub async fn get_metrics(&self) -> Result<PerformanceMetrics> {
let _ = self
.client
.send_command("Performance.enable", serde_json::json!({}))
.await;
let result = self
.client
.send_command("Performance.getMetrics", serde_json::json!({}))
.await?;
let metrics = result
.get("metrics")
.and_then(|v| v.as_array())
.ok_or_else(|| crate::error::BrowsingError::Browser("No metrics array".to_string()))?;
let mut raw = HashMap::new();
let mut timestamp = None;
let mut js_heap_used_size = None;
let mut js_heap_total_size = None;
let mut documents = None;
let mut nodes = None;
let mut js_event_listeners = None;
let mut layout_duration = None;
let mut recalc_style_duration = None;
let mut script_duration = None;
let mut task_duration = None;
for m in metrics {
if let Some(name) = m.get("name").and_then(|v| v.as_str())
&& let Some(value) = m.get("value") {
raw.insert(name.to_string(), value.clone());
match name {
"Timestamp" => timestamp = value.as_f64(),
"JSHeapUsedSize" => js_heap_used_size = value.as_u64(),
"JSHeapTotalSize" => js_heap_total_size = value.as_u64(),
"Documents" => documents = value.as_u64().map(|v| v as u32),
"Nodes" => nodes = value.as_u64().map(|v| v as u32),
"JSEventListeners" => js_event_listeners = value.as_u64().map(|v| v as u32),
"LayoutDuration" => layout_duration = value.as_f64(),
"RecalcStyleDuration" => recalc_style_duration = value.as_f64(),
"ScriptDuration" => script_duration = value.as_f64(),
"TaskDuration" => task_duration = value.as_f64(),
_ => {}
}
}
}
Ok(PerformanceMetrics {
timestamp,
js_heap_used_size,
js_heap_total_size,
documents,
nodes,
js_event_listeners,
layout_duration,
recalc_style_duration,
script_duration,
task_duration,
raw,
})
}
pub async fn get_metric_values(&self, names: &[&str]) -> Result<HashMap<String, Option<f64>>> {
let metrics = self.get_metrics().await?;
let mut result = HashMap::new();
for name in names {
let value = metrics.raw.get(*name).and_then(|v| {
v.as_f64().or_else(|| v.as_u64().map(|u| u as f64))
});
result.insert(name.to_string(), value);
}
Ok(result)
}
pub async fn get_heap_usage(&self) -> Result<Option<u64>> {
let metrics = self.get_metrics().await?;
Ok(metrics.js_heap_used_size)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_performance_metrics_default() {
let m = PerformanceMetrics {
timestamp: Some(1234.5),
js_heap_used_size: Some(1024),
js_heap_total_size: Some(2048),
documents: Some(1),
nodes: Some(100),
js_event_listeners: Some(5),
layout_duration: Some(0.01),
recalc_style_duration: None,
script_duration: None,
task_duration: None,
raw: HashMap::new(),
};
assert_eq!(m.timestamp, Some(1234.5));
assert_eq!(m.js_heap_used_size, Some(1024));
assert_eq!(m.documents, Some(1));
}
#[test]
fn test_performance_metrics_serialization() {
let m = PerformanceMetrics {
timestamp: Some(1.0),
js_heap_used_size: Some(1024),
js_heap_total_size: Some(2048),
documents: Some(1),
nodes: Some(50),
js_event_listeners: Some(3),
layout_duration: None,
recalc_style_duration: None,
script_duration: None,
task_duration: None,
raw: HashMap::new(),
};
let json = serde_json::to_string(&m).unwrap();
assert!(json.contains("1024"));
assert!(json.contains("50"));
}
#[test]
fn test_performance_metrics_deserialization() {
let json = r#"{"timestamp": 1.5, "js_heap_used_size": 512, "nodes": 25, "raw": {}}"#;
let m: PerformanceMetrics = serde_json::from_str(json).unwrap();
assert_eq!(m.timestamp, Some(1.5));
assert_eq!(m.js_heap_used_size, Some(512));
assert_eq!(m.nodes, Some(25));
}
}