summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/util/Throttle.java
blob: 4cd8961311e7258a0dd68451c862a50de4b94e61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
 * Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates.
 * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 *
 * 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.
 */

package com.amazon.carbonado.util;

/**
 * General purpose class for throttling work relative to its actual measured
 * performance. To throttle a task, call the throttle method each time a unit
 * of work has been performed. It computes a rolling average for the amount of
 * time it takes to perform some work, and then it sleeps a calculated amount
 * of time to throttle back.
 *
 * <p>Instances are intended for use by one thread, and so they are not
 * thread-safe.
 *
 * @author Brian S O'Neill
 */
public class Throttle {
    private final double[] mWorkTimeSamples;

    // Next index in mSamples to use.
    private int mSampleIndex;

    private double mWorkTimeSum;

    // Amount of samples gathered.
    private int mSampleCount;

    private long mLastTimestampNanos;

    private double mSleepRequiredNanos;

    /**
     * @param windowSize amount of samples to keep in the rolling average
     */
    public Throttle(int windowSize) {
        if (windowSize < 1) {
            throw new IllegalArgumentException();
        }

        mWorkTimeSamples = new double[windowSize];
    }

    /**
     * @param desiredSpeed 1.0 = perform work at full speed,
     * 0.5 = perform work at half speed, 0.0 = fully suspend work
     * @param sleepPrecisionMillis sleep precision, in milliseconds. Typical
     * value is 10 to 100 milliseconds.
     */
    public void throttle(double desiredSpeed, long sleepPrecisionMillis)
        throws InterruptedException
    {
        long timestampNanos = System.nanoTime();

        int sampleCount = mSampleCount;

        int index = mSampleIndex;
        double workTime = timestampNanos - mLastTimestampNanos;
        double workTimeSum = mWorkTimeSum + workTime;

        double[] workTimeSamples = mWorkTimeSamples;

        if (sampleCount >= workTimeSamples.length) {
            workTimeSum -= workTimeSamples[index];
            double average = workTimeSum / sampleCount;

            double sleepTimeNanos = (average / desiredSpeed) - average;
            double sleepRequiredNanos = mSleepRequiredNanos + sleepTimeNanos;

            if (sleepRequiredNanos > 0.0) {
                double sleepRequiredMillis = sleepRequiredNanos * (1.0 / 1000000);

                long millis;
                if (sleepRequiredMillis > Long.MAX_VALUE) {
                    millis = Long.MAX_VALUE;
                } else {
                    millis = Math.max(sleepPrecisionMillis, (long) sleepRequiredMillis);
                }

                Thread.sleep(millis);

                long nextNanos = System.nanoTime();

                // Subtract off time spent in this method, including sleep.
                sleepRequiredNanos -= (nextNanos - timestampNanos);
                timestampNanos = nextNanos;
            }

            mSleepRequiredNanos = sleepRequiredNanos;
        }

        workTimeSamples[index] = workTime;
        index++;
        if (index >= workTimeSamples.length) {
            index = 0;
        }
        mSampleIndex = index;

        mWorkTimeSum = workTimeSum;

        if (sampleCount < workTimeSamples.length) {
            mSampleCount = sampleCount + 1;
        }

        mLastTimestampNanos = timestampNanos;
    }

    /**
     * Test program which exercises the CPU in an infinite loop, throttled by
     * the amount given in args[0]. On a machine performing no other work, the
     * average CPU load should be about the same as the throttled speed.
     *
     * @param args args[0] - desired speed, 0.0 to 1.0
     */
    public static void main(String[] args) throws Exception {
        Throttle t = new Throttle(50);
        double desiredSpeed = Double.parseDouble(args[0]);
        while (true) {
            new java.util.Date().toString();
            t.throttle(desiredSpeed, 100);
        }
    }
}