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  }