@@ -109,6 +109,47 @@ def record(
109109 event .output_tokens , event .reasoning_tokens , event .cached_tokens , event .credit ,
110110 event .latency_ms , event .ttfb_ms ),
111111 )
112+ # 当前小时增量累加:总览/图表都读小时表,不能等 5 分钟一轮的
113+ # retention rollup 才可见(否则刚发生的请求统计页面显示 0)。
114+ # 增量累加与全量重算等价:rollup 后续会把这一小时算成同样的值。
115+ self ._bump_hourly (conn , event )
116+
117+ @staticmethod
118+ def _bump_hourly (conn , event : UsageEvent ) -> None :
119+ """把一条明细增量累加进所属小时行(同 key 则累加,不存在则插入)。"""
120+ conn .execute (
121+ """
122+ INSERT INTO usage_hourly (hour_utc, username, provider, model, requests, ok_count,
123+ input_tokens, output_tokens, reasoning_tokens,
124+ cached_tokens, cached_known,
125+ credit_sum, credit_known, latency_sum, ttfb_sum)
126+ VALUES (?,?,?,?,1,?,?,?,?,?,?,?,?,?,?)
127+ ON CONFLICT(hour_utc, username, provider, model) DO UPDATE SET
128+ requests = requests + 1,
129+ ok_count = ok_count + excluded.ok_count,
130+ input_tokens = input_tokens + excluded.input_tokens,
131+ output_tokens = output_tokens + excluded.output_tokens,
132+ reasoning_tokens = reasoning_tokens + excluded.reasoning_tokens,
133+ cached_tokens = cached_tokens + excluded.cached_tokens,
134+ cached_known = cached_known + excluded.cached_known,
135+ credit_sum = COALESCE(credit_sum, 0) + excluded.credit_sum,
136+ credit_known = credit_known + excluded.credit_known,
137+ latency_sum = latency_sum + excluded.latency_sum,
138+ ttfb_sum = ttfb_sum + excluded.ttfb_sum
139+ """ ,
140+ # NULL 一律折成 0 参与累加:列定义是 NOT NULL DEFAULT 0,
141+ # `x + NULL` 会交出 NULL,把整行污染掉
142+ ((event .ts // 3600 ) * 3600 , event .username , event .provider , event .model ,
143+ int (event .ok ),
144+ event .input_tokens or 0 , event .output_tokens or 0 ,
145+ event .reasoning_tokens or 0 ,
146+ event .cached_tokens or 0 ,
147+ 0 if event .cached_tokens is None else 1 ,
148+ event .credit or 0.0 ,
149+ 0 if event .credit is None else 1 ,
150+ (event .latency_ms or 0 ) if event .ok else 0 ,
151+ (event .ttfb_ms or 0 ) if event .ok else 0 ),
152+ )
112153
113154 def purge_expired (self , retention_days : int = 90 , now : int | None = None ) -> int :
114155 """明细保留 90 天;小时汇总永久(PROPOSAL §8)。
@@ -139,20 +180,28 @@ def rollup_hourly(self, since: int | None = None) -> int:
139180 cursor = conn .execute (
140181 """
141182 INSERT INTO usage_hourly (hour_utc, username, provider, model, requests, ok_count,
142- input_tokens, output_tokens, credit_sum, credit_known,
143- latency_sum, ttfb_sum)
183+ input_tokens, output_tokens, reasoning_tokens,
184+ cached_tokens, cached_known,
185+ credit_sum, credit_known, latency_sum, ttfb_sum)
144186 SELECT (ts / 3600) * 3600 AS hour_utc, username, provider, model,
145187 COUNT(*), SUM(ok),
146188 COALESCE(SUM(input_tokens), 0), COALESCE(SUM(output_tokens), 0),
147- SUM(credit), SUM(CASE WHEN credit IS NULL THEN 0 ELSE 1 END),
148- COALESCE(SUM(latency_ms), 0), COALESCE(SUM(ttfb_ms), 0)
189+ COALESCE(SUM(reasoning_tokens), 0),
190+ COALESCE(SUM(cached_tokens), 0),
191+ SUM(CASE WHEN cached_tokens IS NULL THEN 0 ELSE 1 END),
192+ COALESCE(SUM(credit), 0), SUM(CASE WHEN credit IS NULL THEN 0 ELSE 1 END),
193+ COALESCE(SUM(CASE WHEN ok = 1 THEN latency_ms END), 0),
194+ COALESCE(SUM(CASE WHEN ok = 1 THEN ttfb_ms END), 0)
149195 FROM usage_events WHERE (? IS NULL OR ts >= ?)
150196 GROUP BY hour_utc, username, provider, model
151197 ON CONFLICT(hour_utc, username, provider, model) DO UPDATE SET
152198 requests = excluded.requests,
153199 ok_count = excluded.ok_count,
154200 input_tokens = excluded.input_tokens,
155201 output_tokens = excluded.output_tokens,
202+ reasoning_tokens = excluded.reasoning_tokens,
203+ cached_tokens = excluded.cached_tokens,
204+ cached_known = excluded.cached_known,
156205 credit_sum = excluded.credit_sum,
157206 credit_known = excluded.credit_known,
158207 latency_sum = excluded.latency_sum,
0 commit comments