histogram.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  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. At least one label name must be
  263. // provided.
  264. func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
  265. desc := NewDesc(
  266. BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
  267. opts.Help,
  268. labelNames,
  269. opts.ConstLabels,
  270. )
  271. return &HistogramVec{
  272. MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
  273. return newHistogram(desc, opts, lvs...)
  274. }),
  275. }
  276. }
  277. // GetMetricWithLabelValues replaces the method of the same name in
  278. // MetricVec. The difference is that this method returns a Histogram and not a
  279. // Metric so that no type conversion is required.
  280. func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Histogram, error) {
  281. metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
  282. if metric != nil {
  283. return metric.(Histogram), err
  284. }
  285. return nil, err
  286. }
  287. // GetMetricWith replaces the method of the same name in MetricVec. The
  288. // difference is that this method returns a Histogram and not a Metric so that no
  289. // type conversion is required.
  290. func (m *HistogramVec) GetMetricWith(labels Labels) (Histogram, error) {
  291. metric, err := m.MetricVec.GetMetricWith(labels)
  292. if metric != nil {
  293. return metric.(Histogram), err
  294. }
  295. return nil, err
  296. }
  297. // WithLabelValues works as GetMetricWithLabelValues, but panics where
  298. // GetMetricWithLabelValues would have returned an error. By not returning an
  299. // error, WithLabelValues allows shortcuts like
  300. // myVec.WithLabelValues("404", "GET").Observe(42.21)
  301. func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
  302. return m.MetricVec.WithLabelValues(lvs...).(Histogram)
  303. }
  304. // With works as GetMetricWith, but panics where GetMetricWithLabels would have
  305. // returned an error. By not returning an error, With allows shortcuts like
  306. // myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
  307. func (m *HistogramVec) With(labels Labels) Histogram {
  308. return m.MetricVec.With(labels).(Histogram)
  309. }
  310. type constHistogram struct {
  311. desc *Desc
  312. count uint64
  313. sum float64
  314. buckets map[float64]uint64
  315. labelPairs []*dto.LabelPair
  316. }
  317. func (h *constHistogram) Desc() *Desc {
  318. return h.desc
  319. }
  320. func (h *constHistogram) Write(out *dto.Metric) error {
  321. his := &dto.Histogram{}
  322. buckets := make([]*dto.Bucket, 0, len(h.buckets))
  323. his.SampleCount = proto.Uint64(h.count)
  324. his.SampleSum = proto.Float64(h.sum)
  325. for upperBound, count := range h.buckets {
  326. buckets = append(buckets, &dto.Bucket{
  327. CumulativeCount: proto.Uint64(count),
  328. UpperBound: proto.Float64(upperBound),
  329. })
  330. }
  331. if len(buckets) > 0 {
  332. sort.Sort(buckSort(buckets))
  333. }
  334. his.Bucket = buckets
  335. out.Histogram = his
  336. out.Label = h.labelPairs
  337. return nil
  338. }
  339. // NewConstHistogram returns a metric representing a Prometheus histogram with
  340. // fixed values for the count, sum, and bucket counts. As those parameters
  341. // cannot be changed, the returned value does not implement the Histogram
  342. // interface (but only the Metric interface). Users of this package will not
  343. // have much use for it in regular operations. However, when implementing custom
  344. // Collectors, it is useful as a throw-away metric that is generated on the fly
  345. // to send it to Prometheus in the Collect method.
  346. //
  347. // buckets is a map of upper bounds to cumulative counts, excluding the +Inf
  348. // bucket.
  349. //
  350. // NewConstHistogram returns an error if the length of labelValues is not
  351. // consistent with the variable labels in Desc.
  352. func NewConstHistogram(
  353. desc *Desc,
  354. count uint64,
  355. sum float64,
  356. buckets map[float64]uint64,
  357. labelValues ...string,
  358. ) (Metric, error) {
  359. if len(desc.variableLabels) != len(labelValues) {
  360. return nil, errInconsistentCardinality
  361. }
  362. return &constHistogram{
  363. desc: desc,
  364. count: count,
  365. sum: sum,
  366. buckets: buckets,
  367. labelPairs: makeLabelPairs(desc, labelValues),
  368. }, nil
  369. }
  370. // MustNewConstHistogram is a version of NewConstHistogram that panics where
  371. // NewConstMetric would have returned an error.
  372. func MustNewConstHistogram(
  373. desc *Desc,
  374. count uint64,
  375. sum float64,
  376. buckets map[float64]uint64,
  377. labelValues ...string,
  378. ) Metric {
  379. m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
  380. if err != nil {
  381. panic(err)
  382. }
  383. return m
  384. }
  385. type buckSort []*dto.Bucket
  386. func (s buckSort) Len() int {
  387. return len(s)
  388. }
  389. func (s buckSort) Swap(i, j int) {
  390. s[i], s[j] = s[j], s[i]
  391. }
  392. func (s buckSort) Less(i, j int) bool {
  393. return s[i].GetUpperBound() < s[j].GetUpperBound()
  394. }