histogram.go 14 KB

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