164 lines
5.8 KiB
C
164 lines
5.8 KiB
C
|
/*
|
||
|
|
||
|
Rate-limit/throttler: stops us from transmitting too fast.
|
||
|
|
||
|
We can send packets at millions of packets/second. This will
|
||
|
melt most networks. Therefore, we need to throttle or rate-limit
|
||
|
how fast we go.
|
||
|
|
||
|
Since we are sending packet at a rate of 10-million-per-second, we
|
||
|
the calculations need to be done in a light-weight manner. For one
|
||
|
thing, we can't do a system-call per packet.
|
||
|
|
||
|
NOTE: one complication to watch for is the difference between clock
|
||
|
time and elapsed time, and that they change. We have to avoid a problem
|
||
|
where somebody suspends the computer for a few days, then wake it up,
|
||
|
at which point the system tries sending a million packets/second instead
|
||
|
of the desired thousand packets/second.
|
||
|
*/
|
||
|
#include "main-throttle.h"
|
||
|
#include "pixie-timer.h"
|
||
|
#include "util-logger.h"
|
||
|
#include <string.h>
|
||
|
#include <stdio.h>
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
void
|
||
|
throttler_start(struct Throttler *throttler, double max_rate)
|
||
|
{
|
||
|
unsigned i;
|
||
|
|
||
|
memset(throttler, 0, sizeof(*throttler));
|
||
|
|
||
|
throttler->max_rate = max_rate;
|
||
|
|
||
|
for (i=0; i<sizeof(throttler->buckets)/sizeof(throttler->buckets[0]); i++) {
|
||
|
throttler->buckets[i].timestamp = pixie_gettime();
|
||
|
throttler->buckets[i].packet_count = 0;
|
||
|
}
|
||
|
|
||
|
throttler->batch_size = 1;
|
||
|
|
||
|
LOG(1, "[+] starting throttler: rate = %0.2f-pps\n", throttler->max_rate);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* We return the number of packets that can be sent in a batch. Thus,
|
||
|
* instead of trying to throttle each packet individually, which has a
|
||
|
* high per-packet cost, we try to throttle a bunch at a time. Normally,
|
||
|
* this function will return 1, only at high rates does it return larger
|
||
|
* numbers.
|
||
|
*
|
||
|
* NOTE: The minimum value this returns is 1. When it's less than that,
|
||
|
* it'll pause and wait until it's ready to send a packet.
|
||
|
***************************************************************************/
|
||
|
uint64_t
|
||
|
throttler_next_batch(struct Throttler *throttler, uint64_t packet_count)
|
||
|
{
|
||
|
uint64_t timestamp;
|
||
|
uint64_t index;
|
||
|
uint64_t old_timestamp;
|
||
|
uint64_t old_packet_count;
|
||
|
double current_rate;
|
||
|
double max_rate = throttler->max_rate;
|
||
|
|
||
|
again:
|
||
|
|
||
|
/* NOTE: this uses CLOCK_MONOTONIC_RAW on Linux, so the timstamp doesn't
|
||
|
* move forward when the machine is suspended */
|
||
|
timestamp = pixie_gettime();
|
||
|
|
||
|
/*
|
||
|
* We record that last 256 buckets, and average the rate over all of
|
||
|
* them.
|
||
|
*/
|
||
|
index = (throttler->index) & 0xFF;
|
||
|
throttler->buckets[index].timestamp = timestamp;
|
||
|
throttler->buckets[index].packet_count = packet_count;
|
||
|
|
||
|
index = (++throttler->index) & 0xFF;
|
||
|
old_timestamp = throttler->buckets[index].timestamp;
|
||
|
old_packet_count = throttler->buckets[index].packet_count;
|
||
|
|
||
|
/*
|
||
|
* If the delay is more than 1-second, then we should reset the system
|
||
|
* in order to avoid transmitting too fast.
|
||
|
*/
|
||
|
if (timestamp - old_timestamp > 1000000) {
|
||
|
//throttler_start(throttler, throttler->max_rate);
|
||
|
throttler->batch_size = 1;
|
||
|
goto again;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Calculate the recent rate.
|
||
|
* NOTE: this isn't the rate "since start", but only the "recent" rate.
|
||
|
* That's so that if the system pauses for a while, we don't flood the
|
||
|
* network trying to catch up.
|
||
|
*/
|
||
|
current_rate = 1.0*(packet_count - old_packet_count)/((timestamp - old_timestamp)/1000000.0);
|
||
|
|
||
|
|
||
|
/*
|
||
|
* If we've been going too fast, then <pause> for a moment, then
|
||
|
* try again.
|
||
|
*/
|
||
|
if (current_rate > max_rate) {
|
||
|
double waittime;
|
||
|
|
||
|
/* calculate waittime, in seconds */
|
||
|
waittime = (current_rate - max_rate) / throttler->max_rate;
|
||
|
|
||
|
/* At higher rates of speed, we don't actually need to wait the full
|
||
|
* interval. It's better to have a much smaller interval, so that
|
||
|
* we converge back on the true rate faster */
|
||
|
waittime *= 0.1;
|
||
|
|
||
|
/* This is in case of gross failure of the system. This should never
|
||
|
* actually happen, unless there is a bug. Really, I ought to make
|
||
|
* this an 'assert()' instead to fail and fix the bug rather than
|
||
|
* silently continuing, but I'm too lazy */
|
||
|
if (waittime > 0.1)
|
||
|
waittime = 0.1;
|
||
|
|
||
|
/* Since we've exceeded the speed limit, we should reduce the
|
||
|
* batch size slightly. We don't do it only by a little bit to
|
||
|
* avoid over-correcting. We want to converge on the correct
|
||
|
* speed gradually. Note that since this happens hundreds or
|
||
|
* thousands of times a second, the convergence is very fast
|
||
|
* even with 0.1% adjustment */
|
||
|
throttler->batch_size *= 0.999;
|
||
|
|
||
|
/* Now we wait for a bit */
|
||
|
pixie_usleep((uint64_t)(waittime * 1000000.0));
|
||
|
|
||
|
/* There are two choices here. We could either return immediately,
|
||
|
* or we can loop around again. Right now, the code loops around
|
||
|
* again in order to support very slow rates, such as 0.5 packets
|
||
|
* per second. Nobody would want to run a scanner that slowly of
|
||
|
* course, but it's great for testing */
|
||
|
//return (uint64_t)throttler->batch_size;
|
||
|
goto again;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Calculate how many packets are needed to catch up again to the current
|
||
|
* rate, and return that.
|
||
|
*
|
||
|
* NOTE: this is almost always going to have the value of 1 (one). Only at
|
||
|
* very high speeds (above 100,000 packets/second) will this value get
|
||
|
* larger.
|
||
|
*/
|
||
|
throttler->batch_size *= 1.005;
|
||
|
if (throttler->batch_size > 10000)
|
||
|
throttler->batch_size = 10000;
|
||
|
throttler->current_rate = current_rate;
|
||
|
|
||
|
throttler->test_timestamp = timestamp;
|
||
|
throttler->test_packet_count = packet_count;
|
||
|
return (uint64_t)throttler->batch_size;
|
||
|
}
|