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 14package prometheus 15 16import ( 17 "fmt" 18 "math" 19 "runtime" 20 "sort" 21 "sync" 22 "sync/atomic" 23 24 "github.com/golang/protobuf/proto" 25 26 dto "github.com/prometheus/client_model/go" 27) 28 29// A Histogram counts individual observations from an event or sample stream in 30// configurable buckets. Similar to a summary, it also provides a sum of 31// observations and an observation count. 32// 33// On the Prometheus server, quantiles can be calculated from a Histogram using 34// the histogram_quantile function in the query language. 35// 36// Note that Histograms, in contrast to Summaries, can be aggregated with the 37// Prometheus query language (see the documentation for detailed 38// procedures). However, Histograms require the user to pre-define suitable 39// buckets, and they are in general less accurate. The Observe method of a 40// Histogram has a very low performance overhead in comparison with the Observe 41// method of a Summary. 42// 43// To create Histogram instances, use NewHistogram. 44type Histogram interface { 45 Metric 46 Collector 47 48 // Observe adds a single observation to the histogram. 49 Observe(float64) 50} 51 52// bucketLabel is used for the label that defines the upper bound of a 53// bucket of a histogram ("le" -> "less or equal"). 54const bucketLabel = "le" 55 56// DefBuckets are the default Histogram buckets. The default buckets are 57// tailored to broadly measure the response time (in seconds) of a network 58// service. Most likely, however, you will be required to define buckets 59// customized to your use case. 60var ( 61 DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} 62 63 errBucketLabelNotAllowed = fmt.Errorf( 64 "%q is not allowed as label name in histograms", bucketLabel, 65 ) 66) 67 68// LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest 69// bucket has an upper bound of 'start'. The final +Inf bucket is not counted 70// and not included in the returned slice. The returned slice is meant to be 71// used for the Buckets field of HistogramOpts. 72// 73// The function panics if 'count' is zero or negative. 74func LinearBuckets(start, width float64, count int) []float64 { 75 if count < 1 { 76 panic("LinearBuckets needs a positive count") 77 } 78 buckets := make([]float64, count) 79 for i := range buckets { 80 buckets[i] = start 81 start += width 82 } 83 return buckets 84} 85 86// ExponentialBuckets creates 'count' buckets, where the lowest bucket has an 87// upper bound of 'start' and each following bucket's upper bound is 'factor' 88// times the previous bucket's upper bound. The final +Inf bucket is not counted 89// and not included in the returned slice. The returned slice is meant to be 90// used for the Buckets field of HistogramOpts. 91// 92// The function panics if 'count' is 0 or negative, if 'start' is 0 or negative, 93// or if 'factor' is less than or equal 1. 94func ExponentialBuckets(start, factor float64, count int) []float64 { 95 if count < 1 { 96 panic("ExponentialBuckets needs a positive count") 97 } 98 if start <= 0 { 99 panic("ExponentialBuckets needs a positive start value") 100 } 101 if factor <= 1 { 102 panic("ExponentialBuckets needs a factor greater than 1") 103 } 104 buckets := make([]float64, count) 105 for i := range buckets { 106 buckets[i] = start 107 start *= factor 108 } 109 return buckets 110} 111 112// HistogramOpts bundles the options for creating a Histogram metric. It is 113// mandatory to set Name to a non-empty string. All other fields are optional 114// and can safely be left at their zero value, although it is strongly 115// encouraged to set a Help string. 116type HistogramOpts struct { 117 // Namespace, Subsystem, and Name are components of the fully-qualified 118 // name of the Histogram (created by joining these components with 119 // "_"). Only Name is mandatory, the others merely help structuring the 120 // name. Note that the fully-qualified name of the Histogram must be a 121 // valid Prometheus metric name. 122 Namespace string 123 Subsystem string 124 Name string 125 126 // Help provides information about this Histogram. 127 // 128 // Metrics with the same fully-qualified name must have the same Help 129 // string. 130 Help string 131 132 // ConstLabels are used to attach fixed labels to this metric. Metrics 133 // with the same fully-qualified name must have the same label names in 134 // their ConstLabels. 135 // 136 // ConstLabels are only used rarely. In particular, do not use them to 137 // attach the same labels to all your metrics. Those use cases are 138 // better covered by target labels set by the scraping Prometheus 139 // server, or by one specific metric (e.g. a build_info or a 140 // machine_role metric). See also 141 // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels 142 ConstLabels Labels 143 144 // Buckets defines the buckets into which observations are counted. Each 145 // element in the slice is the upper inclusive bound of a bucket. The 146 // values must be sorted in strictly increasing order. There is no need 147 // to add a highest bucket with +Inf bound, it will be added 148 // implicitly. The default value is DefBuckets. 149 Buckets []float64 150} 151 152// NewHistogram creates a new Histogram based on the provided HistogramOpts. It 153// panics if the buckets in HistogramOpts are not in strictly increasing order. 154func NewHistogram(opts HistogramOpts) Histogram { 155 return newHistogram( 156 NewDesc( 157 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), 158 opts.Help, 159 nil, 160 opts.ConstLabels, 161 ), 162 opts, 163 ) 164} 165 166func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram { 167 if len(desc.variableLabels) != len(labelValues) { 168 panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues)) 169 } 170 171 for _, n := range desc.variableLabels { 172 if n == bucketLabel { 173 panic(errBucketLabelNotAllowed) 174 } 175 } 176 for _, lp := range desc.constLabelPairs { 177 if lp.GetName() == bucketLabel { 178 panic(errBucketLabelNotAllowed) 179 } 180 } 181 182 if len(opts.Buckets) == 0 { 183 opts.Buckets = DefBuckets 184 } 185 186 h := &histogram{ 187 desc: desc, 188 upperBounds: opts.Buckets, 189 labelPairs: makeLabelPairs(desc, labelValues), 190 counts: [2]*histogramCounts{&histogramCounts{}, &histogramCounts{}}, 191 } 192 for i, upperBound := range h.upperBounds { 193 if i < len(h.upperBounds)-1 { 194 if upperBound >= h.upperBounds[i+1] { 195 panic(fmt.Errorf( 196 "histogram buckets must be in increasing order: %f >= %f", 197 upperBound, h.upperBounds[i+1], 198 )) 199 } 200 } else { 201 if math.IsInf(upperBound, +1) { 202 // The +Inf bucket is implicit. Remove it here. 203 h.upperBounds = h.upperBounds[:i] 204 } 205 } 206 } 207 // Finally we know the final length of h.upperBounds and can make buckets 208 // for both counts: 209 h.counts[0].buckets = make([]uint64, len(h.upperBounds)) 210 h.counts[1].buckets = make([]uint64, len(h.upperBounds)) 211 212 h.init(h) // Init self-collection. 213 return h 214} 215 216type histogramCounts struct { 217 // sumBits contains the bits of the float64 representing the sum of all 218 // observations. sumBits and count have to go first in the struct to 219 // guarantee alignment for atomic operations. 220 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG 221 sumBits uint64 222 count uint64 223 buckets []uint64 224} 225 226type histogram struct { 227 // countAndHotIdx is a complicated one. For lock-free yet atomic 228 // observations, we need to save the total count of observations again, 229 // combined with the index of the currently-hot counts struct, so that 230 // we can perform the operation on both values atomically. The least 231 // significant bit defines the hot counts struct. The remaining 63 bits 232 // represent the total count of observations. This happens under the 233 // assumption that the 63bit count will never overflow. Rationale: An 234 // observations takes about 30ns. Let's assume it could happen in 235 // 10ns. Overflowing the counter will then take at least (2^63)*10ns, 236 // which is about 3000 years. 237 // 238 // This has to be first in the struct for 64bit alignment. See 239 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG 240 countAndHotIdx uint64 241 242 selfCollector 243 desc *Desc 244 writeMtx sync.Mutex // Only used in the Write method. 245 246 upperBounds []float64 247 248 // Two counts, one is "hot" for lock-free observations, the other is 249 // "cold" for writing out a dto.Metric. It has to be an array of 250 // pointers to guarantee 64bit alignment of the histogramCounts, see 251 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG. 252 counts [2]*histogramCounts 253 hotIdx int // Index of currently-hot counts. Only used within Write. 254 255 labelPairs []*dto.LabelPair 256} 257 258func (h *histogram) Desc() *Desc { 259 return h.desc 260} 261 262func (h *histogram) Observe(v float64) { 263 // TODO(beorn7): For small numbers of buckets (<30), a linear search is 264 // slightly faster than the binary search. If we really care, we could 265 // switch from one search strategy to the other depending on the number 266 // of buckets. 267 // 268 // Microbenchmarks (BenchmarkHistogramNoLabels): 269 // 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op 270 // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op 271 // 300 buckets: 154 ns/op linear - binary 61.6 ns/op 272 i := sort.SearchFloat64s(h.upperBounds, v) 273 274 // We increment h.countAndHotIdx by 2 so that the counter in the upper 275 // 63 bits gets incremented by 1. At the same time, we get the new value 276 // back, which we can use to find the currently-hot counts. 277 n := atomic.AddUint64(&h.countAndHotIdx, 2) 278 hotCounts := h.counts[n%2] 279 280 if i < len(h.upperBounds) { 281 atomic.AddUint64(&hotCounts.buckets[i], 1) 282 } 283 for { 284 oldBits := atomic.LoadUint64(&hotCounts.sumBits) 285 newBits := math.Float64bits(math.Float64frombits(oldBits) + v) 286 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) { 287 break 288 } 289 } 290 // Increment count last as we take it as a signal that the observation 291 // is complete. 292 atomic.AddUint64(&hotCounts.count, 1) 293} 294 295func (h *histogram) Write(out *dto.Metric) error { 296 var ( 297 his = &dto.Histogram{} 298 buckets = make([]*dto.Bucket, len(h.upperBounds)) 299 hotCounts, coldCounts *histogramCounts 300 count uint64 301 ) 302 303 // For simplicity, we mutex the rest of this method. It is not in the 304 // hot path, i.e. Observe is called much more often than Write. The 305 // complication of making Write lock-free isn't worth it. 306 h.writeMtx.Lock() 307 defer h.writeMtx.Unlock() 308 309 // This is a bit arcane, which is why the following spells out this if 310 // clause in English: 311 // 312 // If the currently-hot counts struct is #0, we atomically increment 313 // h.countAndHotIdx by 1 so that from now on Observe will use the counts 314 // struct #1. Furthermore, the atomic increment gives us the new value, 315 // which, in its most significant 63 bits, tells us the count of 316 // observations done so far up to and including currently ongoing 317 // observations still using the counts struct just changed from hot to 318 // cold. To have a normal uint64 for the count, we bitshift by 1 and 319 // save the result in count. We also set h.hotIdx to 1 for the next 320 // Write call, and we will refer to counts #1 as hotCounts and to counts 321 // #0 as coldCounts. 322 // 323 // If the currently-hot counts struct is #1, we do the corresponding 324 // things the other way round. We have to _decrement_ h.countAndHotIdx 325 // (which is a bit arcane in itself, as we have to express -1 with an 326 // unsigned int...). 327 if h.hotIdx == 0 { 328 count = atomic.AddUint64(&h.countAndHotIdx, 1) >> 1 329 h.hotIdx = 1 330 hotCounts = h.counts[1] 331 coldCounts = h.counts[0] 332 } else { 333 count = atomic.AddUint64(&h.countAndHotIdx, ^uint64(0)) >> 1 // Decrement. 334 h.hotIdx = 0 335 hotCounts = h.counts[0] 336 coldCounts = h.counts[1] 337 } 338 339 // Now we have to wait for the now-declared-cold counts to actually cool 340 // down, i.e. wait for all observations still using it to finish. That's 341 // the case once the count in the cold counts struct is the same as the 342 // one atomically retrieved from the upper 63bits of h.countAndHotIdx. 343 for { 344 if count == atomic.LoadUint64(&coldCounts.count) { 345 break 346 } 347 runtime.Gosched() // Let observations get work done. 348 } 349 350 his.SampleCount = proto.Uint64(count) 351 his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))) 352 var cumCount uint64 353 for i, upperBound := range h.upperBounds { 354 cumCount += atomic.LoadUint64(&coldCounts.buckets[i]) 355 buckets[i] = &dto.Bucket{ 356 CumulativeCount: proto.Uint64(cumCount), 357 UpperBound: proto.Float64(upperBound), 358 } 359 } 360 361 his.Bucket = buckets 362 out.Histogram = his 363 out.Label = h.labelPairs 364 365 // Finally add all the cold counts to the new hot counts and reset the cold counts. 366 atomic.AddUint64(&hotCounts.count, count) 367 atomic.StoreUint64(&coldCounts.count, 0) 368 for { 369 oldBits := atomic.LoadUint64(&hotCounts.sumBits) 370 newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum()) 371 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) { 372 atomic.StoreUint64(&coldCounts.sumBits, 0) 373 break 374 } 375 } 376 for i := range h.upperBounds { 377 atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i])) 378 atomic.StoreUint64(&coldCounts.buckets[i], 0) 379 } 380 return nil 381} 382 383// HistogramVec is a Collector that bundles a set of Histograms that all share the 384// same Desc, but have different values for their variable labels. This is used 385// if you want to count the same thing partitioned by various dimensions 386// (e.g. HTTP request latencies, partitioned by status code and method). Create 387// instances with NewHistogramVec. 388type HistogramVec struct { 389 *metricVec 390} 391 392// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and 393// partitioned by the given label names. 394func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec { 395 desc := NewDesc( 396 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), 397 opts.Help, 398 labelNames, 399 opts.ConstLabels, 400 ) 401 return &HistogramVec{ 402 metricVec: newMetricVec(desc, func(lvs ...string) Metric { 403 return newHistogram(desc, opts, lvs...) 404 }), 405 } 406} 407 408// GetMetricWithLabelValues returns the Histogram for the given slice of label 409// values (same order as the VariableLabels in Desc). If that combination of 410// label values is accessed for the first time, a new Histogram is created. 411// 412// It is possible to call this method without using the returned Histogram to only 413// create the new Histogram but leave it at its starting value, a Histogram without 414// any observations. 415// 416// Keeping the Histogram for later use is possible (and should be considered if 417// performance is critical), but keep in mind that Reset, DeleteLabelValues and 418// Delete can be used to delete the Histogram from the HistogramVec. In that case, the 419// Histogram will still exist, but it will not be exported anymore, even if a 420// Histogram with the same label values is created later. See also the CounterVec 421// example. 422// 423// An error is returned if the number of label values is not the same as the 424// number of VariableLabels in Desc (minus any curried labels). 425// 426// Note that for more than one label value, this method is prone to mistakes 427// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as 428// an alternative to avoid that type of mistake. For higher label numbers, the 429// latter has a much more readable (albeit more verbose) syntax, but it comes 430// with a performance overhead (for creating and processing the Labels map). 431// See also the GaugeVec example. 432func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { 433 metric, err := v.metricVec.getMetricWithLabelValues(lvs...) 434 if metric != nil { 435 return metric.(Observer), err 436 } 437 return nil, err 438} 439 440// GetMetricWith returns the Histogram for the given Labels map (the label names 441// must match those of the VariableLabels in Desc). If that label map is 442// accessed for the first time, a new Histogram is created. Implications of 443// creating a Histogram without using it and keeping the Histogram for later use 444// are the same as for GetMetricWithLabelValues. 445// 446// An error is returned if the number and names of the Labels are inconsistent 447// with those of the VariableLabels in Desc (minus any curried labels). 448// 449// This method is used for the same purpose as 450// GetMetricWithLabelValues(...string). See there for pros and cons of the two 451// methods. 452func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) { 453 metric, err := v.metricVec.getMetricWith(labels) 454 if metric != nil { 455 return metric.(Observer), err 456 } 457 return nil, err 458} 459 460// WithLabelValues works as GetMetricWithLabelValues, but panics where 461// GetMetricWithLabelValues would have returned an error. Not returning an 462// error allows shortcuts like 463// myVec.WithLabelValues("404", "GET").Observe(42.21) 464func (v *HistogramVec) WithLabelValues(lvs ...string) Observer { 465 h, err := v.GetMetricWithLabelValues(lvs...) 466 if err != nil { 467 panic(err) 468 } 469 return h 470} 471 472// With works as GetMetricWith but panics where GetMetricWithLabels would have 473// returned an error. Not returning an error allows shortcuts like 474// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21) 475func (v *HistogramVec) With(labels Labels) Observer { 476 h, err := v.GetMetricWith(labels) 477 if err != nil { 478 panic(err) 479 } 480 return h 481} 482 483// CurryWith returns a vector curried with the provided labels, i.e. the 484// returned vector has those labels pre-set for all labeled operations performed 485// on it. The cardinality of the curried vector is reduced accordingly. The 486// order of the remaining labels stays the same (just with the curried labels 487// taken out of the sequence – which is relevant for the 488// (GetMetric)WithLabelValues methods). It is possible to curry a curried 489// vector, but only with labels not yet used for currying before. 490// 491// The metrics contained in the HistogramVec are shared between the curried and 492// uncurried vectors. They are just accessed differently. Curried and uncurried 493// vectors behave identically in terms of collection. Only one must be 494// registered with a given registry (usually the uncurried version). The Reset 495// method deletes all metrics, even if called on a curried vector. 496func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) { 497 vec, err := v.curryWith(labels) 498 if vec != nil { 499 return &HistogramVec{vec}, err 500 } 501 return nil, err 502} 503 504// MustCurryWith works as CurryWith but panics where CurryWith would have 505// returned an error. 506func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec { 507 vec, err := v.CurryWith(labels) 508 if err != nil { 509 panic(err) 510 } 511 return vec 512} 513 514type constHistogram struct { 515 desc *Desc 516 count uint64 517 sum float64 518 buckets map[float64]uint64 519 labelPairs []*dto.LabelPair 520} 521 522func (h *constHistogram) Desc() *Desc { 523 return h.desc 524} 525 526func (h *constHistogram) Write(out *dto.Metric) error { 527 his := &dto.Histogram{} 528 buckets := make([]*dto.Bucket, 0, len(h.buckets)) 529 530 his.SampleCount = proto.Uint64(h.count) 531 his.SampleSum = proto.Float64(h.sum) 532 533 for upperBound, count := range h.buckets { 534 buckets = append(buckets, &dto.Bucket{ 535 CumulativeCount: proto.Uint64(count), 536 UpperBound: proto.Float64(upperBound), 537 }) 538 } 539 540 if len(buckets) > 0 { 541 sort.Sort(buckSort(buckets)) 542 } 543 his.Bucket = buckets 544 545 out.Histogram = his 546 out.Label = h.labelPairs 547 548 return nil 549} 550 551// NewConstHistogram returns a metric representing a Prometheus histogram with 552// fixed values for the count, sum, and bucket counts. As those parameters 553// cannot be changed, the returned value does not implement the Histogram 554// interface (but only the Metric interface). Users of this package will not 555// have much use for it in regular operations. However, when implementing custom 556// Collectors, it is useful as a throw-away metric that is generated on the fly 557// to send it to Prometheus in the Collect method. 558// 559// buckets is a map of upper bounds to cumulative counts, excluding the +Inf 560// bucket. 561// 562// NewConstHistogram returns an error if the length of labelValues is not 563// consistent with the variable labels in Desc or if Desc is invalid. 564func NewConstHistogram( 565 desc *Desc, 566 count uint64, 567 sum float64, 568 buckets map[float64]uint64, 569 labelValues ...string, 570) (Metric, error) { 571 if desc.err != nil { 572 return nil, desc.err 573 } 574 if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { 575 return nil, err 576 } 577 return &constHistogram{ 578 desc: desc, 579 count: count, 580 sum: sum, 581 buckets: buckets, 582 labelPairs: makeLabelPairs(desc, labelValues), 583 }, nil 584} 585 586// MustNewConstHistogram is a version of NewConstHistogram that panics where 587// NewConstMetric would have returned an error. 588func MustNewConstHistogram( 589 desc *Desc, 590 count uint64, 591 sum float64, 592 buckets map[float64]uint64, 593 labelValues ...string, 594) Metric { 595 m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...) 596 if err != nil { 597 panic(err) 598 } 599 return m 600} 601 602type buckSort []*dto.Bucket 603 604func (s buckSort) Len() int { 605 return len(s) 606} 607 608func (s buckSort) Swap(i, j int) { 609 s[i], s[j] = s[j], s[i] 610} 611 612func (s buckSort) Less(i, j int) bool { 613 return s[i].GetUpperBound() < s[j].GetUpperBound() 614} 615