/*
* 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.annotation.Nullable;
import io.datakernel.jmx.*;
import io.datakernel.jmx.JmxReducers.JmxReducerSum;
import io.datakernel.util.Stopwatch;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static io.datakernel.jmx.ValueStats.POWERS_OF_TWO;
@SuppressWarnings("unused")
public final class EventloopStats {
private final EventStats loops;
private final ValueStats selectorSelectTimeout;
private final ValueStats selectorSelectTime;
private final ValueStats businessLogicTime;
private final Tasks tasks;
private final Keys keys;
private final ExceptionStats fatalErrors;
private final Map<StackTrace, ExceptionStats> fatalErrorsMap;
private final EventStats idleLoops;
private final EventStats selectOverdues;
private EventloopStats(double smoothingWindow, Eventloop.ExtraStatsExtractor extraStatsExtractor) {
loops = EventStats.create(smoothingWindow);
selectorSelectTimeout = ValueStats.create(smoothingWindow).withHistogram(new int[]{
-256, -128, -64, -32, -16, -8, -4, -2, -1, 0, 1, 2, 4, 8, 16, 32});
selectorSelectTime = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
businessLogicTime = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
tasks = new Tasks(smoothingWindow, extraStatsExtractor);
keys = new Keys(smoothingWindow);
fatalErrors = ExceptionStats.create().withStoreStackTrace(true);
fatalErrorsMap = new HashMap<>();
idleLoops = EventStats.create(smoothingWindow);
selectOverdues = EventStats.create(smoothingWindow);
}
public static EventloopStats create(double smoothingWindow, Eventloop.ExtraStatsExtractor extraStatsExtractor) {
return new EventloopStats(smoothingWindow, extraStatsExtractor);
}
public void setSmoothingWindow(double smoothingWindow) {
loops.setSmoothingWindow(smoothingWindow);
selectorSelectTimeout.setSmoothingWindow(smoothingWindow);
selectorSelectTime.setSmoothingWindow(smoothingWindow);
businessLogicTime.setSmoothingWindow(smoothingWindow);
tasks.setSmoothingWindow(smoothingWindow);
keys.setSmoothingWindow(smoothingWindow);
}
public void reset() {
loops.resetStats();
selectorSelectTimeout.resetStats();
selectorSelectTime.resetStats();
businessLogicTime.resetStats();
tasks.reset();
keys.reset();
fatalErrors.resetStats();
fatalErrorsMap.clear();
idleLoops.resetStats();
selectOverdues.resetStats();
}
// region updating
public void updateBusinessLogicTime(long businessLogicTime) {
loops.recordEvent();
this.businessLogicTime.recordValue((int) businessLogicTime);
}
public void updateSelectorSelectTime(long selectorSelectTime) {
this.selectorSelectTime.recordValue((int) selectorSelectTime);
}
public void updateSelectorSelectTimeout(long selectorSelectTimeout) {
this.selectorSelectTimeout.recordValue((int) selectorSelectTimeout);
if(selectorSelectTimeout < 0) this.selectOverdues.recordEvent();
}
public void updateSelectedKeyDuration(Stopwatch sw) {
if (sw != null) {
keys.oneKeyTime.recordValue((int) sw.elapsed(TimeUnit.MICROSECONDS));
}
}
public void updateSelectedKeysStats(int lastSelectedKeys, int invalidKeys, int acceptKeys,
int connectKeys, int readKeys, int writeKeys, long loopTime) {
keys.all.recordEvents(lastSelectedKeys);
keys.invalid.recordEvents(invalidKeys);
keys.acceptPerLoop.recordValue(acceptKeys);
keys.connectPerLoop.recordValue(connectKeys);
keys.readPerLoop.recordValue(readKeys);
keys.writePerLoop.recordValue(writeKeys);
keys.loopTime.recordValue((int) loopTime);
}
private void updateTaskDuration(ValueStats counter, DurationRunnable longestCounter, Runnable runnable, @Nullable Stopwatch sw) {
if (sw != null) {
int elapsed = (int) sw.elapsed(TimeUnit.MICROSECONDS);
counter.recordValue(elapsed);
if (elapsed > longestCounter.getDuration()) {
longestCounter.update(runnable, elapsed);
}
}
}
public void updateLocalTaskDuration(Runnable runnable, @Nullable Stopwatch sw) {
updateTaskDuration(tasks.local.oneTaskTime, tasks.local.longestTask, runnable, sw);
}
public void updateLocalTasksStats(int newTasks, long loopTime) {
tasks.local.loopTime.recordValue((int) loopTime);
tasks.local.tasksPerLoop.recordValue(newTasks);
}
public void updateConcurrentTaskDuration(Runnable runnable, @Nullable Stopwatch sw) {
updateTaskDuration(tasks.concurrent.oneTaskTime, tasks.concurrent.longestTask, runnable, sw);
}
public void updateConcurrentTasksStats(int newTasks, long loopTime) {
tasks.concurrent.loopTime.recordValue((int) loopTime);
tasks.concurrent.tasksPerLoop.recordValue(newTasks);
}
public void updateScheduledTaskDuration(Runnable runnable, @Nullable Stopwatch sw, boolean background) {
if (background) {
updateTaskDuration(tasks.background.getOneTaskTime(), tasks.background.getLongestTask(), runnable, sw);
} else {
updateTaskDuration(tasks.scheduled.getOneTaskTime(), tasks.scheduled.getLongestTask(), runnable, sw);
}
}
public void updateScheduledTasksStats(int newTasks, long loopTime, boolean background) {
if (background) {
tasks.background.getLoopTime().recordValue((int) loopTime);
tasks.background.getTasksPerLoop().recordValue(newTasks);
} else {
tasks.scheduled.getLoopTime().recordValue((int) loopTime);
tasks.scheduled.getTasksPerLoop().recordValue(newTasks);
}
}
public void recordFatalError(Throwable throwable, Object causedObject) {
StackTrace stackTrace = new StackTrace(throwable.getStackTrace());
fatalErrors.recordException(throwable, causedObject);
ExceptionStats stats = fatalErrorsMap.get(stackTrace);
if (stats == null) {
stats = ExceptionStats.create().withStoreStackTrace(true);
fatalErrorsMap.put(stackTrace, stats);
}
stats.recordException(throwable, causedObject);
}
public void recordScheduledTaskOverdue(int overdue, boolean background) {
if (background) {
tasks.background.overdues.recordValue(overdue);
} else {
tasks.scheduled.overdues.recordValue(overdue);
}
}
public void updateProcessedTasksAndKeys(int tasksAndKeys) {
if (tasksAndKeys == 0) idleLoops.recordEvent();
}
// endregion
// region root attributes
@JmxAttribute
public EventStats getLoops() {
return loops;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getSelectorSelectTime() {
return selectorSelectTime;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getSelectorSelectTimeout() {
return selectorSelectTimeout;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getBusinessLogicTime() {
return businessLogicTime;
}
@JmxAttribute
public Tasks getTasks() {
return tasks;
}
@JmxAttribute
public Keys getKeys() {
return keys;
}
@JmxAttribute
public ExceptionStats getFatalErrors() {
return fatalErrors;
}
@JmxAttribute
public Map<StackTrace, ExceptionStats> getFatalErrorsMap() {
return fatalErrorsMap;
}
@JmxAttribute
public EventStats getIdleLoops() {
return idleLoops;
}
@JmxAttribute
public EventStats getSelectOverdues() {
return selectOverdues;
}
// endregion
// region helper classes for stats grouping
public static final class Tasks {
private final TaskStats local;
private final TaskStats concurrent;
private final ScheduledTaskStats scheduled;
private final ScheduledTaskStats background;
public Tasks(double smoothingWindow, final Eventloop.ExtraStatsExtractor extraStatsExtractor) {
local = new TaskStats(smoothingWindow, new Count() {
@Override
public int getCount() {
return extraStatsExtractor.getLocalTasksCount();
}
});
concurrent = new TaskStats(smoothingWindow, new Count() {
@Override
public int getCount() {
return extraStatsExtractor.getConcurrentTasksCount();
}
});
scheduled = new ScheduledTaskStats(smoothingWindow, new Count() {
@Override
public int getCount() {
return extraStatsExtractor.getScheduledTasksCount();
}
});
background = new ScheduledTaskStats(smoothingWindow, new Count() {
@Override
public int getCount() {
return extraStatsExtractor.getBackgroundTasksCount();
}
});
}
public void setSmoothingWindow(double smoothingWindow) {
local.setSmoothingWindow(smoothingWindow);
concurrent.setSmoothingWindow(smoothingWindow);
scheduled.setSmoothingWindow(smoothingWindow);
background.setSmoothingWindow(smoothingWindow);
}
public void reset() {
local.reset();
concurrent.reset();
scheduled.reset();
background.reset();
}
@JmxAttribute
public TaskStats getLocal() {
return local;
}
@JmxAttribute
public TaskStats getConcurrent() {
return concurrent;
}
@JmxAttribute
public ScheduledTaskStats getScheduled() {
return scheduled;
}
@JmxAttribute
public ScheduledTaskStats getBackground() {
return background;
}
}
public static class TaskStats {
private final ValueStats tasksPerLoop;
private final ValueStats loopTime;
private final ValueStats oneTaskTime;
private final DurationRunnable longestTask;
private final Count count;
public TaskStats(double smoothingWindow, Count count) {
this.tasksPerLoop = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
this.loopTime = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
this.oneTaskTime = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
this.longestTask = new DurationRunnable();
this.count = count;
}
public void setSmoothingWindow(double smoothingWindow) {
tasksPerLoop.setSmoothingWindow(smoothingWindow);
loopTime.setSmoothingWindow(smoothingWindow);
oneTaskTime.setSmoothingWindow(smoothingWindow);
}
public void reset() {
tasksPerLoop.resetStats();
loopTime.resetStats();
oneTaskTime.resetStats();
}
@JmxAttribute(name = "perLoop", extraSubAttributes = "histogram")
public ValueStats getTasksPerLoop() {
return tasksPerLoop;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getLoopTime() {
return loopTime;
}
@JmxAttribute(name = "oneTaskTime(μs)", extraSubAttributes = "histogram")
public ValueStats getOneTaskTime() {
return oneTaskTime;
}
@JmxAttribute
public DurationRunnable getLongestTask() {
return longestTask;
}
@JmxAttribute(reducer = JmxReducerSum.class)
public int getCount() {
return count.getCount();
}
}
public static final class ScheduledTaskStats extends TaskStats {
private final ValueStats overdues;
public ScheduledTaskStats(double smoothingWindow, Count count) {
super(smoothingWindow, count);
overdues = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
}
public void setSmoothingWindow(double smoothingWindow) {
super.setSmoothingWindow(smoothingWindow);
overdues.setSmoothingWindow(smoothingWindow);
}
public void reset() {
super.reset();
overdues.resetStats();
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getOverdues() {
return overdues;
}
}
public static final class Keys {
private final EventStats all;
private final EventStats invalid;
private final ValueStats acceptPerLoop;
private final ValueStats connectPerLoop;
private final ValueStats readPerLoop;
private final ValueStats writePerLoop;
private final ValueStats loopTime;
private final ValueStats oneKeyTime;
public Keys(double smoothingWindow) {
all = EventStats.create(smoothingWindow);
invalid = EventStats.create(smoothingWindow);
acceptPerLoop = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
connectPerLoop = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
readPerLoop = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
writePerLoop = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
loopTime = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
oneKeyTime = ValueStats.create(smoothingWindow).withHistogram(POWERS_OF_TWO);
}
public void setSmoothingWindow(double smoothingWindow) {
all.setSmoothingWindow(smoothingWindow);
invalid.setSmoothingWindow(smoothingWindow);
acceptPerLoop.setSmoothingWindow(smoothingWindow);
connectPerLoop.setSmoothingWindow(smoothingWindow);
readPerLoop.setSmoothingWindow(smoothingWindow);
writePerLoop.setSmoothingWindow(smoothingWindow);
loopTime.setSmoothingWindow(smoothingWindow);
oneKeyTime.setSmoothingWindow(smoothingWindow);
}
public void reset() {
all.resetStats();
invalid.resetStats();
acceptPerLoop.resetStats();
connectPerLoop.resetStats();
readPerLoop.resetStats();
writePerLoop.resetStats();
loopTime.resetStats();
oneKeyTime.resetStats();
}
@JmxAttribute
public EventStats getAll() {
return all;
}
@JmxAttribute
public EventStats getInvalid() {
return invalid;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getAcceptPerLoop() {
return acceptPerLoop;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getConnectPerLoop() {
return connectPerLoop;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getReadPerLoop() {
return readPerLoop;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getWritePerLoop() {
return writePerLoop;
}
@JmxAttribute(extraSubAttributes = "histogram")
public ValueStats getLoopTime() {
return loopTime;
}
@JmxAttribute(name = "oneKeyTime(μs)", extraSubAttributes = "histogram")
public ValueStats getOneKeyTime() {
return oneKeyTime;
}
}
private interface Count {
int getCount();
}
private static final class StackTrace {
private final StackTraceElement[] stackTraceElements;
public StackTrace(StackTraceElement[] stackTraceElements) {
this.stackTraceElements = stackTraceElements;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof StackTrace)) return false;
StackTrace that = (StackTrace) o;
return Arrays.equals(stackTraceElements, that.stackTraceElements);
}
@Override
public int hashCode() {
return stackTraceElements != null ? Arrays.hashCode(stackTraceElements) : 0;
}
}
public static final class DurationRunnable implements JmxStats<DurationRunnable> {
private Runnable runnable;
private long duration;
void reset() {
duration = 0;
runnable = null;
}
void update(Runnable runnable, long duration) {
this.duration = duration;
this.runnable = runnable;
}
@JmxAttribute(name = "duration(μs)")
public long getDuration() {
return duration;
}
@JmxAttribute
public String getClassName() {
return (runnable == null) ? "" : runnable.getClass().getName();
}
@Override
public void add(DurationRunnable another) {
if (another.duration > this.duration) {
this.duration = another.duration;
this.runnable = another.runnable;
}
}
}
// endregion
}