diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1cf980d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,49 @@ +# Auto-detect text files and normalize line endings to LF in the repo. +# Working-tree EOLs are decided per-pattern below. +* text=auto + +# Source and config — normalize to LF in repo, check out as-is on each platform +*.cs text eol=lf +*.csproj text eol=lf +*.sln text eol=lf -crlf +*.props text eol=lf +*.targets text eol=lf +*.json text eol=lf +*.xml text eol=lf +*.config text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.editorconfig text eol=lf +*.gitattributes text eol=lf +*.gitignore text eol=lf + +# Shell scripts must stay LF +*.sh text eol=lf + +# Windows-only scripts must stay CRLF +*.ps1 text eol=crlf +*.psm1 text eol=crlf +*.psd1 text eol=crlf +*.cmd text eol=crlf +*.bat text eol=crlf + +# Web assets +*.js text eol=lf +*.ts text eol=lf +*.css text eol=lf +*.html text eol=lf +*.razor text eol=lf +*.cshtml text eol=lf + +# Binary assets — never touch +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.zip binary +*.dll binary +*.exe binary +*.snk binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c75cb87..c3ac5da 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,89 +1,89 @@ -name: Bug Report -description: Report a problem with SQL Performance Studio -title: "[BUG] " -labels: ["bug"] - -body: - - type: dropdown - id: component - attributes: - label: Component - description: Which part of SQL Performance Studio is affected? - options: - - Desktop App (Windows) - - Desktop App (macOS) - - Desktop App (Linux) - - CLI Tool - - SSMS Extension - validations: - required: true - - - type: input - id: version - attributes: - label: SQL Performance Studio Version - description: Check the About dialog or the release you downloaded. - placeholder: "e.g., 0.7.0" - validations: - required: true - - - type: input - id: os-version - attributes: - label: Operating System - description: The OS where you're running the app. - placeholder: "e.g., Windows 11 23H2, macOS 15.3, Ubuntu 24.04" - validations: - required: true - - - type: textarea - id: description - attributes: - label: Describe the Bug - description: A clear description of what the bug is. - validations: - required: true - - - type: textarea - id: steps - attributes: - label: Steps to Reproduce - description: How can we reproduce this? - placeholder: | - 1. Open a .sqlplan file - 2. Click on... - 3. See error - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Expected Behavior - description: What you expected to happen. - validations: - required: true - - - type: textarea - id: actual - attributes: - label: Actual Behavior - description: What actually happened. - validations: - required: true - - - type: textarea - id: plan-file - attributes: - label: Plan File - description: If applicable, attach or paste the .sqlplan file that triggers the issue. You can redact sensitive table/column names. - validations: - required: false - - - type: textarea - id: screenshots - attributes: - label: Screenshots - description: If applicable, add screenshots to help explain the problem. - validations: - required: false +name: Bug Report +description: Report a problem with SQL Performance Studio +title: "[BUG] " +labels: ["bug"] + +body: + - type: dropdown + id: component + attributes: + label: Component + description: Which part of SQL Performance Studio is affected? + options: + - Desktop App (Windows) + - Desktop App (macOS) + - Desktop App (Linux) + - CLI Tool + - SSMS Extension + validations: + required: true + + - type: input + id: version + attributes: + label: SQL Performance Studio Version + description: Check the About dialog or the release you downloaded. + placeholder: "e.g., 0.7.0" + validations: + required: true + + - type: input + id: os-version + attributes: + label: Operating System + description: The OS where you're running the app. + placeholder: "e.g., Windows 11 23H2, macOS 15.3, Ubuntu 24.04" + validations: + required: true + + - type: textarea + id: description + attributes: + label: Describe the Bug + description: A clear description of what the bug is. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: How can we reproduce this? + placeholder: | + 1. Open a .sqlplan file + 2. Click on... + 3. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What you expected to happen. + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Behavior + description: What actually happened. + validations: + required: true + + - type: textarea + id: plan-file + attributes: + label: Plan File + description: If applicable, attach or paste the .sqlplan file that triggers the issue. You can redact sensitive table/column names. + validations: + required: false + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain the problem. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index eb93a7f..0e2121f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ -blank_issues_enabled: false -contact_links: - - name: Questions & Discussion - url: https://github.com/erikdarlingdata/PerformanceStudio/discussions - about: Ask questions and discuss SQL Performance Studio with the community +blank_issues_enabled: false +contact_links: + - name: Questions & Discussion + url: https://github.com/erikdarlingdata/PerformanceStudio/discussions + about: Ask questions and discuss SQL Performance Studio with the community diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 1bb2f68..1e3def2 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,50 +1,50 @@ -name: Feature Request -description: Suggest a new feature or enhancement -title: "[FEATURE] " -labels: ["enhancement"] - -body: - - type: checkboxes - id: component - attributes: - label: Which component(s) does this affect? - options: - - label: Desktop App - - label: CLI Tool - - label: SSMS Extension - - label: Plan Analysis Rules - - label: Documentation - validations: - required: true - - - type: textarea - id: problem - attributes: - label: Problem Statement - description: Describe the problem you're trying to solve or the limitation you're facing. - validations: - required: true - - - type: textarea - id: solution - attributes: - label: Proposed Solution - description: Describe your proposed feature or enhancement. - validations: - required: true - - - type: textarea - id: use-case - attributes: - label: Use Case - description: How would you use this feature? Provide a specific example. - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Alternatives Considered - description: Have you considered any alternative solutions or workarounds? - validations: - required: false +name: Feature Request +description: Suggest a new feature or enhancement +title: "[FEATURE] " +labels: ["enhancement"] + +body: + - type: checkboxes + id: component + attributes: + label: Which component(s) does this affect? + options: + - label: Desktop App + - label: CLI Tool + - label: SSMS Extension + - label: Plan Analysis Rules + - label: Documentation + validations: + required: true + + - type: textarea + id: problem + attributes: + label: Problem Statement + description: Describe the problem you're trying to solve or the limitation you're facing. + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Describe your proposed feature or enhancement. + validations: + required: true + + - type: textarea + id: use-case + attributes: + label: Use Case + description: How would you use this feature? Provide a specific example. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Have you considered any alternative solutions or workarounds? + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f67926b..40c785a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,25 +1,25 @@ -## What does this PR do? - -A clear description of the change and why it's being made. - -## Which component(s) does this affect? - -- [ ] Desktop App (PlanViewer.App) -- [ ] Core Library (PlanViewer.Core) -- [ ] CLI Tool (PlanViewer.Cli) -- [ ] SSMS Extension (PlanViewer.Ssms) -- [ ] Tests -- [ ] Documentation - -## How was this tested? - -Describe the testing you've done. Include: -- Plan files tested (estimated, actual, Query Store, etc.) -- Platforms tested (Windows, macOS, Linux) - -## Checklist - -- [ ] I have read the [contributing guide](https://github.com/erikdarlingdata/PerformanceStudio/blob/main/CONTRIBUTING.md) -- [ ] My code builds with zero warnings (`dotnet build -c Debug`) -- [ ] All tests pass (`dotnet test`) -- [ ] I have not introduced any hardcoded credentials or server names +## What does this PR do? + +A clear description of the change and why it's being made. + +## Which component(s) does this affect? + +- [ ] Desktop App (PlanViewer.App) +- [ ] Core Library (PlanViewer.Core) +- [ ] CLI Tool (PlanViewer.Cli) +- [ ] SSMS Extension (PlanViewer.Ssms) +- [ ] Tests +- [ ] Documentation + +## How was this tested? + +Describe the testing you've done. Include: +- Plan files tested (estimated, actual, Query Store, etc.) +- Platforms tested (Windows, macOS, Linux) + +## Checklist + +- [ ] I have read the [contributing guide](https://github.com/erikdarlingdata/PerformanceStudio/blob/main/CONTRIBUTING.md) +- [ ] My code builds with zero warnings (`dotnet build -c Debug`) +- [ ] All tests pass (`dotnet test`) +- [ ] I have not introduced any hardcoded credentials or server names diff --git a/.github/workflows/check-pr-branch.yml b/.github/workflows/check-pr-branch.yml index 88fa6fa..1c14086 100644 --- a/.github/workflows/check-pr-branch.yml +++ b/.github/workflows/check-pr-branch.yml @@ -1,21 +1,21 @@ -name: Check pull request target branch -on: - pull_request_target: - types: - - opened - - reopened - - synchronize - - edited -jobs: - check-branches: - runs-on: ubuntu-latest - steps: - - name: Check branches - env: - HEAD_REF: ${{ github.head_ref }} - BASE_REF: ${{ github.base_ref }} - run: | - if [ "$HEAD_REF" != "dev" ] && [ "$BASE_REF" == "main" ]; then - echo "::error::Pull requests to main are only allowed from dev. Please target the dev branch instead." - exit 1 - fi +name: Check pull request target branch +on: + pull_request_target: + types: + - opened + - reopened + - synchronize + - edited +jobs: + check-branches: + runs-on: ubuntu-latest + steps: + - name: Check branches + env: + HEAD_REF: ${{ github.head_ref }} + BASE_REF: ${{ github.base_ref }} + run: | + if [ "$HEAD_REF" != "dev" ] && [ "$BASE_REF" == "main" ]; then + echo "::error::Pull requests to main are only allowed from dev. Please target the dev branch instead." + exit 1 + fi diff --git a/.github/workflows/check-version-bump.yml b/.github/workflows/check-version-bump.yml index 2a5d22a..b48de67 100644 --- a/.github/workflows/check-version-bump.yml +++ b/.github/workflows/check-version-bump.yml @@ -1,48 +1,48 @@ -name: Check version bump -on: - pull_request: - branches: [main] - -jobs: - check-version: - if: github.head_ref == 'dev' - runs-on: ubuntu-latest - - steps: - - name: Checkout PR branch - uses: actions/checkout@v4 - - - name: Get PR version - id: pr - shell: pwsh - run: | - $version = ([xml](Get-Content src/PlanViewer.App/PlanViewer.App.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } - echo "VERSION=$version" >> $env:GITHUB_OUTPUT - Write-Host "PR version: $version" - - - name: Checkout main - uses: actions/checkout@v4 - with: - ref: main - path: main-branch - - - name: Get main version - id: main - shell: pwsh - run: | - $version = ([xml](Get-Content main-branch/src/PlanViewer.App/PlanViewer.App.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } - echo "VERSION=$version" >> $env:GITHUB_OUTPUT - Write-Host "Main version: $version" - - - name: Compare versions - env: - PR_VERSION: ${{ steps.pr.outputs.VERSION }} - MAIN_VERSION: ${{ steps.main.outputs.VERSION }} - run: | - echo "Main version: $MAIN_VERSION" - echo "PR version: $PR_VERSION" - if [ "$PR_VERSION" == "$MAIN_VERSION" ]; then - echo "::error::Version in PlanViewer.App.csproj ($PR_VERSION) has not changed from main. Bump the version before merging to main." - exit 1 - fi - echo "✅ Version bumped: $MAIN_VERSION → $PR_VERSION" +name: Check version bump +on: + pull_request: + branches: [main] + +jobs: + check-version: + if: github.head_ref == 'dev' + runs-on: ubuntu-latest + + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + + - name: Get PR version + id: pr + shell: pwsh + run: | + $version = ([xml](Get-Content src/PlanViewer.App/PlanViewer.App.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } + echo "VERSION=$version" >> $env:GITHUB_OUTPUT + Write-Host "PR version: $version" + + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: main + path: main-branch + + - name: Get main version + id: main + shell: pwsh + run: | + $version = ([xml](Get-Content main-branch/src/PlanViewer.App/PlanViewer.App.csproj)).Project.PropertyGroup.Version | Where-Object { $_ } + echo "VERSION=$version" >> $env:GITHUB_OUTPUT + Write-Host "Main version: $version" + + - name: Compare versions + env: + PR_VERSION: ${{ steps.pr.outputs.VERSION }} + MAIN_VERSION: ${{ steps.main.outputs.VERSION }} + run: | + echo "Main version: $MAIN_VERSION" + echo "PR version: $PR_VERSION" + if [ "$PR_VERSION" == "$MAIN_VERSION" ]; then + echo "::error::Version in PlanViewer.App.csproj ($PR_VERSION) has not changed from main. Bump the version before merging to main." + exit 1 + fi + echo "✅ Version bumped: $MAIN_VERSION → $PR_VERSION" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 325e626..19e94f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,41 +1,41 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main, dev] - -jobs: - build-and-test: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET 8.0 - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - - name: Install WASM workload - run: dotnet workload install wasm-tools - - - name: Restore dependencies - run: | - dotnet restore src/PlanViewer.Core/PlanViewer.Core.csproj - dotnet restore src/PlanViewer.App/PlanViewer.App.csproj - dotnet restore src/PlanViewer.Cli/PlanViewer.Cli.csproj - dotnet restore src/PlanViewer.Web/PlanViewer.Web.csproj - dotnet restore tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj - - - name: Build all projects - run: | - dotnet build src/PlanViewer.Core/PlanViewer.Core.csproj -c Release --no-restore - dotnet build src/PlanViewer.App/PlanViewer.App.csproj -c Release --no-restore - dotnet build src/PlanViewer.Cli/PlanViewer.Cli.csproj -c Release --no-restore - dotnet build src/PlanViewer.Web/PlanViewer.Web.csproj -c Release --no-restore - dotnet build tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-restore - - - name: Run tests - run: dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-build --verbosity normal +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main, dev] + +jobs: + build-and-test: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET 8.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Install WASM workload + run: dotnet workload install wasm-tools + + - name: Restore dependencies + run: | + dotnet restore src/PlanViewer.Core/PlanViewer.Core.csproj + dotnet restore src/PlanViewer.App/PlanViewer.App.csproj + dotnet restore src/PlanViewer.Cli/PlanViewer.Cli.csproj + dotnet restore src/PlanViewer.Web/PlanViewer.Web.csproj + dotnet restore tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj + + - name: Build all projects + run: | + dotnet build src/PlanViewer.Core/PlanViewer.Core.csproj -c Release --no-restore + dotnet build src/PlanViewer.App/PlanViewer.App.csproj -c Release --no-restore + dotnet build src/PlanViewer.Cli/PlanViewer.Cli.csproj -c Release --no-restore + dotnet build src/PlanViewer.Web/PlanViewer.Web.csproj -c Release --no-restore + dotnet build tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-restore + + - name: Run tests + run: dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-build --verbosity normal diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 822bafc..c035742 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,161 +1,161 @@ -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 src/PlanViewer.App/PlanViewer.App.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 src/PlanViewer.Core/PlanViewer.Core.csproj - dotnet restore src/PlanViewer.App/PlanViewer.App.csproj - dotnet restore src/PlanViewer.Cli/PlanViewer.Cli.csproj - dotnet restore tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj - - - name: Run tests - run: dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --verbosity normal - - - name: Publish App (all platforms) - run: | - dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r win-x64 --self-contained -o publish/win-x64 - dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r linux-x64 --self-contained -o publish/linux-x64 - dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-x64 --self-contained -o publish/osx-x64 - dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-arm64 --self-contained -o publish/osx-arm64 - - - name: Package artifacts - shell: pwsh - env: - VERSION: ${{ steps.version.outputs.VERSION }} - run: | - New-Item -ItemType Directory -Force -Path releases - - # Package Windows and Linux as flat zips - foreach ($rid in @('win-x64', 'linux-x64')) { - if (Test-Path 'README.md') { Copy-Item 'README.md' "publish/$rid/" } - if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "publish/$rid/" } - Compress-Archive -Path "publish/$rid/*" -DestinationPath "releases/PerformanceStudio-$rid-$env:VERSION.zip" -Force - } - - # Package macOS as proper .app bundles - foreach ($rid in @('osx-x64', 'osx-arm64')) { - $appName = "PerformanceStudio.app" - $bundleDir = "publish/$rid-bundle/$appName" - - New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/MacOS" - New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/Resources" - - Copy-Item -Path "publish/$rid/*" -Destination "$bundleDir/Contents/MacOS/" -Recurse - - if (Test-Path "$bundleDir/Contents/MacOS/Info.plist") { - Move-Item -Path "$bundleDir/Contents/MacOS/Info.plist" -Destination "$bundleDir/Contents/Info.plist" -Force - } - - $plist = Get-Content "$bundleDir/Contents/Info.plist" -Raw - $plist = $plist -replace '(CFBundleVersion\s*)[^<]*()', "`${1}$env:VERSION`${2}" - $plist = $plist -replace '(CFBundleShortVersionString\s*)[^<]*()', "`${1}$env:VERSION`${2}" - Set-Content -Path "$bundleDir/Contents/Info.plist" -Value $plist -NoNewline - - if (Test-Path "$bundleDir/Contents/MacOS/EDD.icns") { - Move-Item -Path "$bundleDir/Contents/MacOS/EDD.icns" -Destination "$bundleDir/Contents/Resources/EDD.icns" -Force - } - - $wrapperDir = "publish/$rid-bundle" - if (Test-Path 'README.md') { Copy-Item 'README.md' "$wrapperDir/" } - if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "$wrapperDir/" } - - Compress-Archive -Path "$wrapperDir/*" -DestinationPath "releases/PerformanceStudio-$rid-$env: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/PerformanceStudio/releases/latest). - "@ - - gh release create nightly ` - --target dev ` - --title "Nightly Build ($version)" ` - --notes $body ` - --prerelease ` - releases/*.zip releases/SHA256SUMS.txt +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 src/PlanViewer.App/PlanViewer.App.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 src/PlanViewer.Core/PlanViewer.Core.csproj + dotnet restore src/PlanViewer.App/PlanViewer.App.csproj + dotnet restore src/PlanViewer.Cli/PlanViewer.Cli.csproj + dotnet restore tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj + + - name: Run tests + run: dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --verbosity normal + + - name: Publish App (all platforms) + run: | + dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r win-x64 --self-contained -o publish/win-x64 + dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r linux-x64 --self-contained -o publish/linux-x64 + dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-x64 --self-contained -o publish/osx-x64 + dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r osx-arm64 --self-contained -o publish/osx-arm64 + + - name: Package artifacts + shell: pwsh + env: + VERSION: ${{ steps.version.outputs.VERSION }} + run: | + New-Item -ItemType Directory -Force -Path releases + + # Package Windows and Linux as flat zips + foreach ($rid in @('win-x64', 'linux-x64')) { + if (Test-Path 'README.md') { Copy-Item 'README.md' "publish/$rid/" } + if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "publish/$rid/" } + Compress-Archive -Path "publish/$rid/*" -DestinationPath "releases/PerformanceStudio-$rid-$env:VERSION.zip" -Force + } + + # Package macOS as proper .app bundles + foreach ($rid in @('osx-x64', 'osx-arm64')) { + $appName = "PerformanceStudio.app" + $bundleDir = "publish/$rid-bundle/$appName" + + New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/MacOS" + New-Item -ItemType Directory -Force -Path "$bundleDir/Contents/Resources" + + Copy-Item -Path "publish/$rid/*" -Destination "$bundleDir/Contents/MacOS/" -Recurse + + if (Test-Path "$bundleDir/Contents/MacOS/Info.plist") { + Move-Item -Path "$bundleDir/Contents/MacOS/Info.plist" -Destination "$bundleDir/Contents/Info.plist" -Force + } + + $plist = Get-Content "$bundleDir/Contents/Info.plist" -Raw + $plist = $plist -replace '(CFBundleVersion\s*)[^<]*()', "`${1}$env:VERSION`${2}" + $plist = $plist -replace '(CFBundleShortVersionString\s*)[^<]*()', "`${1}$env:VERSION`${2}" + Set-Content -Path "$bundleDir/Contents/Info.plist" -Value $plist -NoNewline + + if (Test-Path "$bundleDir/Contents/MacOS/EDD.icns") { + Move-Item -Path "$bundleDir/Contents/MacOS/EDD.icns" -Destination "$bundleDir/Contents/Resources/EDD.icns" -Force + } + + $wrapperDir = "publish/$rid-bundle" + if (Test-Path 'README.md') { Copy-Item 'README.md' "$wrapperDir/" } + if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' "$wrapperDir/" } + + Compress-Archive -Path "$wrapperDir/*" -DestinationPath "releases/PerformanceStudio-$rid-$env: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/PerformanceStudio/releases/latest). + "@ + + gh release create nightly ` + --target dev ` + --title "Nightly Build ($version)" ` + --notes $body ` + --prerelease ` + releases/*.zip releases/SHA256SUMS.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee7999f..c21d7b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,89 +1,89 @@ -# Contributing to Performance Studio - -Thank you for your interest in contributing to Performance Studio! This guide will help you get started. - -## Reporting Issues - -- Use [GitHub Issues](https://github.com/erikdarlingdata/PerformanceStudio/issues) for bugs and feature requests -- Include the `.sqlplan` file (or a minimal reproduction) when reporting parser or analysis bugs -- Specify your OS and .NET version - -## Development Setup - -### Prerequisites - -- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) -- Git - -### Build and Test - -```bash -git clone https://github.com/erikdarlingdata/PerformanceStudio.git -cd PerformanceStudio -dotnet build -dotnet test tests/PlanViewer.Core.Tests -``` - -### Run the GUI - -```bash -dotnet run --project src/PlanViewer.App -``` - -### Run the CLI - -```bash -dotnet run --project src/PlanViewer.Cli -- analyze --help -``` - -## Project Structure - -``` -PerformanceStudio/ -├── src/ -│ ├── PlanViewer.Core/ # Analysis engine (parser, rules, layout) -│ ├── PlanViewer.App/ # Avalonia desktop GUI -│ └── PlanViewer.Cli/ # CLI tool (planview command) -└── tests/ - └── PlanViewer.Core.Tests/ # xUnit tests with real .sqlplan fixtures -``` - -## Architecture - -- **PlanViewer.Core** is the shared library. It contains the XML parser (`ShowPlanParser`), analysis rules (`PlanAnalyzer`), plan layout engine, text/JSON formatters, and all models. Both the GUI and CLI depend on it. -- **PlanViewer.App** is an Avalonia 11 desktop app using code-behind (no MVVM framework). It renders plan trees on a Canvas with the same operator icons as SSMS. -- **PlanViewer.Cli** is a System.CommandLine-based CLI tool that wraps Core for command-line use. - -## Code Style - -- File-scoped namespaces (`namespace Foo;`) -- Nullable enabled across all projects -- Code-behind pattern for UI (no MVVM, no ReactiveUI) -- No unnecessary abstractions — keep it simple and direct -- Tests use real `.sqlplan` XML fixtures, not mocks - -## Adding Analysis Rules - -Rules live in `PlanAnalyzer.cs`. Each rule: - -1. Inspects `PlanNode` properties (statement-level rules) or individual operator nodes -2. Adds a `PlanWarning` with `WarningType`, `Message`, and `Severity` (Info, Warning, or Critical) -3. Has a corresponding test in `PlanAnalyzerTests.cs` with a minimal `.sqlplan` fixture - -When adding a rule: -- Add the rule logic to `AnalyzeStatement()` or `AnalyzeNode()` in `PlanAnalyzer.cs` -- Create a minimal `.sqlplan` test fixture in `tests/PlanViewer.Core.Tests/Plans/` -- Add a test method in `PlanAnalyzerTests.cs` -- Ensure all existing tests still pass - -## Pull Requests - -1. Fork the repo and create a feature branch -2. Make your changes -3. Run `dotnet test` — all tests must pass -4. Run `dotnet build` — no warnings or errors -5. Open a PR with a clear description of what changed and why - -## License - -By contributing, you agree that your contributions will be licensed under the MIT License. +# Contributing to Performance Studio + +Thank you for your interest in contributing to Performance Studio! This guide will help you get started. + +## Reporting Issues + +- Use [GitHub Issues](https://github.com/erikdarlingdata/PerformanceStudio/issues) for bugs and feature requests +- Include the `.sqlplan` file (or a minimal reproduction) when reporting parser or analysis bugs +- Specify your OS and .NET version + +## Development Setup + +### Prerequisites + +- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) +- Git + +### Build and Test + +```bash +git clone https://github.com/erikdarlingdata/PerformanceStudio.git +cd PerformanceStudio +dotnet build +dotnet test tests/PlanViewer.Core.Tests +``` + +### Run the GUI + +```bash +dotnet run --project src/PlanViewer.App +``` + +### Run the CLI + +```bash +dotnet run --project src/PlanViewer.Cli -- analyze --help +``` + +## Project Structure + +``` +PerformanceStudio/ +├── src/ +│ ├── PlanViewer.Core/ # Analysis engine (parser, rules, layout) +│ ├── PlanViewer.App/ # Avalonia desktop GUI +│ └── PlanViewer.Cli/ # CLI tool (planview command) +└── tests/ + └── PlanViewer.Core.Tests/ # xUnit tests with real .sqlplan fixtures +``` + +## Architecture + +- **PlanViewer.Core** is the shared library. It contains the XML parser (`ShowPlanParser`), analysis rules (`PlanAnalyzer`), plan layout engine, text/JSON formatters, and all models. Both the GUI and CLI depend on it. +- **PlanViewer.App** is an Avalonia 11 desktop app using code-behind (no MVVM framework). It renders plan trees on a Canvas with the same operator icons as SSMS. +- **PlanViewer.Cli** is a System.CommandLine-based CLI tool that wraps Core for command-line use. + +## Code Style + +- File-scoped namespaces (`namespace Foo;`) +- Nullable enabled across all projects +- Code-behind pattern for UI (no MVVM, no ReactiveUI) +- No unnecessary abstractions — keep it simple and direct +- Tests use real `.sqlplan` XML fixtures, not mocks + +## Adding Analysis Rules + +Rules live in `PlanAnalyzer.cs`. Each rule: + +1. Inspects `PlanNode` properties (statement-level rules) or individual operator nodes +2. Adds a `PlanWarning` with `WarningType`, `Message`, and `Severity` (Info, Warning, or Critical) +3. Has a corresponding test in `PlanAnalyzerTests.cs` with a minimal `.sqlplan` fixture + +When adding a rule: +- Add the rule logic to `AnalyzeStatement()` or `AnalyzeNode()` in `PlanAnalyzer.cs` +- Create a minimal `.sqlplan` test fixture in `tests/PlanViewer.Core.Tests/Plans/` +- Add a test method in `PlanAnalyzerTests.cs` +- Ensure all existing tests still pass + +## Pull Requests + +1. Fork the repo and create a feature branch +2. Make your changes +3. Run `dotnet test` — all tests must pass +4. Run `dotnet build` — no warnings or errors +5. Open a PR with a clear description of what changed and why + +## License + +By contributing, you agree that your contributions will be licensed under the MIT License. diff --git a/LICENSE b/LICENSE index 62a9a26..6ada7e9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2026 Erik Darling, Darling Data LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2026 Erik Darling, Darling Data LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PlanViewer.sln b/PlanViewer.sln index 685bc60..8abfbc2 100644 --- a/PlanViewer.sln +++ b/PlanViewer.sln @@ -1,57 +1,57 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{21F75D2E-F228-49B3-825F-43F621760061}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.Core", "src\PlanViewer.Core\PlanViewer.Core.csproj", "{8904045D-E083-4010-A320-104B7466C044}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.App", "src\PlanViewer.App\PlanViewer.App.csproj", "{F1018F88-B289-40CE-852A-56478DFBA91E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.Cli", "src\PlanViewer.Cli\PlanViewer.Cli.csproj", "{1504CE29-3CBF-4F0B-A46E-54644946B8ED}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.Web", "src\PlanViewer.Web\PlanViewer.Web.csproj", "{B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A06217BE-DBE2-47D0-BD59-93F2108D447C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.Core.Tests", "tests\PlanViewer.Core.Tests\PlanViewer.Core.Tests.csproj", "{399A69AD-0CD1-4E9B-9988-E94882B827E6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8904045D-E083-4010-A320-104B7466C044}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8904045D-E083-4010-A320-104B7466C044}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8904045D-E083-4010-A320-104B7466C044}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8904045D-E083-4010-A320-104B7466C044}.Release|Any CPU.Build.0 = Release|Any CPU - {F1018F88-B289-40CE-852A-56478DFBA91E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F1018F88-B289-40CE-852A-56478DFBA91E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F1018F88-B289-40CE-852A-56478DFBA91E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F1018F88-B289-40CE-852A-56478DFBA91E}.Release|Any CPU.Build.0 = Release|Any CPU - {1504CE29-3CBF-4F0B-A46E-54644946B8ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1504CE29-3CBF-4F0B-A46E-54644946B8ED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1504CE29-3CBF-4F0B-A46E-54644946B8ED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1504CE29-3CBF-4F0B-A46E-54644946B8ED}.Release|Any CPU.Build.0 = Release|Any CPU - {399A69AD-0CD1-4E9B-9988-E94882B827E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {399A69AD-0CD1-4E9B-9988-E94882B827E6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {399A69AD-0CD1-4E9B-9988-E94882B827E6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {399A69AD-0CD1-4E9B-9988-E94882B827E6}.Release|Any CPU.Build.0 = Release|Any CPU - {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {8904045D-E083-4010-A320-104B7466C044} = {21F75D2E-F228-49B3-825F-43F621760061} - {F1018F88-B289-40CE-852A-56478DFBA91E} = {21F75D2E-F228-49B3-825F-43F621760061} - {1504CE29-3CBF-4F0B-A46E-54644946B8ED} = {21F75D2E-F228-49B3-825F-43F621760061} - {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D} = {21F75D2E-F228-49B3-825F-43F621760061} - {399A69AD-0CD1-4E9B-9988-E94882B827E6} = {A06217BE-DBE2-47D0-BD59-93F2108D447C} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{21F75D2E-F228-49B3-825F-43F621760061}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.Core", "src\PlanViewer.Core\PlanViewer.Core.csproj", "{8904045D-E083-4010-A320-104B7466C044}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.App", "src\PlanViewer.App\PlanViewer.App.csproj", "{F1018F88-B289-40CE-852A-56478DFBA91E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.Cli", "src\PlanViewer.Cli\PlanViewer.Cli.csproj", "{1504CE29-3CBF-4F0B-A46E-54644946B8ED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.Web", "src\PlanViewer.Web\PlanViewer.Web.csproj", "{B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A06217BE-DBE2-47D0-BD59-93F2108D447C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanViewer.Core.Tests", "tests\PlanViewer.Core.Tests\PlanViewer.Core.Tests.csproj", "{399A69AD-0CD1-4E9B-9988-E94882B827E6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8904045D-E083-4010-A320-104B7466C044}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8904045D-E083-4010-A320-104B7466C044}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8904045D-E083-4010-A320-104B7466C044}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8904045D-E083-4010-A320-104B7466C044}.Release|Any CPU.Build.0 = Release|Any CPU + {F1018F88-B289-40CE-852A-56478DFBA91E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1018F88-B289-40CE-852A-56478DFBA91E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1018F88-B289-40CE-852A-56478DFBA91E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1018F88-B289-40CE-852A-56478DFBA91E}.Release|Any CPU.Build.0 = Release|Any CPU + {1504CE29-3CBF-4F0B-A46E-54644946B8ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1504CE29-3CBF-4F0B-A46E-54644946B8ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1504CE29-3CBF-4F0B-A46E-54644946B8ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1504CE29-3CBF-4F0B-A46E-54644946B8ED}.Release|Any CPU.Build.0 = Release|Any CPU + {399A69AD-0CD1-4E9B-9988-E94882B827E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {399A69AD-0CD1-4E9B-9988-E94882B827E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {399A69AD-0CD1-4E9B-9988-E94882B827E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {399A69AD-0CD1-4E9B-9988-E94882B827E6}.Release|Any CPU.Build.0 = Release|Any CPU + {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8904045D-E083-4010-A320-104B7466C044} = {21F75D2E-F228-49B3-825F-43F621760061} + {F1018F88-B289-40CE-852A-56478DFBA91E} = {21F75D2E-F228-49B3-825F-43F621760061} + {1504CE29-3CBF-4F0B-A46E-54644946B8ED} = {21F75D2E-F228-49B3-825F-43F621760061} + {B2D3F7A1-8C4E-4F5A-9D6B-1E2F3A4B5C6D} = {21F75D2E-F228-49B3-825F-43F621760061} + {399A69AD-0CD1-4E9B-9988-E94882B827E6} = {A06217BE-DBE2-47D0-BD59-93F2108D447C} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 9a22442..cbfed00 100644 --- a/README.md +++ b/README.md @@ -1,522 +1,522 @@ -# Performance Studio - -

