Add request_duration_seconds metric #72

Merged
mrngm merged 3 commits from metrics into master 2021-05-16 21:10:49 +02:00
2 changed files with 86 additions and 30 deletions

View File

@ -14,42 +14,59 @@ import (
const metricNamespace = "rushlink" 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, Namespace: metricNamespace,
Subsystem: "http", Subsystem: "http",
Name: "requests_total", Name: "requests_total",
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.", 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 { // metricRequestsLatencyNanoSeconds keeps track of the request latencies for
var metric float64 // each http request.
if err := database.Transaction(func(tx *db.Database) error { //
var count int64 // This historogram is updated by the router.
if err := database.Model(&db.Paste{}).Count(&count).Error; err != nil { var metricRequestsLatencyNanoSeconds = prometheus.NewHistogramVec(
return err prometheus.HistogramOpts{
} Namespace: metricNamespace,
metric = float64(count) Subsystem: "http",
return nil Name: "request_duration_seconds",
}); err != nil { Buckets: []float64{
log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric")) float64(500e-6),
return 0 float64(1e-3),
} float64(2e-3),
return metric 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,
mrngm marked this conversation as resolved Outdated
Outdated
Review

(technically, the PR is for request_duration_seconds, so anything other than that should be omitted... or change the PR title and description)

(technically, the PR is for `request_duration_seconds`, so anything other than that should be omitted... or change the PR title and description)
// 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 // StartMetricsServer starts sering Prometheus metrics exports on addr
func StartMetricsServer(addr string, database *db.Database, fs *db.FileStore) { func StartMetricsServer(addr string, database *db.Database, fs *db.FileStore) {
prometheus.MustRegister(metricRequestsTotalCounter) prometheus.MustRegister(metricRequestsTotalCounter)
prometheus.MustRegister(metricRequestsLatencyNanoSeconds)
prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{ prometheus.MustRegister(metricURLsTotalGauge)
Namespace: metricNamespace,
Subsystem: "pastes",
Name: "urls_total",
Help: "The current amount of pastes in the database.",
}, func() float64 { return metricURLsTotal(database) }))
router := mux.NewRouter() router := mux.NewRouter()
router.Handle("/metrics", promhttp.Handler()).Methods("GET") router.Handle("/metrics", &MetricsHandler{database}).Methods("GET")
srv := &http.Server{ srv := &http.Server{
Handler: router, Handler: router,
Addr: addr, Addr: addr,
@ -58,3 +75,34 @@ func StartMetricsServer(addr string, database *db.Database, fs *db.FileStore) {
} }
log.Fatal(srv.ListenAndServe()) 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
electricdusk marked this conversation as resolved Outdated
Outdated
Review

Is it possible to query the database once, yielding all counts for all types and states?

Is it possible to query the database once, yielding all counts for all types and states?

I tried to find this, but thought it was not not possible. I have retried searching for this and found this: https://stackoverflow.com/a/19046871/5207081
That would fix this.

I tried to find this, but thought it was not not possible. I have retried searching for this and found this: https://stackoverflow.com/a/19046871/5207081 That would fix this.
Outdated
Review

Ran this query on the test migration database:

sqlite> SELECT type, state, COUNT(*) FROM pastes GROUP BY type, state;
0|2|136  // type: undef      state: deleted
2|1|118  // type: redirect   state: present
3|1|1376 // type: fileupload state: present

Would that roughly be what you expect to get here?

Ran this query on the test migration database: ``` sqlite> SELECT type, state, COUNT(*) FROM pastes GROUP BY type, state; 0|2|136 // type: undef state: deleted 2|1|118 // type: redirect state: present 3|1|1376 // type: fileupload state: present ``` Would that roughly be what you expect to get here?
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)
}
}

View File

@ -58,10 +58,18 @@ func (rl *rushlink) recoveryMiddleware(next http.Handler) http.Handler {
func (rl *rushlink) metricsMiddleware(next http.Handler) http.Handler { func (rl *rushlink) metricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tick := time.Now()
srw := statusResponseWriter{Inner: w} srw := statusResponseWriter{Inner: w}
next.ServeHTTP(&srw, r) next.ServeHTTP(&srw, r)
tock := time.Now()
status := strconv.Itoa(srw.StatusCode) 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())
}) })
} }