Open fxzxmicah opened 1 month ago
Thank you for your suggestion!
I have considered this and think it could be a nice addition. However, to keep this extension lean, as I use it in various VMs for monitoring intensive workloads, I might develop the network stats feature in a separate repository. Managing two repositories is not ideal, but it would allow me to keep this one as basic as it is.
For my desktop use, this would indeed be a handy feature. I'd love to get your feedback on whether this should be enabled by default or via settings.
I will keep this issue open to see if there is any more interest.
I have a little programming skills, so I made some modifications and merged his network monitoring related code into it.
'use strict';
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import St from 'gi://St';
import Clutter from 'gi://Clutter';
import GObject from 'gi://GObject';
import {
Button
} from 'resource:///org/gnome/shell/ui/panelMenu.js';
import {
panel, sessionMode
} from 'resource:///org/gnome/shell/ui/main.js';
// Define the main class for the system monitor indicator
export class SystemMonitorIndicator extends Button {
// Initialize the indicator
_init() {
super._init(0, 'System Monitor Indicator', false);
// Create a layout box to contain labels
this.box = new St.BoxLayout();
// Initialize CPU usage label
this.cpu_usage_label = new St.Label({
text: 'CPU: 0%',
y_align: Clutter.ActorAlign.CENTER,
style: 'margin-left: 4px; margin-right: 4px;'
});
this.box.add_child(this.cpu_usage_label);
// Initialize Memory usage label
this.mem_usage_label = new St.Label({
text: 'Mem: 0%',
y_align: Clutter.ActorAlign.CENTER,
style: 'margin-left: 4px; margin-right: 4px;'
});
this.box.add_child(this.mem_usage_label);
// Initialize Swap usage label
this.swap_usage_label = new St.Label({
text: 'Swap: 0%',
y_align: Clutter.ActorAlign.CENTER,
style: 'margin-left: 4px; margin-right: 4px;'
});
this.box.add_child(this.swap_usage_label);
// Initialize Swap usage label
this.net_usage_label = new St.Label({
text: 'Net: ↓ ↑',
y_align: Clutter.ActorAlign.CENTER,
style: 'margin-left: 4px; margin-right: 4px;'
});
this.box.add_child(this.net_usage_label);
// Add the layout box to this actor
this.add_child(this.box);
// Initialize previous CPU values
this.prev_idle = 0;
this.prev_total = 0;
// Start updating metrics
this._update_metrics();
}
_file_open(file_path) {
try {
// Get file name and return lines of the file as array
const file = Gio.File.new_for_path(file_path);
const [, content] = file.load_contents(null);
const text_decoder = new TextDecoder("utf-8");
const content_str = text_decoder.decode(content);
return content_str.split('\n');
} catch (e) {
logError(e, `PROCESSING ERROR IN FILE: ${file_path}`);
}
}
// Function to update all metrics (CPU, Memory)
_update_metrics() {
const priority = GLib.PRIORITY_DEFAULT_IDLE;
const refresh_time = 1; // Time in seconds
// Update individual metrics
this._update_cpu_usage();
this._update_memory_usage();
this._update_net_usage();
// Remove existing timeout if any
if (this._timeout) {
GLib.source_remove(this._timeout);
}
// Set a timeout to refresh metrics
this._timeout = GLib.timeout_add_seconds(priority, refresh_time, () => {
this._update_metrics();
return true;
});
}
// Function to update CPU usage
_update_cpu_usage() {
try {
const content_lines = this._file_open('/proc/stat');
let current_cpu_used = 0;
let current_cpu_total = 0;
let current_cpu_usage = 0;
for (let i = 0; i < content_lines.length; i++) {
const fields = content_lines[i].trim().split(/\s+/);
if (fields[0] === 'cpu') {
const nums = fields.slice(1).map(Number);
const user = nums[0];
const nice = nums[1];
const system = nums[2];
const idle = nums[3];
const iowait = nums[4] || 0; // Include iowait, defaulting to 0 if not present
current_cpu_total = nums.slice(0, 4).reduce((a, b) => a + b, 0) +
iowait;
current_cpu_used = current_cpu_total - idle - iowait;
// Ensure previous values are set on the first run
this.prev_used = this.prev_used || current_cpu_used;
this.prev_total = this.prev_total || current_cpu_total;
// Calculate CPU usage as the difference from the previous measurement
const total_diff = current_cpu_total - this.prev_total;
const used_diff = current_cpu_used - this.prev_used;
if (total_diff > 0) { // Check to avoid division by zero
current_cpu_usage = (used_diff / total_diff) * 100;
this.cpu_usage_label.set_text(
`CPU: ${current_cpu_usage.toFixed(2)}%`);
}
// Store current values for the next calculation
this.prev_used = current_cpu_used;
this.prev_total = current_cpu_total;
break; // Break after processing the first 'cpu' line
}
}
} catch (e) {
logError(e, `Failed to update CPU usage.`);
}
}
// Function to update Memory usage
_update_memory_usage() {
try {
const content_lines = this._file_open('/proc/meminfo');
let mem_total = null;
let mem_available = null;
let mem_used = null;
let mem_usage = null;
let swap_total = null;
let swap_free = null;
let swap_used = null;
let swap_usage = null;
content_lines.forEach((line) => {
let [key, value] = line.split(':');
if (value) {
value = parseInt(value.trim(), 10);
}
switch (key) {
case 'MemTotal':
mem_total = value;
break;
case 'MemAvailable':
mem_available = value;
break;
case 'SwapTotal':
swap_total = value;
break;
case 'SwapFree':
swap_free = value;
break;
}
});
// Update RAM usage label
if (mem_total !== null && mem_available !== null) {
mem_used = mem_total - mem_available;
mem_usage = (mem_used / mem_total) * 100;
this.mem_usage_label.set_text(`Mem: ${mem_usage.toFixed(2)}%`);
}
// Update Swap usage label only if swap is available
if (swap_total !== null && swap_total > 0 && swap_free !== null) {
swap_used = swap_total - swap_free;
swap_usage = (swap_used / swap_total) * 100;
this.swap_usage_label.set_text(`Swap: ${swap_usage.toFixed(2)}%`);
this.swap_usage_label.show();
} else {
this.swap_usage_label.hide(); // Hide the label because there's no swap
}
} catch (e) {
logError(e, `Failed to update memory usage.`);
}
}
_update_net_usage() {
try {
// Get Active Interface name for parsing
let activeInterfaceName = '';
let [result, output,] = GLib.spawn_command_line_sync('bash -c "ip route get 1 | awk \'{print $5; exit}\'"');
if (result) {
const textDecoder = new TextDecoder("utf-8");
activeInterfaceName = textDecoder.decode(new Uint8Array(output)).trim();
}
const content_lines = this._file_open('/proc/net/dev');
const interface_name = activeInterfaceName;
let tx_bytes = 0;
let rx_bytes = 0;
for (let i = 2; i < content_lines.length; i++) {
const line = content_lines[i].trim();
if (line.startsWith(interface_name)) {
const values = line.split(/\s+/);
tx_bytes = parseInt(values[9]);
rx_bytes = parseInt(values[1]);
break;
}
}
// Calculate network traffic speed in bytes per second
const current_time = Date.now() / 1000; // Convert milliseconds to seconds
const time_difference = current_time - this.prev_time;
let tx_speed = ((tx_bytes - this.prev_tx_bytes) / time_difference);
let rx_speed = ((rx_bytes - this.prev_rx_bytes) / time_difference);
const units = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s'];
let tx_unit_index = 0;
let rx_unit_index = 0;
while (tx_speed > 1024 || rx_speed > 1024) {
if(tx_speed > 1024){
tx_speed /= 1024;
tx_unit_index++;
}
if(rx_speed > 1024){
rx_speed /= 1024;
rx_unit_index++;
}
}
if (activeInterfaceName !== '') {
// Update labels with network traffic speed
let rx_label;
let tx_label;
rx_label = rx_unit_index == 0 ? '<1KB/s' : `${rx_speed.toFixed(0)}${units[rx_unit_index]}`;
tx_label = tx_unit_index == 0 ? '<1KB/s' : `${tx_speed.toFixed(0)}${units[tx_unit_index]}`;
this.net_usage_label.set_text(`Net: ↓ ${rx_label} ↑ ${tx_label}`);
this.net_usage_label.show();
} else {
this.net_usage_label.hide(); // Hide the label because there's no net interface
}
// Store current values for the next calculation
this.prev_time = current_time;
this.prev_tx_bytes = tx_bytes;
this.prev_rx_bytes = rx_bytes;
} catch (e) {
logError(e, `Failed to update network traffic speed.`);
}
}
// Stop updates
stop() {
if (this._timeout) {
GLib.source_remove(this._timeout);
}
this._timeout = undefined;
}
}
// Register the SystemMonitorIndicator class
GObject.registerClass({
GTypeName: 'SystemMonitorIndicator'
}, SystemMonitorIndicator);
// Export the main extension class
export default class SystemMonitorExtension {
_indicator;
// Enable the extension
enable() {
this._indicator = new SystemMonitorIndicator();
let pos = sessionMode.panel.left.length;
panel.addToStatusArea('system-indicator', this._indicator, pos, 'left');
}
// Disable the extension
disable() {
this._indicator.stop();
this._indicator.destroy();
this._indicator = undefined;
}
}
Another thing I don't understand is that the official example uses GTop. Is there any consideration in not using it here?
Support network monitoring.