toolbox

Check-in [677c67fdd7]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:rrdreel: initial import
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 677c67fdd79a3f41918de28841612f253dd3a6f59123cad2f7db982d534f843a
User & Date: jef 2020-04-28 10:01:53
Context
2020-04-29
11:06
lib: new str_ methods and single linked list library check-in: d6005d936d user: jef tags: trunk
2020-04-28
10:01
rrdreel: initial import check-in: 677c67fdd7 user: jef tags: trunk
2020-04-27
18:36
lib: add useful function in jtbuf & jtstr check-in: 33ccecc090 user: jef tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Makefile.

1
2
3
4
5
6
7

8
9
10
11
12
13
14
.POSIX:
.PHONY: doc

PROJS = chrono cook flooze http-code radio \
	renew taillast soundmachine thread calc radios-check \
	i3notify rss i3status-notif i3radio i3codemusic \
	townsign phone ipmi-lcd-set natospell fwiki


all: progs

doc:
	# generate text documentation from man pages
	# (section title is erased using a perl one liner)
	@for proj in ${PROJS}; do \






|
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.POSIX:
.PHONY: doc

PROJS = chrono cook flooze http-code radio \
	renew taillast soundmachine thread calc radios-check \
	i3notify rss i3status-notif i3radio i3codemusic \
	townsign phone ipmi-lcd-set natospell fwiki \
	rrdreel

all: progs

doc:
	# generate text documentation from man pages
	# (section title is erased using a perl one liner)
	@for proj in ${PROJS}; do \

Changes to doc/README.md.

14
15
16
17
18
19
20

21
22
23
24
25
26
27
* [i3status-notif](/doc/tip/doc/i3status-notif.txt): custom i3status with notification area
* [ipmi-lcd-set](/doc/tip/doc/ipmi-lcd-set.txt): set LCD display on Dell servers
* [natospell](/doc/tip/doc/natospell.txt): spell words using NATO phonetic alphabet
* [phone](/doc/tip/doc/phone.txt): list contacts in a phonebook
* [radio](/doc/tip/doc/radio.txt): command line radio player
* [radios-check](/doc/tip/doc/radios-check.txt): check foutaise.org radios stream availability
* [renew](/doc/tip/doc/renew.txt): monitor expiration of domains, certificates and discrete events

* [rss](/doc/tip/doc/rss.txt): RSS reader for the command line
* [soundmachine](/doc/tip/doc/soundmachine.txt): command line soundmachine
* [taillast](/doc/tip/doc/taillast.txt): tail file from last saved offset
* [thread](/doc/tip/doc/thread.txt): execute commands in parallel on a collection of hosts
* [townsign](/doc/tip/doc/townsign.txt): french town sign generator

## Installation







>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
* [i3status-notif](/doc/tip/doc/i3status-notif.txt): custom i3status with notification area
* [ipmi-lcd-set](/doc/tip/doc/ipmi-lcd-set.txt): set LCD display on Dell servers
* [natospell](/doc/tip/doc/natospell.txt): spell words using NATO phonetic alphabet
* [phone](/doc/tip/doc/phone.txt): list contacts in a phonebook
* [radio](/doc/tip/doc/radio.txt): command line radio player
* [radios-check](/doc/tip/doc/radios-check.txt): check foutaise.org radios stream availability
* [renew](/doc/tip/doc/renew.txt): monitor expiration of domains, certificates and discrete events
* [rrdreel](/doc/tip/doc/rrdreel.txt): Live data visualisation framework
* [rss](/doc/tip/doc/rss.txt): RSS reader for the command line
* [soundmachine](/doc/tip/doc/soundmachine.txt): command line soundmachine
* [taillast](/doc/tip/doc/taillast.txt): tail file from last saved offset
* [thread](/doc/tip/doc/thread.txt): execute commands in parallel on a collection of hosts
* [townsign](/doc/tip/doc/townsign.txt): french town sign generator

## Installation

Added doc/rrdreel.txt.































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
RRDREEL(1)                                                          RRDREEL(1)

NAME
     rrdreel – Live data visualisation framework

SYNOPSIS
     rrdreel backend [option...]

DESCRIPTION
     rrdreel is a live data visualisation framework for the impatient:

     •   generate data over time

     •   pipe them to rrdreel with a proper visualisation backend

     •   enjoy the graph, adapt timeslot with left button mouse

     •   hit <Esc> key to quit

