1
0
forked from wrenn/wrenn
Files
wrenn-releases/internal/api/handlers_stats.go
pptx704 a5ad3731f2 Refactored to maintain a separate cloud version
Moves 12 packages from internal/ to pkg/ (config, id, validate, events, db,
auth, lifecycle, scheduler, channels, audit, service) so they can be imported
by the enterprise repo as a Go module dependency.

Introduces pkg/cpextension (shared Extension interface + ServerContext) and
pkg/cpserver (Run() entrypoint with functional options) so the enterprise
main.go can call cpserver.Run(cpserver.WithExtensions(...)) without duplicating
the 20-step server bootstrap. Adds db/migrations/embed.go for go:embed access
to OSS SQL migrations from the enterprise module.

cmd/control-plane/main.go is reduced to a 10-line wrapper around cpserver.Run.
2026-04-15 21:41:48 +06:00

96 lines
2.5 KiB
Go

package api
import (
"log/slog"
"net/http"
"time"
"git.omukk.dev/wrenn/wrenn/pkg/auth"
"git.omukk.dev/wrenn/wrenn/pkg/service"
)
type statsHandler struct {
svc *service.StatsService
}
func newStatsHandler(svc *service.StatsService) *statsHandler {
return &statsHandler{svc: svc}
}
type statsCurrentResponse struct {
RunningCount int32 `json:"running_count"`
VCPUsReserved int32 `json:"vcpus_reserved"`
MemoryMBReserved int32 `json:"memory_mb_reserved"`
}
type statsPeaksResponse struct {
RunningCount int32 `json:"running_count"`
VCPUs int32 `json:"vcpus"`
MemoryMB int32 `json:"memory_mb"`
}
type statsSeriesResponse struct {
Labels []string `json:"labels"`
Running []int32 `json:"running"`
VCPUs []int32 `json:"vcpus"`
MemoryMB []int32 `json:"memory_mb"`
}
type statsResponse struct {
Range string `json:"range"`
Current statsCurrentResponse `json:"current"`
Peaks statsPeaksResponse `json:"peaks"`
Series statsSeriesResponse `json:"series"`
}
// GetStats handles GET /v1/capsules/stats?range=5m|1h|6h|24h|30d
func (h *statsHandler) GetStats(w http.ResponseWriter, r *http.Request) {
ac := auth.MustFromContext(r.Context())
rangeParam := r.URL.Query().Get("range")
if rangeParam == "" {
rangeParam = string(service.Range1h)
}
tr := service.TimeRange(rangeParam)
if !service.ValidRange(tr) {
writeError(w, http.StatusBadRequest, "invalid_request", "range must be one of: 5m, 1h, 6h, 24h, 30d")
return
}
current, peaks, series, err := h.svc.GetStats(r.Context(), ac.TeamID, tr)
if err != nil {
slog.Error("stats handler: get stats failed", "team_id", ac.TeamID, "error", err)
writeError(w, http.StatusInternalServerError, "internal_error", "failed to retrieve stats")
return
}
resp := statsResponse{
Range: rangeParam,
Current: statsCurrentResponse{
RunningCount: current.RunningCount,
VCPUsReserved: current.VCPUsReserved,
MemoryMBReserved: current.MemoryMBReserved,
},
Peaks: statsPeaksResponse{
RunningCount: peaks.RunningCount,
VCPUs: peaks.VCPUs,
MemoryMB: peaks.MemoryMB,
},
Series: statsSeriesResponse{
Labels: make([]string, len(series)),
Running: make([]int32, len(series)),
VCPUs: make([]int32, len(series)),
MemoryMB: make([]int32, len(series)),
},
}
for i, pt := range series {
resp.Series.Labels[i] = pt.Bucket.UTC().Format(time.RFC3339)
resp.Series.Running[i] = pt.RunningCount
resp.Series.VCPUs[i] = pt.VCPUsReserved
resp.Series.MemoryMB[i] = pt.MemoryMBReserved
}
writeJSON(w, http.StatusOK, resp)
}