Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions artifacts/perf/issue38-refresh-spinner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Runtime Benchmarks

- Baseline ref: `a6f60e0ce830c4649ac34fc05e5a1799ec91d151`
- Current source: working tree
- Node: `v25.2.0`
- Selection mode: `scenario-list`
- Declared suite: `user-flow`
- Result-count validation: `1 rows, suite-consistent=true, all-user-flow=true`

## Machine Profile

| Category | Field | Value |
| --- | --- | --- |
| Host | Hostname | n00ne-AERO-17-YD |
| Host | OS | Ubuntu 22.04.5 LTS |
| Host | Kernel | 6.8.0-124-generic |
| Host | Architecture | x64 |
| Host | Load Average | 4.41, 4.11, 4.36 |
| Host | Available Parallelism | - |
| CPU | Model | Intel(R) Core(TM) i9-14900HX |
| CPU | Vendor | GenuineIntel |
| CPU | Topology | 16 logical CPU(s), 2 thread(s)/core, 8 core(s)/socket, 1 socket(s), 1 NUMA node(s) |
| CPU | Frequency | 800 MHz to 5,800 MHz |
| CPU | Cache | L1d 384 KiB (8 instances), L1i 256 KiB (8 instances), L2 16 MiB (8 instances), L3 36 MiB (1 instance) |
| Memory | Total RAM | 62.51 GiB (`67,119,767,552 bytes`) |
| Memory | Available At Collection | 11.59 GiB (`12,448,333,824 bytes`) |
| Memory | Online Physical RAM | 66.00 GiB (`70,866,960,384 bytes`) |
| Memory | Swap | total 120 GiB (`128,848,973,824 bytes`); free 99.16 GiB (`106,472,206,336 bytes`) |
| Memory | DMI / SPD | Unavailable: /sys/firmware/dmi/tables/smbios_entry_point: Permission denied /dev/mem: Permission denied |
| Storage | Root Device | nvme1n1 (Samsung SSD 9100 PRO 4TB), 3.64 TiB (`4,000,787,030,016 bytes`), transport nvme, rotational=false, readOnly=false |

## Scenario Model

| Scenario | Kind | User flow | Measurement scope | Input model |
| --- | --- | --- | --- | --- |
| tree-view-repeat-click-burst | user-flow | Repeatedly click the same view button while the tree state is still mutating. | Overlapping command handling and workspace-state writes. | Command burst against the extension command handlers in a VS Code event harness. |

## Metric Model

| Table | Value model | Accuracy model |
| --- | --- | --- |
| Latency | Wall-clock elapsed time around each harness flow iteration, summarized as min/p50/p90/p95/max. | Exact for each sampled iteration in this run. |
| Profiled RSS Burst | Difference between the isolated scenario worker RSS measured immediately before the flow and that worker iteration's OS high-water-mark peak RSS. | Exact for the measured worker iteration, using `process.memoryUsage().rss` at flow start and `process.resourceUsage().maxRSS` for the peak. |
| Profiled Peak RSS | Highest process RSS reached by each isolated scenario worker iteration. | Exact worker-process high-water mark from `process.resourceUsage().maxRSS`. |

## Latency

| Scenario | Kind | Baseline p50 ms | Current p50 ms | Baseline p90 ms | Current p90 ms | Baseline p95 ms | Current p95 ms |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| tree-view-repeat-click-burst | user-flow | 0.25 | 0.3 | 0.35 | 0.53 | 0.41 | 0.61 |

## Profiled RSS Burst

| Scenario | Kind | Baseline p50 MiB | Current p50 MiB | Baseline p90 MiB | Current p90 MiB | Baseline p95 MiB | Current p95 MiB | Baseline Max MiB | Current Max MiB |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| tree-view-repeat-click-burst | user-flow | 1.13 | 1.63 | 1.38 | 1.88 | 1.5 | 2.13 | 1.5 | 2.13 |

## Profiled Peak RSS

