Skip to content
Merged
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
54 changes: 2 additions & 52 deletions src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -495,45 +495,13 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1)
var iconBitmap = IconHelper.LoadIcon(node.IconName);
if (iconBitmap != null)
{
var iconImage = new Image
iconRow.Children.Add(new Image
{
Source = iconBitmap,
Width = 32,
Height = 32,
Margin = new Thickness(0, 0, 0, 2)
};

// Distinguish Parallelism subtypes (Repartition / Distribute / Gather Streams)
// by overlaying a small letter on the shared parallelism.png icon.
var parallelismGlyph = GetParallelismGlyph(node);
if (parallelismGlyph != null)
{
var iconGrid = new Grid { Margin = new Thickness(0, 0, 0, 2) };
iconGrid.Children.Add(iconImage);
iconImage.Margin = default;
iconGrid.Children.Add(new Border
{
Width = 14, Height = 14,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
Background = new SolidColorBrush(Color.FromRgb(0x33, 0x33, 0x33)),
CornerRadius = new CornerRadius(7),
Child = new TextBlock
{
Text = parallelismGlyph,
FontSize = 9,
FontWeight = FontWeight.Bold,
Foreground = Brushes.White,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
});
iconRow.Children.Add(iconGrid);
}
else
{
iconRow.Children.Add(iconImage);
}
});
}

// Warning indicator badge (orange triangle with !)
Expand Down Expand Up @@ -3094,24 +3062,6 @@ private void UpdateInsightsHeader()
InsightsHeader.Text = " Plan Insights";
}

/// <summary>
/// For Parallelism nodes, returns a single-letter glyph distinguishing
/// the three subtypes: R(epartition) / D(istribute) / G(ather) Streams.
/// Returns null for non-parallelism operators.
/// </summary>
private static string? GetParallelismGlyph(PlanNode node)
{
if (!string.Equals(node.PhysicalOp, "Parallelism", StringComparison.Ordinal))
return null;
return node.LogicalOp switch
{
"Repartition Streams" => "R",
"Distribute Streams" => "D",
"Gather Streams" => "G",
_ => null
};
}

private static string GetWaitCategory(string waitType)
{
if (waitType.StartsWith("SOS_SCHEDULER_YIELD") ||
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 15 additions & 2 deletions src/PlanViewer.Core/Services/PlanIconMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,23 @@ public static class PlanIconMapper
/// <summary>
/// Returns the icon file name (without extension) for a given physical operator.
/// When <paramref name="storageType"/> is "ColumnStore" on a *Index Scan,
/// routes to the columnstore variant.
/// routes to the columnstore variant. When <paramref name="physicalOp"/> is
/// "Parallelism" and <paramref name="logicalOp"/> identifies a stream subtype,
/// routes to the matching subtype icon.
/// </summary>
public static string GetIconName(string physicalOp, string? storageType = null)
public static string GetIconName(string physicalOp, string? storageType = null, string? logicalOp = null)
{
// Parallelism subtypes: PhysicalOp="Parallelism" + LogicalOp identifies which.
if (string.Equals(physicalOp, "Parallelism", StringComparison.Ordinal) && logicalOp != null)
{
switch (logicalOp)
{
case "Repartition Streams": return "parallelism_repartition_streams";
case "Distribute Streams": return "parallelism_distribute_streams";
case "Gather Streams": return "parallelism_gather_streams";
}
}

// Columnstore scans surface as PhysicalOp="Clustered Index Scan" / "Index Scan"
// with Storage="ColumnStore" on the Object element. Route to the columnstore icon.
if (string.Equals(storageType, "ColumnStore", StringComparison.OrdinalIgnoreCase))
Expand Down
8 changes: 4 additions & 4 deletions src/PlanViewer.Core/Services/ShowPlanParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,10 +1369,10 @@ private static PlanNode ParseRelOp(XElement relOpEl)
}
}

// Map to icon — done here so columnstore scans (which surface as
// Clustered/Index Scan with Storage="ColumnStore") can be routed to
// the columnstore icon.
node.IconName = PlanIconMapper.GetIconName(node.PhysicalOp, node.StorageType);
// Map to icon — done here so columnstore scans (Clustered/Index Scan
// with Storage="ColumnStore") and Parallelism subtypes (which depend on
// LogicalOp) can be routed to their specific icons.
node.IconName = PlanIconMapper.GetIconName(node.PhysicalOp, node.StorageType, node.LogicalOp);

// Recurse into child RelOps
foreach (var childRelOp in FindChildRelOps(relOpEl))
Expand Down
Loading