Scott Baker | eee8dd8 | 2019-09-24 12:52:34 -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 | |
| 44 | // DisplayMetrics returns a summary of the metrics from the most recent finished interval. |
| 45 | func (i *InmemSink) DisplayMetrics(resp http.ResponseWriter, req *http.Request) (interface{}, error) { |
| 46 | data := i.Data() |
| 47 | |
| 48 | var interval *IntervalMetrics |
| 49 | n := len(data) |
| 50 | switch { |
| 51 | case n == 0: |
| 52 | return nil, fmt.Errorf("no metric intervals have been initialized yet") |
| 53 | case n == 1: |
| 54 | // Show the current interval if it's all we have |
| 55 | interval = i.intervals[0] |
| 56 | default: |
| 57 | // Show the most recent finished interval if we have one |
| 58 | interval = i.intervals[n-2] |
| 59 | } |
| 60 | |
| 61 | summary := MetricsSummary{ |
| 62 | Timestamp: interval.Interval.Round(time.Second).UTC().String(), |
| 63 | Gauges: make([]GaugeValue, 0, len(interval.Gauges)), |
| 64 | Points: make([]PointValue, 0, len(interval.Points)), |
| 65 | } |
| 66 | |
| 67 | // Format and sort the output of each metric type, so it gets displayed in a |
| 68 | // deterministic order. |
| 69 | for name, points := range interval.Points { |
| 70 | summary.Points = append(summary.Points, PointValue{name, points}) |
| 71 | } |
| 72 | sort.Slice(summary.Points, func(i, j int) bool { |
| 73 | return summary.Points[i].Name < summary.Points[j].Name |
| 74 | }) |
| 75 | |
| 76 | for hash, value := range interval.Gauges { |
| 77 | value.Hash = hash |
| 78 | value.DisplayLabels = make(map[string]string) |
| 79 | for _, label := range value.Labels { |
| 80 | value.DisplayLabels[label.Name] = label.Value |
| 81 | } |
| 82 | value.Labels = nil |
| 83 | |
| 84 | summary.Gauges = append(summary.Gauges, value) |
| 85 | } |
| 86 | sort.Slice(summary.Gauges, func(i, j int) bool { |
| 87 | return summary.Gauges[i].Hash < summary.Gauges[j].Hash |
| 88 | }) |
| 89 | |
| 90 | summary.Counters = formatSamples(interval.Counters) |
| 91 | summary.Samples = formatSamples(interval.Samples) |
| 92 | |
| 93 | return summary, nil |
| 94 | } |
| 95 | |
| 96 | func formatSamples(source map[string]SampledValue) []SampledValue { |
| 97 | output := make([]SampledValue, 0, len(source)) |
| 98 | for hash, sample := range source { |
| 99 | displayLabels := make(map[string]string) |
| 100 | for _, label := range sample.Labels { |
| 101 | displayLabels[label.Name] = label.Value |
| 102 | } |
| 103 | |
| 104 | output = append(output, SampledValue{ |
| 105 | Name: sample.Name, |
| 106 | Hash: hash, |
| 107 | AggregateSample: sample.AggregateSample, |
| 108 | Mean: sample.AggregateSample.Mean(), |
| 109 | Stddev: sample.AggregateSample.Stddev(), |
| 110 | DisplayLabels: displayLabels, |
| 111 | }) |
| 112 | } |
| 113 | sort.Slice(output, func(i, j int) bool { |
| 114 | return output[i].Hash < output[j].Hash |
| 115 | }) |
| 116 | |
| 117 | return output |
| 118 | } |