Browse Source

Initial implementation for 1.12.2

master
parent
commit
8798bc8086
Signed by: Andriy Kushnir (Orhideous) <[email protected]> GPG Key ID: 62E078AB621B0D15
9 changed files with 412 additions and 0 deletions
  1. +61
    -0
      build.gradle
  2. +3
    -0
      gradle.properties
  3. +64
    -0
      src/main/java/name/orhideous/alheimur/Alheimur.java
  4. +61
    -0
      src/main/java/name/orhideous/alheimur/AlheimurCollector.java
  5. +15
    -0
      src/main/java/name/orhideous/alheimur/Config.java
  6. +37
    -0
      src/main/java/name/orhideous/alheimur/ConfigLoader.java
  7. +9
    -0
      src/main/java/name/orhideous/alheimur/EntityInfo.java
  8. +137
    -0
      src/main/java/name/orhideous/alheimur/ForgeSupplier.java
  9. +25
    -0
      src/main/java/name/orhideous/alheimur/Supplier.java

+ 61
- 0
build.gradle View File

@@ -0,0 +1,61 @@
buildscript {
repositories {
jcenter()
maven { url = "http://files.minecraftforge.net/maven" }
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT'
}
}

plugins {
id 'org.spongepowered.plugin' version '0.8.1'
id 'com.github.johnrengelman.shadow' version '2.0.1'
}

apply plugin: 'net.minecraftforge.gradle.forge'

group = pluginGroup
version = pluginVersion
archivesBaseName = pluginId

sourceCompatibility = targetCompatibility = "1.8"
compileJava {
sourceCompatibility = targetCompatibility = "1.8"
}

minecraft {
version = "1.12.2-14.23.5.2768"
runDir = "run"
mappings = "stable_39"
}

dependencies {
compile group: 'org.spongepowered', name: 'spongeapi', version: '7.1.0'
compile group: 'io.prometheus', name: 'simpleclient', version: '0.6.0'
compile group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.6.0'
compile group: 'io.prometheus', name: 'simpleclient_httpserver', version: '0.6.0'
compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.16.18'
}

sponge {
plugin {
meta {
name = 'Alheimur'
version = pluginVersion
description = 'Collects Minecraft metrics for Prometheus'
url = 'https://orhideous.name/'
}
}
}

shadowJar {
dependencies {
exclude(dependency('org.projectlombok:lombok'))
}
exclude 'META-INF/*'
}

reobf {
shadowJar {}
}

+ 3
- 0
gradle.properties View File

@@ -0,0 +1,3 @@
pluginGroup=name.orhideous
pluginId=alheimur
pluginVersion=1.0-SNAPSHOT

+ 64
- 0
src/main/java/name/orhideous/alheimur/Alheimur.java View File

@@ -0,0 +1,64 @@
package name.orhideous.alheimur;

import com.google.inject.Inject;
import io.prometheus.client.exporter.HTTPServer;
import io.prometheus.client.hotspot.DefaultExports;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import org.slf4j.Logger;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.config.DefaultConfig;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GamePreInitializationEvent;
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
import org.spongepowered.api.event.game.state.GameStoppingEvent;
import org.spongepowered.api.plugin.Plugin;

import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.file.Path;

@Plugin(id = "alheimur")
public class Alheimur {
@SuppressWarnings("unused")
static final AlheimurCollector stats = new AlheimurCollector(new ForgeSupplier()).register();
@Inject
private Logger logger;
@Inject @DefaultConfig(sharedRoot = false)
private ConfigurationLoader<CommentedConfigurationNode> configLoader;
@Inject @DefaultConfig(sharedRoot = false)
private Path configPath;
@Inject @ConfigDir(sharedRoot = false)
private Path configDir;
private Config config;
@Nullable
private HTTPServer server;

@Listener
public void onPreInit(GamePreInitializationEvent event) {
try {
config = new ConfigLoader(configLoader, configPath).loadConfig();
} catch (ObjectMappingException | IOException exc) {
logger.error("Error loading config", exc);
}
DefaultExports.initialize();
}

@Listener
public void onServerStart(GameStartedServerEvent event) {
try {
logger.info("Starting Prometheus exporter on {}", config.getPort());
server = new HTTPServer(config.getHost(), config.getPort());
} catch (IOException e) {
logger.error("Failed to start Prometheus exporter", e);
}
}

@Listener
public void onServerStop(GameStoppingEvent event) {
if (server != null) {
server.stop();
}
}
}

