All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.voltdb.client.exampleutils.RateLimiter Maven / Gradle / Ivy

There is a newer version: 10.1.1
Show newest version
/* This file is part of VoltDB.
 * Copyright (C) 2008-2020 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see .
 */
package org.voltdb.client.exampleutils;

import java.util.ArrayList;

/**
 * A simple rate limiter that can be used to bind an asynchronous application/benchmark to a maximum transactional rate, preventing firehosing or providing a fixed expected throughput for performance validation (latency review at specific rates).
 *
 * @author Seb Coursol
 * @since 2.0
 */
public class RateLimiter implements IRateLimiter
{
    private long SleepTime;
    private int StepCount;
    private Long[] CycleStepDuration;
    private Long[] CycleStepMaxCount;
    private Long[] CycleStepCount;
    private Long[] CycleStepEndTime;
    private long MaxProcessPerSecond = 0l;
    private long CycleAdjustment = Long.MIN_VALUE;
    private long LastCycleCount = Long.MAX_VALUE;

    /**
     * Creates a new rate limiter.
     *
     * @param maxProcessPerSecond the maximum number of requests the limiter should allow per second.
     */
    public RateLimiter(long maxProcessPerSecond)
    {
        this.set(maxProcessPerSecond, 0l, false);
    }

    /**
     * {@inheritDoc}
     */
    public void throttle()
    {
        this.throttle(this.MaxProcessPerSecond);
    }

    /**
     * Throttle the execution process and re-adjust the rate requirement on the fly.  This call allows the calling application to dynamically adjust the rate over time, for instance as a result of some external changes or analysis.  For an example of such an application, see {@link LatencyLimiter}.
     *
     * @param maxProcessPerSecond the maximum number of requests the limiter should allow per second.
     */
    public void throttle(long maxProcessPerSecond)
    {
        long adjustment = 0l;
        boolean forceSet = false;
        if (this.CycleAdjustment <= System.currentTimeMillis())
        {
            if ((this.MaxProcessPerSecond == maxProcessPerSecond) && (this.LastCycleCount != Long.MAX_VALUE))
            {
                    if (this.MaxProcessPerSecond > this.LastCycleCount)
                    {
                        adjustment = this.MaxProcessPerSecond - this.LastCycleCount;
                    }
                    else
                        forceSet = true;
            }
            this.CycleAdjustment = System.currentTimeMillis() + 1000l;
            this.LastCycleCount = 0l;
        }
        this.set(maxProcessPerSecond, adjustment, forceSet);
        try
        {
                // For rates below 1/ms a pre-empting sleep is the best way to approach the desired rate without
                // impacting latency (see note in loop below)
                if (this.SleepTime > 0)
                    Thread.sleep(this.SleepTime);

                for(int i=0;i= this.CycleStepMaxCount[i])
                    {
                        // If the Cycle duration is more than 1 ms, we might have to wait for a considerable time.
                        // - sleeping is more CPU-efficient than spinning, so we'll sleep just a bit less so we're
                        // considerate without blowing our window.
                        // Because of Java's threading model, and the fact the callbacks are executed in the calling
                        // thread, low execution rates will experience artificial latency (to the order of a few
                        // milliseconds).
                        long sleepDuration = this.CycleStepEndTime[i]-System.currentTimeMillis()-1;
                        if (sleepDuration > 0)
                            Thread.sleep(sleepDuration);

                        // 1ms or less to wait - Spin until the time changes
                        while (System.currentTimeMillis() < this.CycleStepEndTime[i]);

                        // Reset counters
                        this.CycleStepEndTime[i] = System.currentTimeMillis() + this.CycleStepDuration[i];
                        this.CycleStepCount[i] = 0l;
                    }
                }

                this.LastCycleCount++;
        }
        catch(Exception tie) {}
    }

    /**
     * Adjusts the rate limit, whether through automatic adjustment based on monitoring of executed versus requested rates, or as a consequence of an external request to modify the expected rate.
     *
     * @param maxProcessPerSecond the requested maximum number of requests per second.
     * @param adjustment the adjustment calculated internally by the limiter by comparing actual executions versus the requested rate.
     * @param forceSet the flag indicating this call is a conseuqnce to an external rate-change request and the distribution of executions over time should be recalculated.
     */
    private void set(long maxProcessPerSecond, long adjustment, boolean forceSet)
    {
        if ((this.MaxProcessPerSecond != maxProcessPerSecond) || (adjustment != 0l) || forceSet)
        {
            this.MaxProcessPerSecond = maxProcessPerSecond;
            maxProcessPerSecond += adjustment;

            // Ruth's tests fail with a divide by zero (ENG-2426).
            // So the quick fix is to set maxProcessPerSecond to 1000 if it is rate limited to zero by the latency limiter.
            // Basically set a floor.
            if (0 == maxProcessPerSecond)
                maxProcessPerSecond=1000;

            // For rates below 1/ms we can sleep a while between each execution, which is the most efficient approach.
            this.SleepTime = (1000l/maxProcessPerSecond)-1l;

            // Calculate the largest cycle duration based on requested rate
            long gcd = MathEx.gcd(maxProcessPerSecond,1000l);

            // Calculate incremental sub-cycles that will be used to approach the desired rate (1s, 100ms, 10ms, 1ms (or a
            // similar variation if the largest cycle duration is less than 1s))
            double cycleDuration = (1000l/gcd);
            double cycleMaxCount = (maxProcessPerSecond/gcd);
            ArrayList cycleStepDuration = new ArrayList();
            ArrayList cycleStepMaxCount = new ArrayList();
            ArrayList cycleStepCount    = new ArrayList();
            while((cycleDuration >= 1d) && (cycleMaxCount > 0d))
            {
                cycleStepDuration.add((long)cycleDuration);
                cycleStepMaxCount.add((long)cycleMaxCount);
                cycleStepCount.add(0l);
                cycleDuration = cycleDuration/10d;
                cycleMaxCount = Math.ceil(cycleMaxCount/10d);
            }

            // Convert ArrayLists to arrays (easier/faster to work with)
            this.StepCount = cycleStepCount.size();
            this.CycleStepDuration = cycleStepDuration.toArray(new Long[this.StepCount]);
            this.CycleStepMaxCount = cycleStepMaxCount.toArray(new Long[this.StepCount]);
            this.CycleStepCount = cycleStepCount.toArray(new Long[this.StepCount]);
            this.CycleStepEndTime = cycleStepCount.toArray(new Long[this.StepCount]);

            // Initialize cycle end times
            for(int i=0;i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy