1
0
forked from wrenn/wrenn

Add 5m, 1h, 6h, 12h range filters to metrics endpoint

Maps each user-facing range to the appropriate underlying ring buffer
tier and applies a time cutoff filter. No new ring buffers needed —
5m/10m read from the 10m tier, 1h/2h from the 2h tier, 6h/12h/24h
from the 24h tier.
This commit is contained in:
2026-03-25 20:44:28 +06:00
parent 9acdbb5ae9
commit 49b0b646a8
3 changed files with 65 additions and 17 deletions

View File

@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"net/http" "net/http"
"time"
"connectrpc.com/connect" "connectrpc.com/connect"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -45,8 +46,9 @@ func (h *sandboxMetricsHandler) GetMetrics(w http.ResponseWriter, r *http.Reques
if rangeTier == "" { if rangeTier == "" {
rangeTier = "10m" rangeTier = "10m"
} }
if rangeTier != "10m" && rangeTier != "2h" && rangeTier != "24h" { validRanges := map[string]bool{"5m": true, "10m": true, "1h": true, "2h": true, "6h": true, "12h": true, "24h": true}
writeError(w, http.StatusBadRequest, "invalid_request", "range must be 10m, 2h, or 24h") if !validRanges[rangeTier] {
writeError(w, http.StatusBadRequest, "invalid_request", "range must be one of: 5m, 10m, 1h, 2h, 6h, 12h, 24h")
return return
} }
@ -102,23 +104,41 @@ func (h *sandboxMetricsHandler) getFromAgent(w http.ResponseWriter, r *http.Requ
}) })
} }
// rangeToDB maps a user-facing range filter to the DB tier and cutoff duration.
var rangeToDB = map[string]struct {
tier string
cutoff time.Duration
}{
"5m": {"10m", 5 * time.Minute},
"10m": {"10m", 10 * time.Minute},
"1h": {"2h", 1 * time.Hour},
"2h": {"2h", 2 * time.Hour},
"6h": {"24h", 6 * time.Hour},
"12h": {"24h", 12 * time.Hour},
"24h": {"24h", 24 * time.Hour},
}
func (h *sandboxMetricsHandler) getFromDB(ctx context.Context, w http.ResponseWriter, sandboxID, rangeTier string) { func (h *sandboxMetricsHandler) getFromDB(ctx context.Context, w http.ResponseWriter, sandboxID, rangeTier string) {
mapping := rangeToDB[rangeTier]
rows, err := h.db.GetSandboxMetricPoints(ctx, db.GetSandboxMetricPointsParams{ rows, err := h.db.GetSandboxMetricPoints(ctx, db.GetSandboxMetricPointsParams{
SandboxID: sandboxID, SandboxID: sandboxID,
Tier: rangeTier, Tier: mapping.tier,
}) })
if err != nil { if err != nil {
writeError(w, http.StatusInternalServerError, "internal_error", "failed to read metrics") writeError(w, http.StatusInternalServerError, "internal_error", "failed to read metrics")
return return
} }
points := make([]metricPointResponse, len(rows)) threshold := time.Now().Add(-mapping.cutoff).Unix()
for i, row := range rows { var points []metricPointResponse
points[i] = metricPointResponse{ for _, row := range rows {
TimestampUnix: row.Ts, if row.Ts >= threshold {
CPUPct: row.CpuPct, points = append(points, metricPointResponse{
MemBytes: row.MemBytes, TimestampUnix: row.Ts,
DiskBytes: row.DiskBytes, CPUPct: row.CpuPct,
MemBytes: row.MemBytes,
DiskBytes: row.DiskBytes,
})
} }
} }

View File

@ -782,9 +782,9 @@ paths:
required: false required: false
schema: schema:
type: string type: string
enum: ["10m", "2h", "24h"] enum: ["5m", "10m", "1h", "2h", "6h", "12h", "24h"]
default: "10m" default: "10m"
description: Time range tier to query description: Time range filter to query
responses: responses:
"200": "200":
description: Metrics retrieved description: Metrics retrieved
@ -2042,7 +2042,7 @@ components:
type: string type: string
range: range:
type: string type: string
enum: ["10m", "2h", "24h"] enum: ["5m", "10m", "1h", "2h", "6h", "12h", "24h"]
points: points:
type: array type: array
items: items:

View File

@ -1348,16 +1348,44 @@ func (m *Manager) GetMetrics(sandboxID, rangeTier string) ([]MetricPoint, error)
return nil, nil return nil, nil
} }
// Map the requested range to the appropriate ring tier and time cutoff.
var points []MetricPoint
var cutoff time.Duration
switch rangeTier { switch rangeTier {
case "5m":
points = sb.ring.Get10m()
cutoff = 5 * time.Minute
case "10m": case "10m":
return sb.ring.Get10m(), nil points = sb.ring.Get10m()
cutoff = 10 * time.Minute
case "1h":
points = sb.ring.Get2h()
cutoff = 1 * time.Hour
case "2h": case "2h":
return sb.ring.Get2h(), nil points = sb.ring.Get2h()
cutoff = 2 * time.Hour
case "6h":
points = sb.ring.Get24h()
cutoff = 6 * time.Hour
case "12h":
points = sb.ring.Get24h()
cutoff = 12 * time.Hour
case "24h": case "24h":
return sb.ring.Get24h(), nil points = sb.ring.Get24h()
cutoff = 24 * time.Hour
default: default:
return nil, fmt.Errorf("invalid range: %s (valid: 10m, 2h, 24h)", rangeTier) return nil, fmt.Errorf("invalid range: %s (valid: 5m, 10m, 1h, 2h, 6h, 12h, 24h)", rangeTier)
} }
// Filter points to the requested time window.
threshold := time.Now().Add(-cutoff)
filtered := points[:0:0]
for _, p := range points {
if !p.Timestamp.Before(threshold) {
filtered = append(filtered, p)
}
}
return filtered, nil
} }
// FlushMetrics returns all three tier ring buffers, clears the ring, and // FlushMetrics returns all three tier ring buffers, clears the ring, and