// balb_ratelimiter.h                                                 -*-C++-*-
#ifndef INCLUDED_BALB_RATELIMITER
#define INCLUDED_BALB_RATELIMITER

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a mechanism to limit peak and sustained consumption rates.
//
//@CLASSES:
//  balb::RateLimiter: mechanism to monitor resource consumption rates
//
//@SEE ALSO balb_leakybucket
//
//@DESCRIPTION: This component provides a mechanism, 'balb::RateLimiter', that
// enables clients to monitor and control the use of a resource such that the
// peak consumption rate and the sustained consumption rate do not exceed their
// respective configured limits.
//
// The limits on resource consumption rates of a 'balb::RateLimiter' object are
// configured using a specified peak rate (measured in 'units/s') along with
// its time-window, and a specified sustained rate (measured in 'units/s')
// along with its time-window.  The peak-rate time-window indicates a sliding
// time period over which the average consumption rate shall not exceed the
// peak-rate; similarly, the sustained-rate time-window indicates a sliding
// time period over which the average consumption rate shall not exceed the
// sustained rate.  'unit' is a generic unit of measurement (e.g., bytes,
// megabytes, number of messages, packets, liters, clock cycles, etc.).
//
///Internal Model
///--------------
// Internally, a rate limiter (currently) models resource usage using two
// corresponding 'balb::LeakyBucket' objects, one for limiting peak resource
// usage and one for limiting sustained resource usage.  Each leaky bucket
// provides an approximation for a moving total, where the configured time
// window corresponds to the period of the moving total, and that time window
// multiplied by the corresponding rate indicates the sum that the moving total
// may not exceed (i.e., the capacity of the leaky bucket).  As the units are
// submitted to a rate limiter, they are added to both the peak and sustained
// rate moving-totals, and then removed over time at the corresponding
// configured rate.
//
// Figure 1 illustrates the behavior of a rate limiter during a typical usage
// scenario using moving-totals:
//..
// Fig. 1:
//
//  Rp (peak rate)                  = 2 units/s
//  Wp (peak-rate time-window)      = 2 s
//  Rs (sustained rate)             = 1 units/s
//  Ws (sustained-rate time-window) = 7 s
//
//       Submit 5                                       Submit 7
//
//      |   |     |   |        |   |      |   |       |   |     7|~~~|
//    12|   |    6|   |      12|   |     6|   |     12|   |     6|~~~|
//    11|   |    5|~~~|      11|   |     5|   |     11|   |     5|~~~|
//    10|   | Lp-4|~~~|      10|   |  Lp-4|---|     10|   |  Lp-4|~~~|
//     9|   |    3|~~~|       9|   |     3|   |      9|~~~|     3|~~~|
//     8|   |    2|~~~|       8|   |     2|   |      8|~~~|     2|~~~|
//  Ls-7|---|    1|~~~|    Ls-7|---|     1|~~~|   Ls-7|~~~|     1|~~~|
//     6|   |     +- -+       6|   |      +- -+      6|~~~|      +- -+
//     5|~~~|                 5|   |                 5|~~~|
//     4|~~~|                 4|   |                 4|~~~|
//     3|~~~|                 3|~~~|                 3|~~~|
//     2|~~~|                 2|~~~|                 2|~~~|
//     1|~~~|                 1|~~~|                 1|~~~|
//      +- -+                  +- -+                  +- -+
//
// Time:    t0                     t0 + 2s                  t0 + 2s
//
//
//                                                      Submit 2
//
//      |   |     7|   |       |   |     7|   |       |   |     7|   |
//    12|   |     6|   |     12|   |     6|   |     12|   |     6|   |
//    11|   |     5|   |     11|   |     5|   |     11|   |     5|   |
//    10|   |  Lp-4|---|     10|   |  Lp-4|---|     10|   |  Lp-4|---|
//     9|   |     3|~~~|      9|   |     3|   |      9|   |     3|~~~|
//     8|   |     2|~~~|      8|   |     2|   |      8|   |     2|~~~|
//  Ls-7|~~~|     1|~~~|   Ls-7|---|     1|~~~|   Ls-7|~~~|     1|~~~|
//     6|~~~|      +- -+      6|---|      +- -+      6|~~~|      +- -+
//     5|~~~|                 5|~~~|                 5|~~~|
//     4|~~~|                 4|~~~|                 4|~~~|
//     3|~~~|                 3|~~~|                 3|~~~|
//     2|~~~|                 2|~~~|                 2|~~~|
//     1|~~~|                 1|~~~|                 1|~~~|
//      +- -+                  +- -+                  +- -+
//
// Time:    t0 + 4s               t0 + 6s                t0 + 6s
//..
// Suppose we have a rate limiter with a peak rate of 'Rp = 2 units/s', a
// peak-rate time-window of 'Wp = 2 s', a sustained rate of 'Rs = 1 units/s',
// and a sustained-rate time-window of 'Ws = 7 s'.
//
// This rate limiter maintains a moving-total having a capacity
// 'Lp = Rp * Wp = 4 units' that controls the peak rate and another
// moving-total having a capacity 'Ls = Rs * Ws = 7 units' that controls the
// sustained rate.
//
// Figure 1 shows the following sequence of events:
//: o At time 't0s', we submit 5 units.  The submitted units are added to the
//:   both moving-totals, and as a result the 'Lp' is exceeded, which means
//:   that the average consumption rate over the peak-rate time-window has
//:   exceeded the peak rate.  Note that we can not submit any more units at
//:   this time even though 'Ls' is not exceeded (the average consumption rate
//:   over the sustained-rate time-windows has not exceeded the sustained
//:   rate).
//:
//: o At time 't0 + 2s' the number of units contained moving-totals are
//:   recalculated.  As a result, 4 units ('Rp * 2 s') are subtracted from the
//:   peak rate moving-total, and 2 units ('Rs * 2 s') are subtracted from the
//:   sustained rate moving-total.  Now, capacities of both moving-totals are
//:   no longer exceeded, so we are free to submit more units.  We submit 7
//:   units, causing both 'Lp' and 'Ls' to be exceeded.
//:
//: o At time 't0 + 4s', the moving-totals are again updated.  The 'Lp' limit
//:   is no longer exceeded.  The number of units held by the moving-total
//:   tracking sustained rate matches the moving-total's capacity, and this
//:   boundary condition imply and no units can be submitted, because
//:   submitting any amount of units would cause 'Ls' to be exceeded.
//:
//: o At time 't0 + 6s', the moving-totals are again updated.  Both 'Lp' and
//:   'Ls' are no longer exceeded.  We submit 2 units.  The 'Lp' limit is not
//:   exceeded, but 'Ls' limit is exceeded.
//
///Monitoring Resource Usage
///-------------------------
// A 'balb::LeakyBucket' provides methods to both submit units and reserve
// units for future submission.  Submitting a unit indicates that it has been
// consumed by the entity being modeled, and it is added to the moving-totals
// tracking both peak and sustained resource usage.
//
// Reserving a unit guarantees that available capacity will be reserved so that
// unit can be submitted in the future without exceeding the configured limits.
// Reserved units may be later submitted using the 'submitReserved' method or
// canceled using the 'cancelReserved' method.  Reserved units permanently
// reside in the two moving-totals of consumed units, resulting in the
// reduction in the effective capacities of the moving-totals, until the
// reserved units are canceled or submitted.  Reserving units effectively
// shortens the time-window during which the average sustained and peak rate
// are enforced.  Therefore, the time interval between reserving units and
// submitting or canceling them should be kept as short as possible.  For a
// practical example of using reserved units, please see
// 'balb_reservationguard'.
//
// The recommended usage of a rate limiter is to first check whether 1 unit can
// be added without exceeding the rate limiter's configured limits, and if so,
// consume the desired amount of the resource.  Afterwards, submit the amount
// of consumed resource to the rate limiter.
//
// Whether submitting more units would exceed the configured limits can be
// determined using the 'wouldExceedBandwidth' method.  The estimated amount of
// time to wait before 1 more unit will be allowed to be submitted can be
// determined using the 'calculateTimeToSubmit' method.
//
///Time Synchronization
///--------------------
// rate limiter does not utilize an internal timer, so timing must be handled
// manually.  Clients can specify an initial time interval for a rate limiter
// object at construction or using the 'reset' method.  Whenever the state of a
// rate limiter object needs to be updated, clients must invoke the
// 'updateState' method specifying the current time interval.  Since rate
// limiter cares only about the elapsed time (not absolute time), the specified
// time intervals may be relative to any arbitrary time origin, though all of
// them must refer to the same origin.  For the sake of consistency, clients
// are encouraged to use the unix epoch time (such as the values returned by
// 'bdlt::CurrentTime::now').
//
///Usage
///-----
// This section illustrates the intended use of this component.
//
///Example 1: Controlling Network Traffic Generation
///-------------------------------------------------
// Suppose that we want to send data over a network interface with the load
// spike limitations explained below:
//
//: o The long term average rate of resource usage (i.e., the sustained rate)
//:   should not exceed 1024 bytes/s ('Rs').
//:
//: o The period over which to monitor the long term average rate (i.e., the
//:   sustained-rate time-window) should be 0.5s ('Wp').
//:
//: o The peak resource usage (i.e., the peak rate) should not exceed 2048
//:   bytes/s ('Rp').
//:
//: o The period over which to monitor the peak resource usage should be
//:   0.0625s (Wp).
//
// This is shown in Figure 2 below.
//..
// Fig. 2:
//
//     ^ Rate (Units per second)
//     |                             _____         .
//     |                            /  B  \        .
// 2048|---------------------------/-------\--------Rp (Maximum peak rate)
//     |           __             /         \      .
//     |          /  \           /    A2     \     .
//     |         / A1 \         /             \    .
// 1024|--------/------\ ------/---------------\----Rs (Maximum sustained rate)
//     |   __  /        \     /                 \__.
//     |__/  \/          \___/                     .
//     |                                           .
//      --------------------------------------------->
//                                         T (seconds)
//..
// Notice that we can understand the limitations imposed by the rate-limiter
// graphically as the maximum area above the respective lines, 'Rp' and 'Rs',
// that the usage curve to allowed to achieve.  In the example above:
//
//  o The area above the sustained rate 'Rs' (e.g., 'A1' or 'A2+B') should
//    contain no more than 512 bytes (Rs * Ws).
//
//  o The area above the peak rate 'Rp' should contain no more than 128 bytes
//    (Rp * Wp).
//
// Further suppose that we have a function, 'sendData', that transmits a
// specified amount of data over that network:
//..
//  bool sendData(size_t dataSize);
//      // Send a specified 'dataSize' amount of data over the network.
//      // Return 'true' if data was sent successfully and 'false' otherwise.
//..
// First, we create a 'balb::RateLimiter' object having a sustained rate of
// 1024 bytes/s, a sustained-rate time-window of 0.5s
// (512 bytes / 1024 bytes/s), a peak-rate of 2048 bytes/s, and a peak-rate
// time-window of 0.0625s (128 bytes / 2048 bytes/s):
//..
//  bsls::Types::Uint64 sustainedRateLimit = 1024;
//  bsls::TimeInterval  sustainedRateWindow(0.5);
//  bsls::Types::Uint64 peakRateLimit = 2048;
//  bsls::TimeInterval  peakRateWindow(0.0625);
//  bsls::TimeInterval  now = bdlt::CurrentTime::now();
//
//  balb::RateLimiter   rateLimiter(sustainedRateLimit,
//                                  sustainedRateWindow,
//                                  peakRateLimit,
//                                  peakRateWindow,
//                                  now);
//..
// Note that the rate limiter does not prevent the rate at any instant from
// exceeding either the peak-rate or the sustained rate; instead, it prevents
// the average rate over the peak-rate time-window from exceeding maximum
// peak-rate and the average rate over the sustained-rate time-window from
// exceeding the maximum sustained-rate.
//
// Then, we define the size of data to be send, the size of each data chunk,
// and a counter of data actually sent:
//..
//  bsls::Types::Uint64 sizeOfData = 10 * 1024; // in bytes
//  bsls::Types::Uint64 chunkSize  = 64;        // in bytes
//  bsls::Types::Uint64 bytesSent  = 0;
//..
// Now, we send the chunks of data using a loop.  For each iteration, we check
// whether submitting another byte would exceed the rate limiter's bandwidth
// limits.  If not, we send an additional chunk of data and submit the number
// of bytes sent to the leaky bucket.  Note that 'submit' is invoked only after
// the data has been sent.
//..
//  while (bytesSent < sizeOfData) {
//      now = bdlt::CurrentTime::now();
//      if (!rateLimiter.wouldExceedBandwidth(now)) {
//          if (true == sendData(chunkSize)) {
//              rateLimiter.submit(chunkSize);
//              bytesSent += chunkSize;
//          }
//      }
//..
// Finally, if submitting another byte will cause the rate limiter to exceed
// its bandwidth limits, then we wait until the submission will be allowed by
// waiting for an amount time returned by the 'calculateTimeToSubmit' method:
//..
//      else {
//          bsls::TimeInterval  timeToSubmit =
//                                      rateLimiter.calculateTimeToSubmit(now);
//          bsls::Types::Uint64 uS = timeToSubmit.totalMicroseconds() +
//                                 (timeToSubmit.nanoseconds() % 1000 ? 1 : 0);
//          bslmt::ThreadUtil::microSleep(static_cast<int>(uS));
//      }
//  }
//..
// Notice that we wait by putting the thread into a sleep state instead of
// using busy-waiting to better optimize for multi-threaded applications.