- GitHub Stars - GitHub Forks - License: MIT - Latest Release - Open Issues - Last Commit - CI -

-

- Follow @ErikDarlingData on X - YouTube Subscribe - LinkedIn Connect - Blog -

- -A cross-platform SQL Server execution plan analyzer with built-in MCP server for AI-assisted analysis. Parses `.sqlplan` XML, identifies performance problems, suggests missing indexes, and provides actionable warnings — from the command line or a desktop GUI. - -Built for developers and DBAs who want fast, automated plan analysis without clicking through SSMS. - -## Screenshots - -### Query Editor -Write queries with syntax highlighting and SQL keyword completion, connect to any SQL Server, and capture plans with one click. - -![Query Editor](screenshots/Query%20Editor.png) - -### Actual Execution Plan with Plan Insights -Graphical plan tree with SSMS-style operator icons, cost percentages, row counts, and warning badges. The Plan Insights panel shows runtime summary, missing indexes, parameters, and wait stats at a glance. - -![Actual Execution Plan](screenshots/Actual%20Execution%20Plan.png) - -### Multi-Statement Navigation -Navigate stored procedures and batches with multiple statements. Click any statement in the grid to jump to its plan. Plan Insights shows parameters with compiled vs runtime values. - -![Navigate Stored Procedure Statements and Plans](screenshots/Navigate%20Stored%20Procedure%20Statements%20and%20Plans.png) - -### Operator Tooltip and Properties -Hover over any operator for a detailed tooltip with costs, rows, I/O, timing, parallelism, and warnings. Click to open the full properties panel with per-thread timing, predicates, and more. - -![Operator Tooltip](screenshots/Actual%20Execution%20Plan%20With%20Warning%20Tool%20Tip.png) - -![Operator Properties](screenshots/Operator%20Properties.png) - -### Advice for Humans -One-click text report with server context, warnings, wait stats, and expensive operators — ready to read or share. - -![Advice for Humans](screenshots/Advice%20For%20Humans.png) - -### Plan Comparison -Side-by-side comparison of two plans showing cost, runtime, I/O, memory, and wait stat differences. - -![Plan Comparison](screenshots/Plan%20Comparison.png) - -### Query Store Integration -Fetch top queries by CPU, duration, logical reads, physical reads, writes, memory, or executions from Query Store and load their plans directly into the analyzer. - -![Query Store Integration](screenshots/performance_studio_querystore_analysis_top_cpu_by_query_hash.png) - -### Minimap and colored links by accuracy ratio divergence -The minimap provides a high-level overview of the entire plan, allowing you to quickly navigate to areas of interest. Colored links between operators indicate accuracy ratio divergence, helping you identify where estimates are most off from actuals. -![Minimap and Colored Links](screenshots/minimap_and_planviewer_colored_actual_plan.png) - -### MCP Integration -Ask Claude Code to analyze loaded plans, identify warnings, suggest indexes, and compare plans — all through the built-in MCP server. - -![MCP Integration](screenshots/MCP%20Integration.png) - -## What It Does - -Feed it a query plan and it tells you what's wrong: - -- **Large memory grants** — flags queries hoarding memory they don't use -- **Row estimate mismatches** — finds operators where estimates are 10x+ off from actuals -- **Missing indexes** — extracts SQL Server's index suggestions with ready-to-run CREATE statements -- **Hash, sort, and exchange spills** — identifies operators spilling to TempDB with severity based on volume -- **Parallel skew** — detects threads doing all the work while others sit idle -- **Scan predicates** — warns when scans filter rows with residual predicates -- **Key and RID lookups** — flags lookups back to the base table, distinguishes heaps from clustered indexes -- **Late filters** — finds Filter operators discarding rows deep in the plan -- **Nested loop concerns** — flags high-execution nested loops that might be better as hash joins -- **Parameter sniffing** — compares compiled vs runtime parameter values -- **Scalar UDFs** — warns about T-SQL and CLR scalar functions in execution paths -- **Implicit conversions** — detects type mismatches, upgrades severity when a seek plan is prevented -- **Anti-patterns** — OPTIMIZE FOR UNKNOWN, NOT IN with nullable columns, leading wildcards, function-wrapped predicates, and more - -Each warning includes severity (Info, Warning, or Critical), the operator node ID, and enough context to act on immediately. - -## Prerequisites - -- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (required to build and run) -- SQL Server instance (optional — only needed for live plan capture; file analysis works without one) -- Docker (optional — macOS/Linux users can run SQL Server locally via Docker) - -## Download - -Pre-built binaries are available on the [Releases](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest) page: - -| Platform | Download | -|----------|----------| -| Windows (x64) | [PerformanceStudio-win-x64.zip](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest/download/PerformanceStudio-win-x64.zip) | -| macOS (Apple Silicon) | [PerformanceStudio-osx-arm64.zip](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest/download/PerformanceStudio-osx-arm64.zip) | -| macOS (Intel) | [PerformanceStudio-osx-x64.zip](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest/download/PerformanceStudio-osx-x64.zip) | -| Linux (x64) | [PerformanceStudio-linux-x64.zip](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest/download/PerformanceStudio-linux-x64.zip) | - -These are self-contained — no .NET SDK required. Extract the zip and run. - -**macOS note:** macOS may block the app because it isn't signed with an Apple Developer certificate. If you see a warning that the app "can't be opened," run this after extracting: - -```bash -xattr -cr PerformanceStudio.app -``` - -Then open the app normally. - -## Build from Source - -Clone and build: - -```bash -git clone https://github.com/erikdarlingdata/PerformanceStudio.git -cd PerformanceStudio -dotnet build -``` - -To verify the build: - -```bash -dotnet test tests/PlanViewer.Core.Tests # 37 tests should pass -dotnet run --project src/PlanViewer.Cli -- analyze --help -``` - -## Quick Start - -### Analyze an existing .sqlplan file - -If you already have a `.sqlplan` file (saved from SSMS, Azure Data Studio, or another tool): - -```bash -# JSON output (default) — full operator tree, suitable for automation -planview analyze my_query.sqlplan - -# Human-readable text output -planview analyze my_query.sqlplan --output text - -# Text output, warnings and missing indexes only (skip operator tree) -planview analyze my_query.sqlplan --output text --warnings-only -``` - -### Capture and analyze plans from a live server - -Connect to a SQL Server instance, run queries, and capture their execution plans automatically. - -**Quickest way** — pass credentials directly: - -```bash -# Capture an actual execution plan (the query WILL run) -planview analyze --server sql2022 --database AdventureWorks \ - --login sa --password YourPassword \ - --query "SELECT * FROM Sales.SalesOrderHeader WHERE OrderDate > '2024-01-01'" \ - --trust-cert --output-dir ./results/ - -# Capture an estimated plan (safe for production — query is NOT executed) -planview analyze --server sql2022 --database AdventureWorks \ - --login sa --password YourPassword \ - --query "SELECT * FROM Sales.SalesOrderHeader" \ - --estimated --trust-cert --output-dir ./results/ -``` - -**Using a .env file** — drop a `.env` in your working directory to avoid repeating connection details: - -```bash -# .env -PLANVIEW_SERVER=sql2022 -PLANVIEW_DATABASE=AdventureWorks -PLANVIEW_LOGIN=sa -PLANVIEW_PASSWORD=YourPassword -PLANVIEW_TRUST_CERT=true -``` - -Then just run: - -```bash -planview analyze --query "SELECT * FROM Sales.SalesOrderHeader" -planview analyze ./queries/ --output-dir ./results/ -``` - -CLI arguments override `.env` values when both are provided. - -**Using the credential store** — for longer-term use, store credentials in your OS keychain: - -```bash -# Store credentials (once per server) -planview credential add sql2022 --user sa -# You'll be prompted for the password — it's stored in your OS credential store - -# Now connect without --login/--password -planview analyze --server sql2022 --database AdventureWorks \ - --query "SELECT * FROM Sales.SalesOrderHeader" \ - --trust-cert --output-dir ./results/ -``` - -**Batch processing** a folder of .sql files: - -```bash -planview analyze ./queries/ --server sql2022 --database StackOverflow2013 \ - --login sa --password YourPassword \ - --trust-cert --output-dir ./results/ -``` - -Batch mode produces three files per query: -- `query_name.sqlplan` — the raw execution plan XML (openable in SSMS or the Performance Studio GUI) -- `query_name.analysis.json` — structured analysis with warnings, missing indexes, and operator tree -- `query_name.analysis.txt` — human-readable text report - -### Manage credentials - -```bash -planview credential add my-server --user sa # prompts for password -planview credential add my-server --user sa -p pwd # non-interactive -planview credential list # show stored credentials -planview credential remove my-server # delete credential -``` - -Credentials are stored in the OS credential store — Windows Credential Manager on Windows, Apple Keychain on macOS. Nothing is written to disk in plaintext. - -## Example Output - -These examples were generated against StackOverflow2013 on SQL Server 2022. Source queries are in [`examples/queries/`](examples/queries/), plans and analysis in [`examples/output/`](examples/output/). - -### Text output (`--output text`) - -``` -Plan: 04_comment_heavy_posts.sqlplan -SQL Server: 1.564 (build 16.0.4222.2) -Statements: 1 - ---- Statement 1: SELECT --- - Query: SELECT p.Id, p.Title, p.Score, COUNT(c.Id) AS CommentCount - FROM dbo.Posts AS p JOIN dbo.Comments AS c ON c.PostId = p.Id - WHERE p.PostTypeId = 1 GROUP BY p.Id, p.Title, p.Score - HAVING COUNT(c.Id) > 20 ORDER BY CommentCount DESC - Estimated cost: 4069.8700 - DOP: 8 - Runtime: 4551ms elapsed, 15049ms CPU - Memory grant: 8,022,664 KB granted, 2,514,944 KB used - - Warnings: - [Critical] Large Memory Grant: Query granted 7835 MB of memory. - - Operator warnings: - [Critical] Parallelism (Node 0): Estimated 1 rows, actual 2,889 (2889x underestimated). - [Critical] Sort (Node 1): Estimated 1 rows, actual 2,889 (2889x underestimated). - [Warning] Sort (Node 1): Thread 1 processed 100% of rows. Work is heavily skewed. - [Warning] Filter (Node 2): Filter discards rows late in the plan. - - Missing indexes: - StackOverflow2013.dbo.Posts (impact: 74%) - CREATE NONCLUSTERED INDEX [IX_Posts_PostTypeId] - ON dbo.Posts (PostTypeId) INCLUDE (Score, Title) - StackOverflow2013.dbo.Comments (impact: 19%) - CREATE NONCLUSTERED INDEX [IX_Comments_PostId] - ON dbo.Comments (PostId) - -=== Summary === - Warnings: 8 (4 critical) - Missing indexes: 2 - Actual stats: yes - Warning types: Filter Operator, Large Memory Grant, Parallel Skew, - Row Estimate Mismatch, Scan With Predicate -``` - -### JSON output (default) - -The default JSON output includes the full operator tree, making it suitable for CI pipelines, LLM consumption, or further processing. See [`examples/output/`](examples/output/) for complete examples. - -### Batch processing - -``` -$ planview analyze ./examples/queries/ --server sql2022 \ - --database StackOverflow2013 --trust-cert --output-dir ./results/ - -Capturing actual plans from sql2022/StackOverflow2013 - -[1/5] 01_top_users_by_posts ... OK (1.8s) -[2/5] 02_recent_questions ... OK (0.8s) -[3/5] 03_unanswered_high_score ... OK (0.7s) -[4/5] 04_comment_heavy_posts ... OK (4.7s) -[5/5] 05_user_vote_summary ... OK (4.3s) - -Processed 5 files: 5 succeeded, 0 failed -Output: ./results/ -``` - -## Desktop GUI - -The Avalonia-based GUI renders execution plans visually with the same operator icons as SSMS. Open `.sqlplan` files via File > Open or drag-and-drop. - -Features: -- Graphical plan tree with cost percentages and row counts -- Warning badge on root node showing total warning count -- Plan Insights panel — three-column view with runtime summary, missing indexes, and wait stats visualization -- Zoom and pan (mouse wheel + middle-click drag) -- Minimap for quick navigation of large plans -- Color-coded links between operators based on accuracy ratio divergence (estimates vs actuals) -- Click any operator to see full properties (30 sections) -- Statement grid with sortable columns (cost, rows, DOP, warnings) -- Tooltips on hover with key operator metrics -- **Advice for Humans** — one-click text analysis report you can read or share -- **Advice for Robots** — one-click JSON export designed for LLMs and automation -- **Plan Comparison** — compare two plans side-by-side (cost, runtime, I/O, memory, wait stats) -- **Copy Repro Script** — extracts parameters, SET options, and query text into a runnable `sp_executesql` script -- **Get Actual Plan** — connect to a server and re-execute the query to capture runtime stats -- **Query Store Analysis** — connect to a server and analyze top queries by CPU, duration, or reads -- **Query History** — view a history of executed queries with their plans along the timeline and metrics from query store -- **MCP Server** — built-in Model Context Protocol server for AI-assisted plan analysis (opt-in) -- Dark theme - -```bash -dotnet run --project src/PlanViewer.App -``` - -## SSMS Extension - -A VSIX extension that adds **"Open in Performance Studio"** to the execution plan right-click context menu in SSMS 18-22. - -### How it works - -1. Right-click on any execution plan in SSMS -2. Click "Open in Performance Studio" -3. The extension extracts the plan XML via reflection and saves it to a temp file -4. Performance Studio opens with the plan loaded - -### Installation - -1. Download `PlanViewer.Ssms.vsix` and `InstallSsmsExtension.exe` from the [v0.7.0 release](https://github.com/erikdarlingdata/PerformanceStudio/releases/tag/v0.7.0) (SSMS extension is not yet included in automated builds) -2. Place them in the same folder -3. Double-click `InstallSsmsExtension.exe` and approve the UAC prompt -4. The installer auto-detects SSMS 21 and/or SSMS 22 and installs into both -5. Restart SSMS to activate the extension - -### First run - -On first use, if Performance Studio isn't found automatically, the extension will prompt you to locate `PlanViewer.App.exe`. The path is saved to the registry (`HKCU\SOFTWARE\DarlingData\SQLPerformanceStudio\InstallPath`) so you only need to do this once. - -The extension searches for the app in this order: -1. Registry key (set automatically after first browse) -2. System PATH -3. Common install locations (`%LOCALAPPDATA%\Programs\SQLPerformanceStudio\`, `Program Files`, etc.) - -## MCP Server (LLM Integration) - -The desktop GUI includes an embedded [Model Context Protocol](https://modelcontextprotocol.io) server that exposes loaded execution plans and Query Store data to LLM clients like Claude Code and Cursor. - -### Setup - -1. Enable the MCP server in `~/.planview/settings.json`: - -```json -{ - "mcp_enabled": true, - "mcp_port": 5152 -} -``` - -2. Register with Claude Code: - -``` -claude mcp add --transport http --scope user performance-studio http://localhost:5152/ -``` - -3. Open a new Claude Code session and ask questions like: - - "What plans are loaded in the application?" - - "Analyze the execution plan and tell me what's wrong" - - "Are there any missing index suggestions?" - - "Compare these two plans — which is better?" - - "Fetch the top 10 queries by CPU from Query Store" - -### Available Tools - -13 tools for plan analysis and Query Store data: - -| Category | Tools | -|---|---| -| Discovery | `list_plans`, `get_connections` | -| Plan Analysis | `analyze_plan`, `get_plan_summary`, `get_plan_warnings`, `get_missing_indexes`, `get_plan_parameters`, `get_expensive_operators`, `get_plan_xml`, `compare_plans`, `get_repro_script` | -| Query Store | `check_query_store`, `get_query_store_top` | - -Plan analysis tools work on plans loaded in the app (via file open, paste, query execution, or Query Store fetch). Query Store tools use a built-in read-only DMV query — no arbitrary SQL can be executed. - -The MCP server binds to `localhost` only and does not accept remote connections. Disabled by default. - -## Project Structure - -``` -PerformanceStudio/ -├── src/ -│ ├── PlanViewer.Core/ # Analysis engine (parser, 30 rules, layout) -│ ├── PlanViewer.App/ # Avalonia desktop GUI -│ ├── PlanViewer.Cli/ # CLI tool (planview command) -│ ├── PlanViewer.Ssms/ # SSMS extension (.vsix, .NET Framework 4.7.2) -│ └── PlanViewer.Ssms.Installer/ # SSMS extension installer (auto-detects SSMS 21/22) -├── tests/ -│ └── PlanViewer.Core.Tests/ # 37 xUnit tests with real .sqlplan fixtures -├── examples/ -│ ├── plans/ # Sample .sqlplan files for testing -│ ├── queries/ # Sample .sql files -│ └── output/ # Generated .sqlplan, .analysis.json, .txt -└── PlanViewer.sln -``` - -## CLI Reference - -### `planview analyze` - -``` -Usage: planview analyze [] [options] - -Arguments: - .sqlplan file, .sql file, or directory of .sql files - -Options: - --stdin Read plan XML from stdin - -o, --output json (default) or text - --compact Compact JSON (no indentation) - --warnings-only Skip operator tree, only output warnings and indexes - -s, --server SQL Server name (matches credential store key) - -d, --database Database context for execution - -q, --query Inline SQL text to execute - --output-dir Directory for output files - --estimated Estimated plan only (query is NOT executed) - --auth windows, sql, or entra (default: auto-detect) - --trust-cert Trust server certificate - --timeout Query timeout (default: 60) - --login SQL Server login (bypasses credential store) - --password SQL Server password (bypasses credential store) -``` - -### `planview credential` - -``` -planview credential add --user [-p ] -planview credential list -planview credential remove -``` - -## Authentication - -There are three ways to authenticate, in order of precedence: - -1. **`--login` / `--password`** — passed directly on the command line (or via `.env` file). Simplest for dev/test. -2. **Credential store** — stored in Windows Credential Manager or Apple Keychain via `planview credential add`. Best for repeated use. -3. **Windows Authentication** — used automatically when no SQL credentials are found. Requires a valid Kerberos ticket. - -Override the auto-detection with `--auth windows`, `--auth sql`, or `--auth entra`. - -**macOS note:** Windows Authentication does not work on macOS (no Kerberos ticket by default). Use `--login`/`--password`, the credential store, or `--auth entra` instead. - -## Platform Support - -| Platform | GUI | CLI | Credential Store | -|----------|-----|-----|-----------------| -| Windows | Yes | Yes | Windows Credential Manager | -| macOS | Yes | Yes | Apple Keychain | -| Linux | Yes | Yes | Not yet (file analysis works) | - -### macOS: SQL Server via Docker - -macOS users need a SQL Server instance to use the live capture features. The easiest path is Docker: - -```bash -docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=YourPassword123" \ - -p 1433:1433 --name sql_server \ - -d mcr.microsoft.com/mssql/server:2022-latest - -# Store the credential -planview credential add localhost --user sa -p YourPassword123 - -# Test connectivity -planview analyze --server localhost --database master --trust-cert \ - --query "SELECT @@VERSION" -o text -``` - -Always use `--trust-cert` with local Docker instances. - -## Analysis Rules - -The analyzer runs 30 rules against each plan, covering: - -| Category | Rules | -|----------|-------| -| Memory | Large grants, grant vs used ratio, spills to TempDB (including exchange spills) | -| Estimates | Row estimate mismatches (10x+), zero-row actuals, row goals | -| Indexes | Missing index suggestions, key lookups, RID lookups, scan with residual predicates | -| Parallelism | Serial plan reasons, thread skew, ineffective parallelism, DOP reporting | -| Joins | Nested loop high executions, many-to-many merge join worktables | -| Filters | Late filter operators with impact quantification, function-wrapped predicates | -| Functions | Scalar UDF detection (T-SQL and CLR), UDF timing | -| Parameters | Compiled vs runtime values, sniffing issue detection | -| Patterns | Leading wildcards, implicit conversions (with seek plan severity upgrade), OPTIMIZE FOR UNKNOWN, NOT IN with nullable columns, OR expansion, CASE in predicates | -| Compilation | High compile CPU, compile memory exceeded, early abort | -| Objects | Table variables, table-valued functions, CTE multiple references, eager index spools, lazy spools, row count spools | -| Operators | Operator self-time calculation with per-thread awareness for parallel plans | - -Rules can be disabled or have their severity overridden via a `.planview.json` config file. See the `--config` option. - -## Sponsors - - - - - - -
SignPathFree code signing on Windows provided by SignPath.io, certificate by SignPath Foundation
- -## License - -MIT — see [LICENSE](LICENSE). - -Execution plan operator icons are from Microsoft's [vscode-mssql](https://github.com/microsoft/vscode-mssql) extension (MIT). See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for details. +# Performance Studio + +

