metrics.go 6.3 KB

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