-
Notifications
You must be signed in to change notification settings - Fork 3
🚀 [Feature]: Install only the Nerd Font variants you need with faster reruns #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Marius Storhaug (MariusStorhaug)
wants to merge
34
commits into
main
Choose a base branch
from
perf/install-improvements
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
4f46dd1
perf: add Install-NerdFont performance measurement script
MariusStorhaug e63bfc1
perf: suppress Invoke-WebRequest progress bar during font downloads
MariusStorhaug acb9884
perf: dedup font set with List+HashSet, drop O(n^2) array growth
MariusStorhaug ddd7142
perf: skip download when font is already installed (closes #73)
MariusStorhaug 414f423
fix: handle empty Get-Font result in skip-installed check
MariusStorhaug c3eddbf
perf: extract font archives via ZipFile API (closes #72)
MariusStorhaug 955ea2c
perf: cache downloaded font archives between invocations (closes #76)
MariusStorhaug 3f208ea
perf: add -Variant filter to install only desired font variants (clos…
MariusStorhaug 6df13c6
style: split long Variant switch arms for PSUseConsistentWhitespace/P…
MariusStorhaug 7a054ea
test: add -Variant Mono case to lift code coverage above threshold
MariusStorhaug ddd8014
perf: parallelize font downloads/extract (closes #71)
MariusStorhaug d041a0b
perf: parallel HTTP downloads via HttpClient tasks (closes #71)
MariusStorhaug 1572b9a
perf: bound parallel HTTP downloads to 8 concurrent (closes #71)
MariusStorhaug ed569aa
Document variant installs and cache behavior
MariusStorhaug 62233a5
Handle partial download failures in Install-NerdFont
MariusStorhaug 4ea2bdc
Fix Install-NerdFont indentation for CI
MariusStorhaug 5d90644
Normalize queued download object indentation
MariusStorhaug cadd8cf
Add coverage for installed-font skip path
MariusStorhaug 5d052b7
Use processor count for default parallelism
MariusStorhaug a7223f4
Remove perf-results.jsonl from .gitignore and add the file with perfo…
MariusStorhaug a63442d
Increase Install-NerdFont coverage with Standard variant test
MariusStorhaug d8d51c2
Fix formatting of Write-Host output in Measure-InstallPerformance.ps1
MariusStorhaug 34de022
Stabilize all-font test and fix recursive temp cleanup
MariusStorhaug 5183ed2
Address PR #77 review threads: cache resilience, variant dedupe, docs…
MariusStorhaug 1a4120e
Fix Sort-Object property expressions not being passed to the command;…
MariusStorhaug 75feaf4
Run duplicate-file removal for all variant selections, not just non-All
MariusStorhaug 634c91c
Fix prefix false-positive in skip check, cap download throttle at 8, …
MariusStorhaug 2b35f57
Fix cache resilience and strengthen variant test assertions
MariusStorhaug ef4c99f
Add test for cache-read failure fallback to improve coverage
MariusStorhaug 00d4a36
Fix CI test failures: isolate variant tests from Fonts module, fix ca…
MariusStorhaug f3f71bd
Defer cache directory creation until approved by ShouldProcess; fix p…
MariusStorhaug f8137f2
Clean up test-created cache directories so tests leave no persistent …
MariusStorhaug 5262f49
Refactor tests to use InModuleScope for module-level coverage; add Pr…
MariusStorhaug 2ae527f
Fix CI test failures and address cache write review threads
MariusStorhaug File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| <# | ||
| .SYNOPSIS | ||
| Measures Install-NerdFont performance across known scenarios. | ||
|
|
||
| .DESCRIPTION | ||
| Runs a set of timed scenarios against the currently loaded NerdFonts module | ||
| and emits a structured result object per scenario. Each scenario uninstalls | ||
| the fonts it will measure before timing, so measurements are comparable | ||
| across iterations. | ||
|
|
||
| .EXAMPLE | ||
| ./Measure-InstallPerformance.ps1 -Iteration 'baseline' -Subset 'Hack','FiraCode','JetBrainsMono' | ||
|
|
||
| .NOTES | ||
| Per-iteration result JSON is appended to scripts/perf-results.jsonl | ||
| so a full report can be produced at the end of the improvement cycle. | ||
| #> | ||
| [Diagnostics.CodeAnalysis.SuppressMessageAttribute( | ||
| 'PSAvoidUsingWriteHost', '', | ||
| Justification = 'Console output for an interactive perf script.' | ||
| )] | ||
| [Diagnostics.CodeAnalysis.SuppressMessageAttribute( | ||
| 'PSReviewUnusedParameter', 'ResultsPath', | ||
| Justification = 'Used inside Measure-Scenario via closure on the enclosing scope.' | ||
| )] | ||
| [CmdletBinding()] | ||
| param( | ||
| # Free-form label for the iteration (module version, commit short SHA, etc). | ||
| [Parameter(Mandatory)] | ||
| [string] $Iteration, | ||
|
|
||
| # Named fonts used for the small subset scenarios. Should be small/medium | ||
| # archives to keep iteration time bounded. | ||
| [Parameter()] | ||
| [string[]] $Subset = @('Hack', 'FiraCode', 'JetBrainsMono'), | ||
|
|
||
| # When set, also runs a full Install-NerdFont -All measurement. Slow. | ||
| [Parameter()] | ||
| [switch] $IncludeAll, | ||
|
|
||
| # File where per-scenario JSON lines are appended. | ||
| [Parameter()] | ||
| [string] $ResultsPath = (Join-Path $PSScriptRoot 'perf-results.jsonl') | ||
| ) | ||
|
|
||
| $ErrorActionPreference = 'Stop' | ||
|
|
||
| function Invoke-Uninstall { | ||
| <# | ||
| .SYNOPSIS | ||
| Removes matching Nerd Font families for the current user. | ||
| #> | ||
| param([string[]]$Names) | ||
| foreach ($n in $Names) { | ||
| # Nerd Fonts archives expand to multiple family names that all start | ||
| # with the archive's base name (e.g. "Hack Nerd Font", "Hack Nerd Font Mono"). | ||
| $families = Get-Font -Scope CurrentUser | Where-Object { $_.Name -like "$n Nerd Font*" } | ||
| foreach ($f in $families) { | ||
| try { | ||
| Uninstall-Font -Name $f.Name -Scope CurrentUser -ErrorAction Stop | ||
| } catch { | ||
| Write-Verbose "Uninstall failed for $($f.Name): $_" | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function Invoke-UninstallAll { | ||
| <# | ||
| .SYNOPSIS | ||
| Removes all Nerd Fonts known to the current module. | ||
| #> | ||
| $names = (Get-NerdFont).Name | ||
| Invoke-Uninstall -Names $names | ||
| } | ||
|
|
||
| function Measure-Scenario { | ||
| <# | ||
| .SYNOPSIS | ||
| Runs one setup/action performance scenario and records the result. | ||
| #> | ||
| param( | ||
| [string]$Name, | ||
| [scriptblock]$Setup, | ||
| [scriptblock]$Action | ||
| ) | ||
| Write-Host "[$Iteration] Setup : $Name" -ForegroundColor DarkGray | ||
| & $Setup | Out-Null | ||
| [System.GC]::Collect() | ||
| [System.GC]::WaitForPendingFinalizers() | ||
| Write-Host "[$Iteration] Measure : $Name" -ForegroundColor Cyan | ||
| $sw = [System.Diagnostics.Stopwatch]::StartNew() | ||
| try { | ||
| & $Action | Out-Null | ||
| $err = $null | ||
| } catch { | ||
| $err = $_.ToString() | ||
| } | ||
| $sw.Stop() | ||
| $result = [pscustomobject]@{ | ||
| Iteration = $Iteration | ||
| Scenario = $Name | ||
| DurationMs = [int]$sw.Elapsed.TotalMilliseconds | ||
| DurationS = [math]::Round($sw.Elapsed.TotalSeconds, 2) | ||
| Timestamp = (Get-Date).ToString('o') | ||
| Error = $err | ||
| Module = (Get-Module NerdFonts).Version.ToString() | ||
| } | ||
| Write-Host ("[$Iteration] Result : {0} -> {1}s" -f $Name, $result.DurationS) -ForegroundColor Green | ||
| $result | ConvertTo-Json -Compress | Add-Content -Path $ResultsPath | ||
| return $result | ||
| } | ||
|
|
||
| $results = [System.Collections.Generic.List[object]]::new() | ||
|
|
||
| # --- Scenario 1: single small/medium font --- | ||
| $single = @{ | ||
| Name = 'Single-Hack' | ||
| Setup = { Invoke-Uninstall -Names 'Hack' } | ||
| Action = { Install-NerdFont -Name 'Hack' -Scope CurrentUser -Force } | ||
| } | ||
| $results.Add((Measure-Scenario @single)) | ||
|
|
||
| # --- Scenario 2: subset of named fonts --- | ||
| $subsetArgs = @{ | ||
| Name = "Subset-$($Subset -join '+')" | ||
| Setup = { Invoke-Uninstall -Names $Subset } | ||
| Action = { Install-NerdFont -Name $Subset -Scope CurrentUser -Force } | ||
| } | ||
| $results.Add((Measure-Scenario @subsetArgs)) | ||
|
|
||
| # --- Scenario 3: re-install when already present (no-op path) --- | ||
| $noop = @{ | ||
| Name = 'Subset-AlreadyInstalled' | ||
| Setup = { } | ||
|
MariusStorhaug marked this conversation as resolved.
|
||
| Action = { Install-NerdFont -Name $Subset -Scope CurrentUser } | ||
| } | ||
| $results.Add((Measure-Scenario @noop)) | ||
|
|
||
| # --- Scenario 4: full -All (only when explicitly requested) --- | ||
| if ($IncludeAll) { | ||
| $allArgs = @{ | ||
| Name = 'All' | ||
| Setup = { Invoke-UninstallAll } | ||
| Action = { Install-NerdFont -All -Scope CurrentUser -Force } | ||
| } | ||
| $results.Add((Measure-Scenario @allArgs)) | ||
| } | ||
|
|
||
| Write-Host '' | ||
| Write-Host "Summary for iteration '$Iteration':" -ForegroundColor Yellow | ||
| $results | Format-Table Iteration, Scenario, DurationS, Module -AutoSize | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| {"Iteration":"baseline-1.0.32","Scenario":"Single-Hack","DurationMs":1300,"DurationS":1.3,"Timestamp":"2026-05-17T12:49:25.3421303+02:00","Error":null,"Module":"1.0.32"} | ||
| {"Iteration":"baseline-1.0.32","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":6195,"DurationS":6.2,"Timestamp":"2026-05-17T12:49:31.7706509+02:00","Error":null,"Module":"1.0.32"} | ||
| {"Iteration":"baseline-1.0.32","Scenario":"Subset-AlreadyInstalled","DurationMs":2808,"DurationS":2.81,"Timestamp":"2026-05-17T12:49:34.6122341+02:00","Error":null,"Module":"1.0.32"} | ||
| {"Iteration":"1.0.33-pre001-progressbar","Scenario":"Single-Hack","DurationMs":675,"DurationS":0.67,"Timestamp":"2026-05-17T12:59:23.3818901+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre001-progressbar","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":3583,"DurationS":3.58,"Timestamp":"2026-05-17T12:59:28.5904267+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre001-progressbar","Scenario":"Subset-AlreadyInstalled","DurationMs":2867,"DurationS":2.87,"Timestamp":"2026-05-17T12:59:31.4997320+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre002-dedup","Scenario":"Single-Hack","DurationMs":571,"DurationS":0.57,"Timestamp":"2026-05-17T13:09:00.7799184+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre002-dedup","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":3519,"DurationS":3.52,"Timestamp":"2026-05-17T13:09:05.9546030+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre002-dedup","Scenario":"Subset-AlreadyInstalled","DurationMs":2835,"DurationS":2.83,"Timestamp":"2026-05-17T13:09:08.8327714+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre003-skipinstalled","Scenario":"Single-Hack","DurationMs":703,"DurationS":0.7,"Timestamp":"2026-05-17T13:22:50.2667008+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre003-skipinstalled","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":3559,"DurationS":3.56,"Timestamp":"2026-05-17T13:22:56.1123334+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre003-skipinstalled","Scenario":"Subset-AlreadyInstalled","DurationMs":22,"DurationS":0.02,"Timestamp":"2026-05-17T13:22:56.1734595+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre004-zipfile","Scenario":"Single-Hack","DurationMs":598,"DurationS":0.6,"Timestamp":"2026-05-17T13:31:16.9883423+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre004-zipfile","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":3440,"DurationS":3.44,"Timestamp":"2026-05-17T13:31:22.7322456+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre004-zipfile","Scenario":"Subset-AlreadyInstalled","DurationMs":16,"DurationS":0.02,"Timestamp":"2026-05-17T13:31:22.7860048+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre005-cache-cold","Scenario":"Single-Hack","DurationMs":446,"DurationS":0.45,"Timestamp":"2026-05-17T13:40:07.8376702+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre005-cache-cold","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":3656,"DurationS":3.66,"Timestamp":"2026-05-17T13:40:13.8549945+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre005-cache-cold","Scenario":"Subset-AlreadyInstalled","DurationMs":17,"DurationS":0.02,"Timestamp":"2026-05-17T13:40:13.9101232+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre005-cache-warm","Scenario":"Single-Hack","DurationMs":403,"DurationS":0.4,"Timestamp":"2026-05-17T13:40:14.5276409+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre005-cache-warm","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":3193,"DurationS":3.19,"Timestamp":"2026-05-17T13:40:19.2900547+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre005-cache-warm","Scenario":"Subset-AlreadyInstalled","DurationMs":11,"DurationS":0.01,"Timestamp":"2026-05-17T13:40:19.3360365+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre006-variant-all","Scenario":"Single-Hack","DurationMs":1171,"DurationS":1.17,"Timestamp":"2026-05-17T14:05:52.0905786+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre006-variant-all","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":6096,"DurationS":6.1,"Timestamp":"2026-05-17T14:06:02.0498948+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre006-variant-all","Scenario":"Subset-AlreadyInstalled","DurationMs":35,"DurationS":0.04,"Timestamp":"2026-05-17T14:06:02.1490854+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre006-variant-mono","Scenario":"Subset-Mono","DurationMs":1620,"DurationS":1.62,"Timestamp":"2026-05-17T12:06:18.1677417Z","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre007-parallel","Scenario":"Single-Hack","DurationMs":615,"DurationS":0.61,"Timestamp":"2026-05-17T14:32:21.0280298+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre007-parallel","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":3695,"DurationS":3.7,"Timestamp":"2026-05-17T14:32:24.9507727+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre007-parallel","Scenario":"Subset-AlreadyInstalled","DurationMs":16,"DurationS":0.02,"Timestamp":"2026-05-17T14:32:25.0140743+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre007-parallel-all","Scenario":"Single-Hack","DurationMs":466,"DurationS":0.47,"Timestamp":"2026-05-17T14:34:32.3565252+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre007-parallel-all","Scenario":"Subset-Hack+FiraCode+JetBrainsMono","DurationMs":3821,"DurationS":3.82,"Timestamp":"2026-05-17T14:34:36.3395668+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre007-parallel-all","Scenario":"Subset-AlreadyInstalled","DurationMs":15,"DurationS":0.02,"Timestamp":"2026-05-17T14:34:36.4202031+02:00","Error":null,"Module":"1.0.33"} | ||
| {"Iteration":"1.0.33-pre007-parallel-all","Scenario":"All","DurationMs":221809,"DurationS":221.81,"Timestamp":"2026-05-17T14:38:20.2949322+02:00","Error":null,"Module":"1.0.33"} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.