mirror of
https://github.com/dennypage/dpinger.git
synced 2024-05-19 06:50:01 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24022ac098 | ||
|
|
42c84e965e | ||
|
|
f94f6dcd47 | ||
|
|
99208a60fe | ||
|
|
eb67d76de0 | ||
|
|
0b68c438f2 | ||
|
|
daac746074 | ||
|
|
5348af36c6 | ||
|
|
b0fc95f618 | ||
|
|
e9ffd0b43e | ||
|
|
9e8968adce | ||
|
|
a8b44bedac | ||
|
|
1d34caccbd | ||
|
|
9c5bac8658 | ||
|
|
3a19391cee | ||
|
|
570ade420a | ||
|
|
3825066db9 | ||
|
|
f06b3c8f36 | ||
|
|
4a57f2584c | ||
|
|
afbde05bcb | ||
|
|
85d345f47f | ||
|
|
e0a0ae14f9 | ||
|
|
87cd4b6e3b | ||
|
|
919fad77a2 | ||
|
|
2e0430edea | ||
|
|
31432284dc | ||
|
|
e1d00b4210 | ||
|
|
1ec615486b | ||
|
|
6789e90a38 | ||
|
|
1b2e8e784b | ||
|
|
00229f717d | ||
|
|
4b82af813b | ||
|
|
feb01fa2d3 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
/dpinger
|
||||
/dpinger.debug
|
||||
/dpinger.full
|
||||
/dpinger.o
|
||||
/.*.swp
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2015, Denny Page
|
||||
Copyright (c) 2015-2016, Denny Page
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
12
Makefile
12
Makefile
@@ -1,9 +1,9 @@
|
||||
PROG= dpinger
|
||||
MAN=
|
||||
#CC=gcc
|
||||
#WARNINGS=-Wall -Wextra -Wformat=2 -Wno-unused-result
|
||||
|
||||
BINDIR= ${PREFIX}/bin
|
||||
WARNS= 6
|
||||
CC=clang
|
||||
WARNINGS=-Weverything -Wno-padded -Wno-disabled-macro-expansion
|
||||
|
||||
LDADD= -lpthread
|
||||
CFLAGS=${WARNINGS} -pthread -g -O2
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
all: dpinger
|
||||
|
||||
9
Makefile.freebsd
Normal file
9
Makefile.freebsd
Normal file
@@ -0,0 +1,9 @@
|
||||
PROG= dpinger
|
||||
MAN=
|
||||
|
||||
BINDIR= ${PREFIX}/bin
|
||||
WARNS= 6
|
||||
|
||||
LDADD= -lpthread
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
25
README.md
25
README.md
@@ -1,9 +1,26 @@
|
||||
# dpinger
|
||||
|
||||
dpinger is a daemon for continous monitoring of latency and loss on a network connection. It is
|
||||
dpinger is a daemon for continuous monitoring of latency and loss on a network connection. It is
|
||||
intended for use by firewalls to monitor link health, as well as for providing information to
|
||||
various monitoring systems such as Cacti, Nagios, Zabbix, etc.
|
||||
|
||||
The output of dpinger can either be file or socket based, and consists of three numbers:
|
||||
|
||||
<Average Latency in μs> <Standard Deviation in μs> <Percentage of Loss>
|
||||
|
||||
dpinger also provides for invocation of a command based upon threshold values
|
||||
for Average Latency or Percentage of Loss. Arguments to the command are:
|
||||
|
||||
<Target IP> <Alarm on/off> <Average Latency> <Standard Deviation> <Percentage of Loss>
|
||||
|
||||
In addition to command invocation, dpinger can also log alerts via syslog.
|
||||
|
||||
If several instances of dpinger are being used to monitor different targets, or the same target
|
||||
with different source addresses, etc., an Identifier can be added to the output to identify
|
||||
which instance of dpinger is the source. This is particularly useful with syslog.
|
||||
|
||||
<br>
|
||||
|
||||
Usage examples:
|
||||
|
||||
dpinger -t 300s -r 60s 192.168.0.1 >> /tmp/dpinger.out
|
||||
@@ -38,3 +55,9 @@ Produce a report every 60 seconds and append it to /tmp/dpinger.out.
|
||||
Monitor IP address fe80::1 for latency and loss. Send echo requests every 200 milliseconds.
|
||||
Make current status available on demand via a Unix domain socket /tmp/igb1.status. Record
|
||||
process id in /run/dpinger.
|
||||
|
||||
dpinger -S -i Comcast -s 5s -t 600s -r 0 -L 10% -p /run/dpinger 8.8.8.8
|
||||
|
||||
Monitor IP address 8.8.8.8 for latency and loss. Send echo requests every five seconds and
|
||||
average results over 10 minutes. Log alerts via syslog including identifier string "Comcast"
|
||||
if average loss exceeds 10 percent. Record process id in /run/dpinger.
|
||||
|
||||
427
dpinger.c
427
dpinger.c
@@ -1,6 +1,6 @@
|
||||
|
||||
//
|
||||
// Copyright (c) 2015, Denny Page
|
||||
// Copyright (c) 2015-2016, Denny Page
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -38,6 +38,8 @@
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -50,6 +52,18 @@
|
||||
#include <pthread.h>
|
||||
#include <syslog.h>
|
||||
|
||||
// TODO:
|
||||
//
|
||||
// After December 31st, 2016, review use of fcntl() for setting non blocking
|
||||
// and close on exec. It would be preferable to use accept4(), SOCK_CLOEXEC
|
||||
// and SOCK_NONBLOCK. These are currently avoided to allow use on older
|
||||
// systems such as FreeBSD 9.3, Linux 2.6.26.
|
||||
// For Linux accept4() currently requires defining _GNU_SOURCE which we would
|
||||
// like to avoid.
|
||||
// For FreeBSD, these definitions were introduced with FreeBSD 10.0 and are
|
||||
// not present in 9.3 which is supported through 2016.
|
||||
|
||||
|
||||
// Who we are
|
||||
static const char * progname;
|
||||
|
||||
@@ -62,10 +76,11 @@ static unsigned int flag_rewind = 0;
|
||||
static unsigned int flag_syslog = 0;
|
||||
|
||||
// String representation of target
|
||||
static char dest_str[INET6_ADDRSTRLEN];
|
||||
#define ADDR_STR_MAX (INET6_ADDRSTRLEN + IF_NAMESIZE + 1)
|
||||
static char dest_str[ADDR_STR_MAX];
|
||||
|
||||
// Time period over which we are averaging results in ms
|
||||
static unsigned long time_period_msec = 25000;
|
||||
static unsigned long time_period_msec = 30000;
|
||||
|
||||
// Interval between sends in ms
|
||||
static unsigned long send_interval_msec = 250;
|
||||
@@ -135,8 +150,8 @@ static int recv_sock;
|
||||
|
||||
// IPv4 / IPv6 parameters
|
||||
static uint16_t af_family = AF_INET; // IPv6: AF_INET6
|
||||
static uint16_t echo_request_type = ICMP_ECHO; // IPv6: ICMP6_ECHO_REQUEST
|
||||
static uint16_t echo_reply_type = ICMP_ECHOREPLY; // IPv6: ICMP6_ECHO_REPLY
|
||||
static uint8_t echo_request_type = ICMP_ECHO; // IPv6: ICMP6_ECHO_REQUEST
|
||||
static uint8_t echo_reply_type = ICMP_ECHOREPLY; // IPv6: ICMP6_ECHO_REPLY
|
||||
static int ip_proto = IPPROTO_ICMP; // IPv6: IPPROTO_ICMPV6
|
||||
|
||||
// Destination address
|
||||
@@ -160,8 +175,16 @@ typedef struct
|
||||
uint16_t sequence;
|
||||
} icmphdr_t;
|
||||
|
||||
// Echo request header for sendto
|
||||
static icmphdr_t echo_request;
|
||||
// Echo request/reply packet buffers
|
||||
#define IPV4_ICMP_DATA_MAX (IP_MAXPACKET - sizeof(struct ip) - sizeof(icmphdr_t))
|
||||
#define IPV6_ICMP_DATA_MAX (IP_MAXPACKET - sizeof(icmphdr_t))
|
||||
#define PACKET_BUFLEN (IP_MAXPACKET + 256)
|
||||
|
||||
static unsigned long echo_data_len = 0;
|
||||
static unsigned int echo_request_len = sizeof(icmphdr_t);
|
||||
static unsigned int echo_reply_len = IP_MAXPACKET;
|
||||
static icmphdr_t * echo_request;
|
||||
static void * echo_reply;
|
||||
|
||||
// Echo id and Sequence information
|
||||
static uint16_t echo_id;
|
||||
@@ -172,6 +195,7 @@ static uint16_t sequence_limit;
|
||||
//
|
||||
// Termination handler
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void
|
||||
term_handler(void)
|
||||
{
|
||||
@@ -191,10 +215,7 @@ term_handler(void)
|
||||
//
|
||||
// Log for abnormal events
|
||||
//
|
||||
#ifdef __GNUC__
|
||||
static void logger(const char * format, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
#endif
|
||||
|
||||
__attribute__ ((format (printf, 1, 2)))
|
||||
static void
|
||||
logger(
|
||||
const char * format,
|
||||
@@ -239,7 +260,7 @@ cksum(
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
sum += (sum >> 16);
|
||||
|
||||
return ~sum;
|
||||
return (uint16_t) ~sum;
|
||||
}
|
||||
|
||||
|
||||
@@ -277,7 +298,7 @@ ts_elapsed_usec(
|
||||
const struct timespec * old,
|
||||
const struct timespec * new)
|
||||
{
|
||||
unsigned long r_usec;
|
||||
long r_usec;
|
||||
|
||||
// Note that we are using monotonic clock and time cannot run backwards
|
||||
if (new->tv_nsec >= old->tv_nsec)
|
||||
@@ -289,24 +310,28 @@ ts_elapsed_usec(
|
||||
r_usec = (new->tv_sec - old->tv_sec - 1) * 1000000 + (1000000000 + new->tv_nsec - old->tv_nsec) / 1000;
|
||||
}
|
||||
|
||||
return r_usec;
|
||||
return (unsigned long) r_usec;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Send thread
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void *
|
||||
send_thread(
|
||||
__attribute__ ((unused))
|
||||
void * arg)
|
||||
{
|
||||
struct timespec sleeptime;
|
||||
ssize_t len;
|
||||
int r;
|
||||
|
||||
// Set up our echo request packet
|
||||
echo_request.type = echo_request_type;
|
||||
echo_request.code = 0;
|
||||
echo_request.id = echo_id;
|
||||
memset(echo_request, 0, echo_request_len);
|
||||
echo_request->type = echo_request_type;
|
||||
echo_request->code = 0;
|
||||
echo_request->id = echo_id;
|
||||
|
||||
// Set up the timespec for nanosleep
|
||||
sleeptime.tv_sec = send_interval_msec / 1000;
|
||||
@@ -321,17 +346,17 @@ send_thread(
|
||||
}
|
||||
|
||||
// Set sequence number and checksum
|
||||
echo_request.sequence = htons(next_sequence);
|
||||
echo_request.cksum = 0;
|
||||
echo_request.cksum = cksum((uint16_t *) &echo_request, sizeof(icmphdr_t));
|
||||
echo_request->sequence = htons(next_sequence);
|
||||
echo_request->cksum = 0;
|
||||
echo_request->cksum = cksum((uint16_t *) echo_request, sizeof(icmphdr_t));
|
||||
|
||||
array[next_slot].status = PACKET_STATUS_EMPTY;
|
||||
sched_yield();
|
||||
clock_gettime(CLOCK_MONOTONIC, &array[next_slot].time_sent);
|
||||
array[next_slot].status = PACKET_STATUS_SENT;
|
||||
|
||||
r = sendto(send_sock, &echo_request, sizeof(icmphdr_t), 0, (struct sockaddr *) &dest_addr, dest_addr_len);
|
||||
if (r == -1)
|
||||
array[next_slot].status = PACKET_STATUS_SENT;
|
||||
len = sendto(send_sock, echo_request, echo_request_len, 0, (struct sockaddr *) &dest_addr, dest_addr_len);
|
||||
if (len == -1)
|
||||
{
|
||||
logger("%s%s: sendto error: %d\n", identifier, dest_str, errno);
|
||||
}
|
||||
@@ -339,23 +364,21 @@ send_thread(
|
||||
next_slot = (next_slot + 1) % array_size;
|
||||
next_sequence = (next_sequence + 1) % sequence_limit;
|
||||
}
|
||||
|
||||
// notreached
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Receive thread
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void *
|
||||
recv_thread(
|
||||
__attribute__ ((unused))
|
||||
void * arg)
|
||||
{
|
||||
char packet[1024];
|
||||
unsigned int packet_len;
|
||||
struct sockaddr_storage src_addr;
|
||||
socklen_t src_addr_len;
|
||||
ssize_t len;
|
||||
icmphdr_t * icmp;
|
||||
struct timespec now;
|
||||
unsigned int array_slot;
|
||||
@@ -363,8 +386,8 @@ recv_thread(
|
||||
while (1)
|
||||
{
|
||||
src_addr_len = sizeof(src_addr);
|
||||
packet_len = recvfrom(recv_sock, &packet, sizeof(packet), 0, (struct sockaddr *) &src_addr, &src_addr_len);
|
||||
if (packet_len == (unsigned int) -1)
|
||||
len = recvfrom(recv_sock, echo_reply, echo_reply_len, 0, (struct sockaddr *) &src_addr, &src_addr_len);
|
||||
if (len == -1)
|
||||
{
|
||||
logger("%s%s: recvfrom error: %d\n", identifier, dest_str, errno);
|
||||
continue;
|
||||
@@ -377,25 +400,25 @@ recv_thread(
|
||||
size_t ip_len;
|
||||
|
||||
// With IPv4, we get the entire IP packet
|
||||
if (packet_len < sizeof(struct ip))
|
||||
if (len < (ssize_t) sizeof(struct ip))
|
||||
{
|
||||
logger("%s%s: received packet too small for IP header\n", identifier, dest_str);
|
||||
continue;
|
||||
}
|
||||
ip = (void *) packet;
|
||||
ip_len = ip->ip_hl << 2;
|
||||
ip = echo_reply;
|
||||
ip_len = (size_t) ip->ip_hl << 2;
|
||||
|
||||
icmp = (void *) (packet + ip_len);
|
||||
packet_len -= ip_len;
|
||||
icmp = (void *) ((char *) ip + ip_len);
|
||||
len -= ip_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
// With IPv6, we just get the ICMP payload
|
||||
icmp = (void *) (packet);
|
||||
icmp = echo_reply;
|
||||
}
|
||||
|
||||
// This should never happen
|
||||
if (packet_len < sizeof(icmphdr_t))
|
||||
if (len < (ssize_t) sizeof(icmphdr_t))
|
||||
{
|
||||
logger("%s%s: received packet too small for ICMP header\n", identifier, dest_str);
|
||||
continue;
|
||||
@@ -417,9 +440,6 @@ recv_thread(
|
||||
array[array_slot].latency_usec = ts_elapsed_usec(&array[array_slot].time_sent, &now);
|
||||
array[array_slot].status = PACKET_STATUS_RECEIVED;
|
||||
}
|
||||
|
||||
// notreached
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
@@ -435,6 +455,7 @@ report(
|
||||
struct timespec now;
|
||||
unsigned long packets_received = 0;
|
||||
unsigned long packets_lost = 0;
|
||||
unsigned long latency_usec = 0;
|
||||
unsigned long total_latency_usec = 0;
|
||||
unsigned long long total_latency_usec2 = 0;
|
||||
unsigned int slot;
|
||||
@@ -448,8 +469,9 @@ report(
|
||||
if (array[slot].status == PACKET_STATUS_RECEIVED)
|
||||
{
|
||||
packets_received++;
|
||||
total_latency_usec += array[slot].latency_usec;
|
||||
total_latency_usec2 += array[slot].latency_usec * array[slot].latency_usec;
|
||||
latency_usec = array[slot].latency_usec;
|
||||
total_latency_usec += latency_usec;
|
||||
total_latency_usec2 += latency_usec * latency_usec;
|
||||
}
|
||||
else if (array[slot].status == PACKET_STATUS_SENT &&
|
||||
ts_elapsed_usec(&array[slot].time_sent, &now) > loss_interval_usec)
|
||||
@@ -462,10 +484,12 @@ report(
|
||||
|
||||
if (packets_received)
|
||||
{
|
||||
*average_latency_usec = total_latency_usec / packets_received;
|
||||
unsigned long avg = total_latency_usec / packets_received;
|
||||
unsigned long long avg2 = total_latency_usec2 / packets_received;
|
||||
|
||||
// sqrt( (sum(rtt^2) / packets) - (sum(rtt) / packets)^2)
|
||||
*latency_deviation = llsqrt((total_latency_usec2 / packets_received) - (total_latency_usec / packets_received) * (total_latency_usec / packets_received));
|
||||
// stddev = sqrt((sum(rtt^2) / packets) - (sum(rtt) / packets)^2)
|
||||
*average_latency_usec = avg;
|
||||
*latency_deviation = llsqrt(avg2 - (avg * avg));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -487,8 +511,10 @@ report(
|
||||
//
|
||||
// Report thread
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void *
|
||||
report_thread(
|
||||
__attribute__ ((unused))
|
||||
void * arg)
|
||||
{
|
||||
char buf[OUTPUT_MAX];
|
||||
@@ -496,7 +522,8 @@ report_thread(
|
||||
unsigned long average_latency_usec;
|
||||
unsigned long latency_deviation;
|
||||
unsigned long average_loss_percent;
|
||||
int len;
|
||||
ssize_t len;
|
||||
ssize_t rs;
|
||||
int r;
|
||||
|
||||
// Set up the timespec for nanosleep
|
||||
@@ -519,33 +546,32 @@ report_thread(
|
||||
logger("error formatting output in report thread\n");
|
||||
}
|
||||
|
||||
r = write(report_fd, buf, len);
|
||||
if (r == -1)
|
||||
rs = write(report_fd, buf, (size_t) len);
|
||||
if (rs == -1)
|
||||
{
|
||||
logger("write error in report thread: %d\n", errno);
|
||||
}
|
||||
else if (r != len)
|
||||
else if (rs != len)
|
||||
{
|
||||
logger("short write in report thread: %d/%d\n", r, len);
|
||||
logger("short write in report thread: %zd/%zd\n", rs, len);
|
||||
}
|
||||
|
||||
if (flag_rewind)
|
||||
{
|
||||
ftruncate(report_fd, len);
|
||||
lseek(report_fd, SEEK_SET, 0);
|
||||
(void) ftruncate(report_fd, len);
|
||||
(void) lseek(report_fd, SEEK_SET, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// notreached
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Alert thread
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void *
|
||||
alert_thread(
|
||||
__attribute__ ((unused))
|
||||
void * arg)
|
||||
{
|
||||
struct timespec sleeptime;
|
||||
@@ -639,16 +665,15 @@ alert_thread(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notreached
|
||||
return arg;
|
||||
}
|
||||
|
||||
//
|
||||
// Unix socket thread
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void *
|
||||
usocket_thread(
|
||||
__attribute__ ((unused))
|
||||
void * arg)
|
||||
{
|
||||
char buf[OUTPUT_MAX];
|
||||
@@ -656,12 +681,14 @@ usocket_thread(
|
||||
unsigned long latency_deviation;
|
||||
unsigned long average_loss_percent;
|
||||
int sock_fd;
|
||||
int len;
|
||||
ssize_t len;
|
||||
ssize_t rs;
|
||||
int r;
|
||||
|
||||
while (1)
|
||||
{
|
||||
sock_fd = accept(usocket_fd, NULL, NULL);
|
||||
(void) fcntl(sock_fd, F_SETFL, FD_CLOEXEC);
|
||||
(void) fcntl(sock_fd, F_SETFL, fcntl(sock_fd, F_GETFL, 0) | O_NONBLOCK);
|
||||
|
||||
report(&average_latency_usec, &latency_deviation, &average_loss_percent);
|
||||
@@ -672,14 +699,14 @@ usocket_thread(
|
||||
logger("error formatting output in usocket thread\n");
|
||||
}
|
||||
|
||||
r = write(sock_fd, buf, len);
|
||||
if (r == -1)
|
||||
rs = write(sock_fd, buf, (size_t) len);
|
||||
if (rs == -1)
|
||||
{
|
||||
logger("write error in usocket thread: %d\n", errno);
|
||||
}
|
||||
else if (r != len)
|
||||
else if (rs != len)
|
||||
{
|
||||
logger("short write in usocket thread: %d/%d\n", r, len);
|
||||
logger("short write in usocket thread: %zd/%zd\n", rs, len);
|
||||
}
|
||||
|
||||
r = close(sock_fd);
|
||||
@@ -688,9 +715,6 @@ usocket_thread(
|
||||
logger("close error in usocket thread: %d\n", errno);
|
||||
}
|
||||
}
|
||||
|
||||
// notreached
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
@@ -715,7 +739,7 @@ get_time_arg_msec(
|
||||
else if (*suffix == 's')
|
||||
{
|
||||
// Seconds
|
||||
*value *= 1000;
|
||||
t *= 1000;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
@@ -758,6 +782,41 @@ get_percent_arg(
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Decode a byte length argument
|
||||
//
|
||||
static int
|
||||
get_length_arg(
|
||||
const char * arg,
|
||||
unsigned long * value)
|
||||
{
|
||||
unsigned long t;
|
||||
char * suffix;
|
||||
|
||||
t = strtoul(arg, &suffix, 10);
|
||||
if (*suffix == 'b')
|
||||
{
|
||||
// Bytes
|
||||
suffix++;
|
||||
}
|
||||
else if (*suffix == 'k')
|
||||
{
|
||||
// Kilobytes
|
||||
t *= 1024;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
// Garbage in the number?
|
||||
if (*suffix != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
*value = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Output usage
|
||||
//
|
||||
@@ -765,16 +824,17 @@ static void
|
||||
usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage:\n");
|
||||
fprintf(stderr, " %s [-f] [-R] [-S] [-B bind_addr] [-s send_interval] [-l loss_interval] [-t time_period] [-r report_interval] [-o output_file] [-A alert_interval] [-D latency_alarm] [-L loss_alarm] [-C alert_cmd] [-i identifier] [-u usocket] [-p pidfile] dest_addr\n\n", progname);
|
||||
fprintf(stderr, " %s [-f] [-R] [-S] [-B bind_addr] [-s send_interval] [-l loss_interval] [-t time_period] [-r report_interval] [-d data_length] [-o output_file] [-A alert_interval] [-D latency_alarm] [-L loss_alarm] [-C alert_cmd] [-i identifier] [-u usocket] [-p pidfile] dest_addr\n\n", progname);
|
||||
fprintf(stderr, " options:\n");
|
||||
fprintf(stderr, " -f run in foreground\n");
|
||||
fprintf(stderr, " -R rewind output file between reports\n");
|
||||
fprintf(stderr, " -S log warnings via syslog\n");
|
||||
fprintf(stderr, " -B bind (source) address\n");
|
||||
fprintf(stderr, " -s time interval between echo requests (default 250ms)\n");
|
||||
fprintf(stderr, " -l time interval before packets are treated as lost (default 2x send interval)\n");
|
||||
fprintf(stderr, " -t time period over which results are averaged (default 25s)\n");
|
||||
fprintf(stderr, " -l time interval before packets are treated as lost (default 5x send interval)\n");
|
||||
fprintf(stderr, " -t time period over which results are averaged (default 30s)\n");
|
||||
fprintf(stderr, " -r time interval between reports (default 1s)\n");
|
||||
fprintf(stderr, " -d data length (default 0)\n");
|
||||
fprintf(stderr, " -o output file for reports (default stdout)\n");
|
||||
fprintf(stderr, " -A time interval between alerts (default 1s)\n");
|
||||
fprintf(stderr, " -D time threshold for latency alarm (default none)\n");
|
||||
@@ -784,11 +844,13 @@ usage(void)
|
||||
fprintf(stderr, " -u unix socket name for polling\n");
|
||||
fprintf(stderr, " -p process id file name\n\n");
|
||||
fprintf(stderr, " notes:\n");
|
||||
fprintf(stderr, " IP addresses can be in either IPv4 or IPv6 format\n\n");
|
||||
fprintf(stderr, " time values can be expressed with a suffix of 'm' (milliseconds) or 's' (seconds)\n");
|
||||
fprintf(stderr, " if no suffix is specified, milliseconds is the default\n\n");
|
||||
fprintf(stderr, " IP addresses can be in either IPv4 or IPv6 format\n\n");
|
||||
fprintf(stderr, " the output format is \"latency_avg latency_stddev loss_pct\"\n");
|
||||
fprintf(stderr, " latency values are output in microseconds\n\n");
|
||||
fprintf(stderr, " latency values are output in microseconds\n");
|
||||
fprintf(stderr, " loss percentage is reported in whole numbers of 0-100\n");
|
||||
fprintf(stderr, " resolution of loss calculation is: 100 * send_interval / (time_period - loss_interval)\n\n");
|
||||
fprintf(stderr, " the alert_cmd is invoked as \"alert_cmd dest_addr alarm_flag latency_avg loss_avg\"\n");
|
||||
fprintf(stderr, " alarm_flag is set to 1 if either latency or loss is in alarm state\n");
|
||||
fprintf(stderr, " alarm_flag will return to 0 when both have have cleared alarm state\n\n");
|
||||
@@ -798,6 +860,7 @@ usage(void)
|
||||
//
|
||||
// Fatal error
|
||||
//
|
||||
__attribute__ ((noreturn, format (printf, 1, 2)))
|
||||
static void
|
||||
fatal(
|
||||
const char * format,
|
||||
@@ -824,8 +887,8 @@ parse_args(
|
||||
int argc,
|
||||
char * const argv[])
|
||||
{
|
||||
struct in_addr addr;
|
||||
struct in6_addr addr6;
|
||||
struct addrinfo hint;
|
||||
struct addrinfo * addr_info;
|
||||
const char * dest_arg;
|
||||
const char * bind_arg = NULL;
|
||||
size_t len;
|
||||
@@ -834,7 +897,7 @@ parse_args(
|
||||
|
||||
progname = argv[0];
|
||||
|
||||
while((opt = getopt(argc, argv, "fRSB:s:l:t:r:o:A:D:L:C:i:u:p:")) != -1)
|
||||
while((opt = getopt(argc, argv, "fRSB:s:l:t:r:d:o:A:D:L:C:i:u:p:")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
@@ -886,6 +949,14 @@ parse_args(
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
r = get_length_arg(optarg, &echo_data_len);
|
||||
if (r)
|
||||
{
|
||||
fatal("invalid data length %s\n", optarg);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
report_name = optarg;
|
||||
break;
|
||||
@@ -904,6 +975,7 @@ parse_args(
|
||||
{
|
||||
fatal("invalid latency alarm threshold %s\n", optarg);
|
||||
}
|
||||
latency_alarm_threshold_usec = latency_alarm_threshold_msec * 1000;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
@@ -916,7 +988,7 @@ parse_args(
|
||||
|
||||
case 'C':
|
||||
alert_cmd_offset = strlen(optarg);
|
||||
alert_cmd = malloc (alert_cmd_offset + OUTPUT_MAX);
|
||||
alert_cmd = malloc(alert_cmd_offset + OUTPUT_MAX);
|
||||
if (alert_cmd == NULL)
|
||||
{
|
||||
fatal("malloc of alert command buffer failed\n");
|
||||
@@ -928,7 +1000,7 @@ parse_args(
|
||||
len = strlen(optarg);
|
||||
if (len >= sizeof(identifier) - 1)
|
||||
{
|
||||
fatal("identifier argument too large (max %u bytes)\n", sizeof(identifier) - 1);
|
||||
fatal("identifier argument too large (max %u bytes)\n", (unsigned) sizeof(identifier) - 1);
|
||||
}
|
||||
// optarg with a space appended
|
||||
memcpy(identifier, optarg, len);
|
||||
@@ -956,6 +1028,7 @@ parse_args(
|
||||
usage();
|
||||
fatal(NULL);
|
||||
}
|
||||
dest_arg = argv[optind];
|
||||
|
||||
// Ensure we have something to do: at least one of alarm, report, socket
|
||||
if (report_interval_msec == 0 && latency_alarm_threshold_msec == 0 && loss_alarm_threshold_percent == 0 && usocket_name == NULL)
|
||||
@@ -963,9 +1036,6 @@ parse_args(
|
||||
fatal("no activity enabled\n");
|
||||
}
|
||||
|
||||
// Destination address
|
||||
dest_arg = argv[optind];
|
||||
|
||||
// Ensure we have something to average over
|
||||
if (time_period_msec < send_interval_msec)
|
||||
{
|
||||
@@ -979,61 +1049,71 @@ parse_args(
|
||||
fatal("ratio of time period to send interval cannot exceed 65536:1\n");
|
||||
}
|
||||
|
||||
// Check for an IPv4 address
|
||||
r = inet_pton(AF_INET, dest_arg, &addr);
|
||||
if (r)
|
||||
// Check destination address
|
||||
memset(&hint, 0, sizeof(struct addrinfo));
|
||||
hint.ai_flags = AI_NUMERICHOST;
|
||||
hint.ai_family = AF_UNSPEC;
|
||||
hint.ai_socktype = SOCK_RAW;
|
||||
|
||||
r = getaddrinfo(dest_arg, NULL, &hint, &addr_info);
|
||||
if (r != 0)
|
||||
{
|
||||
struct sockaddr_in * dest = (struct sockaddr_in *) &dest_addr;
|
||||
dest->sin_family = AF_INET;
|
||||
dest->sin_addr = addr;
|
||||
dest_addr_len = sizeof(struct sockaddr_in);
|
||||
|
||||
if (bind_arg)
|
||||
{
|
||||
r = inet_pton(AF_INET, bind_arg, &addr);
|
||||
if (r == 0)
|
||||
{
|
||||
fatal("Invalid bind IP address %s\n", bind_arg);
|
||||
}
|
||||
|
||||
struct sockaddr_in * bind4 = (struct sockaddr_in *) &bind_addr;
|
||||
bind4->sin_family = AF_INET;
|
||||
bind4->sin_addr = addr;
|
||||
bind_addr_len = sizeof(struct sockaddr_in);
|
||||
}
|
||||
fatal("invalid destination IP address %s\n", dest_arg);
|
||||
}
|
||||
else
|
||||
|
||||
if (addr_info->ai_family == AF_INET6)
|
||||
{
|
||||
// Perhaps it's an IPv6 address?
|
||||
r = inet_pton(AF_INET6, dest_arg, &addr6);
|
||||
if (r == 0)
|
||||
{
|
||||
fatal("Invalid destination IP address %s\n", dest_arg);
|
||||
}
|
||||
|
||||
struct sockaddr_in6 * dest6 = (struct sockaddr_in6 *) &dest_addr;
|
||||
dest6->sin6_family = AF_INET6;
|
||||
dest6->sin6_addr = addr6;
|
||||
dest_addr_len = sizeof(struct sockaddr_in6);
|
||||
|
||||
af_family = AF_INET6;
|
||||
ip_proto = IPPROTO_ICMPV6;
|
||||
echo_request_type = ICMP6_ECHO_REQUEST;
|
||||
echo_reply_type = ICMP6_ECHO_REPLY;
|
||||
}
|
||||
else if (addr_info->ai_family != AF_INET)
|
||||
{
|
||||
fatal("invalid destination IP address %s\n", dest_arg);
|
||||
}
|
||||
|
||||
if (bind_arg)
|
||||
|
||||
dest_addr_len = addr_info->ai_addrlen;
|
||||
memcpy(&dest_addr, addr_info->ai_addr, dest_addr_len);
|
||||
freeaddrinfo(addr_info);
|
||||
|
||||
// Check bind address
|
||||
if (bind_arg)
|
||||
{
|
||||
// Address family must match
|
||||
hint.ai_family = af_family;
|
||||
|
||||
r = getaddrinfo(bind_arg, NULL, &hint, &addr_info);
|
||||
if (r != 0)
|
||||
{
|
||||
r = inet_pton(AF_INET6, bind_arg, &addr6);
|
||||
if (r == 0)
|
||||
{
|
||||
fatal("Invalid source IP address %s\n", bind_arg);
|
||||
}
|
||||
|
||||
struct sockaddr_in6 * bind6 = (struct sockaddr_in6 *) &bind_addr;
|
||||
bind6->sin6_family = AF_INET6;
|
||||
bind6->sin6_addr = addr6;
|
||||
bind_addr_len = sizeof(struct sockaddr_in6);
|
||||
fatal("invalid bind IP address %s\n", bind_arg);
|
||||
}
|
||||
|
||||
bind_addr_len = addr_info->ai_addrlen;
|
||||
memcpy(&bind_addr, addr_info->ai_addr, bind_addr_len);
|
||||
freeaddrinfo(addr_info);
|
||||
}
|
||||
|
||||
// Check requested data length
|
||||
if (echo_data_len)
|
||||
{
|
||||
if (af_family == AF_INET)
|
||||
{
|
||||
if (echo_data_len > IPV4_ICMP_DATA_MAX)
|
||||
{
|
||||
fatal("data length too large for IPv4 - maximum is %u bytes\n", (unsigned) IPV4_ICMP_DATA_MAX);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (echo_data_len > IPV6_ICMP_DATA_MAX)
|
||||
{
|
||||
fatal("data length too large for IPv6 - maximum is %u bytes\n", (unsigned) IPV6_ICMP_DATA_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
echo_request_len += echo_data_len;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1046,12 +1126,12 @@ main(
|
||||
int argc,
|
||||
char *argv[])
|
||||
{
|
||||
char bind_str[INET6_ADDRSTRLEN] = "(none)";
|
||||
const void * addr;
|
||||
const char * p;
|
||||
int pidfile_fd;
|
||||
char bind_str[ADDR_STR_MAX] = "(none)";
|
||||
int pidfile_fd = -1;
|
||||
pthread_t thread;
|
||||
struct sigaction act;
|
||||
int buflen = PACKET_BUFLEN;
|
||||
ssize_t rs;
|
||||
int r;
|
||||
|
||||
// Handle command line args
|
||||
@@ -1064,12 +1144,17 @@ main(
|
||||
perror("socket");
|
||||
fatal("cannot create send socket\n");
|
||||
}
|
||||
(void) fcntl(send_sock, F_SETFL, FD_CLOEXEC);
|
||||
(void) setsockopt(send_sock, SOL_SOCKET, SO_SNDBUF, &buflen, sizeof(buflen));
|
||||
|
||||
recv_sock = socket(af_family, SOCK_RAW, ip_proto);
|
||||
if (recv_sock == -1)
|
||||
{
|
||||
perror("socket");
|
||||
fatal("cannot create recv socket\n");
|
||||
}
|
||||
(void) fcntl(recv_sock, F_SETFL, FD_CLOEXEC);
|
||||
(void) setsockopt(recv_sock, SOL_SOCKET, SO_RCVBUF, &buflen, sizeof(buflen));
|
||||
|
||||
// Bind our sockets to an address if requested
|
||||
if (bind_addr_len)
|
||||
@@ -1089,13 +1174,13 @@ main(
|
||||
}
|
||||
|
||||
// Drop privileges
|
||||
r = setgid(getgid());
|
||||
r = setuid(getuid());
|
||||
(void) setgid(getgid());
|
||||
(void) setuid(getuid());
|
||||
|
||||
// Create report file
|
||||
if (report_name)
|
||||
{
|
||||
report_fd = open(report_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
report_fd = open(report_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
|
||||
if (report_fd == -1)
|
||||
{
|
||||
perror("open");
|
||||
@@ -1117,13 +1202,13 @@ main(
|
||||
fatal("socket name too large\n");
|
||||
}
|
||||
|
||||
usocket_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
usocket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (usocket_fd == -1)
|
||||
{
|
||||
perror("socket");
|
||||
fatal("cannot create unix domain socket\n");
|
||||
}
|
||||
|
||||
(void) fcntl(usocket_fd, F_SETFL, FD_CLOEXEC);
|
||||
(void) unlink(usocket_name);
|
||||
|
||||
memset(&uaddr, 0, sizeof(uaddr));
|
||||
@@ -1154,7 +1239,7 @@ main(
|
||||
// Create pid file
|
||||
if (pidfile_name)
|
||||
{
|
||||
pidfile_fd = open(pidfile_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
pidfile_fd = open(pidfile_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
|
||||
if (pidfile_fd == -1)
|
||||
{
|
||||
perror("open");
|
||||
@@ -1186,14 +1271,14 @@ main(
|
||||
// Termination handler
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = (void (*)(int)) term_handler;
|
||||
sigaction(SIGTERM, &act, NULL);
|
||||
sigaction(SIGINT, &act, NULL);
|
||||
(void) sigaction(SIGTERM, &act, NULL);
|
||||
(void) sigaction(SIGINT, &act, NULL);
|
||||
|
||||
// Write pid file
|
||||
if (pidfile_name)
|
||||
if (pidfile_fd != -1)
|
||||
{
|
||||
char buf[64];
|
||||
int len;
|
||||
ssize_t len;
|
||||
|
||||
len = snprintf(buf, sizeof(buf), "%u\n", (unsigned) getpid());
|
||||
if (len < 0 || (size_t) len > sizeof(buf))
|
||||
@@ -1201,14 +1286,14 @@ main(
|
||||
fatal("error formatting pidfile\n");
|
||||
}
|
||||
|
||||
r = write(pidfile_fd, buf, len);
|
||||
if (r == -1)
|
||||
rs = write(pidfile_fd, buf, (size_t) len);
|
||||
if (rs == -1)
|
||||
{
|
||||
perror("write");
|
||||
fatal("error writing pidfile\n");
|
||||
}
|
||||
|
||||
r= close(pidfile_fd);
|
||||
r = close(pidfile_fd);
|
||||
if (r == -1)
|
||||
{
|
||||
perror("close");
|
||||
@@ -1217,69 +1302,57 @@ main(
|
||||
}
|
||||
|
||||
// Create the array
|
||||
array_size = time_period_msec / send_interval_msec;
|
||||
array_size = (unsigned int) (time_period_msec / send_interval_msec);
|
||||
array = calloc(array_size, sizeof(*array));
|
||||
if (array == NULL)
|
||||
{
|
||||
fatal("calloc of packet array failed\n");
|
||||
}
|
||||
|
||||
// Allocate the echo request/reply packet buffers
|
||||
echo_request = (icmphdr_t *) malloc(echo_request_len);
|
||||
echo_reply = malloc(echo_reply_len);
|
||||
if (echo_request == NULL || echo_reply == NULL)
|
||||
{
|
||||
fatal("malloc of packet buffers failed\n");
|
||||
}
|
||||
|
||||
// Set the default loss interval
|
||||
if (loss_interval_msec == 0)
|
||||
{
|
||||
loss_interval_msec = send_interval_msec * 2;
|
||||
loss_interval_msec = send_interval_msec * 5;
|
||||
}
|
||||
loss_interval_usec = loss_interval_msec * 1000;
|
||||
|
||||
// Log our parameters
|
||||
if (af_family == AF_INET)
|
||||
// Log our general parameters
|
||||
r = getnameinfo((struct sockaddr *) &dest_addr, dest_addr_len, dest_str, sizeof(dest_str), NULL, 0, NI_NUMERICHOST);
|
||||
if (r != 0)
|
||||
{
|
||||
addr = (const void *) &((struct sockaddr_in *) &dest_addr)->sin_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
addr = (const void *) &((struct sockaddr_in6 *) &dest_addr)->sin6_addr;
|
||||
}
|
||||
p = inet_ntop(af_family, addr, dest_str, sizeof(dest_str));
|
||||
if (p == NULL)
|
||||
{
|
||||
fatal("inet_ntop of destination address failed\n");
|
||||
fatal("getnameinfo of destination address failed\n");
|
||||
}
|
||||
|
||||
if (bind_addr_len)
|
||||
{
|
||||
if (af_family == AF_INET)
|
||||
r = getnameinfo((struct sockaddr *) &bind_addr, bind_addr_len, bind_str, sizeof(bind_str), NULL, 0, NI_NUMERICHOST);
|
||||
if (r != 0)
|
||||
{
|
||||
addr = (const void *) &((struct sockaddr_in *) &bind_addr)->sin_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
addr = (const void *) &((struct sockaddr_in6 *) &bind_addr)->sin6_addr;
|
||||
}
|
||||
p = inet_ntop(af_family, addr, bind_str, sizeof(bind_str));
|
||||
if (p == NULL)
|
||||
{
|
||||
fatal("inet_ntop of bind address failed\n");
|
||||
fatal("getnameinfo of bind address failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Log our general parameters
|
||||
logger("send_interval %lums loss_interval %lums time_period %lums report_interval %lums alert_interval %lums latency_alarm %lums loss_alarm %lu%% dest_addr %s bind_addr %s identifier \"%s\"\n",
|
||||
send_interval_msec, loss_interval_msec, time_period_msec, report_interval_msec,
|
||||
logger("send_interval %lums loss_interval %lums time_period %lums report_interval %lums data_len %lu alert_interval %lums latency_alarm %lums loss_alarm %lu%% dest_addr %s bind_addr %s identifier \"%s\"\n",
|
||||
send_interval_msec, loss_interval_msec, time_period_msec, report_interval_msec, echo_data_len,
|
||||
alert_interval_msec, latency_alarm_threshold_msec, loss_alarm_threshold_percent,
|
||||
dest_str, bind_str, identifier);
|
||||
|
||||
// Convert loss interval and alarm threshold to microseconds
|
||||
loss_interval_usec = loss_interval_msec * 1000;
|
||||
latency_alarm_threshold_usec = latency_alarm_threshold_msec * 1000;
|
||||
|
||||
// Set my echo id
|
||||
echo_id = htons(getpid());
|
||||
echo_id = htons((uint16_t) getpid());
|
||||
|
||||
// Set the limit for sequence number to ensure a multiple of array size
|
||||
sequence_limit = array_size;
|
||||
sequence_limit = (uint16_t) array_size;
|
||||
while ((sequence_limit & 0x8000) == 0)
|
||||
{
|
||||
sequence_limit = sequence_limit << 1;
|
||||
sequence_limit <<= 1;
|
||||
}
|
||||
|
||||
// Create recv thread
|
||||
|
||||
25
rrd/README.md
Normal file
25
rrd/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
Example scripts for creating RRD graphs with dpinger
|
||||
|
||||
<br>
|
||||
|
||||
Files and Usage:
|
||||
|
||||
dpinger_rrd_create <name>
|
||||
|
||||
Create the rrd initial file.
|
||||
|
||||
dpinger_rrd_update <name> <target> <additional dpinger options>
|
||||
|
||||
Daemon updater script. Runs dpinger and feeds the rrd file.
|
||||
|
||||
dpinger_rrd_gencgi <name>
|
||||
|
||||
Generate a cgi script that displays graphs.
|
||||
|
||||
dpinger_rrd_graph <name>
|
||||
|
||||
Generate png files for use with static html
|
||||
|
||||
sample.html
|
||||
|
||||
Sample static html to display graphs.
|
||||
30
rrd/dpinger_rrd_create
Executable file
30
rrd/dpinger_rrd_create
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $# -ne 1 ]
|
||||
then
|
||||
echo "usage: $0 name"
|
||||
exit 1
|
||||
fi
|
||||
name="$1"
|
||||
|
||||
rrdfile="${name}.rrd"
|
||||
echo "Creating rrd file ${rrdfile}"
|
||||
|
||||
|
||||
# Time duration method doesn't work in all versions of rrdtool
|
||||
#rrdtool create "${rrdfile}" --step 1m \
|
||||
# DS:latency:GAUGE:5m:0:U \
|
||||
# DS:stddev:GAUGE:5m:0:U \
|
||||
# DS:loss:GAUGE:5m:0:100 \
|
||||
# RRA:AVERAGE:0.5:1m:15d \
|
||||
# RRA:AVERAGE:0.5:5m:90d \
|
||||
# RRA:AVERAGE:0.5:1h:3y
|
||||
|
||||
# This method works in all versions
|
||||
rrdtool create "${rrdfile}" --step 60 \
|
||||
DS:latency:GAUGE:300:0:U \
|
||||
DS:stddev:GAUGE:300:0:U \
|
||||
DS:loss:GAUGE:300:0:100 \
|
||||
RRA:AVERAGE:0.5:1:21600 \
|
||||
RRA:AVERAGE:0.5:5:25920 \
|
||||
RRA:AVERAGE:0.5:60:26352
|
||||
140
rrd/dpinger_rrd_gencgi
Executable file
140
rrd/dpinger_rrd_gencgi
Executable file
@@ -0,0 +1,140 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $# -ne 2 ]
|
||||
then
|
||||
echo "usage: $0 rrdname pngname"
|
||||
exit 1
|
||||
fi
|
||||
rrdname="${1}"
|
||||
pngname="${2}"
|
||||
|
||||
# Prefixes for rrd and png files. Note that if the prefix is a directory, it must incldue the trailing slash
|
||||
# If no value is set, the files are located in the current directory when the cgi script runs
|
||||
rrdprefix=
|
||||
pngprefix=/tmp/
|
||||
|
||||
|
||||
# Graph dimensions
|
||||
#graph_height=240
|
||||
#graph_width=720
|
||||
graph_height=280
|
||||
graph_width=840
|
||||
|
||||
# Preferred font
|
||||
font="DejaVuSansMono"
|
||||
|
||||
# Latency breakpoints in milliseconds
|
||||
latency_s0=20
|
||||
latency_s1=40
|
||||
latency_s2=80
|
||||
latency_s3=160
|
||||
latency_s4=320
|
||||
|
||||
# Latency colors
|
||||
latency_c0="dddddd"
|
||||
latency_c1="ddbbbb"
|
||||
latency_c2="d4aaaa"
|
||||
latency_c3="cc9999"
|
||||
latency_c4="c38888"
|
||||
latency_c5="bb7777"
|
||||
|
||||
# Standard deviation color & opacity
|
||||
stddev_c="55333355"
|
||||
|
||||
# Loss color
|
||||
loss_c="ee0000"
|
||||
|
||||
|
||||
gen_graph()
|
||||
{
|
||||
png=$1
|
||||
rrd=$2
|
||||
start=$3
|
||||
end=$4
|
||||
step=$5
|
||||
description=$6
|
||||
|
||||
echo "<RRD::GRAPH \"${png}\""
|
||||
echo "--lazy"
|
||||
echo "--start \"${start}\" --end \"${end}\" --step \"${step}\""
|
||||
echo "--height ${graph_height} --width ${graph_width}"
|
||||
echo "--title \"Average Latency and Packet Loss - ${description}\""
|
||||
echo "--disable-rrdtool-tag"
|
||||
echo "--color BACK#ffffff"
|
||||
echo "--font DEFAULT:9:\"${font}\""
|
||||
echo "--font AXIS:8:\"${font}\""
|
||||
|
||||
echo "DEF:latency_us=\"${rrd}\":latency:AVERAGE:step=\"${step}\""
|
||||
echo "CDEF:latency=latency_us,1000,/"
|
||||
echo "CDEF:latency_s0=latency,${latency_s0},MIN"
|
||||
echo "CDEF:latency_s1=latency,${latency_s1},MIN"
|
||||
echo "CDEF:latency_s2=latency,${latency_s2},MIN"
|
||||
echo "CDEF:latency_s3=latency,${latency_s3},MIN"
|
||||
echo "CDEF:latency_s4=latency,${latency_s4},MIN"
|
||||
echo "VDEF:latency_min=latency,MINIMUM"
|
||||
echo "VDEF:latency_max=latency,MAXIMUM"
|
||||
echo "VDEF:latency_avg=latency,AVERAGE"
|
||||
echo "VDEF:latency_last=latency,LAST"
|
||||
|
||||
echo "DEF:stddev_us=\"${rrd}\":stddev:AVERAGE:step=\"${step}\""
|
||||
echo "CDEF:stddev=stddev_us,1000,/"
|
||||
echo "VDEF:stddev_min=stddev,MINIMUM"
|
||||
echo "VDEF:stddev_max=stddev,MAXIMUM"
|
||||
echo "VDEF:stddev_avg=stddev,AVERAGE"
|
||||
echo "VDEF:stddev_last=stddev,LAST"
|
||||
|
||||
echo "DEF:loss=\"${rrd}\":loss:AVERAGE:step=\"${step}\""
|
||||
echo "CDEF:loss_neg=loss,-1,*"
|
||||
echo "VDEF:loss_min=loss,MINIMUM"
|
||||
echo "VDEF:loss_max=loss,MAXIMUM"
|
||||
echo "VDEF:loss_avg=loss,AVERAGE"
|
||||
echo "VDEF:loss_last=loss,LAST"
|
||||
|
||||
echo "COMMENT:\" Min Max Avg Last\n\""
|
||||
|
||||
echo "COMMENT:\" \""
|
||||
echo "AREA:latency#${latency_c5}"
|
||||
echo "AREA:latency_s4#${latency_c4}"
|
||||
echo "AREA:latency_s3#${latency_c3}"
|
||||
echo "AREA:latency_s2#${latency_c2}"
|
||||
echo "AREA:latency_s1#${latency_c1}"
|
||||
echo "AREA:latency_s0#${latency_c0}"
|
||||
echo "LINE1:latency#000000:\"Latency \""
|
||||
echo "GPRINT:\"latency_min:%8.3lf ms\t\""
|
||||
echo "GPRINT:\"latency_max:%8.3lf ms\t\""
|
||||
echo "GPRINT:\"latency_avg:%8.3lf ms\t\""
|
||||
echo "GPRINT:\"latency_last:%8.3lf ms\n\""
|
||||
|
||||
echo "COMMENT:\" \""
|
||||
echo "LINE1:stddev#${stddev_c}:\"Stddev \""
|
||||
echo "GPRINT:\"stddev_min:%8.3lf ms\t\""
|
||||
echo "GPRINT:\"stddev_max:%8.3lf ms\t\""
|
||||
echo "GPRINT:\"stddev_avg:%8.3lf ms\t\""
|
||||
echo "GPRINT:\"stddev_last:%8.3lf ms\n\""
|
||||
|
||||
echo "COMMENT:\" \""
|
||||
echo "AREA:loss_neg#${loss_c}:\"Loss \""
|
||||
echo "GPRINT:\"loss_min:%4.1lf %%\t\t\""
|
||||
echo "GPRINT:\"loss_max:%4.1lf %%\t\t\""
|
||||
echo "GPRINT:\"loss_avg:%4.1lf %%\t\t\""
|
||||
echo "GPRINT:\"loss_last:%4.1lf %%\n\""
|
||||
echo "COMMENT:\" \n\""
|
||||
echo "GPRINT:\"latency_last:Ending at %H\\:%M on %B %d, %Y\\r:strftime\""
|
||||
|
||||
echo ">"
|
||||
echo "<p>"
|
||||
}
|
||||
|
||||
(
|
||||
echo "#!/usr/bin/rrdcgi"
|
||||
echo "<html> <head> <title>Latency Statistics for ${rrdname}</title> </head> <body>"
|
||||
|
||||
gen_graph "${pngprefix}${pngname}-1.png" "${rrdprefix}${rrdname}.rrd" "now-8h" "now" "60" "Last 8 hours - 1 minute intervals"
|
||||
gen_graph "${pngprefix}${pngname}-2.png" "${rrdprefix}${rrdname}.rrd" "now-36h" "now" "300" "Last 36 hours - 5 minute intervals"
|
||||
gen_graph "${pngprefix}${pngname}-3.png" "${rrdprefix}${rrdname}.rrd" "now-8d" "now" "1800" "Last 8 days - 30 minute intervals"
|
||||
gen_graph "${pngprefix}${pngname}-4.png" "${rrdprefix}${rrdname}.rrd" "now-60d" "now" "14400" "Last 60 days - 4 hour intervals"
|
||||
gen_graph "${pngprefix}${pngname}-5.png" "${rrdprefix}${rrdname}.rrd" "now-1y" "now" "86400" "Last 1 year - 1 day intervals"
|
||||
gen_graph "${pngprefix}${pngname}-6.png" "${rrdprefix}${rrdname}.rrd" "now-4y" "now" "86400" "Last 4 years - 1 day intervals"
|
||||
|
||||
echo "</body> </html>"
|
||||
) > "${pngname}.cgi"
|
||||
131
rrd/dpinger_rrd_graph
Executable file
131
rrd/dpinger_rrd_graph
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $# -ne 2 ]
|
||||
then
|
||||
echo "usage: $0 rrdname pngname"
|
||||
exit 1
|
||||
fi
|
||||
rrdname="${1}"
|
||||
pngname="${2}"
|
||||
|
||||
# Prefixes for rrd and png files. Note that if the prefix is a directory, it must incldue the trailing slash
|
||||
# If no value is set, the files are located in the current directory
|
||||
rrdprefix=
|
||||
pngprefix=/tmp/
|
||||
|
||||
# Graph dimensions
|
||||
graph_height=240
|
||||
graph_width=720
|
||||
#graph_height=280
|
||||
#graph_width=840
|
||||
|
||||
# Preferred font
|
||||
font="DejaVuSansMono"
|
||||
|
||||
# Latency breakpoints in milliseconds
|
||||
latency_s0=20
|
||||
latency_s1=40
|
||||
latency_s2=80
|
||||
latency_s3=160
|
||||
latency_s4=320
|
||||
|
||||
# Latency colors
|
||||
latency_c0="dddddd"
|
||||
latency_c1="ddbbbb"
|
||||
latency_c2="d4aaaa"
|
||||
latency_c3="cc9999"
|
||||
latency_c4="c38888"
|
||||
latency_c5="bb7777"
|
||||
|
||||
# Standard deviation color & opacity
|
||||
stddev_c="55333355"
|
||||
|
||||
# Loss color
|
||||
loss_c="ee0000"
|
||||
|
||||
|
||||
gen_graph()
|
||||
{
|
||||
png=$1
|
||||
rrd=$2
|
||||
start=$3
|
||||
end=$4
|
||||
step=$5
|
||||
description=$6
|
||||
|
||||
rrdtool graph "${png}" \
|
||||
--lazy \
|
||||
--start "${start}" --end "${end}" --step "${step}" \
|
||||
--height "${graph_height}" --width "${graph_width}" \
|
||||
--title "Average Latency and Packet Loss - ${description}" \
|
||||
--disable-rrdtool-tag \
|
||||
--color BACK#ffffff \
|
||||
--font DEFAULT:9:"${font}" \
|
||||
--font AXIS:8:"${font}" \
|
||||
\
|
||||
DEF:latency_us="${rrd}":latency:AVERAGE:step="${step}" \
|
||||
CDEF:latency=latency_us,1000,/ \
|
||||
CDEF:latency_s0=latency,${latency_s0},MIN \
|
||||
CDEF:latency_s1=latency,${latency_s1},MIN \
|
||||
CDEF:latency_s2=latency,${latency_s2},MIN \
|
||||
CDEF:latency_s3=latency,${latency_s3},MIN \
|
||||
CDEF:latency_s4=latency,${latency_s4},MIN \
|
||||
VDEF:latency_min=latency,MINIMUM \
|
||||
VDEF:latency_max=latency,MAXIMUM \
|
||||
VDEF:latency_avg=latency,AVERAGE \
|
||||
VDEF:latency_last=latency,LAST \
|
||||
\
|
||||
DEF:stddev_us="${rrd}":stddev:AVERAGE:step="${step}" \
|
||||
CDEF:stddev=stddev_us,1000,/ \
|
||||
VDEF:stddev_min=stddev,MINIMUM \
|
||||
VDEF:stddev_max=stddev,MAXIMUM \
|
||||
VDEF:stddev_avg=stddev,AVERAGE \
|
||||
VDEF:stddev_last=stddev,LAST \
|
||||
\
|
||||
DEF:loss="${rrd}":loss:AVERAGE:step="${step}" \
|
||||
CDEF:loss_neg=loss,-1,* \
|
||||
VDEF:loss_min=loss,MINIMUM \
|
||||
VDEF:loss_max=loss,MAXIMUM \
|
||||
VDEF:loss_avg=loss,AVERAGE \
|
||||
VDEF:loss_last=loss,LAST \
|
||||
\
|
||||
COMMENT:" Min Max Avg Last\n" \
|
||||
\
|
||||
COMMENT:" " \
|
||||
AREA:latency#${latency_c5} \
|
||||
AREA:latency_s4#${latency_c4} \
|
||||
AREA:latency_s3#${latency_c3} \
|
||||
AREA:latency_s2#${latency_c2} \
|
||||
AREA:latency_s1#${latency_c1} \
|
||||
AREA:latency_s0#${latency_c0} \
|
||||
LINE1:latency#000000:"Latency " \
|
||||
GPRINT:"latency_min:%8.3lf ms\t" \
|
||||
GPRINT:"latency_max:%8.3lf ms\t" \
|
||||
GPRINT:"latency_avg:%8.3lf ms\t" \
|
||||
GPRINT:"latency_last:%8.3lf ms\n" \
|
||||
\
|
||||
COMMENT:" " \
|
||||
LINE1:stddev#${stddev_c}:"Stddev " \
|
||||
GPRINT:"stddev_min:%8.3lf ms\t" \
|
||||
GPRINT:"stddev_max:%8.3lf ms\t" \
|
||||
GPRINT:"stddev_avg:%8.3lf ms\t" \
|
||||
GPRINT:"stddev_last:%8.3lf ms\n" \
|
||||
\
|
||||
COMMENT:" " \
|
||||
AREA:loss_neg#${loss_c}:"Loss " \
|
||||
GPRINT:"loss_min:%4.1lf %%\t\t" \
|
||||
GPRINT:"loss_max:%4.1lf %%\t\t" \
|
||||
GPRINT:"loss_avg:%4.1lf %%\t\t" \
|
||||
GPRINT:"loss_last:%4.1lf %%\n" \
|
||||
\
|
||||
COMMENT:" \n" \
|
||||
GPRINT:"latency_last:Ending at %H\:%M on %B %d, %Y\r:strftime"
|
||||
}
|
||||
|
||||
|
||||
gen_graph "${pngprefix}${pngname}-1.png" "${rrdprefix}${rrdname}.rrd" "now-8h" "now" "60" "Last 8 hours - 1 minute intervals"
|
||||
gen_graph "${pngprefix}${pngname}-2.png" "${rrdprefix}${rrdname}.rrd" "now-36h" "now" "300" "Last 36 hours - 5 minute intervals"
|
||||
gen_graph "${pngprefix}${pngname}-3.png" "${rrdprefix}${rrdname}.rrd" "now-8d" "now" "1800" "Last 8 days - 30 minute intervals"
|
||||
gen_graph "${pngprefix}${pngname}-4.png" "${rrdprefix}${rrdname}.rrd" "now-60d" "now" "14400" "Last 60 days - 4 hour intervals"
|
||||
gen_graph "${pngprefix}${pngname}-5.png" "${rrdprefix}${rrdname}.rrd" "now-1y" "now" "86400" "Last 1 year - 1 day intervals"
|
||||
gen_graph "${pngprefix}${pngname}-6.png" "${rrdprefix}${rrdname}.rrd" "now-4y" "now" "86400" "Last 4 years - 1 day intervals"
|
||||
22
rrd/dpinger_rrd_update
Executable file
22
rrd/dpinger_rrd_update
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $# -lt 2 ]
|
||||
then
|
||||
echo "usage: $0 rrdname targetip [dpinger options]"
|
||||
exit 1
|
||||
fi
|
||||
name="$1"
|
||||
targetip="$2"
|
||||
shift 2
|
||||
options=$*
|
||||
|
||||
# Where the dpinger executable is located
|
||||
dpinger=/usr/local/bin/dpinger
|
||||
|
||||
|
||||
rrdfile="${name}.rrd"
|
||||
|
||||
${dpinger} -f ${options} -s 500m -t 60s -r 60s ${targetip} |
|
||||
while read -r latency stddev loss; do
|
||||
rrdtool update "${rrdfile}" -t latency:stddev:loss "N:$latency:$stddev:$loss"
|
||||
done
|
||||
16
rrd/sample.html
Normal file
16
rrd/sample.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<html>
|
||||
<head><title>WAN Statistics</title></head>
|
||||
<body>
|
||||
<img src="/tmp/wan-1.png" alt="wan-1">
|
||||
<p>
|
||||
<img src="/tmp/wan-2.png" alt="wan-2">
|
||||
<p>
|
||||
<img src="/tmp/wan-3.png" alt="wan-3">
|
||||
<p>
|
||||
<img src="/tmp/wan-4.png" alt="wan-4">
|
||||
<p>
|
||||
<img src="/tmp/wan-5.png" alt="wan-5">
|
||||
<p>
|
||||
<img src="/tmp/wan-6.png" alt="wan-6">
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user