Skip to content

Commit

Permalink
Add support for RDTSC, apply to token bucket
Browse files Browse the repository at this point in the history
The new module lib.tsc provides a generic Time Stamp Counter mechanism
for measuring time intervals based on different time sources.  One
such source makes use of the CPU's TSC register via the rdtsc
instruction for low-latency timing purposes.

The rdtsc method is made the default time source for the token bucket,
formerly provided by core.lib, now moved to a separate module
lib.token_bucket. The commit also includes documentation for the token
bucket, which was previously missing.
  • Loading branch information
alexandergall committed Jun 14, 2018
1 parent b9da7ca commit fb29cd0
Show file tree
Hide file tree
Showing 5 changed files with 434 additions and 78 deletions.
80 changes: 2 additions & 78 deletions src/core/lib.lua
Original file line number Diff line number Diff line change
Expand Up @@ -453,84 +453,8 @@ function root_check (message)
end
end

-- Simple token bucket for rate-limiting of events. A token bucket is
-- created through
--
-- local tb = token_bucket_new({ rate = <rate> })
--
-- where <rate> is the maximum allowed rate in Hz, which defaults to
-- 10. Conceptually, <rate> tokens are added to the bucket each
-- second and the bucket can hold no more than <rate> tokens but at
-- least one.
--

local token_bucket = {}
token_bucket.mt = { __index = token_bucket }
token_bucket.default = { rate = 10 }
function token_bucket_new (config)
local config = config or token_bucket.default
local tb = setmetatable({}, token_bucket.mt)
tb:rate(config.rate or token_bucket.default.rate)
tb._tstamp = C.get_monotonic_time()
return tb
end

-- The rate can be set with the rate() method at any time, which fills
-- the token bucket an also returns the previous value. If called
-- with a nil argument, returns the currently configured rate.
function token_bucket:rate (rate)
if rate ~= nil then
local old_rate = self._rate
self._rate = rate
self._max_tokens = math.max(rate, 1)
self._tokens = self._max_tokens
return old_rate
end
return self._rate
end

function token_bucket:_update (tokens)
local now = C.get_monotonic_time()
local tokens = math.min(self._max_tokens, tokens + self._rate*(now-self._tstamp))
self._tstamp = now
return tokens
end

-- The take() method tries to remove <n> tokens from the bucket. If
-- enough tokens are available, they are subtracted from the bucket
-- and a true value is returned. Otherwise, the bucket remains
-- unchanged and a false value is returned. For efficiency, the
-- tokens accumulated since the last call to take() or can_take() are
-- only added if the request can not be fulfilled by the state of the
-- bucket when the method is called.
function token_bucket:take (n)
local n = n or 1
local result = false
local tokens = self._tokens
if n > tokens then
tokens = self:_update(tokens)
end
if n <= tokens then
tokens = tokens - n
result = true
end
self._tokens = tokens
return result
end

-- The can_take() method returns a true value if the bucket contains
-- at least <n> tokens, false otherwise. The bucket is updated in a
-- layz fashion as described for the take() method.
function token_bucket:can_take (n)
local n = n or 1
local tokens = self._tokens
if n <= tokens then
return true
end
tokens = self:_update(tokens)
self._tokens = tokens
return n <= tokens
end
-- Backward compatibility
token_bucket_new = require("lib.token_bucket").new

-- Simple rate-limited logging facility. Usage:
--
Expand Down
70 changes: 70 additions & 0 deletions src/lib/README.token_bucket.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
### Token Bucket (lib.token_bucket)

