mirror of
https://github.com/dennypage/dpinger.git
synced 2024-05-19 06:50:01 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b032751e5 | ||
|
|
84ee15b155 | ||
|
|
e10c51ad95 | ||
|
|
579ae3d66b | ||
|
|
64e644e7be | ||
|
|
a18d82ab6e | ||
|
|
34b0bb924e | ||
|
|
4173834bbe | ||
|
|
2a8eaa0c8f | ||
|
|
edb883498d | ||
|
|
c276feb339 | ||
|
|
ef21655e77 | ||
|
|
2d2d21892a | ||
|
|
a24c0cd0d0 | ||
|
|
fdbd4a1d96 | ||
|
|
6796fa0752 | ||
|
|
24022ac098 | ||
|
|
42c84e965e | ||
|
|
f94f6dcd47 | ||
|
|
99208a60fe | ||
|
|
eb67d76de0 | ||
|
|
0b68c438f2 | ||
|
|
daac746074 | ||
|
|
5348af36c6 | ||
|
|
b0fc95f618 | ||
|
|
e9ffd0b43e | ||
|
|
9e8968adce | ||
|
|
a8b44bedac | ||
|
|
1d34caccbd | ||
|
|
9c5bac8658 | ||
|
|
3a19391cee | ||
|
|
570ade420a | ||
|
|
3825066db9 | ||
|
|
f06b3c8f36 | ||
|
|
4a57f2584c | ||
|
|
afbde05bcb | ||
|
|
85d345f47f | ||
|
|
e0a0ae14f9 | ||
|
|
87cd4b6e3b |
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-2017, 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 -Wno-reserved-id-macro
|
||||
|
||||
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>
|
||||
13
NOTES.md
Normal file
13
NOTES.md
Normal file
@@ -0,0 +1,13 @@
|
||||
<b>Loss accuracy</b>
|
||||
|
||||
In general, dpinger works a bit differently than other latency monitors. Rather than a "probe" that fires off and processes a handful of echo request/replies all at once, dpinger maintains a rolling array of echo requests spaced on the send interval. In other words, instead of waking up every second and sending 4 echo requests at once, dpinger sends an echo request every 250 milliseconds. When dpinger receives an echo reply, the time difference between the request packet and reply packet (latency) is recorded. There is nothing that times out an echo request/reply and records it as permanently lost.
|
||||
|
||||
When the alert check is made, or a report is generated, dpinger goes through the array and examines each echo request. If a reply has been received, it is used as part of the overall latency calculation. If a reply has not yet been received, the amount of time since the request is compared against the loss interval. If it is greater than the loss interval, the request/reply is counted as lost in the current report. However the concept of the request/reply being lost is not a permanent decision. In subsequent reports, if a the missing reply has been received, its latency will be used instead of being counted as lost.
|
||||
|
||||
It's important to keep in mind that latency and loss are reported as averages across the entire request set. The default time period for dpinger is 30 seconds, with an echo request being sent every 250 milliseconds. This means that the latency and loss will be reported as averages across 115-120 samples. The alert check runs every second by default. So each time, the 4 oldest entries in the set have been replaced by the 4 newest ones.
|
||||
|
||||
Note that if you want accurate loss reporting, it is important that the number of samples be sufficient. In order to achieve 1% loss resolution, you have need more than 100 samples in the set. The calculation for loss resolution is:
|
||||
|
||||
100 * send_interval / (time_period - loss_interval)
|
||||
|
||||
The default settings for dpinger report loss with an accuracy of 0.87%.
|
||||
476
dpinger.c
476
dpinger.c
@@ -1,6 +1,6 @@
|
||||
|
||||
//
|
||||
// Copyright (c) 2015, Denny Page
|
||||
// Copyright (c) 2015-2017, Denny Page
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -27,6 +27,11 @@
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
|
||||
|
||||
// Silly that this is required for accept4 on Linux
|
||||
#define _GNU_SOURCE
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
@@ -39,10 +44,11 @@
|
||||
#include <signal.h>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip_icmp.h>
|
||||
@@ -52,17 +58,6 @@
|
||||
#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;
|
||||
@@ -74,16 +69,17 @@ static const char * pidfile_name = NULL;
|
||||
// Flags
|
||||
static unsigned int flag_rewind = 0;
|
||||
static unsigned int flag_syslog = 0;
|
||||
static unsigned int flag_priority = 0;
|
||||
|
||||
// String representation of target
|
||||
#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 = 30000;
|
||||
static unsigned long time_period_msec = 60000;
|
||||
|
||||
// Interval between sends in ms
|
||||
static unsigned long send_interval_msec = 250;
|
||||
static unsigned long send_interval_msec = 500;
|
||||
|
||||
// Interval before a sequence is initially treated as lost
|
||||
// Input from command line in ms and used in us
|
||||
@@ -150,8 +146,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
|
||||
@@ -175,18 +171,29 @@ 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;
|
||||
static uint16_t next_sequence = 0;
|
||||
static uint16_t sequence_limit;
|
||||
|
||||
// Receive thread ready
|
||||
static unsigned int recv_ready = 0;
|
||||
|
||||
//
|
||||
// Termination handler
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void
|
||||
term_handler(void)
|
||||
{
|
||||
@@ -206,10 +213,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,
|
||||
@@ -254,7 +258,7 @@ cksum(
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
sum += (sum >> 16);
|
||||
|
||||
return ~sum;
|
||||
return (uint16_t) ~sum;
|
||||
}
|
||||
|
||||
|
||||
@@ -280,7 +284,7 @@ llsqrt(
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
return (unsigned long) s;
|
||||
}
|
||||
|
||||
|
||||
@@ -292,7 +296,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)
|
||||
@@ -304,24 +308,39 @@ 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;
|
||||
|
||||
// Give the recv thread a moment to initialize
|
||||
sleeptime.tv_sec = 0;
|
||||
sleeptime.tv_nsec = 10000; // 10us
|
||||
do {
|
||||
r = nanosleep(&sleeptime, NULL);
|
||||
if (r == -1)
|
||||
{
|
||||
logger("nanosleep error in send thread waiting for recv thread: %d\n", errno);
|
||||
}
|
||||
} while (recv_ready == 0);
|
||||
|
||||
// Set up the timespec for nanosleep
|
||||
sleeptime.tv_sec = send_interval_msec / 1000;
|
||||
@@ -329,57 +348,58 @@ send_thread(
|
||||
|
||||
while (1)
|
||||
{
|
||||
r = nanosleep(&sleeptime, NULL);
|
||||
if (r == -1)
|
||||
{
|
||||
logger("nanosleep error in send thread: %d\n", errno);
|
||||
}
|
||||
|
||||
// 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)
|
||||
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);
|
||||
}
|
||||
|
||||
next_slot = (next_slot + 1) % array_size;
|
||||
next_sequence = (next_sequence + 1) % sequence_limit;
|
||||
}
|
||||
|
||||
// notreached
|
||||
return arg;
|
||||
r = nanosleep(&sleeptime, NULL);
|
||||
if (r == -1)
|
||||
{
|
||||
logger("nanosleep error in send thread: %d\n", errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// 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;
|
||||
|
||||
// Thread startup complete
|
||||
recv_ready = 1;
|
||||
|
||||
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;
|
||||
@@ -392,25 +412,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;
|
||||
@@ -432,9 +452,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -450,6 +467,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;
|
||||
@@ -463,8 +481,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 += (unsigned long long) latency_usec * latency_usec;
|
||||
}
|
||||
else if (array[slot].status == PACKET_STATUS_SENT &&
|
||||
ts_elapsed_usec(&array[slot].time_sent, &now) > loss_interval_usec)
|
||||
@@ -477,10 +496,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 - ((unsigned long long) avg * avg));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -502,8 +523,10 @@ report(
|
||||
//
|
||||
// Report thread
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void *
|
||||
report_thread(
|
||||
__attribute__ ((unused))
|
||||
void * arg)
|
||||
{
|
||||
char buf[OUTPUT_MAX];
|
||||
@@ -511,7 +534,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
|
||||
@@ -534,33 +558,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;
|
||||
@@ -654,16 +677,15 @@ alert_thread(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notreached
|
||||
return arg;
|
||||
}
|
||||
|
||||
//
|
||||
// Unix socket thread
|
||||
//
|
||||
__attribute__ ((noreturn))
|
||||
static void *
|
||||
usocket_thread(
|
||||
__attribute__ ((unused))
|
||||
void * arg)
|
||||
{
|
||||
char buf[OUTPUT_MAX];
|
||||
@@ -671,14 +693,20 @@ 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)
|
||||
{
|
||||
#if defined(DISABLE_ACCEPT4)
|
||||
// Legacy
|
||||
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);
|
||||
#else
|
||||
sock_fd = accept4(usocket_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||
#endif
|
||||
|
||||
report(&average_latency_usec, &latency_deviation, &average_loss_percent);
|
||||
|
||||
@@ -688,14 +716,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);
|
||||
@@ -704,9 +732,6 @@ usocket_thread(
|
||||
logger("close error in usocket thread: %d\n", errno);
|
||||
}
|
||||
}
|
||||
|
||||
// notreached
|
||||
return arg;
|
||||
}
|
||||
|
||||
|
||||
@@ -719,10 +744,10 @@ get_time_arg_msec(
|
||||
const char * arg,
|
||||
unsigned long * value)
|
||||
{
|
||||
unsigned long t;
|
||||
long t;
|
||||
char * suffix;
|
||||
|
||||
t = strtoul(arg, &suffix, 10);
|
||||
t = strtol(arg, &suffix, 10);
|
||||
if (*suffix == 'm')
|
||||
{
|
||||
// Milliseconds
|
||||
@@ -731,17 +756,17 @@ get_time_arg_msec(
|
||||
else if (*suffix == 's')
|
||||
{
|
||||
// Seconds
|
||||
*value *= 1000;
|
||||
t *= 1000;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
// Garbage in the number?
|
||||
if (*suffix != 0)
|
||||
// Invalid specification?
|
||||
if (t < 0 || *suffix != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
*value = t;
|
||||
*value = (unsigned long) t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -754,22 +779,57 @@ get_percent_arg(
|
||||
const char * arg,
|
||||
unsigned long * value)
|
||||
{
|
||||
unsigned long t;
|
||||
long t;
|
||||
char * suffix;
|
||||
|
||||
t = strtoul(arg, &suffix, 10);
|
||||
t = strtol(arg, &suffix, 10);
|
||||
if (*suffix == '%')
|
||||
{
|
||||
suffix++;
|
||||
}
|
||||
|
||||
// Garbage in the number?
|
||||
if (*suffix != 0 || t > 100)
|
||||
// Invalid specification?
|
||||
if (t < 0 || t > 100 || *suffix != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
*value = t;
|
||||
*value = (unsigned long) t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Decode a byte length argument
|
||||
//
|
||||
static int
|
||||
get_length_arg(
|
||||
const char * arg,
|
||||
unsigned long * value)
|
||||
{
|
||||
long t;
|
||||
char * suffix;
|
||||
|
||||
t = strtol(arg, &suffix, 10);
|
||||
if (*suffix == 'b')
|
||||
{
|
||||
// Bytes
|
||||
suffix++;
|
||||
}
|
||||
else if (*suffix == 'k')
|
||||
{
|
||||
// Kilobytes
|
||||
t *= 1024;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
// Invalid specification?
|
||||
if (t < 0 || *suffix != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
*value = (unsigned long) t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -781,16 +841,18 @@ 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] [-P] [-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, " -P priority scheduling for receive thread (requires root)\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 5x send interval)\n");
|
||||
fprintf(stderr, " -t time period over which results are averaged (default 30s)\n");
|
||||
fprintf(stderr, " -s time interval between echo requests (default 500ms)\n");
|
||||
fprintf(stderr, " -l time interval before packets are treated as lost (default 4x send interval)\n");
|
||||
fprintf(stderr, " -t time period over which results are averaged (default 60s)\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");
|
||||
@@ -816,19 +878,17 @@ usage(void)
|
||||
//
|
||||
// Fatal error
|
||||
//
|
||||
__attribute__ ((noreturn, format (printf, 1, 2)))
|
||||
static void
|
||||
fatal(
|
||||
const char * format,
|
||||
...)
|
||||
{
|
||||
if (format)
|
||||
{
|
||||
va_list args;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@@ -852,7 +912,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, "fRSPB:s:l:t:r:d:o:A:D:L:C:i:u:p:")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
@@ -868,6 +928,10 @@ parse_args(
|
||||
flag_syslog = 1;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
flag_priority = 1;
|
||||
break;
|
||||
|
||||
case 'B':
|
||||
bind_arg = optarg;
|
||||
break;
|
||||
@@ -904,6 +968,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;
|
||||
@@ -918,7 +990,7 @@ parse_args(
|
||||
|
||||
case 'D':
|
||||
r = get_time_arg_msec(optarg, &latency_alarm_threshold_msec);
|
||||
if (r || latency_alarm_threshold_msec == 0)
|
||||
if (r)
|
||||
{
|
||||
fatal("invalid latency alarm threshold %s\n", optarg);
|
||||
}
|
||||
@@ -927,7 +999,7 @@ parse_args(
|
||||
|
||||
case 'L':
|
||||
r = get_percent_arg(optarg, &loss_alarm_threshold_percent);
|
||||
if (r || loss_alarm_threshold_percent == 0)
|
||||
if (r)
|
||||
{
|
||||
fatal("invalid loss alarm threshold %s\n", optarg);
|
||||
}
|
||||
@@ -935,7 +1007,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");
|
||||
@@ -947,7 +1019,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);
|
||||
@@ -965,7 +1037,7 @@ parse_args(
|
||||
|
||||
default:
|
||||
usage();
|
||||
fatal(NULL);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -973,7 +1045,7 @@ parse_args(
|
||||
if (argc != optind + 1)
|
||||
{
|
||||
usage();
|
||||
fatal(NULL);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
dest_arg = argv[optind];
|
||||
|
||||
@@ -983,17 +1055,17 @@ parse_args(
|
||||
fatal("no activity enabled\n");
|
||||
}
|
||||
|
||||
// Ensure we have something to average over
|
||||
if (time_period_msec < send_interval_msec)
|
||||
// Ensure there is a minimum of one resolved slot at all times
|
||||
if (time_period_msec <= send_interval_msec * 2 + loss_interval_msec)
|
||||
{
|
||||
fatal("time period cannot be less than send interval\n");
|
||||
fatal("the time period must be greater than twice the send interval plus the loss interval\n");
|
||||
}
|
||||
|
||||
// Ensure we don't have sequence space issues. This really should only be hit by
|
||||
// complete accident. Even a ratio of 16384:1 would be excessive.
|
||||
if (time_period_msec / send_interval_msec > 65536)
|
||||
{
|
||||
fatal("ratio of time period to send interval cannot exceed 65536:1\n");
|
||||
fatal("the ratio of time period to send interval cannot exceed 65536:1\n");
|
||||
}
|
||||
|
||||
// Check destination address
|
||||
@@ -1041,6 +1113,27 @@ parse_args(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1053,9 +1146,14 @@ main(
|
||||
char *argv[])
|
||||
{
|
||||
char bind_str[ADDR_STR_MAX] = "(none)";
|
||||
char pidbuf[64];
|
||||
int pidfile_fd = -1;
|
||||
pid_t pid;
|
||||
pthread_t thread;
|
||||
struct sigaction act;
|
||||
int buflen = PACKET_BUFLEN;
|
||||
ssize_t len;
|
||||
ssize_t rs;
|
||||
int r;
|
||||
|
||||
// Handle command line args
|
||||
@@ -1069,6 +1167,7 @@ main(
|
||||
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)
|
||||
@@ -1077,6 +1176,7 @@ main(
|
||||
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)
|
||||
@@ -1096,8 +1196,68 @@ main(
|
||||
}
|
||||
|
||||
// Drop privileges
|
||||
r = setgid(getgid());
|
||||
r = setuid(getuid());
|
||||
(void) setgid(getgid());
|
||||
(void) setuid(getuid());
|
||||
|
||||
// Create pid file
|
||||
if (pidfile_name)
|
||||
{
|
||||
pidfile_fd = open(pidfile_name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
|
||||
if (pidfile_fd != -1)
|
||||
{
|
||||
// Lock the pid file
|
||||
r = flock(pidfile_fd, LOCK_EX | LOCK_NB);
|
||||
if (r == -1)
|
||||
{
|
||||
perror("flock");
|
||||
fatal("error locking pid file\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pid file already exists?
|
||||
pidfile_fd = open(pidfile_name, O_RDWR | O_CREAT | O_CLOEXEC, 0644);
|
||||
if (pidfile_fd == -1)
|
||||
{
|
||||
perror("open");
|
||||
fatal("cannot create/open pid file %s\n", pidfile_name);
|
||||
}
|
||||
|
||||
// Lock the pid file
|
||||
r = flock(pidfile_fd, LOCK_EX | LOCK_NB);
|
||||
if (r == -1)
|
||||
{
|
||||
fatal("pid file %s is in use by another process\n", pidfile_name);
|
||||
}
|
||||
|
||||
// Check for existing pid
|
||||
rs = read(pidfile_fd, pidbuf, sizeof(pidbuf) - 1);
|
||||
if (rs > 0)
|
||||
{
|
||||
pidbuf[rs] = 0;
|
||||
|
||||
pid = (pid_t) strtol(pidbuf, NULL, 10);
|
||||
if (pid > 0)
|
||||
{
|
||||
// Is the pid still alive?
|
||||
r = kill(pid, 0);
|
||||
if (r == 0)
|
||||
{
|
||||
fatal("pid file %s is in use by process %u\n", pidfile_name, (unsigned int) pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the pid file
|
||||
(void) lseek(pidfile_fd, 0, 0);
|
||||
r = ftruncate(pidfile_fd, 0);
|
||||
if (r == -1)
|
||||
{
|
||||
perror("ftruncate");
|
||||
fatal("cannot write pid file %s\n", pidfile_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create report file
|
||||
if (report_name)
|
||||
@@ -1131,7 +1291,6 @@ main(
|
||||
fatal("cannot create unix domain socket\n");
|
||||
}
|
||||
(void) fcntl(usocket_fd, F_SETFL, FD_CLOEXEC);
|
||||
|
||||
(void) unlink(usocket_name);
|
||||
|
||||
memset(&uaddr, 0, sizeof(uaddr));
|
||||
@@ -1159,31 +1318,20 @@ main(
|
||||
}
|
||||
}
|
||||
|
||||
// Create pid file
|
||||
if (pidfile_name)
|
||||
{
|
||||
pidfile_fd = open(pidfile_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
|
||||
if (pidfile_fd == -1)
|
||||
{
|
||||
perror("open");
|
||||
fatal("cannot open/create pid file %s\n", pidfile_name);
|
||||
}
|
||||
}
|
||||
|
||||
// End of general errors from command line options
|
||||
|
||||
// Self background
|
||||
if (foreground == 0)
|
||||
{
|
||||
r = fork();
|
||||
pid = fork();
|
||||
|
||||
if (r == -1)
|
||||
if (pid == -1)
|
||||
{
|
||||
perror("fork");
|
||||
fatal("cannot background\n");
|
||||
}
|
||||
|
||||
if (r)
|
||||
if (pid)
|
||||
{
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
@@ -1194,23 +1342,20 @@ 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_fd != -1)
|
||||
{
|
||||
char buf[64];
|
||||
int len;
|
||||
|
||||
len = snprintf(buf, sizeof(buf), "%u\n", (unsigned) getpid());
|
||||
if (len < 0 || (size_t) len > sizeof(buf))
|
||||
len = snprintf(pidbuf, sizeof(pidbuf), "%u\n", (unsigned) getpid());
|
||||
if (len < 0 || (size_t) len > sizeof(pidbuf))
|
||||
{
|
||||
fatal("error formatting pidfile\n");
|
||||
}
|
||||
|
||||
r = write(pidfile_fd, buf, len);
|
||||
if (r == -1)
|
||||
rs = write(pidfile_fd, pidbuf, (size_t) len);
|
||||
if (rs == -1)
|
||||
{
|
||||
perror("write");
|
||||
fatal("error writing pidfile\n");
|
||||
@@ -1225,17 +1370,25 @@ 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 * 5;
|
||||
loss_interval_msec = send_interval_msec * 4;
|
||||
}
|
||||
loss_interval_usec = loss_interval_msec * 1000;
|
||||
|
||||
@@ -1255,19 +1408,19 @@ main(
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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
|
||||
@@ -1278,6 +1431,27 @@ main(
|
||||
fatal("cannot create recv thread\n");
|
||||
}
|
||||
|
||||
// Set priority on recv thread if requested
|
||||
if (flag_priority)
|
||||
{
|
||||
struct sched_param thread_sched_param;
|
||||
|
||||
r = sched_get_priority_min(SCHED_RR);
|
||||
if (r == -1)
|
||||
{
|
||||
perror("sched_get_priority_min");
|
||||
fatal("cannot determin minimum shceduling priority for SCHED_RR\n");
|
||||
}
|
||||
thread_sched_param.sched_priority = r;
|
||||
|
||||
r = pthread_setschedparam(thread, SCHED_RR, &thread_sched_param);
|
||||
if (r != 0)
|
||||
{
|
||||
perror("pthread_setschedparam");
|
||||
fatal("cannot set receive thread priority\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Create send thread
|
||||
r = pthread_create(&thread, NULL, &send_thread, NULL);
|
||||
if (r != 0)
|
||||
|
||||
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"
|
||||
27
rrd/dpinger_rrd_update
Executable file
27
rrd/dpinger_rrd_update
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/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"
|
||||
if [ \! -w ${rrdfile} ]
|
||||
then
|
||||
echo "$0: file \"${rrdfile}\" does not exist or is not writable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
${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>>Latency Statistics for WAN</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