-
Notifications
You must be signed in to change notification settings - Fork 1
/
summary.go
142 lines (117 loc) · 3.06 KB
/
summary.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main
import (
"sort"
"strings"
"sync"
)
var empty struct{}
// StatTracker holds a summary for a single metric. Note that it does NOT handle RW locking.
type StatTracker struct {
count int
// Map of tag name to tag value (to empty struct, such that it is effectively a set of values)
tags map[string]map[string]struct{}
// Map of each unique combination of tags to the number of times that group appeared.
// This is useful for estimating cardinality, as each set of tags corresponds to a unique timeseries
tagSets map[string]int
}
// StatResponse is used to json-encode a StatTracker
type StatResponse struct {
Count int `json:"count"`
Tags map[string][]string `json:"tags"`
TagSets map[string]int `json:"tag_sets"`
}
// NewStatTracker initializes a StatTracker
func NewStatTracker() *StatTracker {
return &StatTracker{
tags: make(map[string]map[string]struct{}),
tagSets: make(map[string]int),
}
}
func (t *StatTracker) add(stat *Stat) {
t.count++
// Sort tags before joining them
sort.Strings(stat.Tags)
t.tagSets[strings.Join(stat.Tags, ",")]++
for _, tag := range stat.Tags {
parts := strings.Split(tag, ":")
if len(parts) != 2 {
log.Warn("failed parsing a tag: unexpected format", "tag", tag)
continue
}
if _, ok := t.tags[parts[0]]; !ok {
t.tags[parts[0]] = make(map[string]struct{})
}
t.tags[parts[0]][parts[1]] = empty
}
}
func (t *StatTracker) get() *StatResponse {
r := &StatResponse{}
if t == nil {
return r
}
r.Count = t.count
r.Tags = make(map[string][]string, len(t.tags))
for tag, vals := range t.tags {
r.Tags[tag] = make([]string, len(vals))
i := 0
for v := range vals {
r.Tags[tag][i] = v
i++
}
sort.Strings(r.Tags[tag])
}
r.TagSets = t.tagSets
return r
}
// ------------------------------------------------------------------------
// Summary manages locking for a map of metric names to StatTrackers
type Summary struct {
metrics map[string]*StatTracker
sync.RWMutex
}
// NewSummary initializes a Summary
func NewSummary() *Summary {
return &Summary{
metrics: make(map[string]*StatTracker),
}
}
// add a single instance of a reported Stat to that metric's Summary
func (s *Summary) add(stat *Stat) {
s.Lock()
defer s.Unlock()
if _, ok := s.metrics[stat.Name]; !ok {
s.metrics[stat.Name] = NewStatTracker()
}
s.metrics[stat.Name].add(stat)
}
// get a StatResponse for a single metric
func (s *Summary) get(name string) *StatResponse {
s.RLock()
defer s.RUnlock()
return s.metrics[name].get()
}
func (s *Summary) getAllCount() map[string]int {
s.RLock()
defer s.RUnlock()
r := make(map[string]int)
for m, tracker := range s.metrics {
r[m] = tracker.count
}
return r
}
// get a map of all metric names to corresponding StatResponses
func (s *Summary) getAllDetails() map[string]*StatResponse {
s.RLock()
defer s.RUnlock()
r := make(map[string]*StatResponse)
for m := range s.metrics {
r[m] = s.get(m)
}
return r
}
// reset clears the tracked metrics
func (s *Summary) reset() {
s.Lock()
defer s.Unlock()
s.metrics = make(map[string]*StatTracker)
}