This module implements a [token
bucket](https://en.wikipedia.org/wiki/Token_bucket) for rate-limiting
of arbitrary events. The bucket is filled with tokens at a constant
rate up to a given maximum called the *burst_size*. Tokens are added
and removed in integer quantities. An event can only take place if at
least one token is available. A burst of back-to-back events is
allowed to happen by consuming all available tokens at a given point
in time. The maximum size of such a burst is determined by the
capacity of the bucket, hence the name *burst_size*.

The token bucket is updated in a lazy fashion, i.e. only when a
request for tokens cannot be satisfied immediately.

By default, a token bucket uses the `rdtsc` time source via the
[`tsc`](./README.tsc.md) module to minimise overhead. To override,
the `default_source` parameter of the `tsc` module must be set
to the desired value.

#### Functions

— Function **new** *config*

Creates an instance of a token bucket. The required *config* argument
must be a table with the following keys.

— Key **rate**

*Required*. The rate in units of Hz at which tokens are placed in the
bucket as an arbitrary floating point number larger than zero.

— Key **burst_size**

*Optional*. The maximum number of tokens that can be stored in the
bucket. The default is **rate** tokens, i.e. the amount of tokens
accumulated over one second rounded up to the next integer.

#### Methods

The object returned by the **new** function provides the following
methods.

— Method **token_bucket:set** [*rate*], [*burst_size*]

Set the rate and burst size to the values *rate* and *burst_size*,
respectively, and fill the bucket to capacity. If *rate* is `nil`,
the rate remains unchanged. If *burst_size* is `nil`, the burst size
is set to the number of tokens that will be accumulated over one
second with the new rate (like in the **new** function).

— Method **token_bucket:get**

Returns the current rate and burst size.

— Method **token_bucket:can_take** [*n*]

Returns `true` if at least *n* tokens are available, `false`
otherwise. If *n* is `nil`, the bucket is checked for a single token.

— Method **token_bucket:take** [*n*]

If at least *n* tokens are available, they are removed from the bucket
and the method returns `true`. Otherwise, the bucket remains
unchanged and `false` is returned. If *n* is `nil`, the bucket is
checked for a single token.

— Method **token_bucket:take_burst**

Takes all available tokens from the bucket and returns that number.
112 changes: 112 additions & 0 deletions src/lib/README.tsc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
### Time Stamp Counter (lib.tsc)

A Time Stamp Counter (TSC) is an unsigned 64-bit value which increases
at a fixed frequency. The latter property provides a measure of the
time that has passed between two readings of the counter in units of
clock ticks.

To convert a value in clock ticks to the corresponding number of
seconds requires a measurement of the actual frequency at which the
TSC runs. This is referred to as calibration. The `tsc` module
provides a uniform interface to TSCs based on different time sources.

Example usage
```lua
local tsc = require('lib.tsc')
local sleep = require("ffi").C.sleep
local function wait(t)
print("source " .. t:source())
local s1 = t:stamp()
sleep(1)
local s2 = t:stamp()
print(("clock ticks %s, nanoseconds %s"):format(tostring(s2 - s1),
tostring(t:to_ns(s2 - s1))))
end

wait(tsc.new())
-- Override default
tsc.default_source = 'system'
wait(tsc.new())
```

#### Parameters

Parameters can be set by

```lua
local tsc = require('lib.tsc')
tsc.<parameter> = <value>
```

— Parameter **default_source** *source*

The time source used by a new TSC instance if no **source** key is
specified. The default is `rdtsc`.

#### Functions

— Function **new** *config*

Create a new TSC instance. The optional *config* argument is a table
with the following keys.

— Key **source**

*Optional*. The name of the timing source to be used with this
instance. The following sources are available. The default is `rdtsc`
(or whatever has been set by the `default_source` parameter).

* `system`

This source uses `clock_gettime(2)` with `CLOCK_MONOTONIC` provided
by `ffi.C.get_time_ns()`. The frequency is exactly 1e9 Hz,
i.e. one tick per nanosecond.

* `rdtsc`

This source uses the [TSC](https://en.wikipedia.org/wiki/Time_Stamp_Counter) CPU
register via the `rdtsc` instruction, provided that the platform
supports the `constant_tsc` and `nonstop_tsc` features. If these
features are not present, a warning is printed and the TSC falls
back to the `system` time source.

The TSC register is consistent for all cores of a CPU. However,
the calling program is responsible for setting the CPU affinity on
multi-socket systems.

The `system` time source is used for calibration.

— Function **rdtsc**

Returns the current value of the CPU's TSC register through the
`rdtsc` instruction as a `uint64_t` object.

#### Methods

The object returned by the **new** function provides the following
methods.

— Method **tsc:source**

Returns the name of the time source, i.e. `rdtsc` or `system`.

— Method **tsc:time_fn**

Returns the function used to generate a time stamp for the configured
time source, which returns the value of the TSC as a `uint64_t`
object.

— Method **tsc:stamp**

Returns the current value of the TSC as a `uint64_t`. It is
equivalent to the call `tsc:time_fn()()`.

— Method **tsc:tps**

Returns the number of clock ticks per second as a `uint64_t`.

— Method **tsc:to_ns**, *ticks*

Returns *ticks* converted from clock ticks to nanoseconds as a
`uint64_t`. This method should be avoided in low-latency code paths
due to conversions from/to Lua numbers.
Loading

0 comments on commit fb29cd0

Please sign in to comment.