#include <balscm_version.h>

#include <balb_leakybucket.h>

#include <bsls_assert.h>
#include <bsls_timeinterval.h>
#include <bsls_types.h>

#include <bsl_algorithm.h>
#include <bsl_climits.h>

#include <bsl_c_limits.h>

namespace BloombergLP {
namespace balb {

                           //==================
                           // class RateLimiter
                           //==================

class RateLimiter {
    // This mechanism implements a rate limiter that allows clients to monitor
    // and control the usage of a resource such that the rate of consumption
    // stays within configured limits.  The behavior of a rate limiter is
    // determined by four properties: the sustained rate (in units/s), the
    // sustained-rate time-window (in seconds), the peak rate (in units/s), and
    // the peak-rate time-window (in seconds).  All of these properties can be
    // specified at construction or using the 'setRateLimits' method.
    //
    // Units can be indicated to a rate limiter as consumed by either
    // submitting them using the 'submit' method.  Units can be marked as
    // reserved, which effectively shorten the sustained-rate time-window and
    // the peak-rate time-window, by using the 'reserve' method.
    //
    // Whether submitting 1 more unit would exceed the configured limits can be
    // determined using the 'wouldExceedBandwidth' method.  The estimated
    // amount of time to wait before 1 more unit will be allowed to be
    // submitted can be determined using the 'calculateTimeToSubmit' method.
    //
    // The state of a rate limiter must be updated manually using the
    // 'updateState' method supplying the current time interval.  The time
    // intervals supplied should all refer to the same time origin.
    //
    // A rate limiter keeps some statistics, including the number of submitted
    // units, that can be accessed using the 'getStatistics' and reset using
    // the 'resetStatistics' method.
    //
    // This class:
    //: o is *exception* *neutral* (agnostic)
    //: o is *const* *thread-safe*
    // For terminology see 'bsldoc_glossary'.

