metric.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package metric // import "miniflux.app/v2/internal/metric"
  4. import (
  5. "context"
  6. "log/slog"
  7. "time"
  8. "miniflux.app/v2/internal/storage"
  9. "github.com/prometheus/client_golang/prometheus"
  10. )
  11. // Status label values for histogram metrics.
  12. const (
  13. StatusSuccess = "success"
  14. StatusError = "error"
  15. )
  16. // Prometheus Metrics.
  17. var (
  18. BackgroundFeedRefreshDuration = prometheus.NewHistogramVec(
  19. prometheus.HistogramOpts{
  20. Namespace: "miniflux",
  21. Name: "background_feed_refresh_duration",
  22. Help: "Processing time to refresh feeds from the background workers",
  23. Buckets: prometheus.LinearBuckets(1, 2, 15),
  24. },
  25. []string{"status"},
  26. )
  27. ScraperRequestDuration = prometheus.NewHistogramVec(
  28. prometheus.HistogramOpts{
  29. Namespace: "miniflux",
  30. Name: "scraper_request_duration",
  31. Help: "Web scraper request duration",
  32. Buckets: prometheus.LinearBuckets(1, 2, 25),
  33. },
  34. []string{"status"},
  35. )
  36. ArchiveEntriesDuration = prometheus.NewHistogramVec(
  37. prometheus.HistogramOpts{
  38. Namespace: "miniflux",
  39. Name: "archive_entries_duration",
  40. Help: "Archive entries duration",
  41. Buckets: prometheus.LinearBuckets(1, 2, 30),
  42. },
  43. []string{"status"},
  44. )
  45. usersGauge = prometheus.NewGauge(
  46. prometheus.GaugeOpts{
  47. Namespace: "miniflux",
  48. Name: "users",
  49. Help: "Number of users",
  50. },
  51. )
  52. feedsGauge = prometheus.NewGaugeVec(
  53. prometheus.GaugeOpts{
  54. Namespace: "miniflux",
  55. Name: "feeds",
  56. Help: "Number of feeds by status",
  57. },
  58. []string{"status"},
  59. )
  60. brokenFeedsGauge = prometheus.NewGauge(
  61. prometheus.GaugeOpts{
  62. Namespace: "miniflux",
  63. Name: "broken_feeds",
  64. Help: "Number of broken feeds",
  65. },
  66. )
  67. entriesGauge = prometheus.NewGaugeVec(
  68. prometheus.GaugeOpts{
  69. Namespace: "miniflux",
  70. Name: "entries",
  71. Help: "Number of entries by status",
  72. },
  73. []string{"status"},
  74. )
  75. dbOpenConnectionsGauge = prometheus.NewGauge(
  76. prometheus.GaugeOpts{
  77. Namespace: "miniflux",
  78. Name: "db_open_connections",
  79. Help: "The number of established connections both in use and idle",
  80. },
  81. )
  82. dbConnectionsInUseGauge = prometheus.NewGauge(
  83. prometheus.GaugeOpts{
  84. Namespace: "miniflux",
  85. Name: "db_connections_in_use",
  86. Help: "The number of connections currently in use",
  87. },
  88. )
  89. dbConnectionsIdleGauge = prometheus.NewGauge(
  90. prometheus.GaugeOpts{
  91. Namespace: "miniflux",
  92. Name: "db_connections_idle",
  93. Help: "The number of idle connections",
  94. },
  95. )
  96. dbConnectionsWaitCountGauge = prometheus.NewGauge(
  97. prometheus.GaugeOpts{
  98. Namespace: "miniflux",
  99. Name: "db_connections_wait_count",
  100. Help: "The total number of connections waited for",
  101. },
  102. )
  103. dbConnectionsMaxIdleClosedGauge = prometheus.NewGauge(
  104. prometheus.GaugeOpts{
  105. Namespace: "miniflux",
  106. Name: "db_connections_max_idle_closed",
  107. Help: "The total number of connections closed due to SetMaxIdleConns",
  108. },
  109. )
  110. dbConnectionsMaxIdleTimeClosedGauge = prometheus.NewGauge(
  111. prometheus.GaugeOpts{
  112. Namespace: "miniflux",
  113. Name: "db_connections_max_idle_time_closed",
  114. Help: "The total number of connections closed due to SetConnMaxIdleTime",
  115. },
  116. )
  117. dbConnectionsMaxLifetimeClosedGauge = prometheus.NewGauge(
  118. prometheus.GaugeOpts{
  119. Namespace: "miniflux",
  120. Name: "db_connections_max_lifetime_closed",
  121. Help: "The total number of connections closed due to SetConnMaxLifetime",
  122. },
  123. )
  124. )
  125. // collector represents a metric collector.
  126. type collector struct {
  127. store *storage.Storage
  128. refreshInterval time.Duration
  129. }
  130. // NewCollector initializes a new metric collector.
  131. func NewCollector(store *storage.Storage, refreshInterval time.Duration) *collector {
  132. prometheus.MustRegister(BackgroundFeedRefreshDuration)
  133. prometheus.MustRegister(ScraperRequestDuration)
  134. prometheus.MustRegister(ArchiveEntriesDuration)
  135. prometheus.MustRegister(usersGauge)
  136. prometheus.MustRegister(feedsGauge)
  137. prometheus.MustRegister(brokenFeedsGauge)
  138. prometheus.MustRegister(entriesGauge)
  139. prometheus.MustRegister(dbOpenConnectionsGauge)
  140. prometheus.MustRegister(dbConnectionsInUseGauge)
  141. prometheus.MustRegister(dbConnectionsIdleGauge)
  142. prometheus.MustRegister(dbConnectionsWaitCountGauge)
  143. prometheus.MustRegister(dbConnectionsMaxIdleClosedGauge)
  144. prometheus.MustRegister(dbConnectionsMaxIdleTimeClosedGauge)
  145. prometheus.MustRegister(dbConnectionsMaxLifetimeClosedGauge)
  146. return &collector{store, refreshInterval}
  147. }
  148. // GatherStorageMetrics polls the database to fetch metrics.
  149. func (c *collector) GatherStorageMetrics(ctx context.Context) {
  150. ticker := time.NewTicker(c.refreshInterval)
  151. defer ticker.Stop()
  152. for {
  153. select {
  154. case <-ctx.Done():
  155. slog.Debug("Stopping metric collector")
  156. return
  157. case <-ticker.C:
  158. }
  159. slog.Debug("Collecting metrics from the database")
  160. if usersCount, err := c.store.CountUsers(); err != nil {
  161. slog.Warn("Unable to collect users metric", slog.Any("error", err))
  162. } else {
  163. usersGauge.Set(float64(usersCount))
  164. }
  165. if brokenFeedsCount, err := c.store.CountAllFeedsWithErrors(); err != nil {
  166. slog.Warn("Unable to collect broken feeds metric", slog.Any("error", err))
  167. } else {
  168. brokenFeedsGauge.Set(float64(brokenFeedsCount))
  169. }
  170. if feedsCount, err := c.store.CountAllFeeds(); err != nil {
  171. slog.Warn("Unable to collect feeds metric", slog.Any("error", err))
  172. } else {
  173. for status, count := range feedsCount {
  174. feedsGauge.WithLabelValues(status).Set(float64(count))
  175. }
  176. }
  177. if entriesCount, err := c.store.CountAllEntries(); err != nil {
  178. slog.Warn("Unable to collect entries metric", slog.Any("error", err))
  179. } else {
  180. for status, count := range entriesCount {
  181. entriesGauge.WithLabelValues(status).Set(float64(count))
  182. }
  183. }
  184. dbStats := c.store.DBStats()
  185. dbOpenConnectionsGauge.Set(float64(dbStats.OpenConnections))
  186. dbConnectionsInUseGauge.Set(float64(dbStats.InUse))
  187. dbConnectionsIdleGauge.Set(float64(dbStats.Idle))
  188. dbConnectionsWaitCountGauge.Set(float64(dbStats.WaitCount))
  189. dbConnectionsMaxIdleClosedGauge.Set(float64(dbStats.MaxIdleClosed))
  190. dbConnectionsMaxIdleTimeClosedGauge.Set(float64(dbStats.MaxIdleTimeClosed))
  191. dbConnectionsMaxLifetimeClosedGauge.Set(float64(dbStats.MaxLifetimeClosed))
  192. }
  193. }