diff options
author | Luck <git@lucko.me> | 2020-02-04 00:49:40 +0000 |
---|---|---|
committer | Luck <git@lucko.me> | 2020-02-04 00:49:40 +0000 |
commit | e02d52ce8d45550a4d77f11971e31cf0732e5f0c (patch) | |
tree | cfcfb2850ff6b279276e43233e5e1acf82993a98 /spark-common/src/main/java/me/lucko/spark | |
parent | d15a12788ddc8aba09f49003fcef55b927850de3 (diff) | |
download | spark-e02d52ce8d45550a4d77f11971e31cf0732e5f0c.tar.gz spark-e02d52ce8d45550a4d77f11971e31cf0732e5f0c.tar.bz2 spark-e02d52ce8d45550a4d77f11971e31cf0732e5f0c.zip |
Monitor average tick durations & report them in /spark tps
Diffstat (limited to 'spark-common/src/main/java/me/lucko/spark')
15 files changed, 369 insertions, 121 deletions
diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index dc88306..340fa34 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -35,8 +35,9 @@ import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.command.tabcomplete.CompletionSupplier; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.cpu.CpuMonitor; -import me.lucko.spark.common.monitor.tick.TpsCalculator; -import me.lucko.spark.common.sampler.TickCounter; +import me.lucko.spark.common.monitor.tick.TickStatistics; +import me.lucko.spark.common.sampler.tick.TickHook; +import me.lucko.spark.common.sampler.tick.TickReporter; import me.lucko.spark.common.util.BytebinClient; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; @@ -66,8 +67,9 @@ public class SparkPlatform { private final List<CommandModule> commandModules; private final List<Command> commands; private final ActivityLog activityLog; - private final TickCounter tickCounter; - private final TpsCalculator tpsCalculator; + private final TickHook tickHook; + private final TickReporter tickReporter; + private final TickStatistics tickStatistics; public SparkPlatform(SparkPlugin plugin) { this.plugin = plugin; @@ -89,21 +91,29 @@ public class SparkPlatform { this.activityLog = new ActivityLog(plugin.getPluginDirectory().resolve("activity.json")); this.activityLog.load(); - this.tickCounter = plugin.createTickCounter(); - this.tpsCalculator = this.tickCounter != null ? new TpsCalculator() : null; + this.tickHook = plugin.createTickHook(); + this.tickReporter = plugin.createTickReporter(); + this.tickStatistics = this.tickHook != null ? new TickStatistics() : null; } public void enable() { - if (this.tickCounter != null) { - this.tickCounter.addTickTask(this.tpsCalculator); - this.tickCounter.start(); + if (this.tickHook != null) { + this.tickHook.addCallback(this.tickStatistics); + this.tickHook.start(); + } + if (this.tickReporter != null) { + this.tickReporter.addCallback(this.tickStatistics); + this.tickReporter.start(); } CpuMonitor.ensureMonitoring(); } public void disable() { - if (this.tickCounter != null) { - this.tickCounter.close(); + if (this.tickHook != null) { + this.tickHook.close(); + } + if (this.tickReporter != null) { + this.tickReporter.close(); } for (CommandModule module : this.commandModules) { @@ -119,12 +129,16 @@ public class SparkPlatform { return this.activityLog; } - public TickCounter getTickCounter() { - return this.tickCounter; + public TickHook getTickHook() { + return this.tickHook; + } + + public TickReporter getTickReporter() { + return this.tickReporter; } - public TpsCalculator getTpsCalculator() { - return this.tpsCalculator; + public TickStatistics getTickStatistics() { + return this.tickStatistics; } public void executeCommand(CommandSender sender, String[] args) { diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java index 1171b33..c0a928d 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java @@ -22,7 +22,8 @@ package me.lucko.spark.common; import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.sampler.ThreadDumper; -import me.lucko.spark.common.sampler.TickCounter; +import me.lucko.spark.common.sampler.tick.TickHook; +import me.lucko.spark.common.sampler.tick.TickReporter; import java.nio.file.Path; import java.util.stream.Stream; @@ -79,13 +80,24 @@ public interface SparkPlugin { } /** - * Creates a tick counter for the platform, if supported. + * Creates a tick hook for the platform, if supported. * * <p>Returns {@code null} if the platform does not have "ticks"</p> * - * @return a new tick counter + * @return a new tick hook */ - default TickCounter createTickCounter() { + default TickHook createTickHook() { + return null; + } + + /** + * Creates a tick reporter for the platform, if supported. + * + * <p>Returns {@code null} if the platform does not have "ticks"</p> + * + * @return a new tick reporter + */ + default TickReporter createTickReporter() { return null; } diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java index 0737a00..08233db 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java @@ -25,8 +25,9 @@ import me.lucko.spark.common.command.Command; import me.lucko.spark.common.command.CommandModule; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.cpu.CpuMonitor; -import me.lucko.spark.common.monitor.tick.TpsCalculator; +import me.lucko.spark.common.monitor.tick.TickStatistics; import me.lucko.spark.common.util.FormatUtil; +import me.lucko.spark.common.util.RollingAverage; import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; @@ -52,18 +53,29 @@ public class HealthModule implements CommandModule { consumer.accept(Command.builder() .aliases("tps", "cpu") .executor((platform, sender, resp, arguments) -> { - TpsCalculator tpsCalculator = platform.getTpsCalculator(); - if (tpsCalculator != null) { + TickStatistics tickStatistics = platform.getTickStatistics(); + if (tickStatistics != null) { resp.replyPrefixed(TextComponent.of("TPS from last 5s, 10s, 1m, 5m, 15m:")); resp.replyPrefixed(TextComponent.builder(" ") - .append(formatTps(tpsCalculator.avg5Sec())).append(TextComponent.of(", ")) - .append(formatTps(tpsCalculator.avg10Sec())).append(TextComponent.of(", ")) - .append(formatTps(tpsCalculator.avg1Min())).append(TextComponent.of(", ")) - .append(formatTps(tpsCalculator.avg5Min())).append(TextComponent.of(", ")) - .append(formatTps(tpsCalculator.avg15Min())) + .append(formatTps(tickStatistics.tps5Sec())).append(TextComponent.of(", ")) + .append(formatTps(tickStatistics.tps10Sec())).append(TextComponent.of(", ")) + .append(formatTps(tickStatistics.tps1Min())).append(TextComponent.of(", ")) + .append(formatTps(tickStatistics.tps5Min())).append(TextComponent.of(", ")) + .append(formatTps(tickStatistics.tps15Min())) .build() ); resp.replyPrefixed(TextComponent.empty()); + + if (tickStatistics.isDurationSupported()) { + resp.replyPrefixed(TextComponent.of("Tick durations (avg/min/max ms) from last 5s, 10s, 1m:")); + resp.replyPrefixed(TextComponent.builder(" ") + .append(formatTickDurations(tickStatistics.duration5Sec())).append(TextComponent.of(", ")) + .append(formatTickDurations(tickStatistics.duration10Sec())).append(TextComponent.of(", ")) + .append(formatTickDurations(tickStatistics.duration1Min())) + .build() + ); + resp.replyPrefixed(TextComponent.empty()); + } } resp.replyPrefixed(TextComponent.of("CPU usage from last 10s, 1m, 15m:")); @@ -95,8 +107,8 @@ public class HealthModule implements CommandModule { List<Component> report = new LinkedList<>(); report.add(TextComponent.empty()); - TpsCalculator tpsCalculator = platform.getTpsCalculator(); - if (tpsCalculator != null) { + TickStatistics tickStatistics = platform.getTickStatistics(); + if (tickStatistics != null) { report.add(TextComponent.builder("") .append(TextComponent.builder(">").color(TextColor.DARK_GRAY).decoration(TextDecoration.BOLD, true).build()) .append(TextComponent.space()) @@ -104,14 +116,30 @@ public class HealthModule implements CommandModule { .build() ); report.add(TextComponent.builder(" ") - .append(formatTps(tpsCalculator.avg5Sec())).append(TextComponent.of(", ")) - .append(formatTps(tpsCalculator.avg10Sec())).append(TextComponent.of(", ")) - .append(formatTps(tpsCalculator.avg1Min())).append(TextComponent.of(", ")) - .append(formatTps(tpsCalculator.avg5Min())).append(TextComponent.of(", ")) - .append(formatTps(tpsCalculator.avg15Min())) + .append(formatTps(tickStatistics.tps5Sec())).append(TextComponent.of(", ")) + .append(formatTps(tickStatistics.tps10Sec())).append(TextComponent.of(", ")) + .append(formatTps(tickStatistics.tps1Min())).append(TextComponent.of(", ")) + .append(formatTps(tickStatistics.tps5Min())).append(TextComponent.of(", ")) + .append(formatTps(tickStatistics.tps15Min())) .build() ); report.add(TextComponent.empty()); + + if (tickStatistics.isDurationSupported()) { + report.add(TextComponent.builder("") + .append(TextComponent.builder(">").color(TextColor.DARK_GRAY).decoration(TextDecoration.BOLD, true).build()) + .append(TextComponent.space()) + .append(TextComponent.of("Tick durations (avg/min/max ms) from last 5s, 10s, 1m:", TextColor.GOLD)) + .build() + ); + report.add(TextComponent.builder(" ") + .append(formatTickDurations(tickStatistics.duration5Sec())).append(TextComponent.of(", ")) + .append(formatTickDurations(tickStatistics.duration10Sec())).append(TextComponent.of(", ")) + .append(formatTickDurations(tickStatistics.duration1Min())) + .build() + ); + report.add(TextComponent.empty()); + } } report.add(TextComponent.builder("") @@ -269,6 +297,29 @@ public class HealthModule implements CommandModule { return TextComponent.of( (tps > 20.0 ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0), color); } + public static TextComponent formatTickDurations(RollingAverage average){ + return TextComponent.builder("") + .append(formatTickDuration(average.getAverage())) + .append(TextComponent.of('/', TextColor.GRAY)) + .append(formatTickDuration(average.getMin())) + .append(TextComponent.of('/', TextColor.GRAY)) + .append(formatTickDuration(average.getMax())) + .build(); + } + + public static TextComponent formatTickDuration(double duration){ + TextColor color; + if (duration >= 50d) { + color = TextColor.RED; + } else if (duration >= 40d) { + color = TextColor.YELLOW; + } else { + color = TextColor.GREEN; + } + + return TextComponent.of(String.format("%.1f", duration), color); + } + public static TextComponent formatCpuUsage(double usage) { TextColor color; if (usage > 0.9) { diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java index cd98aa4..919931e 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java @@ -32,7 +32,7 @@ import me.lucko.spark.common.sampler.SamplerBuilder; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.sampler.ThreadGrouper; import me.lucko.spark.common.sampler.ThreadNodeOrder; -import me.lucko.spark.common.sampler.TickCounter; +import me.lucko.spark.common.sampler.tick.TickHook; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.format.TextColor; @@ -167,10 +167,10 @@ public class SamplerModule implements CommandModule { } int ticksOver = arguments.intFlag("only-ticks-over"); - TickCounter tickCounter = null; + TickHook tickHook = null; if (ticksOver != -1) { - tickCounter = platform.getTickCounter(); - if (tickCounter == null) { + tickHook = platform.getTickHook(); + if (tickHook == null) { resp.replyPrefixed(TextComponent.of("Tick counting is not supported!", TextColor.RED)); return; } @@ -193,7 +193,7 @@ public class SamplerModule implements CommandModule { builder.includeLineNumbers(includeLineNumbers); builder.ignoreSleeping(ignoreSleeping); if (ticksOver != -1) { - builder.ticksOver(ticksOver, tickCounter); + builder.ticksOver(ticksOver, tickHook); } Sampler sampler = this.activeSampler = builder.start(); diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java index 0ebb252..245a55b 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/TickMonitoringModule.java @@ -26,7 +26,7 @@ import me.lucko.spark.common.command.CommandModule; import me.lucko.spark.common.command.CommandResponseHandler; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.tick.TickMonitor; -import me.lucko.spark.common.sampler.TickCounter; +import me.lucko.spark.common.sampler.tick.TickHook; import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; @@ -35,14 +35,14 @@ import java.util.function.Consumer; public class TickMonitoringModule implements CommandModule { - /** The tick monitor instance currently running, if any */ - private TickCounter tickCounter = null; + /** The tick hook instance currently running, if any */ + private TickHook tickHook = null; private ReportingTickMonitor activeTickMonitor = null; @Override public void close() { if (this.activeTickMonitor != null) { - this.tickCounter.removeTickTask(this.activeTickMonitor); + this.tickHook.removeCallback(this.activeTickMonitor); this.activeTickMonitor.close(); this.activeTickMonitor = null; } @@ -55,10 +55,10 @@ public class TickMonitoringModule implements CommandModule { .argumentUsage("threshold", "percentage increase") .argumentUsage("without-gc", null) .executor((platform, sender, resp, arguments) -> { - if (this.tickCounter == null) { - this.tickCounter = platform.getTickCounter(); + if (this.tickHook == null) { + this.tickHook = platform.getTickHook(); } - if (this.tickCounter == null) { + if (this.tickHook == null) { resp.replyPrefixed(TextComponent.of("Not supported!", TextColor.RED)); return; } @@ -69,8 +69,8 @@ public class TickMonitoringModule implements CommandModule { threshold = 100; } - this.activeTickMonitor = new ReportingTickMonitor(platform, resp, this.tickCounter, threshold, !arguments.boolFlag("without-gc")); - this.tickCounter.addTickTask(this.activeTickMonitor); + this.activeTickMonitor = new ReportingTickMonitor(platform, resp, this.tickHook, threshold, !arguments.boolFlag("without-gc")); + this.tickHook.addCallback(this.activeTickMonitor); } else { close(); resp.broadcastPrefixed(TextComponent.of("Tick monitor disabled.")); @@ -84,8 +84,8 @@ public class TickMonitoringModule implements CommandModule { private static class ReportingTickMonitor extends TickMonitor { private final CommandResponseHandler resp; - ReportingTickMonitor(SparkPlatform platform, CommandResponseHandler resp, TickCounter tickCounter, int percentageChangeThreshold, boolean monitorGc) { - super(platform, tickCounter, percentageChangeThreshold, monitorGc); + ReportingTickMonitor(SparkPlatform platform, CommandResponseHandler resp, TickHook tickHook, int percentageChangeThreshold, boolean monitorGc) { + super(platform, tickHook, percentageChangeThreshold, monitorGc); this.resp = resp; } diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java index fa4b83c..7015b4a 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickMonitor.java @@ -23,7 +23,7 @@ package me.lucko.spark.common.monitor.tick; import com.sun.management.GarbageCollectionNotificationInfo; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.monitor.memory.GarbageCollectionMonitor; -import me.lucko.spark.common.sampler.TickCounter; +import me.lucko.spark.common.sampler.tick.TickHook; import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; @@ -31,11 +31,11 @@ import net.kyori.text.format.TextColor; import java.text.DecimalFormat; import java.util.DoubleSummaryStatistics; -public abstract class TickMonitor implements TickCounter.TickTask, GarbageCollectionMonitor.Listener, AutoCloseable { +public abstract class TickMonitor implements TickHook.Callback, GarbageCollectionMonitor.Listener, AutoCloseable { private static final DecimalFormat df = new DecimalFormat("#.##"); private final SparkPlatform platform; - private final TickCounter tickCounter; + private final TickHook tickHook; private final int zeroTick; private final GarbageCollectionMonitor garbageCollectionMonitor; private final int percentageChangeThreshold; @@ -46,10 +46,10 @@ public abstract class TickMonitor implements TickCounter.TickTask, GarbageCollec private final DoubleSummaryStatistics averageTickTime = new DoubleSummaryStatistics(); private double avg; - public TickMonitor(SparkPlatform platform, TickCounter tickCounter, int percentageChangeThreshold, boolean monitorGc) { + public TickMonitor(SparkPlatform platform, TickHook tickHook, int percentageChangeThreshold, boolean monitorGc) { this.platform = platform; - this.tickCounter = tickCounter; - this.zeroTick = tickCounter.getCurrentTick(); + this.tickHook = tickHook; + this.zeroTick = tickHook.getCurrentTick(); this.percentageChangeThreshold = percentageChangeThreshold; if (monitorGc) { @@ -61,7 +61,7 @@ public abstract class TickMonitor implements TickCounter.TickTask, GarbageCollec } public int getCurrentTick() { - return this.tickCounter.getCurrentTick() - this.zeroTick; + return this.tickHook.getCurrentTick() - this.zeroTick; } protected abstract void sendMessage(Component message); @@ -74,7 +74,7 @@ public abstract class TickMonitor implements TickCounter.TickTask, GarbageCollec } @Override - public void onTick(TickCounter counter) { + public void onTick(TickHook hook) { double now = ((double) System.nanoTime()) / 1000000d; // init diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TpsCalculator.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickStatistics.java index 6b4f2d1..c91ceae 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TpsCalculator.java +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/tick/TickStatistics.java @@ -20,7 +20,9 @@ package me.lucko.spark.common.monitor.tick; -import me.lucko.spark.common.sampler.TickCounter; +import me.lucko.spark.common.sampler.tick.TickHook; +import me.lucko.spark.common.sampler.tick.TickReporter; +import me.lucko.spark.common.util.RollingAverage; import java.math.BigDecimal; import java.math.RoundingMode; @@ -37,28 +39,35 @@ import java.util.concurrent.TimeUnit; * rather avoid that. Secondly, it allows us to generate rolling averages over a shorter period of * time.</p> */ -public class TpsCalculator implements TickCounter.TickTask { +public class TickStatistics implements TickHook.Callback, TickReporter.Callback { private static final long SEC_IN_NANO = TimeUnit.SECONDS.toNanos(1); private static final int TPS = 20; - private static final int SAMPLE_INTERVAL = 20; - private static final BigDecimal TPS_BASE = new BigDecimal(SEC_IN_NANO).multiply(new BigDecimal(SAMPLE_INTERVAL)); - - private final TpsRollingAverage avg5Sec = new TpsRollingAverage(5); - private final TpsRollingAverage avg10Sec = new TpsRollingAverage(10); - private final TpsRollingAverage avg1Min = new TpsRollingAverage(60); - private final TpsRollingAverage avg5Min = new TpsRollingAverage(60 * 5); - private final TpsRollingAverage avg15Min = new TpsRollingAverage(60 * 15); - - private final TpsRollingAverage[] averages = new TpsRollingAverage[]{ - this.avg5Sec, this.avg10Sec, this.avg1Min, this.avg5Min, this.avg15Min - }; + private static final int TPS_SAMPLE_INTERVAL = 20; + private static final BigDecimal TPS_BASE = new BigDecimal(SEC_IN_NANO).multiply(new BigDecimal(TPS_SAMPLE_INTERVAL)); + + private final TpsRollingAverage tps5Sec = new TpsRollingAverage(5); + private final TpsRollingAverage tps10Sec = new TpsRollingAverage(10); + private final TpsRollingAverage tps1Min = new TpsRollingAverage(60); + private final TpsRollingAverage tps5Min = new TpsRollingAverage(60 * 5); + private final TpsRollingAverage tps15Min = new TpsRollingAverage(60 * 15); + private final TpsRollingAverage[] tpsAverages = {this.tps5Sec, this.tps10Sec, this.tps1Min, this.tps5Min, this.tps15Min}; + + private boolean durationSupported = false; + private final RollingAverage tickDuration5Sec = new RollingAverage(TPS * 5); + private final RollingAverage tickDuration10Sec = new RollingAverage(TPS * 10); + private final RollingAverage tickDuration1Min = new RollingAverage(TPS * 60); + private final RollingAverage[] tickDurationAverages = {this.tickDuration5Sec, this.tickDuration10Sec, this.tickDuration1Min}; private long last = 0; + public boolean isDurationSupported() { + return this.durationSupported; + } + @Override - public void onTick(TickCounter counter) { - if (counter.getCurrentTick() % SAMPLE_INTERVAL != 0) { + public void onTick(TickHook hook) { + if (hook.getCurrentTick() % TPS_SAMPLE_INTERVAL != 0) { return; } @@ -73,33 +82,62 @@ public class TpsCalculator implements TickCounter.TickTask { BigDecimal currentTps = TPS_BASE.divide(new BigDecimal(diff), 30, RoundingMode.HALF_UP); BigDecimal total = currentTps.multiply(new BigDecimal(diff)); - for (TpsRollingAverage rollingAverage : this.averages) { + for (TpsRollingAverage rollingAverage : this.tpsAverages) { rollingAverage.add(currentTps, diff, total); } this.last = now; } - public double avg5Sec() { - return this.avg5Sec.getAverage(); + @Override + public void onTick(double duration) { + this.durationSupported = true; + BigDecimal decimal = BigDecimal.valueOf(duration); + for (RollingAverage rollingAverage : this.tickDurationAverages) { + rollingAverage.add(decimal); + } + } + + public double tps5Sec() { + return this.tps5Sec.getAverage(); } - public double avg10Sec() { - return this.avg10Sec.getAverage(); + public double tps10Sec() { + return this.tps10Sec.getAverage(); } - public double avg1Min() { - return this.avg1Min.getAverage(); + public double tps1Min() { + return this.tps1Min.getAverage(); } - public double avg5Min() { - return this.avg5Min.getAverage(); + public double tps5Min() { + return this.tps5Min.getAverage(); } - public double avg15Min() { - return this.avg15Min.getAverage(); + public double tps15Min() { + return this.tps15Min.getAverage(); } + public RollingAverage duration5Sec() { + if (!this.durationSupported) { + return null; + } + return this.tickDuration5Sec; + } + + public RollingAverage duration10Sec() { + if (!this.durationSupported) { + return null; + } + return this.tickDuration10Sec; + } + + public RollingAverage duration1Min() { + if (!this.durationSupported) { + return null; + } + return this.tickDuration1Min; + } /** diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java index 81a757a..56093c0 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java @@ -27,6 +27,7 @@ import me.lucko.spark.common.sampler.aggregator.DataAggregator; import me.lucko.spark.common.sampler.aggregator.SimpleDataAggregator; import me.lucko.spark.common.sampler.aggregator.TickedDataAggregator; import me.lucko.spark.common.sampler.node.ThreadNode; +import me.lucko.spark.common.sampler.tick.TickHook; import me.lucko.spark.proto.SparkProtos.SamplerData; import me.lucko.spark.proto.SparkProtos.SamplerMetadata; @@ -86,9 +87,9 @@ public class Sampler implements Runnable { this.endTime = endTime; } - public Sampler(int interval, ThreadDumper threadDumper, ThreadGrouper threadGrouper, long endTime, boolean includeLineNumbers, boolean ignoreSleeping, TickCounter tickCounter, int tickLengthThreshold) { + public Sampler(int interval, ThreadDumper threadDumper, ThreadGrouper threadGrouper, long endTime, boolean includeLineNumbers, boolean ignoreSleeping, TickHook tickHook, int tickLengthThreshold) { this.threadDumper = threadDumper; - this.dataAggregator = new TickedDataAggregator(this.workerPool, threadGrouper, interval, includeLineNumbers, ignoreSleeping, tickCounter, tickLengthThreshold); + this.dataAggregator = new TickedDataAggregator(this.workerPool, threadGrouper, interval, includeLineNumbers, ignoreSleeping, tickHook, tickLengthThreshold); this.interval = interval; this.endTime = endTime; } diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java index 4808214..ae7fb89 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java @@ -20,6 +20,8 @@ package me.lucko.spark.common.sampler; +import me.lucko.spark.common.sampler.tick.TickHook; + import java.util.concurrent.TimeUnit; /** @@ -35,7 +37,7 @@ public class SamplerBuilder { private ThreadGrouper threadGrouper = ThreadGrouper.BY_NAME; private int ticksOver = -1; - private TickCounter tickCounter = null; + private TickHook tickHook = null; public SamplerBuilder() { } @@ -63,9 +65,9 @@ public class SamplerBuilder { return this; } - public SamplerBuilder ticksOver(int ticksOver, TickCounter tickCounter) { + public SamplerBuilder ticksOver(int ticksOver, TickHook tickHook) { this.ticksOver = ticksOver; - this.tickCounter = tickCounter; + this.tickHook = tickHook; return this; } @@ -83,10 +85,10 @@ public class SamplerBuilder { Sampler sampler; int intervalMicros = (int) (this.samplingInterval * 1000d); - if (this.ticksOver == -1 || this.tickCounter == null) { + if (this.ticksOver == -1 || this.tickHook == null) { sampler = new Sampler(intervalMicros, this.threadDumper, this.threadGrouper, this.timeout, this.includeLineNumbers, this.ignoreSleeping); } else { - sampler = new Sampler(intervalMicros, this.threadDumper, this.threadGrouper, this.timeout, this.includeLineNumbers, this.ignoreSleeping, this.tickCounter, this.ticksOver); + sampler = new Sampler(intervalMicros, this.threadDumper, this.threadGrouper, this.timeout, this.includeLineNumbers, this.ignoreSleeping, this.tickHook, this.ticksOver); } sampler.start(); diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/aggregator/TickedDataAggregator.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/aggregator/TickedDataAggregator.java index a4dfdb8..d8fda73 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/aggregator/TickedDataAggregator.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/aggregator/TickedDataAggregator.java @@ -21,8 +21,8 @@ package me.lucko.spark.common.sampler.aggregator; import me.lucko.spark.common.sampler.ThreadGrouper; -import me.lucko.spark.common.sampler.TickCounter; import me.lucko.spark.common.sampler.node.ThreadNode; +import me.lucko.spark.common.sampler.tick.TickHook; import me.lucko.spark.proto.SparkProtos.SamplerMetadata; import java.lang.management.ThreadInfo; @@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit; public class TickedDataAggregator extends AbstractDataAggregator { /** Used to monitor the current "tick" of the server */ - private final TickCounter tickCounter; + private final TickHook tickHook; /** Tick durations under this threshold will not be inserted, measured in microseconds */ private final long tickLengthThreshold; @@ -53,9 +53,9 @@ public class TickedDataAggregator extends AbstractDataAggregator { private int currentTick = -1; private TickList currentData = new TickList(0); - public TickedDataAggregator(ExecutorService workerPool, ThreadGrouper threadGrouper, int interval, boolean includeLineNumbers, boolean ignoreSleeping, TickCounter tickCounter, int tickLengthThreshold) { + public TickedDataAggregator(ExecutorService workerPool, ThreadGrouper threadGrouper, int interval, boolean includeLineNumbers, boolean ignoreSleeping, TickHook tickHook, int tickLengthThreshold) { super(workerPool, threadGrouper, interval, includeLineNumbers, ignoreSleeping); - this.tickCounter = tickCounter; + this.tickHook = tickHook; this.tickLengthThreshold = TimeUnit.MILLISECONDS.toMicros(tickLengthThreshold); // 50 millis in a tick, plus 10 so we have a bit of room to go over double intervalMilliseconds = interval / 1000d; @@ -74,7 +74,7 @@ public class TickedDataAggregator extends AbstractDataAggregator { @Override public void insertData(ThreadInfo threadInfo) { synchronized (this.mutex) { - int tick = this.tickCounter.getCurrentTick(); + int tick = this.tickHook.getCurrentTick(); if (this.currentTick != tick) { pushCurrentTick(); this.currentTick = tick; diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractTickCounter.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/AbstractTickHook.java index 4633024..b4e5054 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractTickCounter.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/AbstractTickHook.java @@ -18,18 +18,18 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package me.lucko.spark.common.sampler; +package me.lucko.spark.common.sampler.tick; import java.util.HashSet; import java.util.Set; -public abstract class AbstractTickCounter implements TickCounter { +public abstract class AbstractTickHook implements TickHook { - private final Set<TickTask> tasks = new HashSet<>(); + private final Set<Callback> tasks = new HashSet<>(); private int tick = 0; protected void onTick() { - for (TickTask r : this.tasks) { + for (Callback r : this.tasks) { r.onTick(this); } this.tick++; @@ -41,12 +41,12 @@ public abstract class AbstractTickCounter implements TickCounter { } @Override - public void addTickTask(TickTask runnable) { + public void addCallback(Callback runnable) { this.tasks.add(runnable); } @Override - public void removeTickTask(TickTask runnable) { + public void removeCallback(Callback runnable) { this.tasks.remove(runnable); } diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/AbstractTickReporter.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/AbstractTickReporter.java new file mode 100644 index 0000000..4005f87 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/AbstractTickReporter.java @@ -0,0 +1,45 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.common.sampler.tick; + +import java.util.HashSet; +import java.util.Set; + +public abstract class AbstractTickReporter implements TickReporter { + private final Set<Callback> tasks = new HashSet<>(); + + protected void onTick(double duration) { + for (Callback r : this.tasks) { + r.onTick(duration); + } + } + + @Override + public void addCallback(Callback runnable) { + this.tasks.add(runnable); + } + + @Override + public void removeCallback(Callback runnable) { + this.tasks.remove(runnable); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/TickCounter.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/TickHook.java index aa839ba..a0dd425 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/TickCounter.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/TickHook.java @@ -18,20 +18,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package me.lucko.spark.common.sampler; +package me.lucko.spark.common.sampler.tick; /** * A hook with the game's "tick loop". */ -public interface TickCounter extends AutoCloseable { +public interface TickHook extends AutoCloseable { /** - * Starts the counter + * Starts the hook */ void start(); /** - * Stops the counter + * Stops the hook */ @Override void close(); @@ -44,21 +44,21 @@ public interface TickCounter extends AutoCloseable { int getCurrentTick(); /** - * Adds a task to be called each time the tick increments + * Adds a callback to be called each time the tick increments * * @param runnable the task */ - void addTickTask(TickTask runnable); + void addCallback(Callback runnable); /** - * Removes a tick task + * Removes a callback * - * @param runnable the task + * @param runnable the callback */ - void removeTickTask(TickTask runnable); + void removeCallback(Callback runnable); - interface TickTask { - void onTick(TickCounter counter); + interface Callback { + void onTick(TickHook hook); } } diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/TickReporter.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/TickReporter.java new file mode 100644 index 0000000..e922e72 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/tick/TickReporter.java @@ -0,0 +1,57 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.common.sampler.tick; + +/** + * A reporting callback for the game's "tick loop". + */ +public interface TickReporter extends AutoCloseable { + + /** + * Starts the reporter + */ + void start(); + + /** + * Stops the reporter + */ + @Override + void close(); + + /** + * Adds a callback to be called each time the tick increments + * + * @param runnable the callback + */ + void addCallback(Callback runnable); + + /** + * Removes a callback + * + * @param runnable callback + */ + void removeCallback(Callback runnable); + + interface Callback { + void onTick(double duration); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java index 514876d..5cf5bb5 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java @@ -37,18 +37,46 @@ public class RollingAverage { } public void add(BigDecimal num) { - this.total = this.total.add(num); - this.samples.add(num); - if (this.samples.size() > this.size) { - this.total = this.total.subtract(this.samples.remove()); + synchronized (this) { + this.total = this.total.add(num); + this.samples.add(num); + if (this.samples.size() > this.size) { + this.total = this.total.subtract(this.samples.remove()); + } } } public double getAverage() { - if (this.samples.isEmpty()) { - return 0; + synchronized (this) { + if (this.samples.isEmpty()) { + return 0; + } + return this.total.divide(BigDecimal.valueOf(this.samples.size()), 30, RoundingMode.HALF_UP).doubleValue(); + } + } + + public double getMax() { + synchronized (this) { + BigDecimal max = BigDecimal.ZERO; + for (BigDecimal sample : this.samples) { + if (sample.compareTo(max) > 0) { + max = sample; + } + } + return max.doubleValue(); + } + } + + public double getMin() { + synchronized (this) { + BigDecimal min = BigDecimal.ZERO; + for (BigDecimal sample : this.samples) { + if (min == BigDecimal.ZERO || sample.compareTo(min) < 0) { + min = sample; + } + } + return min.doubleValue(); } - return this.total.divide(BigDecimal.valueOf(this.samples.size()), 30, RoundingMode.HALF_UP).doubleValue(); } } |