Add request_duration_seconds metric #72
98
metrics.go
98
metrics.go
@ -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
|
|||||||
|
// 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
mrngm
commented
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?
electricdusk
commented
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 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.
mrngm
commented
Ran this query on the test migration database:
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
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())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user
(technically, the PR is for
request_duration_seconds
, so anything other than that should be omitted... or change the PR title and description)