You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

112 lines
2.7 KiB

  1. package stats
  2. import "fmt"
  3. // Type is the type of aggregation of apply
  4. type Type int
  5. const (
  6. AggregateAvg Type = iota
  7. AggregateSum
  8. AggregateHistogram
  9. )
  10. var (
  11. // HistogramPercentiles is used to determine which percentiles to return for
  12. // SimpleCounter.Aggregate
  13. HistogramPercentiles = map[string]float64{
  14. "p50": 0.5,
  15. "p95": 0.95,
  16. "p99": 0.99,
  17. }
  18. // MinSamplesForPercentiles is used by SimpleCounter.Aggregate to determine
  19. // what the minimum number of samples is required for percentile analysis
  20. MinSamplesForPercentiles = 10
  21. )
  22. // Aggregates can be used to merge counters together. This is not goroutine safe
  23. type Aggregates map[string]Counter
  24. // Add adds the counter for aggregation. This is not goroutine safe
  25. func (a Aggregates) Add(c Counter) error {
  26. key := c.FullKey()
  27. if counter, ok := a[key]; ok {
  28. if counter.GetType() != c.GetType() {
  29. return fmt.Errorf("stats: mismatched aggregation type for: %s", key)
  30. }
  31. counter.AddValues(c.GetValues()...)
  32. } else {
  33. a[key] = c
  34. }
  35. return nil
  36. }
  37. // Counter is the interface used by Aggregates to merge counters together
  38. type Counter interface {
  39. // FullKey is used to uniquely identify the counter
  40. FullKey() string
  41. // AddValues adds values for aggregation
  42. AddValues(...float64)
  43. // GetValues returns the values for aggregation
  44. GetValues() []float64
  45. // GetType returns the type of aggregation to apply
  46. GetType() Type
  47. }
  48. // SimpleCounter is a basic implementation of the Counter interface
  49. type SimpleCounter struct {
  50. Key string
  51. Values []float64
  52. Type Type
  53. }
  54. // FullKey is part of the Counter interace
  55. func (s *SimpleCounter) FullKey() string {
  56. return s.Key
  57. }
  58. // GetValues is part of the Counter interface
  59. func (s *SimpleCounter) GetValues() []float64 {
  60. return s.Values
  61. }
  62. // AddValues is part of the Counter interface
  63. func (s *SimpleCounter) AddValues(vs ...float64) {
  64. s.Values = append(s.Values, vs...)
  65. }
  66. // GetType is part of the Counter interface
  67. func (s *SimpleCounter) GetType() Type {
  68. return s.Type
  69. }
  70. // Aggregate aggregates the provided values appropriately, returning a map
  71. // from key to value. If AggregateHistogram is specified, the map will contain
  72. // the relevant percentiles as specified by HistogramPercentiles
  73. func (s *SimpleCounter) Aggregate() map[string]float64 {
  74. switch s.Type {
  75. case AggregateAvg:
  76. return map[string]float64{
  77. s.Key: Average(s.Values),
  78. }
  79. case AggregateSum:
  80. return map[string]float64{
  81. s.Key: Sum(s.Values),
  82. }
  83. case AggregateHistogram:
  84. histogram := map[string]float64{
  85. s.Key: Average(s.Values),
  86. }
  87. if len(s.Values) > MinSamplesForPercentiles {
  88. for k, v := range Percentiles(s.Values, HistogramPercentiles) {
  89. histogram[fmt.Sprintf("%s.%s", s.Key, k)] = v
  90. }
  91. }
  92. return histogram
  93. }
  94. panic("stats: unsupported aggregation type")
  95. }