github.com/amazechain/amc@v0.1.3/internal/metrics/prometheus/set.go (about) 1 package prometheus 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/prometheus/client_golang/prometheus" 11 ) 12 13 type namedMetric struct { 14 name string 15 metric prometheus.Metric 16 isAux bool 17 } 18 19 // Set is a set of metrics. 20 // 21 // Metrics belonging to a set are exported separately from global metrics. 22 // 23 // Set.WritePrometheus must be called for exporting metrics from the set. 24 type Set struct { 25 mu sync.Mutex 26 a []*namedMetric 27 m map[string]*namedMetric 28 } 29 30 var defaultSet = NewSet() 31 32 // NewSet creates new set of metrics. 33 // 34 // Pass the set to RegisterSet() function in order to export its metrics via global WritePrometheus() call. 35 func NewSet() *Set { 36 return &Set{ 37 m: make(map[string]*namedMetric), 38 } 39 } 40 41 func (s *Set) Describe(ch chan<- *prometheus.Desc) { 42 lessFunc := func(i, j int) bool { 43 return s.a[i].name < s.a[j].name 44 } 45 s.mu.Lock() 46 if !sort.SliceIsSorted(s.a, lessFunc) { 47 sort.Slice(s.a, lessFunc) 48 } 49 sa := append([]*namedMetric(nil), s.a...) 50 s.mu.Unlock() 51 for _, nm := range sa { 52 ch <- nm.metric.Desc() 53 } 54 } 55 56 func (s *Set) Collect(ch chan<- prometheus.Metric) { 57 lessFunc := func(i, j int) bool { 58 return s.a[i].name < s.a[j].name 59 } 60 s.mu.Lock() 61 if !sort.SliceIsSorted(s.a, lessFunc) { 62 sort.Slice(s.a, lessFunc) 63 } 64 sa := append([]*namedMetric(nil), s.a...) 65 s.mu.Unlock() 66 for _, nm := range sa { 67 ch <- nm.metric 68 } 69 } 70 71 // NewHistogram creates and returns new histogram in s with the given name. 72 // 73 // name must be valid Prometheus-compatible metric with possible labels. 74 // For instance, 75 // 76 // - foo 77 // - foo{bar="baz"} 78 // - foo{bar="baz",aaa="b"} 79 // 80 // The returned histogram is safe to use from concurrent goroutines. 81 func (s *Set) NewHistogram(name string, help ...string) (prometheus.Histogram, error) { 82 h, err := NewHistogram(name, help...) 83 84 if err != nil { 85 return nil, err 86 } 87 88 s.registerMetric(name, h) 89 return h, nil 90 } 91 92 func NewHistogram(name string, help ...string) (prometheus.Histogram, error) { 93 name, labels, err := parseMetric(name) 94 95 if err != nil { 96 return nil, err 97 } 98 99 return prometheus.NewHistogram(prometheus.HistogramOpts{ 100 Name: name, 101 ConstLabels: labels, 102 Help: strings.Join(help, " "), 103 }), nil 104 } 105 106 // GetOrCreateHistogram returns registered histogram in s with the given name 107 // or creates new histogram if s doesn't contain histogram with the given name. 108 // 109 // name must be valid Prometheus-compatible metric with possible labels. 110 // For instance, 111 // 112 // - foo 113 // - foo{bar="baz"} 114 // - foo{bar="baz",aaa="b"} 115 // 116 // The returned histogram is safe to use from concurrent goroutines. 117 // 118 // Performance tip: prefer NewHistogram instead of GetOrCreateHistogram. 119 func (s *Set) GetOrCreateHistogram(name string, help ...string) prometheus.Histogram { 120 s.mu.Lock() 121 nm := s.m[name] 122 s.mu.Unlock() 123 if nm == nil { 124 metric, err := NewHistogram(name, help...) 125 126 if err != nil { 127 panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) 128 } 129 130 nmNew := &namedMetric{ 131 name: name, 132 metric: metric, 133 } 134 135 s.mu.Lock() 136 nm = s.m[name] 137 if nm == nil { 138 nm = nmNew 139 s.m[name] = nm 140 s.a = append(s.a, nm) 141 } 142 s.mu.Unlock() 143 } 144 h, ok := nm.metric.(prometheus.Histogram) 145 if !ok { 146 panic(fmt.Errorf("BUG: metric %q isn't a Histogram. It is %T", name, nm.metric)) 147 } 148 return h 149 } 150 151 // NewCounter registers and returns new counter with the given name in the s. 152 // 153 // name must be valid Prometheus-compatible metric with possible labels. 154 // For instance, 155 // 156 // - foo 157 // - foo{bar="baz"} 158 // - foo{bar="baz",aaa="b"} 159 // 160 // The returned counter is safe to use from concurrent goroutines. 161 func (s *Set) NewCounter(name string, help ...string) (prometheus.Counter, error) { 162 c, err := NewCounter(name, help...) 163 164 if err != nil { 165 return nil, err 166 } 167 168 s.registerMetric(name, c) 169 return c, nil 170 } 171 172 func NewCounter(name string, help ...string) (prometheus.Counter, error) { 173 name, labels, err := parseMetric(name) 174 175 if err != nil { 176 return nil, err 177 } 178 179 return prometheus.NewCounter(prometheus.CounterOpts{ 180 Name: name, 181 Help: strings.Join(help, " "), 182 ConstLabels: labels, 183 }), nil 184 } 185 186 // GetOrCreateCounter returns registered counter in s with the given name 187 // or creates new counter if s doesn't contain counter with the given name. 188 // 189 // name must be valid Prometheus-compatible metric with possible labels. 190 // For instance, 191 // 192 // - foo 193 // - foo{bar="baz"} 194 // - foo{bar="baz",aaa="b"} 195 // 196 // The returned counter is safe to use from concurrent goroutines. 197 // 198 // Performance tip: prefer NewCounter instead of GetOrCreateCounter. 199 func (s *Set) GetOrCreateCounter(name string, help ...string) prometheus.Counter { 200 s.mu.Lock() 201 nm := s.m[name] 202 s.mu.Unlock() 203 if nm == nil { 204 // Slow path - create and register missing counter. 205 metric, err := NewCounter(name, help...) 206 207 if err != nil { 208 panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) 209 } 210 211 nmNew := &namedMetric{ 212 name: name, 213 metric: metric, 214 } 215 s.mu.Lock() 216 nm = s.m[name] 217 if nm == nil { 218 nm = nmNew 219 s.m[name] = nm 220 s.a = append(s.a, nm) 221 } 222 s.mu.Unlock() 223 } 224 c, ok := nm.metric.(prometheus.Counter) 225 if !ok { 226 panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric)) 227 } 228 return c 229 } 230 231 // NewGauge registers and returns gauge with the given name in s, which calls f 232 // to obtain gauge value. 233 // 234 // name must be valid Prometheus-compatible metric with possible labels. 235 // For instance, 236 // 237 // - foo 238 // - foo{bar="baz"} 239 // - foo{bar="baz",aaa="b"} 240 // 241 // f must be safe for concurrent calls. 242 // 243 // The returned gauge is safe to use from concurrent goroutines. 244 func (s *Set) NewGauge(name string, help ...string) (prometheus.Gauge, error) { 245 g, err := NewGauge(name, help...) 246 247 if err != nil { 248 return nil, err 249 } 250 251 s.registerMetric(name, g) 252 return g, nil 253 } 254 255 func NewGauge(name string, help ...string) (prometheus.Gauge, error) { 256 257 name, labels, err := parseMetric(name) 258 259 if err != nil { 260 return nil, err 261 } 262 263 return prometheus.NewGauge(prometheus.GaugeOpts{ 264 Name: name, 265 Help: strings.Join(help, " "), 266 ConstLabels: labels, 267 }), nil 268 } 269 270 // GetOrCreateGaugeFunc returns registered gauge with the given name in s 271 // or creates new gauge if s doesn't contain gauge with the given name. 272 // 273 // name must be valid Prometheus-compatible metric with possible labels. 274 // For instance, 275 // 276 // - foo 277 // - foo{bar="baz"} 278 // - foo{bar="baz",aaa="b"} 279 // 280 // The returned gauge is safe to use from concurrent goroutines. 281 // 282 // Performance tip: prefer NewGauge instead of GetOrCreateGauge. 283 func (s *Set) GetOrCreateGauge(name string, help ...string) prometheus.Gauge { 284 s.mu.Lock() 285 nm := s.m[name] 286 s.mu.Unlock() 287 if nm == nil { 288 // Slow path - create and register missing gauge. 289 metric, err := NewGauge(name, help...) 290 291 if err != nil { 292 panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) 293 } 294 295 nmNew := &namedMetric{ 296 name: name, 297 metric: metric, 298 } 299 s.mu.Lock() 300 nm = s.m[name] 301 if nm == nil { 302 nm = nmNew 303 s.m[name] = nm 304 s.a = append(s.a, nm) 305 } 306 s.mu.Unlock() 307 } 308 g, ok := nm.metric.(prometheus.Gauge) 309 if !ok { 310 panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric)) 311 } 312 return g 313 } 314 315 // NewGaugeFunc registers and returns gauge with the given name in s, which calls f 316 // to obtain gauge value. 317 // 318 // name must be valid Prometheus-compatible metric with possible labels. 319 // For instance, 320 // 321 // - foo 322 // - foo{bar="baz"} 323 // - foo{bar="baz",aaa="b"} 324 // 325 // f must be safe for concurrent calls. 326 // 327 // The returned gauge is safe to use from concurrent goroutines. 328 func (s *Set) NewGaugeFunc(name string, f func() float64, help ...string) (prometheus.GaugeFunc, error) { 329 g, err := NewGaugeFunc(name, f, help...) 330 331 if err != nil { 332 return nil, err 333 } 334 335 s.registerMetric(name, g) 336 return g, nil 337 } 338 339 func NewGaugeFunc(name string, f func() float64, help ...string) (prometheus.GaugeFunc, error) { 340 if f == nil { 341 return nil, fmt.Errorf("BUG: f cannot be nil") 342 } 343 344 name, labels, err := parseMetric(name) 345 346 if err != nil { 347 return nil, err 348 } 349 350 return prometheus.NewGaugeFunc(prometheus.GaugeOpts{ 351 Name: name, 352 Help: strings.Join(help, " "), 353 ConstLabels: labels, 354 }, f), nil 355 } 356 357 // GetOrCreateGaugeFunc returns registered gauge with the given name in s 358 // or creates new gauge if s doesn't contain gauge with the given name. 359 // 360 // name must be valid Prometheus-compatible metric with possible labels. 361 // For instance, 362 // 363 // - foo 364 // - foo{bar="baz"} 365 // - foo{bar="baz",aaa="b"} 366 // 367 // The returned gauge is safe to use from concurrent goroutines. 368 // 369 // Performance tip: prefer NewGauge instead of GetOrCreateGauge. 370 func (s *Set) GetOrCreateGaugeFunc(name string, f func() float64, help ...string) prometheus.GaugeFunc { 371 s.mu.Lock() 372 nm := s.m[name] 373 s.mu.Unlock() 374 if nm == nil { 375 // Slow path - create and register missing gauge. 376 if f == nil { 377 panic(fmt.Errorf("BUG: f cannot be nil")) 378 } 379 380 metric, err := NewGaugeFunc(name, f, help...) 381 382 if err != nil { 383 panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) 384 } 385 386 nmNew := &namedMetric{ 387 name: name, 388 metric: metric, 389 } 390 s.mu.Lock() 391 nm = s.m[name] 392 if nm == nil { 393 nm = nmNew 394 s.m[name] = nm 395 s.a = append(s.a, nm) 396 } 397 s.mu.Unlock() 398 } 399 g, ok := nm.metric.(prometheus.GaugeFunc) 400 if !ok { 401 panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric)) 402 } 403 return g 404 } 405 406 const defaultSummaryWindow = 5 * time.Minute 407 408 var defaultSummaryQuantiles = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.97: 0.003, 0.99: 0.001} 409 410 // NewSummary creates and returns new summary with the given name in s. 411 // 412 // name must be valid Prometheus-compatible metric with possible labels. 413 // For instance, 414 // 415 // - foo 416 // - foo{bar="baz"} 417 // - foo{bar="baz",aaa="b"} 418 // 419 // The returned summary is safe to use from concurrent goroutines. 420 func (s *Set) NewSummary(name string, help ...string) (prometheus.Summary, error) { 421 sm, err := NewSummary(name, defaultSummaryWindow, defaultSummaryQuantiles, help...) 422 423 if err != nil { 424 return nil, err 425 } 426 s.mu.Lock() 427 // defer will unlock in case of panic 428 // checks in tests 429 defer s.mu.Unlock() 430 431 s.registerMetric(name, sm) 432 return sm, nil 433 } 434 435 // NewSummary creates and returns new summary in s with the given name, 436 // window and quantiles. 437 // 438 // name must be valid Prometheus-compatible metric with possible labels. 439 // For instance, 440 // 441 // - foo 442 // - foo{bar="baz"} 443 // - foo{bar="baz",aaa="b"} 444 // 445 // The returned summary is safe to use from concurrent goroutines. 446 func NewSummary(name string, window time.Duration, quantiles map[float64]float64, help ...string) (prometheus.Summary, error) { 447 name, labels, err := parseMetric(name) 448 449 if err != nil { 450 return nil, err 451 } 452 453 return prometheus.NewSummary(prometheus.SummaryOpts{ 454 Name: name, 455 ConstLabels: labels, 456 Objectives: quantiles, 457 MaxAge: window, 458 }), nil 459 } 460 461 // GetOrCreateSummary returns registered summary with the given name in s 462 // or creates new summary if s doesn't contain summary with the given name. 463 // 464 // name must be valid Prometheus-compatible metric with possible labels. 465 // For instance, 466 // 467 // - foo 468 // - foo{bar="baz"} 469 // - foo{bar="baz",aaa="b"} 470 // 471 // The returned summary is safe to use from concurrent goroutines. 472 // 473 // Performance tip: prefer NewSummary instead of GetOrCreateSummary. 474 func (s *Set) GetOrCreateSummary(name string, help ...string) prometheus.Summary { 475 return s.GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles, help...) 476 } 477 478 // GetOrCreateSummaryExt returns registered summary with the given name, 479 // window and quantiles in s or creates new summary if s doesn't 480 // contain summary with the given name. 481 // 482 // name must be valid Prometheus-compatible metric with possible labels. 483 // For instance, 484 // 485 // - foo 486 // - foo{bar="baz"} 487 // - foo{bar="baz",aaa="b"} 488 // 489 // The returned summary is safe to use from concurrent goroutines. 490 // 491 // Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt. 492 func (s *Set) GetOrCreateSummaryExt(name string, window time.Duration, quantiles map[float64]float64, help ...string) prometheus.Summary { 493 s.mu.Lock() 494 nm := s.m[name] 495 s.mu.Unlock() 496 if nm == nil { 497 // Slow path - create and register missing summary. 498 metric, err := NewSummary(name, window, quantiles, help...) 499 500 if err != nil { 501 panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) 502 } 503 504 nmNew := &namedMetric{ 505 name: name, 506 metric: metric, 507 } 508 s.mu.Lock() 509 nm = s.m[name] 510 if nm == nil { 511 nm = nmNew 512 s.m[name] = nm 513 s.a = append(s.a, nm) 514 } 515 s.mu.Unlock() 516 } 517 sm, ok := nm.metric.(prometheus.Summary) 518 if !ok { 519 panic(fmt.Errorf("BUG: metric %q isn't a Summary. It is %T", name, nm.metric)) 520 } 521 522 return sm 523 } 524 525 func (s *Set) registerMetric(name string, m prometheus.Metric) { 526 if _, _, err := parseMetric(name); err != nil { 527 panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) 528 } 529 s.mu.Lock() 530 // defer will unlock in case of panic 531 // checks in test 532 defer s.mu.Unlock() 533 s.mustRegisterLocked(name, m) 534 } 535 536 // mustRegisterLocked registers given metric with the given name. 537 // 538 // Panics if the given name was already registered before. 539 func (s *Set) mustRegisterLocked(name string, m prometheus.Metric) { 540 _, ok := s.m[name] 541 if !ok { 542 nm := &namedMetric{ 543 name: name, 544 metric: m, 545 } 546 s.m[name] = nm 547 s.a = append(s.a, nm) 548 } 549 if ok { 550 panic(fmt.Errorf("BUG: metric %q is already registered", name)) 551 } 552 } 553 554 // UnregisterMetric removes metric with the given name from s. 555 // 556 // True is returned if the metric has been removed. 557 // False is returned if the given metric is missing in s. 558 func (s *Set) UnregisterMetric(name string) bool { 559 s.mu.Lock() 560 defer s.mu.Unlock() 561 562 nm, ok := s.m[name] 563 if !ok { 564 return false 565 } 566 return s.unregisterMetricLocked(nm) 567 } 568 569 func (s *Set) unregisterMetricLocked(nm *namedMetric) bool { 570 name := nm.name 571 delete(s.m, name) 572 573 deleteFromList := func(metricName string) { 574 for i, nm := range s.a { 575 if nm.name == metricName { 576 s.a = append(s.a[:i], s.a[i+1:]...) 577 return 578 } 579 } 580 panic(fmt.Errorf("BUG: cannot find metric %q in the list of registered metrics", name)) 581 } 582 583 // remove metric from s.a 584 deleteFromList(name) 585 586 return true 587 } 588 589 // UnregisterAllMetrics de-registers all metrics registered in s. 590 func (s *Set) UnregisterAllMetrics() { 591 metricNames := s.ListMetricNames() 592 for _, name := range metricNames { 593 s.UnregisterMetric(name) 594 } 595 } 596 597 // ListMetricNames returns sorted list of all the metrics in s. 598 func (s *Set) ListMetricNames() []string { 599 s.mu.Lock() 600 defer s.mu.Unlock() 601 metricNames := make([]string, 0, len(s.m)) 602 for _, nm := range s.m { 603 if nm.isAux { 604 continue 605 } 606 metricNames = append(metricNames, nm.name) 607 } 608 sort.Strings(metricNames) 609 return metricNames 610 }