1package metrics 2 3import ( 4 "runtime" 5 "strings" 6 "time" 7 8 "github.com/hashicorp/go-immutable-radix" 9) 10 11type Label struct { 12 Name string 13 Value string 14} 15 16func (m *Metrics) SetGauge(key []string, val float32) { 17 m.SetGaugeWithLabels(key, val, nil) 18} 19 20func (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 45func (m *Metrics) EmitKey(key []string, val float32) { 46 if m.EnableTypePrefix { 47 key = insert(0, "kv", key) 48 } 49 if m.ServiceName != "" { 50 key = insert(0, m.ServiceName, key) 51 } 52 allowed, _ := m.allowMetric(key, nil) 53 if !allowed { 54 return 55 } 56 m.sink.EmitKey(key, val) 57} 58 59func (m *Metrics) IncrCounter(key []string, val float32) { 60 m.IncrCounterWithLabels(key, val, nil) 61} 62 63func (m *Metrics) IncrCounterWithLabels(key []string, val float32, labels []Label) { 64 if m.HostName != "" && m.EnableHostnameLabel { 65 labels = append(labels, Label{"host", m.HostName}) 66 } 67 if m.EnableTypePrefix { 68 key = insert(0, "counter", key) 69 } 70 if m.ServiceName != "" { 71 if m.EnableServiceLabel { 72 labels = append(labels, Label{"service", m.ServiceName}) 73 } else { 74 key = insert(0, m.ServiceName, key) 75 } 76 } 77 allowed, labelsFiltered := m.allowMetric(key, labels) 78 if !allowed { 79 return 80 } 81 m.sink.IncrCounterWithLabels(key, val, labelsFiltered) 82} 83 84func (m *Metrics) AddSample(key []string, val float32) { 85 m.AddSampleWithLabels(key, val, nil) 86} 87 88func (m *Metrics) AddSampleWithLabels(key []string, val float32, labels []Label) { 89 if m.HostName != "" && m.EnableHostnameLabel { 90 labels = append(labels, Label{"host", m.HostName}) 91 } 92 if m.EnableTypePrefix { 93 key = insert(0, "sample", key) 94 } 95 if m.ServiceName != "" { 96 if m.EnableServiceLabel { 97 labels = append(labels, Label{"service", m.ServiceName}) 98 } else { 99 key = insert(0, m.ServiceName, key) 100 } 101 } 102 allowed, labelsFiltered := m.allowMetric(key, labels) 103 if !allowed { 104 return 105 } 106 m.sink.AddSampleWithLabels(key, val, labelsFiltered) 107} 108 109func (m *Metrics) MeasureSince(key []string, start time.Time) { 110 m.MeasureSinceWithLabels(key, start, nil) 111} 112 113func (m *Metrics) MeasureSinceWithLabels(key []string, start time.Time, labels []Label) { 114 if m.HostName != "" && m.EnableHostnameLabel { 115 labels = append(labels, Label{"host", m.HostName}) 116 } 117 if m.EnableTypePrefix { 118 key = insert(0, "timer", key) 119 } 120 if m.ServiceName != "" { 121 if m.EnableServiceLabel { 122 labels = append(labels, Label{"service", m.ServiceName}) 123 } else { 124 key = insert(0, m.ServiceName, key) 125 } 126 } 127 allowed, labelsFiltered := m.allowMetric(key, labels) 128 if !allowed { 129 return 130 } 131 now := time.Now() 132 elapsed := now.Sub(start) 133 msec := float32(elapsed.Nanoseconds()) / float32(m.TimerGranularity) 134 m.sink.AddSampleWithLabels(key, msec, labelsFiltered) 135} 136 137// UpdateFilter overwrites the existing filter with the given rules. 138func (m *Metrics) UpdateFilter(allow, block []string) { 139 m.UpdateFilterAndLabels(allow, block, m.AllowedLabels, m.BlockedLabels) 140} 141 142// UpdateFilterAndLabels overwrites the existing filter with the given rules. 143func (m *Metrics) UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels []string) { 144 m.filterLock.Lock() 145 defer m.filterLock.Unlock() 146 147 m.AllowedPrefixes = allow 148 m.BlockedPrefixes = block 149 150 if allowedLabels == nil { 151 // Having a white list means we take only elements from it 152 m.allowedLabels = nil 153 } else { 154 m.allowedLabels = make(map[string]bool) 155 for _, v := range allowedLabels { 156 m.allowedLabels[v] = true 157 } 158 } 159 m.blockedLabels = make(map[string]bool) 160 for _, v := range blockedLabels { 161 m.blockedLabels[v] = true 162 } 163 m.AllowedLabels = allowedLabels 164 m.BlockedLabels = blockedLabels 165 166 m.filter = iradix.New() 167 for _, prefix := range m.AllowedPrefixes { 168 m.filter, _, _ = m.filter.Insert([]byte(prefix), true) 169 } 170 for _, prefix := range m.BlockedPrefixes { 171 m.filter, _, _ = m.filter.Insert([]byte(prefix), false) 172 } 173} 174 175// labelIsAllowed return true if a should be included in metric 176// the caller should lock m.filterLock while calling this method 177func (m *Metrics) labelIsAllowed(label *Label) bool { 178 labelName := (*label).Name 179 if m.blockedLabels != nil { 180 _, ok := m.blockedLabels[labelName] 181 if ok { 182 // If present, let's remove this label 183 return false 184 } 185 } 186 if m.allowedLabels != nil { 187 _, ok := m.allowedLabels[labelName] 188 return ok 189 } 190 // Allow by default 191 return true 192} 193 194// filterLabels return only allowed labels 195// the caller should lock m.filterLock while calling this method 196func (m *Metrics) filterLabels(labels []Label) []Label { 197 if labels == nil { 198 return nil 199 } 200 toReturn := []Label{} 201 for _, label := range labels { 202 if m.labelIsAllowed(&label) { 203 toReturn = append(toReturn, label) 204 } 205 } 206 return toReturn 207} 208 209// Returns whether the metric should be allowed based on configured prefix filters 210// Also return the applicable labels 211func (m *Metrics) allowMetric(key []string, labels []Label) (bool, []Label) { 212 m.filterLock.RLock() 213 defer m.filterLock.RUnlock() 214 215 if m.filter == nil || m.filter.Len() == 0 { 216 return m.Config.FilterDefault, m.filterLabels(labels) 217 } 218 219 _, allowed, ok := m.filter.Root().LongestPrefix([]byte(strings.Join(key, "."))) 220 if !ok { 221 return m.Config.FilterDefault, m.filterLabels(labels) 222 } 223 224 return allowed.(bool), m.filterLabels(labels) 225} 226 227// Periodically collects runtime stats to publish 228func (m *Metrics) collectStats() { 229 for { 230 time.Sleep(m.ProfileInterval) 231 m.emitRuntimeStats() 232 } 233} 234 235// Emits various runtime statsitics 236func (m *Metrics) emitRuntimeStats() { 237 // Export number of Goroutines 238 numRoutines := runtime.NumGoroutine() 239 m.SetGauge([]string{"runtime", "num_goroutines"}, float32(numRoutines)) 240 241 // Export memory stats 242 var stats runtime.MemStats 243 runtime.ReadMemStats(&stats) 244 m.SetGauge([]string{"runtime", "alloc_bytes"}, float32(stats.Alloc)) 245 m.SetGauge([]string{"runtime", "sys_bytes"}, float32(stats.Sys)) 246 m.SetGauge([]string{"runtime", "malloc_count"}, float32(stats.Mallocs)) 247 m.SetGauge([]string{"runtime", "free_count"}, float32(stats.Frees)) 248 m.SetGauge([]string{"runtime", "heap_objects"}, float32(stats.HeapObjects)) 249 m.SetGauge([]string{"runtime", "total_gc_pause_ns"}, float32(stats.PauseTotalNs)) 250 m.SetGauge([]string{"runtime", "total_gc_runs"}, float32(stats.NumGC)) 251 252 // Export info about the last few GC runs 253 num := stats.NumGC 254 255 // Handle wrap around 256 if num < m.lastNumGC { 257 m.lastNumGC = 0 258 } 259 260 // Ensure we don't scan more than 256 261 if num-m.lastNumGC >= 256 { 262 m.lastNumGC = num - 255 263 } 264 265 for i := m.lastNumGC; i < num; i++ { 266 pause := stats.PauseNs[i%256] 267 m.AddSample([]string{"runtime", "gc_pause_ns"}, float32(pause)) 268 } 269 m.lastNumGC = num 270} 271 272// Inserts a string value at an index into the slice 273func insert(i int, v string, s []string) []string { 274 s = append(s, "") 275 copy(s[i+1:], s[i:]) 276 s[i] = v 277 return s 278} 279