123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- package internal
- import (
- "bytes"
- "time"
- "github.com/newrelic/go-agent/internal/jsonx"
- )
- type metricForce int
- const (
- forced metricForce = iota
- unforced
- )
- type metricID struct {
- Name string `json:"name"`
- Scope string `json:"scope,omitempty"`
- }
- type metricData struct {
- // These values are in the units expected by the collector.
- countSatisfied float64 // Seconds, or count for Apdex
- totalTolerated float64 // Seconds, or count for Apdex
- exclusiveFailed float64 // Seconds, or count for Apdex
- min float64 // Seconds
- max float64 // Seconds
- sumSquares float64 // Seconds**2, or 0 for Apdex
- }
- func metricDataFromDuration(duration, exclusive time.Duration) metricData {
- ds := duration.Seconds()
- return metricData{
- countSatisfied: 1,
- totalTolerated: ds,
- exclusiveFailed: exclusive.Seconds(),
- min: ds,
- max: ds,
- sumSquares: ds * ds,
- }
- }
- type metric struct {
- forced metricForce
- data metricData
- }
- type metricTable struct {
- metricPeriodStart time.Time
- failedHarvests int
- maxTableSize int // After this max is reached, only forced metrics are added
- numDropped int // Number of unforced metrics dropped due to full table
- metrics map[metricID]*metric
- }
- func newMetricTable(maxTableSize int, now time.Time) *metricTable {
- return &metricTable{
- metricPeriodStart: now,
- metrics: make(map[metricID]*metric),
- maxTableSize: maxTableSize,
- failedHarvests: 0,
- }
- }
- func (mt *metricTable) full() bool {
- return len(mt.metrics) >= mt.maxTableSize
- }
- func (data *metricData) aggregate(src metricData) {
- data.countSatisfied += src.countSatisfied
- data.totalTolerated += src.totalTolerated
- data.exclusiveFailed += src.exclusiveFailed
- if src.min < data.min {
- data.min = src.min
- }
- if src.max > data.max {
- data.max = src.max
- }
- data.sumSquares += src.sumSquares
- }
- func (mt *metricTable) mergeMetric(id metricID, m metric) {
- if to := mt.metrics[id]; nil != to {
- to.data.aggregate(m.data)
- return
- }
- if mt.full() && (unforced == m.forced) {
- mt.numDropped++
- return
- }
- // NOTE: `new` is used in place of `&m` since the latter will make `m`
- // get heap allocated regardless of whether or not this line gets
- // reached (running go version go1.5 darwin/amd64). See
- // BenchmarkAddingSameMetrics.
- alloc := new(metric)
- *alloc = m
- mt.metrics[id] = alloc
- }
- func (mt *metricTable) mergeFailed(from *metricTable) {
- fails := from.failedHarvests + 1
- if fails >= failedMetricAttemptsLimit {
- return
- }
- if from.metricPeriodStart.Before(mt.metricPeriodStart) {
- mt.metricPeriodStart = from.metricPeriodStart
- }
- mt.failedHarvests = fails
- mt.merge(from, "")
- }
- func (mt *metricTable) merge(from *metricTable, newScope string) {
- if "" == newScope {
- for id, m := range from.metrics {
- mt.mergeMetric(id, *m)
- }
- } else {
- for id, m := range from.metrics {
- mt.mergeMetric(metricID{Name: id.Name, Scope: newScope}, *m)
- }
- }
- }
- func (mt *metricTable) add(name, scope string, data metricData, force metricForce) {
- mt.mergeMetric(metricID{Name: name, Scope: scope}, metric{data: data, forced: force})
- }
- func (mt *metricTable) addCount(name string, count float64, force metricForce) {
- mt.add(name, "", metricData{countSatisfied: count}, force)
- }
- func (mt *metricTable) addSingleCount(name string, force metricForce) {
- mt.addCount(name, float64(1), force)
- }
- func (mt *metricTable) addDuration(name, scope string, duration, exclusive time.Duration, force metricForce) {
- mt.add(name, scope, metricDataFromDuration(duration, exclusive), force)
- }
- func (mt *metricTable) addValueExclusive(name, scope string, total, exclusive float64, force metricForce) {
- data := metricData{
- countSatisfied: 1,
- totalTolerated: total,
- exclusiveFailed: exclusive,
- min: total,
- max: total,
- sumSquares: total * total,
- }
- mt.add(name, scope, data, force)
- }
- func (mt *metricTable) addValue(name, scope string, total float64, force metricForce) {
- mt.addValueExclusive(name, scope, total, total, force)
- }
- func (mt *metricTable) addApdex(name, scope string, apdexThreshold time.Duration, zone ApdexZone, force metricForce) {
- apdexSeconds := apdexThreshold.Seconds()
- data := metricData{min: apdexSeconds, max: apdexSeconds}
- switch zone {
- case ApdexSatisfying:
- data.countSatisfied = 1
- case ApdexTolerating:
- data.totalTolerated = 1
- case ApdexFailing:
- data.exclusiveFailed = 1
- }
- mt.add(name, scope, data, force)
- }
- func (mt *metricTable) CollectorJSON(agentRunID string, now time.Time) ([]byte, error) {
- if 0 == len(mt.metrics) {
- return nil, nil
- }
- estimatedBytesPerMetric := 128
- estimatedLen := len(mt.metrics) * estimatedBytesPerMetric
- buf := bytes.NewBuffer(make([]byte, 0, estimatedLen))
- buf.WriteByte('[')
- jsonx.AppendString(buf, agentRunID)
- buf.WriteByte(',')
- jsonx.AppendInt(buf, mt.metricPeriodStart.Unix())
- buf.WriteByte(',')
- jsonx.AppendInt(buf, now.Unix())
- buf.WriteByte(',')
- buf.WriteByte('[')
- first := true
- for id, metric := range mt.metrics {
- if first {
- first = false
- } else {
- buf.WriteByte(',')
- }
- buf.WriteByte('[')
- buf.WriteByte('{')
- buf.WriteString(`"name":`)
- jsonx.AppendString(buf, id.Name)
- if id.Scope != "" {
- buf.WriteString(`,"scope":`)
- jsonx.AppendString(buf, id.Scope)
- }
- buf.WriteByte('}')
- buf.WriteByte(',')
- jsonx.AppendFloatArray(buf,
- metric.data.countSatisfied,
- metric.data.totalTolerated,
- metric.data.exclusiveFailed,
- metric.data.min,
- metric.data.max,
- metric.data.sumSquares)
- buf.WriteByte(']')
- }
- buf.WriteByte(']')
- buf.WriteByte(']')
- return buf.Bytes(), nil
- }
- func (mt *metricTable) Data(agentRunID string, harvestStart time.Time) ([]byte, error) {
- return mt.CollectorJSON(agentRunID, harvestStart)
- }
- func (mt *metricTable) MergeIntoHarvest(h *Harvest) {
- h.Metrics.mergeFailed(mt)
- }
- func (mt *metricTable) ApplyRules(rules metricRules) *metricTable {
- if nil == rules {
- return mt
- }
- if len(rules) == 0 {
- return mt
- }
- applied := newMetricTable(mt.maxTableSize, mt.metricPeriodStart)
- cache := make(map[string]string)
- for id, m := range mt.metrics {
- out, ok := cache[id.Name]
- if !ok {
- out = rules.Apply(id.Name)
- cache[id.Name] = out
- }
- if "" != out {
- applied.mergeMetric(metricID{Name: out, Scope: id.Scope}, *m)
- }
- }
- return applied
- }
- func (mt *metricTable) EndpointMethod() string {
- return cmdMetrics
- }
|