pub mod aggregator;
pub mod config;
pub mod database;
pub mod error;
pub mod export;
pub mod models;
pub mod pillar_usage;
pub mod queries;
pub mod retention;
pub use config::{AnalyticsConfig, RetentionConfig};
pub use database::AnalyticsDatabase;
pub use error::{AnalyticsError, Result};
pub use models::*;
pub use pillar_usage::*;
pub use models::{
DriftPercentageMetrics, EndpointCoverage, PersonaCIHit, RealityLevelStaleness,
ScenarioUsageMetrics,
};
pub async fn init(config: AnalyticsConfig) -> Result<AnalyticsDatabase> {
let db = AnalyticsDatabase::new(&config.database_path).await?;
db.run_migrations().await?;
Ok(db)
}
use once_cell::sync::OnceCell;
use std::sync::Arc;
static GLOBAL_DB: OnceCell<Arc<AnalyticsDatabase>> = OnceCell::new();
pub fn set_global_db(db: AnalyticsDatabase) -> std::result::Result<(), Arc<AnalyticsDatabase>> {
GLOBAL_DB.set(Arc::new(db))
}
pub fn get_global_db() -> Option<Arc<AnalyticsDatabase>> {
GLOBAL_DB.get().cloned()
}
pub fn record_drift_percentage_async(
workspace_id: String,
org_id: Option<String>,
total_mocks: i64,
drifting_mocks: i64,
) {
if let Some(db) = get_global_db() {
tokio::spawn(async move {
if let Err(e) = db
.record_drift_percentage(
&workspace_id,
org_id.as_deref(),
total_mocks,
drifting_mocks,
)
.await
{
tracing::warn!(
workspace_id = %workspace_id,
error = %e,
"failed to record drift percentage sample"
);
}
});
}
}
pub fn record_scenario_usage_async(
scenario_id: String,
workspace_id: Option<String>,
org_id: Option<String>,
) {
if let Some(db) = get_global_db() {
tokio::spawn(async move {
if let Err(e) = db
.record_scenario_usage(&scenario_id, workspace_id.as_deref(), org_id.as_deref())
.await
{
tracing::warn!(
scenario_id = %scenario_id,
error = %e,
"failed to record scenario usage sample"
);
}
});
}
}
pub fn record_endpoint_coverage_async(
endpoint: String,
method: Option<String>,
protocol: String,
workspace_id: Option<String>,
org_id: Option<String>,
) {
if let Some(db) = get_global_db() {
tokio::spawn(async move {
if let Err(e) = db
.record_endpoint_coverage(
&endpoint,
method.as_deref(),
&protocol,
workspace_id.as_deref(),
org_id.as_deref(),
None,
)
.await
{
tracing::warn!(
endpoint = %endpoint,
error = %e,
"failed to record endpoint coverage sample"
);
}
});
}
}
pub fn record_reality_level_staleness_async(
workspace_id: String,
org_id: Option<String>,
current_reality_level: Option<String>,
staleness_days: Option<i32>,
) {
if let Some(db) = get_global_db() {
tokio::spawn(async move {
if let Err(e) = db
.record_reality_level_staleness(
&workspace_id,
org_id.as_deref(),
None,
None,
None,
current_reality_level.as_deref(),
staleness_days,
)
.await
{
tracing::warn!(
workspace_id = %workspace_id,
error = %e,
"failed to record reality level staleness sample"
);
}
});
}
}
#[cfg(test)]
mod global_accessor_tests {
use super::*;
#[tokio::test]
async fn get_global_db_returns_none_before_install() {
let observed = get_global_db();
match observed {
Some(_) | None => {
}
}
}
#[tokio::test]
async fn recording_helpers_are_safe_when_global_uninstalled() {
record_drift_percentage_async("ws".to_string(), None, 10, 1);
record_scenario_usage_async("scenario-a".to_string(), None, None);
record_endpoint_coverage_async(
"/users".to_string(),
Some("GET".to_string()),
"http".to_string(),
None,
None,
);
record_reality_level_staleness_async(
"ws".to_string(),
None,
Some("static_stubs".to_string()),
Some(0),
);
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
}