[gnome] Update extensions
This commit is contained in:
@ -17,6 +17,7 @@ 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;
|
||||
|
||||
@ -51,23 +52,30 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
this._warnings = [];
|
||||
this._sensorMenuItems = {};
|
||||
this._hotLabels = {};
|
||||
this._hotIcons = {};
|
||||
this._hotItems = {};
|
||||
this._groups = {};
|
||||
this._widths = {};
|
||||
this._numGpus = 1;
|
||||
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,
|
||||
x_align: Clutter.ActorAlign.START,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
reactive: true,
|
||||
x_expand: true
|
||||
x_expand: true,
|
||||
style_class: 'vitals-panel-menu'
|
||||
});
|
||||
|
||||
this._drawMenu();
|
||||
@ -75,12 +83,12 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
this._settingChangedSignals = [];
|
||||
this._refreshTimeoutId = null;
|
||||
|
||||
this._addSettingChangedSignal('update-time', this._updateTimeChanged.bind(this));
|
||||
this._addSettingChangedSignal('update-time', this._updateTimeSettingChanged.bind(this));
|
||||
this._addSettingChangedSignal('position-in-panel', this._positionInPanelChanged.bind(this));
|
||||
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' ];
|
||||
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' ];
|
||||
for (let setting of Object.values(settings))
|
||||
this._addSettingChangedSignal(setting, this._redrawMenu.bind(this));
|
||||
|
||||
@ -89,6 +97,7 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
this._addSettingChangedSignal('show-' + sensor, this._showHideSensorsChanged.bind(this));
|
||||
|
||||
this._initializeMenu();
|
||||
this._createHistoryPopout();
|
||||
|
||||
// start off with fresh sensors
|
||||
this._querySensors();
|
||||
@ -175,10 +184,216 @@ 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));
|
||||
@ -219,8 +434,7 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
// removes sensors that are no longer available
|
||||
if (!this._sensorMenuItems[sensor]) {
|
||||
hotSensors.splice(i, 1);
|
||||
this._removeHotLabel(sensor);
|
||||
this._removeHotIcon(sensor);
|
||||
this._removeHotItem(sensor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,19 +459,24 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
update_time,
|
||||
(self) => {
|
||||
// only update menu if we have hot sensors
|
||||
if (Object.values(this._hotLabels).length > 0)
|
||||
this._querySensors();
|
||||
// keep the timer running
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
// always query sensors (for panel display when hot, and for history graph data)
|
||||
this._querySensors();
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_createHotItem(key, value) {
|
||||
let icon = this._defaultIcon(key);
|
||||
this._hotIcons[key] = icon;
|
||||
this._menuLayout.add_child(icon)
|
||||
let item = new St.BoxLayout({
|
||||
style_class: 'vitals-panel-item',
|
||||
});
|
||||
this._hotItems[key] = item;
|
||||
this._menuLayout.add_child(item);
|
||||
|
||||
if (!this._settings.get_boolean('hide-icons') || key == '_default_icon_') {
|
||||
let icon = this._defaultIcon(key);
|
||||
item.add_child(icon);
|
||||
}
|
||||
|
||||
// don't add a label when no sensors are in the panel
|
||||
if (key == '_default_icon_') return;
|
||||
@ -268,18 +487,15 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER
|
||||
});
|
||||
|
||||
// attempt to prevent ellipsizes
|
||||
label.get_clutter_text().ellipsize = 0;
|
||||
|
||||
// keep track of label for removal later
|
||||
this._hotLabels[key] = label;
|
||||
|
||||
// prevent "called on the widget" "which is not in the stage" errors by adding before width below
|
||||
this._menuLayout.add_child(label);
|
||||
item.add_child(label);
|
||||
|
||||
// support for fixed widths #55, save label (text) width
|
||||
this._widths[key] = label.width;
|
||||
this._widths[key] = label.get_clutter_text().width;
|
||||
}
|
||||
|
||||
_showHideSensorsChanged(self, sensor) {
|
||||
@ -329,35 +545,24 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
this._redrawMenu();
|
||||
}
|
||||
|
||||
_removeHotLabel(key) {
|
||||
if (key in this._hotLabels) {
|
||||
let label = this._hotLabels[key];
|
||||
_removeHotItems(){
|
||||
for (let key in this._hotItems) {
|
||||
this._removeHotItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
_removeHotItem(key) {
|
||||
if (key in this._hotItems) {
|
||||
this._hotItems[key].destroy();
|
||||
delete this._hotItems[key];
|
||||
delete this._hotLabels[key];
|
||||
// make sure set_label is not called on non existent actor
|
||||
label.destroy();
|
||||
delete this._widths[key];
|
||||
}
|
||||
}
|
||||
|
||||
_removeHotLabels() {
|
||||
for (let key in this._hotLabels)
|
||||
this._removeHotLabel(key);
|
||||
}
|
||||
|
||||
_removeHotIcon(key) {
|
||||
if (key in this._hotIcons) {
|
||||
this._hotIcons[key].destroy();
|
||||
delete this._hotIcons[key];
|
||||
}
|
||||
}
|
||||
|
||||
_removeHotIcons() {
|
||||
for (let key in this._hotIcons)
|
||||
this._removeHotIcon(key);
|
||||
}
|
||||
|
||||
_redrawMenu() {
|
||||
this._removeHotIcons();
|
||||
this._removeHotLabels();
|
||||
this._hideHistoryPopout();
|
||||
this._removeHotItems();
|
||||
|
||||
for (let key in this._sensorMenuItems) {
|
||||
if (key.includes('-group')) continue;
|
||||
@ -391,6 +596,12 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
}
|
||||
}
|
||||
|
||||
_updateTimeSettingChanged() {
|
||||
this._destroyTimer();
|
||||
this._values.clearTimeSeries(this._historyCachePath);
|
||||
this._initializeTimer();
|
||||
}
|
||||
|
||||
_updateTimeChanged() {
|
||||
this._destroyTimer();
|
||||
this._initializeTimer();
|
||||
@ -452,8 +663,7 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
} else {
|
||||
// remove selected sensor from panel
|
||||
hotSensors.splice(hotSensors.indexOf(self.key), 1);
|
||||
this._removeHotLabel(self.key);
|
||||
this._removeHotIcon(self.key);
|
||||
this._removeHotItem(self.key);
|
||||
}
|
||||
|
||||
if (hotSensors.length <= 0) {
|
||||
@ -465,7 +675,7 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
if (defIconPos >= 0) {
|
||||
// remove generic icon from panel when sensors are selected
|
||||
hotSensors.splice(defIconPos, 1);
|
||||
this._removeHotIcon('_default_icon_');
|
||||
this._removeHotItem('_default_icon_');
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,6 +696,25 @@ 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() {
|
||||
@ -508,7 +737,7 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
// don't use the default system icon if the type is a gpu; use the universal gpu icon instead
|
||||
if (type == 'default' || (!(type in this._sensorIcons) && !type.startsWith('gpu'))) {
|
||||
icon.gicon = Gio.icon_new_for_string(this._sensorIconPath('system'));
|
||||
} else if (!this._settings.get_boolean('hide-icons')) { // support for hide icons #80
|
||||
} else { // support for hide icons #80
|
||||
let iconObj = (split.length == 2)?'icon-' + split[1]:'icon';
|
||||
icon.gicon = Gio.icon_new_for_string(this._sensorIconPath(type, iconObj));
|
||||
}
|
||||
@ -627,6 +856,8 @@ var VitalsMenuButton = GObject.registerClass({
|
||||
this._notify('Vitals', this._warnings.join("\n"), 'folder-symbolic');
|
||||
this._warnings = [];
|
||||
}
|
||||
|
||||
this._refreshHistoryPopout();
|
||||
}
|
||||
|
||||
_notify(msg, details, icon) {
|
||||
@ -638,7 +869,13 @@ 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))
|
||||
|
||||
@ -0,0 +1,157 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
});
|
||||
Binary file not shown.
@ -12,9 +12,10 @@
|
||||
"46",
|
||||
"47",
|
||||
"48",
|
||||
"49"
|
||||
"49",
|
||||
"50"
|
||||
],
|
||||
"url": "https://github.com/corecoding/Vitals",
|
||||
"uuid": "Vitals@CoreCoding.com",
|
||||
"version": 73
|
||||
"version": 74
|
||||
}
|
||||
@ -49,7 +49,8 @@ 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-gpu', 'include-static-gpu-info',
|
||||
'show-sensor-history-graph' ];
|
||||
|
||||
for (let key in sensors) {
|
||||
let sensor = sensors[key];
|
||||
|
||||
@ -415,6 +415,35 @@
|
||||
</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">
|
||||
|
||||
Binary file not shown.
@ -151,5 +151,15 @@
|
||||
<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>
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import GObject from 'gi://GObject';
|
||||
import * as SubProcessModule from './helpers/subprocess.js';
|
||||
import * as FileModule from './helpers/file.js';
|
||||
@ -60,6 +61,12 @@ export const Sensors = GObject.registerClass({
|
||||
this._nvidia_labels = [];
|
||||
this._bad_split_count = 0;
|
||||
|
||||
this._frameMonitorSignalId = 0;
|
||||
this._frameMonitorLastTime = 0;
|
||||
this._frameMonitorFrameCount = 0;
|
||||
this._frameMonitorAccTime = 0;
|
||||
this._frameMonitorCurrentHz = 0;
|
||||
|
||||
if (hasGTop) {
|
||||
this.storage = new GTop.glibtop_fsusage();
|
||||
this._storageDevice = '';
|
||||
@ -370,11 +377,19 @@ export const Sensors = GObject.registerClass({
|
||||
let free = this.storage.bfree * this.storage.block_size;
|
||||
let used = total - free;
|
||||
let reserved = (total - avail) - used;
|
||||
let freePercent = 0;
|
||||
let usedPercent = 0;
|
||||
if (total > 0) {
|
||||
freePercent = Math.round((free / total) * 100);
|
||||
usedPercent = Math.round((used / total) * 100);
|
||||
}
|
||||
|
||||
this._returnValue(callback, 'Total', total, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Used', used, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Reserved', reserved, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Free', avail, 'storage', 'storage');
|
||||
this._returnValue(callback, 'Used %', usedPercent + '%', 'storage', 'string');
|
||||
this._returnValue(callback, 'Free %', freePercent + '%', 'storage', 'string');
|
||||
this._returnValue(callback, 'storage', avail, 'storage-group', 'storage');
|
||||
}
|
||||
|
||||
@ -427,8 +442,11 @@ export const Sensors = GObject.registerClass({
|
||||
}
|
||||
|
||||
if ('POWER_NOW' in output) {
|
||||
this._returnValue(callback, 'Rate', output['POWER_NOW'], 'battery', 'watt');
|
||||
this._returnValue(callback, 'battery', output['POWER_NOW'], 'battery-group', 'watt');
|
||||
const powerValue = (
|
||||
parseFloat(output['POWER_NOW']) * (output['STATUS'] === 'Discharging' ? -1 : 1)
|
||||
);
|
||||
this._returnValue(callback, 'Power Rate', powerValue, 'battery', 'watt');
|
||||
this._returnValue(callback, 'battery', powerValue, 'battery-group', 'watt');
|
||||
}
|
||||
|
||||
if ('CHARGE_FULL' in output && 'VOLTAGE_MIN_DESIGN' in output && (!('ENERGY_FULL' in output))) {
|
||||
@ -500,10 +518,56 @@ export const Sensors = GObject.registerClass({
|
||||
}).catch(err => { });
|
||||
}
|
||||
|
||||
_initFrameMonitor() {
|
||||
if (this._frameMonitorSignalId) return;
|
||||
this._frameMonitorLastTime = 0;
|
||||
this._frameMonitorFrameCount = 0;
|
||||
this._frameMonitorAccTime = 0;
|
||||
this._frameMonitorCurrentHz = 0;
|
||||
this._frameMonitorSignalId = global.stage.connect('after-paint', () => {
|
||||
this._onAfterPaint();
|
||||
});
|
||||
}
|
||||
|
||||
_destroyFrameMonitor() {
|
||||
if (this._frameMonitorSignalId) {
|
||||
global.stage.disconnect(this._frameMonitorSignalId);
|
||||
this._frameMonitorSignalId = 0;
|
||||
}
|
||||
this._frameMonitorLastTime = 0;
|
||||
this._frameMonitorCurrentHz = 0;
|
||||
}
|
||||
|
||||
_onAfterPaint() {
|
||||
const now = GLib.get_monotonic_time();
|
||||
|
||||
if (this._frameMonitorLastTime === 0) {
|
||||
this._frameMonitorLastTime = now;
|
||||
return;
|
||||
}
|
||||
|
||||
const delta = now - this._frameMonitorLastTime;
|
||||
this._frameMonitorLastTime = now;
|
||||
|
||||
this._frameMonitorFrameCount++;
|
||||
this._frameMonitorAccTime += delta;
|
||||
|
||||
if (this._frameMonitorAccTime >= 500000) {
|
||||
this._frameMonitorCurrentHz = this._frameMonitorFrameCount / (this._frameMonitorAccTime / 1000000);
|
||||
this._frameMonitorFrameCount = 0;
|
||||
this._frameMonitorAccTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_queryGpu(callback) {
|
||||
if (this._frameMonitorCurrentHz > 0)
|
||||
this._returnValue(callback, 'Refresh Rate', this._frameMonitorCurrentHz, 'gpu#1', 'hertz');
|
||||
|
||||
if (!this._nvidia_smi_process) {
|
||||
// no nvidia-smi, so we use sysfs DRM if any cards was discovered
|
||||
if (!this._gpu_drm_indices){
|
||||
if (this._frameMonitorCurrentHz > 0)
|
||||
this._returnValue(callback, 'Refresh Rate', this._frameMonitorCurrentHz, 'gpu#1-group', 'hertz');
|
||||
this._disableGpuLabels(callback);
|
||||
return;
|
||||
} else {
|
||||
@ -801,6 +865,7 @@ export const Sensors = GObject.registerClass({
|
||||
// Launch nvidia-smi subprocess if nvidia querying is enabled
|
||||
this._reconfigureNvidiaSmiProcess();
|
||||
this._discoverGpuDrm();
|
||||
this._initFrameMonitor();
|
||||
}
|
||||
|
||||
_discoverGpuDrm() {
|
||||
@ -992,9 +1057,13 @@ export const Sensors = GObject.registerClass({
|
||||
this._battery_charge_status = '';
|
||||
this._nvidia_labels = [];
|
||||
this._bad_split_count = 0;
|
||||
this._frameMonitorLastTime = 0;
|
||||
this._frameMonitorFrameCount = 0;
|
||||
this._frameMonitorAccTime = 0;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._destroyFrameMonitor();
|
||||
this._terminateNvidiaSmiProcess();
|
||||
|
||||
for (let signal of Object.values(this._settingChangedSignals))
|
||||
|
||||
@ -1,16 +1,80 @@
|
||||
.vitals-icon { icon-size: 16px; }
|
||||
.vitals-menu-button-container {}
|
||||
.vitals-panel-icon-temperature { margin: 0 1px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-voltage { margin: 0 0 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-fan { margin: 0 4px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-memory { margin: 0 2px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-processor { margin: 0 3px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-system { margin: 0 3px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-network { margin: 0 3px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-storage { margin: 0 2px 0 8px; padding: 0; }
|
||||
.vitals-panel-icon-battery { margin: 0 4px 0 8px; padding: 0; }
|
||||
.vitals-panel-label { margin: 0 3px 0 0; padding: 0; }
|
||||
.vitals-panel-item{spacing: 0;}
|
||||
.vitals-panel-menu{spacing: 11px; padding: 3px; }
|
||||
.vitals-panel-icon-default {}
|
||||
.vitals-panel-icon-temperature { margin: 0 1px 0 0; padding: 0; }
|
||||
.vitals-panel-icon-voltage { margin: 0 0 0 0; padding: 0; }
|
||||
.vitals-panel-icon-fan { margin: 0 4px 0 0; padding: 0; }
|
||||
.vitals-panel-icon-memory { margin: 0 2px 0 0; padding: 0; }
|
||||
.vitals-panel-icon-processor { margin: 0 3px 0 0; padding: 0; }
|
||||
.vitals-panel-icon-system { margin: 0 3px 0 0; padding: 0; }
|
||||
.vitals-panel-icon-network { margin: 0 3px 0 0; padding: 0; }
|
||||
.vitals-panel-icon-storage { margin: 0 2px 0 0; padding: 0; }
|
||||
.vitals-panel-icon-battery { margin: 0 4px 0 0; padding: 0; }
|
||||
.vitals-panel-label {}
|
||||
.vitals-button-action { -st-icon-style: symbolic; border-radius: 32px; margin: 0px; min-height: 22px; min-width: 22px; padding: 10px; font-size: 100%; border: 1px solid transparent; }
|
||||
.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);
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import Gio from 'gi://Gio';
|
||||
import GObject from 'gi://GObject';
|
||||
|
||||
const cbFun = (d, c) => {
|
||||
@ -47,9 +49,138 @@ 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';
|
||||
@ -186,7 +317,9 @@ export const Values = GObject.registerClass({
|
||||
ending = 'mAh';
|
||||
break;
|
||||
case 'watt':
|
||||
format = (use_higher_precision)?'%.2f %s':'%.1f %s';
|
||||
format = (
|
||||
((value > 0) ? '+' : '') + ((use_higher_precision)?'%.2f %s':'%.1f %s')
|
||||
);
|
||||
value = value / 1000000;
|
||||
ending = 'W';
|
||||
break;
|
||||
@ -212,7 +345,7 @@ export const Values = GObject.registerClass({
|
||||
break;
|
||||
}
|
||||
|
||||
return format.format(value, ending);
|
||||
return format.format(value, ending).trim();
|
||||
}
|
||||
|
||||
returnIfDifferent(dwell, label, value, type, format, key) {
|
||||
@ -242,6 +375,8 @@ 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') {
|
||||
@ -272,6 +407,8 @@ export const Values = GObject.registerClass({
|
||||
// appends total upload and download for all interfaces for #216
|
||||
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
|
||||
@ -279,11 +416,14 @@ 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] = {};
|
||||
@ -295,11 +435,12 @@ export const Values = GObject.registerClass({
|
||||
|
||||
// calculate total upload and download device speed
|
||||
for (let direction in this._networkSpeeds) {
|
||||
let sum = 0;
|
||||
let sumNum = 0;
|
||||
for (let iface in this._networkSpeeds[direction])
|
||||
sum += parseFloat(this._networkSpeeds[direction][iface]);
|
||||
sumNum += parseFloat(this._networkSpeeds[direction][iface]);
|
||||
|
||||
sum = this._legible(sum, 'speed');
|
||||
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
|
||||
if (direction == 'rx') output.push([type, sum, type + '-group', '']);
|
||||
|
||||
Reference in New Issue
Block a user