github.com/XiaoMi/Gaea@v1.2.5/stats/export.go (about)

     1  /*
     2  Copyright 2017 Google Inc.
     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 stats is a wrapper for expvar. It addtionally
    18  // exports new types that can be used to track performance.
    19  // It also provides a callback hook that allows a program
    20  // to export the variables using methods other than /debug/vars.
    21  // All variables support a String function that
    22  // is expected to return a JSON representation
    23  // of the variable.
    24  // Any function named Add will add the specified
    25  // number to the variable.
    26  // Any function named Counts returns a map of counts
    27  // that can be used by Rates to track rates over time.
    28  package stats
    29  
    30  import (
    31  	"bytes"
    32  	"expvar"
    33  	"fmt"
    34  	"strconv"
    35  	"sync"
    36  	"time"
    37  
    38  	"github.com/XiaoMi/Gaea/log"
    39  )
    40  
    41  const defaultEmitPeriod = 60 * time.Second
    42  
    43  // NewVarHook is the type of a hook to export variables in a different way
    44  type NewVarHook func(name string, v expvar.Var)
    45  
    46  type varGroup struct {
    47  	sync.Mutex
    48  	vars       map[string]expvar.Var
    49  	newVarHook NewVarHook
    50  }
    51  
    52  func (vg *varGroup) register(nvh NewVarHook) {
    53  	vg.Lock()
    54  	defer vg.Unlock()
    55  	if vg.newVarHook != nil {
    56  		panic("You've already registered a function")
    57  	}
    58  	if nvh == nil {
    59  		panic("nil not allowed")
    60  	}
    61  	vg.newVarHook = nvh
    62  	// Call hook on existing vars because some might have been
    63  	// created before the call to register
    64  	for k, v := range vg.vars {
    65  		nvh(k, v)
    66  	}
    67  	vg.vars = nil
    68  }
    69  
    70  func (vg *varGroup) publish(name string, v expvar.Var) {
    71  	vg.Lock()
    72  	defer vg.Unlock()
    73  
    74  	expvar.Publish(name, v)
    75  	if vg.newVarHook != nil {
    76  		vg.newVarHook(name, v)
    77  	} else {
    78  		vg.vars[name] = v
    79  	}
    80  }
    81  
    82  var defaultVarGroup = varGroup{vars: make(map[string]expvar.Var)}
    83  
    84  // Register allows you to register a callback function
    85  // that will be called whenever a new stats variable gets
    86  // created. This can be used to build alternate methods
    87  // of exporting stats variables.
    88  func Register(nvh NewVarHook) {
    89  	defaultVarGroup.register(nvh)
    90  }
    91  
    92  // Publish is expvar.Publish+hook
    93  func Publish(name string, v expvar.Var) {
    94  	publish(name, v)
    95  }
    96  
    97  func publish(name string, v expvar.Var) {
    98  	defaultVarGroup.publish(name, v)
    99  }
   100  
   101  // PushBackend is an interface for any stats/metrics backend that requires data
   102  // to be pushed to it. It's used to support push-based metrics backends, as expvar
   103  // by default only supports pull-based ones.
   104  type PushBackend interface {
   105  	// PushAll pushes all stats from expvar to the backend
   106  	PushAll() error
   107  }
   108  
   109  var pushBackends = make(map[string]PushBackend)
   110  var pushBackendsLock sync.Mutex
   111  var once sync.Once
   112  
   113  // GetPushBackend return push backends
   114  func GetPushBackend() map[string]PushBackend {
   115  	return pushBackends
   116  }
   117  
   118  // RegisterPushBackend allows modules to register PushBackend implementations.
   119  // Should be called on init().
   120  func RegisterPushBackend(name string, backend PushBackend, emitPeriod time.Duration) {
   121  	pushBackendsLock.Lock()
   122  	defer pushBackendsLock.Unlock()
   123  	if _, ok := pushBackends[name]; ok {
   124  		log.Fatal(fmt.Sprintf("PushBackend %s already exists; can't register the same name multiple times", name))
   125  	}
   126  	pushBackends[name] = backend
   127  
   128  	if emitPeriod <= 0 {
   129  		log.Warn("[stats] push backend got invalid emitPeriod: %v, use default emitPeriod instead: %v", emitPeriod, defaultEmitPeriod)
   130  		emitPeriod = defaultEmitPeriod
   131  	}
   132  	// Start a single goroutine to emit stats periodically
   133  	once.Do(func() {
   134  		go emitToBackend(name, emitPeriod)
   135  	})
   136  }
   137  
   138  // emitToBackend does a periodic emit to the selected PushBackend. If a push fails,
   139  // it will be logged as a warning (but things will otherwise proceed as normal).
   140  func emitToBackend(name string, emitPeriod time.Duration) {
   141  	ticker := time.NewTicker(emitPeriod)
   142  	defer ticker.Stop()
   143  	for range ticker.C {
   144  		backend, ok := pushBackends[name]
   145  		if !ok {
   146  			log.Fatal(fmt.Sprintf("No PushBackend registered with name %s", name))
   147  			return
   148  		}
   149  		err := backend.PushAll()
   150  		if err != nil {
   151  			// TODO(aaijazi): This might cause log spam...
   152  			log.Warn("Pushing stats to backend %v failed: %v", name, err)
   153  		}
   154  	}
   155  }
   156  
   157  // Float is expvar.Float+Get+hook
   158  type Float struct {
   159  	mu sync.Mutex
   160  	f  float64
   161  }
   162  
   163  // NewFloat creates a new Float and exports it.
   164  func NewFloat(name string) *Float {
   165  	v := new(Float)
   166  	publish(name, v)
   167  	return v
   168  }
   169  
   170  // Add adds the provided value to the Float
   171  func (v *Float) Add(delta float64) {
   172  	v.mu.Lock()
   173  	v.f += delta
   174  	v.mu.Unlock()
   175  }
   176  
   177  // Set sets the value
   178  func (v *Float) Set(value float64) {
   179  	v.mu.Lock()
   180  	v.f = value
   181  	v.mu.Unlock()
   182  }
   183  
   184  // Get returns the value
   185  func (v *Float) Get() float64 {
   186  	v.mu.Lock()
   187  	f := v.f
   188  	v.mu.Unlock()
   189  	return f
   190  }
   191  
   192  // String is the implementation of expvar.var
   193  func (v *Float) String() string {
   194  	return strconv.FormatFloat(v.Get(), 'g', -1, 64)
   195  }
   196  
   197  // FloatFunc converts a function that returns
   198  // a float64 as an expvar.
   199  type FloatFunc func() float64
   200  
   201  // String is the implementation of expvar.var
   202  func (f FloatFunc) String() string {
   203  	return strconv.FormatFloat(f(), 'g', -1, 64)
   204  }
   205  
   206  // String is expvar.String+Get+hook
   207  type String struct {
   208  	mu sync.Mutex
   209  	s  string
   210  }
   211  
   212  // NewString returns a new String
   213  func NewString(name string) *String {
   214  	v := new(String)
   215  	publish(name, v)
   216  	return v
   217  }
   218  
   219  // Set sets the value
   220  func (v *String) Set(value string) {
   221  	v.mu.Lock()
   222  	v.s = value
   223  	v.mu.Unlock()
   224  }
   225  
   226  // Get returns the value
   227  func (v *String) Get() string {
   228  	v.mu.Lock()
   229  	s := v.s
   230  	v.mu.Unlock()
   231  	return s
   232  }
   233  
   234  // String is the implementation of expvar.var
   235  func (v *String) String() string {
   236  	return strconv.Quote(v.Get())
   237  }
   238  
   239  // StringFunc converts a function that returns
   240  // an string as an expvar.
   241  type StringFunc func() string
   242  
   243  // String is the implementation of expvar.var
   244  func (f StringFunc) String() string {
   245  	return strconv.Quote(f())
   246  }
   247  
   248  // JSONFunc is the public type for a single function that returns json directly.
   249  type JSONFunc func() string
   250  
   251  // String is the implementation of expvar.var
   252  func (f JSONFunc) String() string {
   253  	return f()
   254  }
   255  
   256  // PublishJSONFunc publishes any function that returns
   257  // a JSON string as a variable. The string is sent to
   258  // expvar as is.
   259  func PublishJSONFunc(name string, f func() string) {
   260  	publish(name, JSONFunc(f))
   261  }
   262  
   263  // StringMap is a map of string -> string
   264  type StringMap struct {
   265  	mu     sync.Mutex
   266  	values map[string]string
   267  }
   268  
   269  // NewStringMap returns a new StringMap
   270  func NewStringMap(name string) *StringMap {
   271  	v := &StringMap{values: make(map[string]string)}
   272  	publish(name, v)
   273  	return v
   274  }
   275  
   276  // Set will set a value (existing or not)
   277  func (v *StringMap) Set(name, value string) {
   278  	v.mu.Lock()
   279  	v.values[name] = value
   280  	v.mu.Unlock()
   281  }
   282  
   283  // Get will return the value, or "" f not set.
   284  func (v *StringMap) Get(name string) string {
   285  	v.mu.Lock()
   286  	s := v.values[name]
   287  	v.mu.Unlock()
   288  	return s
   289  }
   290  
   291  // String is the implementation of expvar.Var
   292  func (v *StringMap) String() string {
   293  	v.mu.Lock()
   294  	defer v.mu.Unlock()
   295  	return stringMapToString(v.values)
   296  }
   297  
   298  // StringMapFunc is the function equivalent of StringMap
   299  type StringMapFunc func() map[string]string
   300  
   301  // String is used by expvar.
   302  func (f StringMapFunc) String() string {
   303  	m := f()
   304  	if m == nil {
   305  		return "{}"
   306  	}
   307  	return stringMapToString(m)
   308  }
   309  
   310  func stringMapToString(m map[string]string) string {
   311  	b := bytes.NewBuffer(make([]byte, 0, 4096))
   312  	fmt.Fprintf(b, "{")
   313  	firstValue := true
   314  	for k, v := range m {
   315  		if firstValue {
   316  			firstValue = false
   317  		} else {
   318  			fmt.Fprintf(b, ", ")
   319  		}
   320  		fmt.Fprintf(b, "\"%v\": %v", k, strconv.Quote(v))
   321  	}
   322  	fmt.Fprintf(b, "}")
   323  	return b.String()
   324  }