github.com/wfusion/gofusion@v1.1.14/common/infra/metrics/datadog/dogstatsd.go (about)

     1  package datadog
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/DataDog/datadog-go/statsd"
     8  
     9  	"github.com/wfusion/gofusion/common/infra/metrics"
    10  	"github.com/wfusion/gofusion/common/utils"
    11  )
    12  
    13  // DogStatsdSink provides a MetricSink that can be used
    14  // with a dogstatsd server. It utilizes the Dogstatsd client at github.com/DataDog/datadog-go/statsd
    15  type DogStatsdSink struct {
    16  	client            *statsd.Client
    17  	hostName          string
    18  	propagateHostname bool
    19  }
    20  
    21  // NewDogStatsdSink is used to create a new DogStatsdSink with sane defaults
    22  func NewDogStatsdSink(addr string, hostName string) (*DogStatsdSink, error) {
    23  	client, err := statsd.New(addr)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  	sink := &DogStatsdSink{
    28  		client:            client,
    29  		hostName:          hostName,
    30  		propagateHostname: false,
    31  	}
    32  	return sink, nil
    33  }
    34  
    35  // SetTags sets common tags on the Dogstatsd Client that will be sent
    36  // along with all dogstatsd packets.
    37  // Ref: http://docs.datadoghq.com/guides/dogstatsd/#tags
    38  func (s *DogStatsdSink) SetTags(tags []string) {
    39  	s.client.Tags = tags
    40  }
    41  
    42  // EnableHostNamePropagation forces a Dogstatsd `host` tag with the value specified by `s.HostName`
    43  // Since the go-metrics package has its own mechanism for attaching a hostname to metrics,
    44  // setting the `propagateHostname` flag ensures that `s.HostName`
    45  // overrides the host tag naively set by the DogStatsd server
    46  func (s *DogStatsdSink) EnableHostNamePropagation() {
    47  	s.propagateHostname = true
    48  }
    49  
    50  func (s *DogStatsdSink) flattenKey(parts []string) string {
    51  	joined := strings.Join(parts, ".")
    52  	return strings.Map(sanitize, joined)
    53  }
    54  
    55  func sanitize(r rune) rune {
    56  	switch r {
    57  	case ':':
    58  		fallthrough
    59  	case ' ':
    60  		return '_'
    61  	default:
    62  		return r
    63  	}
    64  }
    65  
    66  func (s *DogStatsdSink) parseKey(key []string) ([]string, []metrics.Label) {
    67  	// Since DogStatsd supports dimensionality via tags on metric keys,
    68  	// this sink's approach is to splice the hostname out of the key in favor of a `host` tag
    69  	// The `host` tag is either forced here, or set downstream by the DogStatsd server
    70  
    71  	var labels []metrics.Label
    72  	hostName := s.hostName
    73  
    74  	// Splice the hostname out of the key
    75  	for i, el := range key {
    76  		if el == hostName {
    77  			// We need an intermediate key to prevent clobbering the
    78  			// original backing array that other sinks might be consuming.
    79  			tempKey := append([]string{}, key[:i]...)
    80  			key = append(tempKey, key[i+1:]...)
    81  			break
    82  		}
    83  	}
    84  
    85  	if s.propagateHostname {
    86  		labels = append(labels, metrics.Label{"host", hostName})
    87  	}
    88  	return key, labels
    89  }
    90  
    91  // Implementation of methods in the MetricSink interface
    92  
    93  func (s *DogStatsdSink) SetGauge(key []string, val float32, opts ...utils.OptionExtender) {
    94  	s.SetGaugeWithLabels(key, val, nil)
    95  }
    96  
    97  func (s *DogStatsdSink) SetPrecisionGauge(key []string, val float64, opts ...utils.OptionExtender) {
    98  	s.SetPrecisionGaugeWithLabels(key, val, nil)
    99  }
   100  
   101  func (s *DogStatsdSink) IncrCounter(key []string, val float32, opts ...utils.OptionExtender) {
   102  	s.IncrCounterWithLabels(key, val, nil)
   103  }
   104  
   105  // EmitKey is not implemented since DogStatsd does not provide a metric type that holds an
   106  // arbitrary number of values
   107  func (s *DogStatsdSink) EmitKey(key []string, val float32, opts ...utils.OptionExtender) {
   108  }
   109  
   110  func (s *DogStatsdSink) AddSample(key []string, val float32, opts ...utils.OptionExtender) {
   111  	s.AddSampleWithLabels(key, val, nil)
   112  }
   113  
   114  // The following ...WithLabels methods correspond to Datadog's Tag extension to Statsd.
   115  // http://docs.datadoghq.com/guides/dogstatsd/#tags
   116  func (s *DogStatsdSink) SetGaugeWithLabels(key []string, val float32, labels []metrics.Label,
   117  	opts ...utils.OptionExtender) {
   118  	flatKey, tags := s.getFlattenKeyAndCombinedLabels(key, labels)
   119  	rate := 1.0
   120  	s.client.Gauge(flatKey, float64(val), tags, rate)
   121  }
   122  
   123  // The following ...WithLabels methods correspond to Datadog's Tag extension to Statsd.
   124  // http://docs.datadoghq.com/guides/dogstatsd/#tags
   125  func (s *DogStatsdSink) SetPrecisionGaugeWithLabels(key []string, val float64, labels []metrics.Label,
   126  	opts ...utils.OptionExtender) {
   127  	flatKey, tags := s.getFlattenKeyAndCombinedLabels(key, labels)
   128  	rate := 1.0
   129  	s.client.Gauge(flatKey, val, tags, rate)
   130  }
   131  
   132  func (s *DogStatsdSink) IncrCounterWithLabels(key []string, val float32, labels []metrics.Label,
   133  	opts ...utils.OptionExtender) {
   134  	flatKey, tags := s.getFlattenKeyAndCombinedLabels(key, labels)
   135  	rate := 1.0
   136  	s.client.Count(flatKey, int64(val), tags, rate)
   137  }
   138  
   139  func (s *DogStatsdSink) AddSampleWithLabels(key []string, val float32, labels []metrics.Label,
   140  	opts ...utils.OptionExtender) {
   141  	flatKey, tags := s.getFlattenKeyAndCombinedLabels(key, labels)
   142  	rate := 1.0
   143  	s.client.TimeInMilliseconds(flatKey, float64(val), tags, rate)
   144  }
   145  
   146  // Shutdown disables further metric collection, blocks to flush data, and tears down the sink.
   147  func (s *DogStatsdSink) Shutdown() {
   148  	s.client.Close()
   149  }
   150  
   151  func (s *DogStatsdSink) getFlattenKeyAndCombinedLabels(key []string, labels []metrics.Label) (string, []string) {
   152  	key, parsedLabels := s.parseKey(key)
   153  	flatKey := s.flattenKey(key)
   154  	labels = append(labels, parsedLabels...)
   155  
   156  	var tags []string
   157  	for _, label := range labels {
   158  		label.Name = strings.Map(sanitize, label.Name)
   159  		label.Value = strings.Map(sanitize, label.Value)
   160  		if label.Value != "" {
   161  			tags = append(tags, fmt.Sprintf("%s:%s", label.Name, label.Value))
   162  		} else {
   163  			tags = append(tags, label.Name)
   164  		}
   165  	}
   166  
   167  	return flatKey, tags
   168  }