    // DATA
    LeakyBucket d_peakRateBucket;      // 'balb::LeakyBucket' object for
                                       // handling peak load

    LeakyBucket d_sustainedRateBucket; // 'balb::LeakyBucket' object for
                                       // handling sustained load

  private:
    // NOT IMPLEMENTED
    RateLimiter& operator=(const RateLimiter&);
    RateLimiter(const RateLimiter&);

  public:
    // CREATORS
    RateLimiter(bsls::Types::Uint64       sustainedRateLimit,
                const bsls::TimeInterval& sustainedRateWindow,
                bsls::Types::Uint64       peakRateLimit,
                const bsls::TimeInterval& peakRateWindow,
                const bsls::TimeInterval& currentTime);
        // Create a RateLimiter object, having the specified
        // 'sustainedRateLimit', the specified 'sustainedRateWindow', the
        // specified 'peakRateLimit', the specified 'peakRateWindow', and using
        // the specified 'currentTime' as the initial 'lastUpdateTime'.  The
        // behavior is undefined unless '0 < sustainedRateLimit',
        // '0 < sustainedRateWindow', '0 < peakRateLimit',
        // '0 < peakRateWindow', the product of 'sustainedRateLimit' and
        // 'sustainedRateWindow' can be represented by 64-bit unsigned integral
        // type, and the product of 'peakRateLimit' and 'peakRateWindow' can be
        // represented by 64-bit unsigned integral type.

