Add request_duration_seconds metric #72
98
metrics.go
98
metrics.go
@ -14,42 +14,59 @@ import (
|
||||
|
||||
const metricNamespace = "rushlink"
|
||||
|
||||
var metricRequestsTotalCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
// metricURLsTotalGauge counts the number of requests that are handled by
|
||||
// the application, partitioned by status code and HTTP method.
|
||||
//
|
||||
// This counter is updated by the router.
|
||||
var metricRequestsTotalCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: metricNamespace,
|
||||
Subsystem: "http",
|
||||
Name: "requests_total",
|
||||
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
|
||||
}, []string{"code", "method"})
|
||||
}, []string{"code", "method"})
|
||||
|
||||
func metricURLsTotal(database *db.Database) float64 {
|
||||
var metric float64
|
||||
if err := database.Transaction(func(tx *db.Database) error {
|
||||
var count int64
|
||||
if err := database.Model(&db.Paste{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
metric = float64(count)
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric"))
|
||||
return 0
|
||||
}
|
||||
return metric
|
||||
}
|
||||
// metricRequestsLatencyNanoSeconds keeps track of the request latencies for
|
||||
// each http request.
|
||||
//
|
||||
// This historogram is updated by the router.
|
||||
var metricRequestsLatencyNanoSeconds = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Subsystem: "http",
|
||||
Name: "request_duration_seconds",
|
||||
Buckets: []float64{
|
||||
float64(500e-6),
|
||||
float64(1e-3),
|
||||
float64(2e-3),
|
||||
float64(5e-3),
|
||||
float64(10e-3),
|
||||
float64(20e-3),
|
||||
float64(50e-3),
|
||||
},
|
||||
Help: "The latency of each HTTP request, partitioned by status code and HTTP method.",
|
||||
}, []string{"code", "method"})
|
||||
|
||||
// metricURLsTotalGauge measures the amount of pastes stored in the database,
|
||||
// partitioned by type and state.
|
||||
//
|
||||
// Its values are computed on the fly by updateMetrics().
|
||||
var metricURLsTotalGauge = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: metricNamespace,
|
||||
Subsystem: "pastes",
|
||||
Name: "urls_total",
|
||||
Help: "The current amount of pastes in the database, partitioned by state and type.",
|
||||
}, []string{"state", "type"})
|
||||
|
||||
// StartMetricsServer starts sering Prometheus metrics exports on addr
|
||||
func StartMetricsServer(addr string, database *db.Database, fs *db.FileStore) {
|
||||
prometheus.MustRegister(metricRequestsTotalCounter)
|
||||
|
||||
prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Namespace: metricNamespace,
|
||||
Subsystem: "pastes",
|
||||
Name: "urls_total",
|
||||
Help: "The current amount of pastes in the database.",
|
||||
}, func() float64 { return metricURLsTotal(database) }))
|
||||
prometheus.MustRegister(metricRequestsLatencyNanoSeconds)
|
||||
prometheus.MustRegister(metricURLsTotalGauge)
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/metrics", promhttp.Handler()).Methods("GET")
|
||||
router.Handle("/metrics", &MetricsHandler{database}).Methods("GET")
|
||||
srv := &http.Server{
|
||||
Handler: router,
|
||||
Addr: addr,
|
||||
@ -58,3 +75,34 @@ func StartMetricsServer(addr string, database *db.Database, fs *db.FileStore) {
|
||||
}
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
type MetricsHandler struct {
|
||||
db *db.Database
|
||||
}
|
||||
|
||||
func (mh *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
mh.updateMetrics()
|
||||
promhttp.Handler().ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (mh *MetricsHandler) updateMetrics() {
|
||||
// Update metricURLsTotalGauge
|
||||
results := make([](struct {
|
||||
Type db.PasteType
|
||||
State db.PasteState
|
||||
Count float64
|
||||
}), 0)
|
||||
query := mh.db.Unscoped().Model(&db.Paste{}).Select("type", "state", "COUNT(*) as count").Group("type, state").Find(&results)
|
||||
if err := query.Error; err != nil {
|
||||
log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric"))
|
||||
return
|
||||
}
|
||||
metricURLsTotalGauge.Reset()
|
||||
for _, r := range results {
|
||||
labels := map[string]string{
|
||||
"type": r.Type.String(),
|
||||
"state": r.State.String(),
|
||||
}
|
||||
metricURLsTotalGauge.With(labels).Set(r.Count)
|
||||
}
|
||||
}
|
||||
|
10
router.go
10
router.go
@ -58,10 +58,18 @@ func (rl *rushlink) recoveryMiddleware(next http.Handler) http.Handler {
|
||||
|
||||
func (rl *rushlink) metricsMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tick := time.Now()
|
||||
srw := statusResponseWriter{Inner: w}
|
||||
next.ServeHTTP(&srw, r)
|
||||
tock := time.Now()
|
||||
|
||||
status := strconv.Itoa(srw.StatusCode)
|
||||
metricRequestsTotalCounter.WithLabelValues(status, r.Method).Inc()
|
||||
labels := map[string]string{"code": status, "method": r.Method}
|
||||
// Update requests counter metric
|
||||
metricRequestsTotalCounter.With(labels).Inc()
|
||||
// Update request latency metric
|
||||
elapsed := tock.Sub(tick)
|
||||
metricRequestsLatencyNanoSeconds.With(labels).Observe(elapsed.Seconds())
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user