+ GitHub Stars + GitHub Forks + License: MIT + Latest Release + Open Issues + Last Commit + CI +

+

+ Follow @ErikDarlingData on X + YouTube Subscribe + LinkedIn Connect + Blog +

+ +A cross-platform SQL Server execution plan analyzer with built-in MCP server for AI-assisted analysis. Parses `.sqlplan` XML, identifies performance problems, suggests missing indexes, and provides actionable warnings — from the command line or a desktop GUI. + +Built for developers and DBAs who want fast, automated plan analysis without clicking through SSMS. + +## Screenshots + +### Query Editor +Write queries with syntax highlighting and SQL keyword completion, connect to any SQL Server, and capture plans with one click. + +![Query Editor](screenshots/Query%20Editor.png) + +### Actual Execution Plan with Plan Insights +Graphical plan tree with SSMS-style operator icons, cost percentages, row counts, and warning badges. The Plan Insights panel shows runtime summary, missing indexes, parameters, and wait stats at a glance. + +![Actual Execution Plan](screenshots/Actual%20Execution%20Plan.png) + +### Multi-Statement Navigation +Navigate stored procedures and batches with multiple statements. Click any statement in the grid to jump to its plan. Plan Insights shows parameters with compiled vs runtime values. + +![Navigate Stored Procedure Statements and Plans](screenshots/Navigate%20Stored%20Procedure%20Statements%20and%20Plans.png) + +### Operator Tooltip and Properties +Hover over any operator for a detailed tooltip with costs, rows, I/O, timing, parallelism, and warnings. Click to open the full properties panel with per-thread timing, predicates, and more. + +![Operator Tooltip](screenshots/Actual%20Execution%20Plan%20With%20Warning%20Tool%20Tip.png) + +![Operator Properties](screenshots/Operator%20Properties.png) + +### Advice for Humans +One-click text report with server context, warnings, wait stats, and expensive operators — ready to read or share. + +![Advice for Humans](screenshots/Advice%20For%20Humans.png) + +### Plan Comparison +Side-by-side comparison of two plans showing cost, runtime, I/O, memory, and wait stat differences. + +![Plan Comparison](screenshots/Plan%20Comparison.png) + +### Query Store Integration +Fetch top queries by CPU, duration, logical reads, physical reads, writes, memory, or executions from Query Store and load their plans directly into the analyzer. + +![Query Store Integration](screenshots/performance_studio_querystore_analysis_top_cpu_by_query_hash.png) + +### Minimap and colored links by accuracy ratio divergence +The minimap provides a high-level overview of the entire plan, allowing you to quickly navigate to areas of interest. Colored links between operators indicate accuracy ratio divergence, helping you identify where estimates are most off from actuals. +![Minimap and Colored Links](screenshots/minimap_and_planviewer_colored_actual_plan.png) + +### MCP Integration +Ask Claude Code to analyze loaded plans, identify warnings, suggest indexes, and compare plans — all through the built-in MCP server. + +![MCP Integration](screenshots/MCP%20Integration.png) + +## What It Does + +Feed it a query plan and it tells you what's wrong: + +- **Large memory grants** — flags queries hoarding memory they don't use +- **Row estimate mismatches** — finds operators where estimates are 10x+ off from actuals +- **Missing indexes** — extracts SQL Server's index suggestions with ready-to-run CREATE statements +- **Hash, sort, and exchange spills** — identifies operators spilling to TempDB with severity based on volume +- **Parallel skew** — detects threads doing all the work while others sit idle +- **Scan predicates** — warns when scans filter rows with residual predicates +- **Key and RID lookups** — flags lookups back to the base table, distinguishes heaps from clustered indexes +- **Late filters** — finds Filter operators discarding rows deep in the plan +- **Nested loop concerns** — flags high-execution nested loops that might be better as hash joins +- **Parameter sniffing** — compares compiled vs runtime parameter values +- **Scalar UDFs** — warns about T-SQL and CLR scalar functions in execution paths +- **Implicit conversions** — detects type mismatches, upgrades severity when a seek plan is prevented +- **Anti-patterns** — OPTIMIZE FOR UNKNOWN, NOT IN with nullable columns, leading wildcards, function-wrapped predicates, and more + +Each warning includes severity (Info, Warning, or Critical), the operator node ID, and enough context to act on immediately. + +## Prerequisites + +- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (required to build and run) +- SQL Server instance (optional — only needed for live plan capture; file analysis works without one) +- Docker (optional — macOS/Linux users can run SQL Server locally via Docker) + +## Download + +Pre-built binaries are available on the [Releases](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest) page: + +| Platform | Download | +|----------|----------| +| Windows (x64) | [PerformanceStudio-win-x64.zip](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest/download/PerformanceStudio-win-x64.zip) | +| macOS (Apple Silicon) | [PerformanceStudio-osx-arm64.zip](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest/download/PerformanceStudio-osx-arm64.zip) | +| macOS (Intel) | [PerformanceStudio-osx-x64.zip](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest/download/PerformanceStudio-osx-x64.zip) | +| Linux (x64) | [PerformanceStudio-linux-x64.zip](https://github.com/erikdarlingdata/PerformanceStudio/releases/latest/download/PerformanceStudio-linux-x64.zip) | + +These are self-contained — no .NET SDK required. Extract the zip and run. + +**macOS note:** macOS may block the app because it isn't signed with an Apple Developer certificate. If you see a warning that the app "can't be opened," run this after extracting: + +```bash +xattr -cr PerformanceStudio.app +``` + +Then open the app normally. + +## Build from Source + +Clone and build: + +```bash +git clone https://github.com/erikdarlingdata/PerformanceStudio.git +cd PerformanceStudio +dotnet build +``` + +To verify the build: + +```bash +dotnet test tests/PlanViewer.Core.Tests # 37 tests should pass +dotnet run --project src/PlanViewer.Cli -- analyze --help +``` + +## Quick Start + +### Analyze an existing .sqlplan file + +If you already have a `.sqlplan` file (saved from SSMS, Azure Data Studio, or another tool): + +```bash +# JSON output (default) — full operator tree, suitable for automation +planview analyze my_query.sqlplan + +# Human-readable text output +planview analyze my_query.sqlplan --output text + +# Text output, warnings and missing indexes only (skip operator tree) +planview analyze my_query.sqlplan --output text --warnings-only +``` + +### Capture and analyze plans from a live server + +Connect to a SQL Server instance, run queries, and capture their execution plans automatically. + +**Quickest way** — pass credentials directly: + +```bash +# Capture an actual execution plan (the query WILL run) +planview analyze --server sql2022 --database AdventureWorks \ + --login sa --password YourPassword \ + --query "SELECT * FROM Sales.SalesOrderHeader WHERE OrderDate > '2024-01-01'" \ + --trust-cert --output-dir ./results/ + +# Capture an estimated plan (safe for production — query is NOT executed) +planview analyze --server sql2022 --database AdventureWorks \ + --login sa --password YourPassword \ + --query "SELECT * FROM Sales.SalesOrderHeader" \ + --estimated --trust-cert --output-dir ./results/ +``` + +**Using a .env file** — drop a `.env` in your working directory to avoid repeating connection details: + +```bash +# .env +PLANVIEW_SERVER=sql2022 +PLANVIEW_DATABASE=AdventureWorks +PLANVIEW_LOGIN=sa +PLANVIEW_PASSWORD=YourPassword +PLANVIEW_TRUST_CERT=true +``` + +Then just run: + +```bash +planview analyze --query "SELECT * FROM Sales.SalesOrderHeader" +planview analyze ./queries/ --output-dir ./results/ +``` + +CLI arguments override `.env` values when both are provided. + +**Using the credential store** — for longer-term use, store credentials in your OS keychain: + +```bash +# Store credentials (once per server) +planview credential add sql2022 --user sa +# You'll be prompted for the password — it's stored in your OS credential store + +# Now connect without --login/--password +planview analyze --server sql2022 --database AdventureWorks \ + --query "SELECT * FROM Sales.SalesOrderHeader" \ + --trust-cert --output-dir ./results/ +``` + +**Batch processing** a folder of .sql files: + +```bash +planview analyze ./queries/ --server sql2022 --database StackOverflow2013 \ + --login sa --password YourPassword \ + --trust-cert --output-dir ./results/ +``` + +Batch mode produces three files per query: +- `query_name.sqlplan` — the raw execution plan XML (openable in SSMS or the Performance Studio GUI) +- `query_name.analysis.json` — structured analysis with warnings, missing indexes, and operator tree +- `query_name.analysis.txt` — human-readable text report + +### Manage credentials + +```bash +planview credential add my-server --user sa # prompts for password +planview credential add my-server --user sa -p pwd # non-interactive +planview credential list # show stored credentials +planview credential remove my-server # delete credential +``` + +Credentials are stored in the OS credential store — Windows Credential Manager on Windows, Apple Keychain on macOS. Nothing is written to disk in plaintext. + +## Example Output + +These examples were generated against StackOverflow2013 on SQL Server 2022. Source queries are in [`examples/queries/`](examples/queries/), plans and analysis in [`examples/output/`](examples/output/). + +### Text output (`--output text`) + +``` +Plan: 04_comment_heavy_posts.sqlplan +SQL Server: 1.564 (build 16.0.4222.2) +Statements: 1 + +--- Statement 1: SELECT --- + Query: SELECT p.Id, p.Title, p.Score, COUNT(c.Id) AS CommentCount + FROM dbo.Posts AS p JOIN dbo.Comments AS c ON c.PostId = p.Id + WHERE p.PostTypeId = 1 GROUP BY p.Id, p.Title, p.Score + HAVING COUNT(c.Id) > 20 ORDER BY CommentCount DESC + Estimated cost: 4069.8700 + DOP: 8 + Runtime: 4551ms elapsed, 15049ms CPU + Memory grant: 8,022,664 KB granted, 2,514,944 KB used + + Warnings: + [Critical] Large Memory Grant: Query granted 7835 MB of memory. + + Operator warnings: + [Critical] Parallelism (Node 0): Estimated 1 rows, actual 2,889 (2889x underestimated). + [Critical] Sort (Node 1): Estimated 1 rows, actual 2,889 (2889x underestimated). + [Warning] Sort (Node 1): Thread 1 processed 100% of rows. Work is heavily skewed. + [Warning] Filter (Node 2): Filter discards rows late in the plan. + + Missing indexes: + StackOverflow2013.dbo.Posts (impact: 74%) + CREATE NONCLUSTERED INDEX [IX_Posts_PostTypeId] + ON dbo.Posts (PostTypeId) INCLUDE (Score, Title) + StackOverflow2013.dbo.Comments (impact: 19%) + CREATE NONCLUSTERED INDEX [IX_Comments_PostId] + ON dbo.Comments (PostId) + +=== Summary === + Warnings: 8 (4 critical) + Missing indexes: 2 + Actual stats: yes + Warning types: Filter Operator, Large Memory Grant, Parallel Skew, + Row Estimate Mismatch, Scan With Predicate +``` + +### JSON output (default) + +The default JSON output includes the full operator tree, making it suitable for CI pipelines, LLM consumption, or further processing. See [`examples/output/`](examples/output/) for complete examples. + +### Batch processing + +``` +$ planview analyze ./examples/queries/ --server sql2022 \ + --database StackOverflow2013 --trust-cert --output-dir ./results/ + +Capturing actual plans from sql2022/StackOverflow2013 + +[1/5] 01_top_users_by_posts ... OK (1.8s) +[2/5] 02_recent_questions ... OK (0.8s) +[3/5] 03_unanswered_high_score ... OK (0.7s) +[4/5] 04_comment_heavy_posts ... OK (4.7s) +[5/5] 05_user_vote_summary ... OK (4.3s) + +Processed 5 files: 5 succeeded, 0 failed +Output: ./results/ +``` + +## Desktop GUI + +The Avalonia-based GUI renders execution plans visually with the same operator icons as SSMS. Open `.sqlplan` files via File > Open or drag-and-drop. + +Features: +- Graphical plan tree with cost percentages and row counts +- Warning badge on root node showing total warning count +- Plan Insights panel — three-column view with runtime summary, missing indexes, and wait stats visualization +- Zoom and pan (mouse wheel + middle-click drag) +- Minimap for quick navigation of large plans +- Color-coded links between operators based on accuracy ratio divergence (estimates vs actuals) +- Click any operator to see full properties (30 sections) +- Statement grid with sortable columns (cost, rows, DOP, warnings) +- Tooltips on hover with key operator metrics +- **Advice for Humans** — one-click text analysis report you can read or share +- **Advice for Robots** — one-click JSON export designed for LLMs and automation +- **Plan Comparison** — compare two plans side-by-side (cost, runtime, I/O, memory, wait stats) +- **Copy Repro Script** — extracts parameters, SET options, and query text into a runnable `sp_executesql` script +- **Get Actual Plan** — connect to a server and re-execute the query to capture runtime stats +- **Query Store Analysis** — connect to a server and analyze top queries by CPU, duration, or reads +- **Query History** — view a history of executed queries with their plans along the timeline and metrics from query store +- **MCP Server** — built-in Model Context Protocol server for AI-assisted plan analysis (opt-in) +- Dark theme + +```bash +dotnet run --project src/PlanViewer.App +``` + +## SSMS Extension + +A VSIX extension that adds **"Open in Performance Studio"** to the execution plan right-click context menu in SSMS 18-22. + +### How it works + +1. Right-click on any execution plan in SSMS +2. Click "Open in Performance Studio" +3. The extension extracts the plan XML via reflection and saves it to a temp file +4. Performance Studio opens with the plan loaded + +### Installation + +1. Download `PlanViewer.Ssms.vsix` and `InstallSsmsExtension.exe` from the [v0.7.0 release](https://github.com/erikdarlingdata/PerformanceStudio/releases/tag/v0.7.0) (SSMS extension is not yet included in automated builds) +2. Place them in the same folder +3. Double-click `InstallSsmsExtension.exe` and approve the UAC prompt +4. The installer auto-detects SSMS 21 and/or SSMS 22 and installs into both +5. Restart SSMS to activate the extension + +### First run + +On first use, if Performance Studio isn't found automatically, the extension will prompt you to locate `PlanViewer.App.exe`. The path is saved to the registry (`HKCU\SOFTWARE\DarlingData\SQLPerformanceStudio\InstallPath`) so you only need to do this once. + +The extension searches for the app in this order: +1. Registry key (set automatically after first browse) +2. System PATH +3. Common install locations (`%LOCALAPPDATA%\Programs\SQLPerformanceStudio\`, `Program Files`, etc.) + +## MCP Server (LLM Integration) + +The desktop GUI includes an embedded [Model Context Protocol](https://modelcontextprotocol.io) server that exposes loaded execution plans and Query Store data to LLM clients like Claude Code and Cursor. + +### Setup + +1. Enable the MCP server in `~/.planview/settings.json`: + +```json +{ + "mcp_enabled": true, + "mcp_port": 5152 +} +``` + +2. Register with Claude Code: + +``` +claude mcp add --transport http --scope user performance-studio http://localhost:5152/ +``` + +3. Open a new Claude Code session and ask questions like: + - "What plans are loaded in the application?" + - "Analyze the execution plan and tell me what's wrong" + - "Are there any missing index suggestions?" + - "Compare these two plans — which is better?" + - "Fetch the top 10 queries by CPU from Query Store" + +### Available Tools + +13 tools for plan analysis and Query Store data: + +| Category | Tools | +|---|---| +| Discovery | `list_plans`, `get_connections` | +| Plan Analysis | `analyze_plan`, `get_plan_summary`, `get_plan_warnings`, `get_missing_indexes`, `get_plan_parameters`, `get_expensive_operators`, `get_plan_xml`, `compare_plans`, `get_repro_script` | +| Query Store | `check_query_store`, `get_query_store_top` | + +Plan analysis tools work on plans loaded in the app (via file open, paste, query execution, or Query Store fetch). Query Store tools use a built-in read-only DMV query — no arbitrary SQL can be executed. + +The MCP server binds to `localhost` only and does not accept remote connections. Disabled by default. + +## Project Structure + +``` +PerformanceStudio/ +├── src/ +│ ├── PlanViewer.Core/ # Analysis engine (parser, 30 rules, layout) +│ ├── PlanViewer.App/ # Avalonia desktop GUI +│ ├── PlanViewer.Cli/ # CLI tool (planview command) +│ ├── PlanViewer.Ssms/ # SSMS extension (.vsix, .NET Framework 4.7.2) +│ └── PlanViewer.Ssms.Installer/ # SSMS extension installer (auto-detects SSMS 21/22) +├── tests/ +│ └── PlanViewer.Core.Tests/ # 37 xUnit tests with real .sqlplan fixtures +├── examples/ +│ ├── plans/ # Sample .sqlplan files for testing +│ ├── queries/ # Sample .sql files +│ └── output/ # Generated .sqlplan, .analysis.json, .txt +└── PlanViewer.sln +``` + +## CLI Reference + +### `planview analyze` + +``` +Usage: planview analyze [] [options] + +Arguments: + .sqlplan file, .sql file, or directory of .sql files + +Options: + --stdin Read plan XML from stdin + -o, --output json (default) or text + --compact Compact JSON (no indentation) + --warnings-only Skip operator tree, only output warnings and indexes + -s, --server SQL Server name (matches credential store key) + -d, --database Database context for execution + -q, --query Inline SQL text to execute + --output-dir Directory for output files + --estimated Estimated plan only (query is NOT executed) + --auth windows, sql, or entra (default: auto-detect) + --trust-cert Trust server certificate + --timeout Query timeout (default: 60) + --login SQL Server login (bypasses credential store) + --password SQL Server password (bypasses credential store) +``` + +### `planview credential` + +``` +planview credential add --user [-p ] +planview credential list +planview credential remove +``` + +## Authentication + +There are three ways to authenticate, in order of precedence: + +1. **`--login` / `--password`** — passed directly on the command line (or via `.env` file). Simplest for dev/test. +2. **Credential store** — stored in Windows Credential Manager or Apple Keychain via `planview credential add`. Best for repeated use. +3. **Windows Authentication** — used automatically when no SQL credentials are found. Requires a valid Kerberos ticket. + +Override the auto-detection with `--auth windows`, `--auth sql`, or `--auth entra`. + +**macOS note:** Windows Authentication does not work on macOS (no Kerberos ticket by default). Use `--login`/`--password`, the credential store, or `--auth entra` instead. + +## Platform Support + +| Platform | GUI | CLI | Credential Store | +|----------|-----|-----|-----------------| +| Windows | Yes | Yes | Windows Credential Manager | +| macOS | Yes | Yes | Apple Keychain | +| Linux | Yes | Yes | Not yet (file analysis works) | + +### macOS: SQL Server via Docker + +macOS users need a SQL Server instance to use the live capture features. The easiest path is Docker: + +```bash +docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=YourPassword123" \ + -p 1433:1433 --name sql_server \ + -d mcr.microsoft.com/mssql/server:2022-latest + +# Store the credential +planview credential add localhost --user sa -p YourPassword123 + +# Test connectivity +planview analyze --server localhost --database master --trust-cert \ + --query "SELECT @@VERSION" -o text +``` + +Always use `--trust-cert` with local Docker instances. + +## Analysis Rules + +The analyzer runs 30 rules against each plan, covering: + +| Category | Rules | +|----------|-------| +| Memory | Large grants, grant vs used ratio, spills to TempDB (including exchange spills) | +| Estimates | Row estimate mismatches (10x+), zero-row actuals, row goals | +| Indexes | Missing index suggestions, key lookups, RID lookups, scan with residual predicates | +| Parallelism | Serial plan reasons, thread skew, ineffective parallelism, DOP reporting | +| Joins | Nested loop high executions, many-to-many merge join worktables | +| Filters | Late filter operators with impact quantification, function-wrapped predicates | +| Functions | Scalar UDF detection (T-SQL and CLR), UDF timing | +| Parameters | Compiled vs runtime values, sniffing issue detection | +| Patterns | Leading wildcards, implicit conversions (with seek plan severity upgrade), OPTIMIZE FOR UNKNOWN, NOT IN with nullable columns, OR expansion, CASE in predicates | +| Compilation | High compile CPU, compile memory exceeded, early abort | +| Objects | Table variables, table-valued functions, CTE multiple references, eager index spools, lazy spools, row count spools | +| Operators | Operator self-time calculation with per-thread awareness for parallel plans | + +Rules can be disabled or have their severity overridden via a `.planview.json` config file. See the `--config` option. + +## Sponsors + + + + + + +
SignPathFree code signing on Windows provided by SignPath.io, certificate by SignPath Foundation
+ +## License + +MIT — see [LICENSE](LICENSE). + +Execution plan operator icons are from Microsoft's [vscode-mssql](https://github.com/microsoft/vscode-mssql) extension (MIT). See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for details. diff --git a/SECURITY.md b/SECURITY.md index 5a54ea9..da867d4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,34 +1,34 @@ -# Security Policy - -## Reporting a Vulnerability - -If you discover a security vulnerability in Performance Studio, please report it responsibly. - -**Do not open a public GitHub issue for security vulnerabilities.** - -Instead, please email **erik@erikdarling.com** with: - -- A description of the vulnerability -- Steps to reproduce the issue -- The potential impact -- Any suggested fixes (optional) - -You should receive a response within 72 hours. We will work with you to understand the issue and coordinate a fix before any public disclosure. - -## Scope - -This policy applies to: - -- Desktop application (PlanViewer.App) -- Core analysis library (PlanViewer.Core) -- CLI tool (PlanViewer.Cli) -- SSMS extension (PlanViewer.Ssms) - -## Security Best Practices - -When using Performance Studio: - -- Use Windows Authentication where possible when connecting to SQL Server -- Use dedicated accounts with minimal required permissions -- Enable encryption for SQL Server connections -- Keep your SQL Server instances patched and up to date +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in Performance Studio, please report it responsibly. + +**Do not open a public GitHub issue for security vulnerabilities.** + +Instead, please email **erik@erikdarling.com** with: + +- A description of the vulnerability +- Steps to reproduce the issue +- The potential impact +- Any suggested fixes (optional) + +You should receive a response within 72 hours. We will work with you to understand the issue and coordinate a fix before any public disclosure. + +## Scope + +This policy applies to: + +- Desktop application (PlanViewer.App) +- Core analysis library (PlanViewer.Core) +- CLI tool (PlanViewer.Cli) +- SSMS extension (PlanViewer.Ssms) + +## Security Best Practices + +When using Performance Studio: + +- Use Windows Authentication where possible when connecting to SQL Server +- Use dedicated accounts with minimal required permissions +- Enable encryption for SQL Server connections +- Keep your SQL Server instances patched and up to date diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index e253b2c..81c99ec 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1,49 +1,49 @@ -# Third-Party Notices - -Performance Studio includes the following third-party open-source components. Each component is subject to the license terms specified below. - ---- - -## vscode-mssql (Execution Plan Icons) - -**Author**: Microsoft Corporation -**Repository**: https://github.com/microsoft/vscode-mssql -**License**: MIT License - -Execution plan operator icons (PNG) from the vscode-mssql extension are used to render graphical execution plans. Icons are located in `src/PlanViewer.Core/Resources/PlanIcons/`. - -### License Text - -MIT License - -Copyright (c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -Full license: https://github.com/microsoft/vscode-mssql/blob/main/LICENSE - ---- - -## Acknowledgments - -Performance Studio uses execution plan operator icons from **Microsoft's vscode-mssql extension**, which provides SQL Server tooling for Visual Studio Code. We are grateful for their commitment to open-source software. - ---- - -*Last Updated: March 5, 2026* +# Third-Party Notices + +Performance Studio includes the following third-party open-source components. Each component is subject to the license terms specified below. + +--- + +## vscode-mssql (Execution Plan Icons) + +**Author**: Microsoft Corporation +**Repository**: https://github.com/microsoft/vscode-mssql +**License**: MIT License + +Execution plan operator icons (PNG) from the vscode-mssql extension are used to render graphical execution plans. Icons are located in `src/PlanViewer.Core/Resources/PlanIcons/`. + +### License Text + +MIT License + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Full license: https://github.com/microsoft/vscode-mssql/blob/main/LICENSE + +--- + +## Acknowledgments + +Performance Studio uses execution plan operator icons from **Microsoft's vscode-mssql extension**, which provides SQL Server tooling for Visual Studio Code. We are grateful for their commitment to open-source software. + +--- + +*Last Updated: March 5, 2026* diff --git a/server/PlanShare/PlanShare.csproj b/server/PlanShare/PlanShare.csproj index 854439d..5e2bf89 100644 --- a/server/PlanShare/PlanShare.csproj +++ b/server/PlanShare/PlanShare.csproj @@ -1,13 +1,13 @@ - - - - net8.0 - enable - enable - - - - - - - + + + + net8.0 + enable + enable + + + + + + + diff --git a/server/PlanShare/Properties/launchSettings.json b/server/PlanShare/Properties/launchSettings.json index 26c588f..4fcd679 100644 --- a/server/PlanShare/Properties/launchSettings.json +++ b/server/PlanShare/Properties/launchSettings.json @@ -1,38 +1,38 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:60802", - "sslPort": 44322 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5271", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7060;http://localhost:5271", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:60802", + "sslPort": 44322 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5271", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7060;http://localhost:5271", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/server/PlanShare/appsettings.Development.json b/server/PlanShare/appsettings.Development.json index ff66ba6..0c208ae 100644 --- a/server/PlanShare/appsettings.Development.json +++ b/server/PlanShare/appsettings.Development.json @@ -1,8 +1,8 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/server/PlanShare/appsettings.json b/server/PlanShare/appsettings.json index 4d56694..10f68b8 100644 --- a/server/PlanShare/appsettings.json +++ b/server/PlanShare/appsettings.json @@ -1,9 +1,9 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/PlanViewer.App/AboutWindow.axaml b/src/PlanViewer.App/AboutWindow.axaml index 7c49c0b..17f422d 100644 --- a/src/PlanViewer.App/AboutWindow.axaml +++ b/src/PlanViewer.App/AboutWindow.axaml @@ -1,90 +1,90 @@ - - - - - - - - - - - - - - - - - - - - - -