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 }