Scott Baker | 2d89798 | 2019-09-24 11:50:08 -0700 | [diff] [blame] | 1 | package metrics |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "net/http" |
| 6 | "sort" |
| 7 | "time" |
| 8 | ) |
| 9 | |
| 10 | // MetricsSummary holds a roll-up of metrics info for a given interval |
| 11 | type MetricsSummary struct { |
| 12 | Timestamp string |
| 13 | Gauges []GaugeValue |
| 14 | Points []PointValue |
| 15 | Counters []SampledValue |
| 16 | Samples []SampledValue |
| 17 | } |
| 18 | |
| 19 | type GaugeValue struct { |
| 20 | Name string |
| 21 | Hash string `json:"-"` |
| 22 | Value float32 |
| 23 | |
| 24 | Labels []Label `json:"-"` |
| 25 | DisplayLabels map[string]string `json:"Labels"` |
| 26 | } |
| 27 | |
| 28 | type PointValue struct { |
| 29 | Name string |
| 30 | Points []float32 |
| 31 | } |
| 32 | |
| 33 | type SampledValue struct { |
| 34 | Name string |
| 35 | Hash string `json:"-"` |
| 36 | *AggregateSample |
| 37 | Mean float64 |
| 38 | Stddev float64 |
| 39 | |
| 40 | Labels []Label `json:"-"` |
| 41 | DisplayLabels map[string]string `json:"Labels"` |
| 42 | } |
| 43 | |
Scott Baker | 8487c5d | 2019-10-18 12:49:46 -0700 | [diff] [blame] | 44 | // deepCopy allocates a new instance of AggregateSample |
| 45 | func (source *SampledValue) deepCopy() SampledValue { |
| 46 | dest := *source |
| 47 | if source.AggregateSample != nil { |
| 48 | dest.AggregateSample = &AggregateSample{} |
| 49 | *dest.AggregateSample = *source.AggregateSample |
| 50 | } |
| 51 | return dest |
| 52 | } |
| 53 | |
Scott Baker | 2d89798 | 2019-09-24 11:50:08 -0700 | [diff] [blame] | 54 | // DisplayMetrics returns a summary of the metrics from the most recent finished interval. |
| 55 | func (i *InmemSink) DisplayMetrics(resp http.ResponseWriter, req *http.Request) (interface{}, error) { |
| 56 | data := i.Data() |
| 57 | |
| 58 | var interval *IntervalMetrics |
| 59 | n := len(data) |
| 60 | switch { |
| 61 | case n == 0: |
| 62 | return nil, fmt.Errorf("no metric intervals have been initialized yet") |
| 63 | case n == 1: |
| 64 | // Show the current interval if it's all we have |
Scott Baker | 8487c5d | 2019-10-18 12:49:46 -0700 | [diff] [blame] | 65 | interval = data[0] |
Scott Baker | 2d89798 | 2019-09-24 11:50:08 -0700 | [diff] [blame] | 66 | default: |
| 67 | // Show the most recent finished interval if we have one |
Scott Baker | 8487c5d | 2019-10-18 12:49:46 -0700 | [diff] [blame] | 68 | interval = data[n-2] |
Scott Baker | 2d89798 | 2019-09-24 11:50:08 -0700 | [diff] [blame] | 69 | } |
| 70 | |
Scott Baker | 8487c5d | 2019-10-18 12:49:46 -0700 | [diff] [blame] | 71 | interval.RLock() |
| 72 | defer interval.RUnlock() |
| 73 | |
Scott Baker | 2d89798 | 2019-09-24 11:50:08 -0700 | [diff] [blame] | 74 | summary := MetricsSummary{ |
| 75 | Timestamp: interval.Interval.Round(time.Second).UTC().String(), |
| 76 | Gauges: make([]GaugeValue, 0, len(interval.Gauges)), |
| 77 | Points: make([]PointValue, 0, len(interval.Points)), |
| 78 | } |
| 79 | |
| 80 | // Format and sort the output of each metric type, so it gets displayed in a |
| 81 | // deterministic order. |
| 82 | for name, points := range interval.Points { |
| 83 | summary.Points = append(summary.Points, PointValue{name, points}) |
| 84 | } |
| 85 | sort.Slice(summary.Points, func(i, j int) bool { |
| 86 | return summary.Points[i].Name < summary.Points[j].Name |
| 87 | }) |
| 88 | |
| 89 | for hash, value := range interval.Gauges { |
| 90 | value.Hash = hash |
| 91 | value.DisplayLabels = make(map[string]string) |
| 92 | for _, label := range value.Labels { |
| 93 | value.DisplayLabels[label.Name] = label.Value |
| 94 | } |
| 95 | value.Labels = nil |
| 96 | |
| 97 | summary.Gauges = append(summary.Gauges, value) |
| 98 | } |
| 99 | sort.Slice(summary.Gauges, func(i, j int) bool { |
| 100 | return summary.Gauges[i].Hash < summary.Gauges[j].Hash |
| 101 | }) |
| 102 | |
| 103 | summary.Counters = formatSamples(interval.Counters) |
| 104 | summary.Samples = formatSamples(interval.Samples) |
| 105 | |
| 106 | return summary, nil |
| 107 | } |
| 108 | |
| 109 | func formatSamples(source map[string]SampledValue) []SampledValue { |
| 110 | output := make([]SampledValue, 0, len(source)) |
| 111 | for hash, sample := range source { |
| 112 | displayLabels := make(map[string]string) |
| 113 | for _, label := range sample.Labels { |
| 114 | displayLabels[label.Name] = label.Value |
| 115 | } |
| 116 | |
| 117 | output = append(output, SampledValue{ |
| 118 | Name: sample.Name, |
| 119 | Hash: hash, |
| 120 | AggregateSample: sample.AggregateSample, |
| 121 | Mean: sample.AggregateSample.Mean(), |
| 122 | Stddev: sample.AggregateSample.Stddev(), |
| 123 | DisplayLabels: displayLabels, |
| 124 | }) |
| 125 | } |
| 126 | sort.Slice(output, func(i, j int) bool { |
| 127 | return output[i].Hash < output[j].Hash |
| 128 | }) |
| 129 | |
| 130 | return output |
| 131 | } |