diff --git a/Cli/CliOptions.cs b/Cli/CliOptions.cs
index 8f42194..9f03884 100644
--- a/Cli/CliOptions.cs
+++ b/Cli/CliOptions.cs
@@ -30,6 +30,7 @@ internal sealed class CliOptions
public bool ContinueOnError { get; set; } = true;
public string MultiReportDirectory { get; set; } = string.Empty;
public string? MultiReportFilter { get; set; }
+ public bool MultiReportNoReports { get; set; }
public string GoldenPath { get; set; } = string.Empty;
public string? ValidationOutputPath { get; set; }
public string SummaryInputPath { get; set; } = string.Empty;
diff --git a/Cli/CommandLineBuilder.cs b/Cli/CommandLineBuilder.cs
index c439583..d8036d4 100644
--- a/Cli/CommandLineBuilder.cs
+++ b/Cli/CommandLineBuilder.cs
@@ -243,11 +243,16 @@ public static RootCommand Build(
{
Description = "Print progress and timings.",
};
+ var multiNoReportsOpt = new Option("--no-reports")
+ {
+ Description = "Skip generating per-snapshot drill-down reports (faster; rows are not clickable).",
+ };
multiReportCmd.Add(filterOpt);
multiReportCmd.Add(multiOutOpt);
multiReportCmd.Add(multiTitleOpt);
multiReportCmd.Add(multiVerboseOpt);
+ multiReportCmd.Add(multiNoReportsOpt);
multiReportCmd.SetAction((ParseResult parseResult) =>
{
@@ -267,6 +272,7 @@ public static RootCommand Build(
ReportOutputPath = string.IsNullOrWhiteSpace(outPath) ? null : ExpandPath(outPath!),
ReportTitle = parseResult.GetValue(multiTitleOpt)!,
Verbose = parseResult.GetValue(multiVerboseOpt),
+ MultiReportNoReports = parseResult.GetValue(multiNoReportsOpt),
};
return runMultiReport(options);
});
diff --git a/Cli/Program.cs b/Cli/Program.cs
index 29eac6c..9f34c29 100644
--- a/Cli/Program.cs
+++ b/Cli/Program.cs
@@ -92,6 +92,7 @@ private static int RunMultiReport(CliOptions options)
NameFilter = options.MultiReportFilter,
ReportOutputPath = options.ReportOutputPath,
ReportTitle = options.ReportTitle,
+ GenerateReports = !options.MultiReportNoReports,
};
var progress = new ConsoleProgress(options.Verbose);
return MultiSnapshotReportRunner.Run(multiOptions, progress);
diff --git a/Core/Report/MultiSnapshotReport/MultiSnapshotHtmlRenderer.cs b/Core/Report/MultiSnapshotReport/MultiSnapshotHtmlRenderer.cs
index f052daf..6aac292 100644
--- a/Core/Report/MultiSnapshotReport/MultiSnapshotHtmlRenderer.cs
+++ b/Core/Report/MultiSnapshotReport/MultiSnapshotHtmlRenderer.cs
@@ -5,26 +5,52 @@
namespace MemorySnapshotDataTools.Report.MultiSnapshotReport;
///
-/// Renders a to a self-contained HTML document with one session-grouped table.
+/// Renders a to a self-contained HTML document with one
+/// session-grouped table. The table is driven by a column-descriptor list () so
+/// columns — including the high-level Allocated Memory Distribution summary columns — can be toggled on
+/// and off, and each snapshot row can open its full single-snapshot report in an inline iframe drawer.
///
public static class MultiSnapshotHtmlRenderer
{
- private const int ColSnapshot = 0;
- private const int ColAbCount = 1;
- private const int ColAbAlloc = 2;
- private const int ColAbRes = 3;
- private const int ColSfCount = 4;
- private const int ColSfAlloc = 5;
- private const int ColSfRes = 6;
- private const int ColPmrAlloc = 7;
- private const int ColPmrRes = 8;
- private const int ColumnCount = 9;
+ // Allocated Memory Distribution category names, matching SummaryMetricsCalculator. Graphics and
+ // Untracked carry no resident value (ResidentAvailable=false) so their resident cells show N/A.
+ private const string CatNative = "Native";
+ private const string CatManaged = "Managed";
+ private const string CatExecutables = "Executables & Mapped";
+ private const string CatGraphics = "Graphics (Estimated)";
+ private const string CatUntracked = "Untracked";
+ private const string CatAndroidRuntime = "Android Runtime";
+
+ // data-group keys (used for column visibility toggling).
+ private const string GroupAssetBundle = "ab";
+ private const string GroupSerializedFile = "sf";
+ private const string GroupPmr = "pmr";
+ private const string GroupResidentSummary = "sumRes";
+ private const string GroupCommittedSummary = "sumCom";
+
+ /// One renderable value: a number (count or bytes) or null (rendered as N/A).
+ private readonly record struct CellValue(long? Number, bool IsCount);
+
+ /// Describes one data column: its group, headers, default visibility, and value selector.
+ private sealed record ColumnDef(
+ string Group,
+ string GroupHeader,
+ string SubHeader,
+ bool DefaultVisible,
+ Func Value);
+
+ /// Builds the full HTML report string from the model (no per-snapshot report links).
+ public static string Render(MultiSnapshotReportModel model) => Render(model, null);
///
- /// Builds the full HTML report string from the model.
+ /// Builds the full HTML report string. When maps a snapshot's
+ /// to a relative report href, that row becomes
+ /// clickable and opens the report in the inline drawer.
///
- public static string Render(MultiSnapshotReportModel model)
+ public static string Render(MultiSnapshotReportModel model, IReadOnlyDictionary? reportLinks)
{
+ var columns = BuildColumns(model);
+
var sb = new StringBuilder();
sb.Append("""
@@ -57,15 +83,23 @@ public static string Render(MultiSnapshotReportModel model)
sb.Append(" snapshots
\n");
if (model.Sessions.Count == 0)
+ {
sb.Append("No matching database files found.
");
+ }
else
- sb.Append(RenderUnifiedTable(model));
+ {
+ sb.Append(RenderToggleBar(columns));
+ sb.Append(RenderUnifiedTable(model, columns, reportLinks));
+ sb.Append(DrawerHtml);
+ }
sb.Append("""