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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Change Log

## [2.0.0]

Three things change without being asked to, which is what the major version is for. No setting was removed or renamed, so existing configuration carries over untouched.

- The readings are now five status bar entries — CPU, GPU, Battery, Memory and Disk — rather than one. Anyone who had hidden or repositioned the single entry will need to do it again, and in a narrow window VS Code may drop the lower-priority entries (Memory, then Disk) where before it had one entry to fit. In exchange, each hover answers for its own group, each can be hidden on its own from the status bar's right-click menu, where they now appear by name, and a group whose metrics are all switched off or unavailable disappears rather than sitting there empty.
- `systemvitals.updatefrequencyms` now defaults to 10 seconds rather than 2. VS Code redraws an open hover the instant its content changes, so details rebuilt every couple of seconds flicker and resize while being read. One interval governs the reading and its details together, so a panel never shows a different sample from the entry behind it. Lower the setting for livelier numbers.
- The minimum VS Code version is now 1.74, up from 1.53, which is what markdown status bar tooltips require. Installs on older VS Code stay on 1.0.1.
- CPU temperature now sits with the other CPU readings rather than at the end of the line, so the CPU group is contiguous.

Everything else:

- Hovering a reading now opens a details panel for it. Each expands into the figures the status bar has no room for: the user/system split and per-core load bars behind the CPU percentage, cached and swap memory behind the memory fraction, the GPU's name and core count, battery health and cycle count, and used, free and total for every volume regardless of which one `systemvitals.disk.format` picks. A panel covers exactly what is on show, so a metric switched off is absent from both.
- Clicking a reading opens a details view: the same figures as the hover, for every group at once, in something that stays put while it is read and updates in place, with the group you clicked outlined. It docks in the panel beside Terminal and Problems, directly above the readings, rather than taking an editor tab. It closes by its title bar ✕, by clicking the same reading again, or from the command palette; clicking a different reading moves the outline instead of closing. Also on the palette as "System Vitals: Show Details".
- The panel carries no System Vitals tab until one is asked for, and closing the view removes the tab rather than leaving an empty shell. The view reads the samples the status bar already takes rather than polling on its own, so it costs nothing while closed.
- Each group in the details view has a Settings button that opens the Settings editor filtered to that group, so the disk group lands on the disk settings rather than the full list.
- Added a note to the GPU panel explaining that its memory allocation is what the driver has claimed from shared system memory, not a fixed VRAM capacity.
- Each metric is now sampled once per update and feeds both the status bar and its hover, so the details cost no extra polling.

## [1.0.1]

- Disk space is now usable on macOS without configuration. It previously listed all eight APFS volumes that macOS reports for a single physical disk, and the most obvious of them was misleading: `/` is the sealed read-only system snapshot, which reports roughly 95% free however full the machine is. The data volume is now shown in its place, labelled `/`.
Expand Down
52 changes: 48 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,25 @@ Display CPU frequency and usage, GPU utilization, memory consumption, disk space
- **Memory** — consumed out of total
- **Disk** — space remaining or used, per drive
- **Battery** — percentage remaining, hidden automatically on devices without a battery
- **Hover or click for detail** — every metric expands into the figures the status bar has no room for, as a hover or as a panel that stays put

## Screenshot
## Screenshots

![System Vitals in the VS Code status bar](images/statusbar.png)

CPU usage, CPU frequency, GPU utilization and memory, running on an Apple Silicon Mac. GPU memory, disk space and CPU temperature are available as well, and are off by default.
CPU usage, CPU frequency, GPU utilization and GPU memory, running on an Apple Silicon Mac. Each group is its own status bar entry. Disk space and CPU temperature are available as well, and are off by default.

![The hover for the GPU reading](images/hover.png)

Hovering a reading explains it, and nothing else. Here the GPU entry gives the accelerator's name and core count, its utilization, and memory in use against the driver's allocation — with a note about what that allocation actually is.

![The details view docked in the panel](images/details.png)

Clicking a reading opens the details view in the panel, beside Terminal and Problems, with the group you clicked outlined — the GPU here. Note that every figure matches the reading behind it in the status bar, down to the last decimal: one sample feeds both, so they cannot drift apart.

## Requirements

None beyond VS Code 1.53 or newer. The `systeminformation` module is bundled with the extension, and macOS GPU statistics are read from the IOKit registry with `ioreg`, which requires no additional software and no elevated privileges.
None beyond VS Code 1.74 or newer. The `systeminformation` module is bundled with the extension, and macOS GPU statistics are read from the IOKit registry with `ioreg`, which requires no additional software and no elevated privileges.

## Extension Settings

Expand All @@ -35,12 +44,47 @@ None beyond VS Code 1.53 or newer. The `systeminformation` module is bundled wit
- `systemvitals.gpu.unit`: Unit used for GPU memory (GB-B).
- `systemvitals.disk.format`: Configures how the disk space is displayed (percentage remaining/used, absolute remaining, used out of totel).
- `systemvitals.disk.drives`: Drives to show, by mount point or device name. For example, `C:` on Windows, `/home` or `/dev/sda1` on Linux. Leave empty to pick sensible volumes automatically; see Disk Space below.
- `systemvitals.updatefrequencyms`: How frequently to query systeminformation. The minimum is 200 ms as to prevent accidentally updating so fast as to freeze up your machine.
- `systemvitals.updatefrequencyms`: How frequently to query systeminformation, 10 seconds by default. This governs the hover details as well as the status bar; see Hovering for Detail below for why the default is unhurried. The minimum is 200 ms as to prevent accidentally updating so fast as to freeze up your machine.
- `systemvitals.freq.unit`: Unit used for the CPU frequency (GHz-Hz).
- `systemvitals.mem.unit`: Unit used for the RAM consumption (GB-B).
- `systemvitals.show.precision`: Number of decimal places shown for each figure, 0 to 2. Applies to the readings and to the details alike.
- `systemvitals.alignLeft`: Toggles the alignment of the status bar.
- `systemvitals.color`: Color of the status bar text in hex code (for example, #FFFFFF is white). The color must be in the format #RRGGBB, using hex digits.

## Hover and Click for Detail

The status bar has room for one number per metric. Hovering a reading gives that number its context; clicking opens the same detail in a view that stays put.

The readings sit in the status bar as five separate groups — CPU, GPU, Battery, Memory and Disk — and each answers for itself: hovering the GPU reading explains the GPU and nothing else. Each group can also be hidden on its own from the status bar's right-click menu, where they appear as "System Vitals GPU" and so on.

- **CPU** — the processor's name and core count, the user/system split behind the usage percentage, a bar per core showing whether the machine is evenly busy or one core is pinned, the 1/5/15 minute load averages, and the frequency spread across cores where the platform reports one.
- **GPU** — the accelerator's name and core count, utilization, and memory in use alongside the driver's allocation.
- **Memory** — memory in use as a percentage of total, memory still available, cache, and swap. Cache is memory the system hands back the moment anything wants it, which is why "used" so often looks alarming and is not.
- **Disk** — used, free, total and percentage for every volume shown, whichever single figure `systemvitals.disk.format` picks for the status bar.
- **Battery** — charge, whether it is charging or plugged in and idle, time remaining or time until full, health as a percentage of design capacity, and cycle count.

A hover elaborates on what is displayed, so a metric you have switched off, or one your machine cannot report, is absent from both — and a group with nothing left to show disappears from the status bar rather than sitting there empty. Rows whose underlying figure is unavailable are left out rather than shown as zero. Each metric is sampled once per update and feeds both the status bar and the hover, so the detail costs no extra polling.

### The details view

A hover lasts only as long as you hold the pointer still. Clicking a reading opens the same detail as a **System Vitals** tab in the panel, beside Terminal and Problems — directly above the readings themselves. It shows every group at once, updates in place, and outlines the group you clicked so you can find it.

It closes three ways, whichever you reach for first:

- the **✕** in its title bar
- clicking the same reading again
- **System Vitals: Close Details** on the command palette

Clicking a *different* reading keeps it open and moves the outline to that group instead. **System Vitals: Show Details** on the palette opens it without singling out any group.

Each group has a **Settings** button that opens the Settings editor filtered to that group — the disk group lands on the disk format and drive settings rather than the full list.

The panel carries no System Vitals tab until you ask for one, and closing the view takes the tab away again rather than leaving an empty shell behind. The view reads the same samples the status bar does rather than polling on its own, so it costs nothing while closed. It is treated as transient, so reloading the window closes it too; click a reading to bring it back.

### Why updates are unhurried

VS Code redraws an open hover the instant its content changes, so details rebuilt every couple of seconds flicker and resize while you are trying to read them. `systemvitals.updatefrequencyms` therefore defaults to 10 seconds, and it governs the reading and its details together — a panel always says exactly what the entry behind it says, never a sample or two out of date. Lower it if you want livelier numbers and don't mind the hover redrawing under the pointer.

## GPU Monitoring

GPU statistics are **macOS only**, and work on Apple Silicon (M-series) as well as Intel Macs. They are read from the IOKit registry with `ioreg`, which requires no elevated privileges — unlike `powermetrics`, which needs `sudo`. On any machine that does not report GPU statistics, both GPU metrics hide themselves automatically rather than showing an error.
Expand Down
Binary file added images/details.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/hover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/statusbar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 52 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"name": "system-vitals",
"displayName": "System Vitals",
"description": "CPU, GPU, memory, disk and battery monitoring in the VS Code status bar, with native Apple Silicon GPU support.",
"version": "1.0.1",
"version": "2.0.0",
"publisher": "SachinSachdeva",
"license": "SEE LICENSE IN LICENSE.md",
"engines": {
"vscode": "^1.53.0"
"vscode": "^1.74.0"
},
"categories": [
"Other"
Expand All @@ -28,6 +28,53 @@
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "systemvitals.showDetails",
"title": "Show Details",
"category": "System Vitals"
},
{
"command": "systemvitals.hideDetails",
"title": "Close Details",
"category": "System Vitals",
"icon": "$(close)"
}
],
"menus": {
"view/title": [
{
"command": "systemvitals.hideDetails",
"when": "view == systemvitals.details",
"group": "navigation"
}
],
"commandPalette": [
{
"command": "systemvitals.hideDetails",
"when": "systemvitals.detailsRequested"
}
]
},
"viewsContainers": {
"panel": [
{
"id": "systemvitals",
"title": "System Vitals",
"icon": "images/icon.png"
}
]
},
"views": {
"systemvitals": [
{
"id": "systemvitals.details",
"name": "Details",
"type": "webview",
"when": "systemvitals.detailsRequested"
}
]
},
"configuration": {
"type": "object",
"title": "System Vitals",
Expand Down Expand Up @@ -104,9 +151,9 @@
},
"systemvitals.updatefrequencyms": {
"type": "number",
"default": 2000,
"default": 10000,
"minimum": 200,
"description": "How long in ms between updates to resources."
"description": "How long in ms between updates to resources. This governs the hover details as well as the status bar, so the two always agree. VS Code redraws an open hover whenever its content changes, so short intervals make the details flicker while you are reading them."
},
"systemvitals.freq.unit": {
"type": "string",
Expand Down Expand Up @@ -168,7 +215,7 @@
},
"devDependencies": {
"@types/node": "^20.19.43",
"@types/vscode": "1.53.0",
"@types/vscode": "1.74.0",
"tslint": "^5.8.0",
"typescript": "^5.9.3"
}
Expand Down
27 changes: 27 additions & 0 deletions src/appleGpu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export interface GpuStats {
inUseMemory: number;
/** "Alloc system memory", in bytes. */
allocatedMemory: number;
/** The accelerator's "model", such as "Apple M4". Null when the node does not name itself. */
model: string | null;
/** The accelerator's "gpu-core-count". Null when the node does not report one. */
coreCount: number | null;
}

// Recurse one level from each IOAccelerator node so we pick up its properties,
Expand All @@ -34,6 +38,12 @@ const CACHE_WINDOW_MS: number = 100;

const PERFORMANCE_STATISTICS_PATTERN = /"PerformanceStatistics" = \{([^}]*)\}/;

// Descriptive properties sit beside PerformanceStatistics in the same node,
// either side of it, so they are read from the start of that node onwards.
const NODE_MARKER = '+-o ';
const MODEL_PATTERN = /"model" = "([^"]*)"/;
const CORE_COUNT_PATTERN = /"gpu-core-count" = (\d+)/;

/**
* Extracts GPU statistics from the output of
* `ioreg -r -d 1 -w 0 -c IOAccelerator`.
Expand All @@ -57,13 +67,30 @@ export function parsePerformanceStatistics(ioregOutput: string): GpuStats | null
return null;
}

// Anchored at the node that supplied the statistics, so a machine whose
// first accelerator reports nothing cannot lend its name to the second.
let node: string = ioregOutput.slice(nodeStart(ioregOutput, statisticsBlock.index));
let model = MODEL_PATTERN.exec(node);
let coreCount = CORE_COUNT_PATTERN.exec(node);

return {
utilization: utilization,
inUseMemory: readStatistic(statistics, "In use system memory") || 0,
allocatedMemory: readStatistic(statistics, "Alloc system memory") || 0,
model: model === null ? null : model[1],
coreCount: coreCount === null ? null : parseInt(coreCount[1], 10),
};
}

/**
* Where the node containing the given offset begins, or the start of the dump
* when the output carries no node headers at all.
*/
function nodeStart(ioregOutput: string, offset: number): number {
let start: number = ioregOutput.lastIndexOf(NODE_MARKER, offset);
return start === -1 ? 0 : start;
}

/**
* Reads a single `"key"=<integer>` entry out of a PerformanceStatistics dict.
*
Expand Down
Loading
Loading