histogram.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. // Copyright 2015 The Prometheus Authors
  2. // Licensed under the Apache License, Version 2.0 (the "License");
  3. // you may not use this file except in compliance with the License.
  4. // You may obtain a copy of the License at
  5. //
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. package prometheus
  14. import (
  15. "fmt"
  16. "math"
  17. "sort"
  18. "sync/atomic"
  19. "github.com/golang/protobuf/proto"
  20. dto "github.com/prometheus/client_model/go"
  21. )
  22. // A Histogram counts individual observations from an event or sample stream in
  23. // configurable buckets. Similar to a summary, it also provides a sum of
  24. // observations and an observation count.
  25. //
  26. // On the Prometheus server, quantiles can be calculated from a Histogram using
  27. // the histogram_quantile function in the query language.
  28. //
  29. // Note that Histograms, in contrast to Summaries, can be aggregated with the
  30. // Prometheus query language (see the documentation for detailed
  31. // procedures). However, Histograms require the user to pre-define suitable
  32. // buckets, and they are in general less accurate. The Observe method of a
  33. // Histogram has a very low performance overhead in comparison with the Observe
  34. // method of a Summary.
  35. //
  36. // To create Histogram instances, use NewHistogram.
  37. type Histogram interface {
  38. Metric
  39. Collector
  40. // Observe adds a single observation to the histogram.
  41. Observe(float64)
  42. }
  43. // bucketLabel is used for the label that defines the upper bound of a
  44. // bucket of a histogram ("le" -> "less or equal").
  45. const bucketLabel = "le"
  46. // DefBuckets are the default Histogram buckets. The default buckets are
  47. // tailored to broadly measure the response time (in seconds) of a network
  48. // service. Most likely, however, you will be required to define buckets
  49. // customized to your use case.
  50. var (
  51. DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
  52. errBucketLabelNotAllowed = fmt.Errorf(
  53. "%q is not allowed as label name in histograms", bucketLabel,
  54. )
  55. )
  56. // LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest
  57. // bucket has an upper bound of 'start'. The final +Inf bucket is not counted
  58. // and not included in the returned slice. The returned slice is meant to be
  59. // used for the Buckets field of HistogramOpts.
  60. //
  61. // The function panics if 'count' is zero or negative.
  62. func LinearBuckets(start, width float64, count int) []float64 {
  63. if count < 1 {
  64. panic("LinearBuckets needs a positive count")
  65. }
  66. buckets := make([]float64, count)
  67. for i := range buckets {
  68. buckets[i] = start
  69. start += width
  70. }
  71. return buckets
  72. }
  73. // ExponentialBuckets creates 'count' buckets, where the lowest bucket has an
  74. // upper bound of 'start' and each following bucket's upper bound is 'factor'
  75. // times the previous bucket's upper bound. The final +Inf bucket is not counted
  76. // and not included in the returned slice. The returned slice is meant to be
  77. // used for the Buckets field of HistogramOpts.
  78. //
  79. // The function panics if 'count' is 0 or negative, if 'start' is 0 or negative,
  80. // or if 'factor' is less than or equal 1.
  81. func ExponentialBuckets(start, factor float64, count int) []float64 {
  82. if count < 1 {
  83. panic("ExponentialBuckets needs a positive count")
  84. }
  85. if start <= 0 {
  86. panic("ExponentialBuckets needs a positive start value")
  87. }
  88. if factor <= 1 {
  89. panic("ExponentialBuckets needs a factor greater than 1")
  90. }
  91. buckets := make([]float64, count)
  92. for i := range buckets {
  93. buckets[i] = start
  94. start *= factor
  95. }
  96. return buckets
  97. }
  98. // HistogramOpts bundles the options for creating a Histogram metric. It is
  99. // mandatory to set Name and Help to a non-empty string. All other fields are
  100. // optional and can safely be left at their zero value.
  101. type HistogramOpts struct {
  102. // Namespace, Subsystem, and Name are components of the fully-qualified
  103. // name of the Histogram (created by joining these components with
  104. // "_"). Only Name is mandatory, the others merely help structuring the
  105. // name. Note that the fully-qualified name of the Histogram must be a
  106. // valid Prometheus metric name.
  107. Namespace string
  108. Subsystem string
  109. Name string
  110. // Help provides information about this Histogram. Mandatory!
  111. //
  112. // Metrics with the same fully-qualified name must have the same Help
  113. // string.
  114. Help string
  115. // ConstLabels are used to attach fixed labels to this
  116. // Histogram. Histograms with the same fully-qualified name must have the
  117. // same label names in their ConstLabels.
  118. //
  119. // Note that in most cases, labels have a value that varies during the
  120. // lifetime of a process. Those labels are usually managed with a
  121. // HistogramVec. ConstLabels serve only special purposes. One is for the
  122. // special case where the value of a label does not change during the
  123. // lifetime of a process, e.g. if the revision of the running binary is
  124. // put into a label. Another, more advanced purpose is if more than one
  125. // Collector needs to collect Histograms with the same fully-qualified
  126. // name. In that case, those Summaries must differ in the values of
  127. // their ConstLabels. See the Collector examples.
  128. //
  129. // If the value of a label never changes (not even between binaries),
  130. // that label most likely should not be a label at all (but part of the
  131. // metric name).
  132. ConstLabels Labels
  133. // Buckets defines the buckets into which observations are counted. Each
  134. // element in the slice is the upper inclusive bound of a bucket. The
  135. // values must be sorted in strictly increasing order. There is no need
  136. // to add a highest bucket with +Inf bound, it will be added
  137. // implicitly. The default value is DefBuckets.
  138. Buckets []float64
  139. }
  140. // NewHistogram creates a new Histogram based on the provided HistogramOpts. It
  141. // panics if the buckets in HistogramOpts are not in strictly increasing order.
  142. func NewHistogram(opts HistogramOpts) Histogram {
  143. return newHistogram(
  144. NewDesc(
  145. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  146. opts.Help,
  147. nil,
  148. opts.ConstLabels,
  149. ),
  150. opts,
  151. )
  152. }
  153. func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
  154. if len(desc.variableLabels) != len(labelValues) {
  155. panic(errInconsistentCardinality)
  156. }
  157. for _, n := range desc.variableLabels {
  158. if n == bucketLabel {
  159. panic(errBucketLabelNotAllowed)
  160. }
  161. }
  162. for _, lp := range desc.constLabelPairs {
  163. if lp.GetName() == bucketLabel {
  164. panic(errBucketLabelNotAllowed)
  165. }
  166. }
  167. if len(opts.Buckets) == 0 {
  168. opts.Buckets = DefBuckets
  169. }
  170. h := &histogram{
  171. desc: desc,
  172. upperBounds: opts.Buckets,
  173. labelPairs: makeLabelPairs(desc, labelValues),
  174. }
  175. for i, upperBound := range h.upperBounds {
  176. if i < len(h.upperBounds)-1 {
  177. if upperBound >= h.upperBounds[i+1] {
  178. panic(fmt.Errorf(
  179. "histogram buckets must be in increasing order: %f >= %f",
  180. upperBound, h.upperBounds[i+1],
  181. ))
  182. }
  183. } else {
  184. if math.IsInf(upperBound, +1) {
  185. // The +Inf bucket is implicit. Remove it here.
  186. h.upperBounds = h.upperBounds[:i]
  187. }
  188. }
  189. }
  190. // Finally we know the final length of h.upperBounds and can make counts.
  191. h.counts = make([]uint64, len(h.upperBounds))
  192. h.init(h) // Init self-collection.
  193. return h
  194. }
  195. type histogram struct {
  196. // sumBits contains the bits of the float64 representing the sum of all
  197. // observations. sumBits and count have to go first in the struct to
  198. // guarantee alignment for atomic operations.
  199. // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
  200. sumBits uint64
  201. count uint64
  202. selfCollector
  203. // Note that there is no mutex required.
  204. desc *Desc
  205. upperBounds []float64
  206. counts []uint64
  207. labelPairs []*dto.LabelPair
  208. }
  209. func (h *histogram) Desc() *Desc {
  210. return h.desc
  211. }
  212. func (h *histogram) Observe(v float64) {
  213. // TODO(beorn7): For small numbers of buckets (<30), a linear search is
  214. // slightly faster than the binary search. If we really care, we could
  215. // switch from one search strategy to the other depending on the number
  216. // of buckets.
  217. //
  218. // Microbenchmarks (BenchmarkHistogramNoLabels):
  219. // 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
  220. // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
  221. // 300 buckets: 154 ns/op linear - binary 61.6 ns/op
  222. i := sort.SearchFloat64s(h.upperBounds, v)
  223. if i < len(h.counts) {
  224. atomic.AddUint64(&h.counts[i], 1)
  225. }
  226. atomic.AddUint64(&h.count, 1)
  227. for {
  228. oldBits := atomic.LoadUint64(&h.sumBits)
  229. newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
  230. if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) {
  231. break
  232. }
  233. }
  234. }
  235. func (h *histogram) Write(out *dto.Metric) error {
  236. his := &dto.Histogram{}
  237. buckets := make([]*dto.Bucket, len(h.upperBounds))
  238. his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits)))
  239. his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count))
  240. var count uint64
  241. for i, upperBound := range h.upperBounds {
  242. count += atomic.LoadUint64(&h.counts[i])
  243. buckets[i] = &dto.Bucket{
  244. CumulativeCount: proto.Uint64(count),
  245. UpperBound: proto.Float64(upperBound),
  246. }
  247. }
  248. his.Bucket = buckets
  249. out.Histogram = his
  250. out.Label = h.labelPairs
  251. return nil
  252. }
  253. // HistogramVec is a Collector that bundles a set of Histograms that all share the
  254. // same Desc, but have different values for their variable labels. This is used
  255. // if you want to count the same thing partitioned by various dimensions
  256. // (e.g. HTTP request latencies, partitioned by status code and method). Create
  257. // instances with NewHistogramVec.
  258. type HistogramVec struct {
  259. *metricVec
  260. }
  261. // NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
  262. // partitioned by the given label names.
  263. func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
  264. desc := NewDesc(
  265. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  266. opts.Help,
  267. labelNames,
  268. opts.ConstLabels,
  269. )
  270. return &HistogramVec{
  271. metricVec: newMetricVec(desc, func(lvs ...string) Metric {
  272. return newHistogram(desc, opts, lvs...)
  273. }),
  274. }
  275. }
  276. // GetMetricWithLabelValues returns the Histogram for the given slice of label
  277. // values (same order as the VariableLabels in Desc). If that combination of
  278. // label values is accessed for the first time, a new Histogram is created.
  279. //
  280. // It is possible to call this method without using the returned Histogram to only
  281. // create the new Histogram but leave it at its starting value, a Histogram without
  282. // any observations.
  283. //
  284. // Keeping the Histogram for later use is possible (and should be considered if
  285. // performance is critical), but keep in mind that Reset, DeleteLabelValues and
  286. // Delete can be used to delete the Histogram from the HistogramVec. In that case, the
  287. // Histogram will still exist, but it will not be exported anymore, even if a
  288. // Histogram with the same label values is created later. See also the CounterVec
  289. // example.
  290. //
  291. // An error is returned if the number of label values is not the same as the
  292. // number of VariableLabels in Desc.
  293. //
  294. // Note that for more than one label value, this method is prone to mistakes
  295. // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
  296. // an alternative to avoid that type of mistake. For higher label numbers, the
  297. // latter has a much more readable (albeit more verbose) syntax, but it comes
  298. // with a performance overhead (for creating and processing the Labels map).
  299. // See also the GaugeVec example.
  300. func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
  301. metric, err := m.metricVec.getMetricWithLabelValues(lvs...)
  302. if metric != nil {
  303. return metric.(Observer), err
  304. }
  305. return nil, err
  306. }
  307. // GetMetricWith returns the Histogram for the given Labels map (the label names
  308. // must match those of the VariableLabels in Desc). If that label map is
  309. // accessed for the first time, a new Histogram is created. Implications of
  310. // creating a Histogram without using it and keeping the Histogram for later use
  311. // are the same as for GetMetricWithLabelValues.
  312. //
  313. // An error is returned if the number and names of the Labels are inconsistent
  314. // with those of the VariableLabels in Desc.
  315. //
  316. // This method is used for the same purpose as
  317. // GetMetricWithLabelValues(...string). See there for pros and cons of the two
  318. // methods.
  319. func (m *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
  320. metric, err := m.metricVec.getMetricWith(labels)
  321. if metric != nil {
  322. return metric.(Observer), err
  323. }
  324. return nil, err
  325. }
  326. // WithLabelValues works as GetMetricWithLabelValues, but panics where
  327. // GetMetricWithLabelValues would have returned an error. By not returning an
  328. // error, WithLabelValues allows shortcuts like
  329. // myVec.WithLabelValues("404", "GET").Observe(42.21)
  330. func (m *HistogramVec) WithLabelValues(lvs ...string) Observer {
  331. return m.metricVec.withLabelValues(lvs...).(Observer)
  332. }
  333. // With works as GetMetricWith, but panics where GetMetricWithLabels would have
  334. // returned an error. By not returning an error, With allows shortcuts like
  335. // myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
  336. func (m *HistogramVec) With(labels Labels) Observer {
  337. return m.metricVec.with(labels).(Observer)
  338. }
  339. type constHistogram struct {
  340. desc *Desc
  341. count uint64
  342. sum float64
  343. buckets map[float64]uint64
  344. labelPairs []*dto.LabelPair
  345. }
  346. func (h *constHistogram) Desc() *Desc {
  347. return h.desc
  348. }
  349. func (h *constHistogram) Write(out *dto.Metric) error {
  350. his := &dto.Histogram{}
  351. buckets := make([]*dto.Bucket, 0, len(h.buckets))
  352. his.SampleCount = proto.Uint64(h.count)
  353. his.SampleSum = proto.Float64(h.sum)
  354. for upperBound, count := range h.buckets {
  355. buckets = append(buckets, &dto.Bucket{
  356. CumulativeCount: proto.Uint64(count),
  357. UpperBound: proto.Float64(upperBound),
  358. })
  359. }
  360. if len(buckets) > 0 {
  361. sort.Sort(buckSort(buckets))
  362. }
  363. his.Bucket = buckets
  364. out.Histogram = his
  365. out.Label = h.labelPairs
  366. return nil
  367. }
  368. // NewConstHistogram returns a metric representing a Prometheus histogram with
  369. // fixed values for the count, sum, and bucket counts. As those parameters
  370. // cannot be changed, the returned value does not implement the Histogram
  371. // interface (but only the Metric interface). Users of this package will not
  372. // have much use for it in regular operations. However, when implementing custom
  373. // Collectors, it is useful as a throw-away metric that is generated on the fly
  374. // to send it to Prometheus in the Collect method.
  375. //
  376. // buckets is a map of upper bounds to cumulative counts, excluding the +Inf
  377. // bucket.
  378. //
  379. // NewConstHistogram returns an error if the length of labelValues is not
  380. // consistent with the variable labels in Desc.
  381. func NewConstHistogram(
  382. desc *Desc,
  383. count uint64,
  384. sum float64,
  385. buckets map[float64]uint64,
  386. labelValues ...string,
  387. ) (Metric, error) {
  388. if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
  389. return nil, err
  390. }
  391. return &constHistogram{
  392. desc: desc,
  393. count: count,
  394. sum: sum,
  395. buckets: buckets,
  396. labelPairs: makeLabelPairs(desc, labelValues),
  397. }, nil
  398. }
  399. // MustNewConstHistogram is a version of NewConstHistogram that panics where
  400. // NewConstMetric would have returned an error.
  401. func MustNewConstHistogram(
  402. desc *Desc,
  403. count uint64,
  404. sum float64,
  405. buckets map[float64]uint64,
  406. labelValues ...string,
  407. ) Metric {
  408. m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
  409. if err != nil {
  410. panic(err)
  411. }
  412. return m
  413. }
  414. type buckSort []*dto.Bucket
  415. func (s buckSort) Len() int {
  416. return len(s)
  417. }
  418. func (s buckSort) Swap(i, j int) {
  419. s[i], s[j] = s[j], s[i]
  420. }
  421. func (s buckSort) Less(i, j int) bool {
  422. return s[i].GetUpperBound() < s[j].GetUpperBound()
  423. }