diff options
author | Luck <git@lucko.me> | 2022-04-18 11:54:40 +0100 |
---|---|---|
committer | Luck <git@lucko.me> | 2022-04-18 11:54:40 +0100 |
commit | 45edd6197aa777d0c746f37a813d6be4ca916694 (patch) | |
tree | 967c3998eb21a363e9896654dccd86e70e0368b0 /spark-common/src/main/java/me | |
parent | ecc9ac3fdb731ef3beee2ce45ddf4b355b12745f (diff) | |
download | spark-45edd6197aa777d0c746f37a813d6be4ca916694.tar.gz spark-45edd6197aa777d0c746f37a813d6be4ca916694.tar.bz2 spark-45edd6197aa777d0c746f37a813d6be4ca916694.zip |
Include network interface statistics in '/spark health' command
Diffstat (limited to 'spark-common/src/main/java/me')
12 files changed, 651 insertions, 19 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 a087fc9..a961925 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 @@ -40,6 +40,7 @@ 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.memory.GarbageCollectorStatistics; +import me.lucko.spark.common.monitor.net.NetworkMonitor; import me.lucko.spark.common.monitor.ping.PingStatistics; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.monitor.tick.TickStatistics; @@ -166,6 +167,7 @@ public class SparkPlatform { this.pingStatistics.start(); } CpuMonitor.ensureMonitoring(); + NetworkMonitor.ensureMonitoring(); // poll startup GC statistics after plugins & the world have loaded this.plugin.executeAsync(() -> { 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 ea4f140..ee3592a 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 @@ -29,6 +29,9 @@ import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.disk.DiskUsage; +import me.lucko.spark.common.monitor.net.Direction; +import me.lucko.spark.common.monitor.net.NetworkInterfaceAverages; +import me.lucko.spark.common.monitor.net.NetworkMonitor; import me.lucko.spark.common.monitor.ping.PingStatistics; import me.lucko.spark.common.monitor.ping.PingSummary; import me.lucko.spark.common.monitor.tick.TickStatistics; @@ -45,6 +48,7 @@ import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -81,8 +85,9 @@ public class HealthModule implements CommandModule { consumer.accept(Command.builder() .aliases("healthreport", "health", "ht") .argumentUsage("memory", null) + .argumentUsage("network", null) .executor(HealthModule::healthReport) - .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--memory")) + .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--memory", "--network")) .build() ); } @@ -197,6 +202,8 @@ public class HealthModule implements CommandModule { addDetailedMemoryStats(report, memoryMXBean); } + addNetworkStats(report, arguments.boolFlag("network")); + addDiskStats(report); resp.reply(report); @@ -352,6 +359,48 @@ public class HealthModule implements CommandModule { } } + private static void addNetworkStats(List<Component> report, boolean detailed) { + List<Component> averagesReport = new LinkedList<>(); + + for (Map.Entry<String, NetworkInterfaceAverages> ent : NetworkMonitor.systemAverages().entrySet()) { + String interfaceName = ent.getKey(); + NetworkInterfaceAverages averages = ent.getValue(); + + for (Direction direction : Direction.values()) { + long bytesPerSec = (long) averages.bytesPerSecond(direction).mean(); + long packetsPerSec = (long) averages.packetsPerSecond(direction).mean(); + + if (detailed || bytesPerSec > 0 || packetsPerSec > 0) { + averagesReport.add(text() + .color(GRAY) + .content(" ") + .append(FormatUtil.formatBytes(bytesPerSec, GREEN, "/s")) + .append(text(" / ")) + .append(text(String.format("%,d", packetsPerSec), WHITE)) + .append(text(" pps ")) + .append(text().color(DARK_GRAY) + .append(text('(')) + .append(text(interfaceName + " " + direction.abbrev(), WHITE)) + .append(text(')')) + ) + .build() + ); + } + } + } + + if (!averagesReport.isEmpty()) { + report.add(text() + .append(text(">", DARK_GRAY, BOLD)) + .append(space()) + .append(text("Network usage: (system, last 15m)", GOLD)) + .build() + ); + report.addAll(averagesReport); + report.add(empty()); + } + } + private static void addDiskStats(List<Component> report) { long total = DiskUsage.getTotal(); long used = DiskUsage.getUsed(); diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/Direction.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/Direction.java new file mode 100644 index 0000000..d4d11ff --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/Direction.java @@ -0,0 +1,37 @@ +/* + * 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.monitor.net; + +public enum Direction { + + RECEIVE("rx"), + TRANSMIT("tx"); + + private final String abbreviation; + + Direction(String abbreviation) { + this.abbreviation = abbreviation; + } + + public String abbrev() { + return this.abbreviation; + } +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceAverages.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceAverages.java new file mode 100644 index 0000000..0ce0639 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceAverages.java @@ -0,0 +1,88 @@ +/* + * 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.monitor.net; + +import me.lucko.spark.common.util.RollingAverage; + +import java.math.BigDecimal; + +public final class NetworkInterfaceAverages { + private final RollingAverage rxBytesPerSecond; + private final RollingAverage txBytesPerSecond; + private final RollingAverage rxPacketsPerSecond; + private final RollingAverage txPacketsPerSecond; + + NetworkInterfaceAverages(int windowSize) { + this.rxBytesPerSecond = new RollingAverage(windowSize); + this.txBytesPerSecond = new RollingAverage(windowSize); + this.rxPacketsPerSecond = new RollingAverage(windowSize); + this.txPacketsPerSecond = new RollingAverage(windowSize); + } + + void accept(NetworkInterfaceInfo info, RateCalculator rateCalculator) { + this.rxBytesPerSecond.add(rateCalculator.calculate(info.getReceivedBytes())); + this.txBytesPerSecond.add(rateCalculator.calculate(info.getTransmittedBytes())); + this.rxPacketsPerSecond.add(rateCalculator.calculate(info.getReceivedPackets())); + this.txPacketsPerSecond.add(rateCalculator.calculate(info.getTransmittedPackets())); + } + + interface RateCalculator { + BigDecimal calculate(long value); + } + + public RollingAverage bytesPerSecond(Direction direction) { + switch (direction) { + case RECEIVE: + return rxBytesPerSecond(); + case TRANSMIT: + return txBytesPerSecond(); + default: + throw new AssertionError(); + } + } + + public RollingAverage packetsPerSecond(Direction direction) { + switch (direction) { + case RECEIVE: + return rxPacketsPerSecond(); + case TRANSMIT: + return txPacketsPerSecond(); + default: + throw new AssertionError(); + } + } + + public RollingAverage rxBytesPerSecond() { + return this.rxBytesPerSecond; + } + + public RollingAverage rxPacketsPerSecond() { + return this.rxPacketsPerSecond; + } + + public RollingAverage txBytesPerSecond() { + return this.txBytesPerSecond; + } + + public RollingAverage txPacketsPerSecond() { + return this.txPacketsPerSecond; + } +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java new file mode 100644 index 0000000..6ec700b --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java @@ -0,0 +1,274 @@ +/* + * 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.monitor.net; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.util.LinuxProc; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +/** + * Exposes information/statistics about a network interface. + */ +public final class NetworkInterfaceInfo { + public static final NetworkInterfaceInfo ZERO = new NetworkInterfaceInfo("", 0, 0, 0, 0, 0, 0); + + private final String name; + private final long rxBytes; + private final long rxPackets; + private final long rxErrors; + private final long txBytes; + private final long txPackets; + private final long txErrors; + + public NetworkInterfaceInfo(String name, long rxBytes, long rxPackets, long rxErrors, long txBytes, long txPackets, long txErrors) { + this.name = name; + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.rxErrors = rxErrors; + this.txBytes = txBytes; + this.txPackets = txPackets; + this.txErrors = txErrors; + } + + /** + * Gets the name of the network interface. + * + * @return the interface name + */ + public String getName() { + return this.name; + } + + /** + * Gets the total number of bytes of data received by the interface. + * + * @return the total received bytes + */ + public long getReceivedBytes() { + return this.rxBytes; + } + + /** + * Gets the total number of packets of data received by the interface. + * + * @return the total received packets + */ + public long getReceivedPackets() { + return this.rxPackets; + } + + /** + * Gets the total number of receive errors detected by the device driver. + * + * @return the total receive errors + */ + public long getReceiveErrors() { + return this.rxErrors; + } + + /** + * Gets the total number of bytes of data transmitted by the interface. + * + * @return the total transmitted bytes + */ + public long getTransmittedBytes() { + return this.txBytes; + } + + /** + * Gets the total number of packets of data transmitted by the interface. + * + * @return the total transmitted packets + */ + public long getTransmittedPackets() { + return this.txPackets; + } + + /** + * Gets the total number of transmit errors detected by the device driver. + * + * @return the total transmit errors + */ + public long getTransmitErrors() { + return this.txErrors; + } + + public long getBytes(Direction direction) { + switch (direction) { + case RECEIVE: + return getReceivedBytes(); + case TRANSMIT: + return getTransmittedBytes(); + default: + throw new AssertionError(); + } + } + + public long getPackets(Direction direction) { + switch (direction) { + case RECEIVE: + return getReceivedPackets(); + case TRANSMIT: + return getTransmittedPackets(); + default: + throw new AssertionError(); + } + } + + public boolean isZero() { + return this.rxBytes == 0 && this.rxPackets == 0 && this.rxErrors == 0 && + this.txBytes == 0 && this.txPackets == 0 && this.txErrors == 0; + } + + public NetworkInterfaceInfo subtract(NetworkInterfaceInfo other) { + if (other == ZERO || other.isZero()) { + return this; + } + + return new NetworkInterfaceInfo( + this.name, + this.rxBytes - other.rxBytes, + this.rxPackets - other.rxPackets, + this.rxErrors - other.rxErrors, + this.txBytes - other.txBytes, + this.txPackets - other.txPackets, + this.txErrors - other.txErrors + ); + } + + /** + * Calculate the difference between two readings in order to calculate the rate. + * + * @param current the polled values + * @param previous the previously polled values + * @return the difference + */ + public static @NonNull Map<String, NetworkInterfaceInfo> difference(Map<String, NetworkInterfaceInfo> current, Map<String, NetworkInterfaceInfo> previous) { + if (previous == null || previous.isEmpty()) { + return current; + } + + ImmutableMap.Builder<String, NetworkInterfaceInfo> builder = ImmutableMap.builder(); + for (NetworkInterfaceInfo netInf : current.values()) { + String name = netInf.getName(); + builder.put(name, netInf.subtract(previous.getOrDefault(name, ZERO))); + } + return builder.build(); + } + + /** + * Queries the network interface statistics for the system. + * + * <p>Returns an empty {@link Map} if no statistics could be gathered.</p> + * + * @return the system net stats + */ + public static @NonNull Map<String, NetworkInterfaceInfo> pollSystem() { + try { + List<String> output = LinuxProc.NET_DEV.read(); + return read(output); + } catch (Exception e) { + return Collections.emptyMap(); + } + } + + private static final Pattern PROC_NET_DEV_PATTERN = Pattern.compile("^\\s+(\\w+):([\\d\\s]+)$"); + + private static @NonNull Map<String, NetworkInterfaceInfo> read(List<String> output) { + // Inter-| Receive | Transmit + // face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + // lo: 2776770 11307 0 0 0 0 0 0 2776770 11307 0 0 0 0 0 0 + // eth0: 1215645 2751 0 0 0 0 0 0 1782404 4324 0 0 0 427 0 0 + // ppp0: 1622270 5552 1 0 0 0 0 0 354130 5669 0 0 0 0 0 0 + // tap0: 7714 81 0 0 0 0 0 0 7714 81 0 0 0 0 0 0 + + if (output.size() < 3) { + // no data + return Collections.emptyMap(); + } + + String header = output.get(1); + String[] categories = header.split("\\|"); + if (categories.length != 3) { + // unknown format + return Collections.emptyMap(); + } + + List<String> rxFields = Arrays.asList(categories[1].trim().split("\\s+")); + List<String> txFields = Arrays.asList(categories[2].trim().split("\\s+")); + + int rxFieldsLength = rxFields.size(); + int txFieldsLength = txFields.size(); + + int fieldRxBytes = rxFields.indexOf("bytes"); + int fieldRxPackets = rxFields.indexOf("packets"); + int fieldRxErrors = rxFields.indexOf("errs"); + + int fieldTxBytes = rxFieldsLength + txFields.indexOf("bytes"); + int fieldTxPackets = rxFieldsLength + txFields.indexOf("packets"); + int fieldTxErrors = rxFieldsLength + txFields.indexOf("errs"); + + int expectedFields = rxFieldsLength + txFieldsLength; + + if (IntStream.of(fieldRxBytes, fieldRxPackets, fieldRxErrors, fieldTxBytes, fieldTxPackets, fieldTxErrors).anyMatch(i -> i == -1)) { + // missing required fields + return Collections.emptyMap(); + } + + ImmutableMap.Builder<String, NetworkInterfaceInfo> builder = ImmutableMap.builder(); + + for (String line : output.subList(2, output.size())) { + Matcher matcher = PROC_NET_DEV_PATTERN.matcher(line); + if (matcher.matches()) { + String interfaceName = matcher.group(1); + String[] stringValues = matcher.group(2).trim().split("\\s+"); + + if (stringValues.length != expectedFields) { + continue; + } + + long[] values = Arrays.stream(stringValues).mapToLong(Long::parseLong).toArray(); + builder.put(interfaceName, new NetworkInterfaceInfo( + interfaceName, + values[fieldRxBytes], + values[fieldRxPackets], + values[fieldRxErrors], + values[fieldTxBytes], + values[fieldTxPackets], + values[fieldTxErrors] + )); + } + } + + return builder.build(); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkMonitor.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkMonitor.java new file mode 100644 index 0000000..dadd4e5 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkMonitor.java @@ -0,0 +1,140 @@ +/* + * 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.monitor.net; + +import me.lucko.spark.common.monitor.MonitoringExecutor; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +/** + * Exposes and monitors the system/process network usage. + */ +public enum NetworkMonitor { + ; + + // Latest readings + private static final AtomicReference<Map<String, NetworkInterfaceInfo>> SYSTEM = new AtomicReference<>(); + + // a pattern to match the interface names to calculate rolling averages for + private static final Pattern INTERFACES_TO_MONITOR = Pattern.compile("^(eth\\d+)|(lo)$"); + + // Rolling averages for system/process data over 15 mins + private static final Map<String, NetworkInterfaceAverages> SYSTEM_AVERAGES = new ConcurrentHashMap<>(); + + // poll every minute, keep rolling averages for 15 mins + private static final int POLL_INTERVAL = 60; + private static final int WINDOW_SIZE_SECONDS = (int) TimeUnit.MINUTES.toSeconds(15); + private static final int WINDOW_SIZE = WINDOW_SIZE_SECONDS / POLL_INTERVAL; // 15 + + static { + // schedule rolling average calculations. + MonitoringExecutor.INSTANCE.scheduleAtFixedRate(new RollingAverageCollectionTask(), 1, POLL_INTERVAL, TimeUnit.SECONDS); + } + + /** + * Ensures that the static initializer has been called. + */ + @SuppressWarnings("EmptyMethod") + public static void ensureMonitoring() { + // intentionally empty + } + + public static Map<String, NetworkInterfaceInfo> systemTotals() { + Map<String, NetworkInterfaceInfo> values = SYSTEM.get(); + return values == null ? Collections.emptyMap() : values; + } + + public static Map<String, NetworkInterfaceAverages> systemAverages() { + return Collections.unmodifiableMap(SYSTEM_AVERAGES); + } + + /** + * Task to poll network activity and add to the rolling averages in the enclosing class. + */ + private static final class RollingAverageCollectionTask implements Runnable { + private static final BigDecimal POLL_INTERVAL_DECIMAL = BigDecimal.valueOf(POLL_INTERVAL); + + @Override + public void run() { + Map<String, NetworkInterfaceInfo> values = pollAndDiff(NetworkInterfaceInfo::pollSystem, SYSTEM); + if (values != null) { + submit(SYSTEM_AVERAGES, values); + } + } + + /** + * Submits the incoming values into the rolling averages map. + * + * @param values the values + */ + private static void submit(Map<String, NetworkInterfaceAverages> rollingAveragesMap, Map<String, NetworkInterfaceInfo> values) { + // ensure all incoming keys are present in the rolling averages map + for (String key : values.keySet()) { + if (INTERFACES_TO_MONITOR.matcher(key).matches()) { + rollingAveragesMap.computeIfAbsent(key, k -> new NetworkInterfaceAverages(WINDOW_SIZE)); + } + } + + // submit a value (0 if unknown) to each rolling average instance in the map + for (Map.Entry<String, NetworkInterfaceAverages> entry : rollingAveragesMap.entrySet()) { + String interfaceName = entry.getKey(); + NetworkInterfaceAverages rollingAvgs = entry.getValue(); + + NetworkInterfaceInfo info = values.getOrDefault(interfaceName, NetworkInterfaceInfo.ZERO); + rollingAvgs.accept(info, RollingAverageCollectionTask::calculateRate); + } + } + + private static BigDecimal calculateRate(long value) { + return BigDecimal.valueOf(value).divide(POLL_INTERVAL_DECIMAL, RoundingMode.HALF_UP); + } + + private static Map<String, NetworkInterfaceInfo> pollAndDiff(Supplier<Map<String, NetworkInterfaceInfo>> poller, AtomicReference<Map<String, NetworkInterfaceInfo>> valueReference) { + // poll the latest value from the supplier + Map<String, NetworkInterfaceInfo> latest = poller.get(); + + // update the value ref. + // if the previous value was null, and the new value is empty, keep it null + Map<String, NetworkInterfaceInfo> previous = valueReference.getAndUpdate(prev -> { + if (prev == null && latest.isEmpty()) { + return null; + } else { + return latest; + } + }); + + if (previous == null) { + return null; + } + + return NetworkInterfaceInfo.difference(latest, previous); + } + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java index 8b5b5b3..49fcbe1 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java @@ -34,13 +34,14 @@ import java.util.concurrent.TimeUnit; * Provides statistics for player ping RTT to the server. */ public final class PingStatistics implements Runnable, AutoCloseable { - private static final int WINDOW_SIZE_SECONDS = (int) TimeUnit.MINUTES.toSeconds(15); private static final int QUERY_RATE_SECONDS = 10; + private static final int WINDOW_SIZE_SECONDS = (int) TimeUnit.MINUTES.toSeconds(15); // 900 + private static final int WINDOW_SIZE = WINDOW_SIZE_SECONDS / QUERY_RATE_SECONDS; // 90 /** The platform function that provides player ping times */ private final PlayerPingProvider provider; /** Rolling average of the median ping across all players */ - private final RollingAverage rollingAverage = new RollingAverage(WINDOW_SIZE_SECONDS / QUERY_RATE_SECONDS); + private final RollingAverage rollingAverage = new RollingAverage(WINDOW_SIZE); /** The scheduler task that polls pings and calculates the rolling average */ private ScheduledFuture<?> future; diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java index 5b8d1d4..046def7 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java @@ -26,9 +26,10 @@ import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.disk.DiskUsage; import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics; import me.lucko.spark.common.monitor.memory.MemoryInfo; +import me.lucko.spark.common.monitor.net.NetworkInterfaceAverages; +import me.lucko.spark.common.monitor.net.NetworkMonitor; import me.lucko.spark.common.monitor.ping.PingStatistics; import me.lucko.spark.common.monitor.tick.TickStatistics; -import me.lucko.spark.common.util.RollingAverage; import me.lucko.spark.proto.SparkProtos.PlatformStatistics; import me.lucko.spark.proto.SparkProtos.SystemStatistics; @@ -108,6 +109,17 @@ public class PlatformStatisticsProvider { .build() )); + Map<String, NetworkInterfaceAverages> networkInterfaceStats = NetworkMonitor.systemAverages(); + networkInterfaceStats.forEach((name, statistics) -> builder.putNet( + name, + SystemStatistics.NetInterface.newBuilder() + .setRxBytesPerSecond(statistics.rxBytesPerSecond().toProto()) + .setRxPacketsPerSecond(statistics.rxPacketsPerSecond().toProto()) + .setTxBytesPerSecond(statistics.txBytesPerSecond().toProto()) + .setTxPacketsPerSecond(statistics.txPacketsPerSecond().toProto()) + .build() + )); + return builder.build(); } @@ -149,8 +161,8 @@ public class PlatformStatisticsProvider { ); if (tickStatistics.isDurationSupported()) { builder.setMspt(PlatformStatistics.Mspt.newBuilder() - .setLast1M(rollingAverageValues(tickStatistics.duration1Min())) - .setLast5M(rollingAverageValues(tickStatistics.duration5Min())) + .setLast1M(tickStatistics.duration1Min().toProto()) + .setLast5M(tickStatistics.duration5Min().toProto()) .build() ); } @@ -159,7 +171,7 @@ public class PlatformStatisticsProvider { PingStatistics pingStatistics = this.platform.getPingStatistics(); if (pingStatistics != null && pingStatistics.getPingAverage().getSamples() != 0) { builder.setPing(PlatformStatistics.Ping.newBuilder() - .setLast15M(rollingAverageValues(pingStatistics.getPingAverage())) + .setLast15M(pingStatistics.getPingAverage().toProto()) .build() ); } @@ -167,14 +179,4 @@ public class PlatformStatisticsProvider { return builder.build(); } - private static PlatformStatistics.RollingAverageValues rollingAverageValues(RollingAverage rollingAverage) { - return PlatformStatistics.RollingAverageValues.newBuilder() - .setMean(rollingAverage.mean()) - .setMax(rollingAverage.max()) - .setMin(rollingAverage.min()) - .setMedian(rollingAverage.median()) - .setPercentile95(rollingAverage.percentile95th()) - .build(); - } - } diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java index d9e8bf4..7e04400 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java @@ -34,7 +34,7 @@ import java.util.Map; /** * Abstract implementation of {@link ServerConfigProvider}. * - * <p>THis implementation is able to delete hidden paths from + * <p>This implementation is able to delete hidden paths from * the configurations before they are sent to the viewer.</p> * * @param <T> the file type diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java index 492d4ea..cbc496d 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java @@ -20,6 +20,9 @@ package me.lucko.spark.common.util; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; + public enum FormatUtil { ; @@ -37,4 +40,24 @@ public enum FormatUtil { int sizeIndex = (int) (Math.log(bytes) / Math.log(1024)); return String.format("%.1f", bytes / Math.pow(1024, sizeIndex)) + " " + SIZE_UNITS[sizeIndex]; } + + public static Component formatBytes(long bytes, TextColor color, String suffix) { + String value; + String unit; + + if (bytes == 0) { + value = "0"; + unit = "KB" + suffix; + } else { + int sizeIndex = (int) (Math.log(bytes) / Math.log(1024)); + value = String.format("%.1f", bytes / Math.pow(1024, sizeIndex)); + unit = SIZE_UNITS[sizeIndex] + suffix; + } + + return Component.text() + .append(Component.text(value, color)) + .append(Component.space()) + .append(Component.text(unit)) + .build(); + } } diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java b/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java index 0926ae7..7d688d7 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java @@ -44,7 +44,12 @@ public enum LinuxProc { /** * Information about the system memory. */ - MEMINFO("/proc/meminfo"); + MEMINFO("/proc/meminfo"), + + /** + * Information about the system network usage. + */ + NET_DEV("/proc/net/dev"); private final Path path; 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 57dfdff..65753bc 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 @@ -21,6 +21,7 @@ package me.lucko.spark.common.util; import me.lucko.spark.api.statistic.misc.DoubleAverageInfo; +import me.lucko.spark.proto.SparkProtos; import java.math.BigDecimal; import java.math.RoundingMode; @@ -111,4 +112,14 @@ public class RollingAverage implements DoubleAverageInfo { return sortedSamples[rank].doubleValue(); } + public SparkProtos.RollingAverageValues toProto() { + return SparkProtos.RollingAverageValues.newBuilder() + .setMean(mean()) + .setMax(max()) + .setMin(min()) + .setMedian(median()) + .setPercentile95(percentile95th()) + .build(); + } + } |