bgustscraper 0.2.0

Advanced semantic scraping engine with AI-driven compliance checks and legal terms validation
Documentation
use anyhow::Result;
use std::time::Instant;
use std::collections::HashSet;
use sysinfo::System;
use crate::worker::{PlaywrightWorker, ScrapingEngine, ScrapeOptions, ScrapeResponse};
use crate::cache::LocalCache;
use crate::clustering;
use crate::extractor;
use crate::semantic_extractor;

pub fn run_benchmark() -> Result<()> {
    println!("\n=======================================================");
    println!("📊 INICIANDO BENCHMARK INTEGRAL DE RECURSOS - bgustscraper");
    println!("=======================================================\n");

    let test_url = "https://example.com";
    let test_prompt = "Extrae el titulo de la pagina y la descripcion basica";

    let mut sys = System::new_all();
    
    // -------------------------------------------------------
    // ESCENARIO 1: Sintáctico Puro (Tradicional HTML/Regex)
    // -------------------------------------------------------
    println!("🧪 [ESCENARIO 1] Iniciando Scraping Sintáctico Puro...");
    let start_time_1 = Instant::now();
    
    let mut worker_1 = PlaywrightWorker::new(true, ScrapingEngine::Playwright)?;
    let scrape_opts_1 = ScrapeOptions {
        deep_scan: false,
        semantic_embeddings: None,
        text: None,
        prompt: None,
    };
    
    let response_1 = worker_1.scrape(test_url, Some(scrape_opts_1))?;
    let mut _docs_1 = Vec::new();
    if response_1.status == "success" {
        let html = response_1.html.unwrap();
        _docs_1 = extractor::find_documents(&html, test_url);
    }
    
    let ram_peak_1 = get_ecosystem_ram_usage(&mut sys)?;
    worker_1.exit()?;
    let duration_1 = start_time_1.elapsed();
    
    println!("✅ Escenario 1 Completado.");
    println!("   ↳ Tiempo: {:.2?}", duration_1);
    println!("   ↳ RAM Pico Ecosistema: {:.2} MB", ram_peak_1);

    // Limpiar caché antes del Escenario 2 para forzar renderizado completo y ONNX
    let cache = LocalCache::new()?;
    let cache_key = format!("scrape:{}", test_url);
    let _ = cache.set(&cache_key, ""); // Borrar caché

    // -------------------------------------------------------
    // ESCENARIO 2: Semántico Completo (Embeddings, Clustering K-Means, Qwen)
    // -------------------------------------------------------
    println!("\n🧪 [ESCENARIO 2] Iniciando Scraping Semántico Completo (Modelos Locales)...");
    let start_time_2 = Instant::now();
    
    let mut worker_2 = PlaywrightWorker::new(true, ScrapingEngine::Playwright)?;
    let scrape_opts_2 = ScrapeOptions {
        deep_scan: false,
        semantic_embeddings: Some(true),
        text: None,
        prompt: None,
    };
    
    let response_2 = worker_2.scrape(test_url, Some(scrape_opts_2))?;
    
    let mut _docs_2 = Vec::new();
    if response_2.status == "success" {
        let markdown = response_2.markdown.clone().unwrap_or_default();
        
        let compressed_text = if let (Some(sentences), Some(embeddings)) = (&response_2.sentences, &response_2.embeddings) {
            let summary_sentences = clustering::compress_text_by_clustering(sentences, embeddings, 12);
            summary_sentences.join("\n")
        } else {
            markdown.chars().take(2000).collect()
        };

        // Simular llamada a extracción estructurada genérica
        let extract_opts = ScrapeOptions {
            deep_scan: false,
            semantic_embeddings: None,
            text: Some(compressed_text.clone()),
            prompt: Some(test_prompt.to_string()),
        };
        let _extract_resp = worker_2.scrape("extract", Some(extract_opts))?;
        
        // Simular extracción semántica de documentos
        if let Ok(sem_docs) = semantic_extractor::extract_semantic_documents(&mut worker_2, &compressed_text, test_url) {
            _docs_2 = sem_docs;
        }
    }
    
    let ram_peak_2 = get_ecosystem_ram_usage(&mut sys)?;
    
    // Guardar respuesta completa en caché para Escenario 3
    if response_2.status == "success" {
        let resp_json = serde_json::to_string(&response_2)?;
        cache.set(&cache_key, &resp_json)?;
    }
    
    worker_2.exit()?;
    let duration_2 = start_time_2.elapsed();
    
    println!("✅ Escenario 2 Completado.");
    println!("   ↳ Tiempo: {:.2?}", duration_2);
    println!("   ↳ RAM Pico Ecosistema: {:.2} MB", ram_peak_2);

    // -------------------------------------------------------
    // ESCENARIO 3: Caché Embebida (Lectura persistente en redb)
    // -------------------------------------------------------
    println!("\n🧪 [ESCENARIO 3] Iniciando Scraping de Caché Embebida (redb)...");
    let start_time_3 = Instant::now();
    
    let mut worker_3 = PlaywrightWorker::new(true, ScrapingEngine::Playwright)?;
    
    let response_3 = match cache.get(&cache_key) {
        Ok(Some(cached_json)) => {
            serde_json::from_str::<ScrapeResponse>(&cached_json)?
        }
        _ => {
            return Err(anyhow::anyhow!("Fallo al recuperar los datos de la caché para el escenario 3"));
        }
    };
    
    if response_3.status == "success" {
        let markdown = response_3.markdown.unwrap_or_default();
        let compressed_text = if let (Some(sentences), Some(embeddings)) = (&response_3.sentences, &response_3.embeddings) {
            let summary_sentences = clustering::compress_text_by_clustering(sentences, embeddings, 12);
            summary_sentences.join("\n")
        } else {
            markdown.chars().take(2000).collect()
        };

        // Simular llamada a extracción desde texto cacheado
        let extract_opts = ScrapeOptions {
            deep_scan: false,
            semantic_embeddings: None,
            text: Some(compressed_text.clone()),
            prompt: Some(test_prompt.to_string()),
        };
        let _extract_resp = worker_3.scrape("extract", Some(extract_opts))?;
    }
    
    let ram_peak_3 = get_ecosystem_ram_usage(&mut sys)?;
    worker_3.exit()?;
    let duration_3 = start_time_3.elapsed();
    
    println!("✅ Escenario 3 Completado.");
    println!("   ↳ Tiempo: {:.2?}", duration_3);
    println!("   ↳ RAM Pico Ecosistema: {:.2} MB", ram_peak_3);

    // -------------------------------------------------------
    // REPORTE COMPARATIVO FINAL
    // -------------------------------------------------------
    println!("\n=======================================================");
    println!("📊 REPORT COMPARATIVO DE SCRAPING Y RECURSOS");
    println!("=======================================================");
    println!("Escenario 1 (Sintáctico):  Tiempo: {:.2?}, RAM Pico: {:.2} MB", duration_1, ram_peak_1);
    println!("Escenario 2 (Semántico):   Tiempo: {:.2?}, RAM Pico: {:.2} MB", duration_2, ram_peak_2);
    println!("Escenario 3 (Caché redb):  Tiempo: {:.2?}, RAM Pico: {:.2} MB", duration_3, ram_peak_3);
    println!("=======================================================\n");

    Ok(())
}

