diff --git a/freon@UshakovVasilii_Github.yahoo.com/cpuUsageUtil.js b/freon@UshakovVasilii_Github.yahoo.com/cpuUsageUtil.js
new file mode 100644
index 0000000..69ef0ec
--- /dev/null
+++ b/freon@UshakovVasilii_Github.yahoo.com/cpuUsageUtil.js
@@ -0,0 +1,134 @@
+import GLib from 'gi://GLib';
+
+export default class CpuUsageUtil {
+
+ constructor(intervalSeconds = 2) {
+ this._updated = false;
+ this._readings = [];
+ this._prevSnapshot = null;
+ this._intervalUsec = 0;
+ this._lastUpdateUsec = 0;
+ this.interval = intervalSeconds;
+
+ // Take initial snapshot so the first real execute() can compute a delta
+ this._prevSnapshot = this._readProcStat();
+ }
+
+ get available() {
+ return this._prevSnapshot !== null;
+ }
+
+ get updated() {
+ return this._updated;
+ }
+
+ set updated(updated) {
+ this._updated = updated;
+ }
+
+ execute(callback) {
+ const nowUsec = GLib.get_monotonic_time();
+ const remaining = this._intervalUsec - (nowUsec - this._lastUpdateUsec);
+ if (this._intervalUsec > 0 && this._lastUpdateUsec !== 0 && remaining > 0) {
+ if (callback)
+ callback();
+ return;
+ }
+
+ this._updated = false;
+
+ try {
+ const snapshot = this._readProcStat();
+ if (!snapshot || !this._prevSnapshot) {
+ this._prevSnapshot = snapshot;
+ return;
+ }
+
+ this._readings = [];
+
+ for (const [cpu, fields] of snapshot) {
+ const prev = this._prevSnapshot.get(cpu);
+ if (!prev)
+ continue;
+
+ const idle = (fields.idle + fields.iowait) - (prev.idle + prev.iowait);
+ const total = fields.total - prev.total;
+
+ if (total <= 0)
+ continue;
+
+ const usage = 100.0 * (1.0 - idle / total);
+
+ this._readings.push({
+ label: cpu === 'cpu' ? 'CPU Total' : cpu.toUpperCase().replace('CPU', 'CPU '),
+ usage: Math.max(0, Math.min(100, usage)),
+ });
+ }
+
+ this._prevSnapshot = snapshot;
+ } catch (e) {
+ logError(e, '[FREON] Failed to read CPU usage');
+ this._readings = [];
+ } finally {
+ this._lastUpdateUsec = nowUsec;
+ this._updated = true;
+ if (callback)
+ callback();
+ }
+ }
+
+ get usage() {
+ return this._readings;
+ }
+
+ destroy() {
+ this._readings = [];
+ this._prevSnapshot = null;
+ this._lastUpdateUsec = 0;
+ }
+
+ set interval(seconds) {
+ const clamped = Math.max(1, seconds | 0);
+ this._intervalUsec = clamped * 1000000;
+ this._lastUpdateUsec = 0;
+ }
+
+ _readProcStat() {
+ let ok, contents;
+ try {
+ [ok, contents] = GLib.file_get_contents('/proc/stat');
+ } catch (e) {
+ return null;
+ }
+
+ if (!ok)
+ return null;
+
+ const text = new TextDecoder().decode(contents);
+ const result = new Map();
+
+ for (const line of text.split('\n')) {
+ if (!line.startsWith('cpu'))
+ continue;
+
+ const parts = line.trim().split(/\s+/);
+ const name = parts[0];
+
+ // user nice system idle iowait irq softirq steal
+ const user = parseInt(parts[1]) || 0;
+ const nice = parseInt(parts[2]) || 0;
+ const system = parseInt(parts[3]) || 0;
+ const idle = parseInt(parts[4]) || 0;
+ const iowait = parseInt(parts[5]) || 0;
+ const irq = parseInt(parts[6]) || 0;
+ const softirq = parseInt(parts[7]) || 0;
+ const steal = parseInt(parts[8]) || 0;
+
+ const total = user + nice + system + idle + iowait + irq + softirq + steal;
+
+ result.set(name, { idle, iowait, total });
+ }
+
+ return result.size > 0 ? result : null;
+ }
+}
diff --git a/freon@UshakovVasilii_Github.yahoo.com/extension.js b/freon@UshakovVasilii_Github.yahoo.com/extension.js
index 31bbbf1..eced464 100644
--- a/freon@UshakovVasilii_Github.yahoo.com/extension.js
+++ b/freon@UshakovVasilii_Github.yahoo.com/extension.js
@@ -25,6 +25,7 @@ import SmartctlUtil from './smartctlUtil.js';
import NvmecliUtil from './nvmecliUtil.js';
import BatteryUtil from './batteryUtil.js';
import WattdUtil from './wattdUtil.js';
+import CpuUsageUtil from './cpuUsageUtil.js';
import FreonItem from './freonItem.js';
@@ -80,6 +81,7 @@ class FreonMenuButton extends PanelMenu.Button {
this._initLiquidctlUtility();
this._initBatteryUtility();
this._initWattdUtility();
+ this._initCpuUsageUtility();
this._initNvidiaUtility();
this._initBumblebeeNvidiaUtility();
@@ -104,6 +106,7 @@ class FreonMenuButton extends PanelMenu.Button {
'fan' : Gio.icon_new_for_string(path + '/icons/freon-fan-symbolic.svg'),
'power' : voltageIcon,
'battery' : batteryIcon,
+ 'cpu-usage' : Gio.icon_new_for_string('utilities-system-monitor-symbolic'),
}
this._menuLayout = new St.BoxLayout();
@@ -145,6 +148,8 @@ class FreonMenuButton extends PanelMenu.Button {
this._addSettingChangedSignal('use-generic-lmsensors', this._sensorsUtilityChanged.bind(this));
this._addSettingChangedSignal('freeimpi-selected', this._freeipmiUtilityChanged.bind(this));
this._addSettingChangedSignal('use-generic-liquidctl', this._liquidctlUtilityChanged.bind(this));
+ this._addSettingChangedSignal('use-cpu-usage', this._cpuUsageUtilityChanged.bind(this));
+ this._addSettingChangedSignal('cpu-usage-update-time', this._cpuUsageUpdateTimeChanged.bind(this));
this._addSettingChangedSignal('use-generic-wattd', this._wattdUtilityChanged.bind(this));
this._addSettingChangedSignal('show-battery-stats', this._batteryUtilityChanged.bind(this));
@@ -161,7 +166,8 @@ class FreonMenuButton extends PanelMenu.Button {
this._addSettingChangedSignal('show-rotationrate', this._rerender.bind(this));
this._addSettingChangedSignal('show-voltage', this._rerender.bind(this));
this._addSettingChangedSignal('show-power', this._rerender.bind(this));
-
+ this._addSettingChangedSignal('show-cpu-usage', this._rerender.bind(this));
+
this._addSettingChangedSignal('group-temperature', this._rerender.bind(this))
this._addSettingChangedSignal('group-rotationrate', this._rerender.bind(this))
@@ -392,6 +398,31 @@ class FreonMenuButton extends PanelMenu.Button {
this._querySensors();
}
+ _initCpuUsageUtility() {
+ if (this._settings.get_boolean('use-cpu-usage'))
+ this._utils.cpuUsage = new CpuUsageUtil(this._settings.get_int('cpu-usage-update-time'));
+ }
+
+ _destroyCpuUsageUtility() {
+ if (this._utils.cpuUsage) {
+ this._utils.cpuUsage.destroy();
+ delete this._utils.cpuUsage;
+ }
+ }
+
+ _cpuUsageUtilityChanged() {
+ this._destroyCpuUsageUtility();
+ this._initCpuUsageUtility();
+ this._querySensors();
+ this._updateUI(true);
+ }
+
+ _cpuUsageUpdateTimeChanged() {
+ if (this._utils.cpuUsage)
+ this._utils.cpuUsage.interval = this._settings.get_int('cpu-usage-update-time');
+ this._querySensors();
+ }
+
_initNvidiaUtility() {
if (this._settings.get_boolean('use-gpu-nvidia'))
this._utils.nvidia = new NvidiaUtil();
@@ -560,6 +591,7 @@ class FreonMenuButton extends PanelMenu.Button {
this._destroyBatteryUtility();
this._destroyWattdUtility();
+ this._destroyCpuUsageUtility();
GLib.Source.remove(this._timeoutId);
GLib.Source.remove(this._updateUITimeoutId);
@@ -628,6 +660,7 @@ class FreonMenuButton extends PanelMenu.Button {
let fanInfo = [];
let voltageInfo = [];
let powerInfo = [];
+ let cpuUsageInfo = [];
if (this._utils.sensors && this._utils.sensors.available) {
if (this._settings.get_boolean('show-temperature')) {
@@ -674,6 +707,10 @@ class FreonMenuButton extends PanelMenu.Button {
if (this._settings.get_boolean('show-power'))
powerInfo = powerInfo.concat(this._utils.wattd.power);
+ if (this._utils.cpuUsage && this._utils.cpuUsage.available)
+ if (this._settings.get_boolean('show-cpu-usage'))
+ cpuUsageInfo = cpuUsageInfo.concat(this._utils.cpuUsage.usage);
+
if (this._utils.nvidia && this._utils.nvidia.available)
if (this._settings.get_boolean('show-temperature'))
gpuTempInfo = gpuTempInfo.concat(this._utils.nvidia.temp);
@@ -708,12 +745,14 @@ class FreonMenuButton extends PanelMenu.Button {
fanInfo.sort(comparator);
voltageInfo.sort(comparator);
powerInfo.sort(comparator);
+ cpuUsageInfo.sort(comparator);
let tempInfo = gpuTempInfo.concat(sensorsTempInfo).concat(driveTempInfo);
if (tempInfo.length == 0
&& fanInfo.length == 0
- && voltageInfo.length == 0) {
+ && voltageInfo.length == 0
+ && cpuUsageInfo.length == 0) {
this._sensorMenuItems = {};
this.menu.removeAll();
@@ -848,6 +887,18 @@ class FreonMenuButton extends PanelMenu.Button {
});
}
+ if (cpuUsageInfo.length > 0 && (fanInfo.length > 0 || voltageInfo.length > 0 || powerInfo.length > 0))
+ sensors.push({type : 'separator'});
+
+ for (let cpu of cpuUsageInfo){
+ sensors.push({
+ icon: 'cpu-usage',
+ type: 'cpu-usage',
+ label: cpu.label,
+ value: _("%.1f%%").format(cpu.usage)
+ });
+ }
+
this._fixNames(sensors);
for (let k in this._hotLabels)
diff --git a/freon@UshakovVasilii_Github.yahoo.com/prefs.js b/freon@UshakovVasilii_Github.yahoo.com/prefs.js
index ff34079..b38e226 100644
--- a/freon@UshakovVasilii_Github.yahoo.com/prefs.js
+++ b/freon@UshakovVasilii_Github.yahoo.com/prefs.js
@@ -110,6 +110,26 @@ export default class FreonPreferences extends ExtensionPreferences {
width_request: 320,
});
+ const cpuUsageSwitch = this._addSwitch("CPU Usage", "use-cpu-usage", "Read CPU usage from /proc/stat");
+ group.add(cpuUsageSwitch);
+
+ const cpuUsageInterval = new Adw.SpinRow({
+ title: _('CPU Usage Polling Interval'),
+ subtitle: _('Seconds between CPU usage updates'),
+ adjustment: new Gtk.Adjustment({
+ lower: 1,
+ upper: 60,
+ value: this._settings.get_int('cpu-usage-update-time'),
+ step_increment: 1,
+ }),
+ });
+ this._settings.bind('cpu-usage-update-time', cpuUsageInterval, 'value', Gio.SettingsBindFlags.DEFAULT);
+ cpuUsageInterval.sensitive = this._settings.get_boolean('use-cpu-usage');
+ this._settings.connect('changed::use-cpu-usage', () => {
+ cpuUsageInterval.sensitive = this._settings.get_boolean('use-cpu-usage');
+ });
+ group.add(cpuUsageInterval);
+
group.add(this._addSwitch("lm-sensors", "use-generic-lmsensors", "Read sensors from lm-sensors"));
group.add(this._addSwitch("liquidctl", "use-generic-liquidctl", "Read sensors from liquidctl (v1.7.0+)"));
const wattdSwitch = this._addSwitch("wattd", "use-generic-wattd", "Read power data from wattd");
@@ -180,6 +200,7 @@ export default class FreonPreferences extends ExtensionPreferences {
group.add(this._addSwitch("Voltage", "show-voltage"));
group.add(this._addSwitch("Power", "show-power"));
group.add(this._addSwitch("Battery", "show-battery-stats"));
+ group.add(this._addSwitch("CPU Usage", "show-cpu-usage"));
return group
}
diff --git a/freon@UshakovVasilii_Github.yahoo.com/schemas/org.gnome.shell.extensions.sensors.gschema.xml b/freon@UshakovVasilii_Github.yahoo.com/schemas/org.gnome.shell.extensions.sensors.gschema.xml
index d15166f..48aed8f 100644
--- a/freon@UshakovVasilii_Github.yahoo.com/schemas/org.gnome.shell.extensions.sensors.gschema.xml
+++ b/freon@UshakovVasilii_Github.yahoo.com/schemas/org.gnome.shell.extensions.sensors.gschema.xml
@@ -105,6 +105,24 @@
Read sensors from (requires liquidctl v1.7.0 or later)
+
+ true
+ Enable CPU usage sensor
+ Read CPU usage from /proc/stat
+
+
+
+ true
+ Show CPU usage
+ Display CPU usage percentage
+
+
+
+ 2
+ Seconds between CPU usage updates
+ Polling interval for CPU usage sensor
+
+
false
Read power data from wattd