diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/OpenApiConfig.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/OpenApiConfig.java index 970045e8..d75e8258 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/OpenApiConfig.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/OpenApiConfig.java @@ -33,7 +33,8 @@ public class OpenApiConfig { "SearchResultExecutionSummary", "UserInfo", "ProcessorNode", "AppCatalogEntry", "RouteSummary", "AgentSummary", - "RouteMetrics", "AgentEventResponse", "AgentInstanceResponse" + "RouteMetrics", "AgentEventResponse", "AgentInstanceResponse", + "ProcessorMetrics" ); @Bean diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/RouteMetricsController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/RouteMetricsController.java index 9c3d48fc..63aebc15 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/RouteMetricsController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/RouteMetricsController.java @@ -1,5 +1,6 @@ package com.cameleer3.server.app.controller; +import com.cameleer3.server.app.dto.ProcessorMetrics; import com.cameleer3.server.app.dto.RouteMetrics; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -108,4 +109,56 @@ public class RouteMetricsController { return ResponseEntity.ok(metrics); } + + @GetMapping("/metrics/processors") + @Operation(summary = "Get processor metrics", + description = "Returns aggregated performance metrics per processor for the given route and time window") + @ApiResponse(responseCode = "200", description = "Metrics returned") + public ResponseEntity> getProcessorMetrics( + @RequestParam String routeId, + @RequestParam(required = false) String appId, + @RequestParam(required = false) Instant from, + @RequestParam(required = false) Instant to) { + + Instant toInstant = to != null ? to : Instant.now(); + Instant fromInstant = from != null ? from : toInstant.minus(24, ChronoUnit.HOURS); + + var sql = new StringBuilder( + "SELECT processor_id, processor_type, route_id, group_name, " + + "SUM(total_count) AS total_count, " + + "SUM(failed_count) AS failed_count, " + + "CASE WHEN SUM(total_count) > 0 THEN SUM(duration_sum)::double precision / SUM(total_count) ELSE 0 END AS avg_duration_ms, " + + "MAX(p99_duration) AS p99_duration_ms " + + "FROM stats_1m_processor_detail " + + "WHERE bucket >= ? AND bucket < ? AND route_id = ?"); + var params = new ArrayList(); + params.add(Timestamp.from(fromInstant)); + params.add(Timestamp.from(toInstant)); + params.add(routeId); + + if (appId != null) { + sql.append(" AND group_name = ?"); + params.add(appId); + } + sql.append(" GROUP BY processor_id, processor_type, route_id, group_name"); + sql.append(" ORDER BY SUM(total_count) DESC"); + + List metrics = jdbc.query(sql.toString(), (rs, rowNum) -> { + long totalCount = rs.getLong("total_count"); + long failedCount = rs.getLong("failed_count"); + double errorRate = failedCount > 0 ? (double) failedCount / totalCount : 0.0; + return new ProcessorMetrics( + rs.getString("processor_id"), + rs.getString("processor_type"), + rs.getString("route_id"), + rs.getString("group_name"), + totalCount, + failedCount, + rs.getDouble("avg_duration_ms"), + rs.getDouble("p99_duration_ms"), + errorRate); + }, params.toArray()); + + return ResponseEntity.ok(metrics); + } } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/ProcessorMetrics.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/ProcessorMetrics.java new file mode 100644 index 00000000..442e1489 --- /dev/null +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/ProcessorMetrics.java @@ -0,0 +1,15 @@ +package com.cameleer3.server.app.dto; + +import jakarta.validation.constraints.NotNull; + +public record ProcessorMetrics( + @NotNull String processorId, + @NotNull String processorType, + @NotNull String routeId, + @NotNull String appId, + long totalCount, + long failedCount, + double avgDurationMs, + double p99DurationMs, + double errorRate +) {}