-
Notifications
You must be signed in to change notification settings - Fork 147
/
Copy pathMemoryDumpHelper.cs
193 lines (159 loc) · 7.38 KB
/
MemoryDumpHelper.cs
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
// <copyright file="MemoryDumpHelper.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Datadog.Trace.TestHelpers
{
public class MemoryDumpHelper
{
private static string _path;
private static IProgress<string> _output;
private static bool _isCrashMonitoringAvailable = true;
public static bool IsAvailable => _path != null;
public static async Task InitializeAsync(IProgress<string> progress)
{
_output = progress;
if (!EnvironmentTools.IsWindows())
{
var dotnetRuntimeFolder = Path.GetDirectoryName(typeof(object).Assembly.Location);
_path = Path.Combine(dotnetRuntimeFolder!, "createdump");
return;
}
if (!EnvironmentTools.IsTestTarget64BitProcess())
{
// We currently have an issue with procdump on x86
_isCrashMonitoringAvailable = false;
}
// We don't know if procdump is available, so download it fresh
const string url = "https://download.sysinternals.com/files/Procdump.zip";
var client = new HttpClient();
var zipFilePath = Path.GetTempFileName();
_output?.Report($"Downloading Procdump to '{zipFilePath}'");
using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
{
using var bodyStream = await response.Content.ReadAsStreamAsync();
using Stream streamToWriteTo = File.Open(zipFilePath, FileMode.Create);
await bodyStream.CopyToAsync(streamToWriteTo);
}
var unpackedDirectory = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(Path.GetTempFileName()));
_output?.Report($"Procdump downloaded. Unpacking to '{unpackedDirectory}'");
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, unpackedDirectory);
var executable = EnvironmentTools.IsTestTarget64BitProcess() ? "procdump64.exe" : "procdump.exe";
_path = Path.Combine(unpackedDirectory, executable);
}
public static Task MonitorCrashes(int pid)
{
if (!EnvironmentTools.IsWindows() || !IsAvailable || !_isCrashMonitoringAvailable)
{
return Task.CompletedTask;
}
if (_path == null)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_ = Task.Factory.StartNew(
() =>
{
var args = $"-ma -accepteula -g -e {pid} {Path.GetTempPath()}";
using var dumpToolProcess = Process.Start(new ProcessStartInfo(_path, args)
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
});
const string procdumpStarted = "Press Ctrl-C to end monitoring without terminating the process.";
void OnDataReceived(string output)
{
if (output == procdumpStarted)
{
tcs.TrySetResult(true);
}
}
using var helper = new ProcessHelper(dumpToolProcess, OnDataReceived);
helper.Drain();
if (helper.StandardOutput.Contains("Dump count reached") || !helper.StandardOutput.Contains("Dump count not reached"))
{
_output.Report($"[dump] procdump for process {pid} exited with code {helper.Process.ExitCode}");
_output.Report($"[dump] Using {_path}");
_output.Report($"[dump][stdout] {helper.StandardOutput}");
_output.Report($"[dump][stderr] {helper.ErrorOutput}");
}
// It looks like there's a small race condition where this could happen before the OnDataReceived callback is called.
// So redo the check before setting the task as cancelled.
if (helper.StandardOutput.Contains(procdumpStarted))
{
tcs.TrySetResult(true);
}
else
{
tcs.TrySetCanceled();
}
},
TaskCreationOptions.LongRunning);
return tcs.Task;
}
public static bool CaptureMemoryDump(Process process, IProgress<string> output = null, bool includeChildProcesses = false)
{
return CaptureMemoryDump(process.Id, output);
}
private static bool CaptureMemoryDump(int pid, IProgress<string> output = null, bool includeChildProcesses = false)
{
if (!IsAvailable)
{
_output?.Report("Memory dumps not enabled");
return false;
}
// children first and then the parent process last
IEnumerable<int> pids = includeChildProcesses ? [..ProcessHelper.GetChildrenIds(pid), pid] : [pid];
var atLeastOneDump = false;
foreach (var cPid in pids)
{
try
{
var args = EnvironmentTools.IsWindows() ? $"-ma -accepteula {cPid} {Path.GetTempPath()}" : cPid.ToString();
atLeastOneDump |= CaptureMemoryDump(args, output ?? _output);
}
catch (Exception ex)
{
_output?.Report("Error taking memory dump: " + ex);
return false;
}
}
return atLeastOneDump;
}
private static bool CaptureMemoryDump(string args, IProgress<string> output)
{
output?.Report($"Capturing memory dump using '{_path} {args}'");
using var dumpToolProcess = Process.Start(new ProcessStartInfo(_path, args)
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
});
using var helper = new ProcessHelper(dumpToolProcess);
dumpToolProcess.WaitForExit(30_000);
helper.Drain();
output?.Report($"[dump][stdout] {helper.StandardOutput}");
output?.Report($"[dump][stderr] {helper.ErrorOutput}");
if (dumpToolProcess.ExitCode == 0)
{
output?.Report($"Memory dump successfully captured using '{_path} {args}'.");
}
else
{
output?.Report($"Failed to capture memory dump using '{_path} {args}'. Exit code was {dumpToolProcess.ExitCode}.");
}
return true;
}
}
}