vitess.io/vitess@v0.16.2/go/stats/statsd/statsd.go (about)

     1  package statsd
     2  
     3  import (
     4  	"encoding/json"
     5  	"expvar"
     6  	"fmt"
     7  	"hash/crc32"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/DataDog/datadog-go/statsd"
    12  	"github.com/spf13/pflag"
    13  
    14  	"vitess.io/vitess/go/stats"
    15  	"vitess.io/vitess/go/vt/log"
    16  	"vitess.io/vitess/go/vt/servenv"
    17  )
    18  
    19  var (
    20  	statsdAddress    string
    21  	statsdSampleRate = 1.0
    22  )
    23  
    24  func registerFlags(fs *pflag.FlagSet) {
    25  	fs.StringVar(&statsdAddress, "statsd_address", statsdAddress, "Address for statsd client")
    26  	fs.Float64Var(&statsdSampleRate, "statsd_sample_rate", statsdSampleRate, "Sample rate for statsd metrics")
    27  }
    28  
    29  func init() {
    30  	servenv.OnParseFor("vtgate", registerFlags)
    31  	servenv.OnParseFor("vttablet", registerFlags)
    32  }
    33  
    34  // StatsBackend implements PullBackend using statsd
    35  type StatsBackend struct {
    36  	namespace    string
    37  	statsdClient *statsd.Client
    38  	sampleRate   float64
    39  }
    40  
    41  var (
    42  	sb              StatsBackend
    43  	buildGitRecOnce sync.Once
    44  )
    45  
    46  // makeLabel builds a tag list with a single label + value.
    47  func makeLabel(labelName string, labelVal string) []string {
    48  	return []string{fmt.Sprintf("%s:%s", labelName, labelVal)}
    49  }
    50  
    51  // makeLabels takes the vitess stat representation of label values ("."-separated list) and breaks it
    52  // apart into a map of label name -> label value.
    53  func makeLabels(labelNames []string, labelValsCombined string) []string {
    54  	tags := make([]string, len(labelNames))
    55  	labelVals := strings.Split(labelValsCombined, ".")
    56  	for i, v := range labelVals {
    57  		tags[i] = fmt.Sprintf("%s:%s", labelNames[i], v)
    58  	}
    59  	return tags
    60  }
    61  
    62  func makeCommonTags(tags map[string]string) []string {
    63  	var commonTags []string
    64  	for k, v := range tags {
    65  		commonTag := fmt.Sprintf("%s:%s", k, v)
    66  		commonTags = append(commonTags, commonTag)
    67  	}
    68  	return commonTags
    69  }
    70  
    71  // Init initializes the statsd with the given namespace.
    72  func Init(namespace string) {
    73  	servenv.OnRun(func() {
    74  		InitWithoutServenv(namespace)
    75  	})
    76  }
    77  
    78  // InitWithoutServenv initializes the statsd using the namespace but without servenv
    79  func InitWithoutServenv(namespace string) {
    80  	if statsdAddress == "" {
    81  		log.Info("statsdAddress is empty")
    82  		return
    83  	}
    84  	statsdC, err := statsd.NewBuffered(statsdAddress, 100)
    85  	if err != nil {
    86  		log.Errorf("Failed to create statsd client %v", err)
    87  		return
    88  	}
    89  	statsdC.Namespace = namespace + "."
    90  	if tags := stats.ParseCommonTags(stats.CommonTags); len(tags) > 0 {
    91  		statsdC.Tags = makeCommonTags(tags)
    92  	}
    93  	sb.namespace = namespace
    94  	sb.statsdClient = statsdC
    95  	sb.sampleRate = statsdSampleRate
    96  	stats.RegisterPushBackend("statsd", sb)
    97  	stats.RegisterTimerHook(func(statsName, name string, value int64, timings *stats.Timings) {
    98  		tags := makeLabels(strings.Split(timings.Label(), "."), name)
    99  		if err := statsdC.TimeInMilliseconds(statsName, float64(value), tags, sb.sampleRate); err != nil {
   100  			log.Errorf("Fail to TimeInMilliseconds %v: %v", statsName, err)
   101  		}
   102  	})
   103  	stats.RegisterHistogramHook(func(statsName string, val int64) {
   104  		if err := statsdC.Histogram(statsName, float64(val), []string{}, sb.sampleRate); err != nil {
   105  			log.Errorf("Fail to Histogram for %v: %v", statsName, err)
   106  		}
   107  	})
   108  }
   109  
   110  func (sb StatsBackend) addExpVar(kv expvar.KeyValue) {
   111  	k := kv.Key
   112  	switch v := kv.Value.(type) {
   113  	case *stats.Counter:
   114  		if err := sb.statsdClient.Count(k, v.Get(), nil, sb.sampleRate); err != nil {
   115  			log.Errorf("Failed to add Counter %v for key %v", v, k)
   116  		}
   117  	case *stats.Gauge:
   118  		if err := sb.statsdClient.Gauge(k, float64(v.Get()), nil, sb.sampleRate); err != nil {
   119  			log.Errorf("Failed to add Gauge %v for key %v", v, k)
   120  		}
   121  	case *stats.GaugeFloat64:
   122  		if err := sb.statsdClient.Gauge(k, v.Get(), nil, sb.sampleRate); err != nil {
   123  			log.Errorf("Failed to add GaugeFloat64 %v for key %v", v, k)
   124  		}
   125  	case *stats.GaugeFunc:
   126  		if err := sb.statsdClient.Gauge(k, float64(v.F()), nil, sb.sampleRate); err != nil {
   127  			log.Errorf("Failed to add GaugeFunc %v for key %v", v, k)
   128  		}
   129  	case *stats.CounterFunc:
   130  		if err := sb.statsdClient.Gauge(k, float64(v.F()), nil, sb.sampleRate); err != nil {
   131  			log.Errorf("Failed to add CounterFunc %v for key %v", v, k)
   132  		}
   133  	case *stats.CounterDuration:
   134  		if err := sb.statsdClient.TimeInMilliseconds(k, float64(v.Get().Milliseconds()), nil, sb.sampleRate); err != nil {
   135  			log.Errorf("Failed to add CounterDuration %v for key %v", v, k)
   136  		}
   137  	case *stats.CounterDurationFunc:
   138  		if err := sb.statsdClient.TimeInMilliseconds(k, float64(v.F().Milliseconds()), nil, sb.sampleRate); err != nil {
   139  			log.Errorf("Failed to add CounterDuration %v for key %v", v, k)
   140  		}
   141  	case *stats.GaugeDuration:
   142  		if err := sb.statsdClient.TimeInMilliseconds(k, float64(v.Get().Milliseconds()), nil, sb.sampleRate); err != nil {
   143  			log.Errorf("Failed to add GaugeDuration %v for key %v", v, k)
   144  		}
   145  	case *stats.GaugeDurationFunc:
   146  		if err := sb.statsdClient.TimeInMilliseconds(k, float64(v.F().Milliseconds()), nil, sb.sampleRate); err != nil {
   147  			log.Errorf("Failed to add GaugeDuration %v for key %v", v, k)
   148  		}
   149  	case *stats.CountersWithSingleLabel:
   150  		for labelVal, val := range v.Counts() {
   151  			if err := sb.statsdClient.Count(k, val, makeLabel(v.Label(), labelVal), sb.sampleRate); err != nil {
   152  				log.Errorf("Failed to add CountersWithSingleLabel %v for key %v", v, k)
   153  			}
   154  		}
   155  	case *stats.CountersWithMultiLabels:
   156  		for labelVals, val := range v.Counts() {
   157  			if err := sb.statsdClient.Count(k, val, makeLabels(v.Labels(), labelVals), sb.sampleRate); err != nil {
   158  				log.Errorf("Failed to add CountersFuncWithMultiLabels %v for key %v", v, k)
   159  			}
   160  		}
   161  	case *stats.CountersFuncWithMultiLabels:
   162  		for labelVals, val := range v.Counts() {
   163  			if err := sb.statsdClient.Count(k, val, makeLabels(v.Labels(), labelVals), sb.sampleRate); err != nil {
   164  				log.Errorf("Failed to add CountersFuncWithMultiLabels %v for key %v", v, k)
   165  			}
   166  		}
   167  	case *stats.GaugesWithMultiLabels:
   168  		for labelVals, val := range v.Counts() {
   169  			if err := sb.statsdClient.Gauge(k, float64(val), makeLabels(v.Labels(), labelVals), sb.sampleRate); err != nil {
   170  				log.Errorf("Failed to add GaugesWithMultiLabels %v for key %v", v, k)
   171  			}
   172  		}
   173  	case *stats.GaugesFuncWithMultiLabels:
   174  		for labelVals, val := range v.Counts() {
   175  			if err := sb.statsdClient.Gauge(k, float64(val), makeLabels(v.Labels(), labelVals), sb.sampleRate); err != nil {
   176  				log.Errorf("Failed to add GaugesFuncWithMultiLabels %v for key %v", v, k)
   177  			}
   178  		}
   179  	case *stats.GaugesWithSingleLabel:
   180  		for labelVal, val := range v.Counts() {
   181  			if err := sb.statsdClient.Gauge(k, float64(val), makeLabel(v.Label(), labelVal), sb.sampleRate); err != nil {
   182  				log.Errorf("Failed to add GaugesWithSingleLabel %v for key %v", v, k)
   183  			}
   184  		}
   185  	case *stats.Timings, *stats.MultiTimings, *stats.Histogram:
   186  		// it does not make sense to export static expvar to statsd,
   187  		// instead we rely on hooks to integrate with statsd' timing and histogram api directly
   188  	case expvar.Func:
   189  		// Export memstats as gauge so that we don't need to call extra ReadMemStats
   190  		if k == "memstats" {
   191  			var obj map[string]any
   192  			if err := json.Unmarshal([]byte(v.String()), &obj); err != nil {
   193  				return
   194  			}
   195  			for k, v := range obj {
   196  				memstatsVal, ok := v.(float64)
   197  				if ok {
   198  					memstatsKey := "memstats." + k
   199  					if err := sb.statsdClient.Gauge(memstatsKey, memstatsVal, []string{}, sb.sampleRate); err != nil {
   200  						log.Errorf("Failed to export %v %v", k, v)
   201  					}
   202  				}
   203  			}
   204  		}
   205  	case *stats.String:
   206  		if k == "BuildGitRev" {
   207  			buildGitRecOnce.Do(func() {
   208  				checksum := crc32.ChecksumIEEE([]byte(v.Get()))
   209  				if err := sb.statsdClient.Gauge(k, float64(checksum), []string{}, sb.sampleRate); err != nil {
   210  					log.Errorf("Failed to export %v %v", k, v)
   211  				}
   212  			})
   213  		}
   214  	case *stats.Rates, *stats.RatesFunc, *stats.StringFunc, *stats.StringMapFunc,
   215  		stats.StringFunc, stats.StringMapFunc:
   216  		// Silently ignore metrics that does not make sense to be exported to statsd
   217  	default:
   218  		log.Warningf("Silently ignore metrics with key %v [%T]", k, kv.Value)
   219  	}
   220  }
   221  
   222  // PushAll flush out the pending metrics
   223  func (sb StatsBackend) PushAll() error {
   224  	expvar.Do(func(kv expvar.KeyValue) {
   225  		sb.addExpVar(kv)
   226  	})
   227  	if err := sb.statsdClient.Flush(); err != nil {
   228  		return err
   229  	}
   230  	return nil
   231  }