namespace.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package metrics
  2. import (
  3. "fmt"
  4. "sync"
  5. "github.com/prometheus/client_golang/prometheus"
  6. )
  7. type Labels map[string]string
  8. // NewNamespace returns a namespaces that is responsible for managing a collection of
  9. // metrics for a particual namespace and subsystem
  10. //
  11. // labels allows const labels to be added to all metrics created in this namespace
  12. // and are commonly used for data like application version and git commit
  13. func NewNamespace(name, subsystem string, labels Labels) *Namespace {
  14. if labels == nil {
  15. labels = make(map[string]string)
  16. }
  17. return &Namespace{
  18. name: name,
  19. subsystem: subsystem,
  20. labels: labels,
  21. }
  22. }
  23. // Namespace describes a set of metrics that share a namespace and subsystem.
  24. type Namespace struct {
  25. name string
  26. subsystem string
  27. labels Labels
  28. mu sync.Mutex
  29. metrics []prometheus.Collector
  30. }
  31. // WithConstLabels returns a namespace with the provided set of labels merged
  32. // with the existing constant labels on the namespace.
  33. //
  34. // Only metrics created with the returned namespace will get the new constant
  35. // labels. The returned namespace must be registered separately.
  36. func (n *Namespace) WithConstLabels(labels Labels) *Namespace {
  37. n.mu.Lock()
  38. ns := &Namespace{
  39. name: n.name,
  40. subsystem: n.subsystem,
  41. labels: mergeLabels(n.labels, labels),
  42. }
  43. n.mu.Unlock()
  44. return ns
  45. }
  46. func (n *Namespace) NewCounter(name, help string) Counter {
  47. c := &counter{pc: prometheus.NewCounter(n.newCounterOpts(name, help))}
  48. n.Add(c)
  49. return c
  50. }
  51. func (n *Namespace) NewLabeledCounter(name, help string, labels ...string) LabeledCounter {
  52. c := &labeledCounter{pc: prometheus.NewCounterVec(n.newCounterOpts(name, help), labels)}
  53. n.Add(c)
  54. return c
  55. }
  56. func (n *Namespace) newCounterOpts(name, help string) prometheus.CounterOpts {
  57. return prometheus.CounterOpts{
  58. Namespace: n.name,
  59. Subsystem: n.subsystem,
  60. Name: makeName(name, Total),
  61. Help: help,
  62. ConstLabels: prometheus.Labels(n.labels),
  63. }
  64. }
  65. func (n *Namespace) NewTimer(name, help string) Timer {
  66. t := &timer{
  67. m: prometheus.NewHistogram(n.newTimerOpts(name, help)),
  68. }
  69. n.Add(t)
  70. return t
  71. }
  72. func (n *Namespace) NewLabeledTimer(name, help string, labels ...string) LabeledTimer {
  73. t := &labeledTimer{
  74. m: prometheus.NewHistogramVec(n.newTimerOpts(name, help), labels),
  75. }
  76. n.Add(t)
  77. return t
  78. }
  79. func (n *Namespace) newTimerOpts(name, help string) prometheus.HistogramOpts {
  80. return prometheus.HistogramOpts{
  81. Namespace: n.name,
  82. Subsystem: n.subsystem,
  83. Name: makeName(name, Seconds),
  84. Help: help,
  85. ConstLabels: prometheus.Labels(n.labels),
  86. }
  87. }
  88. func (n *Namespace) NewGauge(name, help string, unit Unit) Gauge {
  89. g := &gauge{
  90. pg: prometheus.NewGauge(n.newGaugeOpts(name, help, unit)),
  91. }
  92. n.Add(g)
  93. return g
  94. }
  95. func (n *Namespace) NewLabeledGauge(name, help string, unit Unit, labels ...string) LabeledGauge {
  96. g := &labeledGauge{
  97. pg: prometheus.NewGaugeVec(n.newGaugeOpts(name, help, unit), labels),
  98. }
  99. n.Add(g)
  100. return g
  101. }
  102. func (n *Namespace) newGaugeOpts(name, help string, unit Unit) prometheus.GaugeOpts {
  103. return prometheus.GaugeOpts{
  104. Namespace: n.name,
  105. Subsystem: n.subsystem,
  106. Name: makeName(name, unit),
  107. Help: help,
  108. ConstLabels: prometheus.Labels(n.labels),
  109. }
  110. }
  111. func (n *Namespace) Describe(ch chan<- *prometheus.Desc) {
  112. n.mu.Lock()
  113. defer n.mu.Unlock()
  114. for _, metric := range n.metrics {
  115. metric.Describe(ch)
  116. }
  117. }
  118. func (n *Namespace) Collect(ch chan<- prometheus.Metric) {
  119. n.mu.Lock()
  120. defer n.mu.Unlock()
  121. for _, metric := range n.metrics {
  122. metric.Collect(ch)
  123. }
  124. }
  125. func (n *Namespace) Add(collector prometheus.Collector) {
  126. n.mu.Lock()
  127. n.metrics = append(n.metrics, collector)
  128. n.mu.Unlock()
  129. }
  130. func (n *Namespace) NewDesc(name, help string, unit Unit, labels ...string) *prometheus.Desc {
  131. name = makeName(name, unit)
  132. namespace := n.name
  133. if n.subsystem != "" {
  134. namespace = fmt.Sprintf("%s_%s", namespace, n.subsystem)
  135. }
  136. name = fmt.Sprintf("%s_%s", namespace, name)
  137. return prometheus.NewDesc(name, help, labels, prometheus.Labels(n.labels))
  138. }
  139. // mergeLabels merges two or more labels objects into a single map, favoring
  140. // the later labels.
  141. func mergeLabels(lbs ...Labels) Labels {
  142. merged := make(Labels)
  143. for _, target := range lbs {
  144. for k, v := range target {
  145. merged[k] = v
  146. }
  147. }
  148. return merged
  149. }
  150. func makeName(name string, unit Unit) string {
  151. if unit == "" {
  152. return name
  153. }
  154. return fmt.Sprintf("%s_%s", name, unit)
  155. }
  156. func (n *Namespace) NewDefaultHttpMetrics(handlerName string) []*HTTPMetric {
  157. return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
  158. DurationBuckets: defaultDurationBuckets,
  159. RequestSizeBuckets: defaultResponseSizeBuckets,
  160. ResponseSizeBuckets: defaultResponseSizeBuckets,
  161. })
  162. }
  163. func (n *Namespace) NewHttpMetrics(handlerName string, durationBuckets, requestSizeBuckets, responseSizeBuckets []float64) []*HTTPMetric {
  164. return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
  165. DurationBuckets: durationBuckets,
  166. RequestSizeBuckets: requestSizeBuckets,
  167. ResponseSizeBuckets: responseSizeBuckets,
  168. })
  169. }
  170. func (n *Namespace) NewHttpMetricsWithOpts(handlerName string, opts HTTPHandlerOpts) []*HTTPMetric {
  171. var httpMetrics []*HTTPMetric
  172. inFlightMetric := n.NewInFlightGaugeMetric(handlerName)
  173. requestTotalMetric := n.NewRequestTotalMetric(handlerName)
  174. requestDurationMetric := n.NewRequestDurationMetric(handlerName, opts.DurationBuckets)
  175. requestSizeMetric := n.NewRequestSizeMetric(handlerName, opts.RequestSizeBuckets)
  176. responseSizeMetric := n.NewResponseSizeMetric(handlerName, opts.ResponseSizeBuckets)
  177. httpMetrics = append(httpMetrics, inFlightMetric, requestDurationMetric, requestTotalMetric, requestSizeMetric, responseSizeMetric)
  178. return httpMetrics
  179. }
  180. func (n *Namespace) NewInFlightGaugeMetric(handlerName string) *HTTPMetric {
  181. labels := prometheus.Labels(n.labels)
  182. labels["handler"] = handlerName
  183. metric := prometheus.NewGauge(prometheus.GaugeOpts{
  184. Namespace: n.name,
  185. Subsystem: n.subsystem,
  186. Name: "in_flight_requests",
  187. Help: "The in-flight HTTP requests",
  188. ConstLabels: prometheus.Labels(labels),
  189. })
  190. httpMetric := &HTTPMetric{
  191. Collector: metric,
  192. handlerType: InstrumentHandlerInFlight,
  193. }
  194. n.Add(httpMetric)
  195. return httpMetric
  196. }
  197. func (n *Namespace) NewRequestTotalMetric(handlerName string) *HTTPMetric {
  198. labels := prometheus.Labels(n.labels)
  199. labels["handler"] = handlerName
  200. metric := prometheus.NewCounterVec(
  201. prometheus.CounterOpts{
  202. Namespace: n.name,
  203. Subsystem: n.subsystem,
  204. Name: "requests_total",
  205. Help: "Total number of HTTP requests made.",
  206. ConstLabels: prometheus.Labels(labels),
  207. },
  208. []string{"code", "method"},
  209. )
  210. httpMetric := &HTTPMetric{
  211. Collector: metric,
  212. handlerType: InstrumentHandlerCounter,
  213. }
  214. n.Add(httpMetric)
  215. return httpMetric
  216. }
  217. func (n *Namespace) NewRequestDurationMetric(handlerName string, buckets []float64) *HTTPMetric {
  218. if len(buckets) == 0 {
  219. panic("DurationBuckets must be provided")
  220. }
  221. labels := prometheus.Labels(n.labels)
  222. labels["handler"] = handlerName
  223. opts := prometheus.HistogramOpts{
  224. Namespace: n.name,
  225. Subsystem: n.subsystem,
  226. Name: "request_duration_seconds",
  227. Help: "The HTTP request latencies in seconds.",
  228. Buckets: buckets,
  229. ConstLabels: prometheus.Labels(labels),
  230. }
  231. metric := prometheus.NewHistogramVec(opts, []string{"method"})
  232. httpMetric := &HTTPMetric{
  233. Collector: metric,
  234. handlerType: InstrumentHandlerDuration,
  235. }
  236. n.Add(httpMetric)
  237. return httpMetric
  238. }
  239. func (n *Namespace) NewRequestSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
  240. if len(buckets) == 0 {
  241. panic("RequestSizeBuckets must be provided")
  242. }
  243. labels := prometheus.Labels(n.labels)
  244. labels["handler"] = handlerName
  245. opts := prometheus.HistogramOpts{
  246. Namespace: n.name,
  247. Subsystem: n.subsystem,
  248. Name: "request_size_bytes",
  249. Help: "The HTTP request sizes in bytes.",
  250. Buckets: buckets,
  251. ConstLabels: prometheus.Labels(labels),
  252. }
  253. metric := prometheus.NewHistogramVec(opts, []string{})
  254. httpMetric := &HTTPMetric{
  255. Collector: metric,
  256. handlerType: InstrumentHandlerRequestSize,
  257. }
  258. n.Add(httpMetric)
  259. return httpMetric
  260. }
  261. func (n *Namespace) NewResponseSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
  262. if len(buckets) == 0 {
  263. panic("ResponseSizeBuckets must be provided")
  264. }
  265. labels := prometheus.Labels(n.labels)
  266. labels["handler"] = handlerName
  267. opts := prometheus.HistogramOpts{
  268. Namespace: n.name,
  269. Subsystem: n.subsystem,
  270. Name: "response_size_bytes",
  271. Help: "The HTTP response sizes in bytes.",
  272. Buckets: buckets,
  273. ConstLabels: prometheus.Labels(labels),
  274. }
  275. metrics := prometheus.NewHistogramVec(opts, []string{})
  276. httpMetric := &HTTPMetric{
  277. Collector: metrics,
  278. handlerType: InstrumentHandlerResponseSize,
  279. }
  280. n.Add(httpMetric)
  281. return httpMetric
  282. }