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