I have a web app that is using ActiveResource to talk to another server that has a rate limit on the connection. I'm curious how I can best monitor that from the host my web app is running on - that is, from a bash prompt on linux from my server, how can I measure the outbound requests per second my machine is making to another?
I'm looking for a linux one-liner that given an interface, a hostname, and/or some combination thereof, tells me the connection rate I'm making to that server. I've gotten close with tools like tc and iftop, but those are reporting the amount of data transferred, not the connections made... so its not quite what I'm looking for. I'd love to see something like:
$awesometool --host thetargethost.com --interface eth0 --interval 5
gathering statistics…
Requests per second report for thetargethost.com over interface eth0
avg: 23 req/sec min: 12 req/sec max 39 req/sec
5 samples taken
Can anyone point me to one?
tcpdump(8)
can provide something very similar; search for TCP packets with the SYN
flag set to catch the first packet in the three-way handshake that are destined for your other peer:
$ sudo tcpdump -c 10 -i eth0 "tcp[tcpflags] & (tcp-syn) != 0 and dst 192.168.0.1"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
18:26:24.800308 IP haig.59419 > 192.168.0.1.telnet: Flags [S], seq 3197302320, win 14600, options [mss 1460,sackOK,TS val 19460844 ecr 0,nop,wscale 7], length 0
...
18:26:27.420132 IP haig.59428 > 192.168.0.1.telnet: Flags [S], seq 1238498237, win 14600, options [mss 1460,sackOK,TS val 19461106 ecr 0,nop,wscale 7], length 0
10 packets captured
10 packets received by filter
0 packets dropped by kernel
You could use /usr/bin/time
or your shell's time
built-in, or do some arithmetic with the timestamps in the output, to get your average rate per second. (Use more than ten packets -- this was just for demonstration.)
Update
I wrote a little program to run tcpdump(8)
, count the packets, and report on how many packets were sent within the interval specified:
# ./counter --host 192.168.0.1 --interface eth0 --interval 3
2 requests in 3 seconds; average 0.67 req/seq
20 requests in 3 seconds; average 6.67 req/seq
19 requests in 3 seconds; average 6.33 req/seq
19 requests in 3 seconds; average 6.33 req/seq
^C
# ./counter --host 192.168.0.1 --interface eth0 --interval 5
30 requests in 5 seconds; average 6.00 req/seq
20 requests in 5 seconds; average 4.00 req/seq
1176 requests in 5 seconds; average 235.20 req/seq
1414 requests in 5 seconds; average 282.80 req/seq
0 requests in 5 seconds; average 0.00 req/seq
^C
Because it asks tcpdump(8)
to use line-buffered output, I'm a little afraid that it might not scale beyond 200-300 requests per second, at least on my hardware. But without the line-buffered output, tcpdump(8)
would wait until its output buffer (see setvbuf(3)
for details) would be full before sending any output, causing extremely jittery results.
But if your rate of connections isn't that high, this will probably do what you need. If your rate of connections is higher, then this little hack is probably best ignored -- it strikes me that probably iptables(8)
can count streams.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#define CMDLEN 1024
#define TCPDUMPLEN 4096
int show_stats;
long counter;
void alarm_handler(int signum)
{
show_stats = 1;
}
void install_handler(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_handler = &alarm_handler;
if (sigaction(SIGALRM, &sa, NULL) == -1) {
perror("Can't install alarm handler");
exit(1);
}
}
int count_lines(char *p, int bytes)
{
int counter = 0;
char *i;
for (i=p; i < p+bytes ; i++) {
if (*i == '\n')
counter++;
}
return counter;
}
int spawn_tcpdump(char *host, char *interface)
{
int fd[2];
pid_t child;
if (pipe(fd) == -1) {
perror("Can't create pipes");
exit(1);
}
child = fork();
if (child == -1) {
perror("Can't fork(2) for tcpdump");
exit(1);
}
if (child == 0) {
int null;
int len;
char syn_and_dst[CMDLEN];
len = snprintf(syn_and_dst, CMDLEN, "tcp[tcpflags] & (tcp-syn) != 0 and dst %s", host);
if (len > CMDLEN) {
perror("host argument too long");
exit(1);
}
/* child writes into pipe */
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
/* throw away first two lines of tcpdump output */
null = open("/dev/null", O_WRONLY);
if (null == -1) {
perror("Can't open /dev/null");
exit(1);
}
dup2(null, STDERR_FILENO);
execl("/usr/sbin/tcpdump", "tcpdump", "-l", "-n", "-s 96", "-i", interface, syn_and_dst, (char *) NULL);
/* can't reach */
perror("Cannot execute tcpdump");
exit(1);
} else {
/* parent reads from pipe */
close(fd[1]);
return fd[0];
}
}
int main(int argc, char *argv[])
{
int tcpdump;
char *host;
char *interface;
long interval;
while (1) {
int option_index;
int c;
static struct option opts[] = {
{"host", required_argument, NULL, 'h'},
{"interface", required_argument, NULL, 'i'},
{"interval", required_argument, NULL, 'n'},
{0, 0, 0, 0},
};
c = getopt_long(argc, argv, "", opts, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
host = strdup(optarg);
break;
case 'i':
interface = strdup(optarg);
break;
case 'n': {
char *endptr;
interval = strtol(optarg, &endptr, 10);
if (!(optarg[0] != '\0' && endptr[0] == '\0')) {
fprintf(stderr, "Expected integer; invalid"
" input '%s'\n", optarg);
exit(1);
}
}
break;
default:
fprintf(stderr, "Option parsing error\n");
exit(1);
}
}
if (optind < argc) {
fprintf(stderr, "unexpected arguments: ");
while (optind < argc) {
fprintf(stderr, "%s ", argv[optind++]);
}
fprintf(stderr, "\n");
}
tcpdump = spawn_tcpdump(host, interface);
install_handler();
alarm(interval);
while(1) {
if (show_stats) {
printf("%ld requests in %ld seconds; average %2.2f req/seq\n",
counter, interval, (double)counter / (double)interval);
counter = 0;
show_stats = 0;
alarm(interval);
} else {
char buffer[TCPDUMPLEN];
int ret;
memset(buffer, 0, TCPDUMPLEN);
ret = read(tcpdump, buffer, TCPDUMPLEN);
if (ret == -1 && errno == EINTR) {
/* nop */
} else if (ret == -1) {
perror("read");
exit(1);
} else {
counter += count_lines(buffer, ret);
}
}
}
exit(0);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With