use serde::Serialize;
use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU8, AtomicU64, Ordering::Relaxed};
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ScanStage {
Queued = 0,
Discovering = 1,
Indexing = 2,
LoadingSummaries = 3,
BuildingCallGraph = 4,
Analyzing = 5,
PostProcessing = 6,
Complete = 7,
}
impl ScanStage {
fn as_str(self) -> &'static str {
match self {
Self::Queued => "queued",
Self::Discovering => "discovering",
Self::Indexing => "indexing",
Self::LoadingSummaries => "loading_summaries",
Self::BuildingCallGraph => "building_call_graph",
Self::Analyzing => "analyzing",
Self::PostProcessing => "post_processing",
Self::Complete => "complete",
}
}
}
#[derive(Debug)]
pub struct ScanProgress {
stage: AtomicU8,
files_discovered: AtomicU64,
files_parsed: AtomicU64,
files_analyzed: AtomicU64,
files_skipped: AtomicU64,
batches_total: AtomicU64,
batches_completed: AtomicU64,
current_file: Mutex<String>,
started_at: Instant,
walk_ms: AtomicU64,
pass1_ms: AtomicU64,
call_graph_ms: AtomicU64,
pass2_ms: AtomicU64,
post_process_ms: AtomicU64,
languages: Mutex<HashMap<String, u64>>,
}
impl Default for ScanProgress {
fn default() -> Self {
Self::new()
}
}
impl ScanProgress {
pub fn new() -> Self {
Self {
stage: AtomicU8::new(ScanStage::Queued as u8),
files_discovered: AtomicU64::new(0),
files_parsed: AtomicU64::new(0),
files_analyzed: AtomicU64::new(0),
files_skipped: AtomicU64::new(0),
batches_total: AtomicU64::new(0),
batches_completed: AtomicU64::new(0),
current_file: Mutex::new(String::new()),
started_at: Instant::now(),
walk_ms: AtomicU64::new(0),
pass1_ms: AtomicU64::new(0),
call_graph_ms: AtomicU64::new(0),
pass2_ms: AtomicU64::new(0),
post_process_ms: AtomicU64::new(0),
languages: Mutex::new(HashMap::new()),
}
}
pub fn set_stage(&self, stage: ScanStage) {
self.stage.store(stage as u8, Relaxed);
}
pub fn set_files_discovered(&self, count: u64) {
self.files_discovered.store(count, Relaxed);
}
pub fn inc_parsed(&self, n: u64) {
self.files_parsed.fetch_add(n, Relaxed);
}
pub fn inc_analyzed(&self, n: u64) {
self.files_analyzed.fetch_add(n, Relaxed);
}
pub fn set_files_skipped(&self, count: u64) {
self.files_skipped.store(count, Relaxed);
}
pub fn inc_skipped(&self, n: u64) {
self.files_skipped.fetch_add(n, Relaxed);
}
pub fn set_batches_total(&self, count: u64) {
self.batches_total.store(count, Relaxed);
}
pub fn inc_batches_completed(&self, n: u64) {
self.batches_completed.fetch_add(n, Relaxed);
}
pub fn set_current_file(&self, path: &str) {
if let Ok(mut f) = self.current_file.try_lock() {
f.clear();
f.push_str(path);
}
}
pub fn elapsed_ms(&self) -> u64 {
self.started_at.elapsed().as_millis() as u64
}
pub fn record_walk_ms(&self, ms: u64) {
self.walk_ms.fetch_add(ms, Relaxed);
}
pub fn record_pass1_ms(&self, ms: u64) {
self.pass1_ms.fetch_add(ms, Relaxed);
}
pub fn record_call_graph_ms(&self, ms: u64) {
self.call_graph_ms.fetch_add(ms, Relaxed);
}
pub fn record_pass2_ms(&self, ms: u64) {
self.pass2_ms.fetch_add(ms, Relaxed);
}
pub fn record_post_process_ms(&self, ms: u64) {
self.post_process_ms.fetch_add(ms, Relaxed);
}
pub fn record_language(&self, lang: &str) {
if let Ok(mut langs) = self.languages.try_lock() {
*langs.entry(lang.to_string()).or_insert(0) += 1;
}
}
pub fn snapshot(&self) -> ScanProgressSnapshot {
let stage = match self.stage.load(Relaxed) {
x if x == ScanStage::Queued as u8 => ScanStage::Queued.as_str(),
x if x == ScanStage::Discovering as u8 => ScanStage::Discovering.as_str(),
x if x == ScanStage::Indexing as u8 => ScanStage::Indexing.as_str(),
x if x == ScanStage::LoadingSummaries as u8 => ScanStage::LoadingSummaries.as_str(),
x if x == ScanStage::BuildingCallGraph as u8 => ScanStage::BuildingCallGraph.as_str(),
x if x == ScanStage::Analyzing as u8 => ScanStage::Analyzing.as_str(),
x if x == ScanStage::PostProcessing as u8 => ScanStage::PostProcessing.as_str(),
x if x == ScanStage::Complete as u8 => ScanStage::Complete.as_str(),
_ => "unknown",
}
.to_string();
let current_file = self
.current_file
.try_lock()
.map(|f| f.clone())
.unwrap_or_default();
let languages = self
.languages
.try_lock()
.map(|l| l.clone())
.unwrap_or_default();
ScanProgressSnapshot {
stage,
files_discovered: self.files_discovered.load(Relaxed),
files_parsed: self.files_parsed.load(Relaxed),
files_analyzed: self.files_analyzed.load(Relaxed),
files_skipped: self.files_skipped.load(Relaxed),
batches_total: self.batches_total.load(Relaxed),
batches_completed: self.batches_completed.load(Relaxed),
current_file,
elapsed_ms: self.elapsed_ms(),
timing: TimingBreakdown {
walk_ms: self.walk_ms.load(Relaxed),
pass1_ms: self.pass1_ms.load(Relaxed),
call_graph_ms: self.call_graph_ms.load(Relaxed),
pass2_ms: self.pass2_ms.load(Relaxed),
post_process_ms: self.post_process_ms.load(Relaxed),
},
languages,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ScanProgressSnapshot {
pub stage: String,
pub files_discovered: u64,
pub files_parsed: u64,
pub files_analyzed: u64,
pub files_skipped: u64,
pub batches_total: u64,
pub batches_completed: u64,
pub current_file: String,
pub elapsed_ms: u64,
pub timing: TimingBreakdown,
pub languages: HashMap<String, u64>,
}
#[derive(Debug, Clone, Serialize, serde::Deserialize, Default)]
pub struct TimingBreakdown {
pub walk_ms: u64,
pub pass1_ms: u64,
pub call_graph_ms: u64,
pub pass2_ms: u64,
pub post_process_ms: u64,
}
#[derive(Debug)]
pub struct ScanMetrics {
pub cfg_nodes: AtomicU64,
pub call_edges: AtomicU64,
pub functions_analyzed: AtomicU64,
pub summaries_reused: AtomicU64,
pub unresolved_calls: AtomicU64,
}
impl Default for ScanMetrics {
fn default() -> Self {
Self::new()
}
}
impl ScanMetrics {
pub fn new() -> Self {
Self {
cfg_nodes: AtomicU64::new(0),
call_edges: AtomicU64::new(0),
functions_analyzed: AtomicU64::new(0),
summaries_reused: AtomicU64::new(0),
unresolved_calls: AtomicU64::new(0),
}
}
pub fn snapshot(&self) -> ScanMetricsSnapshot {
ScanMetricsSnapshot {
cfg_nodes: self.cfg_nodes.load(Relaxed),
call_edges: self.call_edges.load(Relaxed),
functions_analyzed: self.functions_analyzed.load(Relaxed),
summaries_reused: self.summaries_reused.load(Relaxed),
unresolved_calls: self.unresolved_calls.load(Relaxed),
}
}
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct ScanMetricsSnapshot {
pub cfg_nodes: u64,
pub call_edges: u64,
pub functions_analyzed: u64,
pub summaries_reused: u64,
pub unresolved_calls: u64,
}