github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/experimental/metrics/implementation/prometheus/metrics.go (about) 1 // Copyright 2022 Meta Platforms, Inc. and affiliates. 2 // 3 // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 // 5 // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 // 7 // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 // 9 // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 // 11 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 13 // Copyright (c) Facebook, Inc. and its affiliates. 14 // 15 // This source code is licensed under the MIT license found in the 16 // LICENSE file in the root directory of this source tree. 17 18 package prometheus 19 20 import ( 21 "fmt" 22 "sort" 23 "strconv" 24 "strings" 25 "sync" 26 27 "github.com/facebookincubator/go-belt" 28 "github.com/facebookincubator/go-belt/pkg/field" 29 "github.com/facebookincubator/go-belt/tool/experimental/metrics" 30 "github.com/facebookincubator/go-belt/tool/experimental/metrics/types" 31 "github.com/go-ng/xsort" 32 "github.com/prometheus/client_golang/prometheus" 33 ) 34 35 var _ types.Metrics = (*Metrics)(nil) 36 37 // Fields is just a type-alias to field.Fields (for convenience). 38 type Fields = field.Fields 39 40 func mergeSortedStrings(dst []string, add ...string) []string { 41 if len(add) == 0 { 42 return dst 43 } 44 if len(dst) == 0 { 45 return append([]string{}, add...) 46 } 47 48 dstLen := len(dst) 49 50 if !sort.StringsAreSorted(dst) || !sort.StringsAreSorted(add) { 51 panic(fmt.Sprintf("%v %v", sort.StringsAreSorted(dst), sort.StringsAreSorted(add))) 52 } 53 54 i, j := 0, 0 55 for i < dstLen && j < len(add) { 56 switch strings.Compare(dst[i], add[j]) { 57 case -1: 58 i++ 59 case 0: 60 i++ 61 j++ 62 case 1: 63 dst = append(dst, add[j]) 64 j++ 65 } 66 } 67 dst = append(dst, add[j:]...) 68 sort.Strings(dst) 69 return dst 70 } 71 72 func labelsWithPlaceholders(labels prometheus.Labels, placeholders []string) prometheus.Labels { 73 if len(labels) == len(placeholders) { 74 return labels 75 } 76 result := make(prometheus.Labels, len(placeholders)) 77 for k, v := range labels { 78 result[k] = v 79 } 80 for _, s := range placeholders { 81 if _, ok := result[s]; ok { 82 continue 83 } 84 result[s] = "" 85 } 86 return result 87 } 88 89 // CounterVec is a family of Count metrics. 90 type CounterVec struct { 91 *prometheus.CounterVec 92 Key string 93 PossibleLabels []string 94 } 95 96 // AddPossibleLabels adds prometheus labels to the list of expected labels of a Metric of this family. 97 func (v *CounterVec) AddPossibleLabels(newLabels []string) { 98 v.PossibleLabels = mergeSortedStrings(v.PossibleLabels, newLabels...) 99 } 100 101 // GetMetricWith returns a Metric with the values of labels as provided. 102 func (v *CounterVec) GetMetricWith(labels prometheus.Labels) (prometheus.Counter, error) { 103 return v.CounterVec.GetMetricWith(labelsWithPlaceholders(labels, v.PossibleLabels)) 104 } 105 106 // GaugeVec is a family of Gauge metrics. 107 type GaugeVec struct { 108 *prometheus.GaugeVec 109 Key string 110 PossibleLabels []string 111 } 112 113 // AddPossibleLabels adds prometheus labels to the list of expected labels of a Metric of this family. 114 func (v *GaugeVec) AddPossibleLabels(newLabels []string) { 115 v.PossibleLabels = mergeSortedStrings(v.PossibleLabels, newLabels...) 116 } 117 118 // GetMetricWith returns a Metric with the values of labels as provided. 119 func (v *GaugeVec) GetMetricWith(labels prometheus.Labels) (prometheus.Gauge, error) { 120 return v.GaugeVec.GetMetricWith(labelsWithPlaceholders(labels, v.PossibleLabels)) 121 } 122 123 type persistentData struct { 124 storage 125 config 126 } 127 128 type storage struct { 129 locker sync.Mutex 130 registry *prometheus.Registry 131 count map[string]*CounterVec 132 gauge map[string]*GaugeVec 133 intGauge map[string]*GaugeVec 134 135 // temporary buffers (placing here to avoid memory allocations) 136 tmpLabels prometheus.Labels 137 tmpLabelNames []string 138 tmpLabelNamesBuf []string 139 } 140 141 // Metrics implements a wrapper of prometheus metrics to implement 142 // metrics.Metrics. 143 // 144 // Pretty slow and naive implementation. Could be improved by on-need basis. 145 // If you need a faster implementation, then try `tsmetrics`. 146 // 147 // Warning! This implementation does not remove automatically metrics, thus 148 // if a metric was created once, it will be kept in memory forever. 149 // If you need a version of metrics which automatically removes metrics 150 // non-used for a long time, then try `tsmetrics`. 151 // 152 // Warning! Prometheus does not support changing amount of labels for a metric, 153 // therefore we delete and create a metric from scratch if it is required to 154 // extend the set of labels. This procedure leads to a reset of the metric 155 // value. 156 // If you need a version of metrics which does not have such flaw, then 157 // try `tsmetrics` or `simplemetrics`. 158 type Metrics struct { 159 *persistentData 160 labels prometheus.Labels 161 labelNames []string 162 } 163 164 // New returns a new instance of Metrics 165 func New(registry *prometheus.Registry, opts ...Option) *Metrics { 166 m := &Metrics{ 167 persistentData: &persistentData{ 168 storage: storage{ 169 registry: registry, 170 count: map[string]*CounterVec{}, 171 gauge: map[string]*GaugeVec{}, 172 intGauge: map[string]*GaugeVec{}, 173 tmpLabels: make(prometheus.Labels), 174 }, 175 config: options(opts).Config(), 176 }, 177 } 178 return m 179 } 180 181 var ( 182 alternativeDefaultRegistry *prometheus.Registry 183 alternativeDefaultRegistryOnce sync.Once 184 ) 185 186 // Default returns metrics.Metrics with the default configuration. 187 var Default = func() metrics.Metrics { 188 registry, ok := prometheus.DefaultRegisterer.(*prometheus.Registry) 189 if !ok { 190 alternativeDefaultRegistryOnce.Do(func() { 191 alternativeDefaultRegistry = prometheus.NewRegistry() 192 }) 193 registry = alternativeDefaultRegistry 194 } 195 return New(registry) 196 } 197 198 // List returns all the metrics. It could be used for a custom exporter. 199 func (m *Metrics) List() []prometheus.Collector { 200 m.storage.locker.Lock() 201 defer m.storage.locker.Unlock() 202 result := make([]prometheus.Collector, 0, len(m.storage.count)+len(m.storage.gauge)+len(m.storage.intGauge)) 203 204 for _, count := range m.storage.count { 205 result = append(result, count.CounterVec) 206 } 207 208 for _, gauge := range m.storage.gauge { 209 result = append(result, gauge.GaugeVec) 210 } 211 212 for _, intGauge := range m.storage.intGauge { 213 result = append(result, intGauge.GaugeVec) 214 } 215 216 return result 217 } 218 219 // Registerer returns prometheus.Registerer of this Metrics. 220 func (m *Metrics) Registerer() prometheus.Registerer { 221 return m.registry 222 } 223 224 // Gatherer returns prometheus.Gatherer of this Metrics. 225 func (m *Metrics) Gatherer() prometheus.Gatherer { 226 return m.registry 227 } 228 229 func (m *Metrics) getOrCreateCountVec(key string, possibleLabelNames []string) *CounterVec { 230 counterVec := m.count[key] 231 if counterVec != nil { 232 return counterVec 233 } 234 235 counterVec = &CounterVec{ 236 CounterVec: prometheus.NewCounterVec(prometheus.CounterOpts{ 237 Name: key + "_count", 238 }, possibleLabelNames), 239 Key: key, 240 PossibleLabels: possibleLabelNames, 241 } 242 243 m.count[key] = counterVec 244 245 if m.registry != nil { 246 err := m.registry.Register(counterVec) 247 if err != nil { 248 panic(fmt.Sprintf("key: '%v', err: %v", key, err)) 249 } 250 } 251 252 return counterVec 253 } 254 255 func (m *Metrics) deleteCountVec(counterVec *CounterVec) { 256 if m.registry != nil { 257 if !unregister(m.registry, counterVec.CounterVec) { 258 panic(counterVec) 259 } 260 } 261 delete(m.count, counterVec.Key) 262 } 263 264 // Count implements context.Metrics (see the description in the interface). 265 func (m *Metrics) Count(key string) types.Count { 266 return m.CountFields(key, nil) 267 } 268 269 // CountFields returns Count metric given key and additional field values (on top of already defined). 270 func (m *Metrics) CountFields(key string, addFields field.AbstractFields) types.Count { 271 m.storage.locker.Lock() 272 defer m.storage.locker.Unlock() 273 labels, labelNames := m.labelsWithAddFields(addFields) 274 275 counterVec := m.getOrCreateCountVec(key, labelNames) 276 277 counter, err := counterVec.GetMetricWith(labels) 278 if err != nil { 279 m.deleteCountVec(counterVec) 280 counterVec.AddPossibleLabels(labelNames) 281 counterVec = m.getOrCreateCountVec(key, counterVec.PossibleLabels) 282 counter, err = counterVec.GetMetricWith(labels) 283 if err != nil { 284 panic(err) 285 } 286 } 287 288 return &Count{Metrics: m, Key: key, CounterVec: counterVec, Counter: counter} 289 } 290 291 // ForEachCount iterates over each Count metric. Stops on first `false` returned by the callback. 292 func (m *Metrics) ForEachCount(callback func(types.Count) bool) bool { 293 m.storage.locker.Lock() 294 defer m.storage.locker.Unlock() 295 296 shouldExitNow := false 297 for key, family := range m.storage.count { 298 ch := make(chan prometheus.Metric) 299 go func() { 300 family.CounterVec.Collect(ch) 301 close(ch) 302 }() 303 for abstractMetric := range ch { 304 metric := abstractMetric.(prometheus.Counter) 305 if !callback(&Count{Metrics: m, Key: key, CounterVec: family, Counter: metric}) { 306 shouldExitNow = true 307 break 308 } 309 } 310 for range ch { 311 } 312 if shouldExitNow { 313 return false 314 } 315 } 316 return true 317 } 318 319 func (m *Metrics) getOrCreateGaugeVec(key string, possibleLabelNames []string) *GaugeVec { 320 gaugeVec := m.gauge[key] 321 if gaugeVec != nil { 322 return gaugeVec 323 } 324 325 _gaugeVec := prometheus.NewGaugeVec(prometheus.GaugeOpts{ 326 Name: key + "_float", 327 }, possibleLabelNames) 328 329 gaugeVec = &GaugeVec{ 330 GaugeVec: _gaugeVec, 331 Key: key, 332 PossibleLabels: possibleLabelNames, 333 } 334 335 m.gauge[key] = gaugeVec 336 337 if m.registry != nil { 338 err := m.registry.Register(gaugeVec) 339 if err != nil { 340 panic(fmt.Sprintf("key: '%v', err: %v", key, err)) 341 } 342 } 343 344 return gaugeVec 345 } 346 347 func (m *Metrics) deleteGaugeVec(gaugeVec *GaugeVec) { 348 if m.registry != nil { 349 if !unregister(m.registry, gaugeVec.GaugeVec) { 350 panic(gaugeVec) 351 } 352 } 353 delete(m.gauge, gaugeVec.Key) 354 } 355 356 // expects m.storage.locker be Locked 357 func (m *Metrics) labelsWithAddFields(addFields field.AbstractFields) (prometheus.Labels, []string) { 358 if addFields == nil { 359 return m.labels, m.labelNames 360 } 361 362 for k := range m.storage.tmpLabels { 363 delete(m.storage.tmpLabels, k) 364 } 365 for k, v := range m.labels { 366 m.storage.tmpLabels[k] = v 367 } 368 if cap(m.storage.tmpLabelNames) < len(m.labels) { 369 m.storage.tmpLabelNames = make([]string, len(m.labels)+addFields.Len()) 370 } 371 m.storage.tmpLabelNames = m.tmpLabelNames[:len(m.labels)] 372 copy(m.storage.tmpLabelNames, m.labelNames) 373 addFields.ForEachField(func(f *field.Field) bool { 374 if !f.Properties.Has(types.FieldPropInclude) { 375 return true 376 } 377 v := FieldValueToString(f.Value) 378 if _, isSet := m.storage.tmpLabels[f.Key]; !isSet { 379 m.storage.tmpLabelNames = append(m.storage.tmpLabelNames, f.Key) 380 } 381 m.storage.tmpLabels[f.Key] = v 382 return true 383 }) 384 unsortedCount := len(m.storage.tmpLabelNames) - len(m.labels) 385 if cap(m.storage.tmpLabelNamesBuf) < unsortedCount { 386 m.storage.tmpLabelNamesBuf = make([]string, unsortedCount) 387 } 388 m.storage.tmpLabelNamesBuf = m.storage.tmpLabelNamesBuf[:unsortedCount] 389 xsort.AppendedWithBuf(sort.StringSlice(m.storage.tmpLabelNames), m.storage.tmpLabelNamesBuf) 390 return m.storage.tmpLabels, m.storage.tmpLabelNames 391 } 392 393 // Gauge implements context.Metrics (see the description in the interface). 394 func (m *Metrics) Gauge(key string) types.Gauge { 395 return m.GaugeFields(key, nil) 396 } 397 398 // GaugeFields returns Gauge metric given key and additional field values (on top of already defined). 399 func (m *Metrics) GaugeFields(key string, addFields field.AbstractFields) types.Gauge { 400 m.storage.locker.Lock() 401 defer m.storage.locker.Unlock() 402 labels, labelNames := m.labelsWithAddFields(addFields) 403 404 gaugeVec := m.getOrCreateGaugeVec(key, labelNames) 405 406 gauge, err := gaugeVec.GetMetricWith(labels) 407 if err != nil { 408 m.deleteGaugeVec(gaugeVec) 409 gaugeVec.AddPossibleLabels(labelNames) 410 gaugeVec = m.getOrCreateGaugeVec(key, gaugeVec.PossibleLabels) 411 gauge, err = gaugeVec.GetMetricWith(labels) 412 if err != nil { 413 panic(err) 414 } 415 } 416 417 return &Gauge{Metrics: m, Key: key, GaugeVec: gaugeVec, Gauge: gauge} 418 } 419 420 // ForEachGauge iterates over each Gauge metric. Stops on first `false` returned by the callback. 421 func (m *Metrics) ForEachGauge(callback func(types.Gauge) bool) bool { 422 m.storage.locker.Lock() 423 defer m.storage.locker.Unlock() 424 425 shouldExitNow := false 426 for key, family := range m.storage.gauge { 427 ch := make(chan prometheus.Metric) 428 go func() { 429 family.GaugeVec.Collect(ch) 430 close(ch) 431 }() 432 for abstractMetric := range ch { 433 metric := abstractMetric.(prometheus.Gauge) 434 if !callback(&Gauge{Metrics: m, Key: key, GaugeVec: family, Gauge: metric}) { 435 shouldExitNow = true 436 break 437 } 438 } 439 for range ch { 440 } 441 if shouldExitNow { 442 return false 443 } 444 } 445 return true 446 } 447 448 func (m *Metrics) getOrCreateIntGaugeVec(key string, possibleLabelNames []string) *GaugeVec { 449 gaugeVec := m.intGauge[key] 450 if gaugeVec != nil { 451 return gaugeVec 452 } 453 454 _gaugeVec := prometheus.NewGaugeVec(prometheus.GaugeOpts{ 455 Name: key + "_int", 456 }, possibleLabelNames) 457 458 gaugeVec = &GaugeVec{ 459 GaugeVec: _gaugeVec, 460 Key: key, 461 PossibleLabels: possibleLabelNames, 462 } 463 464 m.intGauge[key] = gaugeVec 465 466 if m.registry != nil { 467 err := m.registry.Register(gaugeVec) 468 if err != nil { 469 panic(fmt.Sprintf("key: '%v', err: %v", key, err)) 470 } 471 } 472 473 return gaugeVec 474 } 475 476 func (m *Metrics) deleteIntGaugeVec(intGaugeVec *GaugeVec) { 477 if m.registry != nil { 478 if !unregister(m.registry, intGaugeVec) { 479 panic(intGaugeVec) 480 } 481 } 482 delete(m.intGauge, intGaugeVec.Key) 483 } 484 485 // IntGauge implements context.Metrics (see the description in the interface). 486 func (m *Metrics) IntGauge(key string) types.IntGauge { 487 return m.IntGaugeFields(key, nil) 488 } 489 490 // IntGaugeFields returns IntGauge metric given key and additional field values (on top of already defined). 491 func (m *Metrics) IntGaugeFields(key string, addFields field.AbstractFields) types.IntGauge { 492 m.storage.locker.Lock() 493 defer m.storage.locker.Unlock() 494 labels, labelNames := m.labelsWithAddFields(addFields) 495 496 gaugeVec := m.getOrCreateIntGaugeVec(key, labelNames) 497 498 gauge, err := gaugeVec.GetMetricWith(labels) 499 if err != nil { 500 m.deleteIntGaugeVec(gaugeVec) 501 gaugeVec.AddPossibleLabels(labelNames) 502 gaugeVec = m.getOrCreateIntGaugeVec(key, gaugeVec.PossibleLabels) 503 gauge, err = gaugeVec.GetMetricWith(labels) 504 if err != nil { 505 panic(err) 506 } 507 } 508 509 return &IntGauge{Metrics: m, Key: key, GaugeVec: gaugeVec, Gauge: gauge} 510 } 511 512 // ForEachIntGauge iterates over each IntGauge metric. Stops on first `false` returned by the callback. 513 func (m *Metrics) ForEachIntGauge(callback func(types.IntGauge) bool) bool { 514 m.storage.locker.Lock() 515 defer m.storage.locker.Unlock() 516 517 shouldExitNow := false 518 for key, family := range m.storage.intGauge { 519 ch := make(chan prometheus.Metric) 520 go func() { 521 family.GaugeVec.Collect(ch) 522 close(ch) 523 }() 524 for abstractMetric := range ch { 525 metric := abstractMetric.(prometheus.Gauge) 526 if !callback(&IntGauge{Metrics: m, Key: key, GaugeVec: family, Gauge: metric}) { 527 shouldExitNow = true 528 break 529 } 530 } 531 for range ch { 532 } 533 if shouldExitNow { 534 return false 535 } 536 } 537 return true 538 } 539 540 // WithContextFields implements metrics.Metrics. 541 func (m *Metrics) WithContextFields(allFields *field.FieldsChain, newFieldsCount int) belt.Tool { 542 if m.config.DisableLabels { 543 return m 544 } 545 546 result := &Metrics{ 547 persistentData: m.persistentData, 548 } 549 var ( 550 labels prometheus.Labels 551 names []string 552 ) 553 if newFieldsCount == -1 { 554 result.labels = nil 555 result.labelNames = nil 556 if allFields == nil { 557 return result 558 } 559 labels, names = result.labelsWithAddFields(allFields) 560 } else { 561 labels, names = m.labelsWithAddFields(field.NewSlicer(allFields, 0, uint(newFieldsCount))) 562 } 563 result.labels = make(prometheus.Labels, len(labels)) 564 for k, v := range labels { 565 result.labels[k] = v 566 } 567 result.labelNames = make([]string, 0, len(names)) 568 result.labelNames = append(result.labelNames, names...) 569 return result 570 } 571 572 // WithTraceIDs implements metrics.Metrics. 573 func (m *Metrics) WithTraceIDs(traceIDs belt.TraceIDs, newTraceIDsCount int) belt.Tool { 574 // Should be ignored per metrics.Metrics interface description, so returning 575 // as is: 576 return m 577 } 578 579 // Flush implements metrics.Metrics (or more specifically belt.Tool). 580 func (*Metrics) Flush() {} 581 582 func fieldsToLabels(fields field.AbstractFields) prometheus.Labels { 583 labels := make(prometheus.Labels, fields.Len()) 584 fields.ForEachField(func(f *field.Field) bool { 585 labels[f.Key] = FieldValueToString(f.Value) 586 return true 587 }) 588 return labels 589 } 590 591 const prebakeMax = 65536 592 593 var prebackedString [prebakeMax * 2]string 594 595 func init() { 596 for i := -prebakeMax; i < prebakeMax; i++ { 597 prebackedString[i+prebakeMax] = strconv.FormatInt(int64(i), 10) 598 } 599 } 600 601 func getPrebakedString(v int32) string { 602 if v >= prebakeMax || -v <= -prebakeMax { 603 return "" 604 } 605 return prebackedString[v+prebakeMax] 606 } 607 608 // FieldValueToString converts any value to a string, which could be used 609 // as label value in prometheus. 610 func FieldValueToString(vI interface{}) string { 611 switch v := vI.(type) { 612 case int: 613 r := getPrebakedString(int32(v)) 614 if len(r) != 0 { 615 return r 616 } 617 return strconv.FormatInt(int64(v), 10) 618 case uint64: 619 r := getPrebakedString(int32(v)) 620 if len(r) != 0 { 621 return r 622 } 623 return strconv.FormatUint(v, 10) 624 case int64: 625 r := getPrebakedString(int32(v)) 626 if len(r) != 0 { 627 return r 628 } 629 return strconv.FormatInt(v, 10) 630 case string: 631 return strings.Replace(v, ",", "_", -1) 632 case bool: 633 switch v { 634 case true: 635 return "true" 636 case false: 637 return "false" 638 } 639 case []byte: 640 return string(v) 641 case nil: 642 return "null" 643 case interface{ String() string }: 644 return strings.Replace(v.String(), ",", "_", -1) 645 } 646 647 return "<unknown_type>" 648 }