    ~RateLimiter();
        // Destroy this object.

    // CLASS METHODS
    static bool
    supportsRateLimitsExactly(bsls::Types::Uint64       sustainedRateLimit,
                              const bsls::TimeInterval& sustainedRateWindow,
                              bsls::Types::Uint64       peakRateLimit,
                              const bsls::TimeInterval& peakRateWindow);
        // Returns 'true' if, supposing the specified 'sustainedRateLimit',
        // 'sustainedRateWindow', 'peakRateLimit', and 'peakRateWindow' are
        // used to initialize a 'RateLimiter' object, the corresponding query
        // methods return the same values.  The implementation of 'RateLimiter'
        // uses 'balb::LeakyBucket' objects, and for some combinations of
        // values the capacity of the 'balb::LeakyBucket' is rounded such that
        // the rederived values differ.  Note that this method is most likely
        // to return 'true' when the product of each corresponding pair of
        // limit and window (as a fraction of a second) is integral.

    // MANIPULATORS
    bsls::TimeInterval calculateTimeToSubmit(
                                        const bsls::TimeInterval& currentTime);
        // Update the state of this rate limiter to the specified
        // 'currentTime'.  Return the estimated time interval that should pass
        // from 'currentTime' until 1 more unit can be submitted to this rate
        // limiter without exceeding its configured limits.  The number of
        // nanoseconds in the returned time interval is rounded up.  Note that
        // a time interval of 0 is returned if 1 or more units can be submitted
        // at 'currentTime'.  Also note that after waiting for the returned
        // time interval, clients should typically check again using this
        // method, because additional units may have been submitted in the
        // interim.