BACKENDS
     rrdreel provides two kind of backends:

   Gauge:
     Plot a single value over time.

     Options:

     •   step: input value rate in seconds (uint)

     •   title: graph title (string)

     •   vlabel: vertical label (string)

     •   legend: value legend (string)

     •   unit: value unit (string)

   Interface:
     Plot network interface traffic over time.

     Options:

     •   step: input value rate in seconds (uint)

     •   title: graph title (string)

     •   vlabel: vertical label (string)

     •   in_legend: input value legend (string)

     •   in_unit: input value unit (string)

     •   out_legend: output value legend (string)

     •   out_unit: output value unit (string)

EXAMPLE

     #!/usr/bin/env bash
     # Graph a random value between 0 and 9

     step=1
     while true; do
             echo $(( $RANDOM % 10 ))
             sleep $step
     done | rrdreel gauge \
                     step $step \
                     title "A silly graph"
                     legend "Random values" \
                     vlabel "Value" \
                     unit "(0 - 9)"

DEPENDENCIES
     rrdreel depends on rrdtool(1).

AUTHOR
     Gerome Fournier <jef@foutaise.org>.

                                  April 2020

Added src/rrdreel/Makefile.



















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
.POSIX:

PREFIX = /usr/local
MANPREFIX = $(PREFIX)/share/man

CC = cc
CFLAGS = -O3 -Wall -Wextra -Werror -pedantic \
		-I /usr/local/include \
		-I ../include \
		`pkg-config --cflags gtk+-3.0`
LDFLAGS = -L/usr/local/lib -L../lib `pkg-config --libs gtk+-3.0` -ljt -lm
PROG = rrdreel
OBJS = timeslots.o backend.o backend_gauge.o backend_interface.o parse.o rrdreel.o gui.o

all: $(PROG)

lib:
	make -C ../lib

$(PROG): lib $(OBJS)
	$(CC) $(CFLAGS) $(OBJS) -o $(PROG) $(LDFLAGS)

install: $(PROG) $(PROG).1
	mkdir -p $(PREFIX)/bin
	cp -f $(PROG) $(PREFIX)/bin
	chmod 0755 $(PREFIX)/bin/$(PROG)
	mkdir -p $(MANPREFIX)/man1
	cp -f $(PROG).1 $(MANPREFIX)/man1
	gzip -f $(MANPREFIX)/man1/$(PROG).1
	chmod 0644 $(MANPREFIX)/man1/$(PROG).1.gz

uninstall:
	rm -f $(PREFIX)/bin/$(PROG)
	rm -f $(MANPREFIX)/man1/$(PROG).1.gz

clean:
	rm -f $(PROG) $(OBJS) || true

.SUFFIXES: .c .o
.c.o:
	$(CC) $(CFLAGS) -c $<

Added src/rrdreel/backend.c.





































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "backend.h"
#include "backend_gauge.h"
#include "backend_interface.h"
#include "jt.h"

static void 
tmpfile(char **str, const char *template)
{
	int fd;

	*str = xstrdup(template);
	fd = mkstemp(*str);
	if (fd == -1)
		die("unable to create tmpfile '%s'", template);
	close(fd);
}

struct backend *backend_new()
{
	struct backend *b;

	b = xcalloc(1, sizeof(struct backend));
	b->step = 1;
	b->graph_width = 600;
	b->graph_height = 120;
	b->range = 300;
	b->png_update = NULL;
	b->run = NULL;
	tmpfile(&b->rrdfile, "/tmp/rrdreel-rrdfile.XXXXXX");
	tmpfile(&b->pngfile, "/tmp/rrdreel-pngfile.XXXXXX");

	return b;
}

void
backend_parse_cmdline(struct backend *backend, int argc, char **argv)
{
	if (strcmp(argv[0], "gauge") == 0) {
		backend_gauge_parse_cmdline(backend, argc - 1, ++argv);
	} else if (strcmp(argv[0], "interface") == 0) {
		backend_interface_parse_cmdline(backend, argc - 1, ++argv);
	} else {
		die("invalid backend '%s'", argv[0]);
	}
}

void
backend_cleanup(struct backend *b)
{
	if (unlink(b->rrdfile) == -1)
		warning("unable to remove rrdfile '%s'", b->rrdfile);

	if (unlink(b->pngfile) == -1)
		warning("unable to remove pngfile '%s'", b->pngfile);
}

void
backend_free(struct backend *b)
{
	xfree(b->rrdfile);
	xfree(b->pngfile);
	xfree(b);
}

Added src/rrdreel/backend.h.









































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef _BACKEND_H
#define _BACKEND_H

struct backend {
	unsigned int range;
	unsigned int graph_width;
	unsigned int graph_height;
	unsigned int step;
	char *rrdfile;
	char *pngfile;
	void (* run) (void);
	void (* png_update) (void);
};

struct backend *backend_new();
void backend_parse_cmdline(struct backend *backend, int argc, char **argv);
void backend_free(struct backend *backend);
void backend_cleanup(struct backend *backend);

#endif

Added src/rrdreel/backend_gauge.c.



























































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "parse.h"
#include "backend.h"
#include "backend_gauge.h"
#include "timeslots.h"
#include "jt.h"

struct backend_gauge {
	struct backend *base;
	char *title;
	char *vlabel;
	char *legend;
	char *unit;
};

struct backend_gauge bgauge = {
	NULL,
	"",
	"",
	"",
	""
};

static void
rrd_create()
{
	unsigned int heartbeat, steps, rows;
	jtbuf buf;
	size_t i;

	heartbeat = bgauge.base->step * 2;
	buf = jtbuf_new();
	buf = jtbuf_append_sprintf(buf, "rrdtool create %s -bN -s%u 'DS:gauge:GAUGE:%u:U:U'",
			bgauge.base->rrdfile, bgauge.base->step, heartbeat);
	for (i = 0; i < ARRAY_SIZE(timeslots); i++) {
		timeslot_rra_steps_rows(&timeslots[i], bgauge.base, &steps, &rows);
		buf = jtbuf_append_sprintf(buf, " 'RRA:AVERAGE:0.5:%u:%u'", steps, rows);
	}

	if (system(buf) != 0)
		die("unable to create rrd file");

	jtbuf_free(buf);
}

static void
rrd_update()
{
	char *line = NULL, *cmd = NULL;
	size_t linecapp;
	float value;

	if (getline(&line, &linecapp, stdin) == -1)
		die("unable to read next value");
	str_trim(line);
	if (parse_float(line, &value) == -1)
		die("unable to parse next value");

	xasprintf(&cmd, "rrdtool update %s -t gauge 'N:%f'", bgauge.base->rrdfile, value);
	if (system(cmd) != 0)
		die("unable to update rrd file");

	xfree(cmd);
	xfree(line);
}

static void
png_update()
{
	jtbuf buf;

	buf = jtbuf_new();
	buf = jtbuf_append_sprintf(buf, "rrdtool graph %s -s '-%d' -t '%s' -h %u -w %u -l 0 -a PNG -v '%s'",
			bgauge.base->pngfile, bgauge.base->range, bgauge.title,
			bgauge.base->graph_height, bgauge.base->graph_width,
			bgauge.vlabel);
	buf = jtbuf_append_sprintf(buf, " 'DEF:gauge=%s:gauge:AVERAGE'", bgauge.base->rrdfile);
	buf = jtbuf_append_sprintf(buf, " 'AREA:gauge#32CD32:%s'", bgauge.legend);
	buf = jtbuf_append_sprintf(buf, " 'LINE1:gauge#336600'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:gauge:MAX: Max\\: %%5.1lf %%s'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:gauge:AVERAGE: Avg\\: %%5.1lf %%S'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:gauge:LAST: Current\\: %%5.1lf %%S %s\\n'", bgauge.unit);
	buf = jtbuf_append_sprintf(buf, " 'HRULE:0#000000' > /dev/null");
	
	if (system(buf) != 0)
		die("unable to create rrd file");

	jtbuf_free(buf);
}

static void
gauge_run(void) 
{
	rrd_update();
	png_update();
}

void
backend_gauge_parse_cmdline(struct backend *backend, int argc, char **argv)
{
	bgauge.base = backend;

	if (argc % 2 != 0)
		die("missing arguments for gauge backend");

	while (argc > 0) {
		if (strcmp(argv[0], "step") == 0) {
			if (parse_uint(argv[1], &bgauge.base->step) == -1)
				die("step value invalid");
			if (bgauge.base->step == 0)
				die("step value > 0 expected");
		}
		else if (strcmp(argv[0], "title") == 0) {
			bgauge.title = argv[1];
		}
		else if (strcmp(argv[0], "vlabel") == 0) {
			bgauge.vlabel = argv[1];
		}
		else if (strcmp(argv[0], "legend") == 0) {
			bgauge.legend = argv[1];
		}
		else if (strcmp(argv[0], "unit") == 0) {
			bgauge.unit = argv[1];
		}
		else {
			die("invalid argument '%s'", argv[0]);
		}

		argc -= 2;
		argv += 2;
	}

	bgauge.base->run = &gauge_run;
	bgauge.base->png_update = &png_update;
	rrd_create();
	png_update();

}

Added src/rrdreel/backend_gauge.h.

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
#ifndef _BACKEND_GAUGE_H
#define _BACKEND_GAUGE_H

#include "backend.h"

void backend_gauge_parse_cmdline(struct backend *backend, int argc, char **argv);

#endif

Added src/rrdreel/backend_interface.c.





























































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "parse.h"
#include "backend_interface.h"
#include "timeslots.h"
#include "jt.h"

struct backend_interface {
	struct backend *base;
	char *title;
	char *vlabel;
	char *in_legend;
	char *in_unit;
	char *out_legend;
	char *out_unit;
};

struct backend_interface binterface = {
	NULL,
	"",
	"",
	"",
	"",
	"",
	""
};

static void
rrd_create()
{
	unsigned int heartbeat, steps, rows;
	jtbuf buf;
	size_t i;

	heartbeat = binterface.base->step * 2;
	buf = jtbuf_new();
	buf = jtbuf_append_sprintf(buf, "rrdtool create %s -bN -s%u", binterface.base->rrdfile, binterface.base->step);
	buf = jtbuf_append_sprintf(buf, " 'DS:in:DERIVE:%u:0:U'", heartbeat);
	buf = jtbuf_append_sprintf(buf, " 'DS:out:DERIVE:%u:0:U'", heartbeat);
	for (i = 0; i < ARRAY_SIZE(timeslots); i++) {
		timeslot_rra_steps_rows(&timeslots[i], binterface.base, &steps, &rows);
		buf = jtbuf_append_sprintf(buf, " 'RRA:AVERAGE:0.5:%u:%u'", steps, rows);
	}

	if (system(buf) != 0)
		die("unable to create rrd file");

	jtbuf_free(buf);
}

static void
rrd_update()
{
	char *line = NULL, *cmd = NULL;
	size_t linecapp;
	unsigned long in, out;

	if (getline(&line, &linecapp, stdin) == -1)
		die("unable to read next value");
	str_trim(line);

	if (parse_interface(line, &in, &out) == -1)
		die("unable to parse next values");

	xasprintf(&cmd, "rrdtool update %s -t 'in:out' 'N:%lu:%lu'", binterface.base->rrdfile, in, out);
	if (system(cmd) != 0)
		die("unable to update rrd file");

	xfree(cmd);
	xfree(line);
}

static void
png_update()
{
	jtbuf buf;

	buf = jtbuf_new();
	buf = jtbuf_append_sprintf(buf, "rrdtool graph %s -s '-%d' -t '%s' -h %u -w %u -l 0 -a PNG -v '%s'",
			binterface.base->pngfile, binterface.base->range, binterface.title,
			binterface.base->graph_height, binterface.base->graph_width,
			binterface.vlabel);
	buf = jtbuf_append_sprintf(buf, " 'DEF:in=%s:in:AVERAGE'", binterface.base->rrdfile);
	buf = jtbuf_append_sprintf(buf, " 'DEF:out=%s:out:AVERAGE'", binterface.base->rrdfile);
	buf = jtbuf_append_sprintf(buf, " 'CDEF:out_neg=out,-1,*'");
	buf = jtbuf_append_sprintf(buf, " 'AREA:in#32CD32:%s'", binterface.in_legend);
	buf = jtbuf_append_sprintf(buf, " 'LINE1:in#336600'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:in:MAX: Max\\: %%.1lf %%s'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:in:AVERAGE: Avg\\: %%.1lf %%S'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:in:LAST: Current\\: %%.1lf %%S %s\\n'", binterface.in_unit);
	buf = jtbuf_append_sprintf(buf, " 'AREA:out_neg#4169E1:%s'", binterface.out_legend);
	buf = jtbuf_append_sprintf(buf, " 'LINE1:out_neg#0033CC'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:out:MAX: Max\\: %%.1lf %%s'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:out:AVERAGE: Avg\\: %%.1lf %%S'");
	buf = jtbuf_append_sprintf(buf, " 'GPRINT:out:LAST: Current\\: %%.1lf %%S %s\\n'", binterface.out_unit);
	buf = jtbuf_append_sprintf(buf, " 'HRULE:0#000000' > /dev/null");
	
	if (system(buf) != 0)
		die("unable to create rrd file");

	jtbuf_free(buf);
}

static void
binterface_run(void) 
{
	rrd_update();
	png_update();
}

void
backend_interface_parse_cmdline(struct backend *backend, int argc, char **argv)
{
	binterface.base = backend;

	if (argc % 2 != 0)
		die("missing arguments for gauge backend");

	while (argc > 0) {
		if (strcmp(argv[0], "step") == 0) {
			if (parse_uint(argv[1], &binterface.base->step) == -1)
				die("step value invalid");
			if (binterface.base->step == 0)
				die("step value > 0 expected");
		}
		else if (strcmp(argv[0], "title") == 0) {
			binterface.title = argv[1];
		}
		else if (strcmp(argv[0], "vlabel") == 0) {
			binterface.vlabel = argv[1];
		}
		else if (strcmp(argv[0], "in_legend") == 0) {
			binterface.in_legend = argv[1];
		}
		else if (strcmp(argv[0], "in_unit") == 0) {
			binterface.in_unit = argv[1];
		}
		else if (strcmp(argv[0], "out_legend") == 0) {
			binterface.out_legend = argv[1];
		}
		else if (strcmp(argv[0], "out_unit") == 0) {
			binterface.out_unit = argv[1];
		}
		else {
			die("invalid argument '%s'", argv[0]);
		}

		argc -= 2;
		argv += 2;
	}

	binterface.base->run = &binterface_run;
	binterface.base->png_update = &png_update;
	rrd_create();
	png_update();

}

Added src/rrdreel/backend_interface.h.

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
#ifndef _BACKEND_INTERFACE_H
#define _BACKEND_INTERFACE_H

#include "backend.h"

void backend_interface_parse_cmdline(struct backend *backend, int argc, char **argv);

#endif

Added src/rrdreel/examples/cpu-load.























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env bash
# Monitor CPU load

export LC_ALL=C

function usage() {
	cat <<-EOF
	Monitor CPU load
	Usage:
		$0
	EOF
}

if [ "$1" == "-h" ]; then
	usage
	exit
fi

step=1
while true; do
	echo $(uptime | awk '{load=$(NF-2); sub(",", "", load); print load}')
	sleep $step
done | $(dirname $0)/../rrdreel gauge \
	            step $step \
	            title "CPU load" \
	            legend "load" \
	            unit ""

Added src/rrdreel/examples/cpu-load-snmp.

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env bash
# Monitor CPU load through snmp

function usage() {
	cat <<-EOF
	Monitor CPU load through snmp
	Usage:
		$0 <host> <community>
	EOF
}

if [ "$1" == "-h" ]; then
	usage
	exit
fi

if [ $# -ne 2 ]; then
        echo "wrong number of arguments" >&2
        exit 1
fi

host=$1
community=$2
step=1
while true; do
        echo $(snmpget -v1 -Oqv -c $community $host .1.3.6.1.4.1.2021.10.1.3.1)
	sleep $step
done | $(dirname $0)/../rrdreel gauge \
	            step $step \
	            title "$host / CPU load" \
	            legend "load" \
	            unit ""

Added src/rrdreel/examples/interface-bsd.









































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env bash
# Monitor local network interface on BSD

# override locales settings
export LC_ALL=C

function usage() {
	cat <<-EOF
	Monitor local network interface on BSD
	Usage:
		$0 <interface>
	EOF
}

function die() {
	echo "$@" >&2
	exit 1
}

function netstat_field_index() {
        local interface=$1
        local field=$2
        local header
        local i=1

        read header < <(netstat -b -n -I $interface)
        set -- $header
        while [ $# -gt 0 ]; do
                if [ "$1" == "$field" ]; then
                        echo "$i"
                        break
                fi
                ((i++))
                shift
        done
}

if [ "$1" == "-h" ]; then
	usage
	exit
fi

[ $# -eq 1 ] || die "wrong number of arguments"

interface=$1
ibytes_idx=$(netstat_field_index $interface "Ibytes")
obytes_idx=$(netstat_field_index $interface "Obytes")
while true; do
	echo $(netstat -b -n -I $interface \
                | awk "NR == 2 {print 8 * \$$ibytes_idx, 8 * \$$obytes_idx}")
	sleep 1
done | $(dirname $0)/../rrdreel interface title "Traffic on $interface"

Added src/rrdreel/examples/interface-linux.































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env bash
# Monitor local network interface on linux

function usage() {
	cat <<-EOF
	Monitor local network interface on linux
	Usage:
		$0 <interface>
	EOF
}

function die() {
	echo "$@" >&2
	exit 1
}

if [ "$1" == "-h" ]; then
	usage
	exit
fi

[ $# -eq 1 ] || die "wrong number of arguments"

interface=$1
[ -d "/sys/class/net/$interface" ] || die "unknown interface '$interface'"

while true; do
	echo $((8 * $(cat /sys/class/net/$interface/statistics/rx_bytes))) \
		$((8 * $(cat /sys/class/net/$interface/statistics/tx_bytes)))
	sleep 1
done | $(dirname $0)/../rrdreel interface title "Traffic on $interface"

Added src/rrdreel/examples/interface-snmp.



















































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/env bash
# Monitor network interface through snmp

# Use current iftable cache timeout as a step value.
# For a finer resolution (like every second),
# you'll have to setup the iftable cache timeout
# accordingly:
#
# snmpset -v2c -c <rwcommunity> <host> .1.3.6.1.4.1.8072.1.5.3.1.2.1.3.6.1.2.1.2.2 i 1

function usage() {
        cat <<-EOF
	Monitor network interface through snmp
	Usage:
		$0 <host> <community>
	EOF
}

function die() {
	echo "$@" >&2
	exit 1
}

function bytes2bits() {
	local input
	while read input; do
		echo $(($input * 8))
	done
}

# return current iftable cache timeout value
function iftable_cache_timeout() {
        local host=$1
        local community=$2

        snmpget -v1 -Oqv -c $community $host ".1.3.6.1.4.1.8072.1.5.3.1.2.1.3.6.1.2.1.2.2"
}

# return an interface description
function interface_desc() {
        local host=$1
        local community=$2
        local int_id=$3

        snmpget -v1 -Oqv -c $community $host .1.3.6.1.2.1.2.2.1.2.$int_id
}

# select an interface id
function interface_id_select() {
        local $host=$1
        local $community=$2
        local int_ids
        local int_id
        local i

	int_ids=$(snmpwalk -v1 -Oqv -c $community $host .1.3.6.1.2.1.2.2.1.1)
	[ -n "$int_ids" ] || return

        while true; do
                echo "Select interface id number:" >&2
                for i in $int_ids; do
                        echo "$i)" $(interface_desc $host $community $i) >&2
                done
                read -p "Answer: " int_id
                if grep -q -F "$int_id" <<< "$int_ids"; then
                        echo $int_id
                        break
                fi
        done
}

if [ "$1" == "-h" ]; then
	usage
	exit
fi

if [ $# -ne 2 ]; then
	echo "wrong number of arguments" >&2
	exit 1
fi

host=$1
community=$2

step=$(iftable_cache_timeout $host $community)
[ -n "$step" ] || die "can't get iftable cache timeout"

int_id=$(interface_id_select $host $community)
[ -n "$int_id" ] || die "can't select an interface"

int_desc=$(interface_desc $host $community $int_id)
[ -n "$int_desc" ] || die "can't find interface description"

ifinoctets=".1.3.6.1.2.1.2.2.1.10"
ifoutoctets=".1.3.6.1.2.1.2.2.1.16"
while true; do
	values=$(snmpget -v1 -Oqv -c $community $host $ifinoctets.$int_id $ifoutoctets.$int_id)
	if [ $? -eq 0 ]; then
		echo $(bytes2bits <<< "$values" \
			| paste -d " " - -)
	fi
	sleep $step
done | $(dirname $0)/../rrdreel interface \
	            step $step \
                    title "Traffic on $host/$int_desc" \

Added src/rrdreel/examples/ping-times.







































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env bash
# Monitor ping response times

export LC_ALL=C

function usage() {
	cat <<-EOF
	Monitor ping response times
	Usage:
		$0 <host>
	EOF
}

if [ "$1" == "-h" ]; then
	usage
	exit
fi

if [ $# -ne 1 ]; then
	echo "wrong number of arguments" >&2
	exit 1
fi

host=$1
step=1
while true; do
	echo $(ping -c1 $host | awk '/icmp_seq/ {time=$(NF-1); sub(".*=", "", time); print time;}') 
	sleep $step
done \
	| $(dirname $0)/../rrdreel gauge \
		step $step \
		title "Ping response time for $host" \
		vlabel "response time (ms)" \
		legend "time" \
		unit "ms"

Added src/rrdreel/examples/random.



























>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env bash
# Graph a random value between 0 and 9

step=1
while true; do
	echo $(( $RANDOM % 10 ))
	sleep $step
done | $(dirname $0)/../rrdreel gauge \
	step $step \
	title "A silly graph" \
	legend "Random values" \
	vlabel "Value" \
	unit "(0 - 9)"

Added src/rrdreel/examples/sinusoide.































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env bash
# Graph a sinusoide

function usage() {
	cat <<-EOF
	Graph a sinusoide
	Usage:
		$0
	EOF
}

function sin() {
	bc -l <<< "scale=5;s($1 * 0.0174533)"
}

if [ "$1" == "-h" ]; then
	usage
	exit
fi

i=0
step=1
while true; do
	echo $(sin $i)
	i=$(($i + 10))
	sleep $step
done | $(dirname $0)/../rrdreel gauge \
	            step $step \
	            title "Sinusoide" \
		    vlabel "sinus value" \
	            legend "sinus"

Added src/rrdreel/gui.c.





































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <gtk/gtk.h>
#include "timeslots.h"
#include "gui.h"
#include "jt.h"

struct gui {
	struct backend *backend;
	GtkWidget *image;
} gui = {NULL, NULL};

/*
 * Menu
 */

static void
menu_activate(GtkWidget *widget, gpointer data)
{
	(void) widget;

	gui.backend->range = GPOINTER_TO_INT(data);
	gui.backend->png_update();
	gtk_image_set_from_file(GTK_IMAGE(gui.image), gui.backend->pngfile);
}

static GtkWidget *
menu()
{
	GtkWidget *menu, *entry;
	struct timeslot t;
	size_t i;

	menu = gtk_menu_new();
	for (i = 0; i < ARRAY_SIZE(timeslots); i++) {
		t = timeslots[i];
		entry = gtk_menu_item_new_with_label(t.label);
		g_signal_connect(entry, "activate", G_CALLBACK(menu_activate), GINT_TO_POINTER(t.range));
		gtk_menu_shell_append(GTK_MENU_SHELL(menu), entry);
	}

	return menu;
}

/*
 * Callbacks
 */

static gboolean
button_press_callback(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	static GtkWidget *m = NULL;

	(void) widget;
	(void) data;

	if (m == NULL)
		m = menu();

	if (event->button == 1) {
		gtk_widget_show_all(m);
		gtk_menu_popup_at_pointer(GTK_MENU(m), NULL);
		return TRUE;
	}
	return FALSE;
}

static gboolean
key_press_callback(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	(void) widget;
	(void) data;

	if (event->keyval == GDK_KEY_Escape) {
		gtk_main_quit();
		return TRUE;
	}
	return FALSE;
}

/*
 * Main Gui
 */

static gboolean
gui_refresh(void *data)
{
	struct gui *gui = (struct gui *)data;

	gui->backend->run();
	gtk_image_set_from_file(GTK_IMAGE(gui->image), gui->backend->pngfile);

	return G_SOURCE_CONTINUE;
}

void
gui_run(struct backend *b)
{
	GtkWidget *window;

	gui.backend = b;

	gtk_init(NULL, NULL);
	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	g_signal_connect(window, "delete_event", G_CALLBACK(gtk_main_quit), NULL);
	g_signal_connect(window, "key_press_event", G_CALLBACK(key_press_callback), NULL);
	g_signal_connect(window, "button_press_event", G_CALLBACK(button_press_callback), NULL);

	gui.image = gtk_image_new_from_file(b->pngfile);
	gtk_container_add(GTK_CONTAINER(window), gui.image);
	gtk_widget_show_all(window);

	g_timeout_add_seconds(gui.backend->step, gui_refresh, &gui); 

	gtk_main();
}

Added src/rrdreel/gui.h.













>
>
>
>
>
>
1
2
3
4
5
6
#ifndef _GUI_H
#define _GUI_H

void gui_run(struct backend *b);

#endif

Added src/rrdreel/parse.c.

















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <string.h>
#include "parse.h"
#include "jt.h"

int
parse_uint(const char *str, unsigned int *value)
{
        int nparsed;

        if (sscanf(str, "%u%n", value, &nparsed) == 1
                        && strlen(str) == (size_t) nparsed)
                return 0;

        return -1;
}

int
parse_float(const char *str, float *value)
{
        int nparsed;

        if (sscanf(str, "%f%n", value, &nparsed) == 1
                        && strlen(str) == (size_t) nparsed)
                return 0;

        return -1;
}

int
parse_interface(const char *str, unsigned long *in, unsigned long *out)
{
        int nparsed;

        if (sscanf(str, "%lu%lu%n", in, out, &nparsed) == 2
                        && strlen(str) == (size_t) nparsed)
                return 0;

        return -1;
}

Added src/rrdreel/parse.h.

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
#ifndef _PARSE_H
#define _PARSE_H

int parse_uint(const char *str, unsigned int *value);
int parse_float(const char *str, float *value);
int parse_interface(const char *str, unsigned long *in, unsigned long *out);

#endif

Added src/rrdreel/rrdreel.1.











































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
.Dd April 2020
.Dt RRDREEL 1
.Os
.Sh NAME
.Nm rrdreel
.Nd Live data visualisation framework
.Sh SYNOPSIS
.Nm
.Ar backend
.Op Ar option...
.Sh DESCRIPTION
.Nm
is a live data visualisation framework for the impatient:
.Bl -bullet
.It
generate data over time
.It
pipe them to rrdreel with a proper visualisation backend
.It
enjoy the graph, adapt timeslot with left button mouse
.It
hit <Esc> key to quit
.El
.Sh BACKENDS
.Nm
provides two kind of backends:
.Ss Gauge:
Plot a single value over time.
.Pp
Options:
.Bl -bullet
.It
step: input value rate in seconds (uint)
.It
title: graph title (string)
.It
vlabel: vertical label (string)
.It
legend: value legend (string)
.It
unit: value unit (string)
.El
.Ss Interface:
Plot network interface traffic over time.
.Pp
Options:
.Bl -bullet
.It
step: input value rate in seconds (uint)
.It
title: graph title (string)
.It
vlabel: vertical label (string)
.It
in_legend: input value legend (string)
.It
in_unit: input value unit (string)
.It
out_legend: output value legend (string)
.It
out_unit: output value unit (string)
.El
.Sh EXAMPLE
.Bd -literal

#!/usr/bin/env bash
# Graph a random value between 0 and 9

step=1
while true; do
	echo $(( $RANDOM % 10 ))
	sleep $step
done | rrdreel gauge \\
		step $step \\
		title "A silly graph"
		legend "Random values" \\
		vlabel "Value" \\
		unit "(0 - 9)"
.Ed
.Sh DEPENDENCIES
.Nm
depends on
.Xr rrdtool 1 .
.Sh AUTHOR
.An Gerome Fournier Aq Mt jef@foutaise.org .

Added src/rrdreel/rrdreel.c.



























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include "timeslots.h"
#include "backend.h"
#include "gui.h"
#include "jt.h"

#define PROGNAME "rrdreel"

struct backend *backend = NULL;

static void
usage()
{
	fprintf(stderr,
		"Live monitoring framework\n"
		"Look into examples directory for usage scenarios\n"
		"Usage:\n"
		"	<data source> | %s <backend> [backend options]\n"
		"Backends:\n"
		"	gauge:     plot a single value over time\n"
		"	interface: plot network interface traffic over time\n"
		"Options\n"
		"	-h : display this help and exit\n", PROGNAME);
	exit(1);
}

static void
cleanup()
{
	backend_cleanup(backend);
	backend_free(backend);
}

static void
sigaction_handler(int signal)
{
	(void) signal;

	cleanup();
}

int
main(int argc, char **argv)
{
	struct sigaction action;
	int ch;

	while ((ch = getopt(argc, argv, "h")) != -1) {
		switch (ch) {
		case 'h':
			usage();
			break;
		}
	}
	argc -= optind;
	argv += optind;

	if (argc < 1)
		die("wrong number of arguments");

	backend = backend_new();
	backend_parse_cmdline(backend, argc, argv);

	memset(&action, 0, sizeof(struct sigaction));
	action.sa_handler = sigaction_handler;
	sigaction(SIGINT, &action, NULL);
	sigaction(SIGTERM, &action, NULL);
	atexit(cleanup);

	gui_run(backend);

	return EXIT_SUCCESS;
}

Added src/rrdreel/screenshots/interface.png.

cannot compute difference between binary files

Added src/rrdreel/screenshots/ping.png.

cannot compute difference between binary files

Added src/rrdreel/screenshots/sinusoide.png.

cannot compute difference between binary files

Added src/rrdreel/timeslots.c.























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <math.h>
#include "timeslots.h"

struct timeslot timeslots[6] = {
	{"last 5 mns", 300},
	{"last 15 mns", 900},
	{"last 30 mns", 1800},
	{"last hour", 3600},
	{"last 12 hours", 42300},
	{"last day", 86400}
};

void
timeslot_rra_steps_rows(struct timeslot *timeslot, struct backend *backend,
		unsigned int *steps, unsigned int *rows)
{
	unsigned int ndata;

	ndata  = ceil(timeslot->range / backend->step);
	if (ndata < backend->graph_width) {
		*steps = 1;
		*rows = ndata;
	} else {
		*steps = (int)(ndata / backend->graph_width);
		*rows = backend->graph_width;
	}
}

Added src/rrdreel/timeslots.h.

































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef _TIMESLOTS_H
#define _TIMESLOTS_H

#include "backend.h"

struct timeslot {
	char *label;
	unsigned int range;
};

extern struct timeslot timeslots[6];

void timeslot_rra_steps_rows(struct timeslot *timeslot, struct backend *backend,
		unsigned int *steps, unsigned int *rows);

#endif