browsing 0.1.7

Browser automation: navigate, click, extract, screenshot. Standalone browser control via CDP.
Documentation
//! Performance metrics via CDP Performance domain

use crate::browser::views::PerformanceMetrics;
use crate::error::Result;
use std::collections::HashMap;
use std::sync::Arc;

/// Manages browser performance metrics collection
#[derive(Debug, Clone)]
pub struct PerformanceManager {
    client: Arc<crate::browser::cdp::CdpClient>,
}

impl PerformanceManager {
    /// Create a new performance manager
    pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
        Self { client }
    }

    /// Get current performance metrics.
    /// Uses CDP `Performance.getMetrics` after enabling the domain.
    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,
        })
    }

    /// Get metrics for a specific set of known metric names
    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)
    }

    /// Convenience: get JS heap usage only
    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));
    }
}