Move failed count and avg duration from page-derived to backend stats
Some checks failed
CI / build (push) Successful in 1m11s
CI / deploy (push) Has been cancelled
CI / docker (push) Has been cancelled

All stat card values now come from the /search/stats endpoint which
queries the full time window, not just the current page of results.
Consolidated into a single ClickHouse query for efficiency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-13 22:51:43 +01:00
parent 4cdf2ac012
commit 393d19e3f4
5 changed files with 30 additions and 22 deletions

View File

@@ -89,16 +89,18 @@ public class ClickHouseSearchEngine implements SearchEngine {
@Override
public ExecutionStats stats(Instant from, Instant to) {
Long p99 = jdbcTemplate.queryForObject(
"SELECT toInt64(quantile(0.99)(duration_ms)) FROM route_executions " +
"WHERE start_time >= ? AND start_time <= ?",
Long.class, Timestamp.from(from), Timestamp.from(to));
Long active = jdbcTemplate.queryForObject(
"SELECT count() FROM route_executions WHERE status = 'RUNNING'",
Long.class);
return new ExecutionStats(
p99 != null ? p99 : 0L,
active != null ? active : 0L);
return jdbcTemplate.queryForObject(
"SELECT countIf(status = 'FAILED') AS failed_count, " +
"toInt64(avg(duration_ms)) AS avg_duration_ms, " +
"toInt64(quantile(0.99)(duration_ms)) AS p99_duration_ms, " +
"countIf(status = 'RUNNING') AS active_count " +
"FROM route_executions WHERE start_time >= ? AND start_time <= ?",
(rs, rowNum) -> new ExecutionStats(
rs.getLong("failed_count"),
rs.getLong("avg_duration_ms"),
rs.getLong("p99_duration_ms"),
rs.getLong("active_count")),
Timestamp.from(from), Timestamp.from(to));
}
@Override

View File

@@ -1,9 +1,11 @@
package com.cameleer3.server.core.search;
/**
* Aggregate execution statistics.
* Aggregate execution statistics within a time window.
*
* @param p99LatencyMs 99th percentile duration in milliseconds within the requested time window
* @param activeCount number of currently running executions
* @param failedCount number of failed executions
* @param avgDurationMs average duration in milliseconds
* @param p99LatencyMs 99th percentile duration in milliseconds
* @param activeCount number of currently running executions
*/
public record ExecutionStats(long p99LatencyMs, long activeCount) {}
public record ExecutionStats(long failedCount, long avgDurationMs, long p99LatencyMs, long activeCount) {}

View File

@@ -1049,6 +1049,14 @@
"ExecutionStats": {
"type": "object",
"properties": {
"failedCount": {
"type": "integer",
"format": "int64"
},
"avgDurationMs": {
"type": "integer",
"format": "int64"
},
"p99LatencyMs": {
"type": "integer",
"format": "int64"

View File

@@ -217,6 +217,8 @@ export interface ProcessorNode {
export type ProcessorSnapshot = Record<string, string>;
export interface ExecutionStats {
failedCount: number;
avgDurationMs: number;
p99LatencyMs: number;
activeCount: number;
}

View File

@@ -24,12 +24,6 @@ export function ExecutionExplorer() {
const total = data?.total ?? 0;
const results = data?.data ?? [];
// Derive stats from current search results
const failedCount = results.filter((r) => r.status === 'FAILED').length;
const avgDuration = results.length > 0
? Math.round(results.reduce((sum, r) => sum + r.durationMs, 0) / results.length)
: 0;
const showFrom = total > 0 ? offset + 1 : 0;
const showTo = Math.min(offset + limit, total);
@@ -50,8 +44,8 @@ export function ExecutionExplorer() {
{/* Stats Bar */}
<div className={styles.statsBar}>
<StatCard label="Total Matches" value={total.toLocaleString()} accent="amber" change={`from current search`} sparkData={sparkTotal} />
<StatCard label="Avg Duration" value={`${avgDuration.toLocaleString()}ms`} accent="cyan" sparkData={sparkAvgDuration} />
<StatCard label="Failed (page)" value={failedCount.toLocaleString()} accent="rose" sparkData={sparkFailed} />
<StatCard label="Avg Duration" value={stats ? `${stats.avgDurationMs.toLocaleString()}ms` : '--'} accent="cyan" sparkData={sparkAvgDuration} />
<StatCard label="Failed" value={stats ? stats.failedCount.toLocaleString() : '--'} accent="rose" sparkData={sparkFailed} />
<StatCard label="P99 Latency" value={stats ? `${stats.p99LatencyMs.toLocaleString()}ms` : '--'} accent="green" sparkData={sparkP99} />
<StatCard label="In-Flight" value={stats ? stats.activeCount.toLocaleString() : '--'} accent="blue" change="running executions" sparkData={sparkActive} />
</div>