[gnome] Extension update for Vitals

This commit is contained in:
2026-04-09 16:55:07 -04:00
parent 505c67d292
commit 7c9ad06f00
9 changed files with 4 additions and 642 deletions

View File

@ -17,7 +17,6 @@ import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js';
import * as Values from './values.js';
import * as Config from 'resource:///org/gnome/shell/misc/config.js';
import * as MenuItem from './menuItem.js';
import * as HistoryGraph from './history.js';
let vitalsMenu;
@ -59,15 +58,9 @@ var VitalsMenuButton = GObject.registerClass({
this._newGpuDetected = false;
this._newGpuDetectedCount = 0;
this._last_query = new Date().getTime();
this._historyPopout = null;
this._historyHideTimeoutId = null;
this._historyPopoutSensorKey = null;
this._historyPopoutLabel = null;
this._sensors = new Sensors.Sensors(this._settings, this._sensorIcons);
this._values = new Values.Values(this._settings, this._sensorIcons);
this._historyCachePath = GLib.get_user_cache_dir() + '/vitals/history.json';
this._values.loadTimeSeries(this._historyCachePath);
this._menuLayout = new St.BoxLayout({
vertical: false,
clip_to_allocation: true,
@ -88,7 +81,7 @@ var VitalsMenuButton = GObject.registerClass({
this._addSettingChangedSignal('menu-centered', this._positionInPanelChanged.bind(this));
this._addSettingChangedSignal('icon-style', this._iconStyleChanged.bind(this));
let settings = [ 'use-higher-precision', 'alphabetize', 'hide-zeros', 'fixed-widths', 'hide-icons', 'unit', 'memory-measurement', 'include-public-ip', 'network-speed-format', 'storage-measurement', 'include-static-info', 'include-static-gpu-info', 'show-sensor-history-graph' ];
let settings = [ 'use-higher-precision', 'alphabetize', 'hide-zeros', 'fixed-widths', 'hide-icons', 'unit', 'memory-measurement', 'include-public-ip', 'network-speed-format', 'storage-measurement', 'include-static-info', 'include-static-gpu-info' ];
for (let setting of Object.values(settings))
this._addSettingChangedSignal(setting, this._redrawMenu.bind(this));
@ -97,7 +90,6 @@ var VitalsMenuButton = GObject.registerClass({
this._addSettingChangedSignal('show-' + sensor, this._showHideSensorsChanged.bind(this));
this._initializeMenu();
this._createHistoryPopout();
// start off with fresh sensors
this._querySensors();
@ -184,216 +176,10 @@ var VitalsMenuButton = GObject.registerClass({
// refresh sensors now
this._querySensors();
} else {
this._hideHistoryPopout();
}
});
}
_createHistoryPopout() {
const popoutWidth = 280;
const popoutHeight = 145;
this._historyPopout = new St.BoxLayout({
vertical: true,
style_class: 'vitals-history-popout',
width: popoutWidth,
height: popoutHeight,
reactive: true,
visible: false
});
this._historyPopout.clip_to_allocation = true;
this._historyGraph = new HistoryGraph.HistoryGraph();
this._historyTitleLabel = new St.Label({
text: '',
style_class: 'vitals-history-popout-label',
x_align: Clutter.ActorAlign.END
});
this._historyPopout.add_child(this._historyTitleLabel);
this._historyGraphRow = new St.BoxLayout({
vertical: false,
x_expand: true,
style_class: 'vitals-history-graph-row'
});
this._historyYAxis = new St.BoxLayout({
vertical: true,
width: 56,
style_class: 'vitals-history-y-axis'
});
this._historyYMax = new St.Label({
text: '',
style_class: 'vitals-history-popout-axis',
x_align: Clutter.ActorAlign.END
});
this._historyYMin = new St.Label({
text: '',
style_class: 'vitals-history-popout-axis',
x_align: Clutter.ActorAlign.END
});
this._historyYSpacer = new St.BoxLayout({ vertical: true, y_expand: true });
this._historyYAxis.add_child(this._historyYMax);
this._historyYAxis.add_child(this._historyYSpacer);
this._historyYAxis.add_child(this._historyYMin);
this._historyGraphRow.add_child(this._historyYAxis);
this._historyGraphRow.add_child(this._historyGraph);
this._historyGraphRightSpacer = new St.BoxLayout({
vertical: true,
width: 18,
style_class: 'vitals-history-graph-right-spacer'
});
this._historyGraphRow.add_child(this._historyGraphRightSpacer);
this._historyPopout.add_child(this._historyGraphRow);
this._historyXWrap = new St.BoxLayout({
vertical: false,
x_expand: true,
style_class: 'vitals-history-x-wrap'
});
this._historyXSpacer = new St.BoxLayout({
vertical: true,
width: 62,
style_class: 'vitals-history-x-spacer'
});
this._historyXRow = new St.BoxLayout({
vertical: false,
x_expand: true,
style_class: 'vitals-history-x-row'
});
this._historyXLeft = new St.Label({
text: '',
style_class: 'vitals-history-popout-axis',
x_align: Clutter.ActorAlign.START,
x_expand: true
});
this._historyXRight = new St.Label({
text: _('now'),
style_class: 'vitals-history-popout-axis',
x_align: Clutter.ActorAlign.END
});
this._historyXRow.add_child(this._historyXLeft);
this._historyXRow.add_child(this._historyXRight);
this._historyXWrap.add_child(this._historyXSpacer);
this._historyXWrap.add_child(this._historyXRow);
this._historyPopout.add_child(this._historyXWrap);
this._historyPopout.connect('enter-event', () => {
if (this._historyHideTimeoutId) {
GLib.Source.remove(this._historyHideTimeoutId);
this._historyHideTimeoutId = null;
}
});
this._historyPopout.connect('leave-event', () => {
this._scheduleHistoryPopoutHide();
});
}
_updateHistoryGraph(key, label, samples) {
const historyDuration = Math.max(60, this._settings.get_int('sensor-history-duration'));
const nowSec = Date.now() / 1000;
const cutoff = nowSec - historyDuration;
const windowed = samples.filter(s => s.t >= cutoff);
while (windowed.length > 0 && windowed[0].v === null) windowed.shift();
const base = Math.max(1, Math.ceil(windowed.length / 200));
this._historyGraph.setData(windowed, label, '', base);
const actualSpan = this._historyGraph.getTimeSpan();
const displayDuration = actualSpan > 0 ? Math.min(historyDuration, Math.round(actualSpan)) : historyDuration;
this._historyXLeft.text = this._values.formatDuration(displayDuration) + ' ' + _('ago');
const rawRange = this._historyGraph.getRawRange();
if (rawRange) {
this._historyYMax.text = this._values.formatValue(key, rawRange.max);
this._historyYMin.text = this._values.formatValue(key, rawRange.min);
this._historyYAxis.show();
} else {
this._historyYMax.text = '';
this._historyYMin.text = '';
this._historyYAxis.hide();
}
}
_showHistoryPopout(key, label, itemActor) {
if (!this._settings.get_boolean('show-sensor-history-graph')) return;
const samples = this._values.getTimeSeries(key);
if (samples.length === 0) return;
this._historyPopoutSensorKey = key;
this._historyPopoutLabel = label;
try {
this._historyTitleLabel.text = label + ' ' + _('history');
this._historyTitleLabel.show();
this._updateHistoryGraph(key, label, samples);
} catch (e) {
this._historyYMax.text = '';
this._historyYMin.text = '';
}
const parent = this.menu.actor.get_parent();
if (!parent) return;
if (this._historyPopout.get_parent() !== parent) {
if (this._historyPopout.get_parent())
this._historyPopout.get_parent().remove_child(this._historyPopout);
parent.add_child(this._historyPopout);
}
const menuX = this.menu.actor.get_x();
const menuY = this.menu.actor.get_y();
let popoutY = menuY;
if (itemActor) {
let relY = 0;
let node = itemActor;
while (node && node !== this.menu.actor) {
relY += node.get_y();
node = node.get_parent();
}
const rowH = itemActor.get_height();
const popoutH = this._historyPopout.get_height();
popoutY = menuY + relY + Math.round((rowH - popoutH) / 2);
popoutY = Math.max(0, popoutY);
}
const popoutW = this._historyPopout.get_width();
const menuW = this.menu.actor.get_width();
let popoutX = menuX - popoutW - 8;
if (popoutX < 0)
popoutX = menuX + menuW + 8;
this._historyPopout.set_position(popoutX, popoutY);
this._historyPopout.show();
if (this._historyHideTimeoutId) {
GLib.Source.remove(this._historyHideTimeoutId);
this._historyHideTimeoutId = null;
}
}
_scheduleHistoryPopoutHide() {
if (this._historyHideTimeoutId) return;
this._historyHideTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => {
this._hideHistoryPopout();
this._historyHideTimeoutId = null;
return GLib.SOURCE_REMOVE;
});
}
_hideHistoryPopout() {
if (this._historyHideTimeoutId) {
GLib.Source.remove(this._historyHideTimeoutId);
this._historyHideTimeoutId = null;
}
if (this._historyPopout && this._historyPopout.get_parent()) {
this._historyPopout.hide();
this._historyPopout.get_parent().remove_child(this._historyPopout);
}
this._historyPopoutSensorKey = null;
this._historyPopoutLabel = null;
}
_refreshHistoryPopout() {
const key = this._historyPopoutSensorKey;
const label = this._historyPopoutLabel;
if (!key || !label) return;
if (!this._historyPopout || !this._historyPopout.visible) return;
const samples = this._values.getTimeSeries(key);
if (samples.length === 0) return;
try {
this._updateHistoryGraph(key, label, samples);
} catch (e) {
// ignore
}
}
_initializeMenuGroup(groupName, optionName, menuSuffix = '', position = -1) {
this._groups[groupName] = new PopupMenu.PopupSubMenuMenuItem(_(this._ucFirst(groupName) + menuSuffix), true);
this._groups[groupName].icon.gicon = Gio.icon_new_for_string(this._sensorIconPath(groupName));
@ -459,7 +245,7 @@ var VitalsMenuButton = GObject.registerClass({
GLib.PRIORITY_DEFAULT,
update_time,
(self) => {
// always query sensors (for panel display when hot, and for history graph data)
// always query sensors (for panel display when hot)
this._querySensors();
return GLib.SOURCE_CONTINUE;
}
@ -561,7 +347,6 @@ var VitalsMenuButton = GObject.registerClass({
}
_redrawMenu() {
this._hideHistoryPopout();
this._removeHotItems();
for (let key in this._sensorMenuItems) {
@ -598,7 +383,6 @@ var VitalsMenuButton = GObject.registerClass({
_updateTimeSettingChanged() {
this._destroyTimer();
this._values.clearTimeSeries(this._historyCachePath);
this._initializeTimer();
}
@ -696,25 +480,6 @@ var VitalsMenuButton = GObject.registerClass({
}
this._groups[type].menu.addMenuItem(item, i);
if (this._settings.get_boolean('show-sensor-history-graph')) {
const key = item.key;
const label = item.label;
item.actor.connect('enter-event', () => {
const samples = this._values.getTimeSeries(key);
if (this._historyHideTimeoutId) {
GLib.Source.remove(this._historyHideTimeoutId);
this._historyHideTimeoutId = null;
}
if (samples.length > 0)
this._showHistoryPopout(key, label, item.actor);
else
this._hideHistoryPopout();
});
item.actor.connect('leave-event', () => {
this._scheduleHistoryPopoutHide();
});
}
}
_defaultLabel() {
@ -856,8 +621,6 @@ var VitalsMenuButton = GObject.registerClass({
this._notify('Vitals', this._warnings.join("\n"), 'folder-symbolic');
this._warnings = [];
}
this._refreshHistoryPopout();
}
_notify(msg, details, icon) {
@ -869,13 +632,7 @@ var VitalsMenuButton = GObject.registerClass({
}
destroy() {
this._hideHistoryPopout();
if (this._historyPopout) {
this._historyPopout.destroy();
this._historyPopout = null;
}
this._destroyTimer();
this._values.saveTimeSeries(this._historyCachePath);
this._sensors.destroy();
for (let signal of Object.values(this._settingChangedSignals))

View File

@ -1,157 +0,0 @@
/*
Copyright (c) 2018, Chris Monahan <chris@corecoding.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the GNOME nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import GObject from 'gi://GObject';
import St from 'gi://St';
const GRAPH_WIDTH = 208;
const GRAPH_HEIGHT = 90;
const PADDING = 4;
const MIN_BAR_WIDTH = 1;
export const HistoryGraph = GObject.registerClass({
GTypeName: 'HistoryGraph',
}, class HistoryGraph extends St.Widget {
_init(params = {}) {
super._init({
width: GRAPH_WIDTH,
height: GRAPH_HEIGHT,
style_class: 'vitals-history-graph',
...params
});
this._samples = [];
this._label = '';
this._unit = '';
this._vMin = 0;
this._vMax = 0;
this._base = 1;
this._dataOffset = 0;
this.clip_to_allocation = true;
this._barContainer = new St.Widget({
x_expand: true,
y_expand: true
});
this._barContainer.clip_to_allocation = true;
this.add_child(this._barContainer);
}
setData(samples, label, unit, base) {
this._samples = Array.isArray(samples) ? samples : [];
this._label = label || '';
this._unit = unit || '';
this._base = Math.max(1, base);
this._rebuildBars();
}
_rebuildBars() {
try {
const children = this._barContainer.get_children();
if (children && children.length > 0) {
for (let i = children.length - 1; i >= 0; i--)
children[i].destroy();
}
} catch (e) {
// ignore
}
const data = this._samples;
if (data.length === 0) return;
const graphW = GRAPH_WIDTH - 2 * PADDING;
const graphH = GRAPH_HEIGHT - PADDING;
if (graphW <= 0 || graphH <= 0) return;
const base = this._base;
const maxBars = Math.floor(graphW / MIN_BAR_WIDTH);
const totalBars = Math.ceil(data.length / base);
const numBars = Math.min(totalBars, maxBars);
const dataOffset = (totalBars - numBars) * base;
this._dataOffset = dataOffset;
const barWidth = graphW / numBars;
let vMin = Infinity, vMax = -Infinity;
for (let i = dataOffset; i < data.length; i++) {
if (data[i].v === null) continue;
if (data[i].v < vMin) vMin = data[i].v;
if (data[i].v > vMax) vMax = data[i].v;
}
if (vMin === Infinity) {
vMin = 0;
vMax = 1;
} else if (vMax <= vMin) {
const v = vMin;
if (v >= 0 && v <= 1) {
const margin = 0.05;
vMin = Math.max(0, v - margin);
vMax = Math.min(1, v + margin);
if (vMax <= vMin) vMax = vMin + margin;
} else {
vMin -= 1;
vMax += 1;
}
}
this._vMin = vMin;
this._vMax = vMax;
const vRange = vMax - vMin;
for (let b = 0; b < numBars; b++) {
const iStart = dataOffset + b * base;
const iEnd = Math.min(iStart + base, data.length);
let sum = 0;
let count = 0;
for (let i = iStart; i < iEnd; i++) {
if (data[i].v !== null) {
sum += data[i].v;
count++;
}
}
if (count === 0) continue;
const avg = sum / count;
const norm = (avg - vMin) / vRange;
const barH = Math.max(1, Math.round(norm * graphH));
const x = Math.round(b * barWidth);
const w = Math.round((b + 1) * barWidth) - x;
const bar = new St.Bin({
width: w,
height: barH,
style_class: 'vitals-history-graph-bar'
});
bar.set_position(x, graphH - barH);
this._barContainer.add_child(bar);
}
}
getRawRange() {
if (this._samples.length === 0) return null;
return { min: this._vMin, max: this._vMax };
}
getTimeSpan() {
const start = this._dataOffset;
if (this._samples.length - start < 2) return 0;
return this._samples[this._samples.length - 1].t - this._samples[start].t;
}
});

View File

@ -17,5 +17,5 @@
],
"url": "https://github.com/corecoding/Vitals",
"uuid": "Vitals@CoreCoding.com",
"version": 74
"version": 75
}

View File

@ -49,8 +49,7 @@ const Settings = new GObject.Class({
'alphabetize', 'hide-zeros', 'include-public-ip',
'show-battery', 'fixed-widths', 'hide-icons',
'menu-centered', 'include-static-info',
'show-gpu', 'include-static-gpu-info',
'show-sensor-history-graph' ];
'show-gpu', 'include-static-gpu-info' ];
for (let key in sensors) {
let sensor = sensors[key];

View File

@ -415,35 +415,6 @@
</property>
</object>
</child>
<child>
<object class="GtkListBoxRow">
<property name="width_request">100</property>
<property name="selectable">0</property>
<property name="child">
<object class="GtkBox">
<property name="can_focus">0</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<child>
<object class="GtkLabel">
<property name="hexpand">1</property>
<property name="can_focus">0</property>
<property name="halign">start</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="label" translatable="yes">Show sensor history graph on hover</property>
</object>
</child>
<child>
<object class="GtkSwitch" id="show-sensor-history-graph">
<property name="halign">end</property>
<property name="margin-end">5</property>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</property>
<child type="label_item">

View File

@ -151,15 +151,5 @@
<summary>Icon styles</summary>
<description>Set the style for the displayed sensor icons ('original', 'updated')</description>
</key>
<key type="b" name="show-sensor-history-graph">
<default>true</default>
<summary>Show sensor history graph on hover</summary>
<description>When hovering a sensor row in the menu, show a pop-out graph of its history to the left</description>
</key>
<key type="i" name="sensor-history-duration">
<default>3600</default>
<summary>Sensor history duration (seconds)</summary>
<description>How many seconds of history to keep and display in the hover graph (e.g. 3600 for 1 hour)</description>
</key>
</schema>
</schemalist>

View File

@ -17,64 +17,3 @@
.vitals-button-action:hover, .vitals-button-action:focus { border-color: #777; }
.vitals-button-action > StIcon { icon-size: 16px; }
.vitals-button-box { padding: 0px; spacing: 22px; }
.vitals-history-popout {
padding: 6px 14px 6px 6px;
border-radius: 8px;
border: 1px solid rgba(128, 128, 128, 0.4);
background-color: rgba(30, 30, 30, 0.95);
}
.vitals-history-popout-label {
font-size: 10px;
color: rgba(200, 200, 200, 0.9);
}
.vitals-history-popout-axis {
font-size: 9px;
color: rgba(180, 180, 180, 0.85);
}
.vitals-history-graph-row {
margin-top: 2px;
margin-bottom: 2px;
}
.vitals-history-y-axis {
margin-right: 6px;
padding-top: 2px;
padding-bottom: 2px;
}
.vitals-history-x-wrap {
margin-top: 2px;
}
.vitals-history-x-spacer {
width: 62px;
min-width: 62px;
}
.vitals-history-x-row {
min-width: 0;
}
.vitals-history-x-row .vitals-history-popout-axis {
margin-left: 0;
margin-right: 4px;
}
.vitals-history-graph {
background-color: rgba(0, 0, 0, 0.25);
border-radius: 4px;
padding: 4px 4px 0 4px;
margin: 2px 0 2px 0;
}
.vitals-history-graph-bars {
spacing: 0px;
}
.vitals-history-graph-bar {
background-color: rgba(51, 128, 230, 0.87);
}

View File

@ -25,7 +25,6 @@
*/
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
const cbFun = (d, c) => {
@ -49,138 +48,9 @@ export const Values = GObject.registerClass({
this._history = {};
//this._history2 = {};
this._timeSeries = {};
this._timeSeriesFormat = {};
this._graphableFormats = ['temp', 'in', 'fan', 'percent', 'hertz', 'memory', 'speed', 'storage', 'watt', 'watt-gpu', 'milliamp', 'milliamp-hour', 'load'];
this.resetHistory();
}
_getHistoryDurationSeconds() {
if (this._settings && this._settings.get_int)
return Math.max(60, this._settings.get_int('sensor-history-duration'));
return 3600;
}
_pushTimePoint(key, value, format) {
if (!this._graphableFormats.includes(format)) return;
const num = typeof value === 'number' ? value : parseFloat(value);
if (num !== num) return; // NaN check
this._timeSeriesFormat[key] = format;
const now = Date.now() / 1000;
if (!(key in this._timeSeries)) this._timeSeries[key] = [];
const buf = this._timeSeries[key];
const interval = Math.max(1, this._settings.get_int('update-time'));
const minInterval = interval - 0.5;
if (buf.length > 0 && buf[buf.length - 1].v !== null && (now - buf[buf.length - 1].t) < minInterval) {
buf[buf.length - 1].v = num;
return;
}
if (buf.length > 0) {
const lastT = buf[buf.length - 1].t;
const gap = now - lastT;
if (gap > interval * 3) {
let fillT = lastT + interval;
while (fillT < now - interval * 0.5) {
buf.push({ t: fillT, v: null });
fillT += interval;
}
}
}
buf.push({ t: now, v: num });
const maxAge = this._getHistoryDurationSeconds();
while (buf.length > 0 && buf[0].t < now - maxAge) buf.shift();
const maxPoints = 3600;
while (buf.length > maxPoints) buf.shift();
}
clearTimeSeries(cachePath) {
this._timeSeries = {};
this._timeSeriesFormat = {};
if (cachePath) {
try {
const file = Gio.File.new_for_path(cachePath);
if (file.query_exists(null))
file.delete(null);
} catch (e) {
// ignore
}
}
}
saveTimeSeries(path) {
try {
const obj = {
version: 1,
timeSeries: this._timeSeries,
timeSeriesFormat: this._timeSeriesFormat
};
const json = JSON.stringify(obj);
const dir = GLib.path_get_dirname(path);
GLib.mkdir_with_parents(dir, 0o755);
GLib.file_set_contents(path, json);
} catch (e) {
// ignore write failures
}
}
loadTimeSeries(path) {
try {
const file = Gio.File.new_for_path(path);
if (!file.query_exists(null)) return;
const [ok, contents] = GLib.file_get_contents(path);
if (!ok) return;
const decoder = new TextDecoder('utf-8');
const json = decoder.decode(contents);
const obj = JSON.parse(json);
if (!obj || obj.version !== 1) return;
if (obj.timeSeries && typeof obj.timeSeries === 'object')
this._timeSeries = obj.timeSeries;
if (obj.timeSeriesFormat && typeof obj.timeSeriesFormat === 'object')
this._timeSeriesFormat = obj.timeSeriesFormat;
const now = Date.now() / 1000;
const maxAge = this._getHistoryDurationSeconds();
const cutoff = now - maxAge;
for (const key in this._timeSeries) {
const buf = this._timeSeries[key];
if (!Array.isArray(buf)) {
delete this._timeSeries[key];
continue;
}
while (buf.length > 0 && buf[0].t < cutoff)
buf.shift();
while (buf.length > 0 && buf[0].v === null)
buf.shift();
if (buf.length === 0) {
delete this._timeSeries[key];
delete this._timeSeriesFormat[key];
}
}
} catch (e) {
// ignore corrupt or missing file
}
}
getTimeSeries(key) {
if (!(key in this._timeSeries)) return [];
return this._timeSeries[key].slice();
}
formatValue(key, rawValue) {
const format = key in this._timeSeriesFormat ? this._timeSeriesFormat[key] : 'percent';
return this._legible(rawValue, format);
}
formatDuration(seconds) {
seconds = Math.round(Math.abs(seconds));
if (seconds < 60) return seconds + 's';
const m = Math.floor(seconds / 60);
if (m < 60) return m + 'm';
const h = Math.floor(m / 60);
const rm = m % 60;
if (rm === 0) return h + 'h';
return h + 'h ' + rm + 'm';
}
_legible(value, sensorClass) {
let unit = 1000;
if (value === null) return 'N/A';
@ -375,8 +245,6 @@ export const Values = GObject.registerClass({
// save previous values to update screen on changes only
let previousValue = this._history[type][key];
this._history[type][key] = [legible, value];
if (type !== 'network-rx' && type !== 'network-tx')
this._pushTimePoint(key, value, format);
// process average, min and max values
if (type == 'temperature' || type == 'voltage' || type == 'fan') {
@ -408,7 +276,6 @@ export const Values = GObject.registerClass({
let vals = Object.values(this._history[type]).map(x => parseFloat(x[1]));
let sum = vals.reduce((partialSum, a) => partialSum + a, 0);
const memUnit = this._settings.get_int('memory-measurement') ? 1000 : 1024;
this._pushTimePoint('__' + type + '_boot__', sum / memUnit, 'memory');
output.push(['Boot ' + direction, this._legible(sum, format), type, '__' + type + '_boot__']);
// keeps track of session start point
@ -416,14 +283,11 @@ export const Values = GObject.registerClass({
this._networkSpeedOffset[key] = sum;
// outputs session upload and download for all interfaces for #234
const sessionVal = sum - this._networkSpeedOffset[key];
this._pushTimePoint('__' + type + '_ses__', sessionVal / memUnit, 'memory');
output.push(['Session ' + direction, this._legible(sum - this._networkSpeedOffset[key], format), type, '__' + type + '_ses__']);
// calculate speed for this interface
let speed = (value - previousValue[1]) / dwell;
output.push([label, this._legible(speed, 'speed'), type, key]);
this._pushTimePoint(key, speed, 'speed');
// store speed for Device report
if (!(direction in this._networkSpeeds)) this._networkSpeeds[direction] = {};
@ -439,7 +303,6 @@ export const Values = GObject.registerClass({
for (let iface in this._networkSpeeds[direction])
sumNum += parseFloat(this._networkSpeeds[direction][iface]);
this._pushTimePoint('__network-' + direction + '_max__', sumNum, 'speed');
let sum = this._legible(sumNum, 'speed');
output.push(['Device ' + direction, sum, 'network-' + direction, '__network-' + direction + '_max__']);
// append download speed to group itself