github.com/wfusion/gofusion@v1.1.14/common/infra/metrics/inmem_endpoint.go (about) 1 package metrics 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "sort" 8 "time" 9 ) 10 11 // MetricsSummary holds a roll-up of metrics info for a given interval 12 type MetricsSummary struct { 13 Timestamp string 14 Gauges []GaugeValue 15 PrecisionGauges []PrecisionGaugeValue 16 Points []PointValue 17 Counters []SampledValue 18 Samples []SampledValue 19 } 20 21 type GaugeValue struct { 22 Name string 23 Hash string `json:"-"` 24 Value float32 25 26 Labels []Label `json:"-"` 27 DisplayLabels map[string]string `json:"Labels"` 28 } 29 30 type PrecisionGaugeValue struct { 31 Name string 32 Hash string `json:"-"` 33 Value float64 34 35 Labels []Label `json:"-"` 36 DisplayLabels map[string]string `json:"Labels"` 37 } 38 39 type PointValue struct { 40 Name string 41 Points []float32 42 } 43 44 type SampledValue struct { 45 Name string 46 Hash string `json:"-"` 47 *AggregateSample 48 Mean float64 49 Stddev float64 50 51 Labels []Label `json:"-"` 52 DisplayLabels map[string]string `json:"Labels"` 53 } 54 55 // deepCopy allocates a new instance of AggregateSample 56 func (source *SampledValue) deepCopy() SampledValue { 57 dest := *source 58 if source.AggregateSample != nil { 59 dest.AggregateSample = &AggregateSample{} 60 *dest.AggregateSample = *source.AggregateSample 61 } 62 return dest 63 } 64 65 // DisplayMetrics returns a summary of the metrics from the most recent finished interval. 66 func (i *InmemSink) DisplayMetrics(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 67 data := i.Data() 68 69 var interval *IntervalMetrics 70 n := len(data) 71 switch { 72 case n == 0: 73 return nil, fmt.Errorf("no metric intervals have been initialized yet") 74 case n == 1: 75 // Show the current interval if it's all we have 76 interval = data[0] 77 default: 78 // Show the most recent finished interval if we have one 79 interval = data[n-2] 80 } 81 82 return newMetricSummaryFromInterval(interval), nil 83 } 84 85 func newMetricSummaryFromInterval(interval *IntervalMetrics) MetricsSummary { 86 interval.RLock() 87 defer interval.RUnlock() 88 89 summary := MetricsSummary{ 90 Timestamp: interval.Interval.Round(time.Second).UTC().String(), 91 Gauges: make([]GaugeValue, 0, len(interval.Gauges)), 92 PrecisionGauges: make([]PrecisionGaugeValue, 0, len(interval.PrecisionGauges)), 93 Points: make([]PointValue, 0, len(interval.Points)), 94 } 95 96 // Format and sort the output of each metric type, so it gets displayed in a 97 // deterministic order. 98 for name, points := range interval.Points { 99 summary.Points = append(summary.Points, PointValue{name, points}) 100 } 101 sort.Slice(summary.Points, func(i, j int) bool { 102 return summary.Points[i].Name < summary.Points[j].Name 103 }) 104 105 for hash, value := range interval.Gauges { 106 value.Hash = hash 107 value.DisplayLabels = make(map[string]string) 108 for _, label := range value.Labels { 109 value.DisplayLabels[label.Name] = label.Value 110 } 111 value.Labels = nil 112 113 summary.Gauges = append(summary.Gauges, value) 114 } 115 sort.Slice(summary.Gauges, func(i, j int) bool { 116 return summary.Gauges[i].Hash < summary.Gauges[j].Hash 117 }) 118 119 for hash, value := range interval.PrecisionGauges { 120 value.Hash = hash 121 value.DisplayLabels = make(map[string]string) 122 for _, label := range value.Labels { 123 value.DisplayLabels[label.Name] = label.Value 124 } 125 value.Labels = nil 126 127 summary.PrecisionGauges = append(summary.PrecisionGauges, value) 128 } 129 sort.Slice(summary.PrecisionGauges, func(i, j int) bool { 130 return summary.PrecisionGauges[i].Hash < summary.PrecisionGauges[j].Hash 131 }) 132 133 summary.Counters = formatSamples(interval.Counters) 134 summary.Samples = formatSamples(interval.Samples) 135 136 return summary 137 } 138 139 func formatSamples(source map[string]SampledValue) []SampledValue { 140 output := make([]SampledValue, 0, len(source)) 141 for hash, sample := range source { 142 displayLabels := make(map[string]string) 143 for _, label := range sample.Labels { 144 displayLabels[label.Name] = label.Value 145 } 146 147 output = append(output, SampledValue{ 148 Name: sample.Name, 149 Hash: hash, 150 AggregateSample: sample.AggregateSample, 151 Mean: sample.AggregateSample.Mean(), 152 Stddev: sample.AggregateSample.Stddev(), 153 DisplayLabels: displayLabels, 154 }) 155 } 156 sort.Slice(output, func(i, j int) bool { 157 return output[i].Hash < output[j].Hash 158 }) 159 160 return output 161 } 162 163 type Encoder interface { 164 Encode(interface{}) error 165 } 166 167 // Stream writes metrics using encoder.Encode each time an interval ends. Runs 168 // until the request context is cancelled, or the encoder returns an error. 169 // The caller is responsible for logging any errors from encoder. 170 func (i *InmemSink) Stream(ctx context.Context, encoder Encoder) { 171 interval := i.getInterval() 172 173 for { 174 select { 175 case <-interval.done: 176 summary := newMetricSummaryFromInterval(interval) 177 if err := encoder.Encode(summary); err != nil { 178 return 179 } 180 181 // update interval to the next one 182 interval = i.getInterval() 183 case <-ctx.Done(): 184 return 185 } 186 } 187 }