Files
o11y/pkg/apiserver/o11yapiserver/metricsexplorer.go
T
zeekay 46c6241c52 Spell every o11y caller /v1/o11y
The server moved off /api/vN; the integration suite, handler tests,
install.sh's health wait and the docs still called the old tree.

Claude-Session: https://claude.ai/code/session_01TUxvfuta1Z44KVNYuAz71F
2026-09-24 15:05:09 -07:00

217 lines
11 KiB
Go

package o11yapiserver
import (
"net/http"
"github.com/hanzoai/o11y/pkg/http/handler"
"github.com/hanzoai/o11y/pkg/http/routing"
"github.com/hanzoai/o11y/pkg/types"
"github.com/hanzoai/o11y/pkg/types/metricsexplorertypes"
)
// ALL of these routes are ALSO declared as typed ops at the module's mount seam
// (metrics.go in the repo root), which is what carries them into the composed
// document, the SDK, the CLI and the agent surface. That is a second DISPATCH,
// never a second implementation: the ops answer by handing the call to this
// router, so the handlers below stay the one place the work is performed — and
// the gates declared here (ViewAccess on the reads, EditAccess on the metadata
// write) stay the one place access is decided. Both halves are needed — the ops
// serve the composed binary, this router serves the standalone process, which
// has no native router to register an op on — so deleting either drops one of
// the two deployments.
func (provider *provider) addMetricsExplorerRoutes(router routing.Router) {
router.Get("/v1/o11y/metrics", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.ListMetrics),
handler.OpenAPIDef{
ID: "ListMetrics",
Tags: []string{"metrics"},
Summary: "List metric names",
Description: "This endpoint returns a list of distinct metric names within the specified time range",
Request: nil,
RequestQuery: new(metricsexplorertypes.ListMetricsParams),
RequestContentType: "",
Response: new(metricsexplorertypes.ListMetricsResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
router.Post("/v1/o11y/metrics/stats", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetStats),
handler.OpenAPIDef{
ID: "GetMetricsStats",
Tags: []string{"metrics"},
Summary: "Get metrics statistics",
Description: "This endpoint provides list of metrics with their number of samples and timeseries for the given time range",
Request: new(metricsexplorertypes.StatsRequest),
RequestContentType: "application/json",
Response: new(metricsexplorertypes.StatsResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
router.Post("/v1/o11y/metrics/treemap", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetTreemap),
handler.OpenAPIDef{
ID: "GetMetricsTreemap",
Tags: []string{"metrics"},
Summary: "Get metrics treemap",
Description: "This endpoint returns a treemap visualization showing the proportional distribution of metrics by sample count or time series count",
Request: new(metricsexplorertypes.TreemapRequest),
RequestContentType: "application/json",
Response: new(metricsexplorertypes.TreemapResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
router.Get("/v1/o11y/metrics/attributes", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricAttributes),
handler.OpenAPIDef{
ID: "GetMetricAttributes",
Tags: []string{"metrics"},
Summary: "Get metric attributes",
Description: "This endpoint returns attribute keys and their unique values for a specified metric",
Request: nil,
RequestQuery: new(metricsexplorertypes.MetricAttributesRequest),
RequestContentType: "",
Response: new(metricsexplorertypes.MetricAttributesResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
router.Get("/v1/o11y/metrics/metadata", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricMetadata),
handler.OpenAPIDef{
ID: "GetMetricMetadata",
Tags: []string{"metrics"},
Summary: "Get metric metadata",
Description: "This endpoint returns metadata information like metric description, unit, type, temporality, monotonicity for a specified metric",
Request: nil,
RequestQuery: new(metricsexplorertypes.MetricNameQuery),
RequestContentType: "",
Response: new(metricsexplorertypes.MetricMetadata),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
router.Post("/v1/o11y/metrics/metadata", handler.New(
provider.authzMiddleware.EditAccess(provider.metricsExplorerHandler.UpdateMetricMetadata),
handler.OpenAPIDef{
ID: "UpdateMetricMetadata",
Tags: []string{"metrics"},
Summary: "Update metric metadata",
Description: "This endpoint helps to update metadata information like metric description, unit, type, temporality, monotonicity for a specified metric",
Request: new(metricsexplorertypes.UpdateMetricMetadataRequest),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
}))
router.Get("/v1/o11y/metrics/highlights", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricHighlights),
handler.OpenAPIDef{
ID: "GetMetricHighlights",
Tags: []string{"metrics"},
Summary: "Get metric highlights",
Description: "This endpoint returns highlights like number of datapoints, totaltimeseries, active time series, last received time for a specified metric",
Request: nil,
RequestQuery: new(metricsexplorertypes.MetricNameQuery),
RequestContentType: "",
Response: new(metricsexplorertypes.MetricHighlightsResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
router.Get("/v1/o11y/metrics/alerts", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricAlerts),
handler.OpenAPIDef{
ID: "GetMetricAlerts",
Tags: []string{"metrics"},
Summary: "Get metric alerts",
Description: "This endpoint returns associated alerts for a specified metric",
Request: nil,
RequestQuery: new(metricsexplorertypes.MetricNameQuery),
RequestContentType: "",
Response: new(metricsexplorertypes.MetricAlertsResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
// GetMetricDashboardsV2 is the one handler for /v1/o11y/metrics/dashboards.
router.Get("/v1/o11y/metrics/dashboards", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetMetricDashboardsV2),
handler.OpenAPIDef{
ID: "GetMetricDashboardsV2",
Tags: []string{"metrics"},
Summary: "Get metric dashboards (v2)",
Description: "This endpoint returns associated v2 dashboards for a specified metric",
Request: nil,
RequestQuery: new(metricsexplorertypes.MetricNameQuery),
RequestContentType: "",
Response: new(metricsexplorertypes.MetricDashboardPanelsResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
router.Post("/v1/o11y/metrics/inspect", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.InspectMetrics),
handler.OpenAPIDef{
ID: "InspectMetrics",
Tags: []string{"metrics"},
Summary: "Inspect raw metric data points",
Description: "Returns raw time series data points for a metric within a time range (max 30 minutes). Each series includes labels and timestamp/value pairs.",
Request: new(metricsexplorertypes.InspectMetricsRequest),
RequestContentType: "application/json",
Response: new(metricsexplorertypes.InspectMetricsResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
router.Get("/v1/o11y/metrics/onboarding", handler.New(
provider.authzMiddleware.ViewAccess(provider.metricsExplorerHandler.GetOnboardingStatus),
handler.OpenAPIDef{
ID: "GetMetricsOnboardingStatus",
Tags: []string{"metrics"},
Summary: "Check if non-O11y metrics have been received",
Description: "Lightweight endpoint that checks if any non-O11y metrics have been ingested, used for onboarding status detection",
Request: nil,
RequestContentType: "",
Response: new(metricsexplorertypes.MetricsOnboardingResponse),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
}))
}