github.com/hashicorp/go-metrics@v0.5.3/metrics.go (about) 1 package metrics 2 3 import ( 4 "runtime" 5 "strings" 6 "time" 7 8 iradix "github.com/hashicorp/go-immutable-radix" 9 ) 10 11 type Label struct { 12 Name string 13 Value string 14 } 15 16 func (m *Metrics) SetGauge(key []string, val float32) { 17 m.SetGaugeWithLabels(key, val, nil) 18 } 19 20 func (m *Metrics) SetGaugeWithLabels(key []string, val float32, labels []Label) { 21 if m.HostName != "" { 22 if m.EnableHostnameLabel { 23 labels = append(labels, Label{"host", m.HostName}) 24 } else if m.EnableHostname { 25 key = insert(0, m.HostName, key) 26 } 27 } 28 if m.EnableTypePrefix { 29 key = insert(0, "gauge", key) 30 } 31 if m.ServiceName != "" { 32 if m.EnableServiceLabel { 33 labels = append(labels, Label{"service", m.ServiceName}) 34 } else { 35 key = insert(0, m.ServiceName, key) 36 } 37 } 38 allowed, labelsFiltered := m.allowMetric(key, labels) 39 if !allowed { 40 return 41 } 42 m.sink.SetGaugeWithLabels(key, val, labelsFiltered) 43 } 44 45 func (m *Metrics) SetPrecisionGauge(key []string, val float64) { 46 m.SetPrecisionGaugeWithLabels(key, val, nil) 47 } 48 49 func (m *Metrics) SetPrecisionGaugeWithLabels(key []string, val float64, labels []Label) { 50 if m.HostName != "" { 51 if m.EnableHostnameLabel { 52 labels = append(labels, Label{"host", m.HostName}) 53 } else if m.EnableHostname { 54 key = insert(0, m.HostName, key) 55 } 56 } 57 if m.EnableTypePrefix { 58 key = insert(0, "gauge", key) 59 } 60 if m.ServiceName != "" { 61 if m.EnableServiceLabel { 62 labels = append(labels, Label{"service", m.ServiceName}) 63 } else { 64 key = insert(0, m.ServiceName, key) 65 } 66 } 67 allowed, labelsFiltered := m.allowMetric(key, labels) 68 if !allowed { 69 return 70 } 71 sink, ok := m.sink.(PrecisionGaugeMetricSink) 72 if !ok { 73 // Sink does not implement PrecisionGaugeMetricSink. 74 } else { 75 sink.SetPrecisionGaugeWithLabels(key, val, labelsFiltered) 76 } 77 } 78 79 func (m *Metrics) EmitKey(key []string, val float32) { 80 if m.EnableTypePrefix { 81 key = insert(0, "kv", key) 82 } 83 if m.ServiceName != "" { 84 key = insert(0, m.ServiceName, key) 85 } 86 allowed, _ := m.allowMetric(key, nil) 87 if !allowed { 88 return 89 } 90 m.sink.EmitKey(key, val) 91 } 92 93 func (m *Metrics) IncrCounter(key []string, val float32) { 94 m.IncrCounterWithLabels(key, val, nil) 95 } 96 97 func (m *Metrics) IncrCounterWithLabels(key []string, val float32, labels []Label) { 98 if m.HostName != "" && m.EnableHostnameLabel { 99 labels = append(labels, Label{"host", m.HostName}) 100 } 101 if m.EnableTypePrefix { 102 key = insert(0, "counter", key) 103 } 104 if m.ServiceName != "" { 105 if m.EnableServiceLabel { 106 labels = append(labels, Label{"service", m.ServiceName}) 107 } else { 108 key = insert(0, m.ServiceName, key) 109 } 110 } 111 allowed, labelsFiltered := m.allowMetric(key, labels) 112 if !allowed { 113 return 114 } 115 m.sink.IncrCounterWithLabels(key, val, labelsFiltered) 116 } 117 118 func (m *Metrics) AddSample(key []string, val float32) { 119 m.AddSampleWithLabels(key, val, nil) 120 } 121 122 func (m *Metrics) AddSampleWithLabels(key []string, val float32, labels []Label) { 123 if m.HostName != "" && m.EnableHostnameLabel { 124 labels = append(labels, Label{"host", m.HostName}) 125 } 126 if m.EnableTypePrefix { 127 key = insert(0, "sample", key) 128 } 129 if m.ServiceName != "" { 130 if m.EnableServiceLabel { 131 labels = append(labels, Label{"service", m.ServiceName}) 132 } else { 133 key = insert(0, m.ServiceName, key) 134 } 135 } 136 allowed, labelsFiltered := m.allowMetric(key, labels) 137 if !allowed { 138 return 139 } 140 m.sink.AddSampleWithLabels(key, val, labelsFiltered) 141 } 142 143 func (m *Metrics) MeasureSince(key []string, start time.Time) { 144 m.MeasureSinceWithLabels(key, start, nil) 145 } 146 147 func (m *Metrics) MeasureSinceWithLabels(key []string, start time.Time, labels []Label) { 148 if m.HostName != "" && m.EnableHostnameLabel { 149 labels = append(labels, Label{"host", m.HostName}) 150 } 151 if m.EnableTypePrefix { 152 key = insert(0, "timer", key) 153 } 154 if m.ServiceName != "" { 155 if m.EnableServiceLabel { 156 labels = append(labels, Label{"service", m.ServiceName}) 157 } else { 158 key = insert(0, m.ServiceName, key) 159 } 160 } 161 allowed, labelsFiltered := m.allowMetric(key, labels) 162 if !allowed { 163 return 164 } 165 now := time.Now() 166 elapsed := now.Sub(start) 167 msec := float32(elapsed.Nanoseconds()) / float32(m.TimerGranularity) 168 m.sink.AddSampleWithLabels(key, msec, labelsFiltered) 169 } 170 171 // UpdateFilter overwrites the existing filter with the given rules. 172 func (m *Metrics) UpdateFilter(allow, block []string) { 173 m.UpdateFilterAndLabels(allow, block, m.AllowedLabels, m.BlockedLabels) 174 } 175 176 // UpdateFilterAndLabels overwrites the existing filter with the given rules. 177 func (m *Metrics) UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels []string) { 178 m.filterLock.Lock() 179 defer m.filterLock.Unlock() 180 181 m.AllowedPrefixes = allow 182 m.BlockedPrefixes = block 183 184 if allowedLabels == nil { 185 // Having a white list means we take only elements from it 186 m.allowedLabels = nil 187 } else { 188 m.allowedLabels = make(map[string]bool) 189 for _, v := range allowedLabels { 190 m.allowedLabels[v] = true 191 } 192 } 193 m.blockedLabels = make(map[string]bool) 194 for _, v := range blockedLabels { 195 m.blockedLabels[v] = true 196 } 197 m.AllowedLabels = allowedLabels 198 m.BlockedLabels = blockedLabels 199 200 m.filter = iradix.New() 201 for _, prefix := range m.AllowedPrefixes { 202 m.filter, _, _ = m.filter.Insert([]byte(prefix), true) 203 } 204 for _, prefix := range m.BlockedPrefixes { 205 m.filter, _, _ = m.filter.Insert([]byte(prefix), false) 206 } 207 } 208 209 func (m *Metrics) Shutdown() { 210 if ss, ok := m.sink.(ShutdownSink); ok { 211 ss.Shutdown() 212 } 213 } 214 215 // labelIsAllowed return true if a should be included in metric 216 // the caller should lock m.filterLock while calling this method 217 func (m *Metrics) labelIsAllowed(label *Label) bool { 218 labelName := (*label).Name 219 if m.blockedLabels != nil { 220 _, ok := m.blockedLabels[labelName] 221 if ok { 222 // If present, let's remove this label 223 return false 224 } 225 } 226 if m.allowedLabels != nil { 227 _, ok := m.allowedLabels[labelName] 228 return ok 229 } 230 // Allow by default 231 return true 232 } 233 234 // filterLabels return only allowed labels 235 // the caller should lock m.filterLock while calling this method 236 func (m *Metrics) filterLabels(labels []Label) []Label { 237 if labels == nil { 238 return nil 239 } 240 toReturn := []Label{} 241 for _, label := range labels { 242 if m.labelIsAllowed(&label) { 243 toReturn = append(toReturn, label) 244 } 245 } 246 return toReturn 247 } 248 249 // Returns whether the metric should be allowed based on configured prefix filters 250 // Also return the applicable labels 251 func (m *Metrics) allowMetric(key []string, labels []Label) (bool, []Label) { 252 m.filterLock.RLock() 253 defer m.filterLock.RUnlock() 254 255 if m.filter == nil || m.filter.Len() == 0 { 256 return m.Config.FilterDefault, m.filterLabels(labels) 257 } 258 259 _, allowed, ok := m.filter.Root().LongestPrefix([]byte(strings.Join(key, "."))) 260 if !ok { 261 return m.Config.FilterDefault, m.filterLabels(labels) 262 } 263 264 return allowed.(bool), m.filterLabels(labels) 265 } 266 267 // Periodically collects runtime stats to publish 268 func (m *Metrics) collectStats() { 269 for { 270 time.Sleep(m.ProfileInterval) 271 m.EmitRuntimeStats() 272 } 273 } 274 275 // Emits various runtime statsitics 276 func (m *Metrics) EmitRuntimeStats() { 277 // Export number of Goroutines 278 numRoutines := runtime.NumGoroutine() 279 m.SetGauge([]string{"runtime", "num_goroutines"}, float32(numRoutines)) 280 281 // Export memory stats 282 var stats runtime.MemStats 283 runtime.ReadMemStats(&stats) 284 m.SetGauge([]string{"runtime", "alloc_bytes"}, float32(stats.Alloc)) 285 m.SetGauge([]string{"runtime", "sys_bytes"}, float32(stats.Sys)) 286 m.SetGauge([]string{"runtime", "malloc_count"}, float32(stats.Mallocs)) 287 m.SetGauge([]string{"runtime", "free_count"}, float32(stats.Frees)) 288 m.SetGauge([]string{"runtime", "heap_objects"}, float32(stats.HeapObjects)) 289 m.SetGauge([]string{"runtime", "total_gc_pause_ns"}, float32(stats.PauseTotalNs)) 290 m.SetGauge([]string{"runtime", "total_gc_runs"}, float32(stats.NumGC)) 291 292 // Export info about the last few GC runs 293 num := stats.NumGC 294 295 // Handle wrap around 296 if num < m.lastNumGC { 297 m.lastNumGC = 0 298 } 299 300 // Ensure we don't scan more than 256 301 if num-m.lastNumGC >= 256 { 302 m.lastNumGC = num - 255 303 } 304 305 for i := m.lastNumGC; i < num; i++ { 306 pause := stats.PauseNs[i%256] 307 m.AddSample([]string{"runtime", "gc_pause_ns"}, float32(pause)) 308 } 309 m.lastNumGC = num 310 } 311 312 // Creates a new slice with the provided string value as the first element 313 // and the provided slice values as the remaining values. 314 // Ordering of the values in the provided input slice is kept in tact in the output slice. 315 func insert(i int, v string, s []string) []string { 316 // Allocate new slice to avoid modifying the input slice 317 newS := make([]string, len(s)+1) 318 319 // Copy s[0, i-1] into newS 320 for j := 0; j < i; j++ { 321 newS[j] = s[j] 322 } 323 324 // Insert provided element at index i 325 newS[i] = v 326 327 // Copy s[i, len(s)-1] into newS starting at newS[i+1] 328 for j := i; j < len(s); j++ { 329 newS[j+1] = s[j] 330 } 331 332 return newS 333 }