Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
98694ba
Fixes #319
ClaudioESSilva Feb 26, 2026
118bb1a
Merge pull request #320 from ClaudioESSilva/fix/MissingTooltipOnChart…
erikdarlingdata Feb 26, 2026
0979d31
Fixes #321
ClaudioESSilva Feb 26, 2026
e9fc868
Merge pull request #322 from ClaudioESSilva/fix/MissingDarkModeCharts…
erikdarlingdata Feb 26, 2026
34a9c54
Apply dark theme to all 19 SystemEventsContent charts in constructor
erikdarlingdata Feb 26, 2026
2b88082
Merge pull request #323 from erikdarlingdata/fix/system-events-dark-t…
erikdarlingdata Feb 26, 2026
bd0569b
Add --preserve-jobs flag to CLI and GUI installers
erikdarlingdata Feb 26, 2026
bf69c20
Merge pull request #326 from erikdarlingdata/feature/preserve-jobs
erikdarlingdata Feb 26, 2026
0816ede
Add 13 new plan analysis rules to PlanAnalyzer (#327)
erikdarlingdata Feb 26, 2026
ef0e29e
Add sortable statement DataGrid and canvas panning to plan viewer
erikdarlingdata Feb 27, 2026
817aad9
Merge pull request #331 from erikdarlingdata/feature/plan-viewer-grid…
erikdarlingdata Feb 27, 2026
b0083a4
Add nightly build workflow
erikdarlingdata Feb 27, 2026
b026474
Merge pull request #332 from erikdarlingdata/feature/nightly-builds
erikdarlingdata Feb 27, 2026
f4c5f26
Sync PlanAnalyzer rule fixes from plan-b
erikdarlingdata Feb 27, 2026
ed9c6ae
Merge pull request #334 from erikdarlingdata/fix/plan-analyzer-rule-sync
erikdarlingdata Feb 27, 2026
48c0910
Fix UDF timing units: microseconds → milliseconds
erikdarlingdata Feb 27, 2026
c206575
Merge pull request #338 from erikdarlingdata/fix/udf-timing-units
erikdarlingdata Feb 27, 2026
85f320f
Reduce first-run lookback from 3-7 days to 1 hour for all collectors …
erikdarlingdata Feb 27, 2026
6a69930
Rule 7: Replace arbitrary spill write-count threshold with time-based…
erikdarlingdata Feb 27, 2026
153ccc7
Merge pull request #341 from erikdarlingdata/fix/rule7-spill-time-ana…
erikdarlingdata Feb 27, 2026
b22a476
Rule 8: Lower parallel skew thresholds and remove 4-thread minimum
erikdarlingdata Feb 27, 2026
608c1fd
Merge pull request #342 from erikdarlingdata/fix/rule8-skew-thresholds
erikdarlingdata Feb 27, 2026
76eca9c
Rule 9: Raise memory grant warning floor to 1GB, Critical to 4GB
erikdarlingdata Feb 27, 2026
9928aa5
Merge pull request #343 from erikdarlingdata/fix/rule9-memory-grant-t…
erikdarlingdata Feb 27, 2026
a7d38ac
Rule 13: Add GetRangeThroughConvert detection for CONVERT/CAST on col…
erikdarlingdata Feb 27, 2026
640eb23
Merge pull request #344 from erikdarlingdata/fix/rules-13-14-improvem…
erikdarlingdata Feb 27, 2026
64bbead
Skip non-readable AG secondary databases in cross-database collectors…
erikdarlingdata Feb 27, 2026
9b62a27
Rules 13-15: GetRangeThroughConvert, lazy spool threshold, OR expansi…
erikdarlingdata Feb 27, 2026
a9da80b
Merge pull request #349 from erikdarlingdata/fix/rules-13-14-15
erikdarlingdata Feb 27, 2026
25e15aa
Rule 15: Restore join ancestor check alongside Merge Interval
erikdarlingdata Feb 27, 2026
b85dca5
Merge pull request #350 from erikdarlingdata/fix/rule-15-restore-join…
erikdarlingdata Feb 27, 2026
444d927
Add optional query text and plan collection flags to schedule table (…
erikdarlingdata Feb 27, 2026
d101b1b
Merge pull request #351 from erikdarlingdata/feature/optional-plan-co…
erikdarlingdata Feb 27, 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
148 changes: 148 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: Nightly Build

on:
schedule:
# 6:00 AM UTC (1:00 AM EST / 2:00 AM EDT)
- cron: '0 6 * * *'
workflow_dispatch: # manual trigger

permissions:
contents: write

jobs:
check:
runs-on: ubuntu-latest
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0

- name: Check for new commits in last 24 hours
id: check
run: |
RECENT=$(git log --since="24 hours ago" --oneline | head -1)
if [ -n "$RECENT" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "New commits found — building nightly"
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No new commits — skipping nightly build"
fi

build:
needs: check
if: needs.check.outputs.has_changes == 'true' || github.event_name == 'workflow_dispatch'
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
with:
ref: dev

- name: Setup .NET 8.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Set nightly version
id: version
shell: pwsh
run: |
$base = ([xml](Get-Content Dashboard/Dashboard.csproj)).Project.PropertyGroup.Version | Where-Object { $_ }
$date = Get-Date -Format "yyyyMMdd"
$nightly = "$base-nightly.$date"
echo "VERSION=$nightly" >> $env:GITHUB_OUTPUT
echo "Nightly version: $nightly"

- name: Restore dependencies
run: |
dotnet restore Dashboard/Dashboard.csproj
dotnet restore Lite/PerformanceMonitorLite.csproj
dotnet restore Installer/PerformanceMonitorInstaller.csproj
dotnet restore InstallerGui/InstallerGui.csproj
dotnet restore Lite.Tests/Lite.Tests.csproj

- name: Run tests
run: dotnet test Lite.Tests/Lite.Tests.csproj -c Release --verbosity normal

- name: Publish Dashboard
run: dotnet publish Dashboard/Dashboard.csproj -c Release -o publish/Dashboard

- name: Publish Lite
run: dotnet publish Lite/PerformanceMonitorLite.csproj -c Release -o publish/Lite

- name: Publish CLI Installer
run: dotnet publish Installer/PerformanceMonitorInstaller.csproj -c Release

- name: Publish GUI Installer
run: dotnet publish InstallerGui/InstallerGui.csproj -c Release

- name: Package artifacts
shell: pwsh
run: |
$version = "${{ steps.version.outputs.VERSION }}"
New-Item -ItemType Directory -Force -Path releases

Compress-Archive -Path 'publish/Dashboard/*' -DestinationPath "releases/PerformanceMonitorDashboard-$version.zip" -Force
Compress-Archive -Path 'publish/Lite/*' -DestinationPath "releases/PerformanceMonitorLite-$version.zip" -Force

$instDir = 'publish/Installer'
New-Item -ItemType Directory -Force -Path $instDir
New-Item -ItemType Directory -Force -Path "$instDir/install"
New-Item -ItemType Directory -Force -Path "$instDir/upgrades"

Copy-Item 'Installer/bin/Release/net8.0/win-x64/publish/PerformanceMonitorInstaller.exe' $instDir
Copy-Item 'InstallerGui/bin/Release/net8.0-windows/win-x64/publish/PerformanceMonitorInstallerGui.exe' $instDir -ErrorAction SilentlyContinue
Copy-Item 'install/*.sql' "$instDir/install/"
if (Test-Path 'install/templates') { Copy-Item 'install/templates' "$instDir/install/templates" -Recurse -ErrorAction SilentlyContinue }
if (Test-Path 'upgrades') { Copy-Item 'upgrades/*' "$instDir/upgrades/" -Recurse -ErrorAction SilentlyContinue }
if (Test-Path 'README.md') { Copy-Item 'README.md' $instDir }
if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' $instDir }
if (Test-Path 'THIRD_PARTY_NOTICES.md') { Copy-Item 'THIRD_PARTY_NOTICES.md' $instDir }

Compress-Archive -Path 'publish/Installer/*' -DestinationPath "releases/PerformanceMonitorInstaller-$version.zip" -Force

- name: Generate checksums
shell: pwsh
run: |
$checksums = Get-ChildItem releases/*.zip | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
"$hash $($_.Name)"
}
$checksums | Out-File -FilePath releases/SHA256SUMS.txt -Encoding utf8
Write-Host "Checksums:"
$checksums | ForEach-Object { Write-Host $_ }

- name: Delete previous nightly release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release delete nightly --yes --cleanup-tag 2>$null; exit 0
shell: pwsh

- name: Create nightly release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: pwsh
run: |
$version = "${{ steps.version.outputs.VERSION }}"
$sha = git rev-parse --short HEAD
$body = @"
Automated nightly build from ``dev`` branch.

**Version:** ``$version``
**Commit:** ``$sha``
**Built:** $(Get-Date -Format "yyyy-MM-dd HH:mm UTC")

> These builds include the latest changes and may be unstable.
> For production use, download the [latest stable release](https://github.com/erikdarlingdata/PerformanceMonitor/releases/latest).
"@

gh release create nightly `
--target dev `
--title "Nightly Build ($version)" `
--notes $body `
--prerelease `
releases/*.zip releases/SHA256SUMS.txt
8 changes: 8 additions & 0 deletions Dashboard/Controls/MemoryContent.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ public MemoryContent()
SetupChartContextMenus();
Loaded += OnLoaded;

// Apply dark theme immediately so charts don't flash white before data loads
TabHelpers.ApplyDarkModeToChart(MemoryStatsOverviewChart);
TabHelpers.ApplyDarkModeToChart(MemoryGrantSizingChart);
TabHelpers.ApplyDarkModeToChart(MemoryGrantActivityChart);
TabHelpers.ApplyDarkModeToChart(MemoryClerksChart);
TabHelpers.ApplyDarkModeToChart(PlanCacheChart);
TabHelpers.ApplyDarkModeToChart(MemoryPressureEventsChart);

_memoryStatsOverviewHover = new Helpers.ChartHoverHelper(MemoryStatsOverviewChart, "MB");
_memoryGrantSizingHover = new Helpers.ChartHoverHelper(MemoryGrantSizingChart, "MB");
_memoryGrantActivityHover = new Helpers.ChartHoverHelper(MemoryGrantActivityChart, "count");
Expand Down
81 changes: 67 additions & 14 deletions Dashboard/Controls/PlanViewerControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,10 @@
<Separator Margin="12,4"/>
<Button Content="Save .sqlplan" Click="SavePlan_Click" Height="28" Padding="8,0"
ToolTip="Save plan as .sqlplan file"/>
<Separator Margin="12,4"/>
<TextBlock x:Name="StatementLabel" Text="Statement:" VerticalAlignment="Center"
Foreground="{DynamicResource ForegroundMutedBrush}" Margin="0,0,6,0"
Visibility="Collapsed"/>
<ComboBox x:Name="StatementSelector" MinWidth="200" Height="28"
SelectionChanged="StatementSelector_Changed"
Visibility="Collapsed"/>
<Separator x:Name="StatementsButtonSeparator" Margin="12,4" Visibility="Collapsed"/>
<Button x:Name="StatementsButton" Content="Statements" Click="ToggleStatements_Click"
Height="28" Padding="8,0" Visibility="Collapsed"
ToolTip="Show/hide statement list"/>
</StackPanel>
<TextBlock x:Name="CostText" DockPanel.Dock="Right" VerticalAlignment="Center"
HorizontalAlignment="Right"
Expand Down Expand Up @@ -92,20 +89,76 @@
Padding="8,4"/>
</Expander>

<!-- Plan Canvas + Properties Panel -->
<!-- Statements Panel + Plan Canvas + Properties Panel -->
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<!-- Col 0: Statements Panel (hidden by default) -->
<ColumnDefinition x:Name="StatementsColumn" Width="0"/>
<!-- Col 1: Statements Splitter (hidden by default) -->
<ColumnDefinition x:Name="StatementsSplitterColumn" Width="0"/>
<!-- Col 2: Plan Canvas -->
<ColumnDefinition Width="*"/>
<!-- Col 3: Properties Splitter (hidden by default) -->
<ColumnDefinition Width="Auto"/>
<!-- Col 4: Properties Panel (hidden by default) -->
<ColumnDefinition x:Name="PropertiesColumn" Width="0"/>
</Grid.ColumnDefinitions>

<!-- Statements Panel -->
<Border x:Name="StatementsPanel" Grid.Column="0"
Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,1,0"
Visibility="Collapsed">
<DockPanel>
<!-- Header -->
<Border DockPanel.Dock="Top" Padding="10,8"
Background="{DynamicResource BackgroundBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
<DockPanel>
<Button DockPanel.Dock="Right" Content="&#x2715;" Click="CloseStatements_Click"
Width="24" Height="24" Padding="0" FontSize="12"
VerticalAlignment="Center" ToolTip="Close Statements"
Background="Transparent" BorderThickness="0"
Foreground="{DynamicResource ForegroundMutedBrush}"/>
<TextBlock x:Name="StatementsHeader" Text="Statements"
FontWeight="SemiBold" FontSize="13"
Foreground="{DynamicResource ForegroundBrush}"
VerticalAlignment="Center"/>
</DockPanel>
</Border>

<!-- Statement Grid -->
<DataGrid x:Name="StatementsGrid"
AutoGenerateColumns="False"
CanUserSortColumns="True"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
IsReadOnly="True"
SelectionMode="Single"
GridLinesVisibility="Horizontal"
HeadersVisibility="Column"
SelectionChanged="StatementsGrid_SelectionChanged"
FontSize="11"
Background="{DynamicResource BackgroundDarkBrush}"
BorderThickness="0"/>
</DockPanel>
</Border>

<!-- Statements Splitter -->
<GridSplitter x:Name="StatementsSplitter" Grid.Column="1" Width="5"
HorizontalAlignment="Center" VerticalAlignment="Stretch"
Background="{DynamicResource BorderBrush}"
Visibility="Collapsed"/>

<!-- Plan Canvas -->
<ScrollViewer Grid.Column="0" x:Name="PlanScrollViewer"
<ScrollViewer Grid.Column="2" x:Name="PlanScrollViewer"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Background="{DynamicResource BackgroundBrush}"
PreviewMouseWheel="PlanScrollViewer_PreviewMouseWheel">
PreviewMouseWheel="PlanScrollViewer_PreviewMouseWheel"
PreviewMouseLeftButtonDown="PlanScrollViewer_PreviewMouseLeftButtonDown"
PreviewMouseMove="PlanScrollViewer_PreviewMouseMove"
PreviewMouseLeftButtonUp="PlanScrollViewer_PreviewMouseLeftButtonUp">
<Canvas x:Name="PlanCanvas" ClipToBounds="False">
<Canvas.LayoutTransform>
<ScaleTransform x:Name="ZoomTransform" ScaleX="1" ScaleY="1"/>
Expand All @@ -114,22 +167,22 @@
</ScrollViewer>

<!-- Empty State -->
<StackPanel x:Name="EmptyState" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center">
<StackPanel x:Name="EmptyState" Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="No Plan Loaded" FontSize="20" FontWeight="Light"
Foreground="{DynamicResource ForegroundMutedBrush}" HorizontalAlignment="Center"/>
<TextBlock Text="Right-click a query row and choose 'View Plan' to display an execution plan here"
FontSize="13" Foreground="{DynamicResource ForegroundMutedBrush}"
HorizontalAlignment="Center" Margin="0,8,0,0"/>
</StackPanel>

<!-- GridSplitter -->
<GridSplitter x:Name="PropertiesSplitter" Grid.Column="1" Width="5"
<!-- Properties Splitter -->
<GridSplitter x:Name="PropertiesSplitter" Grid.Column="3" Width="5"
HorizontalAlignment="Center" VerticalAlignment="Stretch"
Background="{DynamicResource BorderBrush}"
Visibility="Collapsed"/>

<!-- Properties Panel -->
<Border x:Name="PropertiesPanel" Grid.Column="2"
<Border x:Name="PropertiesPanel" Grid.Column="4"
Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1,0,0,0"
Visibility="Collapsed">
Expand Down
Loading
Loading