1
2
3
4
5 package counter
6
7 import (
8 "fmt"
9 "runtime"
10 "strings"
11 "sync"
12 )
13
14
15
16
17
18
19
20 type StackCounter struct {
21 name string
22 depth int
23 file *file
24
25 mu sync.Mutex
26
27
28 stacks []stack
29 }
30
31 type stack struct {
32 pcs []uintptr
33 counter *Counter
34 }
35
36 func NewStack(name string, depth int) *StackCounter {
37 return &StackCounter{name: name, depth: depth, file: &defaultFile}
38 }
39
40
41
42
43 func (c *StackCounter) Inc() {
44 pcs := make([]uintptr, c.depth)
45 n := runtime.Callers(2, pcs)
46 pcs = pcs[:n]
47
48 c.mu.Lock()
49 defer c.mu.Unlock()
50
51
52 var ctr *Counter
53 for _, s := range c.stacks {
54 if eq(s.pcs, pcs) {
55 if s.counter != nil {
56 ctr = s.counter
57 break
58 }
59 }
60 }
61
62 if ctr == nil {
63
64 ctr = &Counter{
65 name: EncodeStack(pcs, c.name),
66 file: c.file,
67 }
68 c.stacks = append(c.stacks, stack{pcs: pcs, counter: ctr})
69 }
70
71 ctr.Inc()
72 }
73
74
75
76
77 func EncodeStack(pcs []uintptr, prefix string) string {
78 var locs []string
79 lastImport := ""
80 frs := runtime.CallersFrames(pcs)
81 for {
82 fr, more := frs.Next()
83
84
85 path, fname := cutLastDot(fr.Function)
86 if path == lastImport {
87 path = `"`
88 } else {
89 lastImport = path
90 }
91 var loc string
92 if fr.Func != nil {
93
94
95
96
97
98
99 _, entryLine := fr.Func.FileLine(fr.Entry)
100 loc = fmt.Sprintf("%s.%s:%+d,+0x%x", path, fname, fr.Line-entryLine, fr.PC-fr.Entry)
101 } else {
102
103
104
105
106
107
108
109
110
111 loc = fmt.Sprintf("%s.%s:=%d,+0x%x", path, fname, fr.Line, fr.PC-fr.Entry)
112 }
113 locs = append(locs, loc)
114 if !more {
115 break
116 }
117 }
118
119 name := prefix + "\n" + strings.Join(locs, "\n")
120 if len(name) > maxNameLen {
121 const bad = "\ntruncated\n"
122 name = name[:maxNameLen-len(bad)] + bad
123 }
124 return name
125 }
126
127
128 func DecodeStack(ename string) string {
129 if !strings.Contains(ename, "\n") {
130 return ename
131 }
132 lines := strings.Split(ename, "\n")
133 var lastPath string
134 for i, line := range lines {
135 path, rest := cutLastDot(line)
136 if len(path) == 0 {
137 continue
138 }
139 if len(path) == 1 && path[0] == '"' {
140 lines[i] = lastPath + rest
141 } else {
142 lastPath = path + "."
143
144 }
145 }
146 return strings.Join(lines, "\n")
147 }
148
149
150
151 func cutLastDot(x string) (before, after string) {
152 i := strings.LastIndex(x, ".")
153 if i < 0 {
154 return "", x
155 }
156 return x[:i], x[i+1:]
157 }
158
159
160 func (c *StackCounter) Names() []string {
161 c.mu.Lock()
162 defer c.mu.Unlock()
163 names := make([]string, len(c.stacks))
164 for i, s := range c.stacks {
165 names[i] = s.counter.Name()
166 }
167 return names
168 }
169
170
171
172 func (c *StackCounter) Counters() []*Counter {
173 c.mu.Lock()
174 defer c.mu.Unlock()
175 counters := make([]*Counter, len(c.stacks))
176 for i, s := range c.stacks {
177 counters[i] = s.counter
178 }
179 return counters
180 }
181
182 func eq(a, b []uintptr) bool {
183 if len(a) != len(b) {
184 return false
185 }
186 for i := range a {
187 if a[i] != b[i] {
188 return false
189 }
190 }
191 return true
192 }
193
194
195
196
197 func ReadStack(c *StackCounter) (map[string]uint64, error) {
198 ret := map[string]uint64{}
199 for _, ctr := range c.Counters() {
200 v, err := Read(ctr)
201 if err != nil {
202 return nil, err
203 }
204 ret[DecodeStack(ctr.Name())] = v
205 }
206 return ret, nil
207 }
208
209
210 func IsStackCounter(name string) bool {
211 return strings.Contains(name, "\n")
212 }
213
View as plain text