
io.datakernel.eventloop.ThrottlingController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of datakernel-eventloop Show documentation
Show all versions of datakernel-eventloop Show documentation
Efficient non-blocking network and file I/O, for building Node.js-like client/server applications with
high performance requirements. It is similar to Event Loop in Node.js.
Although Eventloop runs in a single thread, multiple event loops can be
launched at the same time allowing for efficient CPU usage.
The newest version!
/*
* Copyright (C) 2015 SoftIndex LLC.
*
* 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 io.datakernel.eventloop;
import io.datakernel.common.Stopwatch;
import io.datakernel.common.inspector.AbstractInspector;
import io.datakernel.eventloop.jmx.EventloopJmxMBean;
import io.datakernel.jmx.api.JmxAttribute;
import io.datakernel.jmx.api.JmxOperation;
import io.datakernel.jmx.api.JmxReducers.JmxReducerSum;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import static io.datakernel.common.Preconditions.checkArgument;
import static java.lang.Math.pow;
public final class ThrottlingController extends AbstractInspector implements EventloopJmxMBean, EventloopInspector {
private static int staticInstanceCounter = 0;
private final Logger logger = LoggerFactory.getLogger(ThrottlingController.class.getName() + "." + staticInstanceCounter++);
public static final Duration TARGET_TIME = Duration.ofMillis(20);
public static final Duration GC_TIME = Duration.ofMillis(20);
public static final Duration SMOOTHING_WINDOW = Duration.ofSeconds(10);
public static final double THROTTLING_DECREASE = 0.1;
public static final double INITIAL_KEYS_PER_SECOND = 100;
public static final double INITIAL_THROTTLING = 0.0;
private Eventloop eventloop;
private int lastSelectedKeys;
private int concurrentTasksSize;
// region settings
private int targetTimeMillis;
private int gcTimeMillis;
private double throttlingDecrease;
private int smoothingWindow;
// endregion
// region intermediate counters for current round
private int bufferedRequests;
private int bufferedRequestsThrottled;
// endregion
// region exponentially smoothed values
private double smoothedThrottling;
private double smoothedTimePerKeyMillis;
// endregion
// region JMX
private long infoTotalRequests;
private long infoTotalRequestsThrottled;
private long infoTotalTimeMillis;
private long infoRounds;
private long infoRoundsZeroThrottling;
private long infoRoundsExceededTargetTime;
private long infoRoundsGc;
// endregion
private float throttling;
// region creators
private ThrottlingController() {
}
@NotNull
public static ThrottlingController create(@NotNull Eventloop eventloop) {
return create().withEventloop(eventloop);
}
@NotNull
public static ThrottlingController create() {
return new ThrottlingController()
.withTargetTime(TARGET_TIME)
.withGcTime(GC_TIME)
.withSmoothingWindow(SMOOTHING_WINDOW)
.withThrottlingDecrease(THROTTLING_DECREASE)
.withInitialKeysPerSecond(INITIAL_KEYS_PER_SECOND)
.withInitialThrottling(INITIAL_THROTTLING);
}
@NotNull
private ThrottlingController withEventloop(@NotNull Eventloop eventloop) {
setEventloop(eventloop);
return this;
}
public void setEventloop(@NotNull Eventloop eventloop) {
this.eventloop = eventloop;
}
@NotNull
public ThrottlingController withTargetTime(@NotNull Duration targetTime) {
setTargetTime(targetTime);
return this;
}
@NotNull
public ThrottlingController withGcTime(@NotNull Duration gcTime) {
setGcTime(gcTime);
return this;
}
@NotNull
public ThrottlingController withSmoothingWindow(@NotNull Duration smoothingWindow) {
setSmoothingWindow(smoothingWindow);
return this;
}
@NotNull
public ThrottlingController withThrottlingDecrease(double throttlingDecrease) {
setThrottlingDecrease(throttlingDecrease);
return this;
}
@NotNull
public ThrottlingController withInitialKeysPerSecond(double initialKeysPerSecond) {
checkArgument(initialKeysPerSecond > 0, "Initial keys per second should not be zero or less");
this.smoothedTimePerKeyMillis = 1000.0 / initialKeysPerSecond;
return this;
}
@NotNull
public ThrottlingController withInitialThrottling(double initialThrottling) {
checkArgument(initialThrottling >= 0, "Initial throttling should not be zero or less");
this.smoothedThrottling = initialThrottling;
return this;
}
// endregion
private static long rngState = System.nanoTime();
private static float nextFloat() {
long x = rngState;
x ^= (x << 21);
x ^= (x >>> 35);
x ^= (x << 4);
rngState = x;
x &= ((1L << 24) - 1);
return (int) x / (float) (1 << 24);
}
public boolean isOverloaded() {
bufferedRequests++;
if (nextFloat() < throttling) {
bufferedRequestsThrottled++;
return true;
}
return false;
}
@Override
public void onUpdateConcurrentTasksStats(int concurrentTasksSize, long loopTime) {
this.concurrentTasksSize = concurrentTasksSize;
}
@Override
public void onUpdateSelectedKeysStats(int lastSelectedKeys, int invalidKeys, int acceptKeys, int connectKeys, int readKeys, int writeKeys, long loopTime) {
this.lastSelectedKeys = lastSelectedKeys;
}
@Override
public void onUpdateBusinessLogicTime(boolean anyTaskOrKeyPresent, boolean externalTaskOrKeyPresent, long businessLogicTime) {
if (businessLogicTime < 0 || businessLogicTime > 60000) {
logger.warn("Invalid processing time: {}", businessLogicTime);
return;
}
int throttlingKeys = lastSelectedKeys + concurrentTasksSize;
int lastTimePredicted = (int) (throttlingKeys * smoothedTimePerKeyMillis);
if (gcTimeMillis != 0.0 && businessLogicTime > lastTimePredicted + gcTimeMillis) {
logger.debug("GC detected {} ms, {} keys", businessLogicTime, throttlingKeys);
businessLogicTime = lastTimePredicted + gcTimeMillis;
infoRoundsGc++;
}
double weight = 1.0 - 1.0 / smoothingWindow;
if (bufferedRequests != 0) {
assert bufferedRequestsThrottled <= bufferedRequests;
double value = (double) bufferedRequestsThrottled / bufferedRequests;
smoothedThrottling = (smoothedThrottling - value) * pow(weight, bufferedRequests) + value;
infoTotalRequests += bufferedRequests;
infoTotalRequestsThrottled += bufferedRequestsThrottled;
bufferedRequests = 0;
bufferedRequestsThrottled = 0;
}
if (throttlingKeys != 0) {
double value = (double) businessLogicTime / throttlingKeys;
smoothedTimePerKeyMillis = (smoothedTimePerKeyMillis - value) * pow(weight, throttlingKeys) + value;
}
infoTotalTimeMillis += businessLogicTime;
}
@Override
public void onUpdateSelectorSelectTime(long selectorSelectTime) {
int throttlingKeys = lastSelectedKeys + concurrentTasksSize;
double predictedTime = throttlingKeys * smoothedTimePerKeyMillis;
double newThrottling = smoothedThrottling - throttlingDecrease;
if (newThrottling < 0)
newThrottling = 0;
if (predictedTime > targetTimeMillis) {
double extraThrottling = 1.0 - targetTimeMillis / predictedTime;
if (extraThrottling > newThrottling) {
newThrottling = extraThrottling;
infoRoundsExceededTargetTime++;
}
}
if (newThrottling == 0)
infoRoundsZeroThrottling++;
infoRounds++;
throttling = (float) newThrottling;
}
// region NOP
@Override
public void onUpdateSelectorSelectTimeout(long selectorSelectTimeout) {
}
@Override
public void onUpdateSelectedKeyDuration(@NotNull Stopwatch sw) {
}
@Override
public void onUpdateLocalTaskDuration(@NotNull Runnable runnable, @Nullable Stopwatch sw) {
}
@Override
public void onUpdateLocalTasksStats(int newTasks, long loopTime) {
}
@Override
public void onUpdateConcurrentTaskDuration(@NotNull Runnable runnable, @Nullable Stopwatch sw) {
}
@Override
public void onUpdateScheduledTaskDuration(@NotNull Runnable runnable, @Nullable Stopwatch sw, boolean background) {
}
@Override
public void onUpdateScheduledTasksStats(int newTasks, long loopTime, boolean background) {
}
@Override
public void onFatalError(@NotNull Throwable e, Object causedObject) {
}
@Override
public void onScheduledTaskOverdue(int overdue, boolean background) {
}
// endregion
public double getAvgTimePerKeyMillis() {
return smoothedTimePerKeyMillis;
}
@JmxAttribute
public double getAvgKeysPerSecond() {
return 1000.0 / smoothedTimePerKeyMillis;
}
@JmxAttribute
public double getAvgThrottling() {
return smoothedThrottling;
}
@JmxAttribute
public Duration getTargetTime() {
return Duration.ofMillis(targetTimeMillis);
}
@JmxAttribute
public void setTargetTime(@NotNull Duration targetTime) {
checkArgument(targetTime.toMillis() > 0, "Target time should not be zero or less");
this.targetTimeMillis = (int) targetTime.toMillis();
}
@JmxAttribute
public Duration getGcTime() {
return Duration.ofMillis(gcTimeMillis);
}
@JmxAttribute
public void setGcTime(@NotNull Duration gcTime) {
checkArgument(gcTime.toMillis() > 0, "GC time should not be zero or less");
this.gcTimeMillis = (int) gcTime.toMillis();
}
@JmxAttribute
public double getThrottlingDecrease() {
return throttlingDecrease;
}
@JmxAttribute
public void setThrottlingDecrease(double throttlingDecrease) {
checkArgument(throttlingDecrease >= 0.0 && throttlingDecrease <= 1.0, "Throttling decrease should not fall out of [0;1] range");
this.throttlingDecrease = throttlingDecrease;
}
@JmxAttribute
public Duration getSmoothingWindow() {
return Duration.ofMillis(smoothingWindow);
}
@JmxAttribute
public void setSmoothingWindow(@NotNull Duration smoothingWindow) {
checkArgument(smoothingWindow.toMillis() > 0, "Smoothing window should not be zero or less");
this.smoothingWindow = (int) smoothingWindow.toMillis();
}
@JmxAttribute(reducer = JmxReducerSum.class)
public long getTotalRequests() {
return infoTotalRequests;
}
@JmxAttribute(reducer = JmxReducerSum.class)
public long getTotalRequestsThrottled() {
return infoTotalRequestsThrottled;
}
@JmxAttribute(reducer = JmxReducerSum.class)
public long getTotalProcessed() {
return infoTotalRequests - infoTotalRequestsThrottled;
}
@JmxAttribute(reducer = JmxReducerSum.class)
public long getTotalTimeMillis() {
return infoTotalTimeMillis;
}
@JmxAttribute(reducer = JmxReducerSum.class)
public long getRounds() {
return infoRounds;
}
@JmxAttribute(reducer = JmxReducerSum.class)
public long getRoundsZeroThrottling() {
return infoRoundsZeroThrottling;
}
@JmxAttribute(reducer = JmxReducerSum.class)
public long getRoundsExceededTargetTime() {
return infoRoundsExceededTargetTime;
}
@JmxAttribute(reducer = JmxReducerSum.class)
public long getInfoRoundsGc() {
return infoRoundsGc;
}
@JmxAttribute
public double getThrottling() {
return throttling;
}
@JmxOperation
public void resetInfo() {
infoTotalRequests = 0;
infoTotalRequestsThrottled = 0;
infoTotalTimeMillis = 0;
infoRounds = 0;
infoRoundsZeroThrottling = 0;
infoRoundsExceededTargetTime = 0;
}
@NotNull
@Override
public Eventloop getEventloop() {
return eventloop;
}
@Override
public String toString() {
return String.format("{throttling:%2d%% avgKps=%-4d avgThrottling=%2d%% requests=%-4d throttled=%-4d rounds=%-3d zero=%-3d >targetTime=%-3d}",
(int) (throttling * 100),
(int) getAvgKeysPerSecond(),
(int) (smoothedThrottling * 100),
infoTotalRequests,
infoTotalRequestsThrottled,
infoRounds,
infoRoundsZeroThrottling,
infoRoundsExceededTargetTime);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy