rushlink/metrics.go

109 lines
3.2 KiB
Go
Raw Normal View History

2019-11-09 15:50:12 +01:00
package rushlink
import (
"log"
"net/http"
"time"
2020-10-25 17:33:51 +01:00
"gitea.hashru.nl/dsprenkels/rushlink/internal/db"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
2019-12-08 21:56:02 +01:00
const metricNamespace = "rushlink"
2021-05-16 20:21:44 +02:00
// metricURLsTotalGauge counts the number of requests that are handled by
// the application, partitioned by status code and HTTP method.
//
2021-05-16 20:24:00 +02:00
// This counter is updated by the router.
2021-05-16 20:21:44 +02:00
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"})
2019-12-08 21:56:02 +01:00
2021-05-16 20:24:00 +02:00
// 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"})
2021-05-16 20:21:44 +02:00
// 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{
2019-12-08 21:56:02 +01:00
Namespace: metricNamespace,
Subsystem: "pastes",
Name: "urls_total",
2021-05-16 20:21:44 +02:00
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)
2021-05-16 20:24:00 +02:00
prometheus.MustRegister(metricRequestsLatencyNanoSeconds)
2021-05-16 20:21:44 +02:00
prometheus.MustRegister(metricURLsTotalGauge)
router := mux.NewRouter()
2021-05-16 20:21:44 +02:00
router.Handle("/metrics", &MetricsHandler{database}).Methods("GET")
srv := &http.Server{
Handler: router,
Addr: addr,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
2021-05-16 20:21:44 +02:00
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(),
2021-05-16 20:21:44 +02:00
}
metricURLsTotalGauge.With(labels).Set(r.Count)
2021-05-16 20:21:44 +02:00
}
}