feat: add GET /agents/{id}/metrics endpoint for JVM metrics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ public class OpenApiConfig {
|
||||
"ProcessorNode",
|
||||
"AppCatalogEntry", "RouteSummary", "AgentSummary",
|
||||
"RouteMetrics", "AgentEventResponse", "AgentInstanceResponse",
|
||||
"ProcessorMetrics"
|
||||
"ProcessorMetrics", "AgentMetricsResponse", "MetricBucket"
|
||||
);
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.cameleer3.server.app.controller;
|
||||
|
||||
import com.cameleer3.server.app.dto.AgentMetricsResponse;
|
||||
import com.cameleer3.server.app.dto.MetricBucket;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/agents/{agentId}/metrics")
|
||||
public class AgentMetricsController {
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public AgentMetricsController(JdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public AgentMetricsResponse getMetrics(
|
||||
@PathVariable String agentId,
|
||||
@RequestParam String names,
|
||||
@RequestParam(required = false) Instant from,
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(defaultValue = "60") int buckets) {
|
||||
|
||||
if (from == null) from = Instant.now().minus(1, ChronoUnit.HOURS);
|
||||
if (to == null) to = Instant.now();
|
||||
|
||||
List<String> metricNames = Arrays.asList(names.split(","));
|
||||
long intervalMs = (to.toEpochMilli() - from.toEpochMilli()) / Math.max(buckets, 1);
|
||||
String intervalStr = intervalMs + " milliseconds";
|
||||
|
||||
Map<String, List<MetricBucket>> result = new LinkedHashMap<>();
|
||||
for (String name : metricNames) {
|
||||
result.put(name.trim(), new ArrayList<>());
|
||||
}
|
||||
|
||||
String sql = """
|
||||
SELECT time_bucket(CAST(? AS interval), collected_at) AS bucket,
|
||||
metric_name,
|
||||
AVG(metric_value) AS avg_value
|
||||
FROM agent_metrics
|
||||
WHERE agent_id = ?
|
||||
AND collected_at >= ? AND collected_at < ?
|
||||
AND metric_name = ANY(?)
|
||||
GROUP BY bucket, metric_name
|
||||
ORDER BY bucket
|
||||
""";
|
||||
|
||||
String[] namesArray = metricNames.stream().map(String::trim).toArray(String[]::new);
|
||||
jdbc.query(sql, rs -> {
|
||||
String metricName = rs.getString("metric_name");
|
||||
Instant bucket = rs.getTimestamp("bucket").toInstant();
|
||||
double value = rs.getDouble("avg_value");
|
||||
result.computeIfAbsent(metricName, k -> new ArrayList<>())
|
||||
.add(new MetricBucket(bucket, value));
|
||||
}, intervalStr, agentId, from, to, namesArray);
|
||||
|
||||
return new AgentMetricsResponse(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cameleer3.server.app.dto;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record AgentMetricsResponse(
|
||||
@NotNull Map<String, List<MetricBucket>> metrics
|
||||
) {}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cameleer3.server.app.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record MetricBucket(
|
||||
@NotNull Instant time,
|
||||
double value
|
||||
) {}
|
||||
@@ -80,6 +80,7 @@ public class SecurityConfig {
|
||||
// Read-only data endpoints — viewer+
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/executions/**").hasAnyRole("VIEWER", "OPERATOR", "ADMIN")
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/diagrams/**").hasAnyRole("VIEWER", "OPERATOR", "ADMIN")
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/agents/*/metrics").hasAnyRole("VIEWER", "OPERATOR", "ADMIN")
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/agents").hasAnyRole("VIEWER", "OPERATOR", "ADMIN")
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/agents/events-log").hasAnyRole("VIEWER", "OPERATOR", "ADMIN")
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/routes/**").hasAnyRole("VIEWER", "OPERATOR", "ADMIN")
|
||||
|
||||
Reference in New Issue
Block a user