Skip to content

Commit

Permalink
Add support for parsing output from commands and using counters from …
Browse files Browse the repository at this point in the history
…there.
  • Loading branch information
gamemann committed Jun 11, 2024
1 parent ad17103 commit 2ac9b76
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 19 deletions.
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
[![Stat Build Workflow](https://github.com/gamemann/Stat/actions/workflows/build.yml/badge.svg)](https://github.com/gamemann/Stat/actions/workflows/build.yml)

## Description
A small project that allows you to gather statistics (integers/counts) from files on the file system. This was designed for Linux.
A small project that allows you to gather statistics (counters) from files on the file system or command outputs. This was designed for Linux.

This is useful for retrieving the incoming/outgoing packets per second or incoming/outgoing bytes per second on a network interface.
This is useful for retrieving the incoming/outgoing packets or bytes per second on a network interface from the file system or from the output of a command such as `ethtool`.

## Building Program
You can simply use `make` to build this program. The `Makefile` uses `clang` to compile the program.
Expand Down Expand Up @@ -33,6 +33,9 @@ gstat [-i <interface> --pps --bps --path <path> -c <"kbps" or "mbps" or "gbps">
--interval => Use this interval (in microseconds) instead of one second.
--count -n => Maximum amount of times to request the counter before stopping program (0 = no limit).
--time -t => Time limit (in seconds) before stopping program (0 = no limit).
--cmd => The command to execute and retrieve output from.
--sep => The separator to apply to the command's output.
--key => The key to search for when separating the command output.
```

**Note** - If you want to receive another counter such as outgoing (TX) packets, you can set the file to pull the count from with the `-p` (or `--path`) flag. For example:
Expand All @@ -41,5 +44,52 @@ gstat [-i <interface> --pps --bps --path <path> -c <"kbps" or "mbps" or "gbps">
gstat --path /sys/class/net/ens18/statistics/tx_packets
```

### Command Argument
You may use a combination of the `--cmd`, `--sep`, and `--key` arguments to retrieve a counter from a command's output such as `ethtool`.

For example, take a look at the following output from `ethtool`.

```bash
$ sudo ethtool -S enp1s0

NIC statistics:
rx_queue_0_packets: 901268
rx_queue_0_bytes: 211930005
rx_queue_0_drops: 0
rx_queue_0_xdp_packets: 0
rx_queue_0_xdp_tx: 0
rx_queue_0_xdp_redirects: 0
rx_queue_0_xdp_drops: 0
rx_queue_0_kicks: 14
rx_queue_1_packets: 1237084
rx_queue_1_bytes: 469671713
rx_queue_1_drops: 0
rx_queue_1_xdp_packets: 0
rx_queue_1_xdp_tx: 0
rx_queue_1_xdp_redirects: 0
rx_queue_1_xdp_drops: 0
rx_queue_1_kicks: 19
tx_queue_0_packets: 16508781
tx_queue_0_bytes: 1528281628
tx_queue_0_xdp_tx: 0
tx_queue_0_xdp_tx_drops: 0
tx_queue_0_kicks: 6598201
tx_queue_0_tx_timeouts: 0
tx_queue_1_packets: 16154602
tx_queue_1_bytes: 1348764220
tx_queue_1_xdp_tx: 0
tx_queue_1_xdp_tx_drops: 0
tx_queue_1_kicks: 6051221
tx_queue_1_tx_timeouts: 0
```

If I want the `rx_queue_0_packets` value used as the counter (starting at `901268`), you would execute the following command.

```bash
sudo gstat --cmd "ethtool -S enp1s0" --sep ":" --key "rx_queue_0_packets"
```

Each line from the command's output is trimmed of white-spaces.

## Credits
* [Christian Deacon](https://github.com/gamemann)
42 changes: 30 additions & 12 deletions src/cmdline.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@

const struct option opts[] =
{
{"dev", required_argument, NULL, 'i'},
{"path", required_argument, NULL, 'p'},
{"convert", required_argument, NULL, 'c'},
{"custom", required_argument, NULL, 1},
{"interval", required_argument, NULL, 2},
{"count", required_argument, NULL, 'n'},
{"time", required_argument, NULL, 't'},
{"pps", no_argument, NULL, 3},
{"bps", no_argument, NULL, 4},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
{ "dev", required_argument, NULL, 'i' },
{ "path", required_argument, NULL, 'p' },
{ "convert", required_argument, NULL, 'c' },
{ "custom", required_argument, NULL, 1 },
{ "interval", required_argument, NULL, 2 },
{ "count", required_argument, NULL, 'n' },
{ "time", required_argument, NULL, 't' },
{ "pps", no_argument, NULL, 3 },
{ "bps", no_argument, NULL, 4 },
{ "cmd", required_argument, NULL, 5 },
{ "sep", required_argument, NULL, 6 },
{ "key", required_argument, NULL, 7 },
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};

/**
Expand All @@ -29,7 +32,7 @@ const struct option opts[] =
*
* @return void
*/
void parsecmdline(int argc, char *argv[], struct cmdline *cmd)
void parsecmdline(int argc, char *argv[], cmdline_t *cmd)
{
int c = -1;

Expand Down Expand Up @@ -74,6 +77,21 @@ void parsecmdline(int argc, char *argv[], struct cmdline *cmd)
cmd->bps = 1;

break;

case 5:
cmd->cmd = optarg;

break;

case 6:
cmd->sep = optarg;

break;

case 7:
cmd->key = optarg;

break;

case 'n':
cmd->countmax = (uint64_t) strtoull((const char *)optarg, (char **)optarg, 0);
Expand Down
8 changes: 6 additions & 2 deletions src/cmdline.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <inttypes.h>

struct cmdline
typedef struct cmdline
{
char *interface;
char *path;
Expand All @@ -18,6 +18,10 @@ struct cmdline

unsigned int pps : 1;
unsigned int bps : 1;
};

char *cmd;
char *sep;
char *key;
} cmdline_t;

void parsecmdline(int argc, char *argv[], struct cmdline *cmd);
93 changes: 93 additions & 0 deletions src/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,97 @@ uint64_t getstat(const char *path)
fclose(fp);

return (uint64_t) strtoull((const char *)buff, (char **)buff, 0);
}

/**
* Trims a string from whitespaces.
*
* @param str A pointer to the string you'd like to trim.
*
* @return void
*/
char *trim(char* str)
{
char* end;

while (isspace((unsigned char)*str))
{
str++;
}

if (*str == 0)
{
return str;
}

end = str + strlen(str) - 1;

while (end > str && isspace((unsigned char)*end))
{
end--;
}

// Write new null terminator
*(end + 1) = '\0';

return str;
}

/**
* Executes a command and returns the result.
*
* @param cmd The command to execute.
*
* @return A string.
*/
char *execcmd(const char *cmd)
{
FILE* fp;
char buffer[128];
size_t size = 0;
size_t buffer_size = 128;
char* result = malloc(buffer_size);

if (result == NULL)
{
fprintf(stderr, "Memory allocation failed\n");

exit(1);
}

result[0] = '\0';

fp = popen(cmd, "r");

if (fp == NULL)
{
fprintf(stderr, "Failed to run command\n");

exit(1);
}

while (fgets(buffer, sizeof(buffer), fp) != NULL)
{
size_t buffer_len = strlen(buffer);

if (size + buffer_len + 1 > buffer_size)
{
buffer_size *= 2;
result = realloc(result, buffer_size);

if (result == NULL)
{
fprintf(stderr, "Memory reallocation failed\n");

exit(1);
}
}

strcat(result, buffer);
size += buffer_len;
}

pclose(fp);

return result;
}
4 changes: 3 additions & 1 deletion src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
#include <inttypes.h>

char *lowerstr(char *str);
uint64_t getstat(const char *path);
uint64_t getstat(const char *path);
char *trim(char* str);
char *execcmd(const char *cmd);
95 changes: 93 additions & 2 deletions src/stat.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,94 @@ void sighdl(int tmp)
cont = 0;
}

__uint64_t get_current(cmdline_t *cmd, const char *path)
{
uint64_t cur = 0;

if (cmd->cmd != NULL)
{
// Make sure key is available.
if (cmd->key == NULL)
{
fprintf(stderr, "Command specified, but no key for separator included. Please use --key.\n");

return cur;
}

char *sep = " ";

if (cmd->sep != NULL)
{
sep = cmd->sep;
}

char *output = execcmd(cmd->cmd);

// Make sure we have output.
if (output == NULL)
{
fprintf(stderr, "Command '%s' output is NULL.\n", cmd->cmd);

return 0;
}

// We need to copy output memory address since strsep() modifies output below.
char *output_copy = output;

// Loop through each line (\n separator).
char *line = NULL;

while ((line = strsep(&output, "\n")))
{
// Make sure we have at least one byte.
if (strlen(line) < 1)
{
continue;
}

// Trim the string so it doesn't mess anything up.
char *trimmed = trim(line);

// We want to now split by our input separator (key).
char *token = NULL;

// Indicate the position we're at.
int i = 1;

while ((token = strsep(&trimmed, sep)))
{
// Key check.
if (i == 1 && strcmp(cmd->key, token) == 0)
{
i++;

continue;
}

// If the key matches, i will be 2. Therefore, use current token as value and convert to integer.
if (i == 2)
{
cur = (__uint64_t) strtoull((const char *)token, (char **)token, 0);
}

break;
}
}

// Free output string.
if (output_copy != NULL)
{
free(output_copy);
}
}
else
{
cur = getstat(path);
}

return cur;
}

int main(int argc, char *argv[])
{
// Start off by parsing our command line.
Expand All @@ -37,6 +125,9 @@ int main(int argc, char *argv[])
"--interval => Use this interval (in microseconds) instead of one second.\n" \
"--count -n => Maximum amount of times to request the counter before stopping program (0 = no limit).\n" \
"--time -t => Time limit (in seconds) before stopping program (0 = no limit).\n" \
"--cmd => The command to execute and retrieve output from.\n" \
"--sep => The separator to apply to the command's output.\n" \
"--key => The key to search for when separating the command output.\n" \
);

return 0;
Expand Down Expand Up @@ -72,7 +163,7 @@ int main(int argc, char *argv[])
signal(SIGINT, sighdl);

// Get current counter and create a loop.
uint64_t old = getstat(path);
uint64_t old = get_current(&cmd, path);
uint64_t totcount = 0;

uint64_t count = 0;
Expand Down Expand Up @@ -105,7 +196,7 @@ int main(int argc, char *argv[])
sleep(1);
}

uint64_t cur = getstat(path);
uint64_t cur = get_current(&cmd, path);
uint64_t new = cur - old;

// Save old counter.
Expand Down

0 comments on commit 2ac9b76

Please sign in to comment.