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