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  }