// Package metrics provides Prometheus metrics for upaas. // //nolint:revive // "metrics" matches the domain; runtime/metrics is rarely imported directly package metrics import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/fx" ) // Params contains dependencies for Metrics. type Params struct { fx.In } // Metrics holds all Prometheus metrics for the application. type Metrics struct { // Deployment metrics. DeploymentsTotal *prometheus.CounterVec DeploymentDuration *prometheus.HistogramVec DeploymentsInFlight *prometheus.GaugeVec // Container health metrics. ContainerHealthy *prometheus.GaugeVec // Webhook metrics. WebhookEventsTotal *prometheus.CounterVec // HTTP request metrics. HTTPRequestsTotal *prometheus.CounterVec HTTPRequestDuration *prometheus.HistogramVec HTTPResponseSizeBytes *prometheus.HistogramVec // Audit log metrics. AuditEventsTotal *prometheus.CounterVec } // New creates a new Metrics instance with all Prometheus metrics registered // in the default Prometheus registry. func New(_ fx.Lifecycle, _ Params) (*Metrics, error) { return newMetrics(promauto.With(prometheus.DefaultRegisterer)), nil } // NewForTest creates a Metrics instance with a custom registry for test isolation. func NewForTest(reg prometheus.Registerer) *Metrics { return newMetrics(promauto.With(reg)) } // newMetrics creates a Metrics instance using the given factory. func newMetrics(factory promauto.Factory) *Metrics { return &Metrics{ DeploymentsTotal: newDeploymentsTotal(factory), DeploymentDuration: newDeploymentDuration(factory), DeploymentsInFlight: newDeploymentsInFlight(factory), ContainerHealthy: newContainerHealthy(factory), WebhookEventsTotal: newWebhookEventsTotal(factory), HTTPRequestsTotal: newHTTPRequestsTotal(factory), HTTPRequestDuration: newHTTPRequestDuration(factory), HTTPResponseSizeBytes: newHTTPResponseSizeBytes(factory), AuditEventsTotal: newAuditEventsTotal(factory), } } func newDeploymentsTotal(f promauto.Factory) *prometheus.CounterVec { return f.NewCounterVec(prometheus.CounterOpts{ Namespace: "upaas", Subsystem: "deployments", Name: "total", Help: "Total number of deployments by app and status.", }, []string{"app", "status"}) } func newDeploymentDuration(f promauto.Factory) *prometheus.HistogramVec { return f.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "upaas", Subsystem: "deployments", Name: "duration_seconds", Help: "Duration of deployments in seconds by app and status.", Buckets: []float64{10, 30, 60, 120, 300, 600, 1800}, }, []string{"app", "status"}) } func newDeploymentsInFlight(f promauto.Factory) *prometheus.GaugeVec { return f.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "upaas", Subsystem: "deployments", Name: "in_flight", Help: "Number of deployments currently in progress by app.", }, []string{"app"}) } func newContainerHealthy(f promauto.Factory) *prometheus.GaugeVec { return f.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "upaas", Subsystem: "container", Name: "healthy", Help: "Whether the app container is healthy (1) or unhealthy (0).", }, []string{"app"}) } func newWebhookEventsTotal(f promauto.Factory) *prometheus.CounterVec { return f.NewCounterVec(prometheus.CounterOpts{ Namespace: "upaas", Subsystem: "webhook", Name: "events_total", Help: "Total number of webhook events by app, event type, and matched status.", }, []string{"app", "event_type", "matched"}) } func newHTTPRequestsTotal(f promauto.Factory) *prometheus.CounterVec { return f.NewCounterVec(prometheus.CounterOpts{ Namespace: "upaas", Subsystem: "http", Name: "requests_total", Help: "Total number of HTTP requests by method and status code.", }, []string{"method", "status_code"}) } func newHTTPRequestDuration(f promauto.Factory) *prometheus.HistogramVec { return f.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "upaas", Subsystem: "http", Name: "request_duration_seconds", Help: "Duration of HTTP requests in seconds by method.", Buckets: prometheus.DefBuckets, }, []string{"method"}) } //nolint:mnd // bucket boundaries are domain-specific constants func newHTTPResponseSizeBytes(f promauto.Factory) *prometheus.HistogramVec { return f.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "upaas", Subsystem: "http", Name: "response_size_bytes", Help: "Size of HTTP responses in bytes by method.", Buckets: prometheus.ExponentialBuckets(100, 10, 7), }, []string{"method"}) } func newAuditEventsTotal(f promauto.Factory) *prometheus.CounterVec { return f.NewCounterVec(prometheus.CounterOpts{ Namespace: "upaas", Subsystem: "audit", Name: "events_total", Help: "Total number of audit log events by action.", }, []string{"action"}) }