diff options
Diffstat (limited to 'spark-common/src/main/java/me')
8 files changed, 264 insertions, 102 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 dd55157..e99cb05 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 @@ -32,6 +32,7 @@ import me.lucko.spark.common.command.modules.SamplerModule; import me.lucko.spark.common.command.modules.TickMonitoringModule; 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.util.BytebinClient; @@ -95,6 +96,7 @@ public class SparkPlatform { this.tickCounter.addTickTask(this.tpsCalculator); this.tickCounter.start(); } + CpuMonitor.ensureMonitoring(); } public void disable() { diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java index c51fe2c..edb8537 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/ActivityLogModule.java @@ -68,10 +68,7 @@ public class ActivityLogModule implements CommandModule { .build() ); - TextComponent.Builder valueComponent = TextComponent.builder(activity.getDataValue()) - .color(TextColor.WHITE) - .decoration(TextDecoration.UNDERLINED, true); - + TextComponent.Builder valueComponent = TextComponent.builder(activity.getDataValue(), TextColor.WHITE); if (activity.getDataType().equals("url")) { valueComponent.clickEvent(ClickEvent.openUrl(activity.getDataValue())); } 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 f2ae6fc..9c139eb 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 @@ -54,7 +54,14 @@ public class HealthModule implements CommandModule { TpsCalculator tpsCalculator = platform.getTpsCalculator(); if (tpsCalculator != null) { resp.replyPrefixed(TextComponent.of("TPS from last 5s, 10s, 1m, 5m, 15m:")); - resp.replyPrefixed(TextComponent.builder(" ").append(tpsCalculator.toFormattedComponent()).build()); + 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())) + .build() + ); } else { resp.replyPrefixed(TextComponent.of("Not supported!")); } @@ -64,6 +71,29 @@ public class HealthModule implements CommandModule { ); consumer.accept(Command.builder() + .aliases("cpu") + .executor((platform, sender, resp, arguments) -> { + resp.replyPrefixed(TextComponent.of("CPU usage from last 10s, 1m, 15m:")); + resp.replyPrefixed(TextComponent.builder(" ") + .append(formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(TextComponent.of(", ")) + .append(formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(TextComponent.of(", ")) + .append(formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) + .append(TextComponent.of(" (system)", TextColor.DARK_GRAY)) + .build() + ); + resp.replyPrefixed(TextComponent.builder(" ") + .append(formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(TextComponent.of(", ")) + .append(formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(TextComponent.of(", ")) + .append(formatCpuUsage(CpuMonitor.processLoad15MinAvg())) + .append(TextComponent.of(" (process)", TextColor.DARK_GRAY)) + .build() + ); + }) + .tabCompleter(Command.TabCompleter.empty()) + .build() + ); + + consumer.accept(Command.builder() .aliases("healthreport", "health", "ht") .argumentUsage("memory", null) .executor((platform, sender, resp, arguments) -> { @@ -80,10 +110,39 @@ public class HealthModule implements CommandModule { .append(TextComponent.of("TPS from last 5s, 10s, 1m, 5m, 15m:", TextColor.GOLD)) .build() ); - report.add(TextComponent.builder(" ").append(tpsCalculator.toFormattedComponent()).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())) + .build() + ); report.add(TextComponent.empty()); } + report.add(TextComponent.builder("") + .append(TextComponent.builder(">").color(TextColor.DARK_GRAY).decoration(TextDecoration.BOLD, true).build()) + .append(TextComponent.space()) + .append(TextComponent.of("CPU usage from last 10s, 1m, 15m:", TextColor.GOLD)) + .build() + ); + report.add(TextComponent.builder(" ") + .append(formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(TextComponent.of(", ")) + .append(formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(TextComponent.of(", ")) + .append(formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) + .append(TextComponent.of(" (system)", TextColor.DARK_GRAY)) + .build() + ); + report.add(TextComponent.builder(" ") + .append(formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(TextComponent.of(", ")) + .append(formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(TextComponent.of(", ")) + .append(formatCpuUsage(CpuMonitor.processLoad15MinAvg())) + .append(TextComponent.of(" (process)", TextColor.DARK_GRAY)) + .build() + ); + report.add(TextComponent.empty()); + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage(); report.add(TextComponent.builder("") @@ -168,37 +227,6 @@ public class HealthModule implements CommandModule { } } - double systemCpuLoad = CpuMonitor.getSystemCpuLoad(); - double processCpuLoad = CpuMonitor.getProcessCpuLoad(); - - if (systemCpuLoad >= 0 || processCpuLoad >= 0) { - report.add(TextComponent.builder("") - .append(TextComponent.builder(">").color(TextColor.DARK_GRAY).decoration(TextDecoration.BOLD, true).build()) - .append(TextComponent.space()) - .append(TextComponent.of("CPU usage:", TextColor.GOLD)) - .build() - ); - - if (systemCpuLoad >= 0) { - report.add(TextComponent.builder(" ") - .append(TextComponent.of("System: ", TextColor.GRAY)) - .append(TextComponent.of(percent(systemCpuLoad, 1.0d), TextColor.GREEN)) - .build() - ); - report.add(TextComponent.builder(" ").append(generateCpuUsageDiagram(systemCpuLoad, 40)).build()); - report.add(TextComponent.empty()); - } - if (processCpuLoad >= 0) { - report.add(TextComponent.builder(" ") - .append(TextComponent.of("Process: ", TextColor.GRAY)) - .append(TextComponent.of(percent(processCpuLoad, 1.0d), TextColor.GREEN)) - .build() - ); - report.add(TextComponent.builder(" ").append(generateCpuUsageDiagram(processCpuLoad, 40)).build()); - report.add(TextComponent.empty()); - } - } - try { FileStore fileStore = Files.getFileStore(Paths.get(".")); long totalSpace = fileStore.getTotalSpace(); @@ -242,6 +270,32 @@ public class HealthModule implements CommandModule { return (int) percent + "%"; } + private static TextComponent formatTps(double tps) { + TextColor color; + if (tps > 18.0) { + color = TextColor.GREEN; + } else if (tps > 16.0) { + color = TextColor.YELLOW; + } else { + color = TextColor.RED; + } + + return TextComponent.of( (tps > 20.0 ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0), color); + } + + private static TextComponent formatCpuUsage(double usage) { + TextColor color; + if (usage > 0.9) { + color = TextColor.RED; + } else if (usage > 0.65) { + color = TextColor.YELLOW; + } else { + color = TextColor.GREEN; + } + + return TextComponent.of(percent(usage, 1d), color); + } + private static TextComponent generateMemoryUsageDiagram(MemoryUsage usage, int length) { double used = usage.getUsed(); double committed = usage.getCommitted(); @@ -300,16 +354,6 @@ public class HealthModule implements CommandModule { .build(); } - private static TextComponent generateCpuUsageDiagram(double usage, int length) { - int usedChars = (int) ((usage * length)); - String line = Strings.repeat("/", usedChars) + Strings.repeat(" ", length - usedChars); - return TextComponent.builder("") - .append(TextComponent.of("[", TextColor.DARK_GRAY)) - .append(TextComponent.of(line, TextColor.GRAY)) - .append(TextComponent.of("]", TextColor.DARK_GRAY)) - .build(); - } - private static TextComponent generateDiskUsageDiagram(double used, double max, int length) { int usedChars = (int) ((used * length) / max); String line = Strings.repeat("/", usedChars) + Strings.repeat(" ", length - usedChars); diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java index 42c03d0..f5854f0 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/MemoryModule.java @@ -65,7 +65,7 @@ public class MemoryModule implements CommandModule { return; } - byte[] output = heapDump.formCompressedDataPayload(); + byte[] output = heapDump.formCompressedDataPayload(sender); try { String key = SparkPlatform.BYTEBIN_CLIENT.postContent(output, JSON_TYPE, false).key(); String url = SparkPlatform.VIEWER_URL + key; @@ -120,7 +120,7 @@ public class MemoryModule implements CommandModule { } resp.broadcastPrefixed(TextComponent.builder("Heap dump written to: ", TextColor.GOLD) - .append(TextComponent.of(file.toString(), TextColor.DARK_GRAY)) + .append(TextComponent.of(file.toString(), TextColor.GRAY)) .build() ); platform.getActivityLog().addToLog(Activity.fileActivity(sender, System.currentTimeMillis(), "Heap dump", file.toString())); 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 4ea6144..4b7baae 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 @@ -228,8 +228,9 @@ public class SamplerModule implements CommandModule { return Collections.emptyList(); } - List<String> opts = new ArrayList<>(Arrays.asList("--timeout", "--regex", "--combine-all", - "--not-combined", "--interval", "--only-ticks-over", "--include-line-numbers")); + List<String> opts = new ArrayList<>(Arrays.asList("--info", "--stop", "--cancel", + "--timeout", "--regex", "--combine-all", "--not-combined", "--interval", + "--only-ticks-over", "--include-line-numbers")); opts.removeAll(arguments); opts.add("--thread"); // allowed multiple times @@ -243,7 +244,7 @@ public class SamplerModule implements CommandModule { private void handleUpload(SparkPlatform platform, CommandResponseHandler resp, Sampler sampler) { platform.getPlugin().runAsync(() -> { - byte[] output = sampler.formCompressedDataPayload(); + byte[] output = sampler.formCompressedDataPayload(resp.sender()); try { String key = SparkPlatform.BYTEBIN_CLIENT.postContent(output, JSON_TYPE, false).key(); String url = SparkPlatform.VIEWER_URL + key; diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/cpu/CpuMonitor.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/cpu/CpuMonitor.java index 20d71ef..f4190e5 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/monitor/cpu/CpuMonitor.java +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/cpu/CpuMonitor.java @@ -20,25 +20,60 @@ package me.lucko.spark.common.monitor.cpu; +import me.lucko.spark.common.util.RollingAverage; + import javax.management.JMX; import javax.management.MBeanServer; import javax.management.ObjectName; import java.lang.management.ManagementFactory; +import java.math.BigDecimal; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +/** + * Exposes and monitors the system/process CPU usage. + */ public final class CpuMonitor { private CpuMonitor() {} /** The object name of the com.sun.management.OperatingSystemMXBean */ private static final String OPERATING_SYSTEM_BEAN = "java.lang:type=OperatingSystem"; + /** The OperatingSystemMXBean instance */ + private static final OperatingSystemMXBean BEAN; + /** The executor used to monitor & calculate rolling averages. */ + private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(r -> { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setName("spark-cpu-monitor"); + return thread; + }); - private static OperatingSystemMXBean getBean() { + // Rolling averages for system/process data + private static final RollingAverage SYSTEM_AVERAGE_10_SEC = new RollingAverage(10); + private static final RollingAverage SYSTEM_AVERAGE_1_MIN = new RollingAverage(60); + private static final RollingAverage SYSTEM_AVERAGE_15_MIN = new RollingAverage(60 * 15); + private static final RollingAverage PROCESS_AVERAGE_10_SEC = new RollingAverage(10); + private static final RollingAverage PROCESS_AVERAGE_1_MIN = new RollingAverage(60); + private static final RollingAverage PROCESS_AVERAGE_15_MIN = new RollingAverage(60 * 15); + + static { try { MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer(); ObjectName diagnosticBeanName = ObjectName.getInstance(OPERATING_SYSTEM_BEAN); - return JMX.newMXBeanProxy(beanServer, diagnosticBeanName, OperatingSystemMXBean.class); + BEAN = JMX.newMXBeanProxy(beanServer, diagnosticBeanName, OperatingSystemMXBean.class); } catch (Exception e) { - throw new RuntimeException(e); + throw new UnsupportedOperationException("OperatingSystemMXBean is not supported by the system", e); } + + // schedule rolling average calculations. + EXECUTOR.scheduleAtFixedRate(new RollingAverageCollectionTask(), 1, 1, TimeUnit.SECONDS); + } + + /** + * Ensures that the static initializer has been called. + */ + public static void ensureMonitoring() { + // intentionally empty } /** @@ -54,8 +89,20 @@ public final class CpuMonitor { * @return the "recent cpu usage" for the whole system; a negative * value if not available. */ - public static double getSystemCpuLoad() { - return getBean().getSystemCpuLoad(); + public static double systemLoad() { + return BEAN.getSystemCpuLoad(); + } + + public static double systemLoad10SecAvg() { + return SYSTEM_AVERAGE_10_SEC.getAverage(); + } + + public static double systemLoad1MinAvg() { + return SYSTEM_AVERAGE_1_MIN.getAverage(); + } + + public static double systemLoad15MinAvg() { + return SYSTEM_AVERAGE_15_MIN.getAverage(); } /** @@ -73,8 +120,49 @@ public final class CpuMonitor { * @return the "recent cpu usage" for the Java Virtual Machine process; * a negative value if not available. */ - public static double getProcessCpuLoad() { - return getBean().getProcessCpuLoad(); + public static double processLoad() { + return BEAN.getProcessCpuLoad(); + } + + public static double processLoad10SecAvg() { + return PROCESS_AVERAGE_10_SEC.getAverage(); + } + + public static double processLoad1MinAvg() { + return PROCESS_AVERAGE_1_MIN.getAverage(); + } + + public static double processLoad15MinAvg() { + return PROCESS_AVERAGE_15_MIN.getAverage(); + } + + /** + * Task to poll CPU loads and add to the rolling averages in the enclosing class. + */ + private static final class RollingAverageCollectionTask implements Runnable { + private final RollingAverage[] systemAverages = new RollingAverage[]{ + SYSTEM_AVERAGE_10_SEC, + SYSTEM_AVERAGE_1_MIN, + SYSTEM_AVERAGE_15_MIN + }; + private final RollingAverage[] processAverages = new RollingAverage[]{ + PROCESS_AVERAGE_10_SEC, + PROCESS_AVERAGE_1_MIN, + PROCESS_AVERAGE_15_MIN + }; + + @Override + public void run() { + BigDecimal systemCpuLoad = new BigDecimal(systemLoad()); + BigDecimal processCpuLoad = new BigDecimal(processLoad()); + + for (RollingAverage average : this.systemAverages) { + average.add(systemCpuLoad); + } + for (RollingAverage average : this.processAverages) { + average.add(processCpuLoad); + } + } } public interface OperatingSystemMXBean { 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/TpsCalculator.java index 2278e0c..3000523 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/TpsCalculator.java @@ -21,8 +21,6 @@ package me.lucko.spark.common.monitor.tick; import me.lucko.spark.common.sampler.TickCounter; -import net.kyori.text.TextComponent; -import net.kyori.text.format.TextColor; import java.math.BigDecimal; import java.math.RoundingMode; @@ -46,14 +44,14 @@ public class TpsCalculator implements TickCounter.TickTask { private static final int SAMPLE_INTERVAL = 20; private static final BigDecimal TPS_BASE = new BigDecimal(SEC_IN_NANO).multiply(new BigDecimal((long) SAMPLE_INTERVAL)); - private final RollingAverage tps5S = new RollingAverage(5); - private final RollingAverage tps10S = new RollingAverage(10); - private final RollingAverage tps1M = new RollingAverage(60); - private final RollingAverage tps5M = new RollingAverage(60 * 5); - private final RollingAverage tps15M = new RollingAverage(60 * 15); + 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 RollingAverage[] averages = new RollingAverage[]{ - this.tps5S, this.tps10S, this.tps1M, this.tps5M, this.tps15M + private final TpsRollingAverage[] averages = new TpsRollingAverage[]{ + this.avg5Sec, this.avg10Sec, this.avg1Min, this.avg5Min, this.avg15Min }; private long last = 0; @@ -75,55 +73,34 @@ public class TpsCalculator implements TickCounter.TickTask { long diff = now - this.last; BigDecimal currentTps = TPS_BASE.divide(new BigDecimal(diff), 30, RoundingMode.HALF_UP); - for (RollingAverage rollingAverage : this.averages) { + for (TpsRollingAverage rollingAverage : this.averages) { rollingAverage.add(currentTps, diff); } this.last = now; } - public RollingAverage avg5Sec() { - return this.tps5S; + public double avg5Sec() { + return this.avg5Sec.getAverage(); } - public RollingAverage avg10Sec() { - return this.tps10S; + public double avg10Sec() { + return this.avg10Sec.getAverage(); } - public RollingAverage avg1Min() { - return this.tps1M; + public double avg1Min() { + return this.avg1Min.getAverage(); } - public RollingAverage avg5Min() { - return this.tps5M; + public double avg5Min() { + return this.avg5Min.getAverage(); } - public RollingAverage avg15Min() { - return this.tps15M; + public double avg15Min() { + return this.avg15Min.getAverage(); } - public TextComponent toFormattedComponent() { - return TextComponent.builder("") - .append(format(this.tps5S.getAverage())).append(TextComponent.of(", ")) - .append(format(this.tps10S.getAverage())).append(TextComponent.of(", ")) - .append(format(this.tps1M.getAverage())).append(TextComponent.of(", ")) - .append(format(this.tps5M.getAverage())).append(TextComponent.of(", ")) - .append(format(this.tps15M.getAverage())) - .build(); - } - - private static TextComponent format(double tps) { - TextColor color; - if (tps > 18.0) { - color = TextColor.GREEN; - } else if (tps > 16.0) { - color = TextColor.YELLOW; - } else { - color = TextColor.RED; - } - return TextComponent.of( (tps > 20.0 ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0), color); - } /** * Rolling average calculator taken. @@ -132,7 +109,7 @@ public class TpsCalculator implements TickCounter.TickTask { * * @author aikar (PaperMC) https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0021-Further-improve-server-tick-loop.patch */ - public static final class RollingAverage { + public static final class TpsRollingAverage { private final int size; private long time; private BigDecimal total; @@ -140,7 +117,7 @@ public class TpsCalculator implements TickCounter.TickTask { private final BigDecimal[] samples; private final long[] times; - RollingAverage(int size) { + TpsRollingAverage(int size) { this.size = size; this.time = size * SEC_IN_NANO; this.total = new BigDecimal((long) TPS).multiply(new BigDecimal(SEC_IN_NANO)).multiply(new BigDecimal((long) size)); 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 new file mode 100644 index 0000000..5b1fd21 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java @@ -0,0 +1,53 @@ +/* + * 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.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayDeque; +import java.util.Queue; + +public class RollingAverage { + + private final Queue<BigDecimal> samples = new ArrayDeque<>(); + private final int size; + private BigDecimal total = BigDecimal.ZERO; + + public RollingAverage(int size) { + this.size = size; + } + + 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()); + } + } + + public double getAverage() { + if (this.samples.isEmpty()) { + return 0; + } + return this.total.divide(BigDecimal.valueOf(this.samples.size()), 30, RoundingMode.HALF_UP).doubleValue(); + } + +} |