Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
04ccefc
Add "Powered by Performance Studio" line on landing page
erikdarlingdata Apr 7, 2026
678e15d
Merge pull request #186 from erikdarlingdata/fix/powered-by-line
erikdarlingdata Apr 7, 2026
6c6c1f0
Add Darling Data favicon to web app
erikdarlingdata Apr 7, 2026
a7e841c
Merge pull request #187 from erikdarlingdata/fix/favicon
erikdarlingdata Apr 7, 2026
4453034
Add Open Graph and Twitter Card meta tags for social sharing
erikdarlingdata Apr 7, 2026
482d385
Merge pull request #188 from erikdarlingdata/fix/og-meta
erikdarlingdata Apr 7, 2026
ba2beeb
Clarify OG description: in-browser, nothing to install
erikdarlingdata Apr 7, 2026
25c7648
Merge pull request #189 from erikdarlingdata/fix/og-description
erikdarlingdata Apr 7, 2026
5c5c4ff
Merge pull request #191 from erikdarlingdata/dev
erikdarlingdata Apr 7, 2026
68ff836
Fix Rule 3 severity: CouldNotGenerateValidParallelPlan is actionable
erikdarlingdata Apr 7, 2026
a96b465
Merge pull request #192 from erikdarlingdata/fix/rule3-actionable
erikdarlingdata Apr 7, 2026
5615021
Expand Rule 3 to cover all NonParallelPlanReason values
erikdarlingdata Apr 7, 2026
923a8e5
Merge pull request #193 from erikdarlingdata/fix/rule3-full-reasons
erikdarlingdata Apr 7, 2026
263f5a8
Merge pull request #196 from erikdarlingdata/dev
erikdarlingdata Apr 7, 2026
e8cd496
Merge pull request #199 from erikdarlingdata/dev
erikdarlingdata Apr 7, 2026
81e7285
Merge pull request #202 from erikdarlingdata/dev
erikdarlingdata Apr 7, 2026
c99311e
Merge pull request #205 from erikdarlingdata/dev
erikdarlingdata Apr 8, 2026
a3a6e5a
Merge pull request #208 from erikdarlingdata/dev
erikdarlingdata Apr 8, 2026
cb199a2
Merge pull request #210 from erikdarlingdata/dev
erikdarlingdata Apr 8, 2026
cbd0c6d
Release: issue #178 round 3 feedback (items 17-25)
erikdarlingdata Apr 9, 2026
838f40f
Merge pull request #220 from erikdarlingdata/dev
erikdarlingdata Apr 13, 2026
32ed53d
Merge pull request #223 from erikdarlingdata/dev
erikdarlingdata Apr 13, 2026
32fea96
Merge pull request #227 from erikdarlingdata/dev
erikdarlingdata Apr 15, 2026
a1f8362
Merge pull request #237 from erikdarlingdata/dev
erikdarlingdata Apr 17, 2026
2958568
Merge pull request #243 from erikdarlingdata/dev
erikdarlingdata Apr 20, 2026
ef7690c
Merge pull request #245 from erikdarlingdata/dev
erikdarlingdata Apr 21, 2026
341678f
Merge pull request #248 from erikdarlingdata/dev
erikdarlingdata Apr 21, 2026
fdf490d
Merge pull request #256 from erikdarlingdata/dev
erikdarlingdata Apr 22, 2026
f18fe57
Merge pull request #258 from erikdarlingdata/dev
erikdarlingdata Apr 22, 2026
48870b0
Merge pull request #260 from erikdarlingdata/dev
erikdarlingdata Apr 22, 2026
7009393
Merge pull request #264 from erikdarlingdata/dev
erikdarlingdata Apr 22, 2026
754b184
Merge pull request #267 from erikdarlingdata/dev
erikdarlingdata Apr 23, 2026
4cce22d
Merge pull request #269 from erikdarlingdata/dev
erikdarlingdata Apr 24, 2026
194d1fc
Merge pull request #274 from erikdarlingdata/dev
erikdarlingdata Apr 24, 2026
66be6df
Merge pull request #292 from erikdarlingdata/dev
erikdarlingdata Apr 27, 2026
02120e6
Merge pull request #309 from erikdarlingdata/dev
erikdarlingdata May 4, 2026
66c2e43
Split ShowPlanParser.cs into partial classes
erikdarlingdata May 13, 2026
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
38 changes: 38 additions & 0 deletions src/PlanViewer.Core/Services/ShowPlanParser.Costs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using PlanViewer.Core.Models;