    void cancelReserved(bsls::Types::Uint64 numUnits);
        // Cancel the specified 'numUnits' that were previously reserved.  The
        // behavior is undefined unless 'numUnits <= unitsReserved()'.

    void reserve(bsls::Types::Uint64 numUnits);
        // Reserve the specified 'numUnits' for future use by this rate
        // limiter.  The behavior is undefined unless the sum of 'numUnits',
        // unused units previously submitted to this rate limiter, and
        // 'unitsReserved' can be represented by a 64-bit unsigned integral
        // type.

    void reset(const bsls::TimeInterval& currentTime);
        // Reset the statistics counter for this rate limiter to 0, and set the
        // 'lastUpdateTime' of this rate limiter to the specified
        // 'currentTime'.

    void resetStatistics();
        // Reset the statics collected for this rate limiter by setting the
        // number of units used and the number of units submitted to 0, and set
        // the 'statisticsCollectionStartTime' to the 'lastUpdateTime' of this
        // leaky bucket.

    void setRateLimits(bsls::Types::Uint64       sustainedRateLimit,
                       const bsls::TimeInterval& sustainedRateWindow,
                       bsls::Types::Uint64       peakRateLimit,
                       const bsls::TimeInterval& peakRateWindow);
        // Set the sustained rate of this rate limiter to the specified
        // 'sustainedRateLimit', the sustained-rate time-window to the
        // specified 'sustainedRateWindow', the peak rate to the specified
        // 'peakRateLimit' and the peak-rate time-window to the specified
        // 'peakRateWindow'.  The behavior is undefined unless
        // '0 < sustainedRateLimit', '0 < sustainedRateWindow',
        // '0 < peakRateLimit', '0 < peakRateWindow', the product of
        // 'sustainedRateLimit' and 'sustainedRateWindow' can be represented by
        // 64-bit unsigned integral type, and the product of 'peakRateLimit'
        // and 'peakRateWindow' can be represented by 64-bit unsigned integral
        // type.

