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 _, entryLine := fr.Func.FileLine(fr.Entry)
97 loc = fmt.Sprintf("%s.%s:%+d", path, fname, fr.Line-entryLine)
98 } else {
99
100
101 loc = fmt.Sprintf("%s.%s:=%d", path, fname, fr.Line)
102 }
103 locs = append(locs, loc)
104 if !more {
105 break
106 }
107 }
108
109 name := prefix + "\n" + strings.Join(locs, "\n")
110 if len(name) > maxNameLen {
111 const bad = "\ntruncated\n"
112 name = name[:maxNameLen-len(bad)] + bad
113 }
114 return name
115 }
116
117
118 func DecodeStack(ename string) string {
119 if !strings.Contains(ename, "\n") {
120 return ename
121 }
122 lines := strings.Split(ename, "\n")
123 var lastPath string
124 for i, line := range lines {
125 path, rest := cutLastDot(line)
126 if len(path) == 0 {
127 continue
128 }
129 if len(path) == 1 && path[0] == '"' {
130 lines[i] = lastPath + rest
131 } else {
132 lastPath = path + "."
133
134 }
135 }
136 return strings.Join(lines, "\n")
137 }
138
139
140
141 func cutLastDot(x string) (before, after string) {
142 i := strings.LastIndex(x, ".")
143 if i < 0 {
144 return "", x
145 }
146 return x[:i], x[i+1:]
147 }
148
149
150 func (c *StackCounter) Names() []string {
151 c.mu.Lock()
152 defer c.mu.Unlock()
153 names := make([]string, len(c.stacks))
154 for i, s := range c.stacks {
155 names[i] = s.counter.Name()
156 }
157 return names
158 }
159
160
161
162 func (c *StackCounter) Counters() []*Counter {
163 c.mu.Lock()
164 defer c.mu.Unlock()
165 counters := make([]*Counter, len(c.stacks))
166 for i, s := range c.stacks {
167 counters[i] = s.counter
168 }
169 return counters
170 }
171
172 func eq(a, b []uintptr) bool {
173 if len(a) != len(b) {
174 return false
175 }
176 for i := range a {
177 if a[i] != b[i] {
178 return false
179 }
180 }
181 return true
182 }
183
184
185
186
187 func ReadStack(c *StackCounter) (map[string]uint64, error) {
188 ret := map[string]uint64{}
189 for _, ctr := range c.Counters() {
190 v, err := Read(ctr)
191 if err != nil {
192 return nil, err
193 }
194 ret[DecodeStack(ctr.Name())] = v
195 }
196 return ret, nil
197 }
198
199
200 func IsStackCounter(name string) bool {
201 return strings.Contains(name, "\n")
202 }
203
View as plain text