namespace PlanViewer.Core.Services;

public static partial class ShowPlanParser
{
private static void ComputeOperatorCosts(ParsedPlan plan)
{
foreach (var batch in plan.Batches)
{
foreach (var stmt in batch.Statements)
{
if (stmt.RootNode == null) continue;
var totalCost = stmt.StatementSubTreeCost > 0
? stmt.StatementSubTreeCost
: stmt.RootNode.EstimatedTotalSubtreeCost;
if (totalCost <= 0) totalCost = 1;
ComputeNodeCosts(stmt.RootNode, totalCost);
}
}
}

private static void ComputeNodeCosts(PlanNode node, double totalStatementCost)
{
var childrenSubtreeCost = node.Children.Sum(c => c.EstimatedTotalSubtreeCost);
node.EstimatedOperatorCost = Math.Max(0, node.EstimatedTotalSubtreeCost - childrenSubtreeCost);
node.CostPercent = (int)Math.Round((node.EstimatedOperatorCost / totalStatementCost) * 100);
node.CostPercent = Math.Min(100, Math.Max(0, node.CostPercent));

foreach (var child in node.Children)
ComputeNodeCosts(child, totalStatementCost);
}
}
84 changes: 84 additions & 0 deletions src/PlanViewer.Core/Services/ShowPlanParser.Helpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using PlanViewer.Core.Models;

namespace PlanViewer.Core.Services;

public static partial class ShowPlanParser
{
/// <summary>
/// Strips the internal padding and hex session suffix from temp table names.
/// SQL Server internally pads #temp names with underscores to 116 chars, then appends a hex suffix.
/// e.g. "#comment_sil_vous_plait_______________________________0000000000A86" → "#comment_sil_vous_plait"
/// </summary>
private static string CleanTempTableName(string name)
{
if (name.Length == 0 || name[0] != '#') return name;

// Find the end of the real name: trim trailing hex suffix, then trailing underscores
// The hex suffix is 8-16 hex chars at the end; the padding is consecutive underscores before it
var i = name.Length - 1;

// Skip trailing hex digits (0-9, A-F, a-f)
while (i > 0 && IsHexDigit(name[i])) i--;

// Skip trailing underscores (the padding)
while (i > 0 && name[i] == '_') i--;

// Only clean if we actually removed a meaningful amount (at least 8 chars of padding+hex)
if (name.Length - i > 8)
return name[..(i + 1)];

return name;
}

private static bool IsHexDigit(char c) =>
(c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');

private static IEnumerable<XElement> ScopedDescendants(XElement element, XName name)
{
foreach (var child in element.Elements())
{
if (child.Name == Ns + "RelOp") continue;
if (child.Name == name) yield return child;
foreach (var desc in ScopedDescendants(child, name))
yield return desc;
}
}

private static string? ParseColumnList(XElement parent, string elementName)
{
var el = parent.Element(Ns + elementName);
if (el == null) return null;
var cols = el.Elements(Ns + "ColumnReference")
.Select(c => FormatColumnRef(c))
.Where(s => !string.IsNullOrEmpty(s));
var result = string.Join(", ", cols);
return string.IsNullOrEmpty(result) ? null : result;
}

private static string FormatColumnRef(XElement colRef)
{
var col = colRef.Attribute("Column")?.Value ?? "";
var tbl = colRef.Attribute("Table")?.Value ?? "";
var result = string.IsNullOrEmpty(tbl) ? col : $"{tbl}.{col}";
return result.Replace("[", "").Replace("]", "");
}

private static double ParseDouble(string? value)
{
if (string.IsNullOrEmpty(value)) return 0;
return double.TryParse(value, System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out var result) ? result : 0;
}

private static long ParseLong(string? value)
{
if (string.IsNullOrEmpty(value)) return 0;
return long.TryParse(value, System.Globalization.NumberStyles.Integer,
System.Globalization.CultureInfo.InvariantCulture, out var result) ? result : 0;
}
}
Loading
Loading