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  }