blob: 5fac958d94163dcda3312b6184d2158710dec01a [file] [log] [blame]
Stephane Barbarie260a5632019-02-26 16:12:49 -05001package metrics
2
3import (
4 "fmt"
5 "net/http"
6 "sort"
7 "time"
8)
9
10// MetricsSummary holds a roll-up of metrics info for a given interval
11type MetricsSummary struct {
12 Timestamp string
13 Gauges []GaugeValue
14 Points []PointValue
15 Counters []SampledValue
16 Samples []SampledValue
17}
18
19type 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
28type PointValue struct {
29 Name string
30 Points []float32
31}
32
33type 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 Baker8461e152019-10-01 14:44:30 -070044// deepCopy allocates a new instance of AggregateSample
45func (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
Stephane Barbarie260a5632019-02-26 16:12:49 -050054// DisplayMetrics returns a summary of the metrics from the most recent finished interval.
55func (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 Baker8461e152019-10-01 14:44:30 -070065 interval = data[0]
Stephane Barbarie260a5632019-02-26 16:12:49 -050066 default:
67 // Show the most recent finished interval if we have one
Scott Baker8461e152019-10-01 14:44:30 -070068 interval = data[n-2]
Stephane Barbarie260a5632019-02-26 16:12:49 -050069 }
70
Scott Baker8461e152019-10-01 14:44:30 -070071 interval.RLock()
72 defer interval.RUnlock()
73
Stephane Barbarie260a5632019-02-26 16:12:49 -050074 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
109func 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}