forked from lostz/percona-agent
/
stats.go
115 lines (102 loc) · 2.86 KB
/
stats.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*
Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package mm
import (
"errors"
"log"
"sort"
)
type Stats struct {
metricType string `json:"-"` // ignore
str string `json:",omitempty"`
firstVal bool `json:"-"`
prevTs int64 `json:"-"`
prevVal float64 `json:"-"`
vals []float64 `json:"-"`
sum float64 `json:"-"`
Cnt int
Min float64
Pct5 float64
Avg float64
Med float64
Pct95 float64
Max float64
}
func NewStats(metricType string) (*Stats, error) {
if !MetricTypes[metricType] {
return nil, errors.New("Invalid metric type: " + metricType)
}
s := &Stats{
metricType: metricType,
vals: []float64{},
firstVal: true,
}
return s, nil
}
func (s *Stats) Add(m *Metric, ts int64) {
switch s.metricType {
case "gauge":
s.vals = append(s.vals, m.Number)
s.sum += m.Number
case "counter":
if !s.firstVal {
if m.Number >= s.prevVal {
// Metric value increased (or stayed same); this is what we expect.
// Per-second rate of value = increase / duration
inc := m.Number - s.prevVal
dur := ts - s.prevTs
val := inc / float64(dur)
s.vals = append(s.vals, val)
// Keep running total to calc Avg.
s.sum += inc
// Current values become previous values.
s.prevTs = ts
s.prevVal = m.Number
} else {
// Metric value reset, e.g. FLUSH GLOBAL STATUS.
s.prevTs = ts
s.prevVal = m.Number
}
} else {
s.prevTs = ts
s.prevVal = m.Number
s.firstVal = false
}
default:
// This should not happen because type is checked in NewStats().
log.Panic("mm:Aggregator:Add: Invalid metric type: " + s.metricType)
}
}
func (s *Stats) Summarize() {
switch s.metricType {
case "gauge", "counter":
s.Cnt = len(s.vals)
if s.Cnt > 1 {
sort.Float64s(s.vals)
s.Min = s.vals[0]
s.Pct5 = s.vals[(5*s.Cnt)/100]
s.Avg = s.sum / float64(s.Cnt)
s.Med = s.vals[(50*s.Cnt)/100] // median = 50th percentile
s.Pct95 = s.vals[(95*s.Cnt)/100]
s.Max = s.vals[s.Cnt-1]
} else if s.Cnt == 1 {
s.Min = s.vals[0]
s.Pct5 = s.vals[0]
s.Avg = s.vals[0]
s.Med = s.vals[0]
s.Pct95 = s.vals[0]
s.Max = s.vals[0]
}
}
}