github.com/rudderlabs/rudder-go-kit@v0.30.0/stats/stats.go (about)

     1  //go:generate mockgen -destination=mock_stats/mock_stats.go -package mock_stats github.com/rudderlabs/rudder-go-kit/stats Stats,Measurement
     2  package stats
     3  
     4  import (
     5  	"context"
     6  	"os"
     7  	"strings"
     8  	"sync/atomic"
     9  	"time"
    10  	"unicode"
    11  
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"go.opentelemetry.io/otel"
    14  	"go.opentelemetry.io/otel/trace/noop"
    15  
    16  	"github.com/rudderlabs/rudder-go-kit/config"
    17  	"github.com/rudderlabs/rudder-go-kit/logger"
    18  	svcMetric "github.com/rudderlabs/rudder-go-kit/stats/metric"
    19  )
    20  
    21  const (
    22  	CountType     = "count"
    23  	TimerType     = "timer"
    24  	GaugeType     = "gauge"
    25  	HistogramType = "histogram"
    26  )
    27  
    28  func init() {
    29  	// TODO once we drop statsd support we can do
    30  	// Default = &otelStats{config: statsConfig{enabled: false}}
    31  	Default = NewStats(config.Default, logger.Default, svcMetric.Instance)
    32  }
    33  
    34  // Default is the default (singleton) Stats instance
    35  var Default Stats
    36  
    37  type GoRoutineFactory interface {
    38  	Go(function func())
    39  }
    40  
    41  // Stats manages stat Measurements
    42  type Stats interface {
    43  	// NewStat creates a new Measurement with provided Name and Type
    44  	NewStat(name, statType string) (m Measurement)
    45  
    46  	// NewTaggedStat creates a new Measurement with provided Name, Type and Tags
    47  	NewTaggedStat(name, statType string, tags Tags) Measurement
    48  
    49  	// NewSampledTaggedStat creates a new Measurement with provided Name, Type and Tags
    50  	// Deprecated: use NewTaggedStat instead
    51  
    52  	NewSampledTaggedStat(name, statType string, tags Tags) Measurement
    53  
    54  	NewTracer(name string) Tracer
    55  
    56  	// Start starts the stats service and the collection of periodic stats.
    57  	Start(ctx context.Context, goFactory GoRoutineFactory) error
    58  
    59  	// Stop stops the service and the collection of periodic stats.
    60  	Stop()
    61  }
    62  
    63  type loggerFactory interface {
    64  	NewLogger() logger.Logger
    65  }
    66  
    67  // NewStats create a new Stats instance using the provided config, logger factory and metric manager as dependencies
    68  func NewStats(
    69  	config *config.Config, loggerFactory loggerFactory, metricManager svcMetric.Manager, opts ...Option,
    70  ) Stats {
    71  	excludedTags := make(map[string]struct{})
    72  	excludedTagsSlice := config.GetStringSlice("statsExcludedTags", nil)
    73  	for _, tag := range excludedTagsSlice {
    74  		excludedTags[tag] = struct{}{}
    75  	}
    76  
    77  	enabled := atomic.Bool{}
    78  	enabled.Store(config.GetBool("enableStats", true))
    79  	statsConfig := statsConfig{
    80  		excludedTags:        excludedTags,
    81  		enabled:             &enabled,
    82  		instanceName:        config.GetString("INSTANCE_ID", ""),
    83  		namespaceIdentifier: os.Getenv("KUBE_NAMESPACE"),
    84  		periodicStatsConfig: periodicStatsConfig{
    85  			enabled:                 config.GetBool("RuntimeStats.enabled", true),
    86  			statsCollectionInterval: config.GetInt64("RuntimeStats.statsCollectionInterval", 10),
    87  			enableCPUStats:          config.GetBool("RuntimeStats.enableCPUStats", true),
    88  			enableMemStats:          config.GetBool("RuntimeStats.enabledMemStats", true),
    89  			enableGCStats:           config.GetBool("RuntimeStats.enableGCStats", true),
    90  			metricManager:           metricManager,
    91  		},
    92  	}
    93  	for _, opt := range opts {
    94  		opt(&statsConfig)
    95  	}
    96  
    97  	if config.GetBool("OpenTelemetry.enabled", false) {
    98  		registerer := prometheus.DefaultRegisterer
    99  		gatherer := prometheus.DefaultGatherer
   100  		if statsConfig.prometheusRegisterer != nil {
   101  			registerer = statsConfig.prometheusRegisterer
   102  		}
   103  		if statsConfig.prometheusGatherer != nil {
   104  			gatherer = statsConfig.prometheusGatherer
   105  		}
   106  		return &otelStats{
   107  			config:                   statsConfig,
   108  			stopBackgroundCollection: func() {},
   109  			meter:                    otel.GetMeterProvider().Meter(defaultMeterName),
   110  			logger:                   loggerFactory.NewLogger().Child("stats"),
   111  			prometheusRegisterer:     registerer,
   112  			prometheusGatherer:       gatherer,
   113  			tracerProvider:           noop.NewTracerProvider(),
   114  			otelConfig: otelStatsConfig{
   115  				tracesEndpoint:           config.GetString("OpenTelemetry.traces.endpoint", ""),
   116  				tracingSamplingRate:      config.GetFloat64("OpenTelemetry.traces.samplingRate", 0.1),
   117  				withTracingSyncer:        config.GetBool("OpenTelemetry.traces.withSyncer", false),
   118  				withZipkin:               config.GetBool("OpenTelemetry.traces.withZipkin", false),
   119  				metricsEndpoint:          config.GetString("OpenTelemetry.metrics.endpoint", ""),
   120  				metricsExportInterval:    config.GetDuration("OpenTelemetry.metrics.exportInterval", 5, time.Second),
   121  				enablePrometheusExporter: config.GetBool("OpenTelemetry.metrics.prometheus.enabled", false),
   122  				prometheusMetricsPort:    config.GetInt("OpenTelemetry.metrics.prometheus.port", 0),
   123  			},
   124  		}
   125  	}
   126  
   127  	backgroundCollectionCtx, backgroundCollectionCancel := context.WithCancel(context.Background())
   128  
   129  	return &statsdStats{
   130  		config:                     statsConfig,
   131  		logger:                     loggerFactory.NewLogger().Child("stats"),
   132  		backgroundCollectionCtx:    backgroundCollectionCtx,
   133  		backgroundCollectionCancel: backgroundCollectionCancel,
   134  		tracer:                     noop.NewTracerProvider().Tracer(""),
   135  		statsdConfig: statsdConfig{
   136  			tagsFormat:          config.GetString("statsTagsFormat", "influxdb"),
   137  			statsdServerURL:     config.GetString("STATSD_SERVER_URL", "localhost:8125"),
   138  			samplingRate:        float32(config.GetFloat64("statsSamplingRate", 1)),
   139  			instanceName:        statsConfig.instanceName,
   140  			namespaceIdentifier: statsConfig.namespaceIdentifier,
   141  		},
   142  		state: &statsdState{
   143  			client:         &statsdClient{},
   144  			clients:        make(map[string]*statsdClient),
   145  			pendingClients: make(map[string]*statsdClient),
   146  		},
   147  	}
   148  }
   149  
   150  var DefaultGoRoutineFactory = defaultGoRoutineFactory{}
   151  
   152  type defaultGoRoutineFactory struct{}
   153  
   154  func (defaultGoRoutineFactory) Go(function func()) {
   155  	go function()
   156  }
   157  
   158  func sanitizeTagKey(key string) string {
   159  	return strings.Map(sanitizeRune, key)
   160  }
   161  
   162  // This function has been copied from the prometheus exporter.
   163  // Thus changes done only here might not always produce the desired result when exporting to prometheus
   164  // unless the prometheus exporter is also updated.
   165  // The rationale behind the duplication is that this function is used across all our Stats modes (statsd, prom, otel...)
   166  // and the one in the prometheus exporter is still used to sanitize some attributes set on a Resource level from
   167  // the OpenTelemetry client itself or 3rd parties.
   168  // Alternatively we could further customise the prometheus exporter and make it use the same function (this one).
   169  func sanitizeRune(r rune) rune {
   170  	if unicode.IsLetter(r) || unicode.IsDigit(r) || r == ':' || r == '_' {
   171  		return r
   172  	}
   173  	return '_'
   174  }