static void Main(string[] args)
{
if (args.Length < 1)
{
Usage();
return;
}
int samples = 10;
int sampleInterval = 1000;
var state = ParseState.Unknown;
foreach (var arg in args.Skip(1))
{
switch (state)
{
case ParseState.Unknown:
if (arg.ToLower() == "/s")
{
state = ParseState.Samples;
}
else if (arg.ToLower() == "/i")
{
state = ParseState.Interval;
}
else
{
Usage();
return;
}
break;
case ParseState.Samples:
if (!Int32.TryParse(arg, out samples))
{
Usage();
return;
}
state = ParseState.Unknown;
break;
case ParseState.Interval:
if (!Int32.TryParse(arg, out sampleInterval))
{
Usage();
return;
}
state = ParseState.Unknown;
break;
default:
break;
}
}
string pidOrProcess = args[0];
var stats = new Dictionary <int, List <ThreadSnapshot> >();
var debugger = new MDbgEngine();
int pid = -1;
var processes = Process.GetProcessesByName(pidOrProcess);
if (processes.Length < 1)
{
try {
pid = Int32.Parse(pidOrProcess);
} catch {
Console.WriteLine("Error: could not find any processes with that name or pid");
return;
}
}
else
{
if (processes.Length > 1)
{
Console.WriteLine("Warning: multiple processes share that name, attaching to the first");
}
pid = processes[0].Id;
}
MDbgProcess attached = null;
try {
attached = debugger.Attach(pid);
} catch (Exception e) {
Console.WriteLine("Error: failed to attach to process: " + e);
return;
}
attached.Go().WaitOne();
for (int i = 0; i < samples; i++)
{
foreach (MDbgThread thread in attached.Threads)
{
var snapshot = ThreadSnapshot.GetThreadSnapshot(thread);
List <ThreadSnapshot> snapshots;
if (!stats.TryGetValue(snapshot.Id, out snapshots))
{
snapshots = new List <ThreadSnapshot>();
stats[snapshot.Id] = snapshots;
}
snapshots.Add(snapshot);
}
attached.Go();
Thread.Sleep(sampleInterval);
attached.AsyncStop().WaitOne();
}
attached.Detach().WaitOne();
// perform basic analysis to see which are the top N stack traces observed,
// weighted on cost
Dictionary <Guid, long> costs = new Dictionary <Guid, long>();
Dictionary <Guid, string> stacks = new Dictionary <Guid, string>();
foreach (var stat in stats.Values)
{
long prevTime = -1;
foreach (var snapshot in stat)
{
long time = snapshot.KernelTime + snapshot.UserTime;
if (prevTime != -1)
{
foreach (var tuple in snapshot.StackHashes)
{
if (costs.ContainsKey(tuple.Item1))
{
costs[tuple.Item1] += time - prevTime;
}
else
{
costs[tuple.Item1] = time - prevTime;
stacks[tuple.Item1] = tuple.Item2;
}
}
}
prevTime = time;
}
}
Console.WriteLine("Most expensive stacks");
Console.WriteLine("------------------------------------");
foreach (var group in costs.OrderByDescending(p => p.Value).GroupBy(p => p.Value))
{
List <string> stacksToShow = new List <string>();
foreach (var pair in group.OrderByDescending(p => stacks[p.Key].Length))
{
if (!stacksToShow.Any(s => s.Contains(stacks[pair.Key])))
{
stacksToShow.Add(stacks[pair.Key]);
}
}
foreach (var stack in stacksToShow)
{
Console.WriteLine(stack);
Console.WriteLine("===> Cost ({0})", group.Key);
Console.WriteLine();
}
}
var offenders = stats.Values
.Select(_ => ThreadSnapshotStats.FromSnapshots(_))
.OrderBy(stat => stat.TotalKernelTime + stat.TotalUserTime)
.Reverse();
foreach (var stat in offenders)
{
Console.WriteLine("------------------------------------");
Console.WriteLine(stat.ThreadId);
Console.WriteLine("Kernel: {0} User: {1}", stat.TotalKernelTime, stat.TotalUserTime);
foreach (var method in stat.CommonStack)
{
Console.WriteLine(method);
}
Console.WriteLine("Other Stacks:");
var prev = new List <string>();
foreach (var trace in stats[stat.ThreadId].Select(_ => _.StackTrace))
{
if (!prev.SequenceEqual(trace))
{
Console.WriteLine();
foreach (var method in trace)
{
Console.WriteLine(method);
}
}
else
{
Console.WriteLine("<skipped>");
}
prev = trace;
}
Console.WriteLine("------------------------------------");
}
}