| Scenario | Kind | Baseline p50 RSS MiB | Current p50 RSS MiB | Baseline p90 RSS MiB | Current p90 RSS MiB | Baseline p95 RSS MiB | Current p95 RSS MiB | Baseline Max RSS MiB | Current Max RSS MiB |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| tree-view-repeat-click-burst | user-flow | 56.55 | 57.12 | 56.93 | 57.61 | 57 | 57.69 | 57 | 57.69 |
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,19 @@
"command": "better-todo-tree.treeStateBusy",
"title": "%better-todo-tree.command.treeStateBusy.title%",
"category": "%better-todo-tree.command.category%",
"icon": "$(loading)"
"icon": {
"light": "resources/button-icons/refresh-spin-light.svg",
"dark": "resources/button-icons/refresh-spin-dark.svg"
}
},
{
"command": "better-todo-tree.scanBusy",
"title": "%better-todo-tree.command.scanBusy.title%",
"category": "%better-todo-tree.command.category%",
"icon": "$(loading)"
"icon": {
"light": "resources/button-icons/refresh-spin-light.svg",
"dark": "resources/button-icons/refresh-spin-dark.svg"
}
},
{
"command": "better-todo-tree.filter",
Expand Down
7 changes: 7 additions & 0 deletions resources/button-icons/refresh-spin-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions resources/button-icons/refresh-spin-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ function activate( context )
{
var message = buildScanProgressMessage( state, snapshot );

statusBarIndicator.text = "$(loading~spin) " + identity.DISPLAY_NAME + " " + snapshot.percent + "%";
statusBarIndicator.text = identity.STATUS_BUSY_ICON_LABEL + " " + identity.DISPLAY_NAME + " " + snapshot.percent + "%";
statusBarIndicator.show();
statusBarIndicator.command = identity.COMMANDS.stopScan;
statusBarIndicator.tooltip = message || 'Click to interrupt scan';
Expand Down Expand Up @@ -1119,7 +1119,7 @@ function activate( context )
interrupted = false;
cancelledScanGenerations.delete( activeScanGeneration );

statusBarIndicator.text = identity.DISPLAY_NAME + ": Scanning...";
statusBarIndicator.text = identity.STATUS_BUSY_ICON_LABEL + " " + identity.DISPLAY_NAME + ": Scanning...";
statusBarIndicator.show();
statusBarIndicator.command = identity.COMMANDS.stopScan;
statusBarIndicator.tooltip = "Click to interrupt scan";
Expand Down
4 changes: 4 additions & 0 deletions src/extensionIdentity.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ var LEGACY_NAMESPACE = 'todo-tree';

var DISPLAY_NAME = 'Better Todo Tree';
var LEGACY_DISPLAY_NAME = 'Todo Tree';
var STATUS_BUSY_ICON = 'sync~spin';
var STATUS_BUSY_ICON_LABEL = '$(' + STATUS_BUSY_ICON + ')';

var VIEW_CONTAINER_ID = 'todo-tree-container';
var VIEW_ID = 'todo-tree-view';
Expand Down Expand Up @@ -242,6 +244,8 @@ module.exports.CURRENT_NAMESPACE = CURRENT_NAMESPACE;
module.exports.LEGACY_NAMESPACE = LEGACY_NAMESPACE;
module.exports.DISPLAY_NAME = DISPLAY_NAME;
module.exports.LEGACY_DISPLAY_NAME = LEGACY_DISPLAY_NAME;
module.exports.STATUS_BUSY_ICON = STATUS_BUSY_ICON;
module.exports.STATUS_BUSY_ICON_LABEL = STATUS_BUSY_ICON_LABEL;
module.exports.VIEW_CONTAINER_ID = VIEW_CONTAINER_ID;
module.exports.VIEW_ID = VIEW_ID;
module.exports.EXPORT_SCHEME = EXPORT_SCHEME;
Expand Down
2 changes: 2 additions & 0 deletions test/extension.scan-parity.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3585,6 +3585,7 @@ QUnit.test( "workspace scans publish progress, current target, and clear the tre
return typeof report.message === 'string' && report.message.indexOf( 'tracked.js' ) >= 0;
} ), true );
assert.equal( harness.vscode.treeViews[ 0 ].message.indexOf( 'tracked.js' ) >= 0, true );
assert.equal( harness.vscode.statusBarItems[ 0 ].text.indexOf( '$(sync~spin) Better Todo Tree' ), 0 );
assert.equal( harness.vscode.statusBarItems[ 0 ].text.indexOf( 'Better Todo Tree' ) >= 0, true );
assert.equal( harness.vscode.statusBarItems[ 0 ].text.indexOf( '100%' ) === -1, true );

