160 lines
4.4 KiB
Go
160 lines
4.4 KiB
Go
// Copyright (C) 2025 wangyusong
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package metric
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
"github.com/glidea/zenfeed/pkg/model"
|
|
telemetrymodel "github.com/glidea/zenfeed/pkg/telemetry/model"
|
|
)
|
|
|
|
func Handler() http.Handler {
|
|
return promhttp.Handler()
|
|
}
|
|
|
|
var (
|
|
operationInFlight = promauto.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: model.AppName,
|
|
Name: "operation_in_flight",
|
|
Help: "Number of operations in flight.",
|
|
},
|
|
[]string{
|
|
telemetrymodel.KeyComponent,
|
|
telemetrymodel.KeyComponentInstance,
|
|
telemetrymodel.KeyOperation,
|
|
},
|
|
)
|
|
|
|
operationTotal = promauto.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: model.AppName,
|
|
Name: "operation_total",
|
|
Help: "Total number of operations.",
|
|
},
|
|
[]string{
|
|
telemetrymodel.KeyComponent,
|
|
telemetrymodel.KeyComponentInstance,
|
|
telemetrymodel.KeyOperation,
|
|
telemetrymodel.KeyResult,
|
|
},
|
|
)
|
|
|
|
operationDuration = promauto.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: model.AppName,
|
|
Name: "operation_duration_seconds",
|
|
Help: "Histogram of operation latencies in seconds.",
|
|
Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 20},
|
|
},
|
|
[]string{
|
|
telemetrymodel.KeyComponent,
|
|
telemetrymodel.KeyComponentInstance,
|
|
telemetrymodel.KeyOperation,
|
|
telemetrymodel.KeyResult,
|
|
},
|
|
)
|
|
)
|
|
|
|
type ctxKey uint8
|
|
|
|
const (
|
|
ctxKeyComponent ctxKey = iota
|
|
ctxKeyInstance
|
|
ctxKeyOperation
|
|
ctxKeyStartTime
|
|
)
|
|
|
|
func StartWith(ctx context.Context, keyvals ...any) context.Context {
|
|
// Extend from parent context.
|
|
component, instance, operation, _ := parseFrom(ctx)
|
|
|
|
// Parse component and operation... from keyvals.
|
|
for i := 0; i < len(keyvals); i += 2 {
|
|
if i+1 < len(keyvals) {
|
|
switch keyvals[i] {
|
|
case telemetrymodel.KeyComponent:
|
|
component = keyvals[i+1].(string)
|
|
case telemetrymodel.KeyComponentInstance:
|
|
instance = keyvals[i+1].(string)
|
|
case telemetrymodel.KeyOperation:
|
|
operation = keyvals[i+1].(string)
|
|
}
|
|
}
|
|
}
|
|
if component == "" || operation == "" {
|
|
panic("missing required keyvals")
|
|
}
|
|
|
|
// Record operation in flight.
|
|
operationInFlight.WithLabelValues(component, instance, operation).Inc()
|
|
|
|
// Add to context.
|
|
ctx = context.WithValue(ctx, ctxKeyComponent, component)
|
|
ctx = context.WithValue(ctx, ctxKeyInstance, instance)
|
|
ctx = context.WithValue(ctx, ctxKeyOperation, operation)
|
|
ctx = context.WithValue(ctx, ctxKeyStartTime, time.Now())
|
|
|
|
return ctx
|
|
}
|
|
|
|
func RecordRED(ctx context.Context, err error) {
|
|
// Parse component, instance, operation, and start time from context.
|
|
component, instance, operation, startTime := parseFrom(ctx)
|
|
duration := time.Since(startTime)
|
|
|
|
// Determine result.
|
|
result := telemetrymodel.ValResultSuccess
|
|
if err != nil {
|
|
result = telemetrymodel.ValResultError
|
|
}
|
|
|
|
// Record metrics.
|
|
operationTotal.WithLabelValues(component, instance, operation, result).Inc()
|
|
operationDuration.WithLabelValues(component, instance, operation, result).Observe(duration.Seconds())
|
|
operationInFlight.WithLabelValues(component, instance, operation).Dec()
|
|
}
|
|
|
|
func Close(id prometheus.Labels) {
|
|
operationInFlight.DeletePartialMatch(id)
|
|
operationTotal.DeletePartialMatch(id)
|
|
operationDuration.DeletePartialMatch(id)
|
|
}
|
|
|
|
func parseFrom(ctx context.Context) (component, instance, operation string, startTime time.Time) {
|
|
if v := ctx.Value(ctxKeyComponent); v != nil {
|
|
component = v.(string)
|
|
}
|
|
if v := ctx.Value(ctxKeyInstance); v != nil {
|
|
instance = v.(string)
|
|
}
|
|
if v := ctx.Value(ctxKeyOperation); v != nil {
|
|
operation = v.(string)
|
|
}
|
|
if v := ctx.Value(ctxKeyStartTime); v != nil {
|
|
startTime = v.(time.Time)
|
|
}
|
|
|
|
return
|
|
}
|