@@ -43,19 +43,24 @@ def _init(self):
4343 lat_p99 REAL,
4444 lat_min REAL,
4545 lat_max REAL,
46- bytes_avg REAL
46+ bytes_avg REAL,
47+ is_ghost INTEGER NOT NULL DEFAULT 0
4748 );
4849
4950 CREATE INDEX IF NOT EXISTS idx_route_ts ON api_metrics (route, method, bucket_ts);
5051 CREATE INDEX IF NOT EXISTS idx_bucket_ts ON api_metrics (bucket_ts);
5152 CREATE INDEX IF NOT EXISTS idx_release ON api_metrics (release_tag)
5253 WHERE release_tag IS NOT NULL;
5354 """ )
54- # Migration for databases created before bytes_avg was introduced
55+ # Migrations for databases created before these columns were introduced
5556 try :
5657 c .execute ("ALTER TABLE api_metrics ADD COLUMN bytes_avg REAL" )
5758 except Exception :
58- pass # column already exists
59+ pass
60+ try :
61+ c .execute ("ALTER TABLE api_metrics ADD COLUMN is_ghost INTEGER NOT NULL DEFAULT 0" )
62+ except Exception :
63+ pass
5964 c .commit ()
6065
6166 def insert_batch (self , rows : list [dict ]):
@@ -66,11 +71,11 @@ def insert_batch(self, rows: list[dict]):
6671 INSERT INTO api_metrics
6772 (bucket_ts, route, method, env, release_tag,
6873 status_2xx, status_4xx, status_5xx, total_calls,
69- lat_p50, lat_p90, lat_p99, lat_min, lat_max, bytes_avg)
74+ lat_p50, lat_p90, lat_p99, lat_min, lat_max, bytes_avg, is_ghost )
7075 VALUES (
7176 :bucket_ts, :route, :method, :env, :release_tag,
7277 :status_2xx, :status_4xx, :status_5xx, :total_calls,
73- :lat_p50, :lat_p90, :lat_p99, :lat_min, :lat_max, :bytes_avg
78+ :lat_p50, :lat_p90, :lat_p99, :lat_min, :lat_max, :bytes_avg, :is_ghost
7479 )
7580 """ , rows )
7681 self ._conn .commit ()
@@ -107,22 +112,22 @@ def get_summary(self) -> dict:
107112 SUM(status_5xx) as calls_5xx,
108113 AVG(lat_p90) as avg_p90,
109114 AVG(lat_p99) as avg_p99
110- FROM api_metrics WHERE bucket_ts >= ?
115+ FROM api_metrics WHERE bucket_ts >= ? AND is_ghost = 0
111116 """ , (since_24h ,)).fetchone ()
112117
113118 baseline = c .execute ("""
114119 SELECT AVG(lat_p90) as baseline_p90
115- FROM api_metrics WHERE bucket_ts >= ? AND bucket_ts < ?
120+ FROM api_metrics WHERE bucket_ts >= ? AND bucket_ts < ? AND is_ghost = 0
116121 """ , (since_7d , since_24h )).fetchone ()
117122
118123 active = c .execute ("""
119124 SELECT COUNT(DISTINCT route || '|' || method) as n
120- FROM api_metrics WHERE bucket_ts >= ?
125+ FROM api_metrics WHERE bucket_ts >= ? AND is_ghost = 0
121126 """ , (since_24h ,)).fetchone ()
122127
123128 total = c .execute ("""
124129 SELECT COUNT(DISTINCT route || '|' || method) as n
125- FROM api_metrics
130+ FROM api_metrics WHERE is_ghost = 0
126131 """ ).fetchone ()
127132
128133 return {
@@ -136,7 +141,7 @@ def get_routes(self, hours: int = 24) -> list[dict]:
136141 since = _now_sec () - hours * 3600
137142 rows = self ._conn .execute ("""
138143 SELECT
139- route, method,
144+ route, method, is_ghost,
140145 SUM(total_calls) as calls,
141146 SUM(status_2xx) as calls_2xx,
142147 SUM(status_4xx) as calls_4xx,
@@ -148,9 +153,9 @@ def get_routes(self, hours: int = 24) -> list[dict]:
148153 AVG(bytes_avg) as bytes_avg
149154 FROM api_metrics
150155 WHERE bucket_ts >= ?
151- GROUP BY route, method
152- ORDER BY calls DESC
153- LIMIT 50
156+ GROUP BY route, method, is_ghost
157+ ORDER BY is_ghost ASC, calls DESC
158+ LIMIT 100
154159 """ , (since ,)).fetchall ()
155160 return [dict (r ) for r in rows ]
156161
@@ -171,6 +176,7 @@ def get_dead_candidates(self, inactive_days: int = 21) -> list[dict]:
171176 rows = self ._conn .execute ("""
172177 SELECT route, method, MAX(bucket_ts) as last_seen
173178 FROM api_metrics
179+ WHERE is_ghost = 0
174180 GROUP BY route, method
175181 HAVING last_seen < ?
176182 ORDER BY last_seen ASC
@@ -217,14 +223,14 @@ def get_latency_anomaly_data(self) -> dict:
217223
218224 recent = self ._conn .execute ("""
219225 SELECT route, method, AVG(lat_p99) as avg_p99
220- FROM api_metrics WHERE bucket_ts >= ?
226+ FROM api_metrics WHERE bucket_ts >= ? AND is_ghost = 0
221227 GROUP BY route, method
222228 """ , (since_1h ,)).fetchall ()
223229
224230 baseline = self ._conn .execute ("""
225231 SELECT route, method, lat_p99
226232 FROM api_metrics
227- WHERE bucket_ts >= ? AND bucket_ts < ? AND lat_p99 IS NOT NULL
233+ WHERE bucket_ts >= ? AND bucket_ts < ? AND lat_p99 IS NOT NULL AND is_ghost = 0
228234 """ , (since_7d , since_1h )).fetchall ()
229235
230236 return {
@@ -270,7 +276,7 @@ def get_drift_data(self) -> list[dict]:
270276 CAST(bucket_ts / 86400 AS INTEGER) as day_bucket,
271277 AVG(lat_p90) as p90
272278 FROM api_metrics
273- WHERE bucket_ts >= ? AND lat_p90 IS NOT NULL
279+ WHERE bucket_ts >= ? AND lat_p90 IS NOT NULL AND is_ghost = 0
274280 GROUP BY route, method, day_bucket
275281 ORDER BY route, method, day_bucket
276282 """ , (since_30d ,)).fetchall ()
@@ -282,7 +288,7 @@ def get_global_time_series(self, hours: int = 24) -> list[dict]:
282288 SELECT bucket_ts, SUM(total_calls) as calls,
283289 AVG(lat_p50) as p50, AVG(lat_p90) as p90,
284290 AVG(lat_p99) as p99, SUM(status_5xx) as errors
285- FROM api_metrics WHERE bucket_ts >= ?
291+ FROM api_metrics WHERE bucket_ts >= ? AND is_ghost = 0
286292 GROUP BY bucket_ts ORDER BY bucket_ts ASC
287293 """ , (since ,)).fetchall ()
288294 return [dict (r ) for r in rows ]
0 commit comments