Add request_duration_seconds metric #72
							
								
								
									
										96
									
								
								metrics.go
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								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"}) | ||||
| 
 | ||||
| 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user