-
Notifications
You must be signed in to change notification settings - Fork 17
/
Leaky.ps1
126 lines (86 loc) · 3.92 KB
/
Leaky.ps1
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
<#
Leak working set memory. For demonstration purposes only. Do not use in a production environment. Just don't. Ever.
Guy Leech, 2018
#>
<#
.SYNOPSIS
Allocate working set memory to simulate a process that leaks memory.
.DESCRIPTION
Run with -verbose to show progress messages
.PARAMETER memoryChunk
Size of the memory chunk to allocate on each loop iteration
.PARAMETER frequency
How often in seconds to allocate the memory chunk
.PARAMETER duration
How long in seconds to run for
.PARAMETER waitToQuit
Require <enter> key to be pressed before the script finishes
.PARAMETER dontFree
Do not free any of the allocated memory. The parent PowerShell process will still be consuming the leaked memory
.EXAMPLE
.\leaky.ps1 -memoryChunk 250MB -frequency 10 -duration 60 -verbose
Allocate 250MB of extra working set every 10 seconds for 60 seconds in total, showing verbose information, and then exit, freeing the "leaked" memory first
.EXAMPLE
.\leaky.ps1 -memoryChunk 100MB -frequency 15 -duraton 120 -verbose -waitToQuit
Allocate 100MB of extra working set every 15 seconds for 120 seconds in total, showing verbose information, and then exit, freeing the "leaked" memory first, when <Enter> has been pressed
.NOTES
Since it is the parent powershell.exe process that is consuming the memory, if the -dontFree option is used, that PowerShell process will still be consuming the total amount of leaked working set until it is exited
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[long]$memoryChunk ,
[int]$frequency = 30 ,
[int]$duration = 300 ,
[switch]$waitToQuit ,
[switch]$dontFree
)
Add-Type @'
using System;
using System.Runtime.InteropServices;
namespace PInvoke.Win32
{
public static class Memory
{
[DllImport("msvcrt.dll", SetLastError=true)]
public static extern IntPtr malloc( int dwBytes );
[DllImport("msvcrt.dll", SetLastError=true)]
public static extern void free( IntPtr memBlock );
[DllImport("ntoskrnl.exe", SetLastError=true)]
public static extern void RtlZeroMemory( IntPtr destination , int length );
}
}
'@
$memories = New-Object -TypeName System.Collections.ArrayList
[long]$allocated = 0
$timer = [Diagnostics.Stopwatch]::StartNew()
Write-Verbose "$(Get-Date) : script started, working set initially $([math]::Round( (Get-Process -Id $pid).WorkingSet64 / 1MB ))MB for process $pid "
While( $timer.Elapsed.TotalSeconds -le $duration )
{
[long]$memory = [PInvoke.Win32.Memory]::malloc( $memoryChunk ) ; $LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()
if( $memory )
{
[PInvoke.Win32.Memory]::RtlZeroMemory( $memory , $memoryChunk ) ## Need to use memory in order for it to actually get added to the working set
$null = $memories.Add( $memory ) ## save the pointer lest we actually decide to free it later
$allocated += $memoryChunk
}
else
{
Write-Error "$(Get-Date) : Failed to allocate $($memoryChunk / 1MB)MB - $LastError"
}
Write-Verbose "$(Get-Date) : total allocated $($allocated / 1MB)MB , working set now $([math]::Round( (Get-Process -Id $pid).WorkingSet64 / 1MB ))MB for process $pid - sleeping for $frequency seconds ..."
Start-Sleep -Seconds $frequency
}
$timer.Stop()
if( $waitToQuit )
{
$null = Read-Host "$(Get-Date) : hit <Enter> to exit "
}
if( ! $dontFree )
{
## We weren't really leaking!
Write-Verbose "Freeing $($memories.Count) allocations of $($memoryChunk / 1MB)MB each"
$memories | ForEach-Object { [PInvoke.Win32.Memory]::free( $_ ) }
}
Write-Verbose "$(Get-Date) : script finished, working set now $([math]::Round( (Get-Process -Id $pid).WorkingSet64 / 1MB ))MB, peak $([math]::Round( (Get-Process -Id $pid).PeakWorkingSet64 / 1MB ))MB for process $pid "