{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/build-perf-diagnostics",
  "version": "1.0.0",
  "name": "build-perf-diagnostics",
  "description": "Diagnose MSBuild build performance bottlenecks using binary log analysis. Only activate in MSBuild/.NET build context. USE FOR: identifying why builds are slow by analyzing binlog performance summaries, detecting ResolveAssemblyReference (RAR) taking >5s, Roslyn analyzers consuming >30% of Csc time, single targets dominating >50% of build time, node utilization below 80%, excessive Copy tasks, NuGet restore running every build. Covers timeline analysis, Target/Task Performance Summary interpretation, and 7 common bottleneck categories. Use after build-perf-baseline has established measurements. DO NOT USE FOR: establishing initial baselines (use build-perf-baseline first), fixing incremental build issues (use incremental-build), parallelism tuning (use build-parallelism), non-MSBuild build systems. INVOKES: binlog MCP server tools (overview, errors, search, items, properties); falls back to dotnet msbuild binlog replay + grep/cat when the MCP is unavailable.",
  "system_prompt_fragment": "## Performance Analysis Methodology\n\n1. **Generate a binlog**: `dotnet build /bl:{} -m`\n2. Use the **binlog MCP server** (`Microsoft.AITools.BinlogMcp`, exposed under the `binlog` MCP namespace) which is bundled with this plugin\n\n### Alternate flow when MCP is unavailable: binlog replay to text logs\n\n1. **Generate a binlog**: `dotnet build /bl:{} -m`\n2. **Replay to diagnostic log with performance summary**:\n   ```bash\n   dotnet msbuild build.binlog -noconlog -fl -flp:v=diag;logfile=full.log;performancesummary\n   ```\n3. **Read the performance summary** (at the end of `full.log`):\n   ```bash\n   grep \"Target Performance Summary\\|Task Performance Summary\" -A 50 full.log\n   ```\n4. **Find expensive targets and tasks**: The PerformanceSummary section lists all targets/tasks sorted by cumulative time\n5. **Check for node utilization**: grep for scheduling and node messages\n   ```bash\n   grep -i \"node.*assigned\\|building with\\|scheduler\" full.log | head -30\n   ```\n6. **Check analyzers**: grep for analyzer timing\n   ```bash\n   grep -i \"analyzer.*elapsed\\|Total analyzer execution time\\|CompilerAnalyzerDriver\" full.log\n   ```\n\n## Key Metrics and Thresholds\n\n- **Build duration**: what's \"normal\" — small project <10s, medium <60s, large <5min\n- **Node utilization**: ideal is >80% active time across nodes. Low utilization = serialization bottleneck\n- **Single target domination**: if one target is >50% of build time, investigate\n- **Analyzer time vs compile time**: analyzers should be <30% of Csc task time. If higher, consider removing expensive analyzers\n- **RAR time**: ResolveAssemblyReference >5s is concerning. >15s is pathological\n\n## Common Bottlenecks\n\n### 1. ResolveAssemblyReference (RAR) Slowness\n\n- **Symptoms**: RAR taking >5s per project\n- **Root causes**: too many assembly references, network-based reference paths, large assembly search paths\n- **Fixes**: reduce reference count, use `<DesignTimeBuild>false</DesignTimeBuild>` for RAR-heavy analysis, set `<ResolveAssemblyReferencesSilent>true</ResolveAssemblyReferencesSilent>` for diagnostic\n- **Advanced**: `<DesignTimeBuild>` and `<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>`\n- **Key insight**: RAR runs unconditionally even on incremental builds because users may have installed targeting packs or GACed assemblies (see dotnet/msbuild#2015). With .NET Core micro-assemblies, the reference count is often very high.\n- **Reduce transitive references**: Set `<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>` to avoid pulling in the full transitive closure (note: projects may need to add direct references for any types they consume). Use `ReferenceOutputAssembly=\"false\"` on ProjectReferences that are only needed at build time (not API surface). Trim unused PackageReferences.\n\n### 2. Roslyn Analyzers and Source Generators\n\n- **Symptoms**: Csc task takes much longer than expected for file count (>2× clean compile time)\n- **Diagnosis**: Check the Task Performance Summary in the replayed log for Csc task time; grep for analyzer timing messages; compare Csc duration with and without analyzers (`/p:RunAnalyzers=false`)\n- **Fixes**:\n  - Conditionally disable in dev: `<RunAnalyzers Condition=\"'$(ContinuousIntegrationBuild)' != 'true'\">false</RunAnalyzers>`\n  - Per-configuration: `<RunAnalyzers Condition=\"'$(Configuration)' == 'Debug'\">false</RunAnalyzers>`\n  - Code-style only: `<EnforceCodeStyleInBuild Condition=\"'$(ContinuousIntegrationBuild)' == 'true'\">true</EnforceCodeStyleInBuild>`\n  - Remove genuinely redundant analyzers from inner loop\n  - Severity config in .editorconfig for less critical rules\n- **Key principle**: Preserve analyzer enforcement in CI. Never just \"remove\" analyzers — configure them conditionally.\n- **GlobalPackageReference**: Analyzers added via `GlobalPackageReference` in `Directory.Packages.props` apply to ALL projects. Consider if test projects need the same analyzer set as production code.\n- **EnforceCodeStyleInBuild**: When set to `true` in `Directory.Build.props`, forces code-style analysis on every build. Should be conditional on CI environment (`ContinuousIntegrationBuild`) to avoid slowing dev inner loop.\n\n### 3. Serialization Bottlenecks (Single-threaded targets)\n\n- **Symptoms**: Performance summary shows most build time concentrated in a single project; diagnostic log shows idle nodes while one works\n- **Common culprits**: targets without proper dependency declaration, single project on critical path\n- **Fixes**: split large projects, optimize the critical path project, ensure proper `BuildInParallel`\n\n### 4. Excessive File I/O (Copy tasks)\n\n- **Symptoms**: Copy task shows high aggregate time\n- **Root causes**: copying thousands of files, copying across network drives, Copy task unintentionally running once per item (per-file) instead of as a single batch (see dotnet/msbuild#12884)\n- **Fixes**: use hardlinks (`<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>`), reduce CopyToOutputDirectory items, use `<UseCommonOutputDirectory>true</UseCommonOutputDirectory>` when appropriate, set `<SkipCopyUnchangedFiles>true</SkipCopyUnchangedFiles>`, consider `--artifacts-path` (.NET 8+) for centralized output layout\n- **Dev Drive**: On Windows, switching to a Dev Drive (ReFS with copy-on-write and reduced Defender scans) can significantly reduce file I/O overhead for Copy-heavy builds. Recommend for both dev machines and self-hosted CI agents.\n\n### 5. Evaluation Overhead\n\n- **Symptoms**: build starts slow before any compilation\n- **Root causes**: complex Directory.Build.props, wildcard globs scanning large directories, NuGetSdkResolver overhead (adds 180-400ms per project evaluation even when restored — see dotnet/msbuild#4025)\n- **Fixes**: reduce Directory.Build.props complexity, use `<EnableDefaultItems>false</EnableDefaultItems>` for legacy projects with explicit file lists, avoid NuGet-based SDK resolvers if possible\n- See: `eval-performance` skill for detailed guidance\n\n### 6. NuGet Restore in Build\n\n- **Symptoms**: restore runs every build even when unnecessary\n- **Fixes**:\n  - Separate restore from build: `dotnet restore` then `dotnet build --no-restore`\n  - Enable static graph evaluation: `<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>` in Directory.Build.props — can save significant time in large builds (results are workload-dependent)\n\n### 7. Large Project Count and Graph Shape\n\n- **Symptoms**: many small projects, each takes minimal time but overhead adds up; deep dependency chains serialize the build\n- **Consider**: project consolidation, or use `/graph` mode for better scheduling\n- **Graph shape matters**: a wide dependency graph (few levels, many parallel branches) builds faster than a deep one (many levels, serialized). Refactoring from deep to wide can yield significant improvements in both clean and incremental build times.\n- **Actions**: look for unnecessary project dependencies, consider splitting a bottleneck project into two, or merging small leaf projects\n\n## Using Binlog Replay for Performance Analysis\n\nStep-by-step workflow using text log replay:\n\n1. **Replay with performance summary**:\n   ```bash\n   dotnet msbuild build.binlog -noconlog -fl -flp:v=diag;logfile=full.log;performancesummary\n   ```\n2. **Read target/task performance summaries** (at the end of `full.log`):\n   ```bash\n   grep \"Target Performance Summary\\|Task Performance Summary\" -A 50 full.log\n   ```\n   This shows all targets and tasks sorted by cumulative time — equivalent to finding expensive targets/tasks.\n3. **Find per-project build times**:\n   ```bash\n   grep \"done building project\\|Project Performance Summary\" full.log\n   ```\n4. **Check parallelism** (multi-node scheduling):\n   ```bash\n   grep -i \"node.*assigned\\|RequiresLeadingNewline\\|Building with\" full.log | head -30\n   ```\n5. **Check analyzer overhead**:\n   ```bash\n   grep -i \"Total analyzer execution time\\|analyzer.*elapsed\\|CompilerAnalyzerDriver\" full.log\n   ```\n6. **Drill into a specific slow target**:\n   ```bash\n   grep 'Target \"CoreCompile\"\\|Target \"ResolveAssemblyReferences\"' full.log\n   ```\n\n## Quick Wins Checklist\n\n- [ ] Use `/maxcpucount` (or `-m`) for parallel builds\n- [ ] Separate restore from build (`dotnet restore` then `dotnet build --no-restore`)\n- [ ] Enable static graph restore (`<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>`)\n- [ ] Enable hardlinks for Copy (`<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>`)\n- [ ] Disable analyzers conditionally in dev inner loop: `<RunAnalyzers Condition=\"'$(ContinuousIntegrationBuild)' != 'true'\">false</RunAnalyzers>`\n- [ ] Enable reference assemblies (`<ProduceReferenceAssembly>true</ProduceReferenceAssembly>`)\n- [ ] Check for broken incremental builds (see `incremental-build` skill)\n- [ ] Check for bin/obj clashes (see `check-bin-obj-clash` skill)\n- [ ] Use graph build (`/graph`) for multi-project solutions\n- [ ] Use `--artifacts-path` (.NET 8+) for centralized output layout\n- [ ] Enable Dev Drive (ReFS) on Windows dev machines and self-hosted CI\n\n## Impact Categorization\n\nWhen reporting findings, categorize by impact to help prioritize fixes:\n\n- 🔴 **HIGH IMPACT** (do first): Items consuming >10% of total build time, or a single target >50% of build time\n- 🟡 **MEDIUM IMPACT**: Items consuming 2-10% of build time\n- 🟢 **QUICK WINS**: Easy changes with modest impact (e.g., property flags in Directory.Build.props)",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/build-perf-diagnostics"
  ],
  "tags": [
    "dotnet-msbuild",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-msbuild/skills/build-perf-diagnostics/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-msbuild/skills/build-perf-diagnostics/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}