Expand Down Expand Up @@ -3617,6 +3618,7 @@ QUnit.test( "scan progress cancellation interrupts the active scan and surfaces
return matrixHelpers.flushAsyncWork().then( function()
{
assert.equal( harness.vscode.progressSessions.length, 1 );
assert.equal( harness.vscode.statusBarItems[ 0 ].text.indexOf( '$(sync~spin) Better Todo Tree' ), 0 );
harness.vscode.progressSessions[ 0 ].cancel();
return matrixHelpers.flushAsyncWork();
} ).then( function()
Expand Down
56 changes: 42 additions & 14 deletions test/package.manifest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ function readPackageNls( fileName )
return JSON.parse( fs.readFileSync( path.join( __dirname, '..', fileName ), 'utf8' ) );
}

function readWorkspaceText( relativePath )
{
return fs.readFileSync( path.join( __dirname, '..', relativePath ), 'utf8' );
}

function findCommandContribution( packageJson, commandId )
{
return packageJson.contributes.commands.find( function( entry )
{
return entry.command === commandId;
} );
}

function getConfigurationProperty( propertyName )
{
return languageMatrix.findConfigurationProperty( propertyName );
Expand Down Expand Up @@ -193,24 +206,39 @@ QUnit.test( 'busy and composite tree commands have localization entries in both
assert.notOk( chinese[ 'better-todo-tree.command.treeStateBusy.title' ].indexOf( '$(' ) >= 0 );
} );

QUnit.test( 'busy placeholder commands use static product icons with plain localized titles', function( assert )
QUnit.test( 'issue #38 busy placeholder commands animate only their svg glyphs', function( assert )
{
var packageJson = readPackageJson();
var treeStateBusy = packageJson.contributes.commands.find( function( entry )
{
return entry.command === 'better-todo-tree.treeStateBusy';
} );
var scanBusy = packageJson.contributes.commands.find( function( entry )
{
return entry.command === 'better-todo-tree.scanBusy';
} );

assert.equal( treeStateBusy.icon, '$(loading)' );
assert.equal( scanBusy.icon, '$(loading)' );
assert.equal( treeStateBusy.icon.indexOf( '~spin' ), -1 );
assert.equal( scanBusy.icon.indexOf( '~spin' ), -1 );
var expectedBusyIcon = {
light: 'resources/button-icons/refresh-spin-light.svg',
dark: 'resources/button-icons/refresh-spin-dark.svg'
};
var treeStateBusy = findCommandContribution( packageJson, 'better-todo-tree.treeStateBusy' );
var scanBusy = findCommandContribution( packageJson, 'better-todo-tree.scanBusy' );

assert.deepEqual( treeStateBusy.icon, expectedBusyIcon );
assert.deepEqual( scanBusy.icon, expectedBusyIcon );
assert.equal( JSON.stringify( treeStateBusy.icon ).indexOf( '~spin' ), -1 );
assert.equal( JSON.stringify( scanBusy.icon ).indexOf( '~spin' ), -1 );
assert.equal( JSON.stringify( treeStateBusy.icon ).indexOf( 'loading' ), -1 );
assert.equal( JSON.stringify( scanBusy.icon ).indexOf( 'loading' ), -1 );
assert.equal( treeStateBusy.title, '%better-todo-tree.command.treeStateBusy.title%' );
assert.equal( scanBusy.title, '%better-todo-tree.command.scanBusy.title%' );

Object.keys( expectedBusyIcon ).forEach( function( theme )
{
var svg = readWorkspaceText( expectedBusyIcon[ theme ] );
var groupStart = svg.indexOf( '<g>' );
var groupEnd = svg.indexOf( '</g>' );
var animateTransform = svg.indexOf( '<animateTransform' );

assert.ok( groupStart >= 0, theme + ' icon has a glyph group' );
assert.ok( groupEnd > groupStart, theme + ' icon closes the glyph group' );
assert.ok( animateTransform > groupStart, theme + ' icon animates inside the glyph group' );
assert.ok( animateTransform < groupEnd, theme + ' icon animation stays inside the glyph group' );
assert.equal( svg.substring( 0, groupStart ).indexOf( '<animateTransform' ), -1, theme + ' icon leaves the svg root static' );
assert.equal( svg.indexOf( '<rect' ), -1, theme + ' icon has no background rectangle' );
} );
} );

QUnit.test( 'showBadges metadata documents file icon theme coupling', function( assert )
Expand Down