1package 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
44// 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
54// 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
65		interval = data[0]
66	default:
67		// Show the most recent finished interval if we have one
68		interval = data[n-2]
69	}
70
71	interval.RLock()
72	defer interval.RUnlock()
73
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
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}
132