1// Copyright 2014 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 "sync" 19 20 "github.com/prometheus/common/model" 21) 22 23// MetricVec is a Collector to bundle metrics of the same name that differ in 24// their label values. MetricVec is not used directly but as a building block 25// for implementations of vectors of a given metric type, like GaugeVec, 26// CounterVec, SummaryVec, and HistogramVec. It is exported so that it can be 27// used for custom Metric implementations. 28// 29// To create a FooVec for custom Metric Foo, embed a pointer to MetricVec in 30// FooVec and initialize it with NewMetricVec. Implement wrappers for 31// GetMetricWithLabelValues and GetMetricWith that return (Foo, error) rather 32// than (Metric, error). Similarly, create a wrapper for CurryWith that returns 33// (*FooVec, error) rather than (*MetricVec, error). It is recommended to also 34// add the convenience methods WithLabelValues, With, and MustCurryWith, which 35// panic instead of returning errors. See also the MetricVec example. 36type MetricVec struct { 37 *metricMap 38 39 curry []curriedLabelValue 40 41 // hashAdd and hashAddByte can be replaced for testing collision handling. 42 hashAdd func(h uint64, s string) uint64 43 hashAddByte func(h uint64, b byte) uint64 44} 45 46// NewMetricVec returns an initialized metricVec. 47func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec { 48 return &MetricVec{ 49 metricMap: &metricMap{ 50 metrics: map[uint64][]metricWithLabelValues{}, 51 desc: desc, 52 newMetric: newMetric, 53 }, 54 hashAdd: hashAdd, 55 hashAddByte: hashAddByte, 56 } 57} 58 59// DeleteLabelValues removes the metric where the variable labels are the same 60// as those passed in as labels (same order as the VariableLabels in Desc). It 61// returns true if a metric was deleted. 62// 63// It is not an error if the number of label values is not the same as the 64// number of VariableLabels in Desc. However, such inconsistent label count can 65// never match an actual metric, so the method will always return false in that 66// case. 67// 68// Note that for more than one label value, this method is prone to mistakes 69// caused by an incorrect order of arguments. Consider Delete(Labels) as an 70// alternative to avoid that type of mistake. For higher label numbers, the 71// latter has a much more readable (albeit more verbose) syntax, but it comes 72// with a performance overhead (for creating and processing the Labels map). 73// See also the CounterVec example. 74func (m *MetricVec) DeleteLabelValues(lvs ...string) bool { 75 h, err := m.hashLabelValues(lvs) 76 if err != nil { 77 return false 78 } 79 80 return m.metricMap.deleteByHashWithLabelValues(h, lvs, m.curry) 81} 82 83// Delete deletes the metric where the variable labels are the same as those 84// passed in as labels. It returns true if a metric was deleted. 85// 86// It is not an error if the number and names of the Labels are inconsistent 87// with those of the VariableLabels in Desc. However, such inconsistent Labels 88// can never match an actual metric, so the method will always return false in 89// that case. 90// 91// This method is used for the same purpose as DeleteLabelValues(...string). See 92// there for pros and cons of the two methods. 93func (m *MetricVec) Delete(labels Labels) bool { 94 h, err := m.hashLabels(labels) 95 if err != nil { 96 return false 97 } 98 99 return m.metricMap.deleteByHashWithLabels(h, labels, m.curry) 100} 101 102// Without explicit forwarding of Describe, Collect, Reset, those methods won't 103// show up in GoDoc. 104 105// Describe implements Collector. 106func (m *MetricVec) Describe(ch chan<- *Desc) { m.metricMap.Describe(ch) } 107 108// Collect implements Collector. 109func (m *MetricVec) Collect(ch chan<- Metric) { m.metricMap.Collect(ch) } 110 111// Reset deletes all metrics in this vector. 112func (m *MetricVec) Reset() { m.metricMap.Reset() } 113 114// CurryWith returns a vector curried with the provided labels, i.e. the 115// returned vector has those labels pre-set for all labeled operations performed 116// on it. The cardinality of the curried vector is reduced accordingly. The 117// order of the remaining labels stays the same (just with the curried labels 118// taken out of the sequence – which is relevant for the 119// (GetMetric)WithLabelValues methods). It is possible to curry a curried 120// vector, but only with labels not yet used for currying before. 121// 122// The metrics contained in the MetricVec are shared between the curried and 123// uncurried vectors. They are just accessed differently. Curried and uncurried 124// vectors behave identically in terms of collection. Only one must be 125// registered with a given registry (usually the uncurried version). The Reset 126// method deletes all metrics, even if called on a curried vector. 127// 128// Note that CurryWith is usually not called directly but through a wrapper 129// around MetricVec, implementing a vector for a specific Metric 130// implementation, for example GaugeVec. 131func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) { 132 var ( 133 newCurry []curriedLabelValue 134 oldCurry = m.curry 135 iCurry int 136 ) 137 for i, label := range m.desc.variableLabels { 138 val, ok := labels[label] 139 if iCurry < len(oldCurry) && oldCurry[iCurry].index == i { 140 if ok { 141 return nil, fmt.Errorf("label name %q is already curried", label) 142 } 143 newCurry = append(newCurry, oldCurry[iCurry]) 144 iCurry++ 145 } else { 146 if !ok { 147 continue // Label stays uncurried. 148 } 149 newCurry = append(newCurry, curriedLabelValue{i, val}) 150 } 151 } 152 if l := len(oldCurry) + len(labels) - len(newCurry); l > 0 { 153 return nil, fmt.Errorf("%d unknown label(s) found during currying", l) 154 } 155 156 return &MetricVec{ 157 metricMap: m.metricMap, 158 curry: newCurry, 159 hashAdd: m.hashAdd, 160 hashAddByte: m.hashAddByte, 161 }, nil 162} 163 164// GetMetricWithLabelValues returns the Metric for the given slice of label 165// values (same order as the variable labels in Desc). If that combination of 166// label values is accessed for the first time, a new Metric is created (by 167// calling the newMetric function provided during construction of the 168// MetricVec). 169// 170// It is possible to call this method without using the returned Metry to only 171// create the new Metric but leave it in its intitial state. 172// 173// Keeping the Metric for later use is possible (and should be considered if 174// performance is critical), but keep in mind that Reset, DeleteLabelValues and 175// Delete can be used to delete the Metric from the MetricVec. In that case, the 176// Metric will still exist, but it will not be exported anymore, even if a 177// Metric with the same label values is created later. 178// 179// An error is returned if the number of label values is not the same as the 180// number of variable labels in Desc (minus any curried labels). 181// 182// Note that for more than one label value, this method is prone to mistakes 183// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as 184// an alternative to avoid that type of mistake. For higher label numbers, the 185// latter has a much more readable (albeit more verbose) syntax, but it comes 186// with a performance overhead (for creating and processing the Labels map). 187// 188// Note that GetMetricWithLabelValues is usually not called directly but through 189// a wrapper around MetricVec, implementing a vector for a specific Metric 190// implementation, for example GaugeVec. 191func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) { 192 h, err := m.hashLabelValues(lvs) 193 if err != nil { 194 return nil, err 195 } 196 197 return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil 198} 199 200// GetMetricWith returns the Metric for the given Labels map (the label names 201// must match those of the variable labels in Desc). If that label map is 202// accessed for the first time, a new Metric is created. Implications of 203// creating a Metric without using it and keeping the Metric for later use 204// are the same as for GetMetricWithLabelValues. 205// 206// An error is returned if the number and names of the Labels are inconsistent 207// with those of the variable labels in Desc (minus any curried labels). 208// 209// This method is used for the same purpose as 210// GetMetricWithLabelValues(...string). See there for pros and cons of the two 211// methods. 212// 213// Note that GetMetricWith is usually not called directly but through a wrapper 214// around MetricVec, implementing a vector for a specific Metric implementation, 215// for example GaugeVec. 216func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) { 217 h, err := m.hashLabels(labels) 218 if err != nil { 219 return nil, err 220 } 221 222 return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil 223} 224 225func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) { 226 if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil { 227 return 0, err 228 } 229 230 var ( 231 h = hashNew() 232 curry = m.curry 233 iVals, iCurry int 234 ) 235 for i := 0; i < len(m.desc.variableLabels); i++ { 236 if iCurry < len(curry) && curry[iCurry].index == i { 237 h = m.hashAdd(h, curry[iCurry].value) 238 iCurry++ 239 } else { 240 h = m.hashAdd(h, vals[iVals]) 241 iVals++ 242 } 243 h = m.hashAddByte(h, model.SeparatorByte) 244 } 245 return h, nil 246} 247 248func (m *MetricVec) hashLabels(labels Labels) (uint64, error) { 249 if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil { 250 return 0, err 251 } 252 253 var ( 254 h = hashNew() 255 curry = m.curry 256 iCurry int 257 ) 258 for i, label := range m.desc.variableLabels { 259 val, ok := labels[label] 260 if iCurry < len(curry) && curry[iCurry].index == i { 261 if ok { 262 return 0, fmt.Errorf("label name %q is already curried", label) 263 } 264 h = m.hashAdd(h, curry[iCurry].value) 265 iCurry++ 266 } else { 267 if !ok { 268 return 0, fmt.Errorf("label name %q missing in label map", label) 269 } 270 h = m.hashAdd(h, val) 271 } 272 h = m.hashAddByte(h, model.SeparatorByte) 273 } 274 return h, nil 275} 276 277// metricWithLabelValues provides the metric and its label values for 278// disambiguation on hash collision. 279type metricWithLabelValues struct { 280 values []string 281 metric Metric 282} 283 284// curriedLabelValue sets the curried value for a label at the given index. 285type curriedLabelValue struct { 286 index int 287 value string 288} 289 290// metricMap is a helper for metricVec and shared between differently curried 291// metricVecs. 292type metricMap struct { 293 mtx sync.RWMutex // Protects metrics. 294 metrics map[uint64][]metricWithLabelValues 295 desc *Desc 296 newMetric func(labelValues ...string) Metric 297} 298 299// Describe implements Collector. It will send exactly one Desc to the provided 300// channel. 301func (m *metricMap) Describe(ch chan<- *Desc) { 302 ch <- m.desc 303} 304 305// Collect implements Collector. 306func (m *metricMap) Collect(ch chan<- Metric) { 307 m.mtx.RLock() 308 defer m.mtx.RUnlock() 309 310 for _, metrics := range m.metrics { 311 for _, metric := range metrics { 312 ch <- metric.metric 313 } 314 } 315} 316 317// Reset deletes all metrics in this vector. 318func (m *metricMap) Reset() { 319 m.mtx.Lock() 320 defer m.mtx.Unlock() 321 322 for h := range m.metrics { 323 delete(m.metrics, h) 324 } 325} 326 327// deleteByHashWithLabelValues removes the metric from the hash bucket h. If 328// there are multiple matches in the bucket, use lvs to select a metric and 329// remove only that metric. 330func (m *metricMap) deleteByHashWithLabelValues( 331 h uint64, lvs []string, curry []curriedLabelValue, 332) bool { 333 m.mtx.Lock() 334 defer m.mtx.Unlock() 335 336 metrics, ok := m.metrics[h] 337 if !ok { 338 return false 339 } 340 341 i := findMetricWithLabelValues(metrics, lvs, curry) 342 if i >= len(metrics) { 343 return false 344 } 345 346 if len(metrics) > 1 { 347 old := metrics 348 m.metrics[h] = append(metrics[:i], metrics[i+1:]...) 349 old[len(old)-1] = metricWithLabelValues{} 350 } else { 351 delete(m.metrics, h) 352 } 353 return true 354} 355 356// deleteByHashWithLabels removes the metric from the hash bucket h. If there 357// are multiple matches in the bucket, use lvs to select a metric and remove 358// only that metric. 359func (m *metricMap) deleteByHashWithLabels( 360 h uint64, labels Labels, curry []curriedLabelValue, 361) bool { 362 m.mtx.Lock() 363 defer m.mtx.Unlock() 364 365 metrics, ok := m.metrics[h] 366 if !ok { 367 return false 368 } 369 i := findMetricWithLabels(m.desc, metrics, labels, curry) 370 if i >= len(metrics) { 371 return false 372 } 373 374 if len(metrics) > 1 { 375 old := metrics 376 m.metrics[h] = append(metrics[:i], metrics[i+1:]...) 377 old[len(old)-1] = metricWithLabelValues{} 378 } else { 379 delete(m.metrics, h) 380 } 381 return true 382} 383 384// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value 385// or creates it and returns the new one. 386// 387// This function holds the mutex. 388func (m *metricMap) getOrCreateMetricWithLabelValues( 389 hash uint64, lvs []string, curry []curriedLabelValue, 390) Metric { 391 m.mtx.RLock() 392 metric, ok := m.getMetricWithHashAndLabelValues(hash, lvs, curry) 393 m.mtx.RUnlock() 394 if ok { 395 return metric 396 } 397 398 m.mtx.Lock() 399 defer m.mtx.Unlock() 400 metric, ok = m.getMetricWithHashAndLabelValues(hash, lvs, curry) 401 if !ok { 402 inlinedLVs := inlineLabelValues(lvs, curry) 403 metric = m.newMetric(inlinedLVs...) 404 m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: inlinedLVs, metric: metric}) 405 } 406 return metric 407} 408 409// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value 410// or creates it and returns the new one. 411// 412// This function holds the mutex. 413func (m *metricMap) getOrCreateMetricWithLabels( 414 hash uint64, labels Labels, curry []curriedLabelValue, 415) Metric { 416 m.mtx.RLock() 417 metric, ok := m.getMetricWithHashAndLabels(hash, labels, curry) 418 m.mtx.RUnlock() 419 if ok { 420 return metric 421 } 422 423 m.mtx.Lock() 424 defer m.mtx.Unlock() 425 metric, ok = m.getMetricWithHashAndLabels(hash, labels, curry) 426 if !ok { 427 lvs := extractLabelValues(m.desc, labels, curry) 428 metric = m.newMetric(lvs...) 429 m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: lvs, metric: metric}) 430 } 431 return metric 432} 433 434// getMetricWithHashAndLabelValues gets a metric while handling possible 435// collisions in the hash space. Must be called while holding the read mutex. 436func (m *metricMap) getMetricWithHashAndLabelValues( 437 h uint64, lvs []string, curry []curriedLabelValue, 438) (Metric, bool) { 439 metrics, ok := m.metrics[h] 440 if ok { 441 if i := findMetricWithLabelValues(metrics, lvs, curry); i < len(metrics) { 442 return metrics[i].metric, true 443 } 444 } 445 return nil, false 446} 447 448// getMetricWithHashAndLabels gets a metric while handling possible collisions in 449// the hash space. Must be called while holding read mutex. 450func (m *metricMap) getMetricWithHashAndLabels( 451 h uint64, labels Labels, curry []curriedLabelValue, 452) (Metric, bool) { 453 metrics, ok := m.metrics[h] 454 if ok { 455 if i := findMetricWithLabels(m.desc, metrics, labels, curry); i < len(metrics) { 456 return metrics[i].metric, true 457 } 458 } 459 return nil, false 460} 461 462// findMetricWithLabelValues returns the index of the matching metric or 463// len(metrics) if not found. 464func findMetricWithLabelValues( 465 metrics []metricWithLabelValues, lvs []string, curry []curriedLabelValue, 466) int { 467 for i, metric := range metrics { 468 if matchLabelValues(metric.values, lvs, curry) { 469 return i 470 } 471 } 472 return len(metrics) 473} 474 475// findMetricWithLabels returns the index of the matching metric or len(metrics) 476// if not found. 477func findMetricWithLabels( 478 desc *Desc, metrics []metricWithLabelValues, labels Labels, curry []curriedLabelValue, 479) int { 480 for i, metric := range metrics { 481 if matchLabels(desc, metric.values, labels, curry) { 482 return i 483 } 484 } 485 return len(metrics) 486} 487 488func matchLabelValues(values []string, lvs []string, curry []curriedLabelValue) bool { 489 if len(values) != len(lvs)+len(curry) { 490 return false 491 } 492 var iLVs, iCurry int 493 for i, v := range values { 494 if iCurry < len(curry) && curry[iCurry].index == i { 495 if v != curry[iCurry].value { 496 return false 497 } 498 iCurry++ 499 continue 500 } 501 if v != lvs[iLVs] { 502 return false 503 } 504 iLVs++ 505 } 506 return true 507} 508 509func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool { 510 if len(values) != len(labels)+len(curry) { 511 return false 512 } 513 iCurry := 0 514 for i, k := range desc.variableLabels { 515 if iCurry < len(curry) && curry[iCurry].index == i { 516 if values[i] != curry[iCurry].value { 517 return false 518 } 519 iCurry++ 520 continue 521 } 522 if values[i] != labels[k] { 523 return false 524 } 525 } 526 return true 527} 528 529func extractLabelValues(desc *Desc, labels Labels, curry []curriedLabelValue) []string { 530 labelValues := make([]string, len(labels)+len(curry)) 531 iCurry := 0 532 for i, k := range desc.variableLabels { 533 if iCurry < len(curry) && curry[iCurry].index == i { 534 labelValues[i] = curry[iCurry].value 535 iCurry++ 536 continue 537 } 538 labelValues[i] = labels[k] 539 } 540 return labelValues 541} 542 543func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string { 544 labelValues := make([]string, len(lvs)+len(curry)) 545 var iCurry, iLVs int 546 for i := range labelValues { 547 if iCurry < len(curry) && curry[iCurry].index == i { 548 labelValues[i] = curry[iCurry].value 549 iCurry++ 550 continue 551 } 552 labelValues[i] = lvs[iLVs] 553 iLVs++ 554 } 555 return labelValues 556} 557