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