github.com/Jeffail/benthos/v3@v3.65.0/lib/metrics/stdout.go (about) 1 package metrics 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "runtime" 7 "strconv" 8 "strings" 9 "sync/atomic" 10 "time" 11 12 "github.com/Jeffail/benthos/v3/internal/docs" 13 "github.com/Jeffail/benthos/v3/lib/log" 14 "github.com/Jeffail/gabs/v2" 15 ) 16 17 //------------------------------------------------------------------------------ 18 19 func init() { 20 Constructors[TypeStdout] = TypeSpec{ 21 constructor: NewStdout, 22 Status: docs.StatusBeta, 23 Summary: ` 24 Prints aggregated metrics as JSON objects to stdout.`, 25 Description: ` 26 When Benthos shuts down all aggregated metrics are printed. If a 27 ` + "`push_interval`" + ` is specified then metrics are also printed 28 periodically.`, 29 FieldSpecs: docs.FieldSpecs{ 30 docs.FieldCommon("push_interval", "An optional period of time to continuously print metrics."), 31 docs.FieldCommon("static_fields", "A map of static fields to add to each flushed metric object.").Map().HasDefault(map[string]interface{}{ 32 "@service": "benthos", 33 }).HasType(docs.FieldTypeUnknown), 34 docs.FieldCommon("flush_metrics", "Whether counters and timing metrics should be reset to 0 each time metrics are printed."), 35 pathMappingDocs(false, false), 36 }, 37 } 38 } 39 40 //------------------------------------------------------------------------------ 41 42 // StdoutConfig contains configuration parameters for the Stdout metrics 43 // aggregator. 44 type StdoutConfig struct { 45 PushInterval string `json:"push_interval" yaml:"push_interval"` 46 StaticFields map[string]interface{} `json:"static_fields" yaml:"static_fields"` 47 FlushMetrics bool `json:"flush_metrics" yaml:"flush_metrics"` 48 PathMapping string `json:"path_mapping" yaml:"path_mapping"` 49 } 50 51 // NewStdoutConfig returns a new StdoutConfig with default values. 52 func NewStdoutConfig() StdoutConfig { 53 return StdoutConfig{ 54 PushInterval: "", 55 StaticFields: map[string]interface{}{ 56 "@service": "benthos", 57 }, 58 FlushMetrics: false, 59 PathMapping: "", 60 } 61 } 62 63 //------------------------------------------------------------------------------ 64 65 // Stdout is an object with capability to hold internal stats and emit them as 66 // individual JSON objects via stdout. 67 type Stdout struct { 68 local *Local 69 timestamp time.Time 70 log log.Modular 71 72 pathMapping *pathMapping 73 config StdoutConfig 74 closedChan chan struct{} 75 running int32 76 77 staticFields []byte 78 } 79 80 // NewStdout creates and returns a new Stdout metric object. 81 func NewStdout(config Config, opts ...func(Type)) (Type, error) { 82 t := &Stdout{ 83 local: NewLocal(), 84 timestamp: time.Now(), 85 config: config.Stdout, 86 closedChan: make(chan struct{}), 87 running: 1, 88 } 89 90 //TODO: add field interpolation here 91 sf, err := json.Marshal(config.Stdout.StaticFields) 92 if err != nil { 93 return t, fmt.Errorf("failed to parse static fields: %v", err) 94 } 95 t.staticFields = sf 96 97 for _, opt := range opts { 98 opt(t) 99 } 100 101 if t.pathMapping, err = newPathMapping(t.config.PathMapping, t.log); err != nil { 102 return nil, fmt.Errorf("failed to init path mapping: %v", err) 103 } 104 105 if len(t.config.PushInterval) > 0 { 106 interval, err := time.ParseDuration(t.config.PushInterval) 107 if err != nil { 108 return nil, fmt.Errorf("failed to parse push interval: %v", err) 109 } 110 go func() { 111 for { 112 select { 113 case <-t.closedChan: 114 return 115 case <-time.After(interval): 116 t.publishMetrics() 117 } 118 } 119 }() 120 } 121 122 return t, nil 123 } 124 125 //------------------------------------------------------------------------------ 126 127 // writeMetric prints a metric object with any configured extras merged in to 128 // t. 129 func (s *Stdout) writeMetric(metricSet *gabs.Container) { 130 base, _ := gabs.ParseJSON(s.staticFields) 131 base.SetP(time.Now().Format(time.RFC3339), "@timestamp") 132 base.Merge(metricSet) 133 134 fmt.Printf("%s\n", base.String()) 135 } 136 137 // publishMetrics 138 func (s *Stdout) publishMetrics() { 139 counterObjs := make(map[string]*gabs.Container) 140 141 var counters map[string]int64 142 var timings map[string]int64 143 if s.config.FlushMetrics { 144 counters = s.local.FlushCounters() 145 timings = s.local.FlushTimings() 146 } else { 147 counters = s.local.GetCounters() 148 timings = s.local.GetTimings() 149 } 150 151 s.constructMetrics(counterObjs, counters) 152 s.constructMetrics(counterObjs, timings) 153 154 system := make(map[string]int64) 155 uptime := time.Since(s.timestamp).Milliseconds() 156 goroutines := runtime.NumGoroutine() 157 system["system.uptime"] = uptime 158 system["system.goroutines"] = int64(goroutines) 159 s.constructMetrics(counterObjs, system) 160 161 for _, o := range counterObjs { 162 s.writeMetric(o) 163 } 164 } 165 166 // constructMetrics groups individual Benthos metrics contained in a map into 167 // a container for each component instance. For example, 168 // pipeline.processor.1.count and pipeline.processor.1.error would be grouped 169 // into a single pipeline.processor.1 object. 170 func (s *Stdout) constructMetrics(co map[string]*gabs.Container, metrics map[string]int64) { 171 for k, v := range metrics { 172 parts := strings.Split(k, ".") 173 var objKey string 174 var valKey string 175 // walk key parts backwards building up objects of instances of processor/broker/etc 176 for i := len(parts) - 1; i >= 0; i-- { 177 if _, err := strconv.Atoi(parts[i]); err == nil { 178 // part is a reference to an index of a processor/broker/etc 179 objKey = strings.Join(parts[:i+1], ".") 180 valKey = strings.Join(parts[i+1:], ".") 181 break 182 } 183 } 184 185 if objKey == "" { 186 // key is not referencing an 'instance' of a processor/broker/etc 187 objKey = parts[0] 188 valKey = strings.Join(parts[0:], ".") 189 } 190 191 _, exists := co[objKey] 192 if !exists { 193 co[objKey] = gabs.New() 194 co[objKey].SetP(objKey, "metric") 195 co[objKey].SetP(parts[0], "component") 196 197 } 198 co[objKey].SetP(v, valKey) 199 } 200 } 201 202 // GetCounter returns a stat counter object for a path. 203 func (s *Stdout) GetCounter(path string) StatCounter { 204 if path = s.pathMapping.mapPathNoTags(path); path == "" { 205 return DudStat{} 206 } 207 return s.local.GetCounter(path) 208 } 209 210 // GetCounterVec returns a stat counter object for a path with the labels 211 // discarded. 212 func (s *Stdout) GetCounterVec(path string, n []string) StatCounterVec { 213 path = s.pathMapping.mapPathNoTags(path) 214 return fakeCounterVec(func([]string) StatCounter { 215 if path == "" { 216 return DudStat{} 217 } 218 return s.local.GetCounter(path) 219 }) 220 } 221 222 // GetTimer returns a stat timer object for a path. 223 func (s *Stdout) GetTimer(path string) StatTimer { 224 if path = s.pathMapping.mapPathNoTags(path); path == "" { 225 return DudStat{} 226 } 227 return s.local.GetTimer(path) 228 } 229 230 // GetTimerVec returns a stat timer object for a path with the labels 231 // discarded. 232 func (s *Stdout) GetTimerVec(path string, n []string) StatTimerVec { 233 path = s.pathMapping.mapPathNoTags(path) 234 return fakeTimerVec(func([]string) StatTimer { 235 if path == "" { 236 return DudStat{} 237 } 238 return s.local.GetTimer(path) 239 }) 240 } 241 242 // GetGauge returns a stat gauge object for a path. 243 func (s *Stdout) GetGauge(path string) StatGauge { 244 if path = s.pathMapping.mapPathNoTags(path); path == "" { 245 return DudStat{} 246 } 247 return s.local.GetGauge(path) 248 } 249 250 // GetGaugeVec returns a stat timer object for a path with the labels 251 // discarded. 252 func (s *Stdout) GetGaugeVec(path string, n []string) StatGaugeVec { 253 path = s.pathMapping.mapPathNoTags(path) 254 return fakeGaugeVec(func([]string) StatGauge { 255 if path == "" { 256 return DudStat{} 257 } 258 return s.local.GetGauge(path) 259 }) 260 } 261 262 // SetLogger does nothing. 263 func (s *Stdout) SetLogger(log log.Modular) { 264 s.log = log 265 } 266 267 // Close stops the Stdout object from aggregating metrics and does a publish 268 // (write to stdout) of metrics. 269 func (s *Stdout) Close() error { 270 if atomic.CompareAndSwapInt32(&s.running, 1, 0) { 271 close(s.closedChan) 272 } 273 s.publishMetrics() 274 275 return nil 276 } 277 278 //------------------------------------------------------------------------------