mini-apm 0.0.0

Minimal APM for Rails
Documentation
use crate::DbPool;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HourlyRollup {
    pub id: i64,
    pub hour: String,
    pub path: String,
    pub method: String,
    pub request_count: i64,
    pub error_count: i64,
    pub total_ms_sum: f64,
    pub total_ms_p50: Option<f64>,
    pub total_ms_p95: Option<f64>,
    pub total_ms_p99: Option<f64>,
    pub db_ms_sum: f64,
    pub db_count_sum: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DailyRollup {
    pub id: i64,
    pub date: String,
    pub path: String,
    pub method: String,
    pub request_count: i64,
    pub error_count: i64,
    pub total_ms_p50: Option<f64>,
    pub total_ms_p95: Option<f64>,
    pub total_ms_p99: Option<f64>,
    pub avg_db_ms: Option<f64>,
    pub avg_db_count: Option<f64>,
}

pub fn insert_hourly(pool: &DbPool, rollup: &HourlyRollup) -> anyhow::Result<()> {
    let conn = pool.get()?;
    conn.execute(
        r#"
        INSERT OR REPLACE INTO rollups_hourly
        (hour, path, method, request_count, error_count, total_ms_sum, total_ms_p50, total_ms_p95, total_ms_p99, db_ms_sum, db_count_sum)
        VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
        "#,
        (
            &rollup.hour,
            &rollup.path,
            &rollup.method,
            rollup.request_count,
            rollup.error_count,
            rollup.total_ms_sum,
            rollup.total_ms_p50,
            rollup.total_ms_p95,
            rollup.total_ms_p99,
            rollup.db_ms_sum,
            rollup.db_count_sum,
        ),
    )?;
    Ok(())
}

pub fn insert_daily(pool: &DbPool, rollup: &DailyRollup) -> anyhow::Result<()> {
    let conn = pool.get()?;
    conn.execute(
        r#"
        INSERT OR REPLACE INTO rollups_daily
        (date, path, method, request_count, error_count, total_ms_p50, total_ms_p95, total_ms_p99, avg_db_ms, avg_db_count)
        VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
        "#,
        (
            &rollup.date,
            &rollup.path,
            &rollup.method,
            rollup.request_count,
            rollup.error_count,
            rollup.total_ms_p50,
            rollup.total_ms_p95,
            rollup.total_ms_p99,
            rollup.avg_db_ms,
            rollup.avg_db_count,
        ),
    )?;
    Ok(())
}

pub fn daily_for_range(
    pool: &DbPool,
    start: &str,
    end: &str,
    limit: i64,
) -> anyhow::Result<Vec<DailyRollup>> {
    let conn = pool.get()?;
    let mut stmt = conn.prepare(
        r#"
        SELECT id, date, path, method, request_count, error_count,
               total_ms_p50, total_ms_p95, total_ms_p99, avg_db_ms, avg_db_count
        FROM rollups_daily
        WHERE date >= ?1 AND date <= ?2
        ORDER BY request_count DESC
        LIMIT ?3
        "#,
    )?;

    let rollups = stmt
        .query_map(rusqlite::params![start, end, limit], |row| {
            Ok(DailyRollup {
                id: row.get(0)?,
                date: row.get(1)?,
                path: row.get(2)?,
                method: row.get(3)?,
                request_count: row.get(4)?,
                error_count: row.get(5)?,
                total_ms_p50: row.get(6)?,
                total_ms_p95: row.get(7)?,
                total_ms_p99: row.get(8)?,
                avg_db_ms: row.get(9)?,
                avg_db_count: row.get(10)?,
            })
        })?
        .collect::<Result<Vec<_>, _>>()?;

    Ok(rollups)
}

pub fn delete_hourly_before(pool: &DbPool, before: &str) -> anyhow::Result<usize> {
    let conn = pool.get()?;
    let deleted = conn.execute("DELETE FROM rollups_hourly WHERE hour < ?1", [before])?;
    Ok(deleted)
}