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 enables lock-free writes with use of atomic updates. 228 // The most significant bit is the hot index [0 or 1] of the count field 229 // below. Observe calls update the hot one. All remaining bits count the 230 // number of Observe calls. Observe starts by incrementing this counter, 231 // and finish by incrementing the count field in the respective 232 // histogramCounts, as a marker for completion. 233 // 234 // Calls of the Write method (which are non-mutating reads from the 235 // perspective of the histogram) swap the hot–cold under the writeMtx 236 // lock. A cooldown is awaited (while locked) by comparing the number of 237 // observations with the initiation count. Once they match, then the 238 // last observation on the now cool one has completed. All cool fields must 239 // be merged into the new hot before releasing writeMtx. 240 // 241 // Fields with atomic access first! See alignment constraint: 242 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG 243 countAndHotIdx uint64 244 245 selfCollector 246 desc *Desc 247 writeMtx sync.Mutex // Only used in the Write method. 248 249 // Two counts, one is "hot" for lock-free observations, the other is 250 // "cold" for writing out a dto.Metric. It has to be an array of 251 // pointers to guarantee 64bit alignment of the histogramCounts, see 252 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG. 253 counts [2]*histogramCounts 254 255 upperBounds []float64 256 labelPairs []*dto.LabelPair 257} 258 259func (h *histogram) Desc() *Desc { 260 return h.desc 261} 262 263func (h *histogram) Observe(v float64) { 264 // TODO(beorn7): For small numbers of buckets (<30), a linear search is 265 // slightly faster than the binary search. If we really care, we could 266 // switch from one search strategy to the other depending on the number 267 // of buckets. 268 // 269 // Microbenchmarks (BenchmarkHistogramNoLabels): 270 // 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op 271 // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op 272 // 300 buckets: 154 ns/op linear - binary 61.6 ns/op 273 i := sort.SearchFloat64s(h.upperBounds, v) 274 275 // We increment h.countAndHotIdx so that the counter in the lower 276 // 63 bits gets incremented. At the same time, we get the new value 277 // back, which we can use to find the currently-hot counts. 278 n := atomic.AddUint64(&h.countAndHotIdx, 1) 279 hotCounts := h.counts[n>>63] 280 281 if i < len(h.upperBounds) { 282 atomic.AddUint64(&hotCounts.buckets[i], 1) 283 } 284 for { 285 oldBits := atomic.LoadUint64(&hotCounts.sumBits) 286 newBits := math.Float64bits(math.Float64frombits(oldBits) + v) 287 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) { 288 break 289 } 290 } 291 // Increment count last as we take it as a signal that the observation 292 // is complete. 293 atomic.AddUint64(&hotCounts.count, 1) 294} 295 296func (h *histogram) Write(out *dto.Metric) error { 297 // For simplicity, we protect this whole method by a mutex. It is not in 298 // the hot path, i.e. Observe is called much more often than Write. The 299 // complication of making Write lock-free isn't worth it, if possible at 300 // all. 301 h.writeMtx.Lock() 302 defer h.writeMtx.Unlock() 303 304 // Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0) 305 // without touching the count bits. See the struct comments for a full 306 // description of the algorithm. 307 n := atomic.AddUint64(&h.countAndHotIdx, 1<<63) 308 // count is contained unchanged in the lower 63 bits. 309 count := n & ((1 << 63) - 1) 310 // The most significant bit tells us which counts is hot. The complement 311 // is thus the cold one. 312 hotCounts := h.counts[n>>63] 313 coldCounts := h.counts[(^n)>>63] 314 315 // Await cooldown. 316 for count != atomic.LoadUint64(&coldCounts.count) { 317 runtime.Gosched() // Let observations get work done. 318 } 319 320 his := &dto.Histogram{ 321 Bucket: make([]*dto.Bucket, len(h.upperBounds)), 322 SampleCount: proto.Uint64(count), 323 SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))), 324 } 325 var cumCount uint64 326 for i, upperBound := range h.upperBounds { 327 cumCount += atomic.LoadUint64(&coldCounts.buckets[i]) 328 his.Bucket[i] = &dto.Bucket{ 329 CumulativeCount: proto.Uint64(cumCount), 330 UpperBound: proto.Float64(upperBound), 331 } 332 } 333 334 out.Histogram = his 335 out.Label = h.labelPairs 336 337 // Finally add all the cold counts to the new hot counts and reset the cold counts. 338 atomic.AddUint64(&hotCounts.count, count) 339 atomic.StoreUint64(&coldCounts.count, 0) 340 for { 341 oldBits := atomic.LoadUint64(&hotCounts.sumBits) 342 newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum()) 343 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) { 344 atomic.StoreUint64(&coldCounts.sumBits, 0) 345 break 346 } 347 } 348 for i := range h.upperBounds { 349 atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i])) 350 atomic.StoreUint64(&coldCounts.buckets[i], 0) 351 } 352 return nil 353} 354 355// HistogramVec is a Collector that bundles a set of Histograms that all share the 356// same Desc, but have different values for their variable labels. This is used 357// if you want to count the same thing partitioned by various dimensions 358// (e.g. HTTP request latencies, partitioned by status code and method). Create 359// instances with NewHistogramVec. 360type HistogramVec struct { 361 *metricVec 362} 363 364// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and 365// partitioned by the given label names. 366func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec { 367 desc := NewDesc( 368 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), 369 opts.Help, 370 labelNames, 371 opts.ConstLabels, 372 ) 373 return &HistogramVec{ 374 metricVec: newMetricVec(desc, func(lvs ...string) Metric { 375 return newHistogram(desc, opts, lvs...) 376 }), 377 } 378} 379 380// GetMetricWithLabelValues returns the Histogram for the given slice of label 381// values (same order as the VariableLabels in Desc). If that combination of 382// label values is accessed for the first time, a new Histogram is created. 383// 384// It is possible to call this method without using the returned Histogram to only 385// create the new Histogram but leave it at its starting value, a Histogram without 386// any observations. 387// 388// Keeping the Histogram for later use is possible (and should be considered if 389// performance is critical), but keep in mind that Reset, DeleteLabelValues and 390// Delete can be used to delete the Histogram from the HistogramVec. In that case, the 391// Histogram will still exist, but it will not be exported anymore, even if a 392// Histogram with the same label values is created later. See also the CounterVec 393// example. 394// 395// An error is returned if the number of label values is not the same as the 396// number of VariableLabels in Desc (minus any curried labels). 397// 398// Note that for more than one label value, this method is prone to mistakes 399// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as 400// an alternative to avoid that type of mistake. For higher label numbers, the 401// latter has a much more readable (albeit more verbose) syntax, but it comes 402// with a performance overhead (for creating and processing the Labels map). 403// See also the GaugeVec example. 404func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { 405 metric, err := v.metricVec.getMetricWithLabelValues(lvs...) 406 if metric != nil { 407 return metric.(Observer), err 408 } 409 return nil, err 410} 411 412// GetMetricWith returns the Histogram for the given Labels map (the label names 413// must match those of the VariableLabels in Desc). If that label map is 414// accessed for the first time, a new Histogram is created. Implications of 415// creating a Histogram without using it and keeping the Histogram for later use 416// are the same as for GetMetricWithLabelValues. 417// 418// An error is returned if the number and names of the Labels are inconsistent 419// with those of the VariableLabels in Desc (minus any curried labels). 420// 421// This method is used for the same purpose as 422// GetMetricWithLabelValues(...string). See there for pros and cons of the two 423// methods. 424func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) { 425 metric, err := v.metricVec.getMetricWith(labels) 426 if metric != nil { 427 return metric.(Observer), err 428 } 429 return nil, err 430} 431 432// WithLabelValues works as GetMetricWithLabelValues, but panics where 433// GetMetricWithLabelValues would have returned an error. Not returning an 434// error allows shortcuts like 435// myVec.WithLabelValues("404", "GET").Observe(42.21) 436func (v *HistogramVec) WithLabelValues(lvs ...string) Observer { 437 h, err := v.GetMetricWithLabelValues(lvs...) 438 if err != nil { 439 panic(err) 440 } 441 return h 442} 443 444// With works as GetMetricWith but panics where GetMetricWithLabels would have 445// returned an error. Not returning an error allows shortcuts like 446// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21) 447func (v *HistogramVec) With(labels Labels) Observer { 448 h, err := v.GetMetricWith(labels) 449 if err != nil { 450 panic(err) 451 } 452 return h 453} 454 455// CurryWith returns a vector curried with the provided labels, i.e. the 456// returned vector has those labels pre-set for all labeled operations performed 457// on it. The cardinality of the curried vector is reduced accordingly. The 458// order of the remaining labels stays the same (just with the curried labels 459// taken out of the sequence – which is relevant for the 460// (GetMetric)WithLabelValues methods). It is possible to curry a curried 461// vector, but only with labels not yet used for currying before. 462// 463// The metrics contained in the HistogramVec are shared between the curried and 464// uncurried vectors. They are just accessed differently. Curried and uncurried 465// vectors behave identically in terms of collection. Only one must be 466// registered with a given registry (usually the uncurried version). The Reset 467// method deletes all metrics, even if called on a curried vector. 468func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) { 469 vec, err := v.curryWith(labels) 470 if vec != nil { 471 return &HistogramVec{vec}, err 472 } 473 return nil, err 474} 475 476// MustCurryWith works as CurryWith but panics where CurryWith would have 477// returned an error. 478func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec { 479 vec, err := v.CurryWith(labels) 480 if err != nil { 481 panic(err) 482 } 483 return vec 484} 485 486type constHistogram struct { 487 desc *Desc 488 count uint64 489 sum float64 490 buckets map[float64]uint64 491 labelPairs []*dto.LabelPair 492} 493 494func (h *constHistogram) Desc() *Desc { 495 return h.desc 496} 497 498func (h *constHistogram) Write(out *dto.Metric) error { 499 his := &dto.Histogram{} 500 buckets := make([]*dto.Bucket, 0, len(h.buckets)) 501 502 his.SampleCount = proto.Uint64(h.count) 503 his.SampleSum = proto.Float64(h.sum) 504 505 for upperBound, count := range h.buckets { 506 buckets = append(buckets, &dto.Bucket{ 507 CumulativeCount: proto.Uint64(count), 508 UpperBound: proto.Float64(upperBound), 509 }) 510 } 511 512 if len(buckets) > 0 { 513 sort.Sort(buckSort(buckets)) 514 } 515 his.Bucket = buckets 516 517 out.Histogram = his 518 out.Label = h.labelPairs 519 520 return nil 521} 522 523// NewConstHistogram returns a metric representing a Prometheus histogram with 524// fixed values for the count, sum, and bucket counts. As those parameters 525// cannot be changed, the returned value does not implement the Histogram 526// interface (but only the Metric interface). Users of this package will not 527// have much use for it in regular operations. However, when implementing custom 528// Collectors, it is useful as a throw-away metric that is generated on the fly 529// to send it to Prometheus in the Collect method. 530// 531// buckets is a map of upper bounds to cumulative counts, excluding the +Inf 532// bucket. 533// 534// NewConstHistogram returns an error if the length of labelValues is not 535// consistent with the variable labels in Desc or if Desc is invalid. 536func NewConstHistogram( 537 desc *Desc, 538 count uint64, 539 sum float64, 540 buckets map[float64]uint64, 541 labelValues ...string, 542) (Metric, error) { 543 if desc.err != nil { 544 return nil, desc.err 545 } 546 if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { 547 return nil, err 548 } 549 return &constHistogram{ 550 desc: desc, 551 count: count, 552 sum: sum, 553 buckets: buckets, 554 labelPairs: makeLabelPairs(desc, labelValues), 555 }, nil 556} 557 558// MustNewConstHistogram is a version of NewConstHistogram that panics where 559// NewConstMetric would have returned an error. 560func MustNewConstHistogram( 561 desc *Desc, 562 count uint64, 563 sum float64, 564 buckets map[float64]uint64, 565 labelValues ...string, 566) Metric { 567 m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...) 568 if err != nil { 569 panic(err) 570 } 571 return m 572} 573 574type buckSort []*dto.Bucket 575 576func (s buckSort) Len() int { 577 return len(s) 578} 579 580func (s buckSort) Swap(i, j int) { 581 s[i], s[j] = s[j], s[i] 582} 583 584func (s buckSort) Less(i, j int) bool { 585 return s[i].GetUpperBound() < s[j].GetUpperBound() 586} 587