/// Suma el uso de RAM del proceso Rust actual y todos sus procesos hijos (Playwright Chromium, Node).
fn get_ecosystem_ram_usage(sys: &mut System) -> Result<f64> {
    sys.refresh_all();
    let current_pid = sysinfo::get_current_pid().map_err(|e| anyhow::anyhow!("No se pudo obtener el PID actual: {}", e))?;
    
    let mut tracked_pids = HashSet::new();
    tracked_pids.insert(current_pid);
    
    // Encontrar recursivamente todos los descendientes (hijos y nietos de procesos)
    let mut added_any = true;
    while added_any {
        added_any = false;
        for (pid, process) in sys.processes() {
            if let Some(parent_pid) = process.parent() {
                if tracked_pids.contains(&parent_pid) && !tracked_pids.contains(pid) {
                    tracked_pids.insert(*pid);
                    added_any = true;
                }
            }
        }
    }
    
    let mut total_memory_bytes = 0;
    for pid in tracked_pids {
        if let Some(proc) = sys.process(pid) {
            total_memory_bytes += proc.memory();
        }
    }
    
    // sysinfo retorna bytes. Convertir bytes a Megabytes
    let memory_mb = total_memory_bytes as f64 / 1024.0 / 1024.0;
    Ok(memory_mb)
}