URL shortener and file dump for hashru.link https://hashru.link
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
3.2 KiB

  1. package rushlink
  2. import (
  3. "log"
  4. "net/http"
  5. "time"
  6. "gitea.hashru.nl/dsprenkels/rushlink/internal/db"
  7. "github.com/gorilla/mux"
  8. "github.com/pkg/errors"
  9. "github.com/prometheus/client_golang/prometheus"
  10. "github.com/prometheus/client_golang/prometheus/promhttp"
  11. )
  12. const metricNamespace = "rushlink"
  13. // metricURLsTotalGauge counts the number of requests that are handled by
  14. // the application, partitioned by status code and HTTP method.
  15. //
  16. // This counter is updated by the router.
  17. var metricRequestsTotalCounter = prometheus.NewCounterVec(
  18. prometheus.CounterOpts{
  19. Namespace: metricNamespace,
  20. Subsystem: "http",
  21. Name: "requests_total",
  22. Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
  23. }, []string{"code", "method"})
  24. // metricRequestsLatencyNanoSeconds keeps track of the request latencies for
  25. // each http request.
  26. //
  27. // This historogram is updated by the router.
  28. var metricRequestsLatencyNanoSeconds = prometheus.NewHistogramVec(
  29. prometheus.HistogramOpts{
  30. Namespace: metricNamespace,
  31. Subsystem: "http",
  32. Name: "request_duration_seconds",
  33. Buckets: []float64{
  34. float64(500e-6),
  35. float64(1e-3),
  36. float64(2e-3),
  37. float64(5e-3),
  38. float64(10e-3),
  39. float64(20e-3),
  40. float64(50e-3),
  41. },
  42. Help: "The latency of each HTTP request, partitioned by status code and HTTP method.",
  43. }, []string{"code", "method"})
  44. // metricURLsTotalGauge measures the amount of pastes stored in the database,
  45. // partitioned by type and state.
  46. //
  47. // Its values are computed on the fly by updateMetrics().
  48. var metricURLsTotalGauge = prometheus.NewGaugeVec(
  49. prometheus.GaugeOpts{
  50. Namespace: metricNamespace,
  51. Subsystem: "pastes",
  52. Name: "urls_total",
  53. Help: "The current amount of pastes in the database, partitioned by state and type.",
  54. }, []string{"state", "type"})
  55. // StartMetricsServer starts sering Prometheus metrics exports on addr
  56. func StartMetricsServer(addr string, database *db.Database, fs *db.FileStore) {
  57. prometheus.MustRegister(metricRequestsTotalCounter)
  58. prometheus.MustRegister(metricRequestsLatencyNanoSeconds)
  59. prometheus.MustRegister(metricURLsTotalGauge)
  60. router := mux.NewRouter()
  61. router.Handle("/metrics", &MetricsHandler{database}).Methods("GET")
  62. srv := &http.Server{
  63. Handler: router,
  64. Addr: addr,
  65. WriteTimeout: 15 * time.Second,
  66. ReadTimeout: 15 * time.Second,
  67. }
  68. log.Fatal(srv.ListenAndServe())
  69. }
  70. type MetricsHandler struct {
  71. db *db.Database
  72. }
  73. func (mh *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  74. mh.updateMetrics()
  75. promhttp.Handler().ServeHTTP(w, r)
  76. }
  77. func (mh *MetricsHandler) updateMetrics() {
  78. // Update metricURLsTotalGauge
  79. results := make([](struct {
  80. Type db.PasteType
  81. State db.PasteState
  82. Count float64
  83. }), 0)
  84. query := mh.db.Unscoped().Model(&db.Paste{}).Select("type", "state", "COUNT(*) as count").Group("type, state").Find(&results)
  85. if err := query.Error; err != nil {
  86. log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric"))
  87. return
  88. }
  89. metricURLsTotalGauge.Reset()
  90. for _, r := range results {
  91. labels := map[string]string{
  92. "type": r.Type.String(),
  93. "state": r.State.String(),
  94. }
  95. metricURLsTotalGauge.With(labels).Set(r.Count)
  96. }
  97. }