1use crate::DbPool;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct HourlyRollup {
6 pub id: i64,
7 pub hour: String,
8 pub path: String,
9 pub method: String,
10 pub request_count: i64,
11 pub error_count: i64,
12 pub total_ms_sum: f64,
13 pub total_ms_p50: Option<f64>,
14 pub total_ms_p95: Option<f64>,
15 pub total_ms_p99: Option<f64>,
16 pub db_ms_sum: f64,
17 pub db_count_sum: i64,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct DailyRollup {
22 pub id: i64,
23 pub date: String,
24 pub path: String,
25 pub method: String,
26 pub request_count: i64,
27 pub error_count: i64,
28 pub total_ms_p50: Option<f64>,
29 pub total_ms_p95: Option<f64>,
30 pub total_ms_p99: Option<f64>,
31 pub avg_db_ms: Option<f64>,
32 pub avg_db_count: Option<f64>,
33}
34
35pub fn insert_hourly(pool: &DbPool, rollup: &HourlyRollup) -> anyhow::Result<()> {
36 let conn = pool.get()?;
37 conn.execute(
38 r#"
39 INSERT OR REPLACE INTO rollups_hourly
40 (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)
41 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
42 "#,
43 (
44 &rollup.hour,
45 &rollup.path,
46 &rollup.method,
47 rollup.request_count,
48 rollup.error_count,
49 rollup.total_ms_sum,
50 rollup.total_ms_p50,
51 rollup.total_ms_p95,
52 rollup.total_ms_p99,
53 rollup.db_ms_sum,
54 rollup.db_count_sum,
55 ),
56 )?;
57 Ok(())
58}
59
60pub fn insert_daily(pool: &DbPool, rollup: &DailyRollup) -> anyhow::Result<()> {
61 let conn = pool.get()?;
62 conn.execute(
63 r#"
64 INSERT OR REPLACE INTO rollups_daily
65 (date, path, method, request_count, error_count, total_ms_p50, total_ms_p95, total_ms_p99, avg_db_ms, avg_db_count)
66 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
67 "#,
68 (
69 &rollup.date,
70 &rollup.path,
71 &rollup.method,
72 rollup.request_count,
73 rollup.error_count,
74 rollup.total_ms_p50,
75 rollup.total_ms_p95,
76 rollup.total_ms_p99,
77 rollup.avg_db_ms,
78 rollup.avg_db_count,
79 ),
80 )?;
81 Ok(())
82}
83
84pub fn daily_for_range(
85 pool: &DbPool,
86 start: &str,
87 end: &str,
88 limit: i64,
89) -> anyhow::Result<Vec<DailyRollup>> {
90 let conn = pool.get()?;
91 let mut stmt = conn.prepare(
92 r#"
93 SELECT id, date, path, method, request_count, error_count,
94 total_ms_p50, total_ms_p95, total_ms_p99, avg_db_ms, avg_db_count
95 FROM rollups_daily
96 WHERE date >= ?1 AND date <= ?2
97 ORDER BY request_count DESC
98 LIMIT ?3
99 "#,
100 )?;
101
102 let rollups = stmt
103 .query_map(rusqlite::params![start, end, limit], |row| {
104 Ok(DailyRollup {
105 id: row.get(0)?,
106 date: row.get(1)?,
107 path: row.get(2)?,
108 method: row.get(3)?,
109 request_count: row.get(4)?,
110 error_count: row.get(5)?,
111 total_ms_p50: row.get(6)?,
112 total_ms_p95: row.get(7)?,
113 total_ms_p99: row.get(8)?,
114 avg_db_ms: row.get(9)?,
115 avg_db_count: row.get(10)?,
116 })
117 })?
118 .collect::<Result<Vec<_>, _>>()?;
119
120 Ok(rollups)
121}
122
123pub fn delete_hourly_before(pool: &DbPool, before: &str) -> anyhow::Result<usize> {
124 let conn = pool.get()?;
125 let deleted = conn.execute("DELETE FROM rollups_hourly WHERE hour < ?1", [before])?;
126 Ok(deleted)
127}