Difference between revisions of "XFCE simple Network Monitor applet"

From ArchWiki
Jump to: navigation, search
m
(One intermediate revision by one other user not shown)
Line 5: Line 5:
 
== Prerequisites ==
 
== Prerequisites ==
  
It runs within the [[http://www.archlinux.org/packages/?q=xfce4-genmon-plugin xfce4-genmon-plugin]]. This implies that it is re-run continuously (typically each second) but this does not causes a performance penalty since it is not an script calling several programs, but a single native C++ application, and the binary will be cached by the system. Since it is executed periodically, it needs to save the state information. By default does it in [[/dev/shm]] (shared memory) to avoid continuous writes on disk. Make sure that your ''/etc/fstab'' file has the following line:
+
It runs within the {{Pkg|xfce4-genmon-plugin}}. This implies that it is re-run continuously (typically each second) but this does not causes a performance penalty since it is not an script calling several programs, but a single native C++ application, and the binary will be cached by the system. Since it is executed periodically, it needs to save the state information. By default does it in [[/dev/shm]] (shared memory) to avoid continuous writes on disk. Make sure that your ''/etc/fstab'' file has the following line:
  
 
  shm  /dev/shm  tmpfs  nodev,nosuid  0  0
 
  shm  /dev/shm  tmpfs  nodev,nosuid  0  0
Line 277: Line 277:
 
  /usr/local/bin/netmon wlan0 CPU
 
  /usr/local/bin/netmon wlan0 CPU
  
In the '''Period(s)''' field select the refresh rate (1 second recommended). Uncheck the '''Label''' field. An excellent text font is Terminus ([[http://www.archlinux.org/packages/?q=terminus-font terminus-font]] package required) but any monospace, fixed or courier font will be ok.
+
In the '''Period(s)''' field select the refresh rate (1 second recommended). Uncheck the '''Label''' field. An excellent text font is Terminus ({{Pkg|terminus-font}} package required) but any monospace, fixed or courier font will be ok.
  
 
[http://img812.imageshack.us/img812/8564/netmonsetup.png This picture] resumes the settings. In case of several network interfaces, you can add as many instances as you want.
 
[http://img812.imageshack.us/img812/8564/netmonsetup.png This picture] resumes the settings. In case of several network interfaces, you can add as many instances as you want.

Revision as of 09:42, 25 June 2013

Introduction

This little "applet" will add a plain text network monitor for XFCE, without requiring gnome applets support. Unlike the native Xfce netload plugin, this one uses precise plain text figures instead of graphical bars, and also (optionally) reports the CPU usage in percentaje (all system cores). In addition, when placing the mouse over it, it displays a tooltip with extended information. This picture shows how it looks like. The speed units are automatically selected between kbps and Mbps, formatted in a way that allows to ignore the unit type, difficult to track when the speed changes too quickly (when changing from 999 kbps to 1 Mbps or vice-versa, one becomes immediately aware without needing to look at the kbps/Mbps text).

Prerequisites

It runs within the xfce4-genmon-plugin. This implies that it is re-run continuously (typically each second) but this does not causes a performance penalty since it is not an script calling several programs, but a single native C++ application, and the binary will be cached by the system. Since it is executed periodically, it needs to save the state information. By default does it in /dev/shm (shared memory) to avoid continuous writes on disk. Make sure that your /etc/fstab file has the following line:

shm   /dev/shm   tmpfs   nodev,nosuid   0   0

Installation

You need to compile the C++ application: save the following file and run the command g++ -O3 -lrt netmon.cpp -o netmon to generate the netmon binary, and place it in a location of your choice (for example, /usr/local/bin).

netmon.cpp
/*
 * Copyright (C) 2010 Ciriaco Garcia de Celis
 *
 * This program is  free software:  you can redistribute it and/or
 * modify it under the terms of the  GNU General Public License as
 * published by the Free Software Foundation,  either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY;  without even the implied warranty of
 * MERCHANTABILITY  or  FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License (GPL) for more details.
 *
 * You should have received a copy of the GNU  GPL along with this
 * program. If not, see <http://www.gnu.org/licenses/>.
 */

// compile with "g++ -O3 -lrt netmon.cpp -o netmon"

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <climits>

#define STATE_FILE_BASE "/dev/shm/netmon"

int exitapp(int exitcode)
{
    char errmsg[64];
    sprintf(errmsg, "ERROR code %d", exitcode);
    write(1, errmsg, strlen(errmsg));
    return exitcode;
}

int main(int argc, char** argv)
{
    if (argc < 2)
    {
         printf("usage: %s <network_interface> [CPU]\n", argv[0]);
         return 1;
    }

    bool reportCPU = (argc > 2) && (strcmp(argv[2], "CPU") == 0);

    char buffer[4096], cad[256], *ni, *nf;
    
    // read network information
    int fd = open("/proc/net/dev", O_RDONLY);
    if (fd < 0) return exitapp(2);
    int bytes = read(fd, buffer, sizeof(buffer)-1);
    close(fd);
    if (bytes < 0) return exitapp(3);
    buffer[bytes] = 0;

    timespec tp;
    clock_gettime(CLOCK_MONOTONIC, &tp);
    long long nanoseconds = tp.tv_sec * 1000000000LL + tp.tv_nsec;

    long long recv_bytes=LLONG_MAX, sent_bytes=LLONG_MAX;
    bool networkAvailable = false;

    // search for the proper network interface
    strcpy(cad, argv[1]);
    strcat(cad, ":");
    char *pif = strstr(buffer, cad);
    if (pif != NULL)
    {
        networkAvailable = true;

        // jump to the received bytes field
        ni = pif + strlen(cad);
        while (*ni && ((*ni == ' ') || (*ni == '\t'))) ni++;
        for (nf = ni; *nf && (*nf != ' ') && (*nf != '\t'); nf++);
        *nf++ = 0;

        // get the received bytes
        recv_bytes = atoll(ni);

        // jump to the sent bytes field
        for (int skip = 0; skip < 8; skip++)
        {
            ni = nf;
            while (*ni && ((*ni == ' ') || (*ni == '\t'))) ni++;
            for (nf = ni; *nf && (*nf != ' ') && (*nf != '\t'); nf++);
            if (!*nf) break;
            *nf++ = 0;
        }

        // get the sent bytes
        sent_bytes = atoll(ni);
    }

    long long user_mode_time=0, user_mode_nice_time=0, system_mode_time=0, idle_time=0;

    if (reportCPU)
    {
        // read CPU information
        fd = open("/proc/stat", O_RDONLY);
        if (fd < 0) return exitapp(4);
        bytes = read(fd, buffer, sizeof(buffer)-1);
        close(fd);
        if (bytes < 0) return exitapp(5);
        buffer[bytes] = 0;

        pif = strstr(buffer, "cpu ");
        if (pif != NULL)
        {
            ni = pif + 3;
            while (*ni && ((*ni == ' ') || (*ni == '\t'))) ni++;
            for (nf = ni; *nf && (*nf != ' ') && (*nf != '\t'); nf++);
            *nf++ = 0;

            // get the user mode time
            user_mode_time = atoll(ni);

            ni = nf;
            while (*ni && ((*ni == ' ') || (*ni == '\t'))) ni++;
            for (nf = ni; *nf && (*nf != ' ') && (*nf != '\t'); nf++);
            *nf++ = 0;

            // get the user mode nice time
            user_mode_nice_time = atoll(ni);

            ni = nf;
            while (*ni && ((*ni == ' ') || (*ni == '\t'))) ni++;
            for (nf = ni; *nf && (*nf != ' ') && (*nf != '\t'); nf++);
            *nf++ = 0;

            // get the system mode time
            system_mode_time = atoll(ni);

            ni = nf;
            while (*ni && ((*ni == ' ') || (*ni == '\t'))) ni++;
            for (nf = ni; *nf && (*nf != ' ') && (*nf != '\t'); nf++);
            *nf++ = 0;

            // get the idle time
            idle_time = atoll(ni);
        }
    }

    // read the received/sent bytes, date and CPU usage stored by a previous execution
    sprintf(cad, "%s.%s.%d", STATE_FILE_BASE, argv[1], getuid());
    fd = open(cad, O_RDWR | O_CREAT, 0664);
    if (fd < 0) return exitapp(6);
    bytes = read(fd, buffer, sizeof(buffer)-1);
    if (bytes < 0)
    {
        close(fd);
        return exitapp(7);
    }
    long long prev_recv_bytes, prev_sent_bytes, prev_nanoseconds = -1;
    long long prev_user_mode_time, prev_user_mode_nice_time, prev_system_mode_time, prev_idle_time = -1;
    if (bytes > 0)
    {
        prev_recv_bytes = atoll(buffer);
        prev_sent_bytes = atoll(buffer+20);
        prev_nanoseconds = atoll(buffer+40);
        prev_user_mode_time = atoll(buffer+60);
        prev_user_mode_nice_time = atoll(buffer+80);
        prev_system_mode_time = atoll(buffer+100);
        prev_idle_time = atoll(buffer+120);
    }

    // store in the file the current values for later use
    sprintf(buffer, "%019lld\n%019lld\n%019lld\n%019lld\n%019lld\n%019lld\n%019lld\n", 
        recv_bytes, sent_bytes, nanoseconds,
        user_mode_time, user_mode_nice_time, system_mode_time, idle_time);
    lseek(fd, 0, SEEK_SET);
    write(fd, buffer, 140);
    close(fd);

    // generate the result

    strcpy(buffer, "<txt>");

    bool hasNet = networkAvailable && (prev_nanoseconds >= 0) && (recv_bytes >= prev_recv_bytes) && (sent_bytes >= prev_sent_bytes);

    if (!networkAvailable)
    {
        sprintf(cad, "  %s is down", argv[1]);
        strcat(buffer, cad);
    }
    else if (!hasNet)
    {
        strcat(buffer, "     ? kbps IN \n     ? kbps OUT");
    }
    else
    {
        long long elapsed = nanoseconds - prev_nanoseconds;
        if (elapsed < 1) elapsed = 1;
        double seconds = elapsed / 1000000000.0;
        long long sent = sent_bytes - prev_sent_bytes;
        long long received = recv_bytes - prev_recv_bytes;
        long inbps = (long) (8 * received / seconds + 999); // adding 999 ensures "1" for any rate above 0
        long outbps = (long) (8 * sent / seconds + 999);
        if (inbps < 1000000)
            sprintf(cad, "%6d kbps IN \n", inbps/1000);
        else
            sprintf(cad, "%6.3f Mbps IN \n", inbps/1000000.0);
        strcat(buffer, cad);

        if (outbps < 1000000)
            sprintf(cad, "%6d kbps OUT", outbps/1000);
        else
            sprintf(cad, "%6.3f Mbps OUT", outbps/1000000.0);
        strcat(buffer, cad);
        
    }

    long long cpu_used = user_mode_time + user_mode_nice_time + system_mode_time
                       - (prev_user_mode_time + prev_user_mode_nice_time + prev_system_mode_time);
    long long total_cpu = cpu_used + (idle_time - prev_idle_time);
    bool hasCPU = (prev_idle_time >= 0) && (total_cpu > 0);
    if (reportCPU)
    {
        if (!hasCPU)
        {
            strcat(buffer, "\n     ?  % CPU");
        }
        else
        {
            sprintf(cad, "\n   %5.1f%% CPU", cpu_used * 100.0 / total_cpu);
            strcat(buffer, cad);
        }
    }

    strcat(buffer, "</txt><tool>");

    if (networkAvailable && hasNet)
    {
        sprintf(cad, " %s:\n    %.2f MB received \n    %.2f MB sent ",
                    argv[1], recv_bytes/1000000.0, sent_bytes/1000000.0);
        strcat(buffer, cad);
    }

    if (reportCPU && hasCPU)
    {
        if (networkAvailable && hasNet) strcat(buffer, "\n");
        long long total_used_cpu = user_mode_time + user_mode_nice_time + system_mode_time;
        sprintf(cad, " CPU usage:\n    %5.1f%% since boot ",
                    total_used_cpu * 100.0 / (total_used_cpu + idle_time));
        strcat(buffer, cad);
    }

    strcat(buffer, "</tool>");

    write(1, buffer, strlen(buffer));

    return 0;
}

Configuration

Insert in your panel a Generic Monitor applet. In the Command field place a invocation of the tool, selecting the network interface (eth0, ppp0, wlan0, ...) and optionally add the CPU parameter to select reporting the CPU usage (the applet will print a third line). For example:

/usr/local/bin/netmon wlan0 CPU

In the Period(s) field select the refresh rate (1 second recommended). Uncheck the Label field. An excellent text font is Terminus (terminus-font package required) but any monospace, fixed or courier font will be ok.

This picture resumes the settings. In case of several network interfaces, you can add as many instances as you want.