prometheus.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. package prometheus
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "net/http"
  7. "strconv"
  8. "time"
  9. "github.com/felixge/httpsnoop"
  10. "github.com/prometheus/client_golang/prometheus"
  11. "github.com/prometheus/client_golang/prometheus/promhttp"
  12. "github.com/imgproxy/imgproxy/v3/config"
  13. "github.com/imgproxy/imgproxy/v3/monitoring/stats"
  14. "github.com/imgproxy/imgproxy/v3/reuseport"
  15. "github.com/imgproxy/imgproxy/v3/vips"
  16. )
  17. var (
  18. enabled = false
  19. requestsTotal prometheus.Counter
  20. statusCodesTotal *prometheus.CounterVec
  21. errorsTotal *prometheus.CounterVec
  22. requestDuration prometheus.Histogram
  23. requestSpanDuration *prometheus.HistogramVec
  24. downloadDuration prometheus.Histogram
  25. processingDuration prometheus.Histogram
  26. bufferSize *prometheus.HistogramVec
  27. bufferDefaultSize *prometheus.GaugeVec
  28. bufferMaxSize *prometheus.GaugeVec
  29. workers prometheus.Gauge
  30. )
  31. func Init() {
  32. if len(config.PrometheusBind) == 0 {
  33. return
  34. }
  35. requestsTotal = prometheus.NewCounter(prometheus.CounterOpts{
  36. Namespace: config.PrometheusNamespace,
  37. Name: "requests_total",
  38. Help: "A counter of the total number of HTTP requests imgproxy processed.",
  39. })
  40. statusCodesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
  41. Namespace: config.PrometheusNamespace,
  42. Name: "status_codes_total",
  43. Help: "A counter of the response status codes.",
  44. }, []string{"status"})
  45. errorsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
  46. Namespace: config.PrometheusNamespace,
  47. Name: "errors_total",
  48. Help: "A counter of the occurred errors separated by type.",
  49. }, []string{"type"})
  50. requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
  51. Namespace: config.PrometheusNamespace,
  52. Name: "request_duration_seconds",
  53. Help: "A histogram of the response latency.",
  54. })
  55. requestSpanDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
  56. Namespace: config.PrometheusNamespace,
  57. Name: "request_span_duration_seconds",
  58. Help: "A histogram of the queue latency.",
  59. }, []string{"span"})
  60. downloadDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
  61. Namespace: config.PrometheusNamespace,
  62. Name: "download_duration_seconds",
  63. Help: "A histogram of the source image downloading latency.",
  64. })
  65. processingDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
  66. Namespace: config.PrometheusNamespace,
  67. Name: "processing_duration_seconds",
  68. Help: "A histogram of the image processing latency.",
  69. })
  70. bufferSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
  71. Namespace: config.PrometheusNamespace,
  72. Name: "buffer_size_bytes",
  73. Help: "A histogram of the buffer size in bytes.",
  74. Buckets: prometheus.ExponentialBuckets(1024, 2, 14),
  75. }, []string{"type"})
  76. bufferDefaultSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  77. Namespace: config.PrometheusNamespace,
  78. Name: "buffer_default_size_bytes",
  79. Help: "A gauge of the buffer default size in bytes.",
  80. }, []string{"type"})
  81. bufferMaxSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
  82. Namespace: config.PrometheusNamespace,
  83. Name: "buffer_max_size_bytes",
  84. Help: "A gauge of the buffer max size in bytes.",
  85. }, []string{"type"})
  86. workers = prometheus.NewGauge(prometheus.GaugeOpts{
  87. Namespace: config.PrometheusNamespace,
  88. Name: "workers",
  89. Help: "A gauge of the number of running workers.",
  90. })
  91. workers.Set(float64(config.Workers))
  92. requestsInProgress := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
  93. Namespace: config.PrometheusNamespace,
  94. Name: "requests_in_progress",
  95. Help: "A gauge of the number of requests currently being in progress.",
  96. }, stats.RequestsInProgress)
  97. imagesInProgress := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
  98. Namespace: config.PrometheusNamespace,
  99. Name: "images_in_progress",
  100. Help: "A gauge of the number of images currently being in progress.",
  101. }, stats.ImagesInProgress)
  102. workersUtilization := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
  103. Namespace: config.PrometheusNamespace,
  104. Name: "workers_utilization",
  105. Help: "A gauge of the workers utilization in percents.",
  106. }, stats.WorkersUtilization)
  107. vipsMemoryBytes := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
  108. Namespace: config.PrometheusNamespace,
  109. Name: "vips_memory_bytes",
  110. Help: "A gauge of the vips tracked memory usage in bytes.",
  111. }, vips.GetMem)
  112. vipsMaxMemoryBytes := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
  113. Namespace: config.PrometheusNamespace,
  114. Name: "vips_max_memory_bytes",
  115. Help: "A gauge of the max vips tracked memory usage in bytes.",
  116. }, vips.GetMemHighwater)
  117. vipsAllocs := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
  118. Namespace: config.PrometheusNamespace,
  119. Name: "vips_allocs",
  120. Help: "A gauge of the number of active vips allocations.",
  121. }, vips.GetAllocs)
  122. prometheus.MustRegister(
  123. requestsTotal,
  124. statusCodesTotal,
  125. errorsTotal,
  126. requestDuration,
  127. requestSpanDuration,
  128. downloadDuration,
  129. processingDuration,
  130. bufferSize,
  131. bufferDefaultSize,
  132. bufferMaxSize,
  133. workers,
  134. requestsInProgress,
  135. imagesInProgress,
  136. workersUtilization,
  137. vipsMemoryBytes,
  138. vipsMaxMemoryBytes,
  139. vipsAllocs,
  140. )
  141. enabled = true
  142. }
  143. func Enabled() bool {
  144. return enabled
  145. }
  146. func StartServer(cancel context.CancelFunc) error {
  147. if !enabled {
  148. return nil
  149. }
  150. s := http.Server{Handler: promhttp.Handler()}
  151. l, err := reuseport.Listen("tcp", config.PrometheusBind, config.SoReuseport)
  152. if err != nil {
  153. return fmt.Errorf("Can't start Prometheus metrics server: %s", err)
  154. }
  155. go func() {
  156. slog.Info(fmt.Sprintf("Starting Prometheus server at %s", config.PrometheusBind))
  157. if err := s.Serve(l); err != nil && err != http.ErrServerClosed {
  158. slog.Error(err.Error())
  159. }
  160. cancel()
  161. }()
  162. return nil
  163. }
  164. func StartRequest(rw http.ResponseWriter) (context.CancelFunc, http.ResponseWriter) {
  165. if !enabled {
  166. return func() {}, rw
  167. }
  168. requestsTotal.Inc()
  169. newRw := httpsnoop.Wrap(rw, httpsnoop.Hooks{
  170. WriteHeader: func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
  171. return func(statusCode int) {
  172. statusCodesTotal.With(prometheus.Labels{"status": strconv.Itoa(statusCode)}).Inc()
  173. next(statusCode)
  174. }
  175. },
  176. })
  177. return startDuration(requestDuration), newRw
  178. }
  179. func StartQueueSegment() context.CancelFunc {
  180. if !enabled {
  181. return func() {}
  182. }
  183. return startDuration(requestSpanDuration.With(prometheus.Labels{"span": "queue"}))
  184. }
  185. func StartDownloadingSegment() context.CancelFunc {
  186. if !enabled {
  187. return func() {}
  188. }
  189. cancel := startDuration(requestSpanDuration.With(prometheus.Labels{"span": "downloading"}))
  190. cancelLegacy := startDuration(downloadDuration)
  191. return func() {
  192. cancel()
  193. cancelLegacy()
  194. }
  195. }
  196. func StartProcessingSegment() context.CancelFunc {
  197. if !enabled {
  198. return func() {}
  199. }
  200. cancel := startDuration(requestSpanDuration.With(prometheus.Labels{"span": "processing"}))
  201. cancelLegacy := startDuration(processingDuration)
  202. return func() {
  203. cancel()
  204. cancelLegacy()
  205. }
  206. }
  207. func StartStreamingSegment() context.CancelFunc {
  208. if !enabled {
  209. return func() {}
  210. }
  211. return startDuration(requestSpanDuration.With(prometheus.Labels{"span": "streaming"}))
  212. }
  213. func startDuration(m prometheus.Observer) context.CancelFunc {
  214. t := time.Now()
  215. return func() {
  216. m.Observe(time.Since(t).Seconds())
  217. }
  218. }
  219. func IncrementErrorsTotal(t string) {
  220. if enabled {
  221. errorsTotal.With(prometheus.Labels{"type": t}).Inc()
  222. }
  223. }
  224. func ObserveBufferSize(t string, size int) {
  225. if enabled {
  226. bufferSize.With(prometheus.Labels{"type": t}).Observe(float64(size))
  227. }
  228. }
  229. func SetBufferDefaultSize(t string, size int) {
  230. if enabled {
  231. bufferDefaultSize.With(prometheus.Labels{"type": t}).Set(float64(size))
  232. }
  233. }
  234. func SetBufferMaxSize(t string, size int) {
  235. if enabled {
  236. bufferMaxSize.With(prometheus.Labels{"type": t}).Set(float64(size))
  237. }
  238. }