1
0

metrics! (and placeholders)

This commit is contained in:
Lee
2024-03-21 23:33:50 +00:00
parent a9192bae57
commit 379a21721a
21 changed files with 576 additions and 7 deletions

4
.gitignore vendored
View File

@ -33,3 +33,7 @@ build/
### Mac OS ### ### Mac OS ###
.DS_Store .DS_Store
### Aetheria ###
**/src/main/resources/git.properties
jars

1
.idea/misc.xml generated
View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="EntryPointsManager"> <component name="EntryPointsManager">
<list size="1"> <list size="1">

114
pom.xml
View File

@ -9,11 +9,96 @@
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <java.version>17</java.version>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<build>
<!-- Plugins -->
<plugins>
<!-- Used for compiling the source code with the proper Java version -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<!-- Enable incremental builds, this is reversed due to -->
<!-- a bug as seen in https://issues.apache.org/jira/browse/MCOMPILER-209 -->
<useIncrementalCompilation>false</useIncrementalCompilation>
</configuration>
</plugin>
<!-- Used for generating a git properties file during build -->
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>4.9.10</version>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<prefix>git</prefix>
<dotGitDirectory>$PROJECT.BASEDIR$/.git</dotGitDirectory>
<injectAllReactorProjects>true</injectAllReactorProjects>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>src/main/resources/git.properties</generateGitPropertiesFilename>
<commitIdGenerationMode>full</commitIdGenerationMode>
<dateFormatTimeZone>$USER.TIMEZONE$</dateFormatTimeZone>
<dateFormat>MM-dd-yyyy@HH:mm:ss</dateFormat>
<includeOnlyProperties>
<includeOnlyProperty>^git.branch$</includeOnlyProperty>
<includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>
<includeOnlyProperty>^git.commit.id.(abbrev|full)$</includeOnlyProperty>
<includeOnlyProperty>^git.build.user.name$</includeOnlyProperty>
</includeOnlyProperties>
</configuration>
</plugin>
<!-- Handles shading of dependencies in the final output jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<outputDirectory>./jars</outputDirectory>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<!-- Filter the resources dir for placeholders -->
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
<repositories> <repositories>
<repository> <repository>
<id>papermc</id> <id>papermc</id>
@ -23,6 +108,10 @@
<id>jitpack.io</id> <id>jitpack.io</id>
<url>https://jitpack.io/</url> <url>https://jitpack.io/</url>
</repository> </repository>
<repository>
<id>placeholderapi</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>
@ -50,6 +139,27 @@
<version>2.1.3</version> <version>2.1.3</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.11.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.5.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>7.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -1,10 +1,16 @@
package cc.fascinated; package cc.fascinated;
import cc.fascinated.command.CommandManager; import cc.fascinated.command.CommandManager;
import cc.fascinated.metrics.MetricManager;
import cc.fascinated.placeholder.PlaceholderManager;
import cc.fascinated.playercolors.ColorManager; import cc.fascinated.playercolors.ColorManager;
import cc.fascinated.worldsize.WorldSizeManager; import cc.fascinated.worldsize.WorldSizeManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Aetheria extends JavaPlugin { public class Aetheria extends JavaPlugin {
/** /**
@ -12,6 +18,9 @@ public class Aetheria extends JavaPlugin {
*/ */
public static Aetheria INSTANCE; public static Aetheria INSTANCE;
public static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(2, 8, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
// todo: move to config
public static final String PREFIX = "§6§lAetheria §7» §f"; public static final String PREFIX = "§6§lAetheria §7» §f";
public Aetheria() { public Aetheria() {
@ -25,5 +34,7 @@ public class Aetheria extends JavaPlugin {
new CommandManager(); new CommandManager();
new WorldSizeManager(); new WorldSizeManager();
new ColorManager(); new ColorManager();
new PlaceholderManager();
new MetricManager();
} }
} }

View File

@ -0,0 +1,7 @@
package cc.fascinated;
import oshi.SystemInfo;
public class Oshi {
public static final SystemInfo SYSTEM_INFO = new SystemInfo();
}

View File

@ -1,7 +1,6 @@
package cc.fascinated.command; package cc.fascinated.command;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

View File

@ -2,10 +2,8 @@ package cc.fascinated.command.impl;
import cc.fascinated.Aetheria; import cc.fascinated.Aetheria;
import cc.fascinated.command.Command; import cc.fascinated.command.Command;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
public class HelpCommand extends Command { public class HelpCommand extends Command {

View File

@ -0,0 +1,22 @@
package cc.fascinated.metrics;
import com.influxdb.client.write.Point;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public abstract class Metric {
/**
* The name of the metric.
*/
private final String name;
/**
* Collect and return the metric.
*
* @return the collected metric
*/
protected abstract Point toPoint();
}

View File

@ -0,0 +1,113 @@
package cc.fascinated.metrics;
import cc.fascinated.Aetheria;
import cc.fascinated.metrics.impl.server.MemoryMetric;
import cc.fascinated.metrics.impl.server.PlayerCountMetric;
import cc.fascinated.metrics.impl.server.TotalJoinsMetric;
import cc.fascinated.metrics.impl.server.TpsMetric;
import cc.fascinated.metrics.impl.system.CpuUsageMetric;
import cc.fascinated.metrics.impl.world.WorldSizeMetric;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.InfluxDBClientOptions;
import com.influxdb.client.WriteApiBlocking;
import com.influxdb.client.domain.WriteConsistency;
import com.influxdb.client.domain.WritePrecision;
import com.influxdb.client.write.Point;
import com.influxdb.client.write.WriteParameters;
import lombok.extern.log4j.Log4j2;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Log4j2
public class MetricManager implements Listener {
private InfluxDBClient influxDB;
private WriteApiBlocking writeApi;
private static final List<Metric> metrics = new ArrayList<>();
public MetricManager() {
FileConfiguration config = Aetheria.INSTANCE.getConfig();
String url = config.getString("influxdb.url");
String token = config.getString("influxdb.token");
String org = config.getString("influxdb.org");
String bucket = config.getString("influxdb.bucket");
if (url == null || token == null || org == null || bucket == null) {
log.error("InfluxDB configuration is missing");
return;
}
InfluxDBClientOptions options = InfluxDBClientOptions.builder()
.url(url)
.authenticateToken(token.toCharArray())
.org(org)
.bucket(bucket)
.build();
try {
influxDB = InfluxDBClientFactory.create(options);
influxDB.ping(); // test the connection
writeApi = influxDB.getWriteApiBlocking();
} catch (Exception e) {
log.error("Failed to connect to influx", e);
return;
}
// system metrics
registerMetric(new CpuUsageMetric());
// world metrics
registerMetric(new WorldSizeMetric());
// server metrics
registerMetric(new TpsMetric());
registerMetric(new MemoryMetric());
registerMetric(new PlayerCountMetric());
registerMetric(new TotalJoinsMetric());
Bukkit.getAsyncScheduler().runAtFixedRate(Aetheria.INSTANCE, (task) -> this.collectMetrics(), 30, 30, TimeUnit.SECONDS);
}
/**
* Collect all metrics and write them to influx
*/
public void collectMetrics() {
log.info("Collecting metrics");
long before = System.currentTimeMillis();
List<Point> points = new ArrayList<>();
for (Metric metric : metrics) {
points.add(metric.toPoint());
}
// write the points async
Aetheria.EXECUTOR.execute(() -> {
writeApi.writePoints(points, new WriteParameters(WritePrecision.MS, WriteConsistency.ONE));
log.info("Wrote {} points to influx ({}ms)", points.size(), System.currentTimeMillis() - before);
});
}
/**
* Register a new metric
*
* @param metric the metric to register
*/
public static void registerMetric(Metric metric) {
metrics.add(metric);
}
@EventHandler
public void onPluginDisable(PluginDisableEvent event) {
if (event.getPlugin() != Aetheria.INSTANCE) {
return;
}
influxDB.close(); // close the connection
}
}

View File

@ -0,0 +1,21 @@
package cc.fascinated.metrics.impl.server;
import cc.fascinated.metrics.Metric;
import com.influxdb.client.write.Point;
public class MemoryMetric extends Metric {
public MemoryMetric() {
super("memory");
}
@Override
protected Point toPoint() {
Runtime runtime = Runtime.getRuntime();
return Point.measurement(getName())
.addField("total", runtime.totalMemory())
.addField("available", runtime.freeMemory())
.addField("used", runtime.totalMemory() - runtime.freeMemory());
}
}

View File

@ -0,0 +1,18 @@
package cc.fascinated.metrics.impl.server;
import cc.fascinated.metrics.Metric;
import com.influxdb.client.write.Point;
import org.bukkit.Bukkit;
public class PlayerCountMetric extends Metric {
public PlayerCountMetric() {
super("player_count");
}
@Override
protected Point toPoint() {
return Point.measurement(getName())
.addField("value", Bukkit.getServer().getOnlinePlayers().size());
}
}

View File

@ -0,0 +1,18 @@
package cc.fascinated.metrics.impl.server;
import cc.fascinated.metrics.Metric;
import com.influxdb.client.write.Point;
import org.bukkit.Bukkit;
public class TotalJoinsMetric extends Metric {
public TotalJoinsMetric() {
super("total_joins");
}
@Override
protected Point toPoint() {
return Point.measurement(getName())
.addField("value", Bukkit.getServer().getOfflinePlayers().length);
}
}

View File

@ -0,0 +1,19 @@
package cc.fascinated.metrics.impl.server;
import cc.fascinated.metrics.Metric;
import com.influxdb.client.write.Point;
import org.bukkit.Bukkit;
public class TpsMetric extends Metric {
public TpsMetric() {
super("tps");
}
@Override
protected Point toPoint() {
return Point.measurement("tps")
.addTag("server", "server")
.addField("tps", Bukkit.getServer().getTPS()[0]);
}
}

View File

@ -0,0 +1,22 @@
package cc.fascinated.metrics.impl.system;
import cc.fascinated.Oshi;
import cc.fascinated.metrics.Metric;
import com.influxdb.client.write.Point;
import oshi.hardware.HardwareAbstractionLayer;
public class CpuUsageMetric extends Metric {
public CpuUsageMetric() {
super("cpu_usage");
}
@Override
protected Point toPoint() {
HardwareAbstractionLayer hardware = Oshi.SYSTEM_INFO.getHardware();
double cpuLoad = hardware.getProcessor().getSystemCpuLoad(500) * 100; // get the CPU load in percentage
return Point.measurement(getName())
.addField("value", cpuLoad);
}
}

View File

@ -0,0 +1,29 @@
package cc.fascinated.metrics.impl.world;
import cc.fascinated.metrics.Metric;
import cc.fascinated.worldsize.WorldSizeManager;
import com.influxdb.client.write.Point;
import org.bukkit.World;
import java.util.Map;
public class WorldSizeMetric extends Metric {
public WorldSizeMetric() {
super("world_size");
}
@Override
protected Point toPoint() {
Map<World, Long> worldSizes = WorldSizeManager.getWorldSizes();
if (worldSizes.isEmpty()) {
return null;
}
Point point = Point.measurement(getName());
for (Map.Entry<World, Long> entry : worldSizes.entrySet()) {
point.addField(entry.getKey().getName(), entry.getValue());
}
return point;
}
}

View File

@ -0,0 +1,31 @@
package cc.fascinated.placeholder;
import org.bukkit.entity.Player;
import javax.annotation.Nullable;
public interface AetheriaPlaceholder {
/**
* @return the identifier for this placeholder
*/
String getIdentifier();
/**
* Parse this placeholder and return the value for the provided player
*
* @param player the player to parse the placeholder for, null if offline
* @param params the parameters for the placeholder
* @return the placeholder string
*/
String parse(@Nullable Player player, String[] params);
/**
* Return the required amount of parameters for this placeholder
* Does not include the expansion identifier or the placeholder identifier
*
* @return the required amount of parameters
*/
int getRequiredParameterCount();
}

View File

@ -0,0 +1,106 @@
package cc.fascinated.placeholder;
import cc.fascinated.Aetheria;
import lombok.extern.log4j.Log4j2;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Log4j2
public class PlaceholderManager {
private static final Map<String, AetheriaPlaceholder> PLACEHOLDERS = new ConcurrentHashMap<>();
public PlaceholderManager() {
new PlaceholderAdapter().register();
}
/**
* Register the provided placeholder
*
* @param placeholder the placeholder to register
*/
public static void registerPlaceholder(AetheriaPlaceholder placeholder) {
PLACEHOLDERS.put(placeholder.getIdentifier(), placeholder);
log.info("Registered placeholder " + placeholder.getIdentifier());
}
/**
* Unregister the provided placeholder
*
* @param placeholder the placeholder to unregister
*/
public static void unregisterPlaceholder(AetheriaPlaceholder placeholder) {
PLACEHOLDERS.remove(placeholder.getIdentifier());
log.info("Unregistered placeholder " + placeholder.getIdentifier());
}
/**
* Replace PAPI placeholders in the given content.
*
* @param player the player to replace the placeholders for, null if offline
* @param content the content to replace the placeholders in
* @return the content with the placeholders replaced
*/
public static String replace(@Nullable Player player, String content) {
return PlaceholderAPI.setPlaceholders(player != null && (player.isOnline()) ? player : null, content);
}
static class PlaceholderAdapter extends PlaceholderExpansion {
@Override
public @NotNull String getIdentifier() {
return "aetheria";
}
@SuppressWarnings("UnstableApiUsage")
@Override
public @NotNull String getAuthor() {
return String.join(", ", Aetheria.INSTANCE.getPluginMeta().getAuthors());
}
@SuppressWarnings("UnstableApiUsage")
@Override
public @NotNull String getVersion() {
return Aetheria.INSTANCE.getPluginMeta().getVersion();
}
/**
* From PlaceholderAPI:
* <p>
* Expansions that do not use the e-cloud and instead register from the dependency should set this to true
* to ensure that your placeholder expansion is not unregistered when the papi reload command is used
*/
public boolean persist() {
return true;
}
@Override
public @Nullable String onRequest(@Nullable OfflinePlayer player, @NotNull String params) {
String[] parameters = params.split("_");
AetheriaPlaceholder placeholder = PLACEHOLDERS.get(parameters[0]);
// Attempt to fetch the placeholder with the provided id
if (placeholder == null) {
log.error("Tried to fetch an unregistered placeholder: " + parameters[0]);
return null;
}
// Ensure the correct amount of parameters is provided
if (parameters.length - 1 < placeholder.getRequiredParameterCount()) {
log.error("Invalid placeholders provided for {}, provided = {}, required = {}",
placeholder.getIdentifier(),
parameters.length - 1,
placeholder.getRequiredParameterCount());
return null;
}
// Trim the placeholder identifier from the parameters before parsing
return placeholder.parse((Player) player, parameters.length > 1 ? Arrays.copyOfRange(parameters, 1, parameters.length) : parameters);
}
}
}

View File

@ -2,6 +2,7 @@ package cc.fascinated.worldsize;
import cc.fascinated.Aetheria; import cc.fascinated.Aetheria;
import cc.fascinated.command.CommandManager; import cc.fascinated.command.CommandManager;
import cc.fascinated.placeholder.PlaceholderManager;
import cc.fascinated.worldsize.impl.WorldSizeCommand; import cc.fascinated.worldsize.impl.WorldSizeCommand;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -31,6 +32,7 @@ public class WorldSizeManager {
public WorldSizeManager() { public WorldSizeManager() {
CommandManager.registerCommand(new WorldSizeCommand()); CommandManager.registerCommand(new WorldSizeCommand());
PlaceholderManager.registerPlaceholder(new WorldSizePlaceholder());
Aetheria.INSTANCE.getServer().getAsyncScheduler().runAtFixedRate(Aetheria.INSTANCE, (task) -> { Aetheria.INSTANCE.getServer().getAsyncScheduler().runAtFixedRate(Aetheria.INSTANCE, (task) -> {
calculateTotalWorldSize(); calculateTotalWorldSize();

View File

@ -0,0 +1,31 @@
package cc.fascinated.worldsize;
import cc.fascinated.placeholder.AetheriaPlaceholder;
import cc.fascinated.utils.FormatterUtils;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
public class WorldSizePlaceholder implements AetheriaPlaceholder {
@Override
public String getIdentifier() {
return "worldsize";
}
@Override
public String parse(@Nullable Player player, String[] params) {
World world = Bukkit.getWorld(params[0].replaceAll("-", "_"));
if (world == null) {
return "-1";
}
long size = WorldSizeManager.getWorldSizes().get(world);
return FormatterUtils.formatBytes(size);
}
@Override
public int getRequiredParameterCount() {
return 1;
}
}

View File

@ -1,3 +1,9 @@
influxdb:
url: "http://localhost:8086"
token: "aetheria"
org: "aetheria"
bucket: "aetheria"
help-command: help-command:
- "&e/help &7- &fShows this help message" - "&e/help &7- &fShows this help message"
- "&e/kill &7- &fKills you" - "&e/kill &7- &fKills you"

View File

@ -1,6 +1,9 @@
name: Aetheria name: Aetheria
main: cc.fascinated.Aetheria main: cc.fascinated.Aetheria
version: 1.0 version: 1.0
author: Fascinated
depend:
- PlaceholderAPI
commands: commands:
totaljoins: totaljoins:
description: "Shows the total amount of joins" description: "Shows the total amount of joins"