forked from electricdusk/rushlink
106 lines
3.2 KiB
Go
106 lines
3.2 KiB
Go
package rushlink
|
|
|
|
import (
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"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"
|
|
)
|
|
|
|
const metricNamespace = "rushlink"
|
|
|
|
// 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"})
|
|
|
|
// 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(metricRequestsLatencyNanoSeconds)
|
|
prometheus.MustRegister(metricURLsTotalGauge)
|
|
|
|
router := mux.NewRouter()
|
|
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())
|
|
}
|
|
|
|
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
|
|
for state := db.PasteStateUndef; state <= db.PasteStateDeleted; state++ {
|
|
for ty := db.PasteTypeUndef; ty <= db.PasteTypeFileUpload; ty++ {
|
|
var count int64
|
|
query := mh.db.Unscoped().Model(&db.Paste{}).Where("type = ? AND state = ?", ty, state).Count(&count)
|
|
if err := query.Error; err != nil {
|
|
log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric"))
|
|
return
|
|
}
|
|
labels := map[string]string{
|
|
"state": state.String(),
|
|
"type": ty.String(),
|
|
}
|
|
metricURLsTotalGauge.With(labels).Set(float64(count))
|
|
}
|
|
}
|
|
}
|