+ 61
- 0
src/main/java/name/orhideous/alheimur/AlheimurCollector.java View File

@@ -0,0 +1,61 @@
package name.orhideous.alheimur;

import io.prometheus.client.Collector;
import io.prometheus.client.GaugeMetricFamily;
import lombok.RequiredArgsConstructor;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@RequiredArgsConstructor
class AlheimurCollector extends Collector {
@Nonnull
private final Supplier supplier;

public List<Collector.MetricFamilySamples> collect() {
List<Collector.MetricFamilySamples> metrics = new ArrayList<>();

GaugeMetricFamily chunkGauge = new GaugeMetricFamily("chunks_total", "Chunks info", Arrays.asList("world", "type"));
GaugeMetricFamily playerGauge = new GaugeMetricFamily("players_total", "Online players", Collections.singletonList("world"));
GaugeMetricFamily tpsGauge = new GaugeMetricFamily("tps", "Ticks per second", Collections.singletonList("world"));
GaugeMetricFamily entityGauge = new GaugeMetricFamily("entities_total", "Entities", Arrays.asList("world", "mod", "name"));
GaugeMetricFamily tileEntityGauge = new GaugeMetricFamily("tile_entities_total", "Tile entities", Arrays.asList("world", "mod", "name"));
GaugeMetricFamily tickingTileEntityGauge = new GaugeMetricFamily("ticking_tile_entity_total", "Ticking tile entities", Arrays.asList("world", "mod", "name"));

supplier.loadedChunks().forEach((worldName, value) -> chunkGauge.addMetric(
Arrays.asList(worldName, "loaded"),
value
));
supplier.players().forEach((worldName, value) -> playerGauge.addMetric(
Collections.singletonList(worldName),
value
));
supplier.TPS().forEach((worldName, value) -> tpsGauge.addMetric(
Collections.singletonList(worldName),
value
));
supplier.loadedEntities().forEach((worldName, values) -> values.forEach((entityInfo, count) -> entityGauge.addMetric(
Arrays.asList(worldName, entityInfo.getNamespace(), entityInfo.getPath()),
count
)));
supplier.loadedTileEntities().forEach((worldName, values) -> values.forEach((entityInfo, count) -> tileEntityGauge.addMetric(
Arrays.asList(worldName, entityInfo.getNamespace(), entityInfo.getPath()),
count
)));
supplier.loadedTickableTileEntities().forEach((worldName, values) -> values.forEach((entityInfo, count) -> tickingTileEntityGauge.addMetric(
Arrays.asList(worldName, entityInfo.getNamespace(), entityInfo.getPath()),
count
)));

metrics.add(chunkGauge);
metrics.add(playerGauge);
metrics.add(tpsGauge);
metrics.add(entityGauge);
metrics.add(tileEntityGauge);
metrics.add(tickingTileEntityGauge);
return metrics;
}
}

+ 15
- 0
src/main/java/name/orhideous/alheimur/Config.java View File

@@ -0,0 +1,15 @@
package name.orhideous.alheimur;

import lombok.Getter;
import ninja.leaping.configurate.objectmapping.Setting;
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;

@ConfigSerializable
class Config {
@Getter
@Setting(comment = "Prometheus host")
private String host = "localhost";
@Getter
@Setting(comment = "Prometheus port")
private int port = 31500;
}

+ 37
- 0
src/main/java/name/orhideous/alheimur/ConfigLoader.java View File

@@ -0,0 +1,37 @@
package name.orhideous.alheimur;


import com.google.common.reflect.TypeToken;
import lombok.AllArgsConstructor;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;

@AllArgsConstructor
class ConfigLoader {
private ConfigurationLoader<CommentedConfigurationNode> loader;
private Path path;

@SuppressWarnings("UnstableApiUsage")
Config loadConfig() throws IOException, ObjectMappingException {
Config config;
ConfigurationNode rootNode;
if (!Files.exists(this.path, LinkOption.NOFOLLOW_LINKS)) {
config = new Config();
rootNode = this.loader.createEmptyNode(this.loader.getDefaultOptions());
rootNode.setValue(TypeToken.of(Config.class), config);
this.loader.save(rootNode);
} else {
rootNode = this.loader.load();
config = rootNode.getValue(TypeToken.of(Config.class));
}

return config;
}
}

+ 9
- 0
src/main/java/name/orhideous/alheimur/EntityInfo.java View File

