vitess.io/vitess@v0.16.2/go/vt/servenv/exporter.go (about) 1 /* 2 Copyright 2020 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package servenv 18 19 import ( 20 "expvar" 21 "net/http" 22 "sync" 23 "time" 24 25 "vitess.io/vitess/go/stats" 26 ) 27 28 // varType is used to specify what type of var to create. 29 type varType int 30 31 const ( 32 typeCounter = varType(iota) 33 typeGauge 34 ) 35 36 // onDup is used to specify how to handle duplicates when creating vars. 37 type onDup int 38 39 const ( 40 replaceOnDup = onDup(iota) 41 reuseOnDup 42 ) 43 44 var ( 45 // exporterMu is for all variables below. 46 exporterMu sync.Mutex 47 48 // exporters contains the full list of exporters. Entries can only be 49 // added. The creation of a new Exporter with a previously existing 50 // name causes that Exporter to be reused. 51 exporters = make(map[string]*Exporter) 52 53 // unnamedExports contain variables that were exported using 54 // an unnamed exporter. If there is a name collision here, we 55 // just reuse the unnamed variable. 56 unnamedExports = make(map[string]expvar.Var) 57 58 // exportedMultiCountVars contains the merged stats vars created for the vars that support Counts. 59 exportedMultiCountVars = make(map[string]*multiCountVars) 60 61 // exportedSingleCountVars contains the merged stats vars created for vars that support Count. 62 exportedSingleCountVars = make(map[string]*singleCountVars) 63 64 // exportedTimingsVars contains all the timings vars. 65 exportedTimingsVars = make(map[string]*stats.MultiTimings) 66 67 // exportedOtherStatsVars contains Rates, Histograms and Publish vars. 68 exportedOtherStatsVars = make(map[string]*expvar.Map) 69 ) 70 71 //----------------------------------------------------------------- 72 73 // Exporter remaps http and stats end-points to distinct namespaces. 74 // 75 // Unnamed exporters are treated as unscoped, and requests are passed 76 // through to the underlying functions. 77 // 78 // For named exporters, http handle requests of the form /path will 79 // be remapped to /name/path. In the case of stats variables, a new 80 // dimension will be added. For example, a Counter of value 1 81 // will be changed to a map {"name": 1}. A multi-counter like 82 // { "a.b": 1, "c.d": 2} will be mapped to {"name.a.b": 1, "name.c.d": 2}. 83 // Stats vars of the same name are merged onto a single map. For example, 84 // if exporters name1 and name2 independently create a stats Counter 85 // named foo and export values 1 and 2, the result is a merged stats var 86 // named foo with the following content: {"name1": 1, "name2": 2}. 87 // 88 // The functions that create the stat vars don't always return 89 // the actual exported variable. Instead they return a variable that 90 // only affects the dimension that was assigned to the exporter. 91 // The exported variables are named evar, and the variables returned 92 // to the caller are named lvar (local var). 93 // 94 // If there are duplicates, "Func" vars will be changed to invoke 95 // the latest callback function. Non-Func vars will be reused. For 96 // counters, the adds will continue to add on top of existing values. 97 // For gauges, this is less material because a new "Set" will overwrite 98 // the previous value. This behavior of reusing counters is necessary 99 // because we build derived variables like Rates, which need to continue 100 // referencing the original variable that was created. 101 type Exporter struct { 102 name, label string 103 handleFuncs map[string]*handleFunc 104 sp *statusPage 105 mu sync.Mutex 106 } 107 108 // NewExporter creates a new Exporter with name as namespace. 109 // label is the name of the additional dimension for the stats vars. 110 func NewExporter(name, label string) *Exporter { 111 if name == "" { 112 return &Exporter{} 113 } 114 115 exporterMu.Lock() 116 defer exporterMu.Unlock() 117 118 if e, ok := exporters[name]; ok { 119 e.resetLocked() 120 return e 121 } 122 e := &Exporter{ 123 name: name, 124 label: label, 125 handleFuncs: make(map[string]*handleFunc), 126 sp: newStatusPage(name), 127 } 128 exporters[name] = e 129 return e 130 } 131 132 func (e *Exporter) resetLocked() { 133 for _, hf := range e.handleFuncs { 134 hf.Set(nil) 135 } 136 e.sp.reset() 137 } 138 139 // Name returns the name of the exporter. 140 func (e *Exporter) Name() string { 141 return e.name 142 } 143 144 // URLPrefix returns the URL prefix for the exporter. 145 func (e *Exporter) URLPrefix() string { 146 // There are two other places where this logic is duplicated: 147 // status.go and go/vt/vtgate/discovery/healthcheck.go. 148 if e.name == "" { 149 return e.name 150 } 151 return "/" + e.name 152 } 153 154 // HandleFunc sets or overwrites the handler for url. If Exporter has a name, 155 // url remapped from /path to /name/path. If name is empty, the request 156 // is passed through to http.HandleFunc. 157 func (e *Exporter) HandleFunc(url string, f func(w http.ResponseWriter, r *http.Request)) { 158 e.mu.Lock() 159 defer e.mu.Unlock() 160 if e.name == "" { 161 http.HandleFunc(url, f) 162 return 163 } 164 165 if hf, ok := e.handleFuncs[url]; ok { 166 hf.Set(f) 167 return 168 } 169 hf := &handleFunc{f: f} 170 e.handleFuncs[url] = hf 171 172 http.HandleFunc(e.URLPrefix()+url, func(w http.ResponseWriter, r *http.Request) { 173 if f := hf.Get(); f != nil { 174 f(w, r) 175 } 176 }) 177 } 178 179 // AddStatusPart adds a status part to the status page. If Exporter has a name, 180 // the part is added to a url named /name/debug/status. Otherwise, it's /debug/status. 181 func (e *Exporter) AddStatusPart(banner, frag string, f func() any) { 182 if e.name == "" { 183 AddStatusPart(banner, frag, f) 184 return 185 } 186 e.sp.addStatusPart(banner, frag, f) 187 } 188 189 // NewCountersFuncWithMultiLabels creates a name-spaced equivalent for stats.NewCountersFuncWithMultiLabels. 190 func (e *Exporter) NewCountersFuncWithMultiLabels(name, help string, labels []string, f func() map[string]int64) *stats.CountersFuncWithMultiLabels { 191 // If e.name is empty, it's a pass-through. 192 // If name is empty, it's an unexported var. 193 if e.name == "" || name == "" { 194 v := stats.NewCountersFuncWithMultiLabels(name, help, labels, f) 195 addUnnamedExport(name, v) 196 return v 197 } 198 lvar := stats.NewCountersFuncWithMultiLabels("", help, labels, f) 199 _ = e.createCountsTracker(name, help, labels, lvar, replaceOnDup, typeCounter) 200 return lvar 201 } 202 203 func (e *Exporter) createCountsTracker(name, help string, labels []string, lvar multiCountVar, ondup onDup, typ varType) multiCountVar { 204 exporterMu.Lock() 205 defer exporterMu.Unlock() 206 207 if c, ok := unnamedExports[name]; ok { 208 if typ == typeCounter { 209 return c.(multiCountVar) 210 } 211 return nil 212 } 213 214 if evar, ok := exportedMultiCountVars[name]; ok { 215 evar.mu.Lock() 216 defer evar.mu.Unlock() 217 218 if ondup == reuseOnDup { 219 if c, ok := evar.vars[e.name]; ok { 220 return c 221 } 222 } 223 evar.vars[e.name] = lvar 224 return nil 225 } 226 evar := &multiCountVars{vars: map[string]multiCountVar{e.name: lvar}} 227 exportedMultiCountVars[name] = evar 228 229 newlabels := combineLabels(e.label, labels) 230 if typ == typeCounter { 231 _ = stats.NewCountersFuncWithMultiLabels(name, help, newlabels, evar.Fetch) 232 } else { 233 _ = stats.NewGaugesFuncWithMultiLabels(name, help, newlabels, evar.Fetch) 234 } 235 return nil 236 } 237 238 // NewGaugesFuncWithMultiLabels creates a name-spaced equivalent for stats.NewGaugesFuncWithMultiLabels. 239 func (e *Exporter) NewGaugesFuncWithMultiLabels(name, help string, labels []string, f func() map[string]int64) *stats.GaugesFuncWithMultiLabels { 240 if e.name == "" || name == "" { 241 v := stats.NewGaugesFuncWithMultiLabels(name, help, labels, f) 242 addUnnamedExport(name, v) 243 return v 244 } 245 lvar := stats.NewGaugesFuncWithMultiLabels("", help, labels, f) 246 _ = e.createCountsTracker(name, help, labels, lvar, replaceOnDup, typeGauge) 247 return lvar 248 } 249 250 // NewCounter creates a name-spaced equivalent for stats.NewCounter. 251 func (e *Exporter) NewCounter(name string, help string) *stats.Counter { 252 if e.name == "" || name == "" { 253 v := stats.NewCounter(name, help) 254 addUnnamedExport(name, v) 255 return v 256 } 257 lvar := stats.NewCounter("", help) 258 if exists := e.createCountTracker(name, help, lvar, reuseOnDup, typeCounter); exists != nil { 259 return exists.(*stats.Counter) 260 } 261 return lvar 262 } 263 264 func (e *Exporter) createCountTracker(name, help string, lvar singleCountVar, ondup onDup, typ varType) singleCountVar { 265 exporterMu.Lock() 266 defer exporterMu.Unlock() 267 268 if c, ok := unnamedExports[name]; ok { 269 if typ == typeCounter { 270 return c.(singleCountVar) 271 } 272 return nil 273 } 274 275 if evar, ok := exportedSingleCountVars[name]; ok { 276 evar.mu.Lock() 277 defer evar.mu.Unlock() 278 279 if ondup == reuseOnDup { 280 c, ok := evar.vars[e.name] 281 if ok { 282 return c 283 } 284 } 285 evar.vars[e.name] = lvar 286 return nil 287 } 288 evar := &singleCountVars{vars: map[string]singleCountVar{e.name: lvar}} 289 exportedSingleCountVars[name] = evar 290 291 if typ == typeCounter { 292 _ = stats.NewCountersFuncWithMultiLabels(name, help, []string{e.label}, evar.Fetch) 293 } else { 294 _ = stats.NewGaugesFuncWithMultiLabels(name, help, []string{e.label}, evar.Fetch) 295 } 296 return nil 297 } 298 299 // NewGauge creates a name-spaced equivalent for stats.NewGauge. 300 func (e *Exporter) NewGauge(name string, help string) *stats.Gauge { 301 if e.name == "" || name == "" { 302 v := stats.NewGauge(name, help) 303 addUnnamedExport(name, v) 304 return v 305 } 306 lvar := stats.NewGauge("", help) 307 if exists := e.createCountTracker(name, help, lvar, reuseOnDup, typeCounter); exists != nil { 308 return exists.(*stats.Gauge) 309 } 310 return lvar 311 } 312 313 // NewGaugeFloat64 314 // exporter assumes all counters/gauges are int64 based; I haven't found a good solution for exporting 315 // a float64 gauge yet. (Shlomi) 316 func (e *Exporter) NewGaugeFloat64(name string, help string) *stats.GaugeFloat64 { 317 return nil 318 } 319 320 // NewCounterFunc creates a name-spaced equivalent for stats.NewCounterFunc. 321 func (e *Exporter) NewCounterFunc(name string, help string, f func() int64) *stats.CounterFunc { 322 if e.name == "" || name == "" { 323 v := stats.NewCounterFunc(name, help, f) 324 addUnnamedExport(name, v) 325 return v 326 } 327 lvar := stats.NewCounterFunc("", help, f) 328 _ = e.createCountTracker(name, help, lvar, replaceOnDup, typeCounter) 329 return lvar 330 } 331 332 // NewGaugeFunc creates a name-spaced equivalent for stats.NewGaugeFunc. 333 func (e *Exporter) NewGaugeFunc(name string, help string, f func() int64) *stats.GaugeFunc { 334 if e.name == "" || name == "" { 335 v := stats.NewGaugeFunc(name, help, f) 336 addUnnamedExport(name, v) 337 return v 338 } 339 lvar := stats.NewGaugeFunc("", help, f) 340 _ = e.createCountTracker(name, help, lvar, replaceOnDup, typeGauge) 341 return lvar 342 } 343 344 // NewCounterDurationFunc creates a name-spaced equivalent for stats.NewCounterDurationFunc. 345 func (e *Exporter) NewCounterDurationFunc(name string, help string, f func() time.Duration) *stats.CounterDurationFunc { 346 if e.name == "" || name == "" { 347 v := stats.NewCounterDurationFunc(name, help, f) 348 addUnnamedExport(name, v) 349 return v 350 } 351 lvar := stats.NewCounterDurationFunc("", help, f) 352 _ = e.createCountTracker(name, help, lvar, replaceOnDup, typeCounter) 353 return lvar 354 } 355 356 // NewGaugeDurationFunc creates a name-spaced equivalent for stats.NewGaugeDurationFunc. 357 func (e *Exporter) NewGaugeDurationFunc(name string, help string, f func() time.Duration) *stats.GaugeDurationFunc { 358 if e.name == "" || name == "" { 359 v := stats.NewGaugeDurationFunc(name, help, f) 360 addUnnamedExport(name, v) 361 return v 362 } 363 lvar := stats.NewGaugeDurationFunc("", help, f) 364 _ = e.createCountTracker(name, help, lvar, replaceOnDup, typeGauge) 365 return lvar 366 } 367 368 // NewCountersWithSingleLabel creates a name-spaced equivalent for stats.NewCountersWithSingleLabel. 369 // Tags are ignored if the exporter is named. 370 func (e *Exporter) NewCountersWithSingleLabel(name, help string, label string, tags ...string) *stats.CountersWithSingleLabel { 371 if e.name == "" || name == "" { 372 v := stats.NewCountersWithSingleLabel(name, help, label, tags...) 373 addUnnamedExport(name, v) 374 return v 375 } 376 lvar := stats.NewCountersWithSingleLabel("", help, label) 377 if exists := e.createCountsTracker(name, help, []string{label}, lvar, reuseOnDup, typeCounter); exists != nil { 378 return exists.(*stats.CountersWithSingleLabel) 379 } 380 return lvar 381 } 382 383 // NewGaugesWithSingleLabel creates a name-spaced equivalent for stats.NewGaugesWithSingleLabel. 384 // Tags are ignored if the exporter is named. 385 func (e *Exporter) NewGaugesWithSingleLabel(name, help string, label string, tags ...string) *stats.GaugesWithSingleLabel { 386 if e.name == "" || name == "" { 387 v := stats.NewGaugesWithSingleLabel(name, help, label, tags...) 388 addUnnamedExport(name, v) 389 return v 390 } 391 392 lvar := stats.NewGaugesWithSingleLabel("", help, label) 393 if exists := e.createCountsTracker(name, help, []string{label}, lvar, reuseOnDup, typeGauge); exists != nil { 394 return exists.(*stats.GaugesWithSingleLabel) 395 } 396 return lvar 397 } 398 399 // NewCountersWithMultiLabels creates a name-spaced equivalent for stats.NewCountersWithMultiLabels. 400 func (e *Exporter) NewCountersWithMultiLabels(name, help string, labels []string) *stats.CountersWithMultiLabels { 401 if e.name == "" || name == "" { 402 v := stats.NewCountersWithMultiLabels(name, help, labels) 403 addUnnamedExport(name, v) 404 return v 405 } 406 407 lvar := stats.NewCountersWithMultiLabels("", help, labels) 408 if exists := e.createCountsTracker(name, help, labels, lvar, reuseOnDup, typeCounter); exists != nil { 409 return exists.(*stats.CountersWithMultiLabels) 410 } 411 return lvar 412 } 413 414 // NewGaugesWithMultiLabels creates a name-spaced equivalent for stats.NewGaugesWithMultiLabels. 415 func (e *Exporter) NewGaugesWithMultiLabels(name, help string, labels []string) *stats.GaugesWithMultiLabels { 416 if e.name == "" || name == "" { 417 v := stats.NewGaugesWithMultiLabels(name, help, labels) 418 addUnnamedExport(name, v) 419 return v 420 } 421 422 lvar := stats.NewGaugesWithMultiLabels("", help, labels) 423 if exists := e.createCountsTracker(name, help, labels, lvar, reuseOnDup, typeGauge); exists != nil { 424 return exists.(*stats.GaugesWithMultiLabels) 425 } 426 return lvar 427 } 428 429 // NewTimings creates a name-spaced equivalent for stats.NewTimings. 430 // The function currently just returns an unexported variable. 431 func (e *Exporter) NewTimings(name string, help string, label string) *TimingsWrapper { 432 if e.name == "" || name == "" { 433 v := &TimingsWrapper{ 434 timings: stats.NewMultiTimings(name, help, []string{label}), 435 } 436 addUnnamedExport(name, v.timings) 437 return v 438 } 439 440 exporterMu.Lock() 441 defer exporterMu.Unlock() 442 443 if v, ok := unnamedExports[name]; ok { 444 return &TimingsWrapper{ 445 timings: v.(*stats.MultiTimings), 446 } 447 } 448 449 if tv, ok := exportedTimingsVars[name]; ok { 450 return &TimingsWrapper{ 451 name: e.name, 452 timings: tv, 453 } 454 } 455 mt := stats.NewMultiTimings(name, help, []string{e.label, label}) 456 exportedTimingsVars[name] = mt 457 return &TimingsWrapper{ 458 name: e.name, 459 timings: mt, 460 } 461 } 462 463 // NewMultiTimings creates a name-spaced equivalent for stats.NewMultiTimings. 464 // The function currently just returns an unexported variable. 465 func (e *Exporter) NewMultiTimings(name string, help string, labels []string) *MultiTimingsWrapper { 466 if e.name == "" || name == "" { 467 v := &MultiTimingsWrapper{ 468 timings: stats.NewMultiTimings(name, help, labels), 469 } 470 addUnnamedExport(name, v.timings) 471 return v 472 } 473 474 exporterMu.Lock() 475 defer exporterMu.Unlock() 476 477 if v, ok := unnamedExports[name]; ok { 478 return &MultiTimingsWrapper{ 479 timings: v.(*stats.MultiTimings), 480 } 481 } 482 483 if tv, ok := exportedTimingsVars[name]; ok { 484 return &MultiTimingsWrapper{ 485 name: e.name, 486 timings: tv, 487 } 488 } 489 mt := stats.NewMultiTimings(name, help, combineLabels(e.label, labels)) 490 exportedTimingsVars[name] = mt 491 return &MultiTimingsWrapper{ 492 name: e.name, 493 timings: mt, 494 } 495 } 496 497 // NewRates creates a name-spaced equivalent for stats.NewRates. 498 // The function currently just returns an unexported variable. 499 func (e *Exporter) NewRates(name string, singleCountVar multiCountVar, samples int, interval time.Duration) *stats.Rates { 500 if e.name == "" || name == "" { 501 v := stats.NewRates(name, singleCountVar, samples, interval) 502 addUnnamedExport(name, v) 503 return v 504 } 505 506 exporterMu.Lock() 507 defer exporterMu.Unlock() 508 509 if v, ok := unnamedExports[name]; ok { 510 return v.(*stats.Rates) 511 } 512 513 ov, ok := exportedOtherStatsVars[name] 514 if !ok { 515 ov = expvar.NewMap(name) 516 exportedOtherStatsVars[name] = ov 517 } 518 if lvar := ov.Get(e.name); lvar != nil { 519 return lvar.(*stats.Rates) 520 } 521 522 rates := stats.NewRates("", singleCountVar, samples, interval) 523 ov.Set(e.name, rates) 524 return rates 525 } 526 527 // NewHistogram creates a name-spaced equivalent for stats.NewHistogram. 528 // The function currently just returns an unexported variable. 529 func (e *Exporter) NewHistogram(name, help string, cutoffs []int64) *stats.Histogram { 530 if e.name == "" || name == "" { 531 v := stats.NewHistogram(name, help, cutoffs) 532 addUnnamedExport(name, v) 533 return v 534 } 535 hist := stats.NewHistogram("", help, cutoffs) 536 e.addToOtherVars(name, hist) 537 return hist 538 } 539 540 // Publish creates a name-spaced equivalent for stats.Publish. 541 // The function just passes through if the Exporter name is empty. 542 func (e *Exporter) Publish(name string, v expvar.Var) { 543 if e.name == "" || name == "" { 544 addUnnamedExport(name, v) 545 stats.Publish(name, v) 546 return 547 } 548 e.addToOtherVars(name, v) 549 } 550 551 func (e *Exporter) addToOtherVars(name string, v expvar.Var) { 552 exporterMu.Lock() 553 defer exporterMu.Unlock() 554 555 if _, ok := unnamedExports[name]; ok { 556 return 557 } 558 559 ov, ok := exportedOtherStatsVars[name] 560 if !ok { 561 ov = expvar.NewMap(name) 562 exportedOtherStatsVars[name] = ov 563 } 564 ov.Set(e.name, v) 565 } 566 567 //----------------------------------------------------------------- 568 569 // singleCountVar is any stats that support Get. 570 type singleCountVar interface { 571 Get() int64 572 } 573 574 // singleCountVars contains all stats that support Get, like *stats.Counter. 575 type singleCountVars struct { 576 mu sync.Mutex 577 vars map[string]singleCountVar 578 } 579 580 // Fetch returns the consolidated stats value for all exporters, like stats.CountersWithSingleLabel. 581 func (evar *singleCountVars) Fetch() map[string]int64 { 582 result := make(map[string]int64) 583 evar.mu.Lock() 584 defer evar.mu.Unlock() 585 for k, c := range evar.vars { 586 result[k] = c.Get() 587 } 588 return result 589 } 590 591 //----------------------------------------------------------------- 592 593 // multiCountVar is any stats that support Counts. 594 type multiCountVar interface { 595 Counts() map[string]int64 596 } 597 598 // multiCountVars contains all stats that support Counts. 599 type multiCountVars struct { 600 mu sync.Mutex 601 vars map[string]multiCountVar 602 } 603 604 // Fetch returns the consolidated stats value for all exporters. 605 func (evar *multiCountVars) Fetch() map[string]int64 { 606 result := make(map[string]int64) 607 evar.mu.Lock() 608 defer evar.mu.Unlock() 609 for k, c := range evar.vars { 610 for innerk, innerv := range c.Counts() { 611 result[k+"."+innerk] = innerv 612 } 613 } 614 return result 615 } 616 617 //----------------------------------------------------------------- 618 619 // TimingsWrapper provides a namespaced version of stats.Timings. 620 type TimingsWrapper struct { 621 name string 622 timings *stats.MultiTimings 623 } 624 625 // Add behaves like Timings.Add. 626 func (tw *TimingsWrapper) Add(name string, elapsed time.Duration) { 627 if tw.name == "" { 628 tw.timings.Add([]string{name}, elapsed) 629 return 630 } 631 tw.timings.Add([]string{tw.name, name}, elapsed) 632 } 633 634 // Record behaves like Timings.Record. 635 func (tw *TimingsWrapper) Record(name string, startTime time.Time) { 636 if tw.name == "" { 637 tw.timings.Record([]string{name}, startTime) 638 return 639 } 640 tw.timings.Record([]string{tw.name, name}, startTime) 641 } 642 643 // Counts behaves like Timings.Counts. 644 func (tw *TimingsWrapper) Counts() map[string]int64 { 645 return tw.timings.Counts() 646 } 647 648 // Reset will clear histograms: used during testing 649 func (tw *TimingsWrapper) Reset() { 650 tw.timings.Reset() 651 } 652 653 //----------------------------------------------------------------- 654 655 // MultiTimingsWrapper provides a namespaced version of stats.MultiTimings. 656 type MultiTimingsWrapper struct { 657 name string 658 timings *stats.MultiTimings 659 } 660 661 // Add behaves like MultiTimings.Add. 662 func (tw *MultiTimingsWrapper) Add(names []string, elapsed time.Duration) { 663 if tw.name == "" { 664 tw.timings.Add(names, elapsed) 665 return 666 } 667 newlabels := combineLabels(tw.name, names) 668 tw.timings.Add(newlabels, elapsed) 669 } 670 671 // Record behaves like MultiTimings.Record. 672 func (tw *MultiTimingsWrapper) Record(names []string, startTime time.Time) { 673 if tw.name == "" { 674 tw.timings.Record(names, startTime) 675 return 676 } 677 newlabels := combineLabels(tw.name, names) 678 tw.timings.Record(newlabels, startTime) 679 } 680 681 // Counts behaves lie MultiTimings.Counts. 682 func (tw *MultiTimingsWrapper) Counts() map[string]int64 { 683 return tw.timings.Counts() 684 } 685 686 // Reset will clear histograms: used during testing 687 func (tw *MultiTimingsWrapper) Reset() { 688 tw.timings.Reset() 689 } 690 691 //----------------------------------------------------------------- 692 693 // handleFunc stores the http Handler for an Exporter. This function can 694 // be replaced as needed. 695 type handleFunc struct { 696 mu sync.Mutex 697 f func(w http.ResponseWriter, r *http.Request) 698 } 699 700 // Set replaces the existing handler with a new one. 701 func (hf *handleFunc) Set(f func(w http.ResponseWriter, r *http.Request)) { 702 hf.mu.Lock() 703 defer hf.mu.Unlock() 704 hf.f = f 705 } 706 707 // Get returns the current handler. 708 func (hf *handleFunc) Get() func(w http.ResponseWriter, r *http.Request) { 709 hf.mu.Lock() 710 defer hf.mu.Unlock() 711 return hf.f 712 } 713 714 //----------------------------------------------------------------- 715 716 func addUnnamedExport(name string, v expvar.Var) { 717 if name == "" { 718 return 719 } 720 exporterMu.Lock() 721 unnamedExports[name] = v 722 exporterMu.Unlock() 723 } 724 725 func combineLabels(label string, labels []string) []string { 726 return append(append(make([]string, 0, len(labels)+1), label), labels...) 727 }