prometheus.go 7.3 KB

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