custom1.c

An example metric library using the Arm® MAP Metric Plugin API.

This implements the functions as defined in Metric Plugin Template and makes calls to the Metric plugin API. This plugin provides a custom metric showing the number of interrupts handled by the system, as obtained from /proc/interrupts.

Compile the plugin using this command:

gcc -fPIC -I/path/to/arm/metrics/include -shared -o libcustom1.so custom1.c

For the corresponding definition file to enable the libcustom1.so metric library to be used by compatible profilers, see custom1.xml.

/* The following functions are assumed to be async-signal-safe, although not
* required by POSIX:
*
* strchr strstr strtoull
*/

#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include "allinea_metric_plugin_api.h"

#define PROC_STAT "/proc/stat"

#define ERROR_NO_PROC_STAT                        1000

#define BUFSIZE     256
#define OVERLAP    64

#ifndef min
#define min(x, y) ( ((x) < (y)) ? (x) : (y) )
#endif

static uint64_t previous = 0;
static int have_previous = 0;

int allinea_plugin_initialise(plugin_id_t plugin_id, void *unused)
{
    // Check that /proc/interrupts exists.
    if (access(PROC_STAT, F_OK) != 0) {
        if (errno == ENOENT)
            allinea_set_plugin_error_messagef(plugin_id, ERROR_NO_PROC_STAT,
                "Not supported (no /proc/interrupts)");
        else
            allinea_set_plugin_error_messagef(plugin_id, errno,
                "Error accessing %s: %s", PROC_STAT, strerror(errno));
        return -1;
    }
}

int allinea_plugin_cleanup(plugin_id_t plugin_id, void *unused)
{
}

int sample_interrupts(metric_id_t metric_id, struct timespec *in_out_sample_time, uint64_t *out_value)
{
    // Main buffer. Add an extra byte for the '\0' we add below.
    char buf[BUFSIZE + 1];
    *in_out_sample_time = allinea_get_current_time();
    // We must use the allinea_safe variants of open / read / write / close so
    // that we are not included in the I/O accounting of the MAP sampler.
    const int fd = allinea_safe_open(PROC_STAT, O_RDONLY);
    if (fd == -1) {
        allinea_set_metric_error_messagef(metric_id, errno,
            "Error opening %s: %d", PROC_STAT, strerror(errno));
        return -1;
    }
    for (;;) {
        const ssize_t bytes_read = allinea_safe_read_line(fd, buf, BUFSIZE);
        if (bytes_read == -1) {
            // read failed
            allinea_set_metric_error_messagef(metric_id, errno,
                "Error opening %s: %d", PROC_STAT, strerror(errno));
            break;
        }
        if (bytes_read == 0) {
            // end of file
            break;
        }
        if (strncmp(buf, "intr ", 5)==0) { // Check if this is the interrupts line.
            // The format of the line is:
            // intr <total> <intr 1 count> <intr 2 count> ...
            // Check we have the total by looking for the space after it.
            const char *total = buf + /* strlen("intr ") */ 5;
            char *space = strchr(total, ' ');
            if (space) {
                uint64_t current;
                // NUL-terminate the total.
                *space = '\0';
                // total now points to the NUL-terminated total. Convert it to
                // an integer.
                current = strtoull(total, NULL, 10);
                if (have_previous)
                    *out_value = current - previous;
                previous = current;
                have_previous = 1;
                break;
            }
        }
    }
    allinea_safe_close(fd);
}