Source file
test/heapsampling.go
1
2
3
4
5
6
7
8
9 package main
10
11 import (
12 "fmt"
13 "math"
14 "runtime"
15 )
16
17 var a16 *[16]byte
18 var a512 *[512]byte
19 var a256 *[256]byte
20 var a1k *[1024]byte
21 var a16k *[16 * 1024]byte
22 var a17k *[17 * 1024]byte
23 var a18k *[18 * 1024]byte
24
25
26
27
28
29 func main() {
30
31 runtime.MemProfileRate = 16 * 1024
32
33 if err := testInterleavedAllocations(); err != nil {
34 panic(err.Error())
35 }
36 if err := testSmallAllocations(); err != nil {
37 panic(err.Error())
38 }
39 }
40
41
42
43
44
45
46
47 func testInterleavedAllocations() error {
48 const iters = 50000
49
50 frames := []string{"main.allocInterleaved1", "main.allocInterleaved2", "main.allocInterleaved3"}
51
52
53
54 allocInterleaved1(iters)
55 if checkAllocations(getMemProfileRecords(), frames[0:1], iters, allocInterleavedSizes) == nil {
56
57 return nil
58 }
59 allocInterleaved2(iters)
60 if checkAllocations(getMemProfileRecords(), frames[0:2], iters, allocInterleavedSizes) == nil {
61
62 return nil
63 }
64 allocInterleaved3(iters)
65
66 return checkAllocations(getMemProfileRecords(), frames[0:3], iters, allocInterleavedSizes)
67 }
68
69 var allocInterleavedSizes = []int64{17 * 1024, 1024, 18 * 1024, 512, 16 * 1024, 256}
70
71
72 func allocInterleaved(n int) {
73 for i := 0; i < n; i++ {
74
75 a17k = new([17 * 1024]byte)
76 a1k = new([1024]byte)
77 a18k = new([18 * 1024]byte)
78 a512 = new([512]byte)
79 a16k = new([16 * 1024]byte)
80 a256 = new([256]byte)
81
82
83
84 runtime.Gosched()
85 }
86 }
87
88 func allocInterleaved1(n int) {
89 allocInterleaved(n)
90 }
91
92 func allocInterleaved2(n int) {
93 allocInterleaved(n)
94 }
95
96 func allocInterleaved3(n int) {
97 allocInterleaved(n)
98 }
99
100
101
102
103
104
105
106 func testSmallAllocations() error {
107 const iters = 50000
108
109 sizes := []int64{1024, 512, 256}
110 frames := []string{"main.allocSmall1", "main.allocSmall2", "main.allocSmall3"}
111
112
113
114 allocSmall1(iters)
115 if checkAllocations(getMemProfileRecords(), frames[0:1], iters, sizes) == nil {
116
117 return nil
118 }
119 allocSmall2(iters)
120 if checkAllocations(getMemProfileRecords(), frames[0:2], iters, sizes) == nil {
121
122 return nil
123 }
124 allocSmall3(iters)
125
126 return checkAllocations(getMemProfileRecords(), frames[0:3], iters, sizes)
127 }
128
129
130 func allocSmall(n int) {
131 for i := 0; i < n; i++ {
132
133 a1k = new([1024]byte)
134 a512 = new([512]byte)
135 a256 = new([256]byte)
136
137
138 runtime.Gosched()
139 }
140 }
141
142
143
144 func allocSmall1(n int) {
145 allocSmall(n)
146 }
147
148 func allocSmall2(n int) {
149 allocSmall(n)
150 }
151
152 func allocSmall3(n int) {
153 allocSmall(n)
154 }
155
156
157
158
159
160
161
162
163
164 func checkAllocations(records []runtime.MemProfileRecord, frames []string, count int64, size []int64) error {
165 objectsPerLine := map[int][]int64{}
166 bytesPerLine := map[int][]int64{}
167 totalCount := []int64{}
168
169
170 var firstLine int
171 for ln := range allocObjects(records, frames[0]) {
172 if firstLine == 0 || firstLine > ln {
173 firstLine = ln
174 }
175 }
176 for _, frame := range frames {
177 var objectCount int64
178 a := allocObjects(records, frame)
179 for s := range size {
180
181 ln := firstLine + s
182 objectsPerLine[ln] = append(objectsPerLine[ln], a[ln].objects)
183 bytesPerLine[ln] = append(bytesPerLine[ln], a[ln].bytes)
184 objectCount += a[ln].objects
185 }
186 totalCount = append(totalCount, objectCount)
187 }
188 for i, w := range size {
189 ln := firstLine + i
190 if err := checkValue(frames[0], ln, "objects", count, objectsPerLine[ln]); err != nil {
191 return err
192 }
193 if err := checkValue(frames[0], ln, "bytes", count*w, bytesPerLine[ln]); err != nil {
194 return err
195 }
196 }
197 return checkValue(frames[0], 0, "total", count*int64(len(size)), totalCount)
198 }
199
200
201
202
203
204 func checkValue(fname string, ln int, testName string, want int64, got []int64) error {
205 if got == nil {
206 return fmt.Errorf("Unexpected empty result")
207 }
208 min, max := got[0], got[0]
209 for _, g := range got[1:] {
210 if g < min {
211 min = g
212 }
213 if g > max {
214 max = g
215 }
216 }
217 margin := want / 10
218 if min > want+margin || max < want-margin {
219 return fmt.Errorf("%s:%d want %s in [%d: %d], got %v", fname, ln, testName, want-margin, want+margin, got)
220 }
221 return nil
222 }
223
224 func getMemProfileRecords() []runtime.MemProfileRecord {
225
226
227
228 runtime.GC()
229 runtime.GC()
230
231
232
233
234
235
236
237 var p []runtime.MemProfileRecord
238 n, ok := runtime.MemProfile(nil, true)
239 for {
240
241
242
243 p = make([]runtime.MemProfileRecord, n+50)
244 n, ok = runtime.MemProfile(p, true)
245 if ok {
246 p = p[0:n]
247 break
248 }
249
250 }
251 return p
252 }
253
254 type allocStat struct {
255 bytes, objects int64
256 }
257
258
259
260
261 func allocObjects(records []runtime.MemProfileRecord, function string) map[int]allocStat {
262 a := make(map[int]allocStat)
263 for _, r := range records {
264 var pcs []uintptr
265 for _, s := range r.Stack0 {
266 if s == 0 {
267 break
268 }
269 pcs = append(pcs, s)
270 }
271 frames := runtime.CallersFrames(pcs)
272 line := 0
273 for {
274 frame, more := frames.Next()
275 name := frame.Function
276 if line == 0 {
277 line = frame.Line
278 }
279 if name == function {
280 allocStat := a[line]
281 allocStat.bytes += r.AllocBytes
282 allocStat.objects += r.AllocObjects
283 a[line] = allocStat
284 }
285 if !more {
286 break
287 }
288 }
289 }
290 for line, stats := range a {
291 objects, bytes := scaleHeapSample(stats.objects, stats.bytes, int64(runtime.MemProfileRate))
292 a[line] = allocStat{bytes, objects}
293 }
294 return a
295 }
296
297
298
299 func scaleHeapSample(count, size, rate int64) (int64, int64) {
300 if count == 0 || size == 0 {
301 return 0, 0
302 }
303
304 if rate <= 1 {
305
306
307 return count, size
308 }
309
310 avgSize := float64(size) / float64(count)
311 scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
312
313 return int64(float64(count) * scale), int64(float64(size) * scale)
314 }
315
View as plain text