Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/ProfileTool/MemoryProfileResultFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Linq;
using static Asynkron.Profiler.CallTreeHelpers;

namespace Asynkron.Profiler;

internal static class MemoryProfileResultFactory
{
public static MemoryProfileResult Build(AllocationCallTreeResult callTree)
{
var allocationEntries = callTree.TypeRoots
.OrderByDescending(root => root.TotalBytes)
.Take(50)
.Select(root => new AllocationEntry(root.Name, root.Count, FormatBytes(root.TotalBytes)))
.ToList();

var totalAllocated = FormatBytes(callTree.TotalBytes);

return new MemoryProfileResult(
null,
null,
null,
totalAllocated,
null,
null,
null,
null,
null,
null,
null,
null,
totalAllocated,
allocationEntries,
callTree,
null,
null);
}
}
2 changes: 1 addition & 1 deletion src/ProfileTool/ProfileCollectionRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public ProfileCollectionRunner(
},
_profileInputLoader.AnalyzeAllocationTrace);

return callTree == null ? null : ProfileInputLoader.BuildMemoryProfileResult(callTree);
return callTree == null ? null : MemoryProfileResultFactory.Build(callTree);
}

public ExceptionProfileResult? RunExceptionProfile(string[] command, string label)
Expand Down
11 changes: 11 additions & 0 deletions src/ProfileTool/ProfileInputKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Asynkron.Profiler;

internal enum ProfileInputKind
{
Unknown,
Speedscope,
NetTrace,
Etlx,
Gcdump,
HeapReport
}
213 changes: 54 additions & 159 deletions src/ProfileTool/ProfileInputLoader.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Spectre.Console;
using static Asynkron.Profiler.CallTreeHelpers;

namespace Asynkron.Profiler;

Expand Down Expand Up @@ -43,19 +40,12 @@ public ProfileInputLoader(
return null;
}

var extension = GetNormalizedExtension(inputPath);
if (extension == ".json")
return ProfileInputPath.GetKind(inputPath) switch
{
return AnalyzeSpeedscope(inputPath);
}

if (!IsSupportedExtension(extension, ".nettrace", ".etlx"))
{
WriteUnsupportedInput("Unsupported CPU input", inputPath);
return null;
}

return AnalyzeCpuTrace(inputPath);
ProfileInputKind.Speedscope => AnalyzeSpeedscope(inputPath),
ProfileInputKind.NetTrace or ProfileInputKind.Etlx => AnalyzeCpuTrace(inputPath),
_ => WriteUnsupportedInputAndReturn<CpuProfileResult>("Unsupported CPU input", inputPath)
};
}

public MemoryProfileResult? LoadMemory(string inputPath)
Expand All @@ -66,7 +56,7 @@ public ProfileInputLoader(
}

var callTree = AnalyzeAllocationTrace(inputPath);
return callTree == null ? null : BuildMemoryProfileResult(callTree);
return callTree == null ? null : MemoryProfileResultFactory.Build(callTree);
}

public ExceptionProfileResult? LoadException(string inputPath)
Expand Down Expand Up @@ -96,170 +86,49 @@ public ProfileInputLoader(
return null;
}

var extension = GetNormalizedExtension(inputPath);
if (extension == ".gcdump")
{
if (!_ensureToolAvailable("dotnet-gcdump", _dotnetGcdumpInstallHint))
{
return null;
}

return GcdumpReportLoader.Load(
inputPath,
_getTheme(),
_runProcess,
_parseGcdumpReport,
_writeLine);
}

if (extension is ".txt" or ".log")
return ProfileInputPath.GetKind(inputPath) switch
{
return _parseGcdumpReport(File.ReadAllText(inputPath));
}

WriteUnsupportedInput("Unsupported heap input", inputPath);
return null;
ProfileInputKind.Gcdump => LoadGcdump(inputPath),
ProfileInputKind.HeapReport => _parseGcdumpReport(File.ReadAllText(inputPath)),
_ => WriteUnsupportedInputAndReturn<HeapProfileResult>("Unsupported heap input", inputPath)
};
}

public CpuProfileResult? AnalyzeCpuTrace(string traceFile)
{
try
var result = TryAnalyzeTrace(traceFile, "CPU trace parse failed", _traceAnalyzer.AnalyzeCpuTrace);
if (result == null)
{
var result = _traceAnalyzer.AnalyzeCpuTrace(traceFile);
if (result.AllFunctions.Count == 0)
{
_writeLine($"[{_getTheme().AccentColor}]No CPU samples found in trace.[/]");
return null;
}

return result;
}
catch (Exception ex)
{
_writeLine($"[{_getTheme().AccentColor}]CPU trace parse failed:[/] {Markup.Escape(ex.Message)}");
return null;
}
}

public AllocationCallTreeResult? AnalyzeAllocationTrace(string traceFile)
{
try
{
return _traceAnalyzer.AnalyzeAllocationTrace(traceFile);
}
catch (Exception ex)
if (result.AllFunctions.Count == 0)
{
_writeLine($"[{_getTheme().AccentColor}]Allocation trace parse failed:[/] {Markup.Escape(ex.Message)}");
_writeLine($"[{_getTheme().AccentColor}]No CPU samples found in trace.[/]");
return null;
}
}

public ExceptionProfileResult? AnalyzeExceptionTrace(string traceFile)
{
try
{
return _traceAnalyzer.AnalyzeExceptionTrace(traceFile);
}
catch (Exception ex)
{
_writeLine($"[{_getTheme().AccentColor}]Exception trace parse failed:[/] {Markup.Escape(ex.Message)}");
return null;
}
return result;
}

public ContentionProfileResult? AnalyzeContentionTrace(string traceFile)
{
try
{
return _traceAnalyzer.AnalyzeContentionTrace(traceFile);
}
catch (Exception ex)
{
_writeLine($"[{_getTheme().AccentColor}]Contention trace parse failed:[/] {Markup.Escape(ex.Message)}");
return null;
}
}

public static MemoryProfileResult BuildMemoryProfileResult(AllocationCallTreeResult callTree)
public AllocationCallTreeResult? AnalyzeAllocationTrace(string traceFile)
{
var allocationEntries = callTree.TypeRoots
.OrderByDescending(root => root.TotalBytes)
.Take(50)
.Select(root => new AllocationEntry(root.Name, root.Count, FormatBytes(root.TotalBytes)))
.ToList();

var totalAllocated = FormatBytes(callTree.TotalBytes);

return new MemoryProfileResult(
null,
null,
null,
totalAllocated,
null,
null,
null,
null,
null,
null,
null,
null,
totalAllocated,
allocationEntries,
callTree,
null,
null);
return TryAnalyzeTrace(traceFile, "Allocation trace parse failed", _traceAnalyzer.AnalyzeAllocationTrace);
}

public static string BuildInputLabel(string inputPath)
public ExceptionProfileResult? AnalyzeExceptionTrace(string traceFile)
{
return FileLabelSanitizer.Sanitize(Path.GetFileNameWithoutExtension(inputPath), "input");
return TryAnalyzeTrace(traceFile, "Exception trace parse failed", _traceAnalyzer.AnalyzeExceptionTrace);
}

public static void ApplyInputDefaults(
string inputPath,
ref bool runCpu,
ref bool runMemory,
ref bool runHeap,
ref bool runException,
ref bool runContention)
public ContentionProfileResult? AnalyzeContentionTrace(string traceFile)
{
switch (GetNormalizedExtension(inputPath))
{
case ".json":
runCpu = true;
break;
case ".nettrace":
runCpu = true;
runException = true;
runContention = true;
break;
case ".etlx":
runMemory = true;
runException = true;
runContention = true;
break;
case ".gcdump":
case ".txt":
case ".log":
runHeap = true;
break;
default:
runCpu = true;
break;
}
return TryAnalyzeTrace(traceFile, "Contention trace parse failed", _traceAnalyzer.AnalyzeContentionTrace);
}

private CpuProfileResult? AnalyzeSpeedscope(string speedscopePath)
{
try
{
return ProfilerTraceAnalyzer.AnalyzeSpeedscope(speedscopePath);
}
catch (Exception ex)
{
_writeLine($"[{_getTheme().AccentColor}]Speedscope parse failed:[/] {Markup.Escape(ex.Message)}");
return null;
}
return TryAnalyzeTrace(speedscopePath, "Speedscope parse failed", ProfilerTraceAnalyzer.AnalyzeSpeedscope);
}

private bool TryValidateTraceInput(string inputPath, string unsupportedMessage)
Expand All @@ -269,7 +138,7 @@ private bool TryValidateTraceInput(string inputPath, string unsupportedMessage)
return false;
}

if (IsSupportedExtension(GetNormalizedExtension(inputPath), ".nettrace", ".etlx"))
if (ProfileInputPath.IsTraceInput(inputPath))
{
return true;
}
Expand All @@ -278,6 +147,21 @@ private bool TryValidateTraceInput(string inputPath, string unsupportedMessage)
return false;
}

private HeapProfileResult? LoadGcdump(string inputPath)
{
if (!_ensureToolAvailable("dotnet-gcdump", _dotnetGcdumpInstallHint))
{
return null;
}

return GcdumpReportLoader.Load(
inputPath,
_getTheme(),
_runProcess,
_parseGcdumpReport,
_writeLine);
}

private bool TryEnsureInputExists(string inputPath)
{
if (File.Exists(inputPath))
Expand All @@ -294,13 +178,24 @@ private void WriteUnsupportedInput(string message, string inputPath)
_writeLine($"[{_getTheme().ErrorColor}]{message}:[/] {Markup.Escape(inputPath)}");
}

private static string GetNormalizedExtension(string inputPath)
private T? TryAnalyzeTrace<T>(string path, string failureMessage, Func<string, T> analyze)
where T : class
{
return Path.GetExtension(inputPath).ToLowerInvariant();
try
{
return analyze(path);
}
catch (Exception ex)
{
_writeLine($"[{_getTheme().AccentColor}]{failureMessage}[/] {Markup.Escape(ex.Message)}");
return null;
}
}

private static bool IsSupportedExtension(string extension, params string[] allowedExtensions)
private T? WriteUnsupportedInputAndReturn<T>(string message, string inputPath)
where T : class
{
return allowedExtensions.Contains(extension, StringComparer.Ordinal);
WriteUnsupportedInput(message, inputPath);
return null;
}
}
Loading
Loading