Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support network monitoring. #1

Open
fxzxmicah opened this issue Jun 1, 2024 · 2 comments
Open

Support network monitoring. #1

fxzxmicah opened this issue Jun 1, 2024 · 2 comments

Comments

@fxzxmicah
Copy link

Support network monitoring.

@michaelknap
Copy link
Owner

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.

@fxzxmicah
Copy link
Author

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants