From bda87a3756d8e52850fbc1f641155c20575b01de Mon Sep 17 00:00:00 2001 From: Tobias Date: Sun, 7 Dec 2014 18:41:13 +0100 Subject: [PATCH 01/73] Added multigraph capabilities and fixed regex --- plugins/ups/apc_status | 46 +++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/plugins/ups/apc_status b/plugins/ups/apc_status index fee73ff5..9860dd24 100755 --- a/plugins/ups/apc_status +++ b/plugins/ups/apc_status @@ -1,33 +1,55 @@ #!/bin/sh # -# (c) Andreas Kreisl +# (c) Andreas Kreisl extended by Tobias Schramm # # Link name will be used as title: apc_{$title} # # env.keys LOADPCT BCHARGE LINEV BATTV TIMELEFT -# env.unit % or Volt or Minutes # - if [ -z "$keys" ]; then - keys="TIMELEFT" + keys="LINEV LOADPCT BCHARGE NUMXFERS TIMELEFT" fi +apcinfo=`/sbin/apcaccess` + if [ "$1" = "config" ]; then title=`basename $0 | sed 's/^apc_//g' | awk '{ sub(/^./,toupper(substr($0,1,1))); print; }'` - echo "graph_title APC Status - $title" - echo 'graph_args --base 1000 -l 0 ' - echo "graph_vlabel $unit" - echo 'graph_category sensors' + echo 'multigraph apc_status' + echo "graph_title UPS Status - $title" + echo 'graph_args --base 1000' + echo 'graph_category hardware' title=`/sbin/apcaccess | egrep "^MODEL" | awk '{print $3" "$4" "$5" "$6" "$7" "$8" "$9;}'` echo "graph_info $title" for key in $keys; do echo "$key.label $key" - echo "$key.info Value of $key." - echo "$key.draw LINE2" + echo "$key.info Value of $key" + echo "$key.draw LINE1" + done + for key in $keys; do + key_lower=`echo "$key" | awk '{print tolower($0);}'` + unit=`echo "$apcinfo" | egrep "^$key" | awk '{print $4;}'` + echo "multigraph apc_status.$key_lower" + echo "graph_title $key" + echo 'graph_args --base 1000' + if [ -n "$unit" ]; then + echo "graph_vlabel $unit" + fi + echo 'graph_category hardware' + echo "$key.label $key" + echo "$key.info $key." + echo "$key.draw LINE1" done exit 0 fi -searchkey=`echo "$keys" | tr " " "\|"` -/sbin/apcaccess | egrep "$searchkey" | awk '{print $1".value "$3;}' +echo 'multigraph apc_status' +for key in $keys; do + echo "$apcinfo" | egrep "^$key" | awk '{print $1".value "$3;}' +done +for key in $keys; do + key_lower=`echo "$key" | awk '{print tolower($0)}'` + echo "multigraph apc_status.$key_lower" + echo "$apcinfo" | egrep "^$key" | awk '{print $1".value "$3;}' +done + From 6ed404cbc9896dc5b75ef7eaf5e44e5b53a8d152 Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Tue, 31 Mar 2015 20:33:54 +0000 Subject: [PATCH 02/73] multicpu1sec-c: ignoring generated files --- plugins/system/multicpu1sec/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/system/multicpu1sec/.gitignore diff --git a/plugins/system/multicpu1sec/.gitignore b/plugins/system/multicpu1sec/.gitignore new file mode 100644 index 00000000..4ff4b96f --- /dev/null +++ b/plugins/system/multicpu1sec/.gitignore @@ -0,0 +1,4 @@ +/multicpu1sec-c +/multicpu1sec.o +/multicpu1sec.pid +/multicpu1sec.value From 7d88587fb6d5b0a33939b75cf38bbf5cb44c82de Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Tue, 31 Mar 2015 22:17:16 +0000 Subject: [PATCH 03/73] multicpu1sec-c: use plain POSIX instead of stdlib As it will be used every second, we should limit the overhead --- plugins/system/multicpu1sec/multicpu1sec-c.c | 44 ++++++++++++-------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/plugins/system/multicpu1sec/multicpu1sec-c.c b/plugins/system/multicpu1sec/multicpu1sec-c.c index b66eab79..f3d00684 100644 --- a/plugins/system/multicpu1sec/multicpu1sec-c.c +++ b/plugins/system/multicpu1sec/multicpu1sec-c.c @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -110,36 +111,45 @@ int acquire() { time_t epoch = wait_until_next_second(); /* Reading /proc/stat */ - FILE* f = fopen(PROC_STAT, "r"); - // Read and ignore the 1rst line - char buffer[1024]; - fgets(buffer, 1024, f); + int f = open(PROC_STAT, O_RDONLY); + + const int buffer_size = 64 * 1024; + char buffer[buffer_size]; + + // whole /proc/stat can be read in 1 syscall + if (read(f, buffer, buffer_size) <= 0) { + return fail("cannot read " PROC_STAT); + } + + // ignore the 1rst line + char* line; + const char* newl = "\n"; + line = strtok(buffer, newl); /* open the spoolfile */ - FILE* cache_file = fopen(cache_filename, "a"); + int cache_file = open(cache_filename, O_CREAT | O_APPEND | O_WRONLY); + /* lock */ - flock(fileno(cache_file), LOCK_EX); - - while (! feof(f)) { - if (fgets(buffer, 1024, f) == 0) { - // EOF - break; - } + flock(cache_file, LOCK_EX); + for (line = strtok(NULL, newl); line; line = strtok(NULL, newl)) { // Not on CPU lines anymore - if (strncmp(buffer, "cpu", 3)) break; + if (strncmp(line, "cpu", 3)) break; char cpu_id[64]; long usr, nice, sys, idle, iowait, irq, softirq; - sscanf(buffer, "%s %ld %ld %ld %ld %ld %ld %ld", cpu_id, &usr, &nice, &sys, &idle, &iowait, &irq, &softirq); + sscanf(line, "%s %ld %ld %ld %ld %ld %ld %ld", cpu_id, &usr, &nice, &sys, &idle, &iowait, &irq, &softirq); long used = usr + nice + sys + iowait + irq + softirq; - fprintf(cache_file, "%s.value %ld:%ld\n", cpu_id, epoch, used); + char out_buffer[1024]; + sprintf(out_buffer, "%s.value %ld:%ld\n", cpu_id, epoch, used); + + write(cache_file, out_buffer, strlen(out_buffer)); } - fclose(cache_file); - fclose(f); + close(cache_file); + close(f); } } From 888d91f4e6d4963e060b434e90c04ccd6392142a Mon Sep 17 00:00:00 2001 From: Pierre Schweitzer Date: Wed, 20 May 2015 15:24:30 +0200 Subject: [PATCH 04/73] cyrus-imapd: First, try to look for proc_path: config parameter. If it doesn't exist, fallback to old method, and extrapolate the configdirectory: config parameter --- plugins/cyrus/cyrus-imapd | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/cyrus/cyrus-imapd b/plugins/cyrus/cyrus-imapd index faaba8cc..e2b54e3a 100755 --- a/plugins/cyrus/cyrus-imapd +++ b/plugins/cyrus/cyrus-imapd @@ -66,11 +66,14 @@ GPLv2 =cut # IMAP Configuration Directory -CONFIGDIR=$(awk -F : '/^configdirectory:/ { gsub(/ /, "", $2); print $2 }' /etc/imapd.conf 2> /dev/null) -PROCDIR="${CONFIGDIR}/proc" +PROCDIR=$(awk -F : '/^proc_path:/ { gsub(/ /, "", $2); print $2 }' /etc/imapd.conf 2> /dev/null) +if [ "x${PROCDIR}x" = "xx" ]; then + CONFIGDIR=$(awk -F : '/^configdirectory:/ { gsub(/ /, "", $2); print $2 }' /etc/imapd.conf 2> /dev/null) + PROCDIR="${CONFIGDIR}/proc" +fi if [ "$1" = "autoconf" ]; then - if [ "x${CONFIGDIR}x" != "xx" ] && [ -d ${PROCDIR} ]; then + if [ "x${PROCDIR}x" != "xx" ] && [ -d ${PROCDIR} ]; then echo yes else echo "no (no cyrus-imapd procdir found)" @@ -79,7 +82,7 @@ if [ "$1" = "autoconf" ]; then fi # Check if we actually got some sensible data -if [ "x${CONFIGDIR}x" = "xx" ]; then +if [ "x${PROCDIR}x" = "xx" ]; then exit 1 fi From 5715bb24087d832466e6c313c6d40787d98bac49 Mon Sep 17 00:00:00 2001 From: Stanislav Kopp Date: Thu, 21 May 2015 15:40:11 +0200 Subject: [PATCH 05/73] fixed regex for "vm_name" resolv, so VMs with dash in the name will be displayed correctly --- plugins/virtualization/kvm_cpu | 2 +- plugins/virtualization/kvm_io | 2 +- plugins/virtualization/kvm_mem | 2 +- plugins/virtualization/kvm_net | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/virtualization/kvm_cpu b/plugins/virtualization/kvm_cpu index eb47ee96..2088b093 100755 --- a/plugins/virtualization/kvm_cpu +++ b/plugins/virtualization/kvm_cpu @@ -68,7 +68,7 @@ def find_vm_names(pids): result = {} for pid in pids: cmdline = open("/proc/%s/cmdline" % pid, "r") - result[pid] = clean_vm_name(re.sub(r"^.*-name\x00([a-zA-Z0-9.-_]*)\x00\-.*$",r"\1", cmdline.readline())) + result[pid] = clean_vm_name(re.sub(r"^.*-name\x00([a-zA-Z0-9.-_-]*)\x00\-.*$",r"\1", cmdline.readline())) return result def list_pids(): diff --git a/plugins/virtualization/kvm_io b/plugins/virtualization/kvm_io index 5019fbdc..8051692f 100755 --- a/plugins/virtualization/kvm_io +++ b/plugins/virtualization/kvm_io @@ -85,7 +85,7 @@ def find_vm_names(pids): result = {} for pid in pids: cmdline = open("/proc/%s/cmdline" % pid, "r") - result[pid] = clean_vm_name(re.sub(r"^.*-name\x00([a-zA-Z0-9.-_]*)\x00\-.*$",r"\1", cmdline.readline())) + result[pid] = clean_vm_name(re.sub(r"^.*-name\x00([a-zA-Z0-9.-_-]*)\x00\-.*$",r"\1", cmdline.readline())) return result def list_pids(): diff --git a/plugins/virtualization/kvm_mem b/plugins/virtualization/kvm_mem index 5038f9b1..2a93aaa6 100755 --- a/plugins/virtualization/kvm_mem +++ b/plugins/virtualization/kvm_mem @@ -82,7 +82,7 @@ def find_vm_names(pids): result = {} for pid in pids: cmdline = open("/proc/%s/cmdline" % pid, "r") - result[pid] = clean_vm_name(re.sub(r"^.*-name\x00([a-zA-Z0-9.-_]*)\x00\-.*$",r"\1", cmdline.readline())) + result[pid] = clean_vm_name(re.sub(r"^.*-name\x00([a-zA-Z0-9.-_-]*)\x00\-.*$",r"\1", cmdline.readline())) return result def list_pids(): diff --git a/plugins/virtualization/kvm_net b/plugins/virtualization/kvm_net index 5754a5ec..ad34e217 100755 --- a/plugins/virtualization/kvm_net +++ b/plugins/virtualization/kvm_net @@ -84,7 +84,7 @@ def find_vm_names(pids): result = {} for pid in pids: cmdline = open("/proc/%s/cmdline" % pid, "r") - result[pid] = clean_vm_name(re.sub(r"^.*-name\x00([a-zA-Z0-9.-_]*)\x00\-.*$",r"\1", cmdline.readline())) + result[pid] = clean_vm_name(re.sub(r"^.*-name\x00([a-zA-Z0-9.-_-]*)\x00\-.*$",r"\1", cmdline.readline())) return result def get_vm_mac(pid): From a8e524449d16ebd7c77a6852473a6b2cc13a135f Mon Sep 17 00:00:00 2001 From: Michel Albert Date: Tue, 26 May 2015 15:52:44 +0200 Subject: [PATCH 06/73] Plugin runs, even if no access to list DBs. If the user as whom "munin" connects has no rights to list existing DBs, the plugin crashes. Even though the rest of the code is OK. Listing DBs is only necessary in the case of auto-configuring the plugin. Not for running it. This removes the line which causes the bug. The variable which is set in this line is anyway not used. --- plugins/postgresql/postgres_space_ | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/postgresql/postgres_space_ b/plugins/postgresql/postgres_space_ index df1fd640..6a8931b0 100755 --- a/plugins/postgresql/postgres_space_ +++ b/plugins/postgresql/postgres_space_ @@ -67,9 +67,6 @@ if (exists $ARGV[0]) { my (undef, undef, $dbname) = split (/_/, $0, 3); die "No dbname configured (did you make the proper symlink?)" unless $dbname; -my @datasources = DBI->data_sources ('Pg') - or die ("Can't read any possible data sources: $?"); - my $dsn = "DBI:Pg:dbname=$dbname"; $dsn .= ";host=$dbhost" if $dbhost; print "#$dsn\n" if $debug; From d6465721f3f00866cda31ee3b86f15c24deaa92c Mon Sep 17 00:00:00 2001 From: Michel Albert Date: Tue, 26 May 2015 16:01:48 +0200 Subject: [PATCH 07/73] Stack postfix mailqueues instead of overlapping. It is very hard to see the real values in the postfix queue when they are overlapping. Having a stacked graph eleviates this whil still retaining all the necessary data. --- plugins/postfix/postfix_mailqueue_ | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/postfix/postfix_mailqueue_ b/plugins/postfix/postfix_mailqueue_ index 5ee84556..8154a677 100755 --- a/plugins/postfix/postfix_mailqueue_ +++ b/plugins/postfix/postfix_mailqueue_ @@ -114,6 +114,12 @@ maildrop.label maildrop incoming.label incoming corrupt.label corrupt hold.label held +active.draw AREA +deferred.draw STACK +maildrop.draw STACK +incoming.draw STACK +corrupt.draw STACK +hold.draw STACK EOF for field in active deferred maildrop incoming corrupt hold; do print_warning $field From 2db160e26812a4f8bfdcbb700a7d1443b180924a Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Tue, 26 May 2015 20:50:26 +0000 Subject: [PATCH 08/73] p/multicpu1sec-c: use posix IO instead of stdlib --- plugins/system/multicpu1sec/multicpu1sec-c.c | 25 +++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/plugins/system/multicpu1sec/multicpu1sec-c.c b/plugins/system/multicpu1sec/multicpu1sec-c.c index f3d00684..9997ef6f 100644 --- a/plugins/system/multicpu1sec/multicpu1sec-c.c +++ b/plugins/system/multicpu1sec/multicpu1sec-c.c @@ -21,23 +21,30 @@ int fail(char* msg) { int config() { /* Get the number of CPU */ - FILE* f; - if ( !(f=fopen(PROC_STAT, "r")) ) { + int f; + if ( !(f=open(PROC_STAT, O_RDONLY)) ) { return fail("cannot open " PROC_STAT); } // Starting with -1, since the first line is the "global cpu line" int ncpu = -1; - while (! feof(f)) { - char buffer[1024]; - if (fgets(buffer, 1024, f) == 0) { - break; - } - if (! strncmp(buffer, "cpu", 3)) ncpu ++; + const int buffer_size = 64 * 1024; + char buffer[buffer_size]; + + // whole /proc/stat can be read in 1 syscall + if (read(f, buffer, buffer_size) <= 0) { + return fail("cannot read " PROC_STAT); } - fclose(f); + // tokenization per-line + char* line; + char* newl = "\n"; + for (line = strtok(buffer, newl); line; line = strtok(NULL, newl)) { + if (! strncmp(line, "cpu", 3)) ncpu ++; + } + + close(f); printf( "graph_title multicpu1sec\n" From 9901e00140c50c6428a16b8a8e6d8c568a60e002 Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Tue, 26 May 2015 20:56:57 +0000 Subject: [PATCH 09/73] p/multicpu1sec-c: keep the files open --- plugins/system/multicpu1sec/multicpu1sec-c.c | 22 +++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/plugins/system/multicpu1sec/multicpu1sec-c.c b/plugins/system/multicpu1sec/multicpu1sec-c.c index 9997ef6f..49b4320a 100644 --- a/plugins/system/multicpu1sec/multicpu1sec-c.c +++ b/plugins/system/multicpu1sec/multicpu1sec-c.c @@ -112,17 +112,25 @@ int acquire() { fprintf(pid_file, "%d\n", getpid()); fclose(pid_file); + /* Reading /proc/stat */ + int f = open(PROC_STAT, O_RDONLY); + + /* open the spoolfile */ + int cache_file = open(cache_filename, O_CREAT | O_APPEND | O_WRONLY); + /* loop each second */ while (1) { /* wait until next second */ time_t epoch = wait_until_next_second(); - /* Reading /proc/stat */ - int f = open(PROC_STAT, O_RDONLY); const int buffer_size = 64 * 1024; char buffer[buffer_size]; + if (lseek(f, 0, SEEK_SET) < 0) { + return fail("cannot seek " PROC_STAT); + } + // whole /proc/stat can be read in 1 syscall if (read(f, buffer, buffer_size) <= 0) { return fail("cannot read " PROC_STAT); @@ -133,9 +141,6 @@ int acquire() { const char* newl = "\n"; line = strtok(buffer, newl); - /* open the spoolfile */ - int cache_file = open(cache_filename, O_CREAT | O_APPEND | O_WRONLY); - /* lock */ flock(cache_file, LOCK_EX); @@ -155,9 +160,12 @@ int acquire() { write(cache_file, out_buffer, strlen(out_buffer)); } - close(cache_file); - close(f); + /* unlock */ + flock(cache_file, LOCK_UN); } + + close(cache_file); + close(f); } int fetch() { From c5238dbc5fcfe1ca6a9d4093d4838fb30255ec7c Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Sat, 30 May 2015 08:42:38 +0000 Subject: [PATCH 10/73] p/multicpu1sec-c: always exit(0) when successful --- plugins/system/multicpu1sec/multicpu1sec-c.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/system/multicpu1sec/multicpu1sec-c.c b/plugins/system/multicpu1sec/multicpu1sec-c.c index 49b4320a..3e018ad8 100644 --- a/plugins/system/multicpu1sec/multicpu1sec-c.c +++ b/plugins/system/multicpu1sec/multicpu1sec-c.c @@ -166,6 +166,8 @@ int acquire() { close(cache_file); close(f); + + return 0; } int fetch() { @@ -182,6 +184,8 @@ int fetch() { ftruncate(fileno(cache_file), 0); fclose(cache_file); + + return 0; } int main(int argc, char **argv) { From 54becf68caf61517f606f2dec018bdbc4f564596 Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Mon, 1 Jun 2015 20:12:47 +0000 Subject: [PATCH 11/73] p/if1sec: initial add --- plugins/system/1sec/if1sec-c.c | 273 +++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 plugins/system/1sec/if1sec-c.c diff --git a/plugins/system/1sec/if1sec-c.c b/plugins/system/1sec/if1sec-c.c new file mode 100644 index 00000000..6793be4c --- /dev/null +++ b/plugins/system/1sec/if1sec-c.c @@ -0,0 +1,273 @@ +/* + * if1sec C plugin + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define PROC_STAT "/proc/net/dev" +#define PLUGIN_NAME "if1sec-c" + +int fail(char* msg) { + perror(msg); + + return 1; +} + +/* Returns the ifname from a /proc/net/dev line + * It will return an inside pointer to line, and modifiy the end with a \0 + */ +char* get_ifname_from_procstatline(char* line) { + char *ifname; + for (ifname = line; (*ifname) == ' '; ifname ++); + + char *ifname_end; + for (ifname_end = ifname; (*ifname_end) != ':'; ifname_end ++); + (*ifname_end) = '\0'; + + return ifname; +} + +int config() { + /* Get the number of if */ + int f; + if ( !(f=open(PROC_STAT, O_RDONLY)) ) { + return fail("cannot open " PROC_STAT); + } + + // Starting with -2, since the 2 lines on top are header lines + int nif = -2; + + const int buffer_size = 64 * 1024; + char buffer[buffer_size]; + + // whole /proc/stat can be read in 1 syscall + if (read(f, buffer, buffer_size) <= 0) { + return fail("cannot read " PROC_STAT); + } + + // tokenization per-line + char* line; char *saveptr; + char* newl = "\n"; + for (line = strtok_r(buffer, newl, &saveptr); line; line = strtok_r(NULL, newl, &saveptr)) { + // Skip the header lines + if (nif ++ < 0) { continue; } + + char* if_name = get_ifname_from_procstatline(line); + printf( + "multigraph if_%s_1sec" "\n" + "graph_order down up" "\n" + "graph_title %s traffic" "\n" + "graph_category system::1sec" "\n" + "graph_vlabel bits in (-) / out (+) per ${graph_period}" "\n" + "graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y" "\n" + , if_name, if_name + ); + + printf( + "down.label -" "\n" + "down.type DERIVE" "\n" + "down.graph no" "\n" + "down.cdef down,8,*" "\n" + "down.min 0" "\n" + + "up.label bps" "\n" + "up.type DERIVE" "\n" + "up.negative down" "\n" + "up.cdef down,8,*" "\n" + "up.min 0" "\n" + ); + } + + close(f); + + + return 0; +} + +char* pid_filename; +char* cache_filename; + +/* Wait until the next second, and return the EPOCH */ +time_t wait_until_next_second() { + struct timespec tp; + clock_gettime(CLOCK_REALTIME, &tp); + + time_t current_epoch = tp.tv_sec; + long nsec_to_sleep = 1000*1000*1000 - tp.tv_nsec; + + + /* Only sleep if needed */ + if (nsec_to_sleep > 0) { + tp.tv_sec = 0; + tp.tv_nsec = nsec_to_sleep; + nanosleep(&tp, NULL); + } + + return current_epoch + 1; +} + +int acquire() { + + /* fork ourselves if not asked otherwise */ + char* no_fork = getenv("no_fork"); + if (! no_fork || strcmp("1", no_fork)) { + if (fork()) return; + // we are the child, complete the daemonization + + /* Close standard IO */ + fclose(stdin); + fclose(stdout); + fclose(stderr); + + /* create new session and process group */ + setsid(); + } + + /* write the pid */ + FILE* pid_file = fopen(pid_filename, "w"); + fprintf(pid_file, "%d\n", getpid()); + fclose(pid_file); + + /* Reading /proc/stat */ + int f = open(PROC_STAT, O_RDONLY); + + /* open the spoolfile */ + int cache_file = open(cache_filename, O_CREAT | O_APPEND | O_WRONLY, S_IRUSR | S_IWUSR); + + /* loop each second */ + while (1) { + /* wait until next second */ + time_t epoch = wait_until_next_second(); + + const int buffer_size = 64 * 1024; + char buffer[buffer_size]; + + if (lseek(f, 0, SEEK_SET) < 0) { + return fail("cannot seek " PROC_STAT); + } + + // whole PROC file can be read in 1 syscall + if (read(f, buffer, buffer_size) <= 0) { + return fail("cannot read " PROC_STAT); + } + + // ignore the 1rst line + char* line; char *saveptr; + const char* newl = "\n"; + + /* lock */ + flock(cache_file, LOCK_EX); + + int nif = -2; + for (line = strtok_r(buffer, newl, &saveptr); line; line = strtok_r(NULL, newl, &saveptr)) { + // Skip the header lines + if (nif ++ < 0) { continue; } + + char if_id[64]; + uint_fast64_t r_bytes, r_packets, r_errs, r_drop, r_fifo, r_frame, r_compressed, r_multicast; + uint_fast64_t t_bytes, t_packets, t_errs, t_drop, t_fifo, t_frame, t_compressed, t_multicast; + sscanf(line, "%s" + " " + "%llu %llu %llu %llu %llu %llu %llu %llu" + " " + "%llu %llu %llu %llu %llu %llu %llu %llu" + , if_id + , &r_bytes, &r_packets, &r_errs, &r_drop, &r_fifo, &r_frame, &r_compressed, &r_multicast + , &t_bytes, &t_packets, &t_errs, &t_drop, &t_fifo, &t_frame, &t_compressed, &t_multicast + ); + + // Remove trailing ':' of if_id + if_id[strlen(if_id) - 1] = '\0'; + + char out_buffer[1024]; + sprintf(out_buffer, + "multigraph if_%s_1sec" "\n" + "up.value %ld:%llu" "\n" + "down.value %ld:%llu" "\n" + , if_id + , epoch, r_bytes + , epoch, t_bytes + ); + + write(cache_file, out_buffer, strlen(out_buffer)); + } + + /* unlock */ + flock(cache_file, LOCK_UN); + } + + close(cache_file); + close(f); + + return 0; +} + +int fetch() { + FILE* cache_file = fopen(cache_filename, "r+"); + + /* lock */ + flock(fileno(cache_file), LOCK_EX); + + /* cat the cache_file to stdout */ + char buffer[1024]; + while (fgets(buffer, 1024, cache_file)) { + printf("%s", buffer); + } + + ftruncate(fileno(cache_file), 0); + fclose(cache_file); + + return 0; +} + +int main(int argc, char **argv) { + /* resolve paths */ + char *MUNIN_PLUGSTATE = getenv("MUNIN_PLUGSTATE"); + + /* Default is current directory */ + if (! MUNIN_PLUGSTATE) MUNIN_PLUGSTATE = "."; + + size_t MUNIN_PLUGSTATE_length = strlen(MUNIN_PLUGSTATE); + + pid_filename = malloc(MUNIN_PLUGSTATE_length + strlen("/" PLUGIN_NAME ".") + strlen("pid") + 1); pid_filename[0] = '\0'; + cache_filename = malloc(MUNIN_PLUGSTATE_length + strlen("/" PLUGIN_NAME ".") + strlen("value") + 1); cache_filename[0] = '\0'; + + strcat(pid_filename, MUNIN_PLUGSTATE); + strcat(pid_filename, "/" PLUGIN_NAME "." "pid"); + + strcat(cache_filename, MUNIN_PLUGSTATE); + strcat(cache_filename, "/" PLUGIN_NAME "." "value"); + + if (argc > 1) { + char* first_arg = argv[1]; + if (! strcmp(first_arg, "config")) { + return config(); + } + + if (! strcmp(first_arg, "acquire")) { + return acquire(); + } + } + + return fetch(); +} + +/***** DEMO + +/proc/net/dev sample + +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + lo: 4364 54 0 0 0 0 0 0 4364 54 0 0 0 0 0 0 + eth0: 3459461624 22016512 0 70 0 0 0 0 3670486138 18117144 0 0 0 0 0 0 + +*****/ From c72251a7f9b9c8117e19db82a7a5da272c959f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Mon, 8 Jun 2015 14:38:11 +0200 Subject: [PATCH 12/73] Not using memory is a warning condition When a daemon fails `-nan` is its memory usage. --- plugins/monit/monit_parser | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/monit/monit_parser b/plugins/monit/monit_parser index 5d34a3a3..1dc5a604 100755 --- a/plugins/monit/monit_parser +++ b/plugins/monit/monit_parser @@ -54,6 +54,7 @@ if len(sys.argv) > 1 and sys.argv[1] == 'config': for process in procs: for stat in procs[process]: print "monit_%s_%s.label %s.%s" % (process, stat, process, stat) + print "monit_%s_%s.warning 1:" sys.exit(0) for process in procs: From 7220fda4ad816012cf0755e2f6437fc5c3db41bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Mon, 8 Jun 2015 17:05:20 +0200 Subject: [PATCH 13/73] print statements need variables --- plugins/monit/monit_parser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/monit/monit_parser b/plugins/monit/monit_parser index 1dc5a604..be6c39c1 100755 --- a/plugins/monit/monit_parser +++ b/plugins/monit/monit_parser @@ -54,7 +54,7 @@ if len(sys.argv) > 1 and sys.argv[1] == 'config': for process in procs: for stat in procs[process]: print "monit_%s_%s.label %s.%s" % (process, stat, process, stat) - print "monit_%s_%s.warning 1:" + print "monit_%s_%s.warning 1:" % (process, stat) sys.exit(0) for process in procs: From 25a1a06a4a0c8329b6eb6b60b438666e2595346d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Mon, 8 Jun 2015 17:10:04 +0200 Subject: [PATCH 14/73] Missing condition --- plugins/monit/monit_parser | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/monit/monit_parser b/plugins/monit/monit_parser index be6c39c1..f89bd4db 100755 --- a/plugins/monit/monit_parser +++ b/plugins/monit/monit_parser @@ -54,7 +54,8 @@ if len(sys.argv) > 1 and sys.argv[1] == 'config': for process in procs: for stat in procs[process]: print "monit_%s_%s.label %s.%s" % (process, stat, process, stat) - print "monit_%s_%s.warning 1:" % (process, stat) + if stat == 'total_memory': + print "monit_%s_%s.warning 1:" % (process, stat) sys.exit(0) for process in procs: From c40eabee7efc6b448244c872ea6c438033ef89de Mon Sep 17 00:00:00 2001 From: Thomas A Date: Thu, 18 Jun 2015 19:37:42 +0200 Subject: [PATCH 15/73] Add snmp__synology_* plugins Synology specific snmp plugins for: - System temperature - HDD temperature - UPS Charge & Load --- plugins/snmp/snmp__synology_hddtemp | 109 ++++++++++++++++++++++++ plugins/snmp/snmp__synology_temperature | 91 ++++++++++++++++++++ plugins/snmp/snmp__synology_ups | 102 ++++++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 plugins/snmp/snmp__synology_hddtemp create mode 100644 plugins/snmp/snmp__synology_temperature create mode 100644 plugins/snmp/snmp__synology_ups diff --git a/plugins/snmp/snmp__synology_hddtemp b/plugins/snmp/snmp__synology_hddtemp new file mode 100644 index 00000000..57931c3d --- /dev/null +++ b/plugins/snmp/snmp__synology_hddtemp @@ -0,0 +1,109 @@ +#!/usr/bin/perl -w +# -*- perl -*- +# vim: ft=perl + +=head1 NAME + +snmp__syno_hddtemp - Munin plugin to monitor the temperature of +harddisks in an Synology NAS. + +=head1 APPLICABLE SYSTEMS + +Any Synology NAS device which provides the synoDisk MIB. + +=head1 CONFIGURATION + +As a rule SNMP plugins need site specific configuration. The default +configuration (shown here) will only work on insecure sites/devices. + + [snmp_*] + env.version 2 + env.community public + +In general SNMP is not very secure at all unless you use SNMP version +3 which supports authentication and privacy (encryption). But in any +case the community string for your devices should not be "public". + +Please see 'perldoc Munin::Plugin::SNMP' for further configuration +information. + +=head1 INTERPRETATION + +The temperature of each disk installed in °C. + +=head1 MIB INFORMATION + +This plugin requires support for the synoDisk. It reports +the temperature of the installed disks. + +=head1 MAGIC MARKERS + + #%# family=snmpauto + #%# capabilities=snmpconf + +=head1 VERSION + + $Id$ + +=head1 BUGS + +None known. + +=head1 AUTHOR + +Copyright (C) 2015 Thomas Arthofer + +This plugin was derived from snmp__netstat by Lars Strand with updates +by Matthew Boyle. + +=head1 LICENSE + +GPLv2. + +=cut + +use strict; +use Munin::Plugin::SNMP; + +my $oid_drives = '1.3.6.1.4.1.6574.2.1.1'; + +if (defined $ARGV[0] and $ARGV[0] eq 'snmpconf') { + print "require ${oid_drives}. [0-9]\n"; + exit 0; +} + +my ($session, $error) = Munin::Plugin::SNMP->session(); + +my $table = $session->get_hash( + -baseoid => $oid_drives, # IF-MIB + -cols => { + 2 => 'name', + 3 => 'type', + 6 => 'temp', + } + ); + +if (defined $ARGV[0] and $ARGV[0] eq 'config') { + my ($host) = Munin::Plugin::SNMP->config_session(); + + print "host_name $host\n" unless $host eq 'localhost'; + print "graph_title HDD temperature\n"; + print "graph_category sensors\n"; + print "graph_vlabel Degrees Celsius\n"; + print "graph_info This graph shows the temperature of all HDDs in the Diskstation.\n"; + + foreach my $key ( sort keys %$table ) + { + print "temp$key.label $table->{$key}->{'name'}\n"; + print "temp$key.info Temperature of $table->{$key}->{'name'} ($table->{$key}->{'type'})\n"; + } + exit 0; +} + + +my $names = $session->get_entries(-columns => [ $oid_drives ]); + +foreach my $key ( sort keys %$table ) +{ + print "temp$key.value $table->{$key}->{'temp'}\n"; +} diff --git a/plugins/snmp/snmp__synology_temperature b/plugins/snmp/snmp__synology_temperature new file mode 100644 index 00000000..522dfa75 --- /dev/null +++ b/plugins/snmp/snmp__synology_temperature @@ -0,0 +1,91 @@ +#!/usr/bin/perl -w +# -*- cperl -*- +# vim: ft=perl + +=head1 NAME + +snmp__syno_temperature - Munin plugin to retrieve current temperature from a +Synology NAS. + +=head1 APPLICABLE SYSTEMS + +Any Synology NAS device which provides the synoSystem MIB. + +=head1 CONFIGURATION + +As a rule SNMP plugins need site specific configuration. The default +configuration (shown here) will only work on insecure sites/devices. + + [snmp_*] + env.version 2 + env.community public + +In general SNMP is not very secure at all unless you use SNMP version +3 which supports authentication and privacy (encryption). But in any +case the community string for your devices should not be "public". + +Please see 'perldoc Munin::Plugin::SNMP' for further configuration +information. + +=head1 INTERPRETATION + +This plugin queries the current temperature of the NAS. + +=head1 MIB INFORMATION + +This plugin requires support for the synoSystem MIB by Synology. +It reports the contents of the temperature OID. + +=head1 MAGIC MARKERS + + #%# family=snmpauto + #%# capabilities=snmpconf + +=head1 VERSION + + $Id$ + +=head1 BUGS + +None known. + +=head1 AUTHOR + +Copyright (C) 2015 Thomas Arthofer + +=head1 LICENSE + +GPLv2 or (at your option) any later version. + +=cut + +use strict; +use Munin::Plugin::SNMP; + +if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") { + print "require 1.3.6.1.4.1.6574.1.2.0 [0-9]\n"; # Number + exit 0; +} + +if (defined $ARGV[0] and $ARGV[0] eq "config") { + my ($host) = Munin::Plugin::SNMP->config_session(); + print "host_name $host\n" unless $host eq 'localhost'; + print "graph_title Temperatures +graph_args --base 1000 -l 0 +graph_vlabel Degrees Celsius +graph_category sensors +graph_info This graph shows the temperature of the diskstation. +temp.label CPU +temp.info The temperature of the onboard CPU. +"; + exit 0; +} + +my $session = Munin::Plugin::SNMP->session(-translate => + [ -timeticks => 0x0 ]); + +my $temp = $session->get_single (".1.3.6.1.4.1.6574.1.2.0") || 'ERROR'; + +print "Retrived uptime is '$temp'\n" if $Munin::Plugin::SNMP::DEBUG; + +print "temp.value ", $temp, "\n"; diff --git a/plugins/snmp/snmp__synology_ups b/plugins/snmp/snmp__synology_ups new file mode 100644 index 00000000..2ceea9e5 --- /dev/null +++ b/plugins/snmp/snmp__synology_ups @@ -0,0 +1,102 @@ +#!/usr/bin/perl -w +# -*- cperl -*- +# vim: ft=perl + +=head1 NAME + +snmp__syno_ups - Munin plugin to retrieve various information of the +UPS attached to a Synology NAS. + +=head1 APPLICABLE SYSTEMS + +Any Synology NAS device which provides the synoUPS MIB. + +=head1 CONFIGURATION + +As a rule SNMP plugins need site specific configuration. The default +configuration (shown here) will only work on insecure sites/devices. + + [snmp_*] + env.version 2 + env.community public + +In general SNMP is not very secure at all unless you use SNMP version +3 which supports authentication and privacy (encryption). But in any +case the community string for your devices should not be "public". + +Please see 'perldoc Munin::Plugin::SNMP' for further configuration +information. + +=head1 INTERPRETATION + +The plugin reports the following stats about the UPS attached: + - Load in % + - Charge in % + +=head1 MIB INFORMATION + +This plugin requires support for the synoUPS MIB by Synology. + +=head1 MAGIC MARKERS + + #%# family=snmpauto + #%# capabilities=snmpconf + +=head1 VERSION + + $Id$ + +=head1 BUGS + +None known. + +=head1 AUTHOR + +Copyright (C) 2015 Thomas Arthofer + +=head1 LICENSE + +GPLv2 or (at your option) any later version. + +=cut + +use strict; +use Munin::Plugin::SNMP; + +if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") { + print "require 1.3.6.1.4.1.6574.4.3.1.1.0 [0-9]\n"; # Charge + print "require 1.3.6.1.4.1.6574.4.2.12.1.0 [0-9]\n"; # Load + exit 0; +} + +if (defined $ARGV[0] and $ARGV[0] eq "config") { + my ($host) = Munin::Plugin::SNMP->config_session(); + print "host_name $host\n" unless $host eq 'localhost'; + print "graph_title UPS +graph_args --base 1000 -l 0 +graph_vlabel Status of UPS +graph_category system +graph_info This graph shows the status of the attached UPS. +charge.label Charge +charge.info Charge status of battery. +charge.draw LINE2 +load.label Load +load.info Load on the UPS +"; + exit 0; +} + +my $session = Munin::Plugin::SNMP->session(-translate => + [ -timeticks => 0x0 ]); + +my $charge = $session->get_single (".1.3.6.1.4.1.6574.4.3.1.1.0") || 'ERROR'; +$charge = unpack "f", reverse pack "H*", $charge; + +my $load = $session->get_single (".1.3.6.1.4.1.6574.4.2.12.1.0") || 'ERROR'; +$load = unpack "f", reverse pack "H*", $load; + +print "Retrived charge '$charge'\n" if $Munin::Plugin::SNMP::DEBUG; +print "Retrived load '$load'\n" if $Munin::Plugin::SNMP::DEBUG; + +print "charge.value ", $charge, "\n"; +print "load.value ", $load, "\n"; From 89e9cdf1edd3b5422a565a15d37bba4b517fc969 Mon Sep 17 00:00:00 2001 From: Alexandre Date: Fri, 10 Jul 2015 15:37:19 +0200 Subject: [PATCH 16/73] Update proftpd_bytes Fixed wrong/old path for xferlog --- plugins/ftp/proftpd_bytes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ftp/proftpd_bytes b/plugins/ftp/proftpd_bytes index e1cdb31d..85d3ef39 100755 --- a/plugins/ftp/proftpd_bytes +++ b/plugins/ftp/proftpd_bytes @@ -20,7 +20,7 @@ mktempfile () { mktemp -t $1 } -LOGFILE=${logfile:-/var/log/xferlog} +LOGFILE=${logfile:-/var/log/proftpd/xferlog} LOGTAIL=${logtail:-`which logtail`} STATEFILE=/var/lib/munin/plugin-state/xferlog-bytes.offset From 3adc1590e6492571868a76d3550a40afb8ca9520 Mon Sep 17 00:00:00 2001 From: Alexandre Date: Fri, 10 Jul 2015 15:38:38 +0200 Subject: [PATCH 17/73] Update proftpd_count Fixed wrong/old path for xferlog --- plugins/ftp/proftpd_count | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ftp/proftpd_count b/plugins/ftp/proftpd_count index 44282167..aa9c9d3f 100755 --- a/plugins/ftp/proftpd_count +++ b/plugins/ftp/proftpd_count @@ -20,7 +20,7 @@ mktempfile () { mktemp -t $1 } -LOGFILE=${logfile:-/var/log/xferlog} +LOGFILE=${logfile:-/var/log/proftpd/xferlog} LOGTAIL=${logtail:-`which logtail`} STATEFILE=/var/lib/munin/plugin-state/xferlog-count.offset From 13b6ca382e29118610ebc43052cdd68e60a2c84f Mon Sep 17 00:00:00 2001 From: "Andrey (suse24)" Date: Sun, 12 Jul 2015 23:23:16 +0300 Subject: [PATCH 18/73] redis plugin: add unixsocket feature --- plugins/redis/redis_ | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/plugins/redis/redis_ b/plugins/redis/redis_ index d2550b1b..2d61ca9d 100755 --- a/plugins/redis/redis_ +++ b/plugins/redis/redis_ @@ -35,9 +35,11 @@ use strict; use IO::Socket::INET; +use IO::Socket::UNIX; use Switch; my $HOST = exists $ENV{'host'} ? $ENV{'host'} : "127.0.0.1"; +my $UNIX_SOCKET = exists $ENV{'unixsocket'} ? $ENV{'unixsocket'} : ''; # path to Redis Unix sock file my $PORT = exists $ENV{'port'} ? $ENV{'port'} : 6379; my $PASSWORD = exists $ENV{'password'} ? $ENV{'password'} : undef; my $TITLE_PREFIX = exists $ENV{'title_prefix'} ? $ENV{'title_prefix'} . ": " : ""; @@ -208,12 +210,26 @@ switch ($0) { close ($sock); sub get_conn { - my $sock = IO::Socket::INET->new( - PeerAddr => $HOST, - PeerPort => $PORT, - Timeout => 10, - Proto => 'tcp' - ); + + my $sock; + + if( $UNIX_SOCKET && -S $UNIX_SOCKET ){ + + $sock = IO::Socket::UNIX->new( + Type => SOCK_STREAM(), + Peer => $UNIX_SOCKET, + ); + + }else{ + + $sock = IO::Socket::INET->new( + PeerAddr => $HOST, + PeerPort => $PORT, + Timeout => 10, + Proto => 'tcp' + ); + } + if ( defined( $PASSWORD ) ) { print $sock "AUTH ", $PASSWORD, "\r\n"; my $result = <$sock> || die "can't read socket: $!"; From fb4914f77d525a6d1ba4a4e4ac4ceabff5ab2905 Mon Sep 17 00:00:00 2001 From: "Andrey (suse24)" Date: Mon, 13 Jul 2015 01:38:54 +0300 Subject: [PATCH 19/73] rewrite memcached family plugins: bytes_, connections_, hits_, items_, requests_, traffic_ for unix socket usage --- plugins/memcached_ext/memcached_ext_bytes_ | 57 ++++++++++++++++++ .../memcached_ext/memcached_ext_connections_ | 53 ++++++++++++++++ plugins/memcached_ext/memcached_ext_hits_ | 60 +++++++++++++++++++ plugins/memcached_ext/memcached_ext_items_ | 53 ++++++++++++++++ plugins/memcached_ext/memcached_ext_requests_ | 59 ++++++++++++++++++ plugins/memcached_ext/memcached_ext_traffic_ | 60 +++++++++++++++++++ 6 files changed, 342 insertions(+) create mode 100755 plugins/memcached_ext/memcached_ext_bytes_ create mode 100755 plugins/memcached_ext/memcached_ext_connections_ create mode 100755 plugins/memcached_ext/memcached_ext_hits_ create mode 100755 plugins/memcached_ext/memcached_ext_items_ create mode 100755 plugins/memcached_ext/memcached_ext_requests_ create mode 100755 plugins/memcached_ext/memcached_ext_traffic_ diff --git a/plugins/memcached_ext/memcached_ext_bytes_ b/plugins/memcached_ext/memcached_ext_bytes_ new file mode 100755 index 00000000..10642a94 --- /dev/null +++ b/plugins/memcached_ext/memcached_ext_bytes_ @@ -0,0 +1,57 @@ +#!/usr/bin/env perl +# ex:ts=4 + +use strict; +use warnings; + +use Cache::Memcached; + +# Based on original plugin, extended to unix socket use +# https://github.com/western, westroads@gmail.com + +=head1 example config for /plugin-conf.d/munin-node + +[memcached_bytes_1] +env.server 127.0.0.1:11211 +env.label "first local server" + +[memcached_bytes_2] +env.server /var/run/memcached/memcached.sock +env.label "second local server" + +=cut + +my $label = exists $ENV{'label'} ? $ENV{'label'} : ''; +unless( $label ){ + + if( $0 =~ /memcached_ext_bytes_([\d\w]+)$/ ){ + $label = $1; + } +} + +my $cmd = shift || ''; +if ($cmd eq 'config') { + print "graph_title Memcached bytes used on $label\n"; + print "graph_args --base 1024 -l 0\n"; + print "graph_vlabel bytes\n"; + print "graph_category memcached\n"; + print "graph_info This graph monitors the size of the memcached cache.\n"; + print "bytes.label bytes used\n"; + print "bytes.info Number of bytes currently used\n"; + print "bytes.min 0\n"; + print "bytes.draw AREA\n"; + print "maxbytes.label maximum available\n"; + print "maxbytes.info The configured cache size\n"; + print "maxbytes.min 0\n"; + exit 0; +} + + +my $server = exists $ENV{'server'} ? $ENV{'server'} : '127.0.0.1:11211'; + +my $memd = new Cache::Memcached { 'servers' => [$server] }; +my $memstats = $memd->stats(['misc']); + +print "bytes.value " . $memstats->{hosts}->{$server}->{misc}->{bytes} . "\n"; +print "maxbytes.value " . + $memstats->{hosts}->{$server}->{misc}->{limit_maxbytes} . "\n"; diff --git a/plugins/memcached_ext/memcached_ext_connections_ b/plugins/memcached_ext/memcached_ext_connections_ new file mode 100755 index 00000000..57d1be7c --- /dev/null +++ b/plugins/memcached_ext/memcached_ext_connections_ @@ -0,0 +1,53 @@ +#!/usr/bin/env perl +# ex:ts=4 + +use strict; +use warnings; + +use Cache::Memcached; + +# Based on original plugin, extended to unix socket use +# https://github.com/western, westroads@gmail.com + +=head1 example config for /plugin-conf.d/munin-node + +[memcached_connections_1] +env.server 127.0.0.1:11211 +env.label "first local server" + +[memcached_connections_2] +env.server /var/run/memcached/memcached.sock +env.label "second local server" + +=cut + +my $label = exists $ENV{'label'} ? $ENV{'label'} : ''; +unless( $label ){ + + if( $0 =~ /memcached_ext_connections_([\w\d]+)$/ ){ + $label = $1; + } +} + +my $cmd = shift || ''; +if ($cmd eq 'config') { + print "graph_title Memcached connections on $label\n"; + print "graph_args --base 1000 -l 0\n"; + print "graph_vlabel connections\n"; + print "graph_category memcached\n"; + print "graph_info This graph monitors the connections to the memcached server.\n"; + print "connections.label connections\n"; + print "connections.info Number of connections to memcached\n"; + print "connections.min 0\n"; + print "connections.draw AREA\n"; + exit 0; +} + + +my $server = exists $ENV{'server'} ? $ENV{'server'} : '127.0.0.1:11211'; + +my $memd = new Cache::Memcached { 'servers' => [$server] }; +my $memstats = $memd->stats(['misc']); + +print "connections.value " . + $memstats->{hosts}->{$server}->{misc}->{curr_connections} . "\n"; diff --git a/plugins/memcached_ext/memcached_ext_hits_ b/plugins/memcached_ext/memcached_ext_hits_ new file mode 100755 index 00000000..d2a7724d --- /dev/null +++ b/plugins/memcached_ext/memcached_ext_hits_ @@ -0,0 +1,60 @@ +#!/usr/bin/env perl +# ex:ts=4 + +use strict; +use warnings; + +use Cache::Memcached; + +# Based on original plugin, extended to unix socket use +# https://github.com/western, westroads@gmail.com + +=head1 example config for /plugin-conf.d/munin-node + +[memcached_hits_1] +env.server 127.0.0.1:11211 +env.label "first local server" + +[memcached_hits_2] +env.server /var/run/memcached/memcached.sock +env.label "second local server" + +=cut + +my $label = exists $ENV{'label'} ? $ENV{'label'} : ''; +unless( $label ){ + + if( $0 =~ /memcached_ext_hits_([\w\d]+)$/ ){ + $label = $1; + } +} + + + +my $cmd = shift || ''; +if ($cmd eq 'config') { + print "graph_title Memcached cache hits and misses on $label\n"; + print "graph_args --base 1000 -l 0\n"; + print "graph_vlabel requests\n"; + print "graph_category memcached\n"; + print "graph_info This graph monitors the number of cache hits and misses.\n"; + print "hits.label hits\n"; + print "hits.info Number of cache hits\n"; + print "hits.min 0\n"; + print "hits.type DERIVE\n"; + print "misses.label misses\n"; + print "misses.info Number of cache misses\n"; + print "misses.min 0\n"; + print "misses.type DERIVE\n"; + print "misses.draw AREA\n"; + exit 0; +} + +my $server = exists $ENV{'server'} ? $ENV{'server'} : '127.0.0.1:11211'; + +my $memd = new Cache::Memcached { 'servers' => [$server] }; +my $memstats = $memd->stats(['misc']); + +print "hits.value " . $memstats->{hosts}->{$server}->{misc}->{get_hits} . "\n"; +print "misses.value " . + $memstats->{hosts}->{$server}->{misc}->{get_misses} . "\n"; diff --git a/plugins/memcached_ext/memcached_ext_items_ b/plugins/memcached_ext/memcached_ext_items_ new file mode 100755 index 00000000..39b56503 --- /dev/null +++ b/plugins/memcached_ext/memcached_ext_items_ @@ -0,0 +1,53 @@ +#!/usr/bin/env perl +# ex:ts=4 + +use strict; +use warnings; + +use Cache::Memcached; + +# Based on original plugin, extended to unix socket use +# https://github.com/western, westroads@gmail.com + +=head1 example config for /plugin-conf.d/munin-node + +[memcached_items_1] +env.server 127.0.0.1:11211 +env.label "first local server" + +[memcached_items_2] +env.server /var/run/memcached/memcached.sock +env.label "second local server" + +=cut + +my $label = exists $ENV{'label'} ? $ENV{'label'} : ''; +unless( $label ){ + + if( $0 =~ /memcached_ext_items_([\w\d]+)$/ ){ + $label = $1; + } +} + + +my $cmd = shift || ''; +if ($cmd eq 'config') { + print "graph_title Memcached cached items on $label\n"; + print "graph_args --base 1000 -l 0\n"; + print "graph_vlabel items\n"; + print "graph_category memcached\n"; + print "graph_info This graph monitors the number of items stored by the memcached server.\n"; + print "items.label items\n"; + print "items.info Number of cached items\n"; + print "items.min 0\n"; + print "items.draw AREA\n"; + exit 0; +} + +my $server = exists $ENV{'server'} ? $ENV{'server'} : '127.0.0.1:11211'; + +my $memd = new Cache::Memcached { 'servers' => [$server] }; +my $memstats = $memd->stats(['misc']); + +print "items.value " . + $memstats->{hosts}->{$server}->{misc}->{curr_items} . "\n"; diff --git a/plugins/memcached_ext/memcached_ext_requests_ b/plugins/memcached_ext/memcached_ext_requests_ new file mode 100755 index 00000000..b7b75fce --- /dev/null +++ b/plugins/memcached_ext/memcached_ext_requests_ @@ -0,0 +1,59 @@ +#!/usr/bin/env perl +# ex:ts=4 + +use strict; +use warnings; + +use Cache::Memcached; + +# Based on original plugin, extended to unix socket use +# https://github.com/western, westroads@gmail.com + +=head1 example config for /plugin-conf.d/munin-node + +[memcached_requests_1] +env.server 127.0.0.1:11211 +env.label "first local server" + +[memcached_requests_2] +env.server /var/run/memcached/memcached.sock +env.label "second local server" + +=cut + +my $label = exists $ENV{'label'} ? $ENV{'label'} : ''; +unless( $label ){ + + if( $0 =~ /memcached_ext_requests_([\w\d]+)$/ ){ + $label = $1; + } +} + + +my $cmd = shift || ''; +if ($cmd eq 'config') { + print "graph_title Memcached requests on $label\n"; + print "graph_args --base 1000 -l 0\n"; + print "graph_vlabel requests\n"; + print "graph_category memcached\n"; + print "graph_info This graph monitors the number of get and set requests.\n"; + print "gets.label gets\n"; + print "gets.info Number of get requests\n"; + print "gets.min 0\n"; + print "gets.type DERIVE\n"; + print "gets.draw AREA\n"; + print "sets.label sets\n"; + print "sets.info Number of set requests\n"; + print "sets.min 0\n"; + print "sets.type DERIVE\n"; + print "sets.draw STACK\n"; + exit 0; +} + +my $server = exists $ENV{'server'} ? $ENV{'server'} : '127.0.0.1:11211'; + +my $memd = new Cache::Memcached { 'servers' => [$server] }; +my $memstats = $memd->stats(['misc']); + +print "gets.value " . $memstats->{hosts}->{$server}->{misc}->{cmd_get} . "\n"; +print "sets.value " . $memstats->{hosts}->{$server}->{misc}->{cmd_set} . "\n"; diff --git a/plugins/memcached_ext/memcached_ext_traffic_ b/plugins/memcached_ext/memcached_ext_traffic_ new file mode 100755 index 00000000..640efc43 --- /dev/null +++ b/plugins/memcached_ext/memcached_ext_traffic_ @@ -0,0 +1,60 @@ +#!/usr/bin/env perl +# ex:ts=4 + +use strict; +use warnings; + +use Cache::Memcached; + +# Based on original plugin, extended to unix socket use +# https://github.com/western, westroads@gmail.com + +=head1 example config for /plugin-conf.d/munin-node + +[memcached_traffic_1] +env.server 127.0.0.1:11211 +env.label "first local server" + +[memcached_traffic_2] +env.server /var/run/memcached/memcached.sock +env.label "second local server" + +=cut + +my $label = exists $ENV{'label'} ? $ENV{'label'} : ''; +unless( $label ){ + + if( $0 =~ /memcached_ext_traffic_([\w\d]+)$/ ){ + $label = $1; + } +} + + +my $cmd = shift || ''; +if ($cmd eq 'config') { + print "graph_title Memcached network traffic on $label\n"; + print "graph_args --base 1000 -l 0\n"; + print "graph_vlabel bits per \${graph_period}\n"; + print "graph_category memcached\n"; + print "graph_info This graph monitors the network traffic of the memcached server.\n"; + print "up.label bits in\n"; + print "up.info Traffic received by memcached\n"; + print "up.min 0\n"; + print "up.cdef up,8,*\n"; + print "up.type COUNTER\n"; + print "up.draw AREA\n"; + print "down.label bits out\n"; + print "down.info Traffic sent by memcached\n"; + print "down.min 0\n"; + print "down.cdef down,8,*\n"; + print "down.type COUNTER\n"; + exit 0; +} + +my $server = exists $ENV{'server'} ? $ENV{'server'} : '127.0.0.1:11211'; + +my $memd = new Cache::Memcached { 'servers' => [$server] }; +my $memstats = $memd->stats(['misc']); + +print "up.value " . $memstats->{hosts}->{$server}->{misc}->{bytes_read} . "\n"; +print "down.value " . $memstats->{hosts}->{$server}->{misc}->{bytes_written} . "\n"; From 338001fb6a5be4459cfe1b2af6cde77846867721 Mon Sep 17 00:00:00 2001 From: ak4t0sh Date: Fri, 3 Jul 2015 21:55:38 +0200 Subject: [PATCH 20/73] file_size graph : useless graph_scale removal --- plugins/moodle/moodle_files.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/moodle/moodle_files.php b/plugins/moodle/moodle_files.php index cdf6c978..13a9a31e 100644 --- a/plugins/moodle/moodle_files.php +++ b/plugins/moodle/moodle_files.php @@ -81,7 +81,6 @@ if (count($argv) === 2 && $argv[1] === 'config') { echo "graph_args --base 1024 --lower-limit 0\n"; echo "graph_vlabel size\n"; echo "graph_category Moodle\n"; - echo "graph_scale no\n"; echo "graph_total total\n"; echo "graph_info Displays the total size of moodle users files and repartition by type\n"; @@ -119,7 +118,6 @@ echo "graph_title Moodle Files Size\n"; echo "graph_args --base 1024 --lower-limit 0\n"; echo "graph_vlabel size\n"; echo "graph_category Moodle\n"; -echo "graph_scale yes\n"; echo "graph_total total\n"; echo "graph_info Displays the total size of moodle users files and repartition by type\n"; From 5df0724eecc2492de00ac1a8e1b123231f2c2d38 Mon Sep 17 00:00:00 2001 From: vkh78 Date: Sat, 1 Aug 2015 11:34:05 +0200 Subject: [PATCH 21/73] Add openntp_offset plugin. Add a plugin that, like ntp_offset plugin, measures offset, delay and jitter for the active OpenNTPd peer. --- plugins/openntpd/openntp_offset | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100755 plugins/openntpd/openntp_offset diff --git a/plugins/openntpd/openntp_offset b/plugins/openntpd/openntp_offset new file mode 100755 index 00000000..dbd2c1a4 --- /dev/null +++ b/plugins/openntpd/openntp_offset @@ -0,0 +1,44 @@ +#!/bin/sh +# +# Plugin to measure OpenNTPd offset, delay and jitter for the active peer. +# +# Usage: Place it in the munin plugins directory (/usr/local/etc/munin/plugins/ +# or /etc/munin/plugins/). Might require running as root in order to access the +# ntpctl socket. +# +# AUTHOR: Vladimir Krstulja +# LICENSE: GPLv2 +# +#%# family=auto +#%# capabilities=autoconf + +NTPCTL=`which ntpctl` + +# Config +if [ "$1" = "autoconf" ] ; then + if [ -f "$NTPCTL" ]; then + echo 'yes' + else + echo 'no (no ntpctl)' + fi + exit 0 +fi + +# Autoconf +if [ "$1" = "config" ] ; then + echo "graph_title OpenNTP offset statistics for active peer" + echo "graph_args --base 1000 --vertical-label seconds --lower-limit 0" + echo "graph_category time" + echo "graph_info Current status: `$NTPCTL -s status`" + echo "delay.label Delay" + echo "delay.cdef delay,1000,/" + echo "offset.label Offset" + echo "offset.cdef offset,1000,/" + echo "jitter.label Jitter" + echo "jitter.cdef jitter,1000,/" + exit 0 +fi + +# Main plugin function +$NTPCTL -s all | awk '/\*/{printf("offset.value %.3f\ndelay.value %.3f\njitter.value %.3f\n",$7,$8,$9)}' + From b751e8eaeb64e5df20e7e752fe4036a7ea214f67 Mon Sep 17 00:00:00 2001 From: Stig Sandbeck Mathisen Date: Sat, 29 Aug 2015 14:41:43 +0200 Subject: [PATCH 22/73] Add apt/acng for graphing Apt-Cacher NG --- plugins/apt/acng | 196 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100755 plugins/apt/acng diff --git a/plugins/apt/acng b/plugins/apt/acng new file mode 100755 index 00000000..03f9f9d8 --- /dev/null +++ b/plugins/apt/acng @@ -0,0 +1,196 @@ +#!/usr/bin/perl + +=head1 NAME + +acng - Graph activity for Apt-Cacher NG, request count and bytes + +=head1 APPLICABLE SYSTEMS + +Systems with "Apt-Cacher NG" installed and running. + +=head1 DESCRIPTION + +This plugin will add graphs for "bytes in and out" and "requests in +and out" for systems with "Apt-Cacher NG" installed. + +=head1 CONFIGURATION + +The plugin must have permission to read the log of Apt-Cacher NG. (On +Debian 8, this file is world readable by default). + +The path to the logfile can be set with the "logfile" environment +variable. + +=head2 DEFAULT CONFIGURATION + + [acng] + env.logfile /var/log/apt-cacher-ng/apt-cacher.log + +=head1 USAGE + +Link this plugin to /etc/munin/plugins/ and restart the munin-node. + +=head1 MAGIC MARKERS + + #%# family=contrib + #%# capabilities=autoconf + +=head1 AUTHOR + +Stig Sandbeck Mathisen + +=head1 LICENSE + +GPLv3 + +=cut + +use strict; +use warnings; +use Munin::Plugin; + +use Storable qw(nfreeze thaw); +use MIME::Base64; + +my $logfile = $ENV{'logfile'} ||= '/var/log/apt-cacher-ng/apt-cacher.log'; + +need_multigraph; + +# Read or initialize state used by the log tailer, and the plugin. +sub read_state { + + my ($pos, $statsin) = restore_state; + my $stats = thaw(decode_base64 $statsin) if $statsin; + + $pos = 0 unless defined $pos; + $stats = {} unless defined $stats; + + return ($pos, $stats); +} + +# Write state. +# +# "pos" is logfile position, and "stats" is a data structure with +# counters used by the plugin. +# +# Note: Munin::Plugin::save_state has limited functionality, so the +# data structure is serialized and converted to plain text. +sub write_state { + my ($pos, $stats) = @_; + + my $statsout = encode_base64 nfreeze($stats); + save_state($pos, $statsout); +} + +sub parse_logfile { + my $logfile = shift; + my ($pos, $stats) = read_state; + + my @keys = ( 'time', 'direction', 'size', 'client', 'file' ); + + # Open log + my ( $fh, $reset ) = tail_open( $logfile, $pos ); + + die "Unable to open logfile\n" unless ($fh); + + while (<$fh>) { + chomp; + my @values = split( /\|/, $_ ); + + my %logentry; + @logentry{@keys} = @values; + + $stats->{'bytes'}{ $logentry{'direction'} } += $logentry{'size'}; + $stats->{'requests'}{ $logentry{'direction'} }++; + } + + # Close log + $pos = tail_close($fh); + + write_state($pos, $stats); + + return $stats; +} + +sub print_autoconf{ + my $logfile = shift; + if ( open(my $fh, '<', $logfile) ) { + print "yes\n"; + } + else { + printf "no (could not open %s)\n", $logfile; + } +} + +sub print_config{ + my $stats = shift; + + print << 'EOC'; +multigraph acng_bytes +graph_title Apt-Cacher NG bytes +graph_order origin client +graph_vlabel bytes per ${graph_period} +graph_info Bytes transferred between origin, apt-cacher-ng and clients +origin.info bytes transferred between origin and apt-cacher-ng +origin.label origin +origin.type DERIVE +origin.min 0 +origin.info bytes transferred between apt-cacher-ng and clients +client.label client +client.type DERIVE +client.min 0 +EOC + print << "EOV" if $ENV{'MUNIN_CAP_DIRTYCONFIG'}; +origin.value $stats->{bytes}{I} +client.value $stats->{bytes}{O} +EOV + + print << 'EOC'; + +multigraph acng_requests +graph_title Apt-Cacher NG requests +graph_order origin client +graph_vlabel requests per ${graph_period} +graph_info Requests from clients to apt-cacher-ng, and from apt-cacher-ng to origin +origin.info requests from apt-cacher-ng to origin +origin.label origin +origin.type DERIVE +origin.min 0 +origin.info requests from clients to apt-cacher-ng +client.label client +client.type DERIVE +client.min 0 +EOC + + print << "EOV" if $ENV{'MUNIN_CAP_DIRTYCONFIG'}; +origin.value $stats->{requests}{I} +client.value $stats->{requests}{O} +EOV + +} + +sub print_values{ + my $stats = shift; + + print << "EOV"; +multigraph acng_bytes +origin.value $stats->{bytes}{I} +client.value $stats->{bytes}{O} + +multigraph acng_requests +origin.value $stats->{requests}{I} +client.value $stats->{requests}{O} +EOV +} + +if ($ARGV[0] and $ARGV[0] eq 'autoconf') { + print_autoconf($logfile); +} +elsif ($ARGV[0] and $ARGV[0] eq 'config') { + my $stats = parse_logfile($logfile); + print_config($stats); +} +else { + my $stats = parse_logfile($logfile); + print_values($stats); +} From 100dc35e98aa4b995ea43f2342bfbbc21c166714 Mon Sep 17 00:00:00 2001 From: Stig Sandbeck Mathisen Date: Sat, 29 Aug 2015 15:15:24 +0200 Subject: [PATCH 23/73] Add category "time" to ntpdate_ plugin This closes #633 --- plugins/time/ntpdate_ | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/time/ntpdate_ b/plugins/time/ntpdate_ index 34778c80..bb62d5bd 100755 --- a/plugins/time/ntpdate_ +++ b/plugins/time/ntpdate_ @@ -30,6 +30,7 @@ fi if [ "$1" = "config" ]; then echo "graph_title NTP offset and delay to peer $PEER" + echo "graph_category time" echo "graph_args --base 1000 --vertical-label msec" echo "offset.label Offset" echo "offset.draw LINE2" From 08479bda7c145d1dfd97833f60d87cb23f5ba04c Mon Sep 17 00:00:00 2001 From: Stig Sandbeck Mathisen Date: Sat, 29 Aug 2015 17:08:42 +0200 Subject: [PATCH 24/73] fix wrong label --- plugins/apt/acng | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/apt/acng b/plugins/apt/acng index 03f9f9d8..fb63fa43 100755 --- a/plugins/apt/acng +++ b/plugins/apt/acng @@ -135,7 +135,7 @@ origin.info bytes transferred between origin and apt-cacher-ng origin.label origin origin.type DERIVE origin.min 0 -origin.info bytes transferred between apt-cacher-ng and clients +client.info bytes transferred between apt-cacher-ng and clients client.label client client.type DERIVE client.min 0 @@ -156,7 +156,7 @@ origin.info requests from apt-cacher-ng to origin origin.label origin origin.type DERIVE origin.min 0 -origin.info requests from clients to apt-cacher-ng +client.info requests from clients to apt-cacher-ng client.label client client.type DERIVE client.min 0 From 70b61f16210a6fd6f4a3c80dfcc0e13eb927b017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20S=C3=89RIE?= Date: Mon, 31 Aug 2015 18:15:40 +0200 Subject: [PATCH 25/73] Support for Debian Jessie. --- plugins/virtualization/kvm_cpu | 2 +- plugins/virtualization/kvm_io | 2 +- plugins/virtualization/kvm_mem | 2 +- plugins/virtualization/kvm_net | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/virtualization/kvm_cpu b/plugins/virtualization/kvm_cpu index 2088b093..d8ec7934 100755 --- a/plugins/virtualization/kvm_cpu +++ b/plugins/virtualization/kvm_cpu @@ -75,7 +75,7 @@ def list_pids(): ''' Find the pid of kvm processes @return a list of pids from running kvm ''' - pid = Popen("pidof kvm", shell=True, stdout=PIPE) + pid = Popen("pidof qemu-system-x86_64", shell=True, stdout=PIPE) return pid.communicate()[0].split() def fetch(vms): diff --git a/plugins/virtualization/kvm_io b/plugins/virtualization/kvm_io index 8051692f..065f1a19 100755 --- a/plugins/virtualization/kvm_io +++ b/plugins/virtualization/kvm_io @@ -92,7 +92,7 @@ def list_pids(): ''' Find the pid of kvm processes @return a list of pids from running kvm ''' - pid = Popen("pidof kvm", shell=True, stdout=PIPE) + pid = Popen("pidof qemu-system-x86_64", shell=True, stdout=PIPE) return pid.communicate()[0].split() if __name__ == "__main__": diff --git a/plugins/virtualization/kvm_mem b/plugins/virtualization/kvm_mem index 2a93aaa6..66e7f399 100755 --- a/plugins/virtualization/kvm_mem +++ b/plugins/virtualization/kvm_mem @@ -89,7 +89,7 @@ def list_pids(): ''' Find the pid of kvm processes @return a list of pids from running kvm ''' - pid = Popen("pidof kvm", shell=True, stdout=PIPE) + pid = Popen("pidof qemu-system-x86_64", shell=True, stdout=PIPE) return pid.communicate()[0].split() if __name__ == "__main__": diff --git a/plugins/virtualization/kvm_net b/plugins/virtualization/kvm_net index ad34e217..dfe2cd79 100755 --- a/plugins/virtualization/kvm_net +++ b/plugins/virtualization/kvm_net @@ -100,7 +100,7 @@ def list_pids(): ''' Find the pid of kvm processes @return a list of pids from running kvm ''' - pid = Popen("pidof kvm", shell=True, stdout=PIPE) + pid = Popen("pidof qemu-system-x86_64", shell=True, stdout=PIPE) return pid.communicate()[0].split() def find_vms_tap(): From 0e2cac351cf2bbaf3f8bfae026a3a3933707c477 Mon Sep 17 00:00:00 2001 From: Alexander Koch Date: Tue, 1 Sep 2015 20:45:43 +0200 Subject: [PATCH 26/73] add postfix-policyd-spf-python plugin --- plugins/mail/policyd-spf-python | 86 +++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100755 plugins/mail/policyd-spf-python diff --git a/plugins/mail/policyd-spf-python b/plugins/mail/policyd-spf-python new file mode 100755 index 00000000..15d12a08 --- /dev/null +++ b/plugins/mail/policyd-spf-python @@ -0,0 +1,86 @@ +#! /bin/bash +# +# Munin plugin to monitor postfix-policyd-spf-python results +# Contributed by Alexander Koch +# +# Parameters understood: +# config (required) +# autoconf (optional - used by munin-config) +# +# Config variables: +# logfile - Where to find the postfix log (mail.log) +# +# Add the following line to a file in /etc/munin/plugin-conf.d: +# env.logfile /var/log/your/mail.log +# +# Magic markers (optional - used by munin-config and installation scripts): +# +#%# family=auto +#%# capabilities=autoconf + + +# +# Configuration +# + +STAT_FILE=${STAT_FILE:-/var/lib/munin/plugin-state/plugin-plcyd-spf-python.state} +LOGFILE=${logfile:-/var/log/mail.log} + +if [ "$1" = "autoconf" ]; then + echo yes + exit 0 +fi + +if [ "$1" = "config" ]; then + echo 'graph_title SPF Check Results' + echo 'graph_category postfix' + echo 'graph_args --base 1000 -l 0' + echo 'graph_vlabel Count/s' + + echo 'count_pass.label Pass' + echo 'count_pass.type DERIVE' + echo 'count_pass.min 0' + echo 'count_pass.colour 00cc00' + echo 'count_fail.label Fail' + echo 'count_fail.type DERIVE' + echo 'count_fail.min 0' + echo 'count_fail.colour cc0000' + echo 'count_none.label None' + echo 'count_none.type DERIVE' + echo 'count_none.min 0' + echo 'count_none.colour 0066b3' + echo 'count_temperror.label Temperror' + echo 'count_temperror.type DERIVE' + echo 'count_temperror.min 0' + echo 'count_temperror.colour ff8000' + echo 'count_neutral.label Neutral' + echo 'count_neutral.type DERIVE' + echo 'count_neutral.min 0' + echo 'count_neutral.colour ffcc00' + + exit 0 +fi + + +# +# Log parsing +# + +function get_log_count() { + egrep "policyd-spf\[[0-9]+\]: $1;" "$LOGFILE" | grep "$(date '+%b %e')" | wc -l +} + +PASS=$(get_log_count "Pass") +FAIL=$(get_log_count "Fail") +NONE=$(get_log_count "None") +TEMPERR=$(get_log_count "Temperror") +NEUTRAL=$(get_log_count "Neutral") + +echo "count_pass.value $PASS" +echo "count_fail.value $FAIL" +echo "count_none.value $NONE" +echo "count_temperror.value $TEMPERR" +echo "count_neutral.value $NEUTRAL" + + +exit 0 From 4fac7e61b88c3c367084e574d3c5b12468761902 Mon Sep 17 00:00:00 2001 From: jmdevince Date: Wed, 2 Sep 2015 16:00:24 -0500 Subject: [PATCH 27/73] Plugin monitoring CPU on Cisco SB Devices --- plugins/snmp/snmp__cisco_sbs_cpu | 90 ++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 plugins/snmp/snmp__cisco_sbs_cpu diff --git a/plugins/snmp/snmp__cisco_sbs_cpu b/plugins/snmp/snmp__cisco_sbs_cpu new file mode 100644 index 00000000..9dd58a3c --- /dev/null +++ b/plugins/snmp/snmp__cisco_sbs_cpu @@ -0,0 +1,90 @@ +#!/usr/bin/perl -w +# -*- perl -*- +# vim: ft=perl + +=head1 NAME + +snmp__cisco_sbs_cpu - Munin plugin to monitor CPU Usage on Cisco Small Business Switches. + +=head1 APPLICABLE SYSTEMS + +Cisco Small Business Switches (SBXXXX devices) + +=head1 CONFIGURATION + +As a rule SNMP plugins need site specific configuration. The default +configuration (shown here) will only work on insecure sites/devices. + + [snmp_*] + env.version 2 + env.community public + +In general SNMP is not very secure at all unless you use SNMP version +3 which supports authentication and privacy (encryption). But in any +case the community string for your devices should not be "public". + +Please see 'perldoc Munin::Plugin::SNMP' for further configuration +information. + +=head1 INTERPRETATION + +CPU Percentage gives an idea of utilization of the device. High CPU +can indicate a configuration problem or overutilization of the device. + +=head1 MAGIC MARKERS + + #%# family=snmpauto + #%# capabilities=snmpconf + +=head1 VERSION + + $Id$ + +=head1 BUGS + +None known. + +=head1 AUTHOR + +Copyright (C) 2015 James DeVincentis + +=head1 LICENSE + +GPLv3. + +=cut + +use strict; +use Munin::Plugin::SNMP; + +if (defined $ARGV[0] and $ARGV[0] eq 'snmpconf') { + print "require 1.3.6.1.4.1.9.6.1.101.1.7.0 [0-9]\n"; + exit 0; +} + +if (defined $ARGV[0] and $ARGV[0] eq "config") { + my ($host) = Munin::Plugin::SNMP->config_session(); + + print "host_name $host\n" unless $host eq 'localhost'; + print <<"EOF"; +graph_title CPU Utilization +graph_args --base 1000 -l 0 -u 100 +graph_vlabel CPU % +graph_category system +graph_info This graph shows the percentage of CPU used at different intervals. High CPU Utilization can indicate a configuration problem or overutilization of the device. +load5sec.label 5s +load5sec.info 5 Second CPU Utilization Average +load5sec.draw LINE1 +load1min.label 1m +load1min.info 1 Minute CPU Utilization Average +load1min.draw LINE1 +load5min.label 5m +load5min.info 5 Minute CPU Utilization Average +EOF + exit 0; +} + +my $session = Munin::Plugin::SNMP->session(); +print "load5sec.value ", $session->get_single('1.3.6.1.4.1.9.6.1.101.1.7.0'), "\n"; +print "load1min.value ", $session->get_single('1.3.6.1.4.1.9.6.1.101.1.8.0'), "\n"; +print "load5min.value ", $session->get_single('1.3.6.1.4.1.9.6.1.101.1.9.0'), "\n"; From 5ecffc35586331c1ded4cda3d95fc5511604126a Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Thu, 3 Sep 2015 17:13:06 +0200 Subject: [PATCH 28/73] battery_: first version --- plugins/sensors/battery-uevent_ | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100755 plugins/sensors/battery-uevent_ diff --git a/plugins/sensors/battery-uevent_ b/plugins/sensors/battery-uevent_ new file mode 100755 index 00000000..f4cbd541 --- /dev/null +++ b/plugins/sensors/battery-uevent_ @@ -0,0 +1,58 @@ +#! /bin/sh +# Plugin to monitor the battery status via the uevent API + +battery_name=CMB1 +percent=yes + +if [ "$1" = "config" ] +then + echo "graph_title Battery $battery_name" + if [ "$percent" = "yes" ] + then + echo "graph_vlabel %" + else + echo "graph_vlabel mAh" + fi + + echo "charge_design.label Design charge" + echo "charge_design.draw AREA" + [ "$percent" = "yes" ] && echo "charge_design.cdef charge_design,charge_design,/,100,*" + + echo "charge_full.label Full charge" + echo "charge_full.draw AREA" + [ "$percent" = "yes" ] && echo "charge_full.cdef charge_full,charge_design,/,100,*" + echo "charge_now.label Current charge" + echo "charge_now.draw AREA" + [ "$percent" = "yes" ] && echo "charge_now.cdef charge_now,charge_design,/,100,*" + + exit 0 +fi + +# Crudely read all the vars into the current namespace +. /sys/class/power_supply/$battery_name/uevent + +echo "charge_design.value $(( $POWER_SUPPLY_CHARGE_FULL_DESIGN / 1000 )) " +echo "charge_full.value $(( $POWER_SUPPLY_CHARGE_FULL / 1000 ))" +echo "charge_now.value $(( $POWER_SUPPLY_CHARGE_NOW / 1000 ))" + +exit 0 + + +:<< DATA +cat /sys/class/power_supply/$1/uevent +POWER_SUPPLY_NAME=CMB1 +POWER_SUPPLY_STATUS=Charging +POWER_SUPPLY_PRESENT=1 +POWER_SUPPLY_TECHNOLOGY=Li-ion +POWER_SUPPLY_CYCLE_COUNT=0 +POWER_SUPPLY_VOLTAGE_MIN_DESIGN=10800000 +POWER_SUPPLY_VOLTAGE_NOW=11418000 +POWER_SUPPLY_CURRENT_NOW=2668000 +POWER_SUPPLY_CHARGE_FULL_DESIGN=5200000 +POWER_SUPPLY_CHARGE_FULL=5000000 +POWER_SUPPLY_CHARGE_NOW=100000 +POWER_SUPPLY_CAPACITY=2 +POWER_SUPPLY_CAPACITY_LEVEL=Normal +POWER_SUPPLY_MODEL_NAME=CP293570 +POWER_SUPPLY_MANUFACTURER=Fujitsu +DATA From 0ab4b3a083248e7ed205a1db9939b7622d086ddc Mon Sep 17 00:00:00 2001 From: Steve Schnepp Date: Thu, 3 Sep 2015 17:14:15 +0200 Subject: [PATCH 29/73] battery_: make a wildcard plugin --- plugins/sensors/{battery-uevent_ => battery_} | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) rename plugins/sensors/{battery-uevent_ => battery_} (82%) diff --git a/plugins/sensors/battery-uevent_ b/plugins/sensors/battery_ similarity index 82% rename from plugins/sensors/battery-uevent_ rename to plugins/sensors/battery_ index f4cbd541..01f6f9d0 100755 --- a/plugins/sensors/battery-uevent_ +++ b/plugins/sensors/battery_ @@ -1,8 +1,18 @@ #! /bin/sh # Plugin to monitor the battery status via the uevent API +# +# (c) 2015 - GPLv2 - steve.schnepp@pwkf.org +# +# It is a wildcard plugin, symlink it with the battery directory +# default is to display charge as mAh, but you can also use percentage if you +# prefer, by setting the env var "percent" to "yes". +# +# [battery_*] +# env.percent no +# -battery_name=CMB1 -percent=yes +battery_name=${0##*_} +percent=${percent:-"no"} if [ "$1" = "config" ] then From ecad9659fca58753d4a6dd4e2110d04b179cfd0d Mon Sep 17 00:00:00 2001 From: Alexander Koch Date: Fri, 4 Sep 2015 20:38:55 +0200 Subject: [PATCH 30/73] postfix-policyd-spf-python: add license statement --- plugins/mail/policyd-spf-python | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mail/policyd-spf-python b/plugins/mail/policyd-spf-python index 15d12a08..f51a7b8b 100755 --- a/plugins/mail/policyd-spf-python +++ b/plugins/mail/policyd-spf-python @@ -2,6 +2,8 @@ # # Munin plugin to monitor postfix-policyd-spf-python results # Contributed by Alexander Koch +# +# This plugin is published under the terms of the MIT License. # # Parameters understood: # config (required) From cf88bc6c41e014c78d193a12f700d0738aac9552 Mon Sep 17 00:00:00 2001 From: Dominic Hargreaves Date: Thu, 10 Sep 2015 14:23:35 +0100 Subject: [PATCH 31/73] jenkins_: Reformat status mapping for better readability --- plugins/jenkins/jenkins_ | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/jenkins/jenkins_ b/plugins/jenkins/jenkins_ index 6112c8c2..edab804f 100644 --- a/plugins/jenkins/jenkins_ +++ b/plugins/jenkins/jenkins_ @@ -66,7 +66,18 @@ my $wgetBin = "/usr/bin/wget"; my $type = basename($0); $type =~ s/jenkins_//; -my %states = ('blue' =>'stable', 'blue_anime' =>'stable', 'yellow'=>'unstable', 'yellow_anime'=>'unstable', 'red'=>'failing', 'red_anime'=>'failing', 'disabled'=>'disabled', 'notbuilt_anime' =>'disabled', 'aborted'=>'failing', 'aborted_anime'=>'failing' ); +my %states = ( + 'blue' =>'stable', + 'blue_anime' =>'stable', + 'yellow'=>'unstable', + 'yellow_anime'=>'unstable', + 'red'=>'failing', + 'red_anime'=>'failing', + 'disabled'=>'disabled', + 'notbuilt_anime' =>'disabled', + 'aborted'=>'failing', + 'aborted_anime'=>'failing' +); my %counts = ('blue' => 0, 'yellow'=>0, 'red'=>0, 'disabled'=>0); if ( exists $ARGV[0] and $ARGV[0] eq "config" ) { From b2080e1178ea401d4aba8f9b9ef33875671d2b33 Mon Sep 17 00:00:00 2001 From: Dominic Hargreaves Date: Thu, 10 Sep 2015 14:25:26 +0100 Subject: [PATCH 32/73] jenkins_: Fix uninitialized values warning with unmapped states --- plugins/jenkins/jenkins_ | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/jenkins/jenkins_ b/plugins/jenkins/jenkins_ index edab804f..d60ef71c 100644 --- a/plugins/jenkins/jenkins_ +++ b/plugins/jenkins/jenkins_ @@ -134,7 +134,11 @@ if ( exists $ARGV[0] and $ARGV[0] eq "config" ) { my $result = `$cmd/api/json`; my $parsed = decode_json($result); foreach my $cur(@{$parsed->{'jobs'}}) { - $counts{$cur->{'color'}} += 1; + if (defined $states{$cur->{'color'}}) { + $counts{$cur->{'color'}} += 1; + } else { + warn "Ignoring unknown color " . $cur->{'color'} . "\n" + } } foreach my $status (keys %counts) { From 8ad8e1b9e21c107ea4aae697248de1c93536c9f5 Mon Sep 17 00:00:00 2001 From: Dominic Hargreaves Date: Thu, 10 Sep 2015 14:26:05 +0100 Subject: [PATCH 33/73] jenkins_: Add a missing mapping for notbuilt --- plugins/jenkins/jenkins_ | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/jenkins/jenkins_ b/plugins/jenkins/jenkins_ index d60ef71c..d37c2605 100644 --- a/plugins/jenkins/jenkins_ +++ b/plugins/jenkins/jenkins_ @@ -74,6 +74,7 @@ my %states = ( 'red'=>'failing', 'red_anime'=>'failing', 'disabled'=>'disabled', + 'notbuilt' => 'disabled', 'notbuilt_anime' =>'disabled', 'aborted'=>'failing', 'aborted_anime'=>'failing' From 3e70ec380380d433cc2f178bf4a9e135466e3eff Mon Sep 17 00:00:00 2001 From: Dominic Hargreaves Date: Thu, 10 Sep 2015 14:26:51 +0100 Subject: [PATCH 34/73] jenkins_: Accumulate build result counts correctly The previous behaviour ignored initial statuses other than blue, yellow, red, disabled --- plugins/jenkins/jenkins_ | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/jenkins/jenkins_ b/plugins/jenkins/jenkins_ index d37c2605..7dd34068 100644 --- a/plugins/jenkins/jenkins_ +++ b/plugins/jenkins/jenkins_ @@ -79,7 +79,7 @@ my %states = ( 'aborted'=>'failing', 'aborted_anime'=>'failing' ); -my %counts = ('blue' => 0, 'yellow'=>0, 'red'=>0, 'disabled'=>0); +my %counts = ('stable' => 0, 'unstable'=>0, 'failing'=>0, 'disabled'=>0); if ( exists $ARGV[0] and $ARGV[0] eq "config" ) { if( $type eq "results" ) { @@ -136,14 +136,14 @@ if ( exists $ARGV[0] and $ARGV[0] eq "config" ) { my $parsed = decode_json($result); foreach my $cur(@{$parsed->{'jobs'}}) { if (defined $states{$cur->{'color'}}) { - $counts{$cur->{'color'}} += 1; + $counts{$states{$cur->{'color'}}} += 1; } else { warn "Ignoring unknown color " . $cur->{'color'} . "\n" } } foreach my $status (keys %counts) { - print "build_$states{$status}.value $counts{$status}\n"; + print "build_$status.value $counts{$status}\n"; } exit; } From 12ee36319ecf8462695c05e5d6d33f4fb81a278e Mon Sep 17 00:00:00 2001 From: stimpy23 Date: Tue, 22 Sep 2015 12:33:13 +0200 Subject: [PATCH 35/73] Updated to v0.2 Updated to match the outputs of current ice- and shoutcast versions... --- plugins/network/radio | 283 ++++++++++++++++++++---------------------- 1 file changed, 134 insertions(+), 149 deletions(-) diff --git a/plugins/network/radio b/plugins/network/radio index bd06bed6..1c815823 100755 --- a/plugins/network/radio +++ b/plugins/network/radio @@ -1,155 +1,140 @@ #!/usr/bin/php "ice", // server-type (ice/shout) - "host" => "192.168.1.5", // server hostname or ip - "port" => 8000, // server port - "mountpoint" => "eclectix.mp3", // mountpoint to check (icecast only) - "name" => "IceCast" // name for use in munin graphs - ), - // SERVER #2 - array( "type" => "shout", // server-type - "host" => "radio.eclectix.de", // server hostname or ip - "port" => 8000, // server port - "name" => "ShoutCast" // name for use in munin graphs - ) - ); - -// -------------- CONFIGURATION END ---------------------------------------------------------------- - - function getIce( $host, $port, $mount, $name ) { - $error = false; - $fp = fsockopen( $host, $port, $errno, $errstr, 10 ); - if ( !$fp ) { - $error = $errstr ."(". $errno .")"; - } else { - fputs( $fp, "GET /status HTTP/1.1\r\n" ); - fputs( $fp, "Host: ". $host ."\r\n" ); - fputs($fp, "User-Agent: Mozilla\r\n"); - fputs( $fp, "Connection: close\r\n\r\n" ); - - $xml = ""; - - while ( !feof( $fp ) ) { - $xml .= fgets( $fp, 512 ); - } - - fclose($fp); - - if ( stristr( $xml, "HTTP/1.0 200 OK" ) == true ) { - $xml = trim( substr( $xml, 42 ) ); - } else { - $error = "Bad login"; - } - if ( !$error ) { - $res = array( "found" => true ); - - $mount = str_replace( ".", "\.", $mount ); - preg_match_all( "/Mount Point : \(\/(". $mount .")\).*?\\Current Listeners:\<\/td\>\(\d*?)\<\/td\>\<\/tr\>\Peak Listeners:\<\/td\>\(\d*?)\<\/td\>\<\/tr\>/s", $xml, $parser ); - - $res["mount"] = $parser[1][0]; - $res["listeners"] = $parser[2][0]; - $res["listeners_peak"] = $parser[3][0]; - $res["name"] = $name; - } else { - $res = $error; - } - } - return $res; - } - - function getShout( $host, $port, $name ) { - $error = false; - $fp = fsockopen( $host, $port, $errno, $errstr, 10 ); - if ( !$fp ) { - $error = $errstr ."(". $errno .")"; - } else { - fputs( $fp, "GET / HTTP/1.0\r\n" ); - fputs($fp, "User-Agent: Mozilla\r\n"); - fputs( $fp, "Connection: close\r\n\r\n" ); - - $xml = ""; - - while ( !feof( $fp ) ) { - $xml .= fgets($fp, 512); - } - fclose( $fp ); - - if ( stristr( $xml, "HTTP/1.0 200 OK" ) == true ) { - $xml = trim( substr( $xml, 42 ) ); - } else { - $error = "Bad login"; - } - if ( !$error ) { - $res = array( "found" => true ); - - preg_match_all( "/.*?Stream Status: \<\/font\>\<\/td\>\\\Stream is up at \d*? kbps with \(\d*?) of \d*? listeners \(\d*? unique\)\<\/b\>\<\/b\>/s", $xml, $parser ); - - $res["listeners"] = $parser[1][0]; - $res["name"] = $name; - - } else { - $res = $error; - } - } - return $res; +// -------------- CONFIGURATION START --------------------------------------- + $cfg = array( + // SERVER #1 + array( "name" => "IceCast", // name for munin + "type" => "ice", // server-type (ice/shout) + "host" => "ice.example.com", // server hostname or ip + "port" => 8000, // server port + "mountpoint" => "live" // mountpoint to check + // (icecast only) + ), + // SERVER #2 + array( "name" => "ShoutCast", // name for munin + "type" => "shout", // server-type + "host" => "127.0.0.1", // server hostname or ip + "port" => 8000 // server port + ) + ); +// -------------- CONFIGURATION END ----------------------------------------- + error_reporting(E_ERROR); + function getIce( $host, $port, $mount, $name ) { + $error = false; + if ( !$fp = fsockopen( $host, $port, $errno, $errstr, 10 ) ) { + $error = $errstr ."(". $errno .")"; + } else { + fputs( $fp, "GET /status.xsl HTTP/1.1\r\n" ); + fputs( $fp, "Host: ". $host ."\r\n" ); + fputs( $fp, "User-Agent: Mozilla\r\n" ); + fputs( $fp, "Connection: close\r\n\r\n" ); + $xml = ""; + while ( !feof( $fp ) ) { + $xml .= fgets( $fp, 512 ); + } + fclose( $fp ); + if ( stristr( $xml, "HTTP/1.0 200 OK" ) == true ) { + $xml = trim( substr( $xml, 42 ) ); + } else { + $error = "Bad login"; + } + if ( !$error ) { + $res = array( "found" => true ); + $mount = str_replace( ".", "\.", $mount ); + preg_match_all( "/Mount Point \/(". $mount .").*?\\Current Listeners:\<\/td\>\(\d*?)\<\/td\>\<\/tr\>\Peak Listeners:\<\/td\>\(\d*?)\<\/td\>\<\/tr\>/s", $xml, $parser ); + $res["mount"] = $parser[1][0]; + $res["listeners"] = intval( $parser[2][0] ); + $res["listeners_peak"] = intval( $parser[3][0] ); + $res["name"] = $name; + } else { + $res = $error; + } + } + return $res; + } + function getShout( $host, $port, $name ) { + $error = false; + if ( !$fp = fsockopen( $host, $port, $errno, $errstr, 10 ) ) { + $error = $errstr ."(". $errno .")"; + } else { + fputs( $fp, "GET /index.html?sid=1 HTTP/1.0\r\n" ); + fputs( $fp, "User-Agent: Mozilla\r\n" ); + fputs( $fp, "Connection: close\r\n\r\n" ); + $xml = ""; + while ( !feof( $fp ) ) { + $xml .= fgets( $fp, 512 ); + } + fclose( $fp ); + if ( stristr( $xml, "HTTP/1.1 200 OK" ) == true ) { + $xml = trim( substr( $xml, 42 ) ); + } else { + $error = "Bad login"; + } + if ( !$error ) { + $res = array( "found" => true ); + preg_match_all( "/.*?Stream Status: <\/td\>\Stream is up at \d*? kbps with (\d*?) of \d*? listeners/s", $xml, $parser ); + $res["listeners"] = intval( $parser[1][0] ); + $res["name"] = $name; + } else { + $res = $error; + } + } + return $res; + } + if ( !isset( $argv[1] ) ) $argv[1] = ''; + switch( $argv[1] ) { + case "config": + echo "graph_title Stream Listeners\n"; + echo "graph_category Network\n"; + echo "graph_vlabel listeners\n"; + echo "graph_hlabel listeners\n"; + echo "graph_args --base 1000 -l 0\n"; + echo "graph_scale no\n"; + echo "graph_info Number of listeners to shout- and / or icecast streams\n"; + echo "complete.info Complete listeners\n"; + echo "complete.label complete\n"; + echo "graph_order"; + foreach ( $cfg as $c ) { + echo " ". strtolower( $c["name"] ); + } + echo " complete\n"; + foreach ( $cfg as $c ) { + echo strtolower( $c["name"] ) .".info ". $c["name"] ." listeners\n"; + echo strtolower( $c["name"] ) .".label ". strtolower( $c["name"] ) ."\n"; + } + break; + default: + $complete = 0; + foreach ( $cfg as $c ) { + switch ( $c["type"] ) { + case "ice": + $res = getIce( $c["host"], $c["port"], $c["mountpoint"], $c["name"] ); + break; + case "shout": + $res = getShout( $c["host"], $c["port"], $c["name"] ); + break; } - - switch( $argv[1] ) { - case "config": - echo "graph_title Stream Listeners\n"; - echo "graph_category Network\n"; - echo "graph_vlabel listeners\n"; - echo "graph_hlabel listeners\n"; - echo "graph_args --base 1000 -l 0\n"; - echo "graph_scale no\n"; - echo "graph_order"; - foreach ( $cfg as $c ) { - echo " ". strtolower( $c["name"] ); - } - echo " complete\n"; -// echo "\n"; - echo "graph_info Number of listeners to shout- and / or icecast streams\n"; - foreach ( $cfg as $c ) { - echo strtolower( $c["name"] ) .".info ". $c["name"] ." listeners\n"; - echo strtolower( $c["name"] ) .".label ". strtolower( $c["name"] ) ."\n"; - } - echo "complete.info Complete listeners\n"; - echo "complete.label complete\n"; - break; - default: - $complete = 0; - - foreach ( $cfg as $c ) { - switch ( $c["type"] ) { - case "ice": - $res = getIce( $c["host"], $c["port"], $c["mountpoint"], $c["name"] ); - $complete += $res["listeners"]; - break; - case "shout": - $res = getShout( $c["host"], $c["port"], $c["name"] ); - $complete += $res["listeners"]; - break; - } - echo strtolower($c["name"]) .".value ". $res["listeners"] ."\n"; - } - - echo "complete.value ". $complete ."\n"; - break; - } + if ( is_array( $res ) ) { + echo strtolower($c["name"]) .".value ". $res["listeners"] ."\n"; + $complete += $res["listeners"]; + } else { + echo strtolower($c["name"]) .".value 0\n"; + } + } + echo "complete.value ". $complete ."\n"; + break; + } ?> From d153fc8a4f7dab13d9eec61f3c3e0e7c8a5cb4f9 Mon Sep 17 00:00:00 2001 From: lduchosal Date: Thu, 24 Sep 2015 08:07:18 +0200 Subject: [PATCH 36/73] pf_tables : Munin plugin to monitor pf tables. Inout: bandwidth usage for table Addresses: number of entries in table --- plugins/network/pf_tables_ | 252 +++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 plugins/network/pf_tables_ diff --git a/plugins/network/pf_tables_ b/plugins/network/pf_tables_ new file mode 100644 index 00000000..3e98903f --- /dev/null +++ b/plugins/network/pf_tables_ @@ -0,0 +1,252 @@ +#!/usr/bin/perl -w +# -*- perl -*- + +=head1 NAME + +pf_tables : Munin plugin to monitor pf tables. +Inout: bandwidth usage for table +Addresses: number of entries in table + + +=head1 APPLICABLE SYSTEMS + +Should work on any BSD that has pf(4). + +Examples: + +=over + +=item pf_tables_inout_tablename + +=item pf_tables_addresses_authenticated + +=item pf_tables_addresses_badboys + + +=head1 CONFIGURATION + +[pf_tables_*] +user root + +=head1 INTERPRETATION + +The plugin simply runs the pfctl -sTables -vvv command and counts the number of +Addresses and InBytes/OutBytes in each table. + +=head1 BUGS + +Only tested extensively on FreeBSD. + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf suggest + +=head1 VERSION + + $Id$ + +=head1 AUTHOR + +Copyright (C) 2015. + +Original version by Luc Duchosal (at) arcantel (dot) ch. +Created by Luc Duchosal, 2015 + +=head1 LICENSE + +BSD + +=cut + + +use strict; +use Munin::Plugin; + +$0 =~ /pf_tables_(addresses|inout)_(.+)$/; +my $name = $2; +my $operation = $1; + +if ( defined($ARGV[0])) { + if ($ARGV[0] eq 'autoconf') { + print "yes\n"; + exit 0; + } + + if ($ARGV[0] eq "config") { + + if (!defined($name)) { + print "Unknown table\n"; + exit 0; + } + + if (!defined($operation)) { + print "Unknown operation\n"; + exit 0; + } + + if ($operation =~ m/addresses/) { + + print "graph_title Connected users ($name)\n"; + print "graph_args --base 1000 -l 0\n"; + print "graph_vlabel Users\n"; + print "graph_scale no\n"; + print "graph_category pf\n"; + print "graph_printf %3.0lf\n"; + + print "users.label users\n"; + print "users.draw AREASTACK\n"; + print "users.colour 00C000\n"; + foreach my $field (qw(users)) { + print_thresholds($field); + } + } + + if ($operation =~ m/inout/) { + + print "graph_title Network bandwidth ($name)\n"; + print "graph_args --base 1024 -l 0\n"; + print "graph_vlabel Bandwidth\n"; + print "graph_scale yes\n"; + print "graph_category pf\n"; +# print "graph_printf %3.0lf\n"; + + print "in.label in\n"; + print "in.type DERIVE\n"; + print "in.draw AREASTACK\n"; + print "in.colour C00000\n"; + print "in.cdef in,8,*\n"; + print "in.min 0\n"; + print "in.graph no\n"; + print "out.label out\n"; + print "out.type DERIVE\n"; + print "out.negative in\n"; + print "out.draw AREASTACK\n"; + print "out.colour 0000C0\n"; + print "out.cdef out,8,*\n"; + print "out.min 0\n"; + print "out.graph no\n"; + + foreach my $field (qw(in out)) { + print_thresholds($field); + } + + } + exit 0; + } + + if ($ARGV[0] eq "suggest") { + my %tables = &tables(); + foreach my $key (keys(%tables)) { + print "addresses_$key\n"; + print "inout_$key\n"; + } + exit 0; + } + +} + +if (!defined($name)) { + print "Usage: pf_tables_addresses_tablename or pf_tables_inout_tablename\n"; + exit 1; +} + +my %tables = &tables(); +if (!exists $tables{$name}) { + print "Unknown table name $name\n"; + exit 2; +} + +if ($operation =~ m/addresses/) { + my $users = $tables{$name}->{"addresses"}; + print "users.value $users\n"; +} + +if ($operation =~ m/inout/) { + my $in = $tables{$name}->{"inpassbytes"}; + my $out = $tables{$name}->{"outpassbytes"}; + print "in.value $in\n"; + print "out.value $out\n"; +} + + +sub tables { + + # # pfctl -s Tables -vv + # -pa-r-- auth + # Addresses: 0 + # Cleared: Fri Sep 18 17:34:42 2015 + # References: [ Anchors: 0 Rules: 14 ] + # Evaluations: [ NoMatch: 43624 Match: 788 ] + # In/Block: [ Packets: 0 Bytes: 0 ] + # In/Pass: [ Packets: 30908 Bytes: 2704516 ] + # In/XPass: [ Packets: 124 Bytes: 7897 ] + # Out/Block: [ Packets: 0 Bytes: 0 ] + # Out/Pass: [ Packets: 30288 Bytes: 26313114 ] + # Out/XPass: [ Packets: 89 Bytes: 21166 ] + + my $output = `/sbin/pfctl -s Tables -vv 2> /dev/null`; + my %tables; + my $name; + + foreach (split(/\n/, $output)) { + + if (m|^[cpairhC\-]{7}\s+(\w+)$|) { + $name = $1; + $tables{$name}->{"name"} = $1; + next; + } + + if (m|Addresses:\s+([0-9]+)$|) { + $tables{$name}->{"addresses"} = $1; + next; + } + + if (m|Cleared:\s+(.+)$|) { + $tables{$name}->{"cleared"} = $1; + next; + } + + if (m|In/Block:\s+\[\s+Packets:\s+([0-9]+)\s+Bytes:\s+([0-9]+)\s+\]$|) { + $tables{$name}->{"inblockpackets"} = $1; + $tables{$name}->{"inblockbytes"} = $2; + next; + } + + if (m|In/Pass:\s+\[\s+Packets:\s+([0-9]+)\s+Bytes:\s+([0-9]+)\s+\]$|) { + $tables{$name}->{"inpasspackets"} = $1; + $tables{$name}->{"inpassbytes"} = $2; + next; + } + + if (m|In/XPass:\s+\[\s+Packets:\s+([0-9]+)\s+Bytes:\s+([0-9]+)\s+\]$|) { + $tables{$name}->{"inxpasspackets"} = $1; + $tables{$name}->{"inxpassbytes"} = $2; + next; + } + + if (m|Out/Block:\s+\[\s+Packets:\s+([0-9]+)\s+Bytes:\s+([0-9]+)\s+\]$|) { + $tables{$name}->{"outblockpackets"} = $1; + $tables{$name}->{"outblockbytes"} = $2; + next; + } + + if (m|Out/Pass:\s+\[\s+Packets:\s+([0-9]+)\s+Bytes:\s+([0-9]+)\s+\]$|) { + $tables{$name}->{"outpasspackets"} = $1; + $tables{$name}->{"outpassbytes"} = $2; + next; + } + + if (m|Out/XPass:\s+\[\s+Packets:\s+([0-9]+)\s+Bytes:\s+([0-9]+)\s+\]$|) { + $tables{$name}->{"outxpasspackets"} = $1; + $tables{$name}->{"outxpassbytes"} = $2; + next; + } + + } + + return %tables; + +} + +# vim:syntax=perl From 51b962245a3ac3e36fcc918d0ada5d6197023035 Mon Sep 17 00:00:00 2001 From: lduchosal Date: Thu, 24 Sep 2015 08:35:44 +0200 Subject: [PATCH 37/73] Wildcard-plugin to monitor arp on interfaces on BSD --- plugins/network/arp_bsd_ | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 plugins/network/arp_bsd_ diff --git a/plugins/network/arp_bsd_ b/plugins/network/arp_bsd_ new file mode 100644 index 00000000..7572e8b6 --- /dev/null +++ b/plugins/network/arp_bsd_ @@ -0,0 +1,54 @@ +#!/bin/sh +# +# Wildcard-plugin to monitor arp on interfaces. To monitor an +# interface, link arp_bsd_ to this file. E.g. +# +# ln -s /usr/local/share/munin/plugins/arp_bsd_ /usr/local/etc/munin/plugins/arp_bsd_vlanX +# +# ...will monitor arp on interface vlanX. +# +# Any device found in /sbin/ifconfig can be monitored. +# +# Bugs : This plugins has been tested extensively on FreeBSD only +# +# Author : Luc Duchosal +# +# Magic markers (optional - used by munin-config and some installation +# scripts): +# +#%# family=auto +#%# capabilities=autoconf suggest + + +INTERFACE=`basename $0 | sed 's/^arp_bsd_//g'` + +if [ "$1" = "autoconf" ]; then + if [ -x /sbin/ifconfig ]; then + echo yes + exit 0 + else + echo "no (/sbin/ifconfig not found)" + exit 0 + fi +fi + +if [ "$1" = "suggest" ]; then + if [ -x /sbin/ifconfig ]; then + /sbin/ifconfig -a | /usr/bin/grep -E '^[a-z]' | /usr/bin/awk -F\: '{print $1;}' + exit 0 + else + exit 1 + fi +fi + +if [ "$1" = "config" ]; then + + echo "graph_title $INTERFACE arp" + echo 'graph_args --base 1000' + echo 'graph_vlabel arp per ${graph_period}' + echo 'graph_category network' + echo 'arp.label arp' + exit 0 +fi + +/usr/sbin/arp -an | /usr/bin/grep $INTERFACE | /usr/bin/wc -l | /usr/bin/awk '{ print "arp.value", $1;}' From f07be516b79eaa8bd6b8a8285a8c01eba4230233 Mon Sep 17 00:00:00 2001 From: obma Date: Tue, 13 Oct 2015 17:48:04 +0200 Subject: [PATCH 38/73] p: openweather_: Add OpenWeather API Key OpenWeather needs you to use an API key since Oct 9 2015. Therefore I added env.apikey. To get a key just register at OpenWeather. --- plugins/weather/openweather_ | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/weather/openweather_ b/plugins/weather/openweather_ index 47b1c128..bbd3c7a4 100644 --- a/plugins/weather/openweather_ +++ b/plugins/weather/openweather_ @@ -13,6 +13,11 @@ # http://api.openweathermap.org/data//weather? # +## From Oct 9 2015 OpenWeather needs you to register and get an APIKEY +# include this key by setting 'env.apikey' in munin plugin config, i.e.: +# [openweather_*] +# env.apikey XYZ + query_string=$(printf '%s' "${0#*_}" | tr '_' '=') TMPFILE=$(mktemp) trap 'rm -f $TMPFILE' EXIT @@ -20,7 +25,7 @@ trap 'rm -f $TMPFILE' EXIT # API returns temp in K, we have to convert it in C KELVIN_BIAS=273 -curl -s "http://api.openweathermap.org/data/2.5/weather?mode=xml&${query_string}" > $TMPFILE +curl -s "http://api.openweathermap.org/data/2.5/weather?mode=xml&${query_string}&APPID=${apikey}" > $TMPFILE CITY=$(fgrep " Date: Fri, 16 Oct 2015 00:19:07 -0300 Subject: [PATCH 39/73] Ability to configure Apache access_log field number --- plugins/apache/apache_average_time_last_n_requests | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/apache/apache_average_time_last_n_requests b/plugins/apache/apache_average_time_last_n_requests index 709d43e0..f01bb638 100755 --- a/plugins/apache/apache_average_time_last_n_requests +++ b/plugins/apache/apache_average_time_last_n_requests @@ -1,5 +1,4 @@ #!/usr/bin/perl -w -# $Id$ # Author: Nicolas Mendoza - 2008-06-18 # # Monitors the average time requests matching a custom regexp takes @@ -16,12 +15,16 @@ # For instance: # LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %T %v" # Check http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#formats for more info +# +# Configurable variables +# fieldno - Override the default field number +#%# family=auto use strict; my $LAST_N_REQUESTS = 100000; # calculate based on this amount of requests my $ACCESS_LOG_PATTERN = '/var/log/apache2/access.log.*'; # log pattern, if many it will take the last one. -my $TIME_FIELD_INDEX = -2; # second last field +my $TIME_FIELD_INDEX = exists $ENV{'fieldno'} ? $ENV{'fieldno'} : -2; # second last field my $config =<< "CONFIG" graph_title Apache average seconds last $LAST_N_REQUESTS requests From 2a776fa560b2f1cac76578af47bc825869c22f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Fri, 16 Oct 2015 00:24:20 -0300 Subject: [PATCH 40/73] Update apache_average_time_last_n_requests --- plugins/apache/apache_average_time_last_n_requests | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/apache/apache_average_time_last_n_requests b/plugins/apache/apache_average_time_last_n_requests index f01bb638..04002839 100755 --- a/plugins/apache/apache_average_time_last_n_requests +++ b/plugins/apache/apache_average_time_last_n_requests @@ -18,13 +18,14 @@ # # Configurable variables # fieldno - Override the default field number +# linecount - How many last request to consider #%# family=auto use strict; -my $LAST_N_REQUESTS = 100000; # calculate based on this amount of requests -my $ACCESS_LOG_PATTERN = '/var/log/apache2/access.log.*'; # log pattern, if many it will take the last one. +my $LAST_N_REQUESTS = exists $ENV{'linecount'} ? $ENV{'linecount'} : 100000; # calculate based on this amount of requests my $TIME_FIELD_INDEX = exists $ENV{'fieldno'} ? $ENV{'fieldno'} : -2; # second last field +my $ACCESS_LOG_PATTERN = '/var/log/apache2/access.log.*'; # log pattern, if many it will take the last one. my $config =<< "CONFIG" graph_title Apache average seconds last $LAST_N_REQUESTS requests From 6de1145610d12788a41121e044964709afc6d33d Mon Sep 17 00:00:00 2001 From: Samuel Cantero Date: Thu, 22 Oct 2015 15:37:24 -0300 Subject: [PATCH 41/73] Add Docker plugins Docker plugins for: - Monitor container CPU usage - Monitor memory CPU usage --- plugins/docker/docker_cpu | 101 ++++++++++++++++++ plugins/docker/docker_memory | 81 ++++++++++++++ .../example_graphs/docker_cpu_usage.png | Bin 0 -> 85915 bytes .../example_graphs/docker_memory_usage.png | Bin 0 -> 72634 bytes 4 files changed, 182 insertions(+) create mode 100755 plugins/docker/docker_cpu create mode 100755 plugins/docker/docker_memory create mode 100644 plugins/docker/example_graphs/docker_cpu_usage.png create mode 100644 plugins/docker/example_graphs/docker_memory_usage.png diff --git a/plugins/docker/docker_cpu b/plugins/docker/docker_cpu new file mode 100755 index 00000000..7a30fad9 --- /dev/null +++ b/plugins/docker/docker_cpu @@ -0,0 +1,101 @@ +#!/usr/bin/perl -w +# -*- perl -*- + +=head1 NAME + +docker_cpu - Munin plugin to monitor docker container CPU usage. + +=head1 APPLICABLE SYSTEMS + +Should work on any Linux system that has docker support. + +=head1 CONFIGURATION + +Root privilege required to execute docker command. + +1. Create a new file named "docker" inside the folder /etc/munin/plugin-conf.d/ +2. Docker file content: + +[docker_cpu] +user root + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf + +=head1 VERSION + + v.0.1 + +=head1 AUTHOR + +Copyright (C) 2015 Samuel Cantero. +Email: scanterog at gmail dot com + +=head1 LICENSE + +GPLv3 + +=cut + +my @containers = split "\n" , `/usr/bin/docker ps --no-trunc=true`; +my $result; + +for my $i (1 .. $#containers) +{ + my @fields = split / +/, $containers[$i]; + my $id = $fields[0]; + my $name = $fields[$#fields]; + # manage container name containing arithmetic operators and dots. E.g, my-container. + $name =~ s/[-\+*\/\.]/_/g; + # truncate container name with "," character. + $name =~ s/,.*//g; + open(my $file, '<', "/sys/fs/cgroup/cpuacct/docker/$id/cpuacct.usage") or die "Unable to open file, $!"; + my $total_cpu_ns = <$file>; + $total_cpu_ns =~ s/\s+$//; + close $file; + open($file, '<', "/sys/fs/cgroup/cpuacct/docker/$id/cpuacct.usage_percpu") or die "Unable to open file, $!"; + my @ncpu = split / /, <$file>; + close $file; + push @result, {'name'=>$name, 'total_cpu_ns'=>$total_cpu_ns, 'ncpu'=>$#ncpu}; +} + +if (defined $ARGV[0] and $ARGV[0] eq "config") +{ + my $nanoSecondsInSecond=1000000000; + my $graphlimit = $result[0]{'ncpu'}; + foreach(@result){ + if ($$_{'ncpu'} > $graphlimit){ + $graphlimit = $$_{'ncpu'}; + } + } + $graphlimit = $graphlimit * 100; + print "graph_title Docker container CPU usage\n"; + print "graph_args --base 1000 -r --lower-limit 0 --upper-limit $graphlimit\n"; + print "graph_vlabel %\n"; + print "graph_scale no\n"; + print "graph_period second\n"; + print "graph_category Docker\n"; + print "graph_info This graph shows docker container CPU usage.\n"; + + foreach(@result) + { + print "$$_{'name'}.label $$_{'name'}\n"; + print "$$_{'name'}.draw LINE2\n"; + print "$$_{'name'}.min 0\n"; + print "$$_{'name'}.type DERIVE\n"; + print "$$_{'name'}.cdef $$_{'name'},$nanoSecondsInSecond,/\n"; + } + exit 0; +} + +# Note: Counters/derive need to report integer values. + +foreach(@result) +{ + $tcpu = ($$_{'total_cpu_ns'}*100); #to percentage + print "$$_{'name'}.value $tcpu\n"; +} + +# vim:syntax=perl diff --git a/plugins/docker/docker_memory b/plugins/docker/docker_memory new file mode 100755 index 00000000..d48b0fc8 --- /dev/null +++ b/plugins/docker/docker_memory @@ -0,0 +1,81 @@ +#!/usr/bin/perl -w +# -*- perl -*- + +=head1 NAME + +docker_memory - Munin plugin to monitor docker container memory usage. + +=head1 APPLICABLE SYSTEMS + +Should work on any Linux system that has docker support. + +=head1 CONFIGURATION + +Root privilege required to execute docker command. + +1. Create a new file named "docker" inside the folder /etc/munin/plugin-conf.d/ +2. Docker file content: + +[docker_memory] +user root + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf + +=head1 VERSION + + v.0.1 + +=head1 AUTHOR + +Copyright (C) 2015 Samuel Cantero. +Email: scanterog at gmail dot com + +=head1 LICENSE + +GPLv3 + +=cut + +my @containers = split "\n" , `/usr/bin/docker ps --no-trunc=true`; +my $result; + +for my $i (1 .. $#containers) +{ + my @fields = split / +/, $containers[$i]; + my $id = $fields[0]; + my $name = $fields[$#fields]; + # manage container name containing arithmetic operators and dots. E.g, my-container. + $name =~ s/[-\+*\/\.]/_/g; + # truncate container name with "," character. + $name =~ s/,.*//g; + open(my $file, '<', "/sys/fs/cgroup/memory/docker/$id/memory.usage_in_bytes") or die "Unable to open file, $!"; + my $memory_bytes = <$file>; + $memory_bytes =~ s/\s+$//; + push @result, {'name'=>$name, 'memory_bytes'=>$memory_bytes}; +} + +if (defined $ARGV[0] and $ARGV[0] eq "config") +{ + print "graph_title Docker container memory usage\n"; + print "graph_args --base 1024 -l 0\n"; + print "graph_vlabel Bytes\n"; + print "graph_category Docker\n"; + print "graph_info This graph shows docker container memory usage.\n"; + + foreach(@result) + { + print "$$_{'name'}.label $$_{'name'}\n"; + print "$$_{'name'}.draw LINE2\n"; + } + exit 0; +} + +foreach(@result) +{ + print "$$_{'name'}.value $$_{'memory_bytes'}\n"; +} + +# vim:syntax=perl diff --git a/plugins/docker/example_graphs/docker_cpu_usage.png b/plugins/docker/example_graphs/docker_cpu_usage.png new file mode 100644 index 0000000000000000000000000000000000000000..c09b6917bba390525f3d7023aadefbc7b30dd5de GIT binary patch literal 85915 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 literal 0 HcmV?d00001 diff --git a/plugins/docker/example_graphs/docker_memory_usage.png b/plugins/docker/example_graphs/docker_memory_usage.png new file mode 100644 index 0000000000000000000000000000000000000000..04ab75ed032c71e42342be077e8524bc4c41a331 GIT binary patch literal 72634 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%0D(Y`| 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 Y{J7y5Glx^&4F(|aboFyt=akR{04}EJmH+?% literal 0 HcmV?d00001 From ae03bc6fb886836cf16376acf6c65b4cedd16678 Mon Sep 17 00:00:00 2001 From: Samuel Cantero Date: Sun, 25 Oct 2015 18:04:02 -0300 Subject: [PATCH 42/73] Docker plugin Fix autoconf support --- plugins/docker/docker_cpu | 17 ++++++++++++++++- plugins/docker/docker_memory | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/plugins/docker/docker_cpu b/plugins/docker/docker_cpu index 7a30fad9..c9b7e642 100755 --- a/plugins/docker/docker_cpu +++ b/plugins/docker/docker_cpu @@ -39,7 +39,22 @@ GPLv3 =cut -my @containers = split "\n" , `/usr/bin/docker ps --no-trunc=true`; +my $docker=`which docker`; + +if ( defined $ARGV[0] and $ARGV[0] eq "autoconf" ) { + if ($docker) { + print "yes\n"; + exit 0; + } + else{ + print "no (Docker has not been found)\n"; + exit 0; + } +} + +$docker =~ s/\s+$//; + +my @containers = split "\n" , `$docker ps --no-trunc=true`; my $result; for my $i (1 .. $#containers) diff --git a/plugins/docker/docker_memory b/plugins/docker/docker_memory index d48b0fc8..b4cb3cc9 100755 --- a/plugins/docker/docker_memory +++ b/plugins/docker/docker_memory @@ -39,7 +39,22 @@ GPLv3 =cut -my @containers = split "\n" , `/usr/bin/docker ps --no-trunc=true`; +my $docker=`which docker`; + +if ( defined $ARGV[0] and $ARGV[0] eq "autoconf" ) { + if ($docker) { + print "yes\n"; + exit 0; + } + else{ + print "no (Docker has not been found)\n"; + exit 0; + } +} + +$docker =~ s/\s+$//; + +my @containers = split "\n" , `$docker ps --no-trunc=true`; my $result; for my $i (1 .. $#containers) From 90c4727a61019e550b8694049d399fc5e0c3e484 Mon Sep 17 00:00:00 2001 From: Lutz Reinhardt Date: Tue, 27 Oct 2015 19:28:42 +0100 Subject: [PATCH 43/73] Update zfs-filesystem-graph: using env with env the script is running with linux and bsd --- plugins/zfs/zfs-filesystem-graph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/zfs/zfs-filesystem-graph b/plugins/zfs/zfs-filesystem-graph index f5d1e8d2..7a3d1c3d 100755 --- a/plugins/zfs/zfs-filesystem-graph +++ b/plugins/zfs/zfs-filesystem-graph @@ -1,4 +1,4 @@ -#!/usr/local/bin/bash +#!/usr/bin/env bash # # Plugin to monitor a ZFS Filesystem # From d1a8965b5a1274ebb637a3f245311966ca0275dc Mon Sep 17 00:00:00 2001 From: Lars Kruse Date: Fri, 30 Oct 2015 06:09:12 +0100 Subject: [PATCH 44/73] tests: test compiling with python3 if given as shebang interpreter Currently plugins fail to compile if they use Python 3 syntax features, even though they use a correct shebang. --- t/test.t | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/test.t b/t/test.t index a12151fb..7bc55d0b 100644 --- a/t/test.t +++ b/t/test.t @@ -94,6 +94,14 @@ sub process_file { } ); } + elsif ( $interpreter =~ m{python3} ) { + run_check( + { command => [ 'python3', '-m', 'py_compile', $file ], + description => 'python3 compile', + filename => $filename + } + ); + } elsif ( $interpreter =~ m{python} ) { run_check( { command => [ 'python', '-m', 'py_compile', $file ], From 702db510b607c7b98586e61032427eb8b34642b5 Mon Sep 17 00:00:00 2001 From: Lars Kruse Date: Fri, 30 Oct 2015 20:53:20 +0100 Subject: [PATCH 45/73] add "python3" to the travis environment --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 64c7c5d7..76144bd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: perl install: - sudo apt-get update - - sudo apt-get --no-install-recommends install devscripts python ruby php5-cli gawk ksh pylint + - sudo apt-get --no-install-recommends install devscripts python python3 ruby php5-cli gawk ksh pylint - sudo apt-get --no-install-recommends install pkg-config libdb-dev libvirt-dev libexpat-dev # - Munin/Plugin.pm is in "munin-node" on precise - sudo apt-get --no-install-recommends install munin-node From 0cb53cdbd3b84b52592d2187708e88753cf40dc4 Mon Sep 17 00:00:00 2001 From: Valur Hrafn Einarsson Date: Mon, 9 Nov 2015 10:24:04 +0000 Subject: [PATCH 46/73] varnish4_: Varnish 4.1 compatibiltiy --- plugins/varnish4/README.md | 3 +++ plugins/varnish4/varnish4_ | 21 ++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/plugins/varnish4/README.md b/plugins/varnish4/README.md index c861ce3b..f8700e85 100644 --- a/plugins/varnish4/README.md +++ b/plugins/varnish4/README.md @@ -19,9 +19,12 @@ your actual plugins directory. In your plugins.conf add ``` [varnish4_*] + group varnish env.varnishstat varnishstat env.name ``` +`group varnish` Since Varnish version 4.1, Varnish shared log utilities must be run in a context with *varnish* group membership. + `env.varnishstat` can be a full path to varnishstat if it's not in the path already. diff --git a/plugins/varnish4/varnish4_ b/plugins/varnish4/varnish4_ index ed0833e1..6f56db09 100644 --- a/plugins/varnish4/varnish4_ +++ b/plugins/varnish4/varnish4_ @@ -34,6 +34,7 @@ The plugin needs to be able to execute varnishstat. The configuration section shows the defaults [varnish4_*] + group varnish env.varnishstat varnishstat env.name @@ -630,15 +631,15 @@ my %ASPECTS = ( }, 'bans_tested' => { 'type' => 'DERIVE', - 'min' => '0' + 'min' => '0' }, 'bans_obj_killed' => { 'type' => 'DERIVE', - 'min' => '0' + 'min' => '0' }, 'bans_tests_tested' => { 'type' => 'DERIVE', - 'min' => '0' + 'min' => '0' }, 'bans_dups' => { 'type' => 'GAUGE' @@ -714,24 +715,31 @@ my %ASPECTS = ( }, 'sess_drop' => { 'type' => 'DERIVE' + }, 'backend_unhealthy' => { 'type' => 'DERIVE' + }, 'fetch_failed' => { 'type' => 'DERIVE' + }, 'backend_busy' => { 'type' => 'DERIVE' + }, 'threads_failed' => { 'type' => 'DERIVE' + }, 'threads_limited' => { 'type' => 'DERIVE' + }, 'threads_destroyed' => { 'type' => 'DERIVE' + }, 'thread_queue_len' => { 'type' => 'GAUGE' @@ -741,15 +749,18 @@ my %ASPECTS = ( }, 'esi_errors' => { 'type' => 'DERIVE' + }, 'esi_warnings' => { 'type' => 'DERIVE' + }, 'sess_fail' => { 'type' => 'DERIVE' }, 'sess_pipe_overflow' => { 'type' => 'DERIVE' + } } }, @@ -778,9 +789,9 @@ my %ASPECTS = ( sub translate_type { my $d = $_[0]; - if ($d eq "i") { + if ($d eq "i" or $d eq "g") { $d = "GAUGE"; - } elsif ($d eq "a") { + } elsif ($d eq "a" or $d eq "c") { $d = "DERIVE"; } return $d; From 0f53bf5f0d495b2b5d7762d260072f59a459f9cf Mon Sep 17 00:00:00 2001 From: Valur Hrafn Einarsson Date: Mon, 9 Nov 2015 10:27:59 +0000 Subject: [PATCH 47/73] varnish4_: Remove extra linebreaks --- plugins/varnish4/varnish4_ | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugins/varnish4/varnish4_ b/plugins/varnish4/varnish4_ index 6f56db09..e1ac4601 100644 --- a/plugins/varnish4/varnish4_ +++ b/plugins/varnish4/varnish4_ @@ -715,31 +715,24 @@ my %ASPECTS = ( }, 'sess_drop' => { 'type' => 'DERIVE' - }, 'backend_unhealthy' => { 'type' => 'DERIVE' - }, 'fetch_failed' => { 'type' => 'DERIVE' - }, 'backend_busy' => { 'type' => 'DERIVE' - }, 'threads_failed' => { 'type' => 'DERIVE' - }, 'threads_limited' => { 'type' => 'DERIVE' - }, 'threads_destroyed' => { 'type' => 'DERIVE' - }, 'thread_queue_len' => { 'type' => 'GAUGE' @@ -749,11 +742,9 @@ my %ASPECTS = ( }, 'esi_errors' => { 'type' => 'DERIVE' - }, 'esi_warnings' => { 'type' => 'DERIVE' - }, 'sess_fail' => { 'type' => 'DERIVE' From 6243541d65968203311b17c9bab30582c644dab5 Mon Sep 17 00:00:00 2001 From: Lars Kruse Date: Fri, 30 Oct 2015 05:12:06 +0100 Subject: [PATCH 48/73] streaming: add configurable icecast2 plugin This icecast plugin captures a set of data similar to the "icecast2_" and "icecast2_all" plugin. Sadly both are not configurable (manual changes are required for target host and access credentials). Additionally they use the rather old-fashioned (and password restricted) xml status output. This new plugin "icecast2_stats" (written from scratch) contains the following notable differences: * runs with Python 3 * supports a real "autoconf" and "suggest" interface * is configurable (target host and port) via environment settings * uses the json status output instead of the xml data * contains no hard-coded values, names or patterns --- plugins/streaming/icecast2_stats_ | 182 ++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100755 plugins/streaming/icecast2_stats_ diff --git a/plugins/streaming/icecast2_stats_ b/plugins/streaming/icecast2_stats_ new file mode 100755 index 00000000..ea6a159c --- /dev/null +++ b/plugins/streaming/icecast2_stats_ @@ -0,0 +1,182 @@ +#!/usr/bin/python3 +# +# This plugin shows the statistics of every source currently connected to the Icecast2 server. +# See the Icecast2_ plugin for collecting data of specific mountpoints. +# +# An icecast server v2.4 or later is required for this module since it uses the status-json.xsl +# output (see http://www.icecast.org/docs/icecast-2.4.1/server-stats.html). +# +# The following data for each source is available: +# * listeners: current count of listeners +# * duration: the age of the stream/source +# +# Additionally the Icecast service uptime is available. +# +# This plugin requires Python 3 (e.g. for urllib instead of urllib2). +# +# +# Environment variables: +# * status_url: defaults to "http://localhost:8000/status-json.xsl" +# +# +# Copyright (C) 2015 Lars Kruse +# +# 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 for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# +# Magic markers +#%# capabilities=autoconf suggest +#%# family=auto + + +import datetime +import json +import os +import urllib.request +import sys + + +status_url = os.getenv("status_url", "http://localhost:8000/status-json.xsl") +PLUGIN_SCOPES = ("sources_listeners", "sources_duration", "service_uptime") +PLUGIN_NAME_PREFIX = "icecast2_stats_" + + +def clean_fieldname(name): + """ see http://munin-monitoring.org/wiki/notes_on_datasource_names + + This function is a bit clumsy as it tries to avoid using a regular + expression for the sake of micropython compatibility. + """ + def get_valid(char, position): + if char == '_': + return '_' + elif 'a' <= char.lower() <= 'z': + return char + elif (position > 0) and ('0' <= char <= '9'): + return char + else: + return '_' + return "".join([get_valid(char, position) for position, char in enumerate(name)]) + + +def parse_iso8601(datestring): + """ try to avoid using an external library for parsing an ISO8601 date string """ + if datestring.endswith("Z"): + timestamp_string = datestring[:-1] + time_delta = datetime.timedelta(minutes=0) + else: + # the "offset_text" is something like "+0500" or "-0130" + timestamp_string, offset_text = datestring[:-5], datestring[-5:] + offset_minutes = int(offset_text[1:3]) * 60 + int(offset_text[3:]) + if offset_text.startswith("+"): + pass + elif offset_text.startswith("-"): + offset_minutes *= -1 + else: + # invalid format + return None + time_delta = datetime.timedelta(minutes=offset_minutes) + local_time = datetime.datetime.strptime(timestamp_string, "%Y-%m-%dT%H:%M:%S") + return local_time + time_delta + + +def get_iso8601_age_days(datestring): + now = datetime.datetime.now() + timestamp = parse_iso8601(datestring) + if timestamp: + return (now - timestamp).total_seconds() / (24 * 60 * 60) + else: + return None + + +def _get_json_statistics(): + with urllib.request.urlopen(status_url) as conn: + json_body = conn.read() + return json.loads(json_body.decode("utf-8")) + + +def get_sources(): + sources = [] + for source in _get_json_statistics()["icestats"]["source"]: + path_name = source["listenurl"].split("/")[-1] + sources.append({"name": path_name, + "fieldname": clean_fieldname(path_name), + "listeners": source["listeners"], + "duration_days": get_iso8601_age_days(source["stream_start_iso8601"])}) + sources.sort(key=lambda item: item["name"]) + return sources + + +def get_server_uptime_days(): + return get_iso8601_age_days(_get_json_statistics()["icestats"]["server_start_iso8601"]) + + +def get_scope(): + called_name = os.path.basename(sys.argv[0]) + if called_name.startswith(PLUGIN_NAME_PREFIX): + scope = called_name[len(PLUGIN_NAME_PREFIX):] + if not scope in PLUGIN_SCOPES: + print("Invalid scope requested: {0} (expected: {1})".format(scope, "/".join(PLUGIN_SCOPES)), file=sys.stderr) + sys.exit(2) + else: + print("Invalid filename - failed to discover plugin scope", file=sys.stderr) + sys.exit(2) + return scope + + +if __name__ == "__main__": + action = sys.argv[1] if (len(sys.argv) > 1) else None + if action == "autoconf": + try: + get_sources() + print("yes") + except OSError: + print("no") + elif action == "suggest": + for scope in PLUGIN_SCOPES: + print(scope) + elif action == "config": + scope = get_scope() + if scope == "sources_listeners": + print("graph_title Total number of listeners") + print("graph_vlabel listeners") + print("graph_category Icecast") + for index, source in enumerate(get_sources()): + print("{0}.label {1}".format(source["fieldname"], source["name"])) + print("{0}.draw {1}".format(source["fieldname"], ("AREA" if (index == 0) else "STACK"))) + elif scope == "sources_duration": + print("graph_title Duration of sources") + print("graph_vlabel duration in days") + print("graph_category Icecast") + for source in get_sources(): + print("{0}.label {1}".format(source["fieldname"], source["name"])) + elif scope == "service_uptime": + print("graph_title Icecast service uptime") + print("graph_vlabel uptime in days") + print("graph_category Icecast") + print("uptime.label service uptime") + elif action is None: + scope = get_scope() + if scope == "sources_listeners": + for source in get_sources(): + print("{0}.value {1}".format(source["fieldname"], source["listeners"])) + elif scope == "sources_duration": + for source in get_sources(): + print("{0}.value {1}".format(source["fieldname"], source["duration_days"] or 0)) + elif scope == "service_uptime": + print("uptime.value {0}".format(get_server_uptime_days())) + else: + print("Invalid argument given: {0}".format(action), file=sys.stderr) + sys.exit(1) + sys.exit(0) From 66fd4a05eff5b6665abd76b79ba71661d4fbb351 Mon Sep 17 00:00:00 2001 From: Rhonda D'Vine Date: Tue, 17 Nov 2015 14:16:02 +0100 Subject: [PATCH 49/73] munin plugin for BalanceNG The script fetches the data from bng's SNMP interface and thus dynamicly adapts to bng's configuration. This means that no further configuration is needed after enabling the plugin. --- plugins/balanceng/bng | 274 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100755 plugins/balanceng/bng diff --git a/plugins/balanceng/bng b/plugins/balanceng/bng new file mode 100755 index 00000000..5515dbd9 --- /dev/null +++ b/plugins/balanceng/bng @@ -0,0 +1,274 @@ +#!/usr/bin/perl + +# munin plugin for BalanceNG +# https://www.inlab.de/load-balancer/index.html +# +# (w) 2015 by Rhonda D'Vine +# (c) 2015 by strg.at GmbH +# +# 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; version 2 dated June, +# 1991. +# +# 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 for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +use warnings; +use strict; + +use Munin::Plugin; + +my $host; + +my %servers; +my %targets; + +# we support autoconf +#%# family=auto +#%# capabilities=autoconf suggest + +# Check for multigraph capabilities +need_multigraph(); + +# Handle munin 'autoconf' command +do_autoconf() if ( $ARGV[0] && $ARGV[0] eq 'autoconf' ); + +# Fetch data values +do_fetch_values(); + +# Handle munin 'config' command +# This can only be done after getting the data +if ( defined $ARGV[0] && $ARGV[0] eq 'config' ) { + do_config(); +} + + +generate_output(); + +exit 0; + +sub do_autoconf { # {{{ check if we can offer autoconf + print (-x '/sbin/bng' ? "yes\n" : "no\n"); + exit 0; +} # }}} + +sub do_fetch_values { # {{{ fetch all the names and values needed + + ## MIBs from https://www.inlab.de/load-balancer/BalanceNG-V3-Manual.pdf + + # node name + open READ, "/sbin/bng -g .1.3.6.1.4.1.2771.1.5 |" || die "$0: can't fork bng: $!\n"; + ; + ; + $host = ; + chomp $host; + close READ; + + my $next; + + # server names + $next = '.1.3.6.1.4.1.2771.1.60.3'; + while (open READ, "/sbin/bng -n $next |") { + $next = ; + chomp $next; + + if ($next !~ /^\.1\.3\.6\.1\.4\.1\.2771\.1\.60\.3/) { + close READ; + last; + } + + ; + my $name = ; + chomp $name; + push @{$servers{name}}, clean_fieldname($name); + + close READ; + } + + # server sent bytes + $next = '.1.3.6.1.4.1.2771.1.60.15'; + while (open READ, "/sbin/bng -n $next |") { + $next = ; + chomp $next; + + if ($next !~ /^\.1\.3\.6\.1\.4\.1\.2771\.1\.60\.15/) { + close READ; + last; + } + + ; + my $name = ; + chomp $name; + push @{$servers{sent}}, $name; + $servers{senttotal} += $name; + + close READ; + } + + # server recv bytes + $next = '.1.3.6.1.4.1.2771.1.60.17'; + while (open READ, "/sbin/bng -n $next |") { + $next = ; + chomp $next; + + if ($next !~ /^\.1\.3\.6\.1\.4\.1\.2771\.1\.60\.17/) { + close READ; + last; + } + + ; + my $name = ; + chomp $name; + push @{$servers{recv}}, $name; + $servers{recvtotal} += $name; + + close READ; + } + + # target names + $next = '.1.3.6.1.4.1.2771.1.70.3'; + while (open READ, "/sbin/bng -n $next |") { + $next = ; + chomp $next; + + if ($next !~ /^\.1\.3\.6\.1\.4\.1\.2771\.1\.70\.3/) { + close READ; + last; + } + + ; + my $name = ; + chomp $name; + push @{$targets{name}}, clean_fieldname($name); + + close READ; + } + + # target sent bytes + $next = '.1.3.6.1.4.1.2771.1.70.27'; + while (open READ, "/sbin/bng -n $next |") { + $next = ; + chomp $next; + + if ($next !~ /^\.1\.3\.6\.1\.4\.1\.2771\.1\.70\.27/) { + close READ; + last; + } + + ; + my $name = ; + chomp $name; + push @{$targets{sent}}, $name; + $targets{senttotal} += $name; + + close READ; + } + + # target recv bytes + $next = '.1.3.6.1.4.1.2771.1.70.29'; + while (open READ, "/sbin/bng -n $next |") { + $next = ; + chomp $next; + + if ($next !~ /^\.1\.3\.6\.1\.4\.1\.2771\.1\.70\.29/) { + close READ; + last; + } + + ; + my $name = ; + chomp $name; + push @{$targets{recv}}, $name; + $targets{recvtotal} += $name; + + close READ; + } + +} # }}} + +sub do_config { # {{{ write out the configuration + print <<"EOF"; +multigraph bng +graph_title Bng Throughput +graph_order sent recv +graph_args --base 1000 -l 0 +graph_vlabel Packets/\${graph_period} +graph_category network +graph_info this graph shows the outbound traffic for $host + +sent.label Sent +sent.draw AREA +sent.type DERIVE +sent.min 0 +recv.label Received +recv.draw AREA +recv.type DERIVE +recv.min 0 + +EOF + + foreach my $interface (@{$servers{name}}, @{$targets{name}}) { + print <<"EOF"; +multigraph bng.${interface} + +graph_title Interface $interface traffic +graph_order sent recv +graph_args --base 1000 -l 0 +graph_vlabel Packets/\${graph_period} +graph_category network +graph_info this graph shows the total traffic for ${interface} + +sent.label ${interface}_Sent +sent.draw AREA +sent.type DERIVE +sent.min 0 +recv.label ${interface}_Received +recv.draw AREA +recv.type DERIVE +recv.min 0 + +EOF + } + + exit 0; +} # }}} + +sub generate_output { # {{{ write out the data + print <<"EOF"; +multigraph bng +sent.value $servers{senttotal} +recv.value $servers{recvtotal} +EOF + + my $i; + $i = 0; + foreach my $interface (@{$servers{name}}) { + print <<"EOF"; +multigraph bng.${interface} +sent.value $servers{sent}[$i] +recv.value $servers{recv}[$i] +EOF + $i++; + } + + $i = 0; + foreach my $interface (@{$targets{name}}) { + print <<"EOF"; +multigraph bng.${interface} +sent.value $targets{sent}[$i] +recv.value $targets{recv}[$i] +EOF + $i++; + } + +} # }}} + +# vim:fdm=marker: From 7d58abe5c53f41010a78ccbcd2a38978b459cee3 Mon Sep 17 00:00:00 2001 From: Johann Schmitz Date: Wed, 2 Dec 2015 06:31:52 +0100 Subject: [PATCH 50/73] Added instance name for gunicorn_memory_status plugin if linked as gunicorn_memory_status_INSTANCE or gunicorn_INSTANCE_memory_status --- plugins/gunicorn/gunicorn_memory_status | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/gunicorn/gunicorn_memory_status b/plugins/gunicorn/gunicorn_memory_status index 49e8719c..4cdbb58b 100755 --- a/plugins/gunicorn/gunicorn_memory_status +++ b/plugins/gunicorn/gunicorn_memory_status @@ -22,7 +22,7 @@ """ -import sys, os +import sys, os, re from subprocess import check_output # set path to your gunicorn pid @@ -44,11 +44,9 @@ class GunicornMemoryStatus(): except: raise Exception("Couldn't read gunicorn pid information") - def print_total_memory(self): print ('total_memory.value %d' % self._get_total_memory()) - def _get_master_pid(self): master_pid_file = open(GUNICORN_PID_PATH) self.master_pid = master_pid_file.read().rstrip() @@ -74,7 +72,18 @@ class GunicornMemoryStatus(): return worker_memory_usage def print_config(): - print "graph_title Gunicorn - Memory Usage" + instance = None + name = os.path.basename(sys.argv[0]) + if name != "gunicorn_memory_status": + for r in ("^gunicorn_(.*?)_memory_status$", "^gunicorn_memory_status_(.*?)$"): + m = re.match(r, name, re.IGNORECASE) + if m: + instance = m.group(1) + break + graph_title = "graph_title Gunicorn - Memory Usage" + if instance: + graph_title = "%s - %s" % (graph_title, instance) + print graph_title print "graph_args --base 1024 -l 0" print "graph_vlabel Megabytes" print "graph_category gunicorn" From 0c7bdc92c42c5bf58f9fdef7c03578341f37e447 Mon Sep 17 00:00:00 2001 From: Johann Schmitz Date: Wed, 2 Dec 2015 06:35:24 +0100 Subject: [PATCH 51/73] Added instance name for gunicorn_status plugin if linked as gunicorn_status_INSTANCE or gunicorn_INSTANCE_status --- plugins/gunicorn/gunicorn_status | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/plugins/gunicorn/gunicorn_status b/plugins/gunicorn/gunicorn_status index 2c9fc888..78cd48d8 100755 --- a/plugins/gunicorn/gunicorn_status +++ b/plugins/gunicorn/gunicorn_status @@ -21,7 +21,7 @@ """ -import sys, os +import sys, os, re from subprocess import check_output from time import sleep @@ -87,7 +87,18 @@ class GunicornStatus(): return user_time + kernel_time def print_config(): - print "graph_title Gunicorn - Status" + instance = None + name = os.path.basename(sys.argv[0]) + if name != "gunicorn_status": + for r in ("^gunicorn_(.*?)_status$", "^gunicorn_status_(.*?)$"): + m = re.match(r, name, re.IGNORECASE) + if m: + instance = m.group(1) + break + graph_title = "graph_title Gunicorn - Status" + if instance: + graph_title = "%s - %s" % (graph_title, instance) + print graph_title print "graph_args -l 0" print "graph_vlabel Number of workers" print "graph_category gunicorn" From b1f8b9f03e8860b5325e2cccaf9ad4f22bde0295 Mon Sep 17 00:00:00 2001 From: Fran Rodriguez Date: Tue, 15 Dec 2015 14:55:30 +0100 Subject: [PATCH 52/73] Remove old params --- plugins/hhvm/hhvm_ | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/hhvm/hhvm_ b/plugins/hhvm/hhvm_ index 42419999..e3db61be 100755 --- a/plugins/hhvm/hhvm_ +++ b/plugins/hhvm/hhvm_ @@ -40,7 +40,7 @@ server { =head2 APACHE 2.2 CONFIG FastCgiExternalServer /var/run/hhvm_admin.fcgi -host 127.0.0.1:8080 -Listen 127.0.0.1:8081 +Listen 127.0.0.1:8081 Alias /check-health /var/run/hhvm_admin.fcgi Alias /status.json /var/run/hhvm_admin.fcgi @@ -107,8 +107,6 @@ tc-profsize.label TC Profiling Size tc-profsize.info Translation Cache Profiling Size tc-coldsize.label TC Cold Size tc-coldsize.info Translation Cache Cold Size -tc-trampolinessize.label TC Trampoline Size -tc-trampolinessize.info Translation Cache Trampoline Size tc-frozensize.label TC Frozen Size tc-frozensize.info Translation Cache Frozen Size rds.label RDS Used Bytes @@ -121,7 +119,7 @@ EOF my $status = getJson( sprintf( '%s/status.json', $url ) ); printf( "multigraph hhvm_%s_threads\n", $graphName ); - printf( "threads.value %d\n", int( @{ $status->{'status'}->{'threads'} } ) ); + printf( "threads.value %d\n", int( @{ $status->{'status'}->{'threads'} } ) ); printf( "load.value %d\n", $health->{'load'} ); printf( "queued.value %d\n", $health->{'queued'} ); print "\n"; @@ -131,7 +129,6 @@ EOF printf( "tc-size.value %d\n", $health->{'tc-size'} ); printf( "tc-profsize.value %d\n", $health->{'tc-profsize'} ); printf( "tc-coldsize.value %d\n", $health->{'tc-coldsize'} ); - printf( "tc-trampolinessize.value %d\n", $health->{'tc-trampolinessize'} ); printf( "tc-frozensize.value %d\n", $health->{'tc-frozensize'} ); printf( "rds.value %d\n", $health->{'rds'} ); printf( "units.value %d\n", $health->{'units'} ); @@ -151,4 +148,4 @@ sub getJson( $ ) { die( sprintf( "Could not decode json from '%s'.", $url ) ); } return $data; -} \ No newline at end of file +} From 0a5f6adf2cf5209a9b7a543f39a3ac4125b19945 Mon Sep 17 00:00:00 2001 From: Samuel Cantero Date: Mon, 4 Jan 2016 22:42:47 -0300 Subject: [PATCH 53/73] Docker Plugin Ignore containers in weird state. --- plugins/docker/docker_cpu | 20 ++++++++++++-------- plugins/docker/docker_memory | 10 ++++++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/plugins/docker/docker_cpu b/plugins/docker/docker_cpu index c9b7e642..63165a11 100755 --- a/plugins/docker/docker_cpu +++ b/plugins/docker/docker_cpu @@ -66,14 +66,18 @@ for my $i (1 .. $#containers) $name =~ s/[-\+*\/\.]/_/g; # truncate container name with "," character. $name =~ s/,.*//g; - open(my $file, '<', "/sys/fs/cgroup/cpuacct/docker/$id/cpuacct.usage") or die "Unable to open file, $!"; - my $total_cpu_ns = <$file>; - $total_cpu_ns =~ s/\s+$//; - close $file; - open($file, '<', "/sys/fs/cgroup/cpuacct/docker/$id/cpuacct.usage_percpu") or die "Unable to open file, $!"; - my @ncpu = split / /, <$file>; - close $file; - push @result, {'name'=>$name, 'total_cpu_ns'=>$total_cpu_ns, 'ncpu'=>$#ncpu}; + if (open(my $file, '<', "/sys/fs/cgroup/cpuacct/docker/$id/cpuacct.usage")) + { + my $total_cpu_ns = <$file>; + $total_cpu_ns =~ s/\s+$//; + close $file; + if (open($file, '<', "/sys/fs/cgroup/cpuacct/docker/$id/cpuacct.usage_percpu")) + { + my @ncpu = split / /, <$file>; + close $file; + push @result, {'name'=>$name, 'total_cpu_ns'=>$total_cpu_ns, 'ncpu'=>$#ncpu}; + } + } } if (defined $ARGV[0] and $ARGV[0] eq "config") diff --git a/plugins/docker/docker_memory b/plugins/docker/docker_memory index b4cb3cc9..1d848043 100755 --- a/plugins/docker/docker_memory +++ b/plugins/docker/docker_memory @@ -66,10 +66,12 @@ for my $i (1 .. $#containers) $name =~ s/[-\+*\/\.]/_/g; # truncate container name with "," character. $name =~ s/,.*//g; - open(my $file, '<', "/sys/fs/cgroup/memory/docker/$id/memory.usage_in_bytes") or die "Unable to open file, $!"; - my $memory_bytes = <$file>; - $memory_bytes =~ s/\s+$//; - push @result, {'name'=>$name, 'memory_bytes'=>$memory_bytes}; + if (open(my $file, '<', "/sys/fs/cgroup/memory/docker/$id/memory.usage_in_bytes")) + { + my $memory_bytes = <$file>; + $memory_bytes =~ s/\s+$//; + push @result, {'name'=>$name, 'memory_bytes'=>$memory_bytes}; + } } if (defined $ARGV[0] and $ARGV[0] eq "config") From 40cc1233c635c94b9e1d4fe8a41800eeb675be91 Mon Sep 17 00:00:00 2001 From: Jyrki Muukkonen Date: Tue, 26 Jan 2016 09:24:56 +0200 Subject: [PATCH 54/73] varnish4: lower limit 0 in hit rate graph args As usual for percentage based graphs (cpu, df etc). Otherwise it might autoscale and look weird. --- plugins/varnish4/varnish4_ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/varnish4/varnish4_ b/plugins/varnish4/varnish4_ index ed0833e1..b3f1fb3d 100644 --- a/plugins/varnish4/varnish4_ +++ b/plugins/varnish4/varnish4_ @@ -232,7 +232,7 @@ my %ASPECTS = ( 'order' => 'client_req cache_hit cache_miss ' . 'cache_hitpass' , 'vlabel' => '%', - 'args' => '-u 100 --rigid', + 'args' => '-l 0 -u 100 --rigid', 'scale' => 'no', 'values' => { 'client_req' => { From 46fd4dd47f852decee94197c86909d7e8e2849a2 Mon Sep 17 00:00:00 2001 From: Aron Novak Date: Wed, 10 Feb 2016 14:11:59 +0100 Subject: [PATCH 55/73] deb_packages: fix the README by specifying the proper filename for the plugin and document a way to easily verify the installation of the plugin --- plugins/apt/deb_packages/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/apt/deb_packages/README.md b/plugins/apt/deb_packages/README.md index 1ccffd40..56b60ff3 100644 --- a/plugins/apt/deb_packages/README.md +++ b/plugins/apt/deb_packages/README.md @@ -29,9 +29,13 @@ check out this git repository from aptitude install python-apt git clone git://github.com/munin-monitoring/contrib.git cd contrib/plugins/apt/deb_packages - sudo cp deb_packages.py /etc/munin/plugins + sudo cp deb_packages.py /etc/munin/plugins/deb_packages sudo cp deb_packages.munin-conf /etc/munin/plugin-conf.d/deb_packages +Verify the installation by + + sudo munin-run deb_packages + ### Configuration If you copied deb_packages.munin-conf to plugin-conf.d you have a starting point. A typical configuration looks like this From 7033527d3f4d6a67eeafc9f04bbd0143a870d1e8 Mon Sep 17 00:00:00 2001 From: ka7 at github Date: Sun, 20 Mar 2016 13:28:39 +0100 Subject: [PATCH 56/73] adding WARNING and CRITICAL config options to backup AGE --- plugins/backuppc/backuppc | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/plugins/backuppc/backuppc b/plugins/backuppc/backuppc index c7db2299..ae4082a9 100755 --- a/plugins/backuppc/backuppc +++ b/plugins/backuppc/backuppc @@ -4,6 +4,10 @@ # [backuppc] # user backuppc # env.pcdir /var/lib/BackupPC/pc +# env.full_warning 10 # warn if last backup older than N days +# env.full_critical 20 # critical if last backup older than N days +# env.incr_warning 1 # warn if last backup older than N days +# env.incr_critical 3 # critical if last backup older than N days # #%# family=backuppc #%# capabilities=autoconf @@ -28,8 +32,8 @@ if [ "$1" = "config" ]; then for h in ${HOSTS} do - echo "$(clean_fieldname ${h})_full.label $(clean_fieldname ${h}) Full" - echo "$(clean_fieldname ${h})_incr.label $(clean_fieldname ${h}) Incr" + echo "$(clean_fieldname ${h})_size_full.label $(clean_fieldname ${h}) Full" + echo "$(clean_fieldname ${h})_size_incr.label $(clean_fieldname ${h}) Incr" done echo "multigraph backuppc_ages" @@ -40,8 +44,20 @@ if [ "$1" = "config" ]; then for h in ${HOSTS} do - echo "$(clean_fieldname ${h})_full.label $(clean_fieldname ${h}) Full" - echo "$(clean_fieldname ${h})_incr.label $(clean_fieldname ${h}) Incr" + echo "$(clean_fieldname ${h})_age_full.label $(clean_fieldname ${h}) Full" + echo "$(clean_fieldname ${h})_age_incr.label $(clean_fieldname ${h}) Incr" + if [ -n "$full_warning" ]; then + echo "$(clean_fieldname ${h})_age_full.warning $full_warning" + fi + if [ -n "$incr_warning" ]; then + echo "$(clean_fieldname ${h})_age_incr.warning $incr_warning" + fi + if [ -n "$full_critical" ]; then + echo "$(clean_fieldname ${h})_age_full.critical $full_critical" + fi + if [ -n "$incr_critical" ]; then + echo "$(clean_fieldname ${h})_age_incr.critical $incr_critical" + fi done exit 0 @@ -51,18 +67,18 @@ echo "multigraph backuppc_sizes" for h in $HOSTS do SIZE=$(awk '/full/ { size = $6 } END { print size; }' ${PCDIR}/${h}/backups) - echo "$(clean_fieldname ${h})_full.value $SIZE" + echo "$(clean_fieldname ${h})_size_full.value $SIZE" SIZE=$(awk '/incr/ { size = $6 } END { print size; }' ${PCDIR}/${h}/backups) - echo "$(clean_fieldname ${h})_incr.value $SIZE" + echo "$(clean_fieldname ${h})_size_incr.value $SIZE" done echo "multigraph backuppc_ages" for h in $HOSTS do SIZE=$(awk '/full/ { age = systime() - $3 } END { print age / 3600 / 24; }' ${PCDIR}/${h}/backups) - echo "$(clean_fieldname ${h})_full.value $SIZE" + echo "$(clean_fieldname ${h})_age_full.value $SIZE" SIZE=$(awk '/incr/ { age = systime() - $3 } END { print age / 3600 / 24; }' ${PCDIR}/${h}/backups) - echo "$(clean_fieldname ${h})_incr.value $SIZE" + echo "$(clean_fieldname ${h})_age_incr.value $SIZE" done <<'__END__' @@ -150,3 +166,5 @@ one per row. The columns are: mangle Set if this backup has mangled file names and attributes. Always true for backups in v1.4.0 and above. False for all backups prior to v1.4.0. + +__END__ From c9a5d6099ee9025f16ec1a3e62a7a6c8aa457402 Mon Sep 17 00:00:00 2001 From: ka7 at github Date: Sun, 20 Mar 2016 13:39:40 +0100 Subject: [PATCH 57/73] backuppc: undo renaming fields to _age_ _size_ (would break existing setups) --- plugins/backuppc/backuppc | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/backuppc/backuppc b/plugins/backuppc/backuppc index ae4082a9..222771e2 100755 --- a/plugins/backuppc/backuppc +++ b/plugins/backuppc/backuppc @@ -4,10 +4,10 @@ # [backuppc] # user backuppc # env.pcdir /var/lib/BackupPC/pc -# env.full_warning 10 # warn if last backup older than N days -# env.full_critical 20 # critical if last backup older than N days -# env.incr_warning 1 # warn if last backup older than N days -# env.incr_critical 3 # critical if last backup older than N days +# env.full_warning 10 # warn if FULL backup older than N days +# env.full_critical 20 # critical if FULL backup older than N days +# env.incr_warning 1 # warn if INCR backup older than N days +# env.incr_critical 3 # critical if INCR backup older than N days # #%# family=backuppc #%# capabilities=autoconf @@ -32,8 +32,8 @@ if [ "$1" = "config" ]; then for h in ${HOSTS} do - echo "$(clean_fieldname ${h})_size_full.label $(clean_fieldname ${h}) Full" - echo "$(clean_fieldname ${h})_size_incr.label $(clean_fieldname ${h}) Incr" + echo "$(clean_fieldname ${h})_full.label $(clean_fieldname ${h}) Full" + echo "$(clean_fieldname ${h})_incr.label $(clean_fieldname ${h}) Incr" done echo "multigraph backuppc_ages" @@ -44,19 +44,19 @@ if [ "$1" = "config" ]; then for h in ${HOSTS} do - echo "$(clean_fieldname ${h})_age_full.label $(clean_fieldname ${h}) Full" - echo "$(clean_fieldname ${h})_age_incr.label $(clean_fieldname ${h}) Incr" + echo "$(clean_fieldname ${h})_full.label $(clean_fieldname ${h}) Full" + echo "$(clean_fieldname ${h})_incr.label $(clean_fieldname ${h}) Incr" if [ -n "$full_warning" ]; then - echo "$(clean_fieldname ${h})_age_full.warning $full_warning" + echo "$(clean_fieldname ${h})_full.warning $full_warning" fi if [ -n "$incr_warning" ]; then - echo "$(clean_fieldname ${h})_age_incr.warning $incr_warning" + echo "$(clean_fieldname ${h})_incr.warning $incr_warning" fi if [ -n "$full_critical" ]; then - echo "$(clean_fieldname ${h})_age_full.critical $full_critical" + echo "$(clean_fieldname ${h})_full.critical $full_critical" fi if [ -n "$incr_critical" ]; then - echo "$(clean_fieldname ${h})_age_incr.critical $incr_critical" + echo "$(clean_fieldname ${h})_incr.critical $incr_critical" fi done @@ -67,18 +67,18 @@ echo "multigraph backuppc_sizes" for h in $HOSTS do SIZE=$(awk '/full/ { size = $6 } END { print size; }' ${PCDIR}/${h}/backups) - echo "$(clean_fieldname ${h})_size_full.value $SIZE" + echo "$(clean_fieldname ${h})_full.value $SIZE" SIZE=$(awk '/incr/ { size = $6 } END { print size; }' ${PCDIR}/${h}/backups) - echo "$(clean_fieldname ${h})_size_incr.value $SIZE" + echo "$(clean_fieldname ${h})_incr.value $SIZE" done echo "multigraph backuppc_ages" for h in $HOSTS do SIZE=$(awk '/full/ { age = systime() - $3 } END { print age / 3600 / 24; }' ${PCDIR}/${h}/backups) - echo "$(clean_fieldname ${h})_age_full.value $SIZE" + echo "$(clean_fieldname ${h})_full.value $SIZE" SIZE=$(awk '/incr/ { age = systime() - $3 } END { print age / 3600 / 24; }' ${PCDIR}/${h}/backups) - echo "$(clean_fieldname ${h})_age_incr.value $SIZE" + echo "$(clean_fieldname ${h})_incr.value $SIZE" done <<'__END__' From 855c31d8ba7fee6c7de5ffb6cf5361c2bf5687fd Mon Sep 17 00:00:00 2001 From: clarkspark Date: Tue, 22 Mar 2016 16:17:24 -0400 Subject: [PATCH 58/73] remove trailing whitespace --- plugins/prosody/prosody_ | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/prosody/prosody_ b/plugins/prosody/prosody_ index 92340439..ae8c35ab 100644 --- a/plugins/prosody/prosody_ +++ b/plugins/prosody/prosody_ @@ -25,7 +25,7 @@ import os import telnetlib import re -def main(): +def main(): try: mode = sys.argv[1] except IndexError: @@ -33,7 +33,7 @@ def main(): wildcard = sys.argv[0].split("prosody_")[1].split("_")[0] host = os.environ.get('host', 'localhost') port = int(os.environ.get('port', 5582)) - + if mode == "suggest": print "c2s" print "s2s" @@ -47,7 +47,7 @@ def main(): print "graph_title Prosody C2S Connections" print "graph_vlabel users" print "graph_category Prosody" - + print "all_client_connections.label client connections" print "secure_client_connections.label secure client connections" print "insecure_client_connections.label insecure client " \ @@ -64,7 +64,7 @@ def main(): secure_client_connections = int(parsed_info[0]) print "secure_client_connections.value %s" % \ (secure_client_connections) - + telnet.write("c2s:show_insecure()\n") telnet_response = telnet.read_until("insecure client connections", 5) @@ -76,18 +76,18 @@ def main(): insecure_client_connections print "all_client_connections.value %s" % (all_client_connections) telnet.write("quit") - + elif wildcard == "s2s": if mode == "config": print "graph_title Prosody S2S Connections" print "graph_vlabel servers" print "graph_category Prosody" - + print "outgoing_connections.label outgoing connections" print "incoming_connections.label incoming connections" sys.exit(0) - - else: + + else: server_connections_re = re.compile(r"(\d+) outgoing, (\d+)") telnet = telnetlib.Telnet(host, port) telnet.write("s2s:show()\n") @@ -96,7 +96,7 @@ def main(): print "outgoing_connections.value %s" % (parsed_info[0][0]) print "incoming_connections.value %s" % (parsed_info[0][1]) telnet.write("quit") - + elif wildcard == "presence": if mode == "config": print "graph_title Prosody Client Presence" @@ -110,7 +110,7 @@ def main(): print "dnd.label Do Not Disturb Clients" sys.exit(0) - else: + else: client_presence_re = re.compile(r"- (.*?)\(\d+\)") telnet = telnetlib.Telnet(host, port) telnet.write("c2s:show()\n") From bc53425e8c6f369f6147665e8c84e83ea994785f Mon Sep 17 00:00:00 2001 From: clarkspark Date: Tue, 22 Mar 2016 16:18:28 -0400 Subject: [PATCH 59/73] close telnet session --- plugins/prosody/prosody_ | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/prosody/prosody_ b/plugins/prosody/prosody_ index ae8c35ab..e5821a53 100644 --- a/plugins/prosody/prosody_ +++ b/plugins/prosody/prosody_ @@ -75,7 +75,7 @@ def main(): all_client_connections = secure_client_connections + \ insecure_client_connections print "all_client_connections.value %s" % (all_client_connections) - telnet.write("quit") + telnet.write("quit\n") elif wildcard == "s2s": if mode == "config": @@ -95,7 +95,7 @@ def main(): parsed_info = server_connections_re.findall(telnet_response) print "outgoing_connections.value %s" % (parsed_info[0][0]) print "incoming_connections.value %s" % (parsed_info[0][1]) - telnet.write("quit") + telnet.write("quit\n") elif wildcard == "presence": if mode == "config": @@ -121,7 +121,7 @@ def main(): print "away.value %s" % (parsed_info.count("away")) print "xa.value %s" % (parsed_info.count("xa")) print "dnd.value %s" % (parsed_info.count("dnd")) - telnet.write("quit") + telnet.write("quit\n") elif wildcard == "uptime": if mode == "config": @@ -147,7 +147,7 @@ def main(): uptime_value = float(parsed_info[0]) + float(parsed_info[1])/24 +\ float(parsed_info[2])/60/24 print "uptime.value %s" % (uptime_value) - telnet.write("quit") + telnet.write("quit\n") elif wildcard == "users": if mode == "config": From a816c917b610e238a2bb82e84abb1175bf62b0ac Mon Sep 17 00:00:00 2001 From: clarkspark Date: Tue, 22 Mar 2016 16:19:19 -0400 Subject: [PATCH 60/73] get client presence for prosody 0.10 --- plugins/prosody/prosody_ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/prosody/prosody_ b/plugins/prosody/prosody_ index e5821a53..17d3da29 100644 --- a/plugins/prosody/prosody_ +++ b/plugins/prosody/prosody_ @@ -111,7 +111,7 @@ def main(): sys.exit(0) else: - client_presence_re = re.compile(r"- (.*?)\(\d+\)") + client_presence_re = re.compile(r"[-\]] (.*?)\(\d+\)") telnet = telnetlib.Telnet(host, port) telnet.write("c2s:show()\n") telnet_response = telnet.read_until("clients", 5) From 0b849f0eb2f11e034273105baaf30d6992d2b278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gajdos=20Tam=C3=A1s?= Date: Sun, 3 Apr 2016 23:31:24 +0200 Subject: [PATCH 61/73] Update xfs plugin. There is a new line "fibt2" line in the xfs stat output. I updated the plugin to process this line also. (Reference for the counter names)[http://lxr.free-electrons.com/source/fs/xfs/xfs_stats.h#L184] --- plugins/disk/xfs | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/disk/xfs b/plugins/disk/xfs index 94a7cb53..02fc827b 100755 --- a/plugins/disk/xfs +++ b/plugins/disk/xfs @@ -46,6 +46,7 @@ my %runtime_stats = ( "vnodes" => ["vn_active","vn_alloc","vn_get","vn_hold","vn_rele","vn_reclaim","vn_remove","vn_free"], "buf" => ["xb_get","xb_create","xb_get_locked","xb_get_locked_waited","xb_busy_locked","xb_miss_locked","xb_page_retries","xb_page_found","xb_get_read"], "ibt2" => ["xs_ibt_2_lookup","xs_ibt_2_compare","xs_ibt_2_insrec","xs_ibt_2_delrec","xs_ibt_2_newroot","xs_ibt_2_killroot","xs_ibt_2_increment","xs_ibt_2_decrement","xs_ibt_2_lshift","xs_ibt_2_rshift","xs_ibt_2_split","xs_ibt_2_join","xs_ibt_2_alloc","xs_ibt_2_free","xs_ibt_2_moves"], + "fibt2" => ["xs_fibt_2_lookup","xs_fibt_2_compare","xs_fibt_2_insrec","xs_fibt_2_delrec","xs_fibt_2_newroot","xs_fibt_2_killroot","xs_fibt_2_increment","xs_fibt_2_decrement","xs_fibt_2_lshift","xs_fibt_2_rshift","xs_fibt_2_split","xs_fibt_2_join","xs_fibt_2_alloc","xs_fibt_2_free","xs_fibt_2_moves"], "bmbt2" => ["xs_bmbt_2_lookup","xs_bmbt_2_compare","xs_bmbt_2_insrec","xs_bmbt_2_delrec","xs_bmbt_2_newroot","xs_bmbt_2_killroot","xs_bmbt_2_increment","xs_bmbt_2_decrement","xs_bmbt_2_lshift","xs_bmbt_2_rshift","xs_bmbt_2_split","xs_bmbt_2_join","xs_bmbt_2_alloc","xs_bmbt_2_free","xs_bmbt_2_moves"], "abtc2" => ["xs_abtc_2_lookup","xs_abtc_2_compare","xs_abtc_2_insrec","xs_abtc_2_delrec","xs_abtc_2_newroot","xs_abtc_2_killroot","xs_abtc_2_increment","xs_abtc_2_decrement","xs_abtc_2_lshift","xs_abtc_2_rshift","xs_abtc_2_split","xs_abtc_2_join","xs_abtc_2_alloc","xs_abtc_2_free","xs_abtc_2_moves"], "abtb2" => ["xs_abtb_2_lookup","xs_abtb_2_compare","xs_abtb_2_insrec","xs_abtb_2_delrec","xs_abtb_2_newroot","xs_abtb_2_killroot","xs_abtb_2_increment","xs_abtb_2_decrement","xs_abtb_2_lshift","xs_abtb_2_rshift","xs_abtb_2_split","xs_abtb_2_join","xs_abtb_2_alloc","xs_abtb_2_free","xs_abtb_2_moves"], From c3cf6b45d32725419d806a5f64bb08eadf7763a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Wed, 23 Mar 2016 22:33:22 -0300 Subject: [PATCH 62/73] php_errors handle multiple logs --- plugins/php/php_errors | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/plugins/php/php_errors b/plugins/php/php_errors index b38ce74c..315c0f6f 100644 --- a/plugins/php/php_errors +++ b/plugins/php/php_errors @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Plugin to monitor error.log from apache server. # Revision 0.1 2011/06/17 12:00:00 Ulrich Lusseau @@ -14,27 +14,28 @@ #%# capabilities=autoconf # config example for /etc/munin/plugin-conf.d/munin-node #[apache_log] -#user root -#env.logfile /home/newsite/logs/errors.log +#user www-data +#env.logfile /home/newsite/logs/errors.log /var/log/php/otherlog.log # -LOG=${logfile:-/var/log/apache2/error.log} +LOGS=${logfile:-/var/log/apache2/error.log} if [ "$1" = "autoconf" ]; then - if [ -r "$LOG" ]; then - echo yes - exit 0 - else - echo no - exit 1 + for LOG in $LOGS; do + if [[ ! -r $LOGS ]]; then + echo no + exit 1 fi + done + + echo yes + exit 0 fi if [ "$1" = "config" ]; then - - echo 'graph_title PHP Errors from ' $LOG + echo 'graph_title PHP Errors from ' $LOGS echo 'graph_args --base 1000 -l 0' echo 'graph_vlabel Errors' echo 'LogWarning.label PHP Warning errors' @@ -49,4 +50,4 @@ awk 'BEGIN{c["LogWarning"]=0;c["LogNotice"]=0;c["LogFatal"]=0;c["LogFile"]=0; } /PHP Notice/{c["LogNotice"]++} /PHP Fatal error/{c["LogFatal"]++} /File does not exist/{c["LogFile"]++} - END{for(i in c){print i".value " c[i]} }' < $LOG + END{for(i in c){print i".value " c[i]} }' $LOGS From c324c34ea7a1890b7cfd6d58498abeaabf82a626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Wed, 23 Mar 2016 22:42:47 -0300 Subject: [PATCH 63/73] php_errors: coding-style/bash --- plugins/php/php_errors | 71 +++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/plugins/php/php_errors b/plugins/php/php_errors index 315c0f6f..4e7efe8e 100644 --- a/plugins/php/php_errors +++ b/plugins/php/php_errors @@ -1,28 +1,38 @@ #!/bin/bash -# -# Plugin to monitor error.log from apache server. -# Revision 0.1 2011/06/17 12:00:00 Ulrich Lusseau -# Initial revision -# -# Parameters: -# -# config (required) -# autoconf (optional - used by munin-config) -# -# Magick markers (optional): -#%# family=auto -#%# capabilities=autoconf -# config example for /etc/munin/plugin-conf.d/munin-node -#[apache_log] -#user www-data -#env.logfile /home/newsite/logs/errors.log /var/log/php/otherlog.log -# - - + +: << =cut + +=head1 NAME + +Plugin to monitor error.log from apache server + +=head1 CONFIGURATION + +[apache_log] + user www-data + env.logfile /home/newsite/logs/errors.log /var/log/php/otherlog.log + +=head1 AUTHOR + +Raphaël Droz + +Revision 0.2 2016/03/23 22:00:00 Raphaël Droz +Revision 0.1 2011/06/17 12:00:00 Ulrich Lusseau + +=head1 MAGICK MARKERS + + #%# family=auto + #%# capabilities=autoconf + +=cut + + +. $MUNIN_LIBDIR/plugins/plugin.sh + LOGS=${logfile:-/var/log/apache2/error.log} -if [ "$1" = "autoconf" ]; then +if [[ $1 == autoconf ]]; then for LOG in $LOGS; do if [[ ! -r $LOGS ]]; then echo no @@ -34,7 +44,7 @@ if [ "$1" = "autoconf" ]; then exit 0 fi -if [ "$1" = "config" ]; then +if [[ $1 == config ]]; then echo 'graph_title PHP Errors from ' $LOGS echo 'graph_args --base 1000 -l 0' echo 'graph_vlabel Errors' @@ -44,10 +54,13 @@ if [ "$1" = "config" ]; then echo 'LogFile.label File does not exist errors' exit 0 fi - -awk 'BEGIN{c["LogWarning"]=0;c["LogNotice"]=0;c["LogFatal"]=0;c["LogFile"]=0; } - /PHP Warning/{c["LogWarning"]++} - /PHP Notice/{c["LogNotice"]++} - /PHP Fatal error/{c["LogFatal"]++} - /File does not exist/{c["LogFile"]++} - END{for(i in c){print i".value " c[i]} }' $LOGS + +awk -f - $LOGS < Date: Wed, 23 Mar 2016 23:10:03 -0300 Subject: [PATCH 64/73] php_errors plugin is multi-instance compatible --- plugins/php/{php_errors => php_errors_} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/php/{php_errors => php_errors_} (100%) diff --git a/plugins/php/php_errors b/plugins/php/php_errors_ similarity index 100% rename from plugins/php/php_errors rename to plugins/php/php_errors_ From 71f28aaf6382d8ca51b716c1b04d0b9889df21b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Fri, 8 Jan 2016 13:32:18 -0300 Subject: [PATCH 65/73] added apache_cache_disk_count --- plugins/apache/apache_cache_disk_count | 123 +++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100755 plugins/apache/apache_cache_disk_count diff --git a/plugins/apache/apache_cache_disk_count b/plugins/apache/apache_cache_disk_count new file mode 100755 index 00000000..e3f1c807 --- /dev/null +++ b/plugins/apache/apache_cache_disk_count @@ -0,0 +1,123 @@ +#!/bin/bash + +: << =cut + +=head1 NAME + +Munin plugin to monitor apache mod_cache_disk usage. + +=head1 CONFIGURATION + +[apache_cache_disk_count] + user www-data + env.cache_path /var/cache/apache2/mod_cache_disk + env.strings css js + env.label_cs CSS + env.colour_css FFFF00 + env.label_js JS + env.colour_js FF0000 + +=head1 AUTHOR + +Raphaël Droz + +=head1 LICENSE + +GPLv2 + +=head1 MAGICK MARKERS + + #%# family=auto + #%# capabilities=autoconf + +=cut + +. $MUNIN_LIBDIR/plugins/plugin.sh + +my_cache_path="${cache_path:-/var/cache/apache2/mod_cache_disk}" +declare -a fieldnames labels colours + +getenvdata() { + let during_autoconf=0 + [[ $1 == -v ]] && during_autoconf=1 + arg=( "${strings:-}" ) + for i in ${arg[*]}; do + if [[ ! $i =~ ^[a-zA-Z0-9]+$ ]]; then + (( $during_autoconf )) && echo "no ($i isn't a valid fixed-string)" + ok=0 + continue + fi + label=label_$i + if [[ -z "${!label}" ]]; then + (( $during_autoconf )) && echo "no ($i isn't given a label)" + ok=0 + continue + fi + colour=colour_$i + if [[ -z "${!colour}" ]]; then + (( $during_autoconf )) && echo "no ($i isn't given a colour)" + ok=0 + continue + fi + + fieldnames+=($i) + labels+=("${!label}") + colours+=("${!colour}") + done +} + +if [[ $1 == autoconf ]]; then + let ok=1 + + [[ -z $strings ]] && echo "no strings to monitor defined" && ok=0 + ! type -P htcacheclean &>/dev/null && echo "can't find htcacheclean" && ok=0 + ! test -d "$my_cache_path" && echo "cache_path \"$cache_path\" is not readable" && ok=0 + + getenvdata -v + + (( ${#fieldnames[*]} == 0 )) && echo "no (no valid strings)" && ok=0 + (( $ok == 1 )) && echo yes + exit 0 +fi + +getenvdata + +if [[ $1 == config ]]; then +cat < Date: Fri, 8 Jan 2016 13:27:56 -0300 Subject: [PATCH 66/73] apache_average_time_last_n_requests: ability to create multiple instances for distincts log files --- .../apache_average_time_last_n_requests | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) mode change 100755 => 100644 plugins/apache/apache_average_time_last_n_requests diff --git a/plugins/apache/apache_average_time_last_n_requests b/plugins/apache/apache_average_time_last_n_requests old mode 100755 new mode 100644 index 04002839..f302eda4 --- a/plugins/apache/apache_average_time_last_n_requests +++ b/plugins/apache/apache_average_time_last_n_requests @@ -1,5 +1,6 @@ #!/usr/bin/perl -w # Author: Nicolas Mendoza - 2008-06-18 +# Raphaël Droz - 2016-01-08 # # Monitors the average time requests matching a custom regexp takes # For instance monitor time execution of files in http://example.com/foo/bar, @@ -17,15 +18,35 @@ # Check http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#formats for more info # # Configurable variables -# fieldno - Override the default field number -# linecount - How many last request to consider +# fieldno - Override the default field number +# linecount - How many last request to consider +# Multiples instances for specific log files could be created by suffixing a configuration group. +# Eg: ln -s apache_average_time_last_n_requests apache_average_time_last_n_requests_vhost1 +# Then: +# [apache_average_time_last_n_requests] +# logfile_vhost1 /var/log/apache/vhost1.log +# #%# family=auto use strict; +$0 =~ /apache_average_time_last_n_requests_(.+)*$/; +my $name = $1; + my $LAST_N_REQUESTS = exists $ENV{'linecount'} ? $ENV{'linecount'} : 100000; # calculate based on this amount of requests -my $TIME_FIELD_INDEX = exists $ENV{'fieldno'} ? $ENV{'fieldno'} : -2; # second last field -my $ACCESS_LOG_PATTERN = '/var/log/apache2/access.log.*'; # log pattern, if many it will take the last one. +my $TIME_FIELD_INDEX = exists $ENV{'fieldno'} ? $ENV{'fieldno'} : -2; # second last field +my $ACCESS_LOG_PATTERN; + +# log pattern, if globbing is used, it will take the last one. +if (! $name) { + $ACCESS_LOG_PATTERN = exists $ENV{'logfile'} ? $ENV{'logfile'} : '/var/log/apache2/access.log.*'; +} +elsif (exists $ENV{'logfile_' . $name}) { + $ACCESS_LOG_PATTERN = $ENV{'logfile_' . $name}; +} +else { + $ACCESS_LOG_PATTERN = '/var/log/apache2/access.log.*'; +} my $config =<< "CONFIG" graph_title Apache average seconds last $LAST_N_REQUESTS requests @@ -65,12 +86,12 @@ my $types = { my ($fields) = @_; my $script; ($script = $fields->[6]) =~ s/\?.*\z //mx; - return $script =~ m{ \.(png|jpe?g|jpg|gif|tiff|ilbm|tga) \z }mx; + return $script =~ m{ \.(png|jpe?g|gif|tiff|ilbm|tga) \z }mx; }, }, }; -if (defined(@ARGV) && ($ARGV[0] eq 'config')) { +if (@ARGV && $ARGV[0] eq 'config') { print $config; @@ -88,16 +109,21 @@ chomp $config_file; my @lines = `tail -n $LAST_N_REQUESTS "$config_file"`; +FOO: { foreach my $line (@lines) { foreach my $type (keys %{$types}) { my @fields = split /\s+/, $line; if ($types->{$type}->{'matches'}(\@fields)) { + if ($fields[$TIME_FIELD_INDEX] !~ m/^[0-9]+$/) { + last FOO; + } $types->{$type}->{'sum'} += $fields[$TIME_FIELD_INDEX]; $types->{$type}->{'lines'}++; } } } +} foreach my $type (keys %{$types}) { my $value = $types->{$type}->{'lines'} ? $types->{$type}->{'sum'} / $types->{$type}->{'lines'} : 'U'; printf "%s.value %s\n", ($type, $value); From edd681a891f2257eb1325989b71aee2917c1eb62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Mon, 4 Apr 2016 13:17:51 -0300 Subject: [PATCH 67/73] fixed usage example --- plugins/php/php_errors_ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/php/php_errors_ b/plugins/php/php_errors_ index 4e7efe8e..e8788368 100644 --- a/plugins/php/php_errors_ +++ b/plugins/php/php_errors_ @@ -8,7 +8,7 @@ Plugin to monitor error.log from apache server =head1 CONFIGURATION -[apache_log] +[php_errors_newsite] user www-data env.logfile /home/newsite/logs/errors.log /var/log/php/otherlog.log From f8bf3961dadfcc094c6ff93da8cc3a24a0905812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Fri, 8 Jan 2016 13:28:29 -0300 Subject: [PATCH 68/73] added slow requests for php fpm (going multigraph) --- plugins/php/php_fpm_process | 96 ++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/plugins/php/php_fpm_process b/plugins/php/php_fpm_process index e306da83..89b6acfd 100755 --- a/plugins/php/php_fpm_process +++ b/plugins/php/php_fpm_process @@ -10,23 +10,23 @@ Inspirated by php5-fpm_status plugin by Daniel Caillibaud =head1 APPLICABLE SYSTEMS -Any php-fpm host -You will need the perl fastcgi::client on your host +Any php-fpm host +You will need the perl fastcgi::client on your host =head1 CONFIGURATION -You have to put this in your plugin.conf.d folder +You have to put this in your plugin.conf.d folder -# If your php process is listening on TCP +# If your php process is listening on TCP [php_fpm_process] env.serveraddr 127.0.0.1 env.port 9000 - env.path /status + env.path /status # If your php process is listening on Unix Socket [php_fpm_process] env.sock /var/run/php5-fpm.sock - env.path /status + env.path /status =head1 MAGIC MARKERS @@ -35,11 +35,11 @@ You have to put this in your plugin.conf.d folder =head1 VERSION - v1.0 + v1.0 =head1 AUTHOR -Minitux +Minitux =head1 LICENSE @@ -47,6 +47,7 @@ GNU General Public License, version 3 =cut +use File::Basename; use FCGI::Client; my $ish = 1; @@ -55,6 +56,8 @@ my $body = ""; my $IDLE = 0; my $ACTIVE = 0; my $TOTAL = 0; +my $SLOW_REQUESTS = 0; +my $PLUGIN_NAME = basename($0); my $SERVERADDR = $ENV{'serveraddr'} || "127.0.0.1"; my $PORT = $ENV{'port'} || "9000"; @@ -68,7 +71,7 @@ if ($UNIX_SOCK) { $sock = IO::Socket::UNIX->new( Peer => $UNIX_SOCK, ); - if (!$sock) { + if (!$sock) { print "Server maybe down, unabled to connect to $UNIX_SOCK"; exit 2; } @@ -78,7 +81,7 @@ if ($UNIX_SOCK) { PeerAddr => $SERVERADDR, PeerPort => $PORT, ); - if (!$sock) { + if (!$sock) { print "Server maybe down, unabled to connect to $SERVERADDR:$PORT"; exit 2; } @@ -86,7 +89,7 @@ if ($UNIX_SOCK) { my $client = FCGI::Client::Connection->new( sock => $sock ); -my ( $stdout, $stderr, $appstatus ) = $client->request( +my ( $stdout, $stderr, $appstatus ) = $client->request( +{ REQUEST_METHOD => 'GET', SCRIPT_FILENAME => '', @@ -112,33 +115,53 @@ while($stdout =~ /([^\n]*)\n?/g) { if ( defined $ARGV[0] and $ARGV[0] eq "config" ) { - + if($body =~ m/pool:\s+(.*?)\n/) { $pool = $1; } - print "graph_title php5-fpm status $pool\n"; - print "graph_args --base 1000 -l 0\n"; - print "graph_vlabel Processes\n"; - print "graph_scale yes\n"; - print "graph_category php-fpm\n"; - print "graph_info This graph shows the php5-fpm process manager status from pool: $pool\n"; - print "active.label Active processes\n"; - print "active.type GAUGE\n"; - print "active.draw AREA\n"; - print "active.info The number of active processes\n"; - print "idle.label Idle processes\n"; - print "idle.type GAUGE\n"; - print "idle.draw STACK\n"; - print "idle.info The number of idle processes\n"; - print "total.label Total processes\n"; - print "total.type GAUGE\n"; - print "total.draw LINE2\n"; - print "total.info The number of idle + active processes\n"; - exit 0 -} + print <<"EOF"; +multigraph ${PLUGIN_NAME}_process +graph_title php5-fpm processes for $pool +graph_args --base 1000 -l 0 +graph_vlabel Processes +graph_scale yes +graph_category php-fpm +graph_info This graph shows the php5-fpm process manager status from pool: $pool +active.label Active processes +active.type GAUGE +active.draw AREA +active.info The number of active processes +idle.label Idle processes +idle.type GAUGE +idle.draw STACK +idle.info The number of idle processes +total.label Total processes +total.type GAUGE +total.draw LINE2 +total.info The number of idle + active processes -print $body; +multigraph ${PLUGIN_NAME}_slowrequests +graph_title php5-fpm slow requests $pool +graph_args --base 1000 -l 0 +graph_vlabel Slow requests +graph_scale yes +graph_category php-fpm +graph_info This graph shows the php5-fpm slow request from pool: $pool +slow_requests.label Slow requests +slow_requests.type DERIVE +slow_requests.draw LINE2 +slow_requests.min 0 +slow_requests.info evolution of slow requests + +EOF + + exit 0 +} + +# print $body; + +print "multigraph ${PLUGIN_NAME}_process\n"; if($body =~ m/idle processes: (.*?)\n/) { $IDLE = $1; @@ -152,3 +175,10 @@ if($body =~ m/total processes: (.*?)\n/) { $TOTAL = $1; print "total.value ".$TOTAL."\n"; } + +if($body =~ m/slow requests: (.*?)\n/) { + $SLOW_REQUESTS = $1; + print "\n"; + print "multigraph ${PLUGIN_NAME}_slowrequests\n"; + print "slow_requests.value ".$SLOW_REQUESTS."\n"; +} From 6efaef76bf1a10c16557599f33818cdc5b09e9d0 Mon Sep 17 00:00:00 2001 From: Igor Borodikhin Date: Fri, 15 Apr 2016 14:13:10 +0600 Subject: [PATCH 69/73] Comments update and error messages --- plugins/nginx/nginx_upstream_multi_ | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/nginx/nginx_upstream_multi_ b/plugins/nginx/nginx_upstream_multi_ index 07780041..d22fc80a 100755 --- a/plugins/nginx/nginx_upstream_multi_ +++ b/plugins/nginx/nginx_upstream_multi_ @@ -25,9 +25,12 @@ # Use it in your site configuration (/etc/nginx/sites-enabled/anything.conf): # access_log /var/log/nginx/upstream.log upstream; # +# Attention! Because munin-node does not have read permission for nginx log files we need to run it as root. +# # And specify some options in /etc/munin/plugin-conf.d/munin-node: # # [nginx_upstream_multi_upstream] +# user root # env.graphs cache http time request # env.log /var/log/nginx/upstream.log # env.upstream 10.0.0.1:80 10.0.0.2:8080 unix:/tmp/upstream3 @@ -199,8 +202,8 @@ else: try: logHandle = open(logPath, "r") - except Exception: - print "Log file %s not readable" % logPath + except Exception as e: + print "Log file %s not readable: %s" % (logPath, e.strerror) sys.exit(1) try: @@ -278,7 +281,8 @@ else: lastByteHandle = open(lastBytePath, "w") lastByteHandle.write(str(logHandle.tell())) lastByteHandle.close() - except Exception: + except Exception as e: + print e.strerror sys.exit(1) logHandle.close() From 5ff3522eccc5241167cadc30f3212f7983c733aa Mon Sep 17 00:00:00 2001 From: Kjetil Torgrim Homme Date: Mon, 18 Jul 2016 17:30:39 +0200 Subject: [PATCH 70/73] if1sec_: implement autostart, be more like if_ * look for already running acquire process, or start one * use CDEF to return bits/second (like if_) * increase lifetime to 450 days (like if_) * changed category to plain "network" (like if_) * report max interface speed (like if_) * small performance improvement: don't fork two cat(1) and one date(1) every second (this roughly halves the CPU time used on my system) --- plugins/network/if1sec_ | 100 +++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/plugins/network/if1sec_ b/plugins/network/if1sec_ index 28b45ec7..1414fa3b 100755 --- a/plugins/network/if1sec_ +++ b/plugins/network/if1sec_ @@ -1,15 +1,11 @@ #! /bin/sh -# Currently the plugin does *not* autostart -# -# It has to be launched via rc.d : -# munin-run if1sec_eth0 acquire -pluginfull="$0" # full name of plugin -plugin="${0##*/}" # name of plugin +pluginfull="$0" # full name of plugin +plugin="${0##*/}" # name of plugin pidfile="$MUNIN_PLUGSTATE/munin.$plugin.pid" cache="$MUNIN_PLUGSTATE/munin.$plugin.value" -IFACE="${0##*/if1sec_}" # interface +IFACE="${0##*/if1sec_}" # interface if [ ! -r "/sys/class/net/$IFACE/statistics/tx_bytes" ] then @@ -19,44 +15,86 @@ fi if [ "$1" = "acquire" ] then - ( - while sleep 1 - do - echo $( - date +%s - cat /sys/class/net/$IFACE/statistics/tx_bytes - cat /sys/class/net/$IFACE/statistics/rx_bytes - ) - done | awk "{ - print \"${IFACE}_tx.value \" \$1 \":\" \$2; - print \"${IFACE}_rx.value \" \$1 \":\" \$3; - system(\"\"); - }" >> $cache - ) & - echo $! > $pidfile - exit 0 + ( + exec <&- >&- 2>&- + while sleep 1 + do + read tx < /sys/class/net/$IFACE/statistics/tx_bytes + read rx < /sys/class/net/$IFACE/statistics/rx_bytes + echo "$tx $rx" + done | awk "{ + now = systime() + print \"${IFACE}_tx.value \" now \":\" \$1 + print \"${IFACE}_rx.value \" now \":\" \$2 + system(\"\") + }" >> $cache + ) & + echo $! > $pidfile + exit 0 fi if [ "$1" = "config" ] then - cat < ${cache} From ca71d12f290f1169a225e70b4418423afcbfc5e4 Mon Sep 17 00:00:00 2001 From: Felix Engelmann Date: Wed, 3 Aug 2016 12:49:53 +0200 Subject: [PATCH 71/73] added lxd memory plugin the lxd deamon provides a REST interface which can be queried by pylxd to get container related information. It stacks all containers, so the total memory footprint of lxd is visible. This plugin depends on python3 pylxd --- plugins/lxd/lxd_mem | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 plugins/lxd/lxd_mem diff --git a/plugins/lxd/lxd_mem b/plugins/lxd/lxd_mem new file mode 100755 index 00000000..9895133f --- /dev/null +++ b/plugins/lxd/lxd_mem @@ -0,0 +1,24 @@ +#!/usr/bin/python3 + +import sys +from pylxd import api + +c=api.API() + +if len(sys.argv) == 2: + if sys.argv[1]=="autoconf": + print("yes") + sys.exit(0) + elif sys.argv[1]=="config": + print("graph_title LXD container memory") + print("graph_args --base 1024 --lower-limit 0") + print("graph_vlabel Bytes") + print("graph_category lxd") + print("graph_info This shows the memory usage of each container. Make sure to install pylxd in python3.") + for name in c.container_list(): + print(name+".label "+name) + print(name+".draw AREASTACK") + sys.exit(0) + +for name in c.container_list(): + print(name+".value "+str(c.container_info(name)['memory']['usage'])) From 46983fdc99f530476fd5c9abdf64b3be52ae3a9a Mon Sep 17 00:00:00 2001 From: Felix Engelmann Date: Wed, 3 Aug 2016 12:50:28 +0200 Subject: [PATCH 72/73] added lxd disk plugin the lxd deamon provides a REST interface which can be queried by pylxd to get container related information. It graphs the disk usage of all disks in all containers. This plugin depends on python3 pylxd --- plugins/lxd/lxd_disk | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 plugins/lxd/lxd_disk diff --git a/plugins/lxd/lxd_disk b/plugins/lxd/lxd_disk new file mode 100755 index 00000000..b0e2671c --- /dev/null +++ b/plugins/lxd/lxd_disk @@ -0,0 +1,26 @@ +#!/usr/bin/python3 + +import sys +from pylxd import api + +c=api.API() + +if len(sys.argv) == 2: + if sys.argv[1]=="autoconf": + print("yes") + sys.exit(0) + elif sys.argv[1]=="config": + print("graph_title LXD container disk usage") + print("graph_args --base 1000 --lower-limit 0") + print("graph_vlabel Bytes") + print("graph_category lxd") + print("graph_info This shows the disk usage of storage in containers. Make sure to install pylxd in python3.") + for name in c.container_list(): + for disk in c.container_info(name)['disk']: + print(name+"-"+disk+".label "+name) + print(name+"-"+disk+".draw LINE2") + sys.exit(0) + +for name in c.container_list(): + for disk in c.container_info(name)['disk']: + print(name+"-"+disk+".value "+str(c.container_info(name)['disk'][disk]['usage'])) From 365e9932007b282b30fd10135c25e78d39866c83 Mon Sep 17 00:00:00 2001 From: Felix Engelmann Date: Wed, 3 Aug 2016 17:31:15 +0200 Subject: [PATCH 73/73] corrected lxd_ autoconf with proper checks the autoconf now checks for availablity of the pylxd module and access to the lxd socket. this ensures that the module will work properly. On failure, helpful errors are displayed --- plugins/lxd/lxd_disk | 54 +++++++++++++++++++++++++++++++------------- plugins/lxd/lxd_mem | 52 ++++++++++++++++++++++++++++++------------ 2 files changed, 75 insertions(+), 31 deletions(-) diff --git a/plugins/lxd/lxd_disk b/plugins/lxd/lxd_disk index b0e2671c..9e7c04f5 100755 --- a/plugins/lxd/lxd_disk +++ b/plugins/lxd/lxd_disk @@ -1,25 +1,47 @@ #!/usr/bin/python3 import sys -from pylxd import api -c=api.API() +errors=[] -if len(sys.argv) == 2: - if sys.argv[1]=="autoconf": +HAS_LIB=True +try: + from pylxd import api +except: + HAS_LIB=False + errors.append("no pylxd module") + +c=None +HAS_ACCESS=True +try: + c=api.API() + c.container_list() +except: + HAS_ACCESS=False + errors.append("no socket access") + +if len(sys.argv) == 2 and sys.argv[1]=="autoconf": + if HAS_LIB and HAS_ACCESS: print("yes") - sys.exit(0) - elif sys.argv[1]=="config": - print("graph_title LXD container disk usage") - print("graph_args --base 1000 --lower-limit 0") - print("graph_vlabel Bytes") - print("graph_category lxd") - print("graph_info This shows the disk usage of storage in containers. Make sure to install pylxd in python3.") - for name in c.container_list(): - for disk in c.container_info(name)['disk']: - print(name+"-"+disk+".label "+name) - print(name+"-"+disk+".draw LINE2") - sys.exit(0) + else: + print("no ("+" and ".join(errors)+")") + sys.exit(0) + +if not (HAS_LIB and HAS_ACCESS): + # pylxd not installed or lxd socket not accessible + sys.exit(1) + +if len(sys.argv) == 2 and sys.argv[1]=="config": + print("graph_title LXD container disk usage") + print("graph_args --base 1000 --lower-limit 0") + print("graph_vlabel Bytes") + print("graph_category lxd") + print("graph_info This shows the disk usage of storage in containers. Make sure to install pylxd in python3.") + for name in c.container_list(): + for disk in c.container_info(name)['disk']: + print(name+"-"+disk+".label "+name) + print(name+"-"+disk+".draw LINE2") + sys.exit(0) for name in c.container_list(): for disk in c.container_info(name)['disk']: diff --git a/plugins/lxd/lxd_mem b/plugins/lxd/lxd_mem index 9895133f..6c797836 100755 --- a/plugins/lxd/lxd_mem +++ b/plugins/lxd/lxd_mem @@ -1,24 +1,46 @@ #!/usr/bin/python3 import sys -from pylxd import api -c=api.API() +errors=[] -if len(sys.argv) == 2: - if sys.argv[1]=="autoconf": +HAS_LIB=True +try: + from pylxd import api +except: + HAS_LIB=False + errors.append("no pylxd module") + +c=None +HAS_ACCESS=True +try: + c=api.API() + c.container_list() +except: + HAS_ACCESS=False + errors.append("no socket access") + +if len(sys.argv) == 2 and sys.argv[1]=="autoconf": + if HAS_LIB and HAS_ACCESS: print("yes") - sys.exit(0) - elif sys.argv[1]=="config": - print("graph_title LXD container memory") - print("graph_args --base 1024 --lower-limit 0") - print("graph_vlabel Bytes") - print("graph_category lxd") - print("graph_info This shows the memory usage of each container. Make sure to install pylxd in python3.") - for name in c.container_list(): - print(name+".label "+name) - print(name+".draw AREASTACK") - sys.exit(0) + else: + print("no ("+" and ".join(errors)+")") + sys.exit(0) + +if not (HAS_LIB and HAS_ACCESS): + # pylxd not installed or lxd socket not accessible + sys.exit(1) + +if len(sys.argv) == 2 and sys.argv[1]=="config": + print("graph_title LXD container memory") + print("graph_args --base 1024 --lower-limit 0") + print("graph_vlabel Bytes") + print("graph_category lxd") + print("graph_info This shows the memory usage of each container. Make sure to install pylxd in python3.") + for name in c.container_list(): + print(name+".label "+name) + print(name+".draw AREASTACK") + sys.exit(0) for name in c.container_list(): print(name+".value "+str(c.container_info(name)['memory']['usage']))