    void submit(bsls::Types::Uint64 numUnits);
        // Submit the specified 'numUnits' to this rate limiter.  The behavior
        // is undefined unless the sum of 'numUnits', unused units previously
        // submitted to this rate limiter, and 'unitsReserved' can be
        // represented by a 64-bit unsigned integral type.

    void submitReserved(bsls::Types::Uint64 numUnits);
        // Submit the specified 'numUnits' that were previously reserved.  The
        // behavior is undefined unless 'numUnits <= unitsReserved()'.

    void updateState(const bsls::TimeInterval& currentTime);
        // Set the 'lastUpdateTime' of this rate limiter to the specified
        // 'currentTime'.  If the 'currentTime' is after 'lastUpdateTime', then
        // recalculate number of units available for consumption based on the
        // 'peakRate', 'sustainedRate' and the time interval between
        // 'lastUpdateTime' and 'currentTime'.  If 'currentTime' is before
        // 'statisticsCollectionStartTime', set it' to 'currentTime'.

    bool wouldExceedBandwidth(const bsls::TimeInterval& currentTime);
        // Update the state of this rate limiter to the specified
        // 'currentTime'.  Return 'true' if submitting 1 unit at the
        // 'currentTime' would exceed the configured limits, and false
        // otherwise.

    // ACCESSORS
    void getStatistics(bsls::Types::Uint64* submittedUnits,
                       bsls::Types::Uint64* unusedUnits) const;
        // Load, into the specified 'submittedUnits' and the specified
        // 'unusedUnits' respectively, the numbers of submitted units and the
        // number of unused units for this rate limiter from the
        // 'statisticsCollectionStartTime' to the 'lastUpdateTime.  The number
        // of unused units is the difference between the number of units that
        // could have been consumed at the sustained rate and the number of
        // units actually submitted for the time period.

    bsls::TimeInterval lastUpdateTime() const;
        // Return the time when this rate limiter was last updated.

    bsls::Types::Uint64 peakRateLimit() const;
        // Return the peak rate of this rate limiter.

    bsls::TimeInterval peakRateWindow() const;
        // Return the peak-rate time-period of this rate limiter.  Note that
        // this period is generally significantly shorter than
        // 'sustainedRateWindow'.

    bsls::TimeInterval statisticsCollectionStartTime() const;
        // Return the time interval when the collection of the statistics (as
        // returned by 'getStatistics') started.

    bsls::Types::Uint64 sustainedRateLimit() const;
        // Return the sustained rate of this rate limiter.

    bsls::TimeInterval sustainedRateWindow() const;
        // Return the sustained-rate time-period of this rate limiter.  Note
        // that this period is generally significantly longer than the
        // 'peakRateWindow'.

    bsls::Types::Uint64 unitsReserved() const;
        // Return the number of reserved units for this rate limiter.
};

// ============================================================================
//                      INLINE FUNCTION DEFINITIONS
// ============================================================================

