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