Summary
_returnGpuValue() builds a fresh object literal on every call and then guards insertion with Array.prototype.includes(). includes() compares with SameValueZero, which for objects is reference identity — and the object is newly constructed each time, so the guard can never match an existing entry. Every GPU sensor reading appends another copy.
After 7 days 4 hours of uptime with update-time = 3, _nvidia_labels held 792,730 entries representing exactly 5 distinct values, retaining roughly 200 MB of JS heap.
This affects any GPU, not just Nvidia — the labels I accumulated are all gpu#1 on an AMD Radeon RX 570.
Environment
|
|
| OS |
Ubuntu 26.04 LTS |
| GNOME Shell |
50.1 (Wayland) |
| GJS |
1.88.0 |
| Vitals |
version 80 |
| GPU |
AMD Radeon RX 570 |
update-time |
3 seconds |
hot-sensors |
includes _gpu#1_usage_ |
The bug
sensors.js:791:
let nvidiaLabel = {'label': label, 'type': type, 'format': format};
if (!this._nvidia_labels.includes(nvidiaLabel))
this._nvidia_labels.push(nvidiaLabel);
nvidiaLabel is allocated immediately above the check, so it is never reference-equal to anything already in the array. includes() returns false unconditionally and push() always runs.
The array's only consumer is _disableGpuLabels() (sensors.js:772), which iterates it to emit a disabled value per known GPU label — so the intent is clearly a set of distinct descriptors, and the duplicates serve no purpose beyond making that loop progressively slower.
Evidence
Read from the running shell after 7d 4h uptime:
_nvidia_labels.length = 792,730
Collapsing it by value yields 5 entries:
Memory Total | gpu#1
Graphics | gpu#1-group
Vendor | gpu#1
Usage | gpu#1
Memory Used | gpu#1
A sample of 500 consecutive entries showed a single distinct object shape (label,type,format), e.g.:
{"label":"Memory Total","type":"gpu#1","format":"memory"}
Growth rate observed live was ~0.4–1.3 entries/second depending on activity, consistent with 792k over the uptime.
Deduplicating the array in place and forcing a GC reclaimed ~200 MB of JS heap (measured as part of a 211.8 MB reclaim that also trimmed an unrelated ~946k-element numeric array in another extension, which can account for at most ~8 MB).
There is a secondary cost too: because includes() is O(n) and runs on every GPU reading, each poll scans the entire array. At 792k entries that is a full linear scan several times per update cycle, on the compositor's main thread.
Suggested fix
Compare by value:
let nvidiaLabel = {'label': label, 'type': type, 'format': format};
if (!this._nvidia_labels.some(l => l.label === label && l.type === type && l.format === format))
this._nvidia_labels.push(nvidiaLabel);
This preserves _disableGpuLabels() semantics exactly while bounding the array to the distinct label set (5 entries on this hardware).
A Map keyed on `${label}|${type}|${format}` would also work and would make the membership test O(1) rather than O(n), which may be preferable given it runs on every poll.
Note on verification
I applied the some() version above to my local install and it passes a syntax check, but I have not yet exercised it at runtime — GNOME 45+ extensions are ES modules and cannot be reloaded without restarting the shell, so it takes effect at my next login. I'll follow up here once it has run for a day and I can confirm the array stays at 5 entries.
Reproduction
- Enable Vitals with a GPU sensor in
hot-sensors (any vendor).
- Leave the session running.
- In Looking Glass:
Main.panel.statusArea.vitalsMenu._sensors._nvidia_labels.length
The length climbs monotonically and never plateaus.
Summary
_returnGpuValue()builds a fresh object literal on every call and then guards insertion withArray.prototype.includes().includes()compares with SameValueZero, which for objects is reference identity — and the object is newly constructed each time, so the guard can never match an existing entry. Every GPU sensor reading appends another copy.After 7 days 4 hours of uptime with
update-time = 3,_nvidia_labelsheld 792,730 entries representing exactly 5 distinct values, retaining roughly 200 MB of JS heap.This affects any GPU, not just Nvidia — the labels I accumulated are all
gpu#1on an AMD Radeon RX 570.Environment
update-timehot-sensors_gpu#1_usage_The bug
sensors.js:791:nvidiaLabelis allocated immediately above the check, so it is never reference-equal to anything already in the array.includes()returnsfalseunconditionally andpush()always runs.The array's only consumer is
_disableGpuLabels()(sensors.js:772), which iterates it to emit adisabledvalue per known GPU label — so the intent is clearly a set of distinct descriptors, and the duplicates serve no purpose beyond making that loop progressively slower.Evidence
Read from the running shell after 7d 4h uptime:
Collapsing it by value yields 5 entries:
A sample of 500 consecutive entries showed a single distinct object shape (
label,type,format), e.g.:{"label":"Memory Total","type":"gpu#1","format":"memory"}Growth rate observed live was ~0.4–1.3 entries/second depending on activity, consistent with 792k over the uptime.
Deduplicating the array in place and forcing a GC reclaimed ~200 MB of JS heap (measured as part of a 211.8 MB reclaim that also trimmed an unrelated ~946k-element numeric array in another extension, which can account for at most ~8 MB).
There is a secondary cost too: because
includes()is O(n) and runs on every GPU reading, each poll scans the entire array. At 792k entries that is a full linear scan several times per update cycle, on the compositor's main thread.Suggested fix
Compare by value:
This preserves
_disableGpuLabels()semantics exactly while bounding the array to the distinct label set (5 entries on this hardware).A
Mapkeyed on`${label}|${type}|${format}`would also work and would make the membership test O(1) rather than O(n), which may be preferable given it runs on every poll.Note on verification
I applied the
some()version above to my local install and it passes a syntax check, but I have not yet exercised it at runtime — GNOME 45+ extensions are ES modules and cannot be reloaded without restarting the shell, so it takes effect at my next login. I'll follow up here once it has run for a day and I can confirm the array stays at 5 entries.Reproduction
hot-sensors(any vendor).The length climbs monotonically and never plateaus.