github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/metrics.go (about) 1 package internal 2 3 import ( 4 "bytes" 5 "time" 6 7 "github.com/lulzWill/go-agent/internal/jsonx" 8 ) 9 10 type metricForce int 11 12 const ( 13 forced metricForce = iota 14 unforced 15 ) 16 17 type metricID struct { 18 Name string `json:"name"` 19 Scope string `json:"scope,omitempty"` 20 } 21 22 type metricData struct { 23 // These values are in the units expected by the collector. 24 countSatisfied float64 // Seconds, or count for Apdex 25 totalTolerated float64 // Seconds, or count for Apdex 26 exclusiveFailed float64 // Seconds, or count for Apdex 27 min float64 // Seconds 28 max float64 // Seconds 29 sumSquares float64 // Seconds**2, or 0 for Apdex 30 } 31 32 func metricDataFromDuration(duration, exclusive time.Duration) metricData { 33 ds := duration.Seconds() 34 return metricData{ 35 countSatisfied: 1, 36 totalTolerated: ds, 37 exclusiveFailed: exclusive.Seconds(), 38 min: ds, 39 max: ds, 40 sumSquares: ds * ds, 41 } 42 } 43 44 type metric struct { 45 forced metricForce 46 data metricData 47 } 48 49 type metricTable struct { 50 metricPeriodStart time.Time 51 failedHarvests int 52 maxTableSize int // After this max is reached, only forced metrics are added 53 numDropped int // Number of unforced metrics dropped due to full table 54 metrics map[metricID]*metric 55 } 56 57 func newMetricTable(maxTableSize int, now time.Time) *metricTable { 58 return &metricTable{ 59 metricPeriodStart: now, 60 metrics: make(map[metricID]*metric), 61 maxTableSize: maxTableSize, 62 failedHarvests: 0, 63 } 64 } 65 66 func (mt *metricTable) full() bool { 67 return len(mt.metrics) >= mt.maxTableSize 68 } 69 70 func (data *metricData) aggregate(src metricData) { 71 data.countSatisfied += src.countSatisfied 72 data.totalTolerated += src.totalTolerated 73 data.exclusiveFailed += src.exclusiveFailed 74 75 if src.min < data.min { 76 data.min = src.min 77 } 78 if src.max > data.max { 79 data.max = src.max 80 } 81 82 data.sumSquares += src.sumSquares 83 } 84 85 func (mt *metricTable) mergeMetric(id metricID, m metric) { 86 if to := mt.metrics[id]; nil != to { 87 to.data.aggregate(m.data) 88 return 89 } 90 91 if mt.full() && (unforced == m.forced) { 92 mt.numDropped++ 93 return 94 } 95 // NOTE: `new` is used in place of `&m` since the latter will make `m` 96 // get heap allocated regardless of whether or not this line gets 97 // reached (running go version go1.5 darwin/amd64). See 98 // BenchmarkAddingSameMetrics. 99 alloc := new(metric) 100 *alloc = m 101 mt.metrics[id] = alloc 102 } 103 104 func (mt *metricTable) mergeFailed(from *metricTable) { 105 fails := from.failedHarvests + 1 106 if fails >= failedMetricAttemptsLimit { 107 return 108 } 109 if from.metricPeriodStart.Before(mt.metricPeriodStart) { 110 mt.metricPeriodStart = from.metricPeriodStart 111 } 112 mt.failedHarvests = fails 113 mt.merge(from, "") 114 } 115 116 func (mt *metricTable) merge(from *metricTable, newScope string) { 117 if "" == newScope { 118 for id, m := range from.metrics { 119 mt.mergeMetric(id, *m) 120 } 121 } else { 122 for id, m := range from.metrics { 123 mt.mergeMetric(metricID{Name: id.Name, Scope: newScope}, *m) 124 } 125 } 126 } 127 128 func (mt *metricTable) add(name, scope string, data metricData, force metricForce) { 129 mt.mergeMetric(metricID{Name: name, Scope: scope}, metric{data: data, forced: force}) 130 } 131 132 func (mt *metricTable) addCount(name string, count float64, force metricForce) { 133 mt.add(name, "", metricData{countSatisfied: count}, force) 134 } 135 136 func (mt *metricTable) addSingleCount(name string, force metricForce) { 137 mt.addCount(name, float64(1), force) 138 } 139 140 func (mt *metricTable) addDuration(name, scope string, duration, exclusive time.Duration, force metricForce) { 141 mt.add(name, scope, metricDataFromDuration(duration, exclusive), force) 142 } 143 144 func (mt *metricTable) addValueExclusive(name, scope string, total, exclusive float64, force metricForce) { 145 data := metricData{ 146 countSatisfied: 1, 147 totalTolerated: total, 148 exclusiveFailed: exclusive, 149 min: total, 150 max: total, 151 sumSquares: total * total, 152 } 153 mt.add(name, scope, data, force) 154 } 155 156 func (mt *metricTable) addValue(name, scope string, total float64, force metricForce) { 157 mt.addValueExclusive(name, scope, total, total, force) 158 } 159 160 func (mt *metricTable) addApdex(name, scope string, apdexThreshold time.Duration, zone ApdexZone, force metricForce) { 161 apdexSeconds := apdexThreshold.Seconds() 162 data := metricData{min: apdexSeconds, max: apdexSeconds} 163 164 switch zone { 165 case ApdexSatisfying: 166 data.countSatisfied = 1 167 case ApdexTolerating: 168 data.totalTolerated = 1 169 case ApdexFailing: 170 data.exclusiveFailed = 1 171 } 172 173 mt.add(name, scope, data, force) 174 } 175 176 func (mt *metricTable) CollectorJSON(agentRunID string, now time.Time) ([]byte, error) { 177 if 0 == len(mt.metrics) { 178 return nil, nil 179 } 180 estimatedBytesPerMetric := 128 181 estimatedLen := len(mt.metrics) * estimatedBytesPerMetric 182 buf := bytes.NewBuffer(make([]byte, 0, estimatedLen)) 183 buf.WriteByte('[') 184 185 jsonx.AppendString(buf, agentRunID) 186 buf.WriteByte(',') 187 jsonx.AppendInt(buf, mt.metricPeriodStart.Unix()) 188 buf.WriteByte(',') 189 jsonx.AppendInt(buf, now.Unix()) 190 buf.WriteByte(',') 191 192 buf.WriteByte('[') 193 first := true 194 for id, metric := range mt.metrics { 195 if first { 196 first = false 197 } else { 198 buf.WriteByte(',') 199 } 200 buf.WriteByte('[') 201 buf.WriteByte('{') 202 buf.WriteString(`"name":`) 203 jsonx.AppendString(buf, id.Name) 204 if id.Scope != "" { 205 buf.WriteString(`,"scope":`) 206 jsonx.AppendString(buf, id.Scope) 207 } 208 buf.WriteByte('}') 209 buf.WriteByte(',') 210 211 jsonx.AppendFloatArray(buf, 212 metric.data.countSatisfied, 213 metric.data.totalTolerated, 214 metric.data.exclusiveFailed, 215 metric.data.min, 216 metric.data.max, 217 metric.data.sumSquares) 218 219 buf.WriteByte(']') 220 } 221 buf.WriteByte(']') 222 223 buf.WriteByte(']') 224 return buf.Bytes(), nil 225 } 226 227 func (mt *metricTable) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { 228 return mt.CollectorJSON(agentRunID, harvestStart) 229 } 230 func (mt *metricTable) MergeIntoHarvest(h *Harvest) { 231 h.Metrics.mergeFailed(mt) 232 } 233 234 func (mt *metricTable) ApplyRules(rules metricRules) *metricTable { 235 if nil == rules { 236 return mt 237 } 238 if len(rules) == 0 { 239 return mt 240 } 241 242 applied := newMetricTable(mt.maxTableSize, mt.metricPeriodStart) 243 cache := make(map[string]string) 244 245 for id, m := range mt.metrics { 246 out, ok := cache[id.Name] 247 if !ok { 248 out = rules.Apply(id.Name) 249 cache[id.Name] = out 250 } 251 252 if "" != out { 253 applied.mergeMetric(metricID{Name: out, Scope: id.Scope}, *m) 254 } 255 } 256 257 return applied 258 }