forked from StackExchange/wmi
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmemory_test.go
447 lines (381 loc) · 13.6 KB
/
memory_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
// +build windows
package wmi
import (
"fmt"
"os"
"runtime"
"sync"
"testing"
"time"
"github.com/bi-zone/go-ole"
"github.com/bi-zone/go-ole/oleutil"
"github.com/scjalliance/comshim"
)
const memReps = 50 * 1000
// Run using: `TEST_MEM=1 go test -run TestMemory_Services -timeout 60m`
func TestMemory_Services(t *testing.T) {
if os.Getenv("TEST_MEM") == "" {
t.Skip("Skipping TestMemory_Services; $TEST_MEM is not set")
}
s, err := NewSWbemServices()
if err != nil {
t.Fatalf("InitializeSWbemServices: %s", err)
}
start := time.Now()
fmt.Printf("Benchmark Iterations: %d (Private Memory should stabilize around 7MB after ~3000)\n", memReps)
var privateMB, allocMB, allocTotalMB float64
for i := 0; i < memReps; i++ {
privateMB, allocMB, allocTotalMB = wbemGetMemoryUsageMB(t, s)
if i%100 == 0 {
fmt.Printf("Time: %4ds Count: %5d ", time.Now().Sub(start)/time.Second, i)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
}
errClose := s.Close()
if errClose != nil {
t.Fatalf("Close: %s", err)
}
fmt.Printf("Final Time: %4ds ", time.Now().Sub(start)/time.Second)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
// Run using: `TEST_MEM=1 go test -run TestMemory_WbemConnection -timeout 60m`
func TestMemory_WbemConnection(t *testing.T) {
if os.Getenv("TEST_MEM") == "" {
t.Skip("Skipping TestMemory_WbemConnection; $TEST_MEM is not set")
}
s, err := ConnectSWbemServices()
if err != nil {
t.Fatalf("InitializeSWbemServices: %s", err)
}
start := time.Now()
fmt.Printf("Benchmark Iterations: %d (Private Memory should stabilize around 7MB after ~3000)\n", memReps)
var privateMB, allocMB, allocTotalMB float64
for i := 0; i < memReps; i++ {
privateMB, allocMB, allocTotalMB = wbemConnGetMemoryUsageMB(t, s)
if i%100 == 0 {
fmt.Printf("Time: %4ds Count: %5d ", time.Now().Sub(start)/time.Second, i)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
}
errClose := s.Close()
if errClose != nil {
t.Fatalf("Close: %s", err)
}
fmt.Printf("Final Time: %4ds ", time.Now().Sub(start)/time.Second)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
// Run using: `TEST_MEM=1 go test -run TestMemory_WMISimple -timeout 60m`
func TestMemory_WMISimple(t *testing.T) {
if os.Getenv("TEST_MEM") == "" {
t.Skip("Skipping TestMemory_WMISimple; $TEST_MEM is not set")
}
start := time.Now()
fmt.Printf("Benchmark Iterations: %d (Private Memory should stabilize around 7MB after ~3000)\n", memReps)
var privateMB, allocMB, allocTotalMB float64
for i := 0; i < memReps; i++ {
privateMB, allocMB, allocTotalMB = getMemoryUsageMB(t)
if i%1000 == 0 {
fmt.Printf("Time: %4ds Count: %5d ", time.Now().Sub(start)/time.Second, i)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
}
fmt.Printf("Final Time: %4ds ", time.Now().Sub(start)/time.Second)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
// Run using: `TEST_MEM=1 go test -run TestMemory_WMIConcurrent -timeout 60m`
func TestMemory_WMIConcurrent(t *testing.T) {
if os.Getenv("TEST_MEM") == "" {
t.Skip("Skipping TestMemory_WMIConcurrent; $TEST_MEM is not set")
}
fmt.Println("Total Iterations:", memReps)
fmt.Println("No panics mean it succeeded. Other errors are OK. Private Memory should stabilize after ~1500 iterations.")
runtime.GOMAXPROCS(2)
start := time.Now()
var wg sync.WaitGroup
wg.Add(2)
go func() {
for i := 0; i < memReps; i++ {
if i%500 == 0 {
privateMB, allocMB, allocTotalMB := getMemoryUsageMB(t)
fmt.Printf("Time: %4ds Count: %5d ", time.Now().Sub(start)/time.Second, i)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
var dst []Win32_PerfRawData_PerfDisk_LogicalDisk
q := CreateQuery(&dst, "")
err := Query(q, &dst)
if err != nil {
fmt.Println("ERROR disk", err)
}
}
wg.Done()
}()
go func() {
for i := 0; i > -memReps; i-- {
var dst []Win32_OperatingSystem
q := CreateQuery(&dst, "")
err := Query(q, &dst)
if err != nil {
fmt.Println("ERROR OS", err)
}
}
wg.Done()
}()
wg.Wait()
fmt.Printf("Final Time: %4ds\n", time.Now().Sub(start)/time.Second)
}
// In that test case we are going to ensure that large amount of timeout
// exceptions won't leak a memory (as it does in github.com/go-ole/[email protected]
// and below).
//
// Run using: `TEST_MEM=1 go test -run TestMemory_OLEErrors -timeout 60m`
func TestMemory_OLEErrors(t *testing.T) {
if os.Getenv("TEST_MEM") == "" {
t.Skip("Skipping TestMemory_OLEErrors; $TEST_MEM is not set")
}
// Subscribe to some rare event. E.g. removal of the local drive.
const query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType=3"
// We don't care about events, so listen to chan of nothing.
events := make(chan struct{})
q, err := NewNotificationQuery(events, query)
if err != nil {
t.Fatalf("Failed to create NotificationQuery; %s", err)
}
// Set some really small notification timeout so we will get a lot of timeouts.
q.SetNotificationTimeout(time.Millisecond)
// Start listening notifications. Blocks until stop.
done := make(chan error, 1)
go func() {
done <- q.StartNotifications()
}()
go func() {
t.Log("Listening for events")
for range events { // Do nothing.
}
}()
const sleep = 30 * time.Second
const N = (50 * time.Minute) / sleep
fmt.Printf("Benchmark Iterations: %d (Private Memory should stabilize around 7MB after ~5min)\n", N)
start := time.Now()
for i := 0; i < int(N); i++ {
time.Sleep(sleep)
privateMB, allocMB, allocTotalMB := getMemoryUsageMB(t)
fmt.Printf("Time: %4ds ", time.Now().Sub(start)/time.Second)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
q.Stop()
close(events)
if err := <-done; err != nil {
t.Errorf("Error listening to notifications query: %s", err)
}
}
// Run using: `TEST_MEM=1 go test -run TestMemory_Dereference -timeout 60m`
func TestMemory_Dereference(t *testing.T) {
if os.Getenv("TEST_MEM") == "" {
t.Skip("Skipping TestMemory_Dereference; $TEST_MEM is not set")
}
type netAdapter struct {
Adapter struct {
MACAddress string
} `wmi:"Element,ref"`
Settings struct {
IPEnabled bool
DHCPEnabled bool
} `wmi:"Setting,ref"`
}
var dumbRes []netAdapter
testQueryMem(t, &dumbRes,
CreateQueryFrom(netAdapter{}, "Win32_NetworkAdapterSetting ", ""))
}
// Run using: `TEST_MEM=1 go test -run TestMemory_OLEArrays -timeout 60m`
func TestMemory_OLEArrays(t *testing.T) {
if os.Getenv("TEST_MEM") == "" {
t.Skip("Skipping TestMemory_OLEArrays; $TEST_MEM is not set")
}
// Actually we could use any class and use in system fields, but Win32_BIOS
// already has arrays inside, so it's a free COMBO!
type miniBIOS struct {
Derivation_ []string // System-property. Available for any class.
BIOSVersion []string
BiosCharacteristics []uint16
}
var dumbRes []miniBIOS
testQueryMem(t, &dumbRes,
// Write a query manually cos we shouldn't specify a system field in WQL string.
"SELECT * FROM Win32_BIOS")
}
// testQueryMem tests memory usage performing repeating WMI queries for a long time.
func testQueryMem(t *testing.T, dst interface{}, query string) {
s, err := ConnectSWbemServices()
if err != nil {
t.Fatalf("ConnectSWbemServices: %s", err)
}
start := time.Now()
fmt.Printf("Benchmark Iterations: %d (Private Memory should stabilize around 7MB after ~3000)\n", memReps)
var privateMB, allocMB, allocTotalMB float64
for i := 0; i < memReps; i++ {
if err := s.Query(query, dst); err != nil {
t.Fatalf("Failed to perform query %q; %s", query, err)
}
if i%100 == 0 {
privateMB, allocMB, allocTotalMB = wbemConnGetMemoryUsageMB(t, s)
fmt.Printf("Time: %4ds Count: %5d ", time.Now().Sub(start)/time.Second, i)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
}
if errClose := s.Close(); errClose != nil {
t.Fatalf("Close: %s", errClose)
}
fmt.Printf("Final Time: %4ds ", time.Now().Sub(start)/time.Second)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
var refcount1 int32
var refcount2 int32
// Test function showing memory leak in unknown.QueryInterface call on Server2016/Windows10
func getRSS(url string, xmlhttp *ole.IDispatch, MinimalTest bool) (int, error) {
// call using url,nil to see memory leak
if xmlhttp == nil {
//Initialize inside loop if not passed in from outer section
comshim.Add(1)
defer comshim.Done()
//fmt.Println("CreateObject Microsoft.XMLHTTP")
unknown, err := oleutil.CreateObject("Microsoft.XMLHTTP")
if err != nil {
return 0, err
}
defer func() { refcount1 += xmlhttp.Release() }()
//Memory leak occurs here
xmlhttp, err = unknown.QueryInterface(ole.IID_IDispatch)
if err != nil {
return 0, err
}
defer func() { refcount2 += xmlhttp.Release() }()
//Nothing below this really matters. Can be removed if you want a tighter loop
}
//fmt.Printf("Download %s\n", url)
openRaw, err := oleutil.CallMethod(xmlhttp, "open", "GET", url, false)
if err != nil {
return 0, err
}
defer openRaw.Clear()
if MinimalTest {
return 1, nil
}
//Initiate http request
sendRaw, err := oleutil.CallMethod(xmlhttp, "send", nil)
if err != nil {
return 0, err
}
defer sendRaw.Clear()
state := -1 // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
for state != 4 {
time.Sleep(5 * time.Millisecond)
stateRaw := oleutil.MustGetProperty(xmlhttp, "readyState")
state = int(stateRaw.Val)
stateRaw.Clear()
}
responseXMLRaw := oleutil.MustGetProperty(xmlhttp, "responseXml")
responseXML := responseXMLRaw.ToIDispatch()
defer responseXMLRaw.Clear()
itemsRaw := oleutil.MustCallMethod(responseXML, "selectNodes", "/rdf:RDF/item")
items := itemsRaw.ToIDispatch()
defer itemsRaw.Clear()
lengthRaw := oleutil.MustGetProperty(items, "length")
defer lengthRaw.Clear()
length := int(lengthRaw.Val)
return length, nil
}
// Testing go-ole/oleutil
// Run using: `TEST_MEM=1 go test -run TestMemoryOLE -timeout 60m`
// Code from https://github.com/go-ole/go-ole/blob/master/example/msxml/rssreader.go
func TestMemoryOLE(t *testing.T) {
if os.Getenv("TEST_MEM") == "" {
t.Skip("Skipping TestMemoryOLE; $TEST_MEM is not set")
}
defer func() {
if r := recover(); r != nil {
t.Error(r)
}
}()
start := time.Now()
limit := 50000000
url := "http://localhost/slashdot.xml" //http://rss.slashdot.org/Slashdot/slashdot"
fmt.Printf("Benchmark Iterations: %d (Private Memory should stabilize around 8MB to 12MB after ~2k full or 250k minimal)\n", limit)
//On Server 2016 or Windows 10 changing leakMemory=true will cause it to leak ~1.5MB per 10000 calls to unknown.QueryInterface
leakMemory := true
////////////////////////////////////////
//Start outer section
var unknown *ole.IUnknown
var xmlhttp *ole.IDispatch
if !leakMemory {
comshim.Add(1)
defer comshim.Done()
//fmt.Println("CreateObject Microsoft.XMLHTTP")
var err error
unknown, err = oleutil.CreateObject("Microsoft.XMLHTTP")
if err != nil {
t.Fatal(err)
}
defer unknown.Release()
//Memory leak starts here
xmlhttp, err = unknown.QueryInterface(ole.IID_IDispatch)
if err != nil {
t.Fatal(err)
}
defer xmlhttp.Release()
}
//End outer section
////////////////////////////////////////
totalItems := uint64(0)
for i := 0; i < limit; i++ {
if i%2000 == 0 {
privateMB, allocMB, allocTotalMB := getMemoryUsageMB(t)
fmt.Printf("Time: %4ds Count: %7d ", time.Now().Sub(start)/time.Second, i)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
//This should use less than 10MB for 1 million iterations if xmlhttp was initialized above
//On Server 2016 or Windows 10 changing leakMemory=true above will cause it to leak ~1.5MB per 10000 calls to unknown.QueryInterface
count, err := getRSS(url, xmlhttp, true) //last argument is for Minimal test. Doesn't effect leak just overall allocations/time
if err != nil {
t.Fatal(err)
}
totalItems += uint64(count)
}
privateMB, allocMB, allocTotalMB := getMemoryUsageMB(t)
fmt.Printf("Final totalItems: %d ", totalItems)
printlnMemUsage(privateMB, allocMB, allocTotalMB)
}
const MB = 1024 * 1024
var (
mMemoryUsageMB runtime.MemStats
errGetMemoryUsageMB error
dstGetMemoryUsageMB []Win32_PerfRawData_PerfProc_Process
filterProcessID = fmt.Sprintf("WHERE IDProcess = %d", os.Getpid())
qGetMemoryUsageMB = CreateQuery(&dstGetMemoryUsageMB, filterProcessID)
)
func getMemoryUsageMB(t *testing.T) (float64, float64, float64) {
runtime.ReadMemStats(&mMemoryUsageMB)
errGetMemoryUsageMB = Query(qGetMemoryUsageMB, &dstGetMemoryUsageMB)
if errGetMemoryUsageMB != nil {
t.Fatalf("ERROR GetMemoryUsage; %s", errGetMemoryUsageMB)
}
return float64(dstGetMemoryUsageMB[0].WorkingSetPrivate) / MB, float64(mMemoryUsageMB.Alloc) / MB, float64(mMemoryUsageMB.TotalAlloc) / MB
}
func wbemGetMemoryUsageMB(t *testing.T, s *SWbemServices) (float64, float64, float64) {
runtime.ReadMemStats(&mMemoryUsageMB)
errGetMemoryUsageMB = s.Query(qGetMemoryUsageMB, &dstGetMemoryUsageMB)
if errGetMemoryUsageMB != nil {
t.Fatalf("ERROR GetMemoryUsage; %s", errGetMemoryUsageMB)
}
return float64(dstGetMemoryUsageMB[0].WorkingSetPrivate) / MB, float64(mMemoryUsageMB.Alloc) / MB, float64(mMemoryUsageMB.TotalAlloc) / MB
}
func wbemConnGetMemoryUsageMB(t *testing.T, s *SWbemServicesConnection) (float64, float64, float64) {
runtime.ReadMemStats(&mMemoryUsageMB)
errGetMemoryUsageMB = s.Query(qGetMemoryUsageMB, &dstGetMemoryUsageMB)
if errGetMemoryUsageMB != nil {
t.Fatalf("ERROR GetMemoryUsage; %s", errGetMemoryUsageMB)
}
return float64(dstGetMemoryUsageMB[0].WorkingSetPrivate) / MB, float64(mMemoryUsageMB.Alloc) / MB, float64(mMemoryUsageMB.TotalAlloc) / MB
}
func printlnMemUsage(privateMB, allocMB, allocTotalMB float64) {
fmt.Printf("Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB\n", privateMB, allocMB, allocTotalMB)
}