feat: add environment filtering across all APIs and UI
Backend: Added optional `environment` query parameter to catalog, search, stats, timeseries, punchcard, top-errors, logs, and agents endpoints. ClickHouse queries filter by environment when specified (literal SQL for AggregatingMergeTree, ? binds for raw tables). StatsStore interface methods all accept environment parameter. UI: Added EnvironmentSelector component (compact native select). LayoutShell extracts distinct environments from agent data and passes selected environment to catalog and agent queries via URL search param (?env=). TopBar shows current environment label. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -271,7 +271,8 @@ public class AgentRegistrationController {
|
||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||
public ResponseEntity<List<AgentInstanceResponse>> listAgents(
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(required = false) String application) {
|
||||
@RequestParam(required = false) String application,
|
||||
@RequestParam(required = false) String environment) {
|
||||
List<AgentInfo> agents;
|
||||
|
||||
if (status != null) {
|
||||
@@ -292,6 +293,13 @@ public class AgentRegistrationController {
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Apply environment filter if specified
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
agents = agents.stream()
|
||||
.filter(a -> environment.equals(a.environmentId()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Enrich with runtime metrics from continuous aggregates
|
||||
Map<String, double[]> agentMetrics = queryAgentMetrics();
|
||||
final List<AgentInfo> finalAgents = agents;
|
||||
|
||||
@@ -40,6 +40,7 @@ public class LogQueryController {
|
||||
@RequestParam(name = "agentId", required = false) String instanceId,
|
||||
@RequestParam(required = false) String exchangeId,
|
||||
@RequestParam(required = false) String logger,
|
||||
@RequestParam(required = false) String environment,
|
||||
@RequestParam(required = false) String from,
|
||||
@RequestParam(required = false) String to,
|
||||
@RequestParam(required = false) String cursor,
|
||||
@@ -63,7 +64,7 @@ public class LogQueryController {
|
||||
|
||||
LogSearchRequest request = new LogSearchRequest(
|
||||
searchText, levels, application, instanceId, exchangeId,
|
||||
logger, fromInstant, toInstant, cursor, limit, sort);
|
||||
logger, environment, fromInstant, toInstant, cursor, limit, sort);
|
||||
|
||||
LogSearchResponse result = logIndex.search(request);
|
||||
|
||||
|
||||
@@ -59,9 +59,17 @@ public class RouteCatalogController {
|
||||
@ApiResponse(responseCode = "200", description = "Catalog returned")
|
||||
public ResponseEntity<List<AppCatalogEntry>> getCatalog(
|
||||
@RequestParam(required = false) String from,
|
||||
@RequestParam(required = false) String to) {
|
||||
@RequestParam(required = false) String to,
|
||||
@RequestParam(required = false) String environment) {
|
||||
List<AgentInfo> allAgents = registryService.findAll();
|
||||
|
||||
// Filter agents by environment if specified
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
allAgents = allAgents.stream()
|
||||
.filter(a -> environment.equals(a.environmentId()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Group agents by application name
|
||||
Map<String, List<AgentInfo>> agentsByApp = allAgents.stream()
|
||||
.collect(Collectors.groupingBy(AgentInfo::applicationId, LinkedHashMap::new, Collectors.toList()));
|
||||
@@ -87,9 +95,12 @@ public class RouteCatalogController {
|
||||
Map<String, Long> routeExchangeCounts = new LinkedHashMap<>();
|
||||
Map<String, Instant> routeLastSeen = new LinkedHashMap<>();
|
||||
try {
|
||||
String envFilter = (environment != null && !environment.isBlank())
|
||||
? " AND environment = " + lit(environment) : "";
|
||||
jdbc.query(
|
||||
"SELECT application_id, route_id, countMerge(total_count) AS cnt, MAX(bucket) AS last_seen " +
|
||||
"FROM stats_1m_route WHERE bucket >= " + lit(rangeFrom) + " AND bucket < " + lit(rangeTo) +
|
||||
envFilter +
|
||||
" GROUP BY application_id, route_id",
|
||||
rs -> {
|
||||
String key = rs.getString("application_id") + "/" + rs.getString("route_id");
|
||||
@@ -169,6 +180,11 @@ public class RouteCatalogController {
|
||||
.format(instant.truncatedTo(ChronoUnit.SECONDS)) + "'";
|
||||
}
|
||||
|
||||
/** Format a string as a ClickHouse SQL literal with backslash + quote escaping. */
|
||||
private static String lit(String value) {
|
||||
return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'";
|
||||
}
|
||||
|
||||
private String computeWorstHealth(List<AgentInfo> agents) {
|
||||
boolean hasDead = false;
|
||||
boolean hasStale = false;
|
||||
|
||||
@@ -115,7 +115,7 @@ public class RouteMetricsController {
|
||||
.map(AppSettings::slaThresholdMs).orElse(300);
|
||||
|
||||
Map<String, long[]> slaCounts = statsStore.slaCountsByRoute(fromInstant, toInstant,
|
||||
effectiveAppId, threshold);
|
||||
effectiveAppId, threshold, null);
|
||||
|
||||
for (int i = 0; i < metrics.size(); i++) {
|
||||
RouteMetrics m = metrics.get(i);
|
||||
|
||||
@@ -60,6 +60,7 @@ public class SearchController {
|
||||
@RequestParam(name = "agentId", required = false) String instanceId,
|
||||
@RequestParam(required = false) String processorType,
|
||||
@RequestParam(required = false) String application,
|
||||
@RequestParam(required = false) String environment,
|
||||
@RequestParam(defaultValue = "0") int offset,
|
||||
@RequestParam(defaultValue = "50") int limit,
|
||||
@RequestParam(required = false) String sortField,
|
||||
@@ -75,7 +76,8 @@ public class SearchController {
|
||||
routeId, instanceId, processorType,
|
||||
application, agentIds,
|
||||
offset, limit,
|
||||
sortField, sortDir
|
||||
sortField, sortDir,
|
||||
environment
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(searchService.search(request));
|
||||
@@ -100,23 +102,24 @@ public class SearchController {
|
||||
@RequestParam Instant from,
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(required = false) String routeId,
|
||||
@RequestParam(required = false) String application) {
|
||||
@RequestParam(required = false) String application,
|
||||
@RequestParam(required = false) String environment) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
ExecutionStats stats;
|
||||
if (routeId == null && application == null) {
|
||||
stats = searchService.stats(from, end);
|
||||
stats = searchService.stats(from, end, environment);
|
||||
} else if (routeId == null) {
|
||||
stats = searchService.statsForApp(from, end, application);
|
||||
stats = searchService.statsForApp(from, end, application, environment);
|
||||
} else {
|
||||
List<String> agentIds = resolveApplicationToAgentIds(application);
|
||||
stats = searchService.stats(from, end, routeId, agentIds);
|
||||
stats = searchService.stats(from, end, routeId, agentIds, environment);
|
||||
}
|
||||
|
||||
// Enrich with SLA compliance
|
||||
int threshold = appSettingsRepository
|
||||
.findByApplicationId(application != null ? application : "")
|
||||
.map(AppSettings::slaThresholdMs).orElse(300);
|
||||
double sla = searchService.slaCompliance(from, end, threshold, application, routeId);
|
||||
double sla = searchService.slaCompliance(from, end, threshold, application, routeId, environment);
|
||||
return ResponseEntity.ok(stats.withSlaCompliance(sla));
|
||||
}
|
||||
|
||||
@@ -127,19 +130,20 @@ public class SearchController {
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(defaultValue = "24") int buckets,
|
||||
@RequestParam(required = false) String routeId,
|
||||
@RequestParam(required = false) String application) {
|
||||
@RequestParam(required = false) String application,
|
||||
@RequestParam(required = false) String environment) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
if (routeId == null && application == null) {
|
||||
return ResponseEntity.ok(searchService.timeseries(from, end, buckets));
|
||||
return ResponseEntity.ok(searchService.timeseries(from, end, buckets, environment));
|
||||
}
|
||||
if (routeId == null) {
|
||||
return ResponseEntity.ok(searchService.timeseriesForApp(from, end, buckets, application));
|
||||
return ResponseEntity.ok(searchService.timeseriesForApp(from, end, buckets, application, environment));
|
||||
}
|
||||
List<String> agentIds = resolveApplicationToAgentIds(application);
|
||||
if (routeId == null && agentIds.isEmpty()) {
|
||||
return ResponseEntity.ok(searchService.timeseries(from, end, buckets));
|
||||
return ResponseEntity.ok(searchService.timeseries(from, end, buckets, environment));
|
||||
}
|
||||
return ResponseEntity.ok(searchService.timeseries(from, end, buckets, routeId, agentIds));
|
||||
return ResponseEntity.ok(searchService.timeseries(from, end, buckets, routeId, agentIds, environment));
|
||||
}
|
||||
|
||||
@GetMapping("/stats/timeseries/by-app")
|
||||
@@ -147,9 +151,10 @@ public class SearchController {
|
||||
public ResponseEntity<Map<String, StatsTimeseries>> timeseriesByApp(
|
||||
@RequestParam Instant from,
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(defaultValue = "24") int buckets) {
|
||||
@RequestParam(defaultValue = "24") int buckets,
|
||||
@RequestParam(required = false) String environment) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
return ResponseEntity.ok(searchService.timeseriesGroupedByApp(from, end, buckets));
|
||||
return ResponseEntity.ok(searchService.timeseriesGroupedByApp(from, end, buckets, environment));
|
||||
}
|
||||
|
||||
@GetMapping("/stats/timeseries/by-route")
|
||||
@@ -158,18 +163,20 @@ public class SearchController {
|
||||
@RequestParam Instant from,
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(defaultValue = "24") int buckets,
|
||||
@RequestParam String application) {
|
||||
@RequestParam String application,
|
||||
@RequestParam(required = false) String environment) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
return ResponseEntity.ok(searchService.timeseriesGroupedByRoute(from, end, buckets, application));
|
||||
return ResponseEntity.ok(searchService.timeseriesGroupedByRoute(from, end, buckets, application, environment));
|
||||
}
|
||||
|
||||
@GetMapping("/stats/punchcard")
|
||||
@Operation(summary = "Transaction punchcard: weekday x hour grid (rolling 7 days)")
|
||||
public ResponseEntity<List<StatsStore.PunchcardCell>> punchcard(
|
||||
@RequestParam(required = false) String application) {
|
||||
@RequestParam(required = false) String application,
|
||||
@RequestParam(required = false) String environment) {
|
||||
Instant to = Instant.now();
|
||||
Instant from = to.minus(java.time.Duration.ofDays(7));
|
||||
return ResponseEntity.ok(searchService.punchcard(from, to, application));
|
||||
return ResponseEntity.ok(searchService.punchcard(from, to, application, environment));
|
||||
}
|
||||
|
||||
@GetMapping("/attributes/keys")
|
||||
@@ -185,9 +192,10 @@ public class SearchController {
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(required = false) String application,
|
||||
@RequestParam(required = false) String routeId,
|
||||
@RequestParam(required = false) String environment,
|
||||
@RequestParam(defaultValue = "5") int limit) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
return ResponseEntity.ok(searchService.topErrors(from, end, application, routeId, limit));
|
||||
return ResponseEntity.ok(searchService.topErrors(from, end, application, routeId, limit, environment));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ public record AgentInstanceResponse(
|
||||
@NotNull String instanceId,
|
||||
@NotNull String displayName,
|
||||
@NotNull String applicationId,
|
||||
String environmentId,
|
||||
@NotNull String status,
|
||||
@NotNull List<String> routeIds,
|
||||
@NotNull Instant registeredAt,
|
||||
@@ -30,6 +31,7 @@ public record AgentInstanceResponse(
|
||||
long uptime = Duration.between(info.registeredAt(), Instant.now()).toSeconds();
|
||||
return new AgentInstanceResponse(
|
||||
info.instanceId(), info.displayName(), info.applicationId(),
|
||||
info.environmentId(),
|
||||
info.state().name(), info.routeIds(),
|
||||
info.registeredAt(), info.lastHeartbeat(),
|
||||
info.version(), info.capabilities(),
|
||||
@@ -41,7 +43,8 @@ public record AgentInstanceResponse(
|
||||
|
||||
public AgentInstanceResponse withMetrics(double tps, double errorRate, int activeRoutes) {
|
||||
return new AgentInstanceResponse(
|
||||
instanceId, displayName, applicationId, status, routeIds, registeredAt, lastHeartbeat,
|
||||
instanceId, displayName, applicationId, environmentId,
|
||||
status, routeIds, registeredAt, lastHeartbeat,
|
||||
version, capabilities,
|
||||
tps, errorRate, activeRoutes, totalRoutes, uptimeSeconds
|
||||
);
|
||||
|
||||
@@ -108,6 +108,11 @@ public class ClickHouseLogStore implements LogIndex {
|
||||
baseConditions.add("tenant_id = ?");
|
||||
baseParams.add(tenantId);
|
||||
|
||||
if (request.environment() != null && !request.environment().isEmpty()) {
|
||||
baseConditions.add("environment = ?");
|
||||
baseParams.add(request.environment());
|
||||
}
|
||||
|
||||
if (request.application() != null && !request.application().isEmpty()) {
|
||||
baseConditions.add("application = ?");
|
||||
baseParams.add(request.application());
|
||||
|
||||
@@ -182,6 +182,11 @@ public class ClickHouseSearchIndex implements SearchIndex {
|
||||
params.add(request.durationMax());
|
||||
}
|
||||
|
||||
if (request.environment() != null && !request.environment().isBlank()) {
|
||||
conditions.add("environment = ?");
|
||||
params.add(request.environment());
|
||||
}
|
||||
|
||||
// Global full-text search: exact ID match, full-text on execution + processor level
|
||||
if (request.text() != null && !request.text().isBlank()) {
|
||||
String term = escapeLike(request.text());
|
||||
|
||||
@@ -42,20 +42,20 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
// ── Stats (aggregate) ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public ExecutionStats stats(Instant from, Instant to) {
|
||||
return queryStats("stats_1m_all", from, to, List.of(), true);
|
||||
public ExecutionStats stats(Instant from, Instant to, String environment) {
|
||||
return queryStats("stats_1m_all", from, to, List.of(), true, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutionStats statsForApp(Instant from, Instant to, String applicationId) {
|
||||
public ExecutionStats statsForApp(Instant from, Instant to, String applicationId, String environment) {
|
||||
return queryStats("stats_1m_app", from, to, List.of(
|
||||
new Filter("application_id", applicationId)), true);
|
||||
new Filter("application_id", applicationId)), true, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutionStats statsForRoute(Instant from, Instant to, String routeId, List<String> agentIds) {
|
||||
public ExecutionStats statsForRoute(Instant from, Instant to, String routeId, List<String> agentIds, String environment) {
|
||||
return queryStats("stats_1m_route", from, to, List.of(
|
||||
new Filter("route_id", routeId)), true);
|
||||
new Filter("route_id", routeId)), true, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,21 +66,21 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
// ── Timeseries ───────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public StatsTimeseries timeseries(Instant from, Instant to, int bucketCount) {
|
||||
return queryTimeseries("stats_1m_all", from, to, bucketCount, List.of(), true);
|
||||
public StatsTimeseries timeseries(Instant from, Instant to, int bucketCount, String environment) {
|
||||
return queryTimeseries("stats_1m_all", from, to, bucketCount, List.of(), true, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatsTimeseries timeseriesForApp(Instant from, Instant to, int bucketCount, String applicationId) {
|
||||
public StatsTimeseries timeseriesForApp(Instant from, Instant to, int bucketCount, String applicationId, String environment) {
|
||||
return queryTimeseries("stats_1m_app", from, to, bucketCount, List.of(
|
||||
new Filter("application_id", applicationId)), true);
|
||||
new Filter("application_id", applicationId)), true, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatsTimeseries timeseriesForRoute(Instant from, Instant to, int bucketCount,
|
||||
String routeId, List<String> agentIds) {
|
||||
String routeId, List<String> agentIds, String environment) {
|
||||
return queryTimeseries("stats_1m_route", from, to, bucketCount, List.of(
|
||||
new Filter("route_id", routeId)), true);
|
||||
new Filter("route_id", routeId)), true, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,23 +92,23 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
// ── Grouped timeseries ───────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount) {
|
||||
public Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount, String environment) {
|
||||
return queryGroupedTimeseries("stats_1m_app", "application_id", from, to,
|
||||
bucketCount, List.of());
|
||||
bucketCount, List.of(), environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, StatsTimeseries> timeseriesGroupedByRoute(Instant from, Instant to,
|
||||
int bucketCount, String applicationId) {
|
||||
int bucketCount, String applicationId, String environment) {
|
||||
return queryGroupedTimeseries("stats_1m_route", "route_id", from, to,
|
||||
bucketCount, List.of(new Filter("application_id", applicationId)));
|
||||
bucketCount, List.of(new Filter("application_id", applicationId)), environment);
|
||||
}
|
||||
|
||||
// ── SLA compliance (raw table — prepared statements OK) ──────────────
|
||||
|
||||
@Override
|
||||
public double slaCompliance(Instant from, Instant to, int thresholdMs,
|
||||
String applicationId, String routeId) {
|
||||
String applicationId, String routeId, String environment) {
|
||||
String sql = "SELECT " +
|
||||
"countIf(duration_ms <= ? AND status != 'RUNNING') AS compliant, " +
|
||||
"countIf(status != 'RUNNING') AS total " +
|
||||
@@ -120,6 +120,10 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
params.add(tenantId);
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
sql += " AND environment = ?";
|
||||
params.add(environment);
|
||||
}
|
||||
if (applicationId != null) {
|
||||
sql += " AND application_id = ?";
|
||||
params.add(applicationId);
|
||||
@@ -137,37 +141,59 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs) {
|
||||
public Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs, String environment) {
|
||||
String sql = "SELECT application_id, " +
|
||||
"countIf(duration_ms <= ? AND status != 'RUNNING') AS compliant, " +
|
||||
"countIf(status != 'RUNNING') AS total " +
|
||||
"FROM executions FINAL " +
|
||||
"WHERE tenant_id = ? AND start_time >= ? AND start_time < ? " +
|
||||
"GROUP BY application_id";
|
||||
"WHERE tenant_id = ? AND start_time >= ? AND start_time < ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(defaultThresholdMs);
|
||||
params.add(tenantId);
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
sql += " AND environment = ?";
|
||||
params.add(environment);
|
||||
}
|
||||
sql += " GROUP BY application_id";
|
||||
|
||||
Map<String, long[]> result = new LinkedHashMap<>();
|
||||
jdbc.query(sql, (rs) -> {
|
||||
result.put(rs.getString("application_id"),
|
||||
new long[]{rs.getLong("compliant"), rs.getLong("total")});
|
||||
}, defaultThresholdMs, tenantId, Timestamp.from(from), Timestamp.from(to));
|
||||
}, params.toArray());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, long[]> slaCountsByRoute(Instant from, Instant to,
|
||||
String applicationId, int thresholdMs) {
|
||||
String applicationId, int thresholdMs, String environment) {
|
||||
String sql = "SELECT route_id, " +
|
||||
"countIf(duration_ms <= ? AND status != 'RUNNING') AS compliant, " +
|
||||
"countIf(status != 'RUNNING') AS total " +
|
||||
"FROM executions FINAL " +
|
||||
"WHERE tenant_id = ? AND start_time >= ? AND start_time < ? " +
|
||||
"AND application_id = ? GROUP BY route_id";
|
||||
"AND application_id = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(thresholdMs);
|
||||
params.add(tenantId);
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
params.add(applicationId);
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
sql += " AND environment = ?";
|
||||
params.add(environment);
|
||||
}
|
||||
sql += " GROUP BY route_id";
|
||||
|
||||
Map<String, long[]> result = new LinkedHashMap<>();
|
||||
jdbc.query(sql, (rs) -> {
|
||||
result.put(rs.getString("route_id"),
|
||||
new long[]{rs.getLong("compliant"), rs.getLong("total")});
|
||||
}, thresholdMs, tenantId, Timestamp.from(from), Timestamp.from(to), applicationId);
|
||||
}, params.toArray());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -175,12 +201,16 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
|
||||
@Override
|
||||
public List<TopError> topErrors(Instant from, Instant to, String applicationId,
|
||||
String routeId, int limit) {
|
||||
String routeId, int limit, String environment) {
|
||||
StringBuilder where = new StringBuilder(
|
||||
"status = 'FAILED' AND start_time >= ? AND start_time < ?");
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
where.append(" AND environment = ?");
|
||||
params.add(environment);
|
||||
}
|
||||
if (applicationId != null) {
|
||||
where.append(" AND application_id = ?");
|
||||
params.add(applicationId);
|
||||
@@ -247,7 +277,7 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int activeErrorTypes(Instant from, Instant to, String applicationId) {
|
||||
public int activeErrorTypes(Instant from, Instant to, String applicationId, String environment) {
|
||||
String sql = "SELECT COUNT(DISTINCT COALESCE(error_type, substring(error_message, 1, 200))) " +
|
||||
"FROM executions FINAL " +
|
||||
"WHERE tenant_id = ? AND status = 'FAILED' AND start_time >= ? AND start_time < ?";
|
||||
@@ -256,6 +286,10 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
params.add(tenantId);
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
sql += " AND environment = ?";
|
||||
params.add(environment);
|
||||
}
|
||||
if (applicationId != null) {
|
||||
sql += " AND application_id = ?";
|
||||
params.add(applicationId);
|
||||
@@ -268,7 +302,7 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
// ── Punchcard (AggregatingMergeTree — literal SQL) ───────────────────
|
||||
|
||||
@Override
|
||||
public List<PunchcardCell> punchcard(Instant from, Instant to, String applicationId) {
|
||||
public List<PunchcardCell> punchcard(Instant from, Instant to, String applicationId, String environment) {
|
||||
String view = applicationId != null ? "stats_1m_app" : "stats_1m_all";
|
||||
String sql = "SELECT toDayOfWeek(bucket, 1) % 7 AS weekday, " +
|
||||
"toHour(bucket) AS hour, " +
|
||||
@@ -278,6 +312,9 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
" WHERE tenant_id = " + lit(tenantId) +
|
||||
" AND bucket >= " + lit(from) +
|
||||
" AND bucket < " + lit(to);
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
sql += " AND environment = " + lit(environment);
|
||||
}
|
||||
if (applicationId != null) {
|
||||
sql += " AND application_id = " + lit(applicationId);
|
||||
}
|
||||
@@ -294,7 +331,7 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
|
||||
/**
|
||||
* Format an Instant as a ClickHouse DateTime literal.
|
||||
* Uses java.sql.Timestamp to match the JVM→ClickHouse timezone convention
|
||||
* Uses java.sql.Timestamp to match the JVM-ClickHouse timezone convention
|
||||
* used by the JDBC driver, then truncates to second precision for DateTime
|
||||
* column compatibility.
|
||||
*/
|
||||
@@ -318,7 +355,7 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
* Build -Merge combinator SQL for the given view and time range.
|
||||
*/
|
||||
private String buildStatsSql(String view, Instant rangeFrom, Instant rangeTo,
|
||||
List<Filter> filters, boolean hasRunning) {
|
||||
List<Filter> filters, boolean hasRunning, String environment) {
|
||||
String runningCol = hasRunning ? "countIfMerge(running_count)" : "0";
|
||||
String sql = "SELECT " +
|
||||
"countMerge(total_count) AS total_count, " +
|
||||
@@ -330,6 +367,9 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
" WHERE tenant_id = " + lit(tenantId) +
|
||||
" AND bucket >= " + lit(rangeFrom) +
|
||||
" AND bucket < " + lit(rangeTo);
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
sql += " AND environment = " + lit(environment);
|
||||
}
|
||||
for (Filter f : filters) {
|
||||
sql += " AND " + f.column() + " = " + lit(f.value());
|
||||
}
|
||||
@@ -341,15 +381,15 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
* Uses literal SQL to avoid ClickHouse JDBC driver PreparedStatement issues.
|
||||
*/
|
||||
private ExecutionStats queryStats(String view, Instant from, Instant to,
|
||||
List<Filter> filters, boolean hasRunning) {
|
||||
List<Filter> filters, boolean hasRunning, String environment) {
|
||||
|
||||
String sql = buildStatsSql(view, from, to, filters, hasRunning);
|
||||
String sql = buildStatsSql(view, from, to, filters, hasRunning, environment);
|
||||
|
||||
long totalCount = 0, failedCount = 0, avgDuration = 0, p99Duration = 0, activeCount = 0;
|
||||
var currentResult = jdbc.query(sql, (rs, rowNum) -> {
|
||||
long tc = rs.getLong("total_count");
|
||||
long fc = rs.getLong("failed_count");
|
||||
long ds = rs.getLong("duration_sum"); // Nullable → 0 if null
|
||||
long ds = rs.getLong("duration_sum"); // Nullable -> 0 if null
|
||||
long p99 = (long) rs.getDouble("p99_duration"); // quantileMerge returns Float64
|
||||
long ac = rs.getLong("active_count");
|
||||
return new long[]{tc, fc, ds, p99, ac};
|
||||
@@ -364,7 +404,7 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
// Previous period (shifted back 24h)
|
||||
Instant prevFrom = from.minus(Duration.ofHours(24));
|
||||
Instant prevTo = to.minus(Duration.ofHours(24));
|
||||
String prevSql = buildStatsSql(view, prevFrom, prevTo, filters, hasRunning);
|
||||
String prevSql = buildStatsSql(view, prevFrom, prevTo, filters, hasRunning, environment);
|
||||
|
||||
long prevTotal = 0, prevFailed = 0, prevAvg = 0, prevP99 = 0;
|
||||
var prevResult = jdbc.query(prevSql, (rs, rowNum) -> {
|
||||
@@ -383,7 +423,7 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
|
||||
// Today total
|
||||
Instant todayStart = Instant.now().truncatedTo(ChronoUnit.DAYS);
|
||||
String todaySql = buildStatsSql(view, todayStart, Instant.now(), filters, hasRunning);
|
||||
String todaySql = buildStatsSql(view, todayStart, Instant.now(), filters, hasRunning, environment);
|
||||
|
||||
long totalToday = 0;
|
||||
var todayResult = jdbc.query(todaySql, (rs, rowNum) -> rs.getLong("total_count"));
|
||||
@@ -399,7 +439,7 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
*/
|
||||
private StatsTimeseries queryTimeseries(String view, Instant from, Instant to,
|
||||
int bucketCount, List<Filter> filters,
|
||||
boolean hasRunningCount) {
|
||||
boolean hasRunningCount, String environment) {
|
||||
long intervalSeconds = Duration.between(from, to).toSeconds() / Math.max(bucketCount, 1);
|
||||
if (intervalSeconds < 60) intervalSeconds = 60;
|
||||
|
||||
@@ -416,6 +456,9 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
" WHERE tenant_id = " + lit(tenantId) +
|
||||
" AND bucket >= " + lit(from) +
|
||||
" AND bucket < " + lit(to);
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
sql += " AND environment = " + lit(environment);
|
||||
}
|
||||
for (Filter f : filters) {
|
||||
sql += " AND " + f.column() + " = " + lit(f.value());
|
||||
}
|
||||
@@ -439,7 +482,7 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
*/
|
||||
private Map<String, StatsTimeseries> queryGroupedTimeseries(
|
||||
String view, String groupCol, Instant from, Instant to,
|
||||
int bucketCount, List<Filter> filters) {
|
||||
int bucketCount, List<Filter> filters, String environment) {
|
||||
|
||||
long intervalSeconds = Duration.between(from, to).toSeconds() / Math.max(bucketCount, 1);
|
||||
if (intervalSeconds < 60) intervalSeconds = 60;
|
||||
@@ -456,6 +499,9 @@ public class ClickHouseStatsStore implements StatsStore {
|
||||
" WHERE tenant_id = " + lit(tenantId) +
|
||||
" AND bucket >= " + lit(from) +
|
||||
" AND bucket < " + lit(to);
|
||||
if (environment != null && !environment.isBlank()) {
|
||||
sql += " AND environment = " + lit(environment);
|
||||
}
|
||||
for (Filter f : filters) {
|
||||
sql += " AND " + f.column() + " = " + lit(f.value());
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class ClickHouseLogStoreIT {
|
||||
}
|
||||
|
||||
private LogSearchRequest req(String application) {
|
||||
return new LogSearchRequest(null, null, application, null, null, null, null, null, null, 100, "desc");
|
||||
return new LogSearchRequest(null, null, application, null, null, null, null, null, null, null, 100, "desc");
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────
|
||||
@@ -99,7 +99,7 @@ class ClickHouseLogStoreIT {
|
||||
));
|
||||
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
null, List.of("ERROR"), "my-app", null, null, null, null, null, null, 100, "desc"));
|
||||
null, List.of("ERROR"), "my-app", null, null, null, null, null, null, null, 100, "desc"));
|
||||
|
||||
assertThat(result.data()).hasSize(1);
|
||||
assertThat(result.data().get(0).level()).isEqualTo("ERROR");
|
||||
@@ -116,7 +116,7 @@ class ClickHouseLogStoreIT {
|
||||
));
|
||||
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
null, List.of("WARN", "ERROR"), "my-app", null, null, null, null, null, null, 100, "desc"));
|
||||
null, List.of("WARN", "ERROR"), "my-app", null, null, null, null, null, null, null, 100, "desc"));
|
||||
|
||||
assertThat(result.data()).hasSize(2);
|
||||
}
|
||||
@@ -130,7 +130,7 @@ class ClickHouseLogStoreIT {
|
||||
));
|
||||
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
"order #12345", null, "my-app", null, null, null, null, null, null, 100, "desc"));
|
||||
"order #12345", null, "my-app", null, null, null, null, null, null, null, 100, "desc"));
|
||||
|
||||
assertThat(result.data()).hasSize(1);
|
||||
assertThat(result.data().get(0).message()).contains("order #12345");
|
||||
@@ -147,7 +147,7 @@ class ClickHouseLogStoreIT {
|
||||
));
|
||||
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
null, null, "my-app", null, "exchange-abc", null, null, null, null, 100, "desc"));
|
||||
null, null, "my-app", null, "exchange-abc", null, null, null, null, null, 100, "desc"));
|
||||
|
||||
assertThat(result.data()).hasSize(1);
|
||||
assertThat(result.data().get(0).message()).isEqualTo("msg with exchange");
|
||||
@@ -170,7 +170,7 @@ class ClickHouseLogStoreIT {
|
||||
Instant to = Instant.parse("2026-03-31T13:00:00Z");
|
||||
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
null, null, "my-app", null, null, null, from, to, null, 100, "desc"));
|
||||
null, null, "my-app", null, null, null, null, from, to, null, 100, "desc"));
|
||||
|
||||
assertThat(result.data()).hasSize(1);
|
||||
assertThat(result.data().get(0).message()).isEqualTo("noon");
|
||||
@@ -188,7 +188,7 @@ class ClickHouseLogStoreIT {
|
||||
|
||||
// No application filter — should return both
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
null, null, null, null, null, null, null, null, null, 100, "desc"));
|
||||
null, null, null, null, null, null, null, null, null, null, 100, "desc"));
|
||||
|
||||
assertThat(result.data()).hasSize(2);
|
||||
}
|
||||
@@ -202,7 +202,7 @@ class ClickHouseLogStoreIT {
|
||||
));
|
||||
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
null, null, "my-app", null, null, "OrderProcessor", null, null, null, 100, "desc"));
|
||||
null, null, "my-app", null, null, "OrderProcessor", null, null, null, null, 100, "desc"));
|
||||
|
||||
assertThat(result.data()).hasSize(1);
|
||||
assertThat(result.data().get(0).loggerName()).contains("OrderProcessor");
|
||||
@@ -221,7 +221,7 @@ class ClickHouseLogStoreIT {
|
||||
|
||||
// Page 1: limit 2
|
||||
LogSearchResponse page1 = store.search(new LogSearchRequest(
|
||||
null, null, "my-app", null, null, null, null, null, null, 2, "desc"));
|
||||
null, null, "my-app", null, null, null, null, null, null, null, 2, "desc"));
|
||||
|
||||
assertThat(page1.data()).hasSize(2);
|
||||
assertThat(page1.hasMore()).isTrue();
|
||||
@@ -230,7 +230,7 @@ class ClickHouseLogStoreIT {
|
||||
|
||||
// Page 2: use cursor
|
||||
LogSearchResponse page2 = store.search(new LogSearchRequest(
|
||||
null, null, "my-app", null, null, null, null, null, page1.nextCursor(), 2, "desc"));
|
||||
null, null, "my-app", null, null, null, null, null, null, page1.nextCursor(), 2, "desc"));
|
||||
|
||||
assertThat(page2.data()).hasSize(2);
|
||||
assertThat(page2.hasMore()).isTrue();
|
||||
@@ -238,7 +238,7 @@ class ClickHouseLogStoreIT {
|
||||
|
||||
// Page 3: last page
|
||||
LogSearchResponse page3 = store.search(new LogSearchRequest(
|
||||
null, null, "my-app", null, null, null, null, null, page2.nextCursor(), 2, "desc"));
|
||||
null, null, "my-app", null, null, null, null, null, null, page2.nextCursor(), 2, "desc"));
|
||||
|
||||
assertThat(page3.data()).hasSize(1);
|
||||
assertThat(page3.hasMore()).isFalse();
|
||||
@@ -257,7 +257,7 @@ class ClickHouseLogStoreIT {
|
||||
|
||||
// Filter for ERROR only, but counts should include all levels
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
null, List.of("ERROR"), "my-app", null, null, null, null, null, null, 100, "desc"));
|
||||
null, List.of("ERROR"), "my-app", null, null, null, null, null, null, null, 100, "desc"));
|
||||
|
||||
assertThat(result.data()).hasSize(1);
|
||||
assertThat(result.levelCounts()).containsEntry("INFO", 2L);
|
||||
@@ -275,7 +275,7 @@ class ClickHouseLogStoreIT {
|
||||
));
|
||||
|
||||
LogSearchResponse result = store.search(new LogSearchRequest(
|
||||
null, null, "my-app", null, null, null, null, null, null, 100, "asc"));
|
||||
null, null, "my-app", null, null, null, null, null, null, null, 100, "asc"));
|
||||
|
||||
assertThat(result.data()).hasSize(3);
|
||||
assertThat(result.data().get(0).message()).isEqualTo("msg-1");
|
||||
|
||||
@@ -118,7 +118,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_withNoFilters_returnsAllExecutions() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -130,7 +130,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_byStatus_filtersCorrectly() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
"FAILED", null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -145,7 +145,7 @@ class ClickHouseSearchIndexIT {
|
||||
// Time window covering exec-1 and exec-2 but not exec-3
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, baseTime, baseTime.plusMillis(1500), null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -158,7 +158,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_fullTextSearch_findsInErrorMessage() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, "NullPointerException", null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -170,7 +170,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_fullTextSearch_findsInInputBody() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, "12345", null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -182,7 +182,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_textInBody_searchesProcessorBodies() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, null, "Hello World", null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -194,7 +194,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_textInHeaders_searchesProcessorHeaders() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, null, null, "secret-token", null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -206,7 +206,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_textInErrors_searchesErrorFields() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, null, null, null, "Foo.bar",
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -218,7 +218,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_withHighlight_returnsSnippet() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, "NullPointerException", null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -230,7 +230,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_pagination_works() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, 0, 2, null, null);
|
||||
null, null, null, null, null, 0, 2, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -244,7 +244,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_byApplication_filtersCorrectly() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, "other-app", null, 0, 50, null, null);
|
||||
null, null, null, "other-app", null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -256,7 +256,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_byAgentIds_filtersCorrectly() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, List.of("agent-b"), 0, 50, null, null);
|
||||
null, null, null, null, List.of("agent-b"), 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -268,7 +268,7 @@ class ClickHouseSearchIndexIT {
|
||||
void count_returnsMatchingCount() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
"COMPLETED", null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
long count = searchIndex.count(request);
|
||||
|
||||
@@ -279,7 +279,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_multipleStatusFilter_works() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
"COMPLETED,FAILED", null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -290,7 +290,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_byCorrelationId_filtersCorrectly() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, null, null, "corr-1", null, null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
@@ -302,7 +302,7 @@ class ClickHouseSearchIndexIT {
|
||||
void search_byDurationRange_filtersCorrectly() {
|
||||
SearchRequest request = new SearchRequest(
|
||||
null, null, null, 300L, 600L, null, null, null, null, null,
|
||||
null, null, null, null, null, 0, 50, null, null);
|
||||
null, null, null, null, null, 0, 50, null, null, null);
|
||||
|
||||
SearchResult<ExecutionSummary> result = searchIndex.search(request);
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ class ClickHouseChunkPipelineIT {
|
||||
null, null, null, null, null, null,
|
||||
"ORD-123", null, null, null,
|
||||
null, null, null, null, null,
|
||||
0, 50, null, null));
|
||||
0, 50, null, null, null));
|
||||
assertThat(result.total()).isEqualTo(1);
|
||||
assertThat(result.data().get(0).executionId()).isEqualTo("pipeline-1");
|
||||
assertThat(result.data().get(0).status()).isEqualTo("COMPLETED");
|
||||
@@ -173,7 +173,7 @@ class ClickHouseChunkPipelineIT {
|
||||
null, null, null, null, null, null,
|
||||
null, "ABC-123", null, null,
|
||||
null, null, null, null, null,
|
||||
0, 50, null, null));
|
||||
0, 50, null, null, null));
|
||||
assertThat(bodyResult.total()).isEqualTo(1);
|
||||
|
||||
// Verify iteration data in processor_executions
|
||||
|
||||
@@ -156,7 +156,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
ExecutionStats stats = store.stats(from, to);
|
||||
ExecutionStats stats = store.stats(from, to, null);
|
||||
|
||||
assertThat(stats.totalCount()).isEqualTo(10);
|
||||
assertThat(stats.failedCount()).isEqualTo(2);
|
||||
@@ -170,10 +170,10 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
ExecutionStats app1 = store.statsForApp(from, to, "app-1");
|
||||
ExecutionStats app1 = store.statsForApp(from, to, "app-1", null);
|
||||
assertThat(app1.totalCount()).isEqualTo(8);
|
||||
|
||||
ExecutionStats app2 = store.statsForApp(from, to, "app-2");
|
||||
ExecutionStats app2 = store.statsForApp(from, to, "app-2", null);
|
||||
assertThat(app2.totalCount()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
ExecutionStats routeA = store.statsForRoute(from, to, "route-a", List.of());
|
||||
ExecutionStats routeA = store.statsForRoute(from, to, "route-a", List.of(), null);
|
||||
assertThat(routeA.totalCount()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
StatsTimeseries ts = store.timeseries(from, to, 5);
|
||||
StatsTimeseries ts = store.timeseries(from, to, 5, null);
|
||||
|
||||
assertThat(ts.buckets()).isNotEmpty();
|
||||
long totalAcrossBuckets = ts.buckets().stream()
|
||||
@@ -206,7 +206,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
StatsTimeseries ts = store.timeseriesForApp(from, to, 5, "app-1");
|
||||
StatsTimeseries ts = store.timeseriesForApp(from, to, 5, "app-1", null);
|
||||
|
||||
long totalAcrossBuckets = ts.buckets().stream()
|
||||
.mapToLong(StatsTimeseries.TimeseriesBucket::totalCount).sum();
|
||||
@@ -218,7 +218,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
Map<String, StatsTimeseries> grouped = store.timeseriesGroupedByApp(from, to, 5);
|
||||
Map<String, StatsTimeseries> grouped = store.timeseriesGroupedByApp(from, to, 5, null);
|
||||
|
||||
assertThat(grouped).containsKeys("app-1", "app-2");
|
||||
}
|
||||
@@ -228,7 +228,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
Map<String, StatsTimeseries> grouped = store.timeseriesGroupedByRoute(from, to, 5, "app-1");
|
||||
Map<String, StatsTimeseries> grouped = store.timeseriesGroupedByRoute(from, to, 5, "app-1", null);
|
||||
|
||||
assertThat(grouped).containsKeys("route-a", "route-b");
|
||||
}
|
||||
@@ -244,7 +244,7 @@ class ClickHouseStatsStoreIT {
|
||||
// compliant (<=250ms): exec-01(200), exec-05(100), exec-06(150), exec-07(50), exec-08(60) = 5
|
||||
// total non-running: 9
|
||||
// compliance = 5/9 * 100 ~ 55.56%
|
||||
double sla = store.slaCompliance(from, to, 250, null, null);
|
||||
double sla = store.slaCompliance(from, to, 250, null, null, null);
|
||||
assertThat(sla).isBetween(55.0, 56.0);
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
List<TopError> errors = store.topErrors(from, to, null, null, 10);
|
||||
List<TopError> errors = store.topErrors(from, to, null, null, 10, null);
|
||||
|
||||
assertThat(errors).isNotEmpty();
|
||||
assertThat(errors.get(0).errorType()).isEqualTo("NPE");
|
||||
@@ -269,7 +269,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
int count = store.activeErrorTypes(from, to, "app-1");
|
||||
int count = store.activeErrorTypes(from, to, "app-1", null);
|
||||
|
||||
assertThat(count).isEqualTo(1); // only "NPE"
|
||||
}
|
||||
@@ -281,7 +281,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
List<PunchcardCell> cells = store.punchcard(from, to, null);
|
||||
List<PunchcardCell> cells = store.punchcard(from, to, null, null);
|
||||
|
||||
assertThat(cells).isNotEmpty();
|
||||
long totalCount = cells.stream().mapToLong(PunchcardCell::totalCount).sum();
|
||||
@@ -294,7 +294,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
// threshold=250ms
|
||||
Map<String, long[]> counts = store.slaCountsByApp(from, to, 250);
|
||||
Map<String, long[]> counts = store.slaCountsByApp(from, to, 250, null);
|
||||
|
||||
assertThat(counts).containsKeys("app-1", "app-2");
|
||||
// app-1: 8 total executions, all non-RUNNING
|
||||
@@ -313,7 +313,7 @@ class ClickHouseStatsStoreIT {
|
||||
Instant from = BASE.minusSeconds(60);
|
||||
Instant to = BASE.plusSeconds(300);
|
||||
|
||||
Map<String, long[]> counts = store.slaCountsByRoute(from, to, "app-1", 250);
|
||||
Map<String, long[]> counts = store.slaCountsByRoute(from, to, "app-1", 250, null);
|
||||
|
||||
assertThat(counts).containsKeys("route-a", "route-b");
|
||||
// route-a: exec-01(200)OK, exec-02(300)NO, exec-03(400)NO, exec-04(500)NO,
|
||||
|
||||
Reference in New Issue
Block a user