                            //------------------
                            // class RateLimiter
                            //------------------

// MANIPULATORS
inline
void RateLimiter::cancelReserved(bsls::Types::Uint64 numUnits)
{
    BSLS_ASSERT(numUnits <= unitsReserved());

    d_peakRateBucket.cancelReserved(numUnits);
    d_sustainedRateBucket.cancelReserved(numUnits);
}

inline
void RateLimiter::reserve(bsls::Types::Uint64 numUnits)
{
    BSLS_ASSERT_SAFE(numUnits <= ULLONG_MAX - unitsReserved());

    BSLS_ASSERT_SAFE(d_sustainedRateBucket.unitsInBucket() <=
                                       ULLONG_MAX - unitsReserved()- numUnits);

    BSLS_ASSERT_SAFE(d_peakRateBucket.unitsInBucket() <=
                                       ULLONG_MAX - unitsReserved()- numUnits);

    d_peakRateBucket.reserve(numUnits);
    d_sustainedRateBucket.reserve(numUnits);
}

inline
void RateLimiter::reset(const bsls::TimeInterval& currentTime)
{
    d_peakRateBucket.reset(currentTime);
    d_sustainedRateBucket.reset(currentTime);
}

inline
void RateLimiter::resetStatistics()
{
    d_sustainedRateBucket.resetStatistics();
}

inline
void RateLimiter::submit(bsls::Types::Uint64 numUnits)
{
    BSLS_ASSERT_SAFE(numUnits <=
                           ULLONG_MAX - d_sustainedRateBucket.unitsInBucket());

    BSLS_ASSERT_SAFE(unitsReserved() <=
                 ULLONG_MAX - d_sustainedRateBucket.unitsInBucket()- numUnits);

    BSLS_ASSERT_SAFE(numUnits <=
                                ULLONG_MAX - d_peakRateBucket.unitsInBucket());

    BSLS_ASSERT_SAFE(unitsReserved() <=
                      ULLONG_MAX - d_peakRateBucket.unitsInBucket()- numUnits);

    d_peakRateBucket.submit(numUnits);
    d_sustainedRateBucket.submit(numUnits);
}

inline
void RateLimiter::submitReserved(bsls::Types::Uint64 numUnits)
{
    BSLS_ASSERT(numUnits <= unitsReserved());

    // There is no need to check whether 'numUnits' causes overflow because the
    // reserved units was already checked by the 'reserve' method.

    d_peakRateBucket.submitReserved(numUnits);
    d_sustainedRateBucket.submitReserved(numUnits);
}

inline
void RateLimiter::updateState(const bsls::TimeInterval& currentTime)
{
    d_peakRateBucket.updateState(currentTime);
    d_sustainedRateBucket.updateState(currentTime);
}

inline
bool RateLimiter::wouldExceedBandwidth(const bsls::TimeInterval& currentTime)
{
    return (d_peakRateBucket.wouldOverflow(currentTime) ||
                             d_sustainedRateBucket.wouldOverflow(currentTime));
}

// ACCESSORS
inline
void RateLimiter::getStatistics(bsls::Types::Uint64* submittedUnits,
                                bsls::Types::Uint64* unusedUnits) const
{
    BSLS_ASSERT_SAFE(0 != submittedUnits);
    BSLS_ASSERT_SAFE(0 != unusedUnits);

    // The statistics is collected from the sustained rate leaky bucket.

    d_sustainedRateBucket.getStatistics(submittedUnits, unusedUnits);
}

inline
bsls::TimeInterval RateLimiter::lastUpdateTime() const
{
    return bsl::max(d_sustainedRateBucket.lastUpdateTime(),
                    d_peakRateBucket.lastUpdateTime());
}

inline
bsls::Types::Uint64 RateLimiter::peakRateLimit() const
{
    return d_peakRateBucket.drainRate();
}

inline
bsls::TimeInterval RateLimiter::peakRateWindow() const
{
    return LeakyBucket::calculateTimeWindow(d_peakRateBucket.drainRate(),
                                            d_peakRateBucket.capacity());
}

inline
bsls::TimeInterval RateLimiter::statisticsCollectionStartTime() const
{
    return d_sustainedRateBucket.statisticsCollectionStartTime();
}

inline
bsls::Types::Uint64 RateLimiter::sustainedRateLimit() const
{
    return d_sustainedRateBucket.drainRate();
}

inline
bsls::TimeInterval RateLimiter::sustainedRateWindow() const
{
    return LeakyBucket::calculateTimeWindow(d_sustainedRateBucket.drainRate(),
                                            d_sustainedRateBucket.capacity());
}

inline
bsls::Types::Uint64 RateLimiter::unitsReserved() const
{
    BSLS_ASSERT_SAFE(d_sustainedRateBucket.unitsReserved() ==
                                             d_peakRateBucket.unitsReserved());

    return d_sustainedRateBucket.unitsReserved();
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2021 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------