@@ -0,0 +1,9 @@
package name.orhideous.alheimur;

import lombok.Data;

@Data
class EntityInfo {
private final String namespace;
private final String path;
}

+ 137
- 0
src/main/java/name/orhideous/alheimur/ForgeSupplier.java View File

@@ -0,0 +1,137 @@
package name.orhideous.alheimur;


import com.google.common.collect.Iterables;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.registries.IForgeRegistryEntry;

import javax.annotation.Nonnull;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;

class ForgeSupplier implements Supplier {
private static long mean(long[] values) {
long sum = 0L;
for (long v : values) {
sum += v;
}
return sum / values.length;
}

private WorldServer[] worlds() {
return DimensionManager.getWorlds();
}

private Map<EntityInfo, Long> collectEntityNames(List<Entity> entities) {
return new CopyOnWriteArrayList<>(entities)
.stream()
.map(EntityList::getKey)
.filter(Objects::nonNull)
.map(rs -> new EntityInfo(rs.getNamespace(), rs.toString()))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}

private Map<EntityInfo, Long> collectTileEntityNames(List<TileEntity> tileEntities) {
return new CopyOnWriteArrayList<>(tileEntities)
.stream()
.map(TileEntity::getBlockType)
.map(IForgeRegistryEntry.Impl::getRegistryName)
.filter(Objects::nonNull)
.map(rs -> new EntityInfo(rs.getNamespace(), rs.toString()))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}

@Override
@Nonnull
public Map<String, Double> TPS() {
ConcurrentHashMap<String, Double> map = new ConcurrentHashMap<>();
MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();

for (WorldServer ws : this.worlds()) {
double worldTickTime = mean(server.worldTickTimes.get(ws.provider.getDimension())) * 1.0E-6D;
double worldTPS = Math.min(1000.0 / worldTickTime, 20);
map.put(
ws.getWorldInfo().getWorldName(),
worldTPS
);
}
return map;
}

@Override
@Nonnull
public Map<String, Integer> loadedChunks() {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
for (WorldServer ws : this.worlds()) {
map.put(
ws.getWorldInfo().getWorldName(),
Iterables.size(ws.getChunkProvider().getLoadedChunks())
);
}
return map;
}

@Override
@Nonnull
public Map<String, Integer> players() {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
for (WorldServer ws : this.worlds()) {
map.put(
ws.getWorldInfo().getWorldName(),
Iterables.size(ws.playerEntities)
);

}
return map;
}

@Override
@Nonnull
public Map<String, Map<EntityInfo, Long>> loadedEntities() {
ConcurrentHashMap<String, Map<EntityInfo, Long>> map = new ConcurrentHashMap<>();
for (WorldServer ws : this.worlds()) {
map.put(
ws.getWorldInfo().getWorldName(),
collectEntityNames(ws.loadedEntityList)
);
}
return map;
}

@Override
@Nonnull
public Map<String, Map<EntityInfo, Long>> loadedTileEntities() {
ConcurrentHashMap<String, Map<EntityInfo, Long>> map = new ConcurrentHashMap<>();
for (WorldServer ws : this.worlds()) {
map.put(
ws.getWorldInfo().getWorldName(),
collectTileEntityNames(ws.loadedTileEntityList)
);
}
return map;
}

@Override
@Nonnull
public Map<String, Map<EntityInfo, Long>> loadedTickableTileEntities() {
ConcurrentHashMap<String, Map<EntityInfo, Long>> map = new ConcurrentHashMap<>();
for (WorldServer ws : this.worlds()) {
map.put(
ws.getWorldInfo().getWorldName(),
collectTileEntityNames(ws.tickableTileEntities)
);
}
return map;
}
}

+ 25
- 0
src/main/java/name/orhideous/alheimur/Supplier.java View File

@@ -0,0 +1,25 @@
package name.orhideous.alheimur;

import javax.annotation.Nonnull;
import java.util.Map;

interface Supplier {
@Nonnull
Map<String, Double> TPS();

@Nonnull
Map<String, Integer> loadedChunks();

@Nonnull
Map<String, Integer> players();

@Nonnull
Map<String, Map<EntityInfo, Long>> loadedEntities();

@Nonnull
Map<String, Map<EntityInfo, Long>> loadedTileEntities();

@Nonnull
Map<String, Map<EntityInfo, Long>> loadedTickableTileEntities();

}

Loading…
Cancel
Save