1package lib 2 3import ( 4 "reflect" 5 "time" 6 7 "github.com/armon/go-metrics" 8 "github.com/armon/go-metrics/circonus" 9 "github.com/armon/go-metrics/datadog" 10 "github.com/armon/go-metrics/prometheus" 11) 12 13// TelemetryConfig is embedded in config.RuntimeConfig and holds the 14// configuration variables for go-metrics. It is a separate struct to allow it 15// to be exported as JSON and passed to other process like managed connect 16// proxies so they can inherit the agent's telemetry config. 17// 18// It is in lib package rather than agent/config because we need to use it in 19// the shared InitTelemetry functions below, but we can't import agent/config 20// due to a dependency cycle. 21type TelemetryConfig struct { 22 // Disable may be set to true to have InitTelemetry to skip initialization 23 // and return a nil MetricsSink. 24 Disable bool 25 26 // Circonus*: see https://github.com/circonus-labs/circonus-gometrics 27 // for more details on the various configuration options. 28 // Valid configuration combinations: 29 // - CirconusAPIToken 30 // metric management enabled (search for existing check or create a new one) 31 // - CirconusSubmissionUrl 32 // metric management disabled (use check with specified submission_url, 33 // broker must be using a public SSL certificate) 34 // - CirconusAPIToken + CirconusCheckSubmissionURL 35 // metric management enabled (use check with specified submission_url) 36 // - CirconusAPIToken + CirconusCheckID 37 // metric management enabled (use check with specified id) 38 39 // CirconusAPIApp is an app name associated with API token. 40 // Default: "consul" 41 // 42 // hcl: telemetry { circonus_api_app = string } 43 CirconusAPIApp string `json:"circonus_api_app,omitempty" mapstructure:"circonus_api_app"` 44 45 // CirconusAPIToken is a valid API Token used to create/manage check. If provided, 46 // metric management is enabled. 47 // Default: none 48 // 49 // hcl: telemetry { circonus_api_token = string } 50 CirconusAPIToken string `json:"circonus_api_token,omitempty" mapstructure:"circonus_api_token"` 51 52 // CirconusAPIURL is the base URL to use for contacting the Circonus API. 53 // Default: "https://api.circonus.com/v2" 54 // 55 // hcl: telemetry { circonus_api_url = string } 56 CirconusAPIURL string `json:"circonus_apiurl,omitempty" mapstructure:"circonus_apiurl"` 57 58 // CirconusBrokerID is an explicit broker to use when creating a new check. The numeric portion 59 // of broker._cid. If metric management is enabled and neither a Submission URL nor Check ID 60 // is provided, an attempt will be made to search for an existing check using Instance ID and 61 // Search Tag. If one is not found, a new HTTPTRAP check will be created. 62 // Default: use Select Tag if provided, otherwise, a random Enterprise Broker associated 63 // with the specified API token or the default Circonus Broker. 64 // Default: none 65 // 66 // hcl: telemetry { circonus_broker_id = string } 67 CirconusBrokerID string `json:"circonus_broker_id,omitempty" mapstructure:"circonus_broker_id"` 68 69 // CirconusBrokerSelectTag is a special tag which will be used to select a broker when 70 // a Broker ID is not provided. The best use of this is to as a hint for which broker 71 // should be used based on *where* this particular instance is running. 72 // (e.g. a specific geo location or datacenter, dc:sfo) 73 // Default: none 74 // 75 // hcl: telemetry { circonus_broker_select_tag = string } 76 CirconusBrokerSelectTag string `json:"circonus_broker_select_tag,omitempty" mapstructure:"circonus_broker_select_tag"` 77 78 // CirconusCheckDisplayName is the name for the check which will be displayed in the Circonus UI. 79 // Default: value of CirconusCheckInstanceID 80 // 81 // hcl: telemetry { circonus_check_display_name = string } 82 CirconusCheckDisplayName string `json:"circonus_check_display_name,omitempty" mapstructure:"circonus_check_display_name"` 83 84 // CirconusCheckForceMetricActivation will force enabling metrics, as they are encountered, 85 // if the metric already exists and is NOT active. If check management is enabled, the default 86 // behavior is to add new metrics as they are encountered. If the metric already exists in the 87 // check, it will *NOT* be activated. This setting overrides that behavior. 88 // Default: "false" 89 // 90 // hcl: telemetry { circonus_check_metrics_activation = (true|false) 91 CirconusCheckForceMetricActivation string `json:"circonus_check_force_metric_activation,omitempty" mapstructure:"circonus_check_force_metric_activation"` 92 93 // CirconusCheckID is the check id (not check bundle id) from a previously created 94 // HTTPTRAP check. The numeric portion of the check._cid field. 95 // Default: none 96 // 97 // hcl: telemetry { circonus_check_id = string } 98 CirconusCheckID string `json:"circonus_check_id,omitempty" mapstructure:"circonus_check_id"` 99 100 // CirconusCheckInstanceID serves to uniquely identify the metrics coming from this "instance". 101 // It can be used to maintain metric continuity with transient or ephemeral instances as 102 // they move around within an infrastructure. 103 // Default: hostname:app 104 // 105 // hcl: telemetry { circonus_check_instance_id = string } 106 CirconusCheckInstanceID string `json:"circonus_check_instance_id,omitempty" mapstructure:"circonus_check_instance_id"` 107 108 // CirconusCheckSearchTag is a special tag which, when coupled with the instance id, helps to 109 // narrow down the search results when neither a Submission URL or Check ID is provided. 110 // Default: service:app (e.g. service:consul) 111 // 112 // hcl: telemetry { circonus_check_search_tag = string } 113 CirconusCheckSearchTag string `json:"circonus_check_search_tag,omitempty" mapstructure:"circonus_check_search_tag"` 114 115 // CirconusCheckSearchTag is a special tag which, when coupled with the instance id, helps to 116 // narrow down the search results when neither a Submission URL or Check ID is provided. 117 // Default: service:app (e.g. service:consul) 118 // 119 // hcl: telemetry { circonus_check_tags = string } 120 CirconusCheckTags string `json:"circonus_check_tags,omitempty" mapstructure:"circonus_check_tags"` 121 122 // CirconusSubmissionInterval is the interval at which metrics are submitted to Circonus. 123 // Default: 10s 124 // 125 // hcl: telemetry { circonus_submission_interval = "duration" } 126 CirconusSubmissionInterval string `json:"circonus_submission_interval,omitempty" mapstructure:"circonus_submission_interval"` 127 128 // CirconusCheckSubmissionURL is the check.config.submission_url field from a 129 // previously created HTTPTRAP check. 130 // Default: none 131 // 132 // hcl: telemetry { circonus_submission_url = string } 133 CirconusSubmissionURL string `json:"circonus_submission_url,omitempty" mapstructure:"circonus_submission_url"` 134 135 // DisableCompatOneNine is a flag to stop emitting metrics that have been deprecated in version 1.9. 136 // 137 // hcl: telemetry { disable_compat_1.9 = (true|false) } 138 DisableCompatOneNine bool `json:"disable_compat_1.9,omitempty" mapstructure:"disable_compat_1.9"` 139 140 // DisableHostname will disable hostname prefixing for all metrics. 141 // 142 // hcl: telemetry { disable_hostname = (true|false) 143 DisableHostname bool `json:"disable_hostname,omitempty" mapstructure:"disable_hostname"` 144 145 // DogStatsdAddr is the address of a dogstatsd instance. If provided, 146 // metrics will be sent to that instance 147 // 148 // hcl: telemetry { dogstatsd_addr = string } 149 DogstatsdAddr string `json:"dogstatsd_addr,omitempty" mapstructure:"dogstatsd_addr"` 150 151 // DogStatsdTags are the global tags that should be sent with each packet to dogstatsd 152 // It is a list of strings, where each string looks like "my_tag_name:my_tag_value" 153 // 154 // hcl: telemetry { dogstatsd_tags = []string } 155 DogstatsdTags []string `json:"dogstatsd_tags,omitempty" mapstructure:"dogstatsd_tags"` 156 157 // FilterDefault is the default for whether to allow a metric that's not 158 // covered by the filter. 159 // 160 // hcl: telemetry { filter_default = (true|false) } 161 FilterDefault bool `json:"filter_default,omitempty" mapstructure:"filter_default"` 162 163 // AllowedPrefixes is a list of filter rules to apply for allowing metrics 164 // by prefix. Use the 'prefix_filter' option and prefix rules with '+' to be 165 // included. 166 // 167 // hcl: telemetry { prefix_filter = []string{"+<expr>", "+<expr>", ...} } 168 AllowedPrefixes []string `json:"allowed_prefixes,omitempty" mapstructure:"allowed_prefixes"` 169 170 // BlockedPrefixes is a list of filter rules to apply for blocking metrics 171 // by prefix. Use the 'prefix_filter' option and prefix rules with '-' to be 172 // excluded. 173 // 174 // hcl: telemetry { prefix_filter = []string{"-<expr>", "-<expr>", ...} } 175 BlockedPrefixes []string `json:"blocked_prefixes,omitempty" mapstructure:"blocked_prefixes"` 176 177 // MetricsPrefix is the prefix used to write stats values to. 178 // Default: "consul." 179 // 180 // hcl: telemetry { metrics_prefix = string } 181 MetricsPrefix string `json:"metrics_prefix,omitempty" mapstructure:"metrics_prefix"` 182 183 // StatsdAddr is the address of a statsd instance. If provided, 184 // metrics will be sent to that instance. 185 // 186 // hcl: telemetry { statsd_address = string } 187 StatsdAddr string `json:"statsd_address,omitempty" mapstructure:"statsd_address"` 188 189 // StatsiteAddr is the address of a statsite instance. If provided, 190 // metrics will be streamed to that instance. 191 // 192 // hcl: telemetry { statsite_address = string } 193 StatsiteAddr string `json:"statsite_address,omitempty" mapstructure:"statsite_address"` 194 195 // PrometheusOpts provides configuration for the PrometheusSink. Currently the only configuration 196 // we acquire from hcl is the retention time. We also use definition slices that are set in agent setup 197 // before being passed to InitTelemmetry. 198 // 199 // hcl: telemetry { prometheus_retention_time = "duration" } 200 PrometheusOpts prometheus.PrometheusOpts 201} 202 203// MergeDefaults copies any non-zero field from defaults into the current 204// config. 205// TODO(kit): We no longer use this function and can probably delete it 206func (c *TelemetryConfig) MergeDefaults(defaults *TelemetryConfig) { 207 if defaults == nil { 208 return 209 } 210 cfgPtrVal := reflect.ValueOf(c) 211 cfgVal := cfgPtrVal.Elem() 212 otherVal := reflect.ValueOf(*defaults) 213 for i := 0; i < cfgVal.NumField(); i++ { 214 f := cfgVal.Field(i) 215 if !f.IsValid() || !f.CanSet() { 216 continue 217 } 218 // See if the current value is a zero-value, if _not_ skip it 219 // 220 // No built in way to check for zero-values for all types so only 221 // implementing this for the types we actually have for now. Test failure 222 // should catch the case where we add new types later. 223 switch f.Kind() { 224 case reflect.Struct: 225 if f.Type() == reflect.TypeOf(prometheus.PrometheusOpts{}) { 226 continue 227 } 228 case reflect.Slice: 229 if !f.IsNil() { 230 continue 231 } 232 case reflect.Int, reflect.Int64: // time.Duration == int64 233 if f.Int() != 0 { 234 continue 235 } 236 case reflect.String: 237 if f.String() != "" { 238 continue 239 } 240 case reflect.Bool: 241 if f.Bool() { 242 continue 243 } 244 default: 245 // Needs implementing, should be caught by tests. 246 continue 247 } 248 249 // It's zero, copy it from defaults 250 f.Set(otherVal.Field(i)) 251 } 252} 253 254func statsiteSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 255 addr := cfg.StatsiteAddr 256 if addr == "" { 257 return nil, nil 258 } 259 return metrics.NewStatsiteSink(addr) 260} 261 262func statsdSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 263 addr := cfg.StatsdAddr 264 if addr == "" { 265 return nil, nil 266 } 267 return metrics.NewStatsdSink(addr) 268} 269 270func dogstatdSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 271 addr := cfg.DogstatsdAddr 272 if addr == "" { 273 return nil, nil 274 } 275 sink, err := datadog.NewDogStatsdSink(addr, hostname) 276 if err != nil { 277 return nil, err 278 } 279 sink.SetTags(cfg.DogstatsdTags) 280 return sink, nil 281} 282 283func prometheusSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 284 285 if cfg.PrometheusOpts.Expiration.Nanoseconds() < 1 { 286 return nil, nil 287 } 288 289 sink, err := prometheus.NewPrometheusSinkFrom(cfg.PrometheusOpts) 290 if err != nil { 291 return nil, err 292 } 293 return sink, nil 294} 295 296func circonusSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 297 token := cfg.CirconusAPIToken 298 url := cfg.CirconusSubmissionURL 299 if token == "" && url == "" { 300 return nil, nil 301 } 302 303 conf := &circonus.Config{} 304 conf.Interval = cfg.CirconusSubmissionInterval 305 conf.CheckManager.API.TokenKey = token 306 conf.CheckManager.API.TokenApp = cfg.CirconusAPIApp 307 conf.CheckManager.API.URL = cfg.CirconusAPIURL 308 conf.CheckManager.Check.SubmissionURL = url 309 conf.CheckManager.Check.ID = cfg.CirconusCheckID 310 conf.CheckManager.Check.ForceMetricActivation = cfg.CirconusCheckForceMetricActivation 311 conf.CheckManager.Check.InstanceID = cfg.CirconusCheckInstanceID 312 conf.CheckManager.Check.SearchTag = cfg.CirconusCheckSearchTag 313 conf.CheckManager.Check.DisplayName = cfg.CirconusCheckDisplayName 314 conf.CheckManager.Check.Tags = cfg.CirconusCheckTags 315 conf.CheckManager.Broker.ID = cfg.CirconusBrokerID 316 conf.CheckManager.Broker.SelectTag = cfg.CirconusBrokerSelectTag 317 318 if conf.CheckManager.Check.DisplayName == "" { 319 conf.CheckManager.Check.DisplayName = "Consul" 320 } 321 322 if conf.CheckManager.API.TokenApp == "" { 323 conf.CheckManager.API.TokenApp = "consul" 324 } 325 326 if conf.CheckManager.Check.SearchTag == "" { 327 conf.CheckManager.Check.SearchTag = "service:consul" 328 } 329 330 sink, err := circonus.NewCirconusSink(conf) 331 if err != nil { 332 return nil, err 333 } 334 sink.Start() 335 return sink, nil 336} 337 338// InitTelemetry configures go-metrics based on map of telemetry config 339// values as returned by Runtimecfg.Config(). 340func InitTelemetry(cfg TelemetryConfig) (*metrics.InmemSink, error) { 341 if cfg.Disable { 342 return nil, nil 343 } 344 // Setup telemetry 345 // Aggregate on 10 second intervals for 1 minute. Expose the 346 // metrics over stderr when there is a SIGUSR1 received. 347 memSink := metrics.NewInmemSink(10*time.Second, time.Minute) 348 metrics.DefaultInmemSignal(memSink) 349 metricsConf := metrics.DefaultConfig(cfg.MetricsPrefix) 350 metricsConf.EnableHostname = !cfg.DisableHostname 351 metricsConf.FilterDefault = cfg.FilterDefault 352 metricsConf.AllowedPrefixes = cfg.AllowedPrefixes 353 metricsConf.BlockedPrefixes = cfg.BlockedPrefixes 354 355 var sinks metrics.FanoutSink 356 addSink := func(fn func(TelemetryConfig, string) (metrics.MetricSink, error)) error { 357 s, err := fn(cfg, metricsConf.HostName) 358 if err != nil { 359 return err 360 } 361 if s != nil { 362 sinks = append(sinks, s) 363 } 364 return nil 365 } 366 367 if err := addSink(statsiteSink); err != nil { 368 return nil, err 369 } 370 if err := addSink(statsdSink); err != nil { 371 return nil, err 372 } 373 if err := addSink(dogstatdSink); err != nil { 374 return nil, err 375 } 376 if err := addSink(circonusSink); err != nil { 377 return nil, err 378 } 379 if err := addSink(circonusSink); err != nil { 380 return nil, err 381 } 382 if err := addSink(prometheusSink); err != nil { 383 return nil, err 384 } 385 386 if len(sinks) > 0 { 387 sinks = append(sinks, memSink) 388 metrics.NewGlobal(metricsConf, sinks) 389 } else { 390 metricsConf.EnableHostname = false 391 metrics.NewGlobal(metricsConf, memSink) 392 } 393 return memSink, nil 394} 395