-
Notifications
You must be signed in to change notification settings - Fork 2
/
Program.cs
192 lines (172 loc) · 8.44 KB
/
Program.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
using Microsoft.Diagnostics.Tracing;
using System.IO;
using System.CommandLine;
using System;
using Microsoft.Diagnostics.NETCore.Client;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Runtime.InteropServices;
#nullable enable
namespace MonoGCDump
{
class Program
{
static int Main(string[] args)
{
var inputFileNameArgument = new Argument<string>("input-filename", "The path to a nettrace file to be converted.");
var outputFileNameOption = new Option<string>(new[] { "-o", "--output" }, description: "Output filename.");
var processIdOption = new Option<int?>(new[] { "-p", "--process-id" }, "The process id to collect the gcdump from.");
var diagnosticPortOption = new Option<string?>(new[] { "--diagnostic-port" }, "The path to a diagnostic port to be used.");
var interactiveOption = new Option<bool?>(new[] { "--interactive" }, "Run in interactive mode to collect more GC dumps.");
var collectCommand = new Command("collect", "Collects a diagnostic trace from a currently running process")
{
processIdOption,
diagnosticPortOption,
outputFileNameOption,
interactiveOption
};
collectCommand.SetHandler(HandleCollect, processIdOption, diagnosticPortOption, outputFileNameOption, interactiveOption);
var convertCommand = new Command("convert", "Converts existing nettrace file into gcdump file")
{
inputFileNameArgument,
outputFileNameOption
};
convertCommand.SetHandler(HandleConvert, inputFileNameArgument, outputFileNameOption);
return new RootCommand { convertCommand, collectCommand }.Invoke(args);
}
static async Task HandleConvert(string inputFileName, string? outputFileName)
{
outputFileName ??= Path.ChangeExtension(inputFileName, "gcdump");
var source = new EventPipeEventSource(inputFileName);
var memoryGraph = await MonoMemoryGraphBuilder.Build(source);
GCHeapDump.WriteMemoryGraph(memoryGraph, outputFileName, "Mono");
Console.WriteLine($"Converted {inputFileName} to {outputFileName}");
}
static async Task HandleCollect(int? processId, string? diagnosticPort, string? outputFileName, bool? interactive)
{
if (processId is null && diagnosticPort is null)
{
Console.WriteLine("Either a process id or a diagnostic port must be specified.");
return;
}
outputFileName ??= DateTime.UtcNow.ToString("yyyyMMdd'_'HHssmm'.gcdump'");
DiagnosticsClient diagnosticsClient;
if (processId is not null)
{
diagnosticsClient = new DiagnosticsClient(processId.Value);
}
else
{
if (!IpcEndpointConfig.TryParse(diagnosticPort, out var config))
{
Console.WriteLine("Invalid diagnostic port.");
return;
}
if (config.IsConnectConfig)
{
diagnosticsClient = new DiagnosticsClient(config);
}
else
{
string fullPort = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? config.Address : Path.GetFullPath(config.Address);
ReversedDiagnosticsServer server = new(fullPort);
server.Start();
Console.WriteLine($"Waiting for connection on {fullPort}");
Console.WriteLine($"Start an application with the following environment variable: DOTNET_DiagnosticPorts={fullPort}");
IpcEndpointInfo endpointInfo = await server.AcceptAsync(default).ConfigureAwait(false);
diagnosticsClient = new DiagnosticsClient(endpointInfo.Endpoint);
}
}
if (interactive == true)
{
// In interactive mode we run a logging event session to precisely track
// GC root registations and unregistrations. We also resume the process if
// it was started in suspended mode.
int outputFileNameCounter = 1;
using var eventPipeLogSession = diagnosticsClient.StartEventPipeSession(
new EventPipeProvider(
"Microsoft-DotNETRuntimeMonoProfiler",
System.Diagnostics.Tracing.EventLevel.Informational,
0x4000000),
requestRundown: false,
circularBufferMB: 1024);
using var source = new EventPipeEventSource(eventPipeLogSession.EventStream);
var monoProfilerEventParser = new MonoProfilerTraceEventParser(source);
var rootRangeTracker = new MonoGCRootRangeTracker();
rootRangeTracker.Attach(monoProfilerEventParser);
await diagnosticsClient.ResumeRuntimeAsync(default);
var eventProcessTask = Task.Run(() => source.Process());
var readKeyTask = Task.Run(() => Console.ReadKey(true));
Console.WriteLine("Press [Escape] to quit, or [d] to collect GC heap dump");
while (true)
{
Task completedTask = await Task.WhenAny(eventProcessTask, readKeyTask);
if (completedTask == eventProcessTask)
{
break;
}
else
{
if (readKeyTask.Result.Key == ConsoleKey.Escape)
{
Console.WriteLine("Quitting");
await eventPipeLogSession.StopAsync(default);
}
else if (readKeyTask.Result.Key == ConsoleKey.D)
{
string outputFileName1 = $"{Path.GetDirectoryName(outputFileName)}{Path.GetFileNameWithoutExtension(outputFileName)}_{outputFileNameCounter}{Path.GetExtension(outputFileName)}";
Console.WriteLine($"Dumping GC heap to file {outputFileName1}");
try
{
await CollectDump(diagnosticsClient, outputFileName1, rootRangeTracker);
outputFileNameCounter++;
}
catch (Exception e)
{
Console.WriteLine($"Collecting dump failed: {e}");
}
}
readKeyTask = Task.Run(() => Console.ReadKey(true));
}
}
rootRangeTracker.Detach(monoProfilerEventParser);
}
else
{
// One-shot live GC dump
await CollectDump(diagnosticsClient, outputFileName);
}
}
private static async Task CollectDump(
DiagnosticsClient diagnosticsClient,
string outputFileName,
MonoGCRootRangeTracker? rootRangeTracker = null)
{
using var eventPipeSession = diagnosticsClient.StartEventPipeSession(
new EventPipeProvider(
"Microsoft-DotNETRuntimeMonoProfiler",
System.Diagnostics.Tracing.EventLevel.Informational,
0xC900003,
new Dictionary<string, string>
{
{ "heapcollect", "ondemand" }
}),
requestRundown: true,
circularBufferMB: 1024);
using var source = new EventPipeEventSource(eventPipeSession.EventStream);
var gcDumpFinished = new TaskCompletionSource();
var buildTask = MonoMemoryGraphBuilder.Build(source, rootRangeTracker, () => { gcDumpFinished.SetResult(); });
try
{
await Task.WhenAny(gcDumpFinished.Task, buildTask);
}
finally
{
await eventPipeSession.StopAsync(default);
}
var memoryGraph = await buildTask;
GCHeapDump.WriteMemoryGraph(memoryGraph, outputFileName, "Mono");
}
}
}