Compare commits

...

1 Commits

Author SHA1 Message Date
Sebastian Hartte 53f7046f93 Metrics WIP 2020-09-10 18:55:22 +02:00
16 changed files with 421 additions and 5 deletions
@@ -18,6 +18,7 @@
package appeng.client.render.cablebus; package appeng.client.render.cablebus;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
@@ -51,12 +52,21 @@ import net.minecraftforge.client.model.data.IModelData;
import appeng.api.parts.IPartModel; import appeng.api.parts.IPartModel;
import appeng.api.util.AECableType; import appeng.api.util.AECableType;
import appeng.api.util.AEColor; import appeng.api.util.AEColor;
import appeng.metrics.Metrics;
public class CableBusBakedModel implements IBakedModel { public class CableBusBakedModel implements IBakedModel {
// The number of quads overall that will be cached // The number of quads overall that will be cached
private static final int CACHE_QUAD_COUNT = 5000; private static final int CACHE_QUAD_COUNT = 5000;
// Keep a weak-ref to the last created cache for reporting metrics
private static WeakReference<LoadingCache<CableBusRenderState, List<BakedQuad>>> cacheForMetrics = new WeakReference<>(
null);
static {
Metrics.cache("cable_bus_model_cache", () -> cacheForMetrics.get());
}
private final LoadingCache<CableBusRenderState, List<BakedQuad>> cableModelCache; private final LoadingCache<CableBusRenderState, List<BakedQuad>> cableModelCache;
private final CableBuilder cableBuilder; private final CableBuilder cableBuilder;
@@ -74,6 +84,7 @@ public class CableBusBakedModel implements IBakedModel {
this.partModels = partModels; this.partModels = partModels;
this.particleTexture = particleTexture; this.particleTexture = particleTexture;
this.cableModelCache = CacheBuilder.newBuilder()// this.cableModelCache = CacheBuilder.newBuilder()//
.recordStats()//
.maximumWeight(CACHE_QUAD_COUNT)// .maximumWeight(CACHE_QUAD_COUNT)//
.weigher((Weigher<CableBusRenderState, List<BakedQuad>>) (key, value) -> value.size())// .weigher((Weigher<CableBusRenderState, List<BakedQuad>>) (key, value) -> value.size())//
.build(new CacheLoader<CableBusRenderState, List<BakedQuad>>() { .build(new CacheLoader<CableBusRenderState, List<BakedQuad>>() {
@@ -84,6 +95,7 @@ public class CableBusBakedModel implements IBakedModel {
return model; return model;
} }
}); });
cacheForMetrics = new WeakReference<>(cableModelCache);
} }
@Override @Override
+11
View File
@@ -404,6 +404,10 @@ public final class AEConfig {
return COMMON.improvedFluidMultiplier.get().floatValue(); return COMMON.improvedFluidMultiplier.get().floatValue();
} }
public int getPrometheusMetricsServerPort() {
return COMMON.prometheusMetricsServer.get();
}
// Setters keep visibility as low as possible. // Setters keep visibility as low as possible.
private static class ClientConfig { private static class ClientConfig {
@@ -474,6 +478,7 @@ public final class AEConfig {
public final BooleanValue removeCrashingItemsOnLoad; public final BooleanValue removeCrashingItemsOnLoad;
public final ConfigValue<Integer> formationPlaneEntityLimit; public final ConfigValue<Integer> formationPlaneEntityLimit;
public final ConfigValue<Integer> craftingCalculationTimePerTick; public final ConfigValue<Integer> craftingCalculationTimePerTick;
public final ConfigValue<Integer> prometheusMetricsServer;
// Spatial IO/Dimension // Spatial IO/Dimension
public final ConfigValue<Double> spatialPowerExponent; public final ConfigValue<Double> spatialPowerExponent;
@@ -558,6 +563,12 @@ public final class AEConfig {
.define("removeCrashingItemsOnLoad", false); .define("removeCrashingItemsOnLoad", false);
builder.pop(); builder.pop();
builder.push("metrics");
prometheusMetricsServer = builder.comment(
"Enables the Prometheus Metrics exporting endpoint on 127.0.0.1 on this port number. 0 to disable.")
.define("prometheusMetricsServer", 0);
builder.pop();
builder.push("automation"); builder.push("automation");
formationPlaneEntityLimit = builder.comment("TODO").define("formationPlaneEntityLimit", 128); formationPlaneEntityLimit = builder.comment("TODO").define("formationPlaneEntityLimit", 128);
builder.pop(); builder.pop();
+17
View File
@@ -66,6 +66,7 @@ import appeng.entity.TinyTNTPrimedEntity;
import appeng.entity.TinyTNTPrimedRenderer; import appeng.entity.TinyTNTPrimedRenderer;
import appeng.hooks.TickHandler; import appeng.hooks.TickHandler;
import appeng.integration.Integrations; import appeng.integration.Integrations;
import appeng.metrics.endpoint.PrometheusEndpoint;
import appeng.parts.PartPlacement; import appeng.parts.PartPlacement;
import appeng.server.ServerHelper; import appeng.server.ServerHelper;
@@ -90,6 +91,8 @@ public final class AppEng {
} }
INSTANCE = this; INSTANCE = this;
startMetricsServer();
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, AEConfig.CLIENT_SPEC); ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, AEConfig.CLIENT_SPEC);
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, AEConfig.COMMON_SPEC); ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, AEConfig.COMMON_SPEC);
@@ -148,6 +151,20 @@ public final class AppEng {
registerNetworkHandler(); registerNetworkHandler();
AddonLoader.loadAddons(Api.INSTANCE); AddonLoader.loadAddons(Api.INSTANCE);
startMetricsServer();
}
private void startMetricsServer() {
int prometheusPort = AEConfig.instance().getPrometheusMetricsServerPort();
if (prometheusPort != 0) {
try {
AELog.info("Starting Prometheus Metrics Server on Port %s", prometheusPort);
new PrometheusEndpoint("localhost", prometheusPort);
} catch (Exception e) {
AELog.warn("Failed to start Prometheus Metrics-Server: %s", e);
}
}
} }
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
@@ -157,4 +157,8 @@ final class StorageData extends WorldSavedData implements IWorldGridStorageData
return tag; return tag;
} }
public int size() {
return storage.size();
}
} }
@@ -28,12 +28,13 @@ import com.google.common.base.Preconditions;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.world.server.ServerWorld; import net.minecraft.world.server.ServerWorld;
import appeng.metrics.Metrics;
import appeng.services.CompassService; import appeng.services.CompassService;
import appeng.services.compass.CompassThreadFactory; import appeng.services.compass.CompassThreadFactory;
/** /**
* Singleton access to anything related to world-based data. * Singleton access to anything related to world-based data.
* * <p>
* Data will change depending which world is loaded. Will probably not affect * Data will change depending which world is loaded. Will probably not affect
* SMP at all since only one world is loaded, but SSP more, cause they play on * SMP at all since only one world is loaded, but SSP more, cause they play on
* different worlds. * different worlds.
@@ -53,8 +54,15 @@ public final class WorldData implements IWorldData {
@Nullable @Nullable
private static MinecraftServer server; private static MinecraftServer server;
static {
Metrics.gauge("grid_storage_count", () -> {
WorldData worldData = (WorldData) instance;
return worldData != null ? worldData.storageData.size() : 0;
});
}
private final IWorldPlayerData playerData; private final IWorldPlayerData playerData;
private final IWorldGridStorageData storageData; private final StorageData storageData;
private final IWorldCompassData compassData; private final IWorldCompassData compassData;
private WorldData(@Nonnull final ServerWorld overworld) { private WorldData(@Nonnull final ServerWorld overworld) {
@@ -80,7 +88,6 @@ public final class WorldData implements IWorldData {
/** /**
* @return ae2 data related to a specific world * @return ae2 data related to a specific world
*
* @deprecated do not use singletons which are dependent on specific world state * @deprecated do not use singletons which are dependent on specific world state
*/ */
@Deprecated @Deprecated
@@ -102,7 +109,7 @@ public final class WorldData implements IWorldData {
/** /**
* Requires to start up from external from here * Requires to start up from external from here
* * <p>
* drawback of the singleton build style * drawback of the singleton build style
*/ */
public static void onServerStarting(MinecraftServer server) { public static void onServerStarting(MinecraftServer server) {
@@ -57,6 +57,7 @@ import appeng.core.AppEng;
import appeng.core.sync.packets.PaintedEntityPacket; import appeng.core.sync.packets.PaintedEntityPacket;
import appeng.crafting.CraftingJob; import appeng.crafting.CraftingJob;
import appeng.me.Grid; import appeng.me.Grid;
import appeng.metrics.Metrics;
import appeng.tile.AEBaseTileEntity; import appeng.tile.AEBaseTileEntity;
import appeng.util.IWorldCallable; import appeng.util.IWorldCallable;
import appeng.util.Platform; import appeng.util.Platform;
@@ -77,6 +78,11 @@ public class TickHandler {
return INSTANCE; return INSTANCE;
} }
static {
Metrics.gauge("ticking_network_count", () -> INSTANCE.server.networks.size());
Metrics.gauge("crafting_job_count", INSTANCE.craftingJobs::size);
}
public static void setup(IEventBus eventBus) { public static void setup(IEventBus eventBus) {
eventBus.addListener(INSTANCE::onServerTick); eventBus.addListener(INSTANCE::onServerTick);
eventBus.addListener(INSTANCE::onWorldTick); eventBus.addListener(INSTANCE::onWorldTick);
@@ -0,0 +1,54 @@
package appeng.metrics;
import java.util.function.Supplier;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
/**
* Cache Metrics simplify reporting of metrics for a Guava {@link Cache}.
*/
public class CacheMetrics extends Metric {
private final Supplier<Cache<?, ?>> cacheSupplier;
// Pre-allocate these to avoid repeated concatenation during reporting
private final String sizeId;
private final String hitCountId;
private final String missCountId;
private final String loadSuccessCountId;
private final String loadExceptionCountId;
private final String totalLoadTimeId;
private final String evictionCountId;
public CacheMetrics(String id, Supplier<Cache<?, ?>> cacheSupplier) {
super(id);
this.cacheSupplier = cacheSupplier;
this.sizeId = id + ".size";
this.hitCountId = id + ".hit-count";
this.missCountId = id + ".miss-count";
this.loadSuccessCountId = id + ".load-success-count";
this.loadExceptionCountId = id + ".load-exception-count";
this.totalLoadTimeId = id + ".total-load-time";
this.evictionCountId = id + ".eviction-count";
}
@Override
public void accept(MetricVisitor visitor) {
Cache<?, ?> cache = this.cacheSupplier.get();
if (cache != null) {
visitor.visitGauge(sizeId, cache.size()); // Thread-safe
CacheStats stats = cache.stats(); // Thread-safe
visitor.visitGauge(hitCountId, stats.hitCount());
visitor.visitGauge(missCountId, stats.missCount());
visitor.visitGauge(loadSuccessCountId, stats.loadSuccessCount());
visitor.visitGauge(loadExceptionCountId, stats.loadExceptionCount());
visitor.visitGauge(totalLoadTimeId, stats.totalLoadTime());
visitor.visitGauge(evictionCountId, stats.evictionCount());
}
}
}
+31
View File
@@ -0,0 +1,31 @@
package appeng.metrics;
import java.util.function.Supplier;
/**
* A Gauge is a Metric that will simply read the current value of something when
* the metrics are being reported.
*/
class Gauge extends Metric {
private final Supplier<Number> valueSupplier;
public Gauge(String id, Supplier<Number> valueSupplier) {
super(id);
this.valueSupplier = valueSupplier;
}
public Supplier<Number> getValueSupplier() {
return valueSupplier;
}
public Number getValue() {
return valueSupplier.get();
}
@Override
public void accept(MetricVisitor visitor) {
visitor.visitGauge(getId(), getValue());
}
}
+34
View File
@@ -0,0 +1,34 @@
package appeng.metrics;
import java.util.Objects;
abstract class Metric {
private final String id;
public Metric(String id) {
this.id = Objects.requireNonNull(id);
}
public String getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Metric metric = (Metric) o;
return id.equals(metric.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public abstract void accept(MetricVisitor visitor);
}
@@ -0,0 +1,7 @@
package appeng.metrics;
public interface MetricVisitor {
void visitGauge(String id, Number value);
}
+51
View File
@@ -0,0 +1,51 @@
package appeng.metrics;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
/**
* Registry to register Metrics.
*/
public final class Metrics {
private static final List<Metric> metrics = new ArrayList<>();
private Metrics() {
}
/**
* Registers a Gauge, which is a simple metric with no state that will be
* queried for its current value whenever metrics are being reported.
*/
public static synchronized void gauge(String id, Supplier<Number> valueSupplier) {
metrics.add(new Gauge(id, valueSupplier));
}
/**
* Register several standard metrics for a Guava {@link Cache}.
*/
public static synchronized void cache(String name, Cache<?, ?> cache) {
metrics.add(new CacheMetrics(name, () -> cache));
}
/**
* Register several standard metrics for a Guava {@link Cache} based on a
* supplier to support an underlying changing cache.
*/
public static synchronized void cache(String name, Supplier<Cache<?, ?>> cacheSupplier) {
metrics.add(new CacheMetrics(name, cacheSupplier));
}
public static synchronized void visit(MetricVisitor visitor) {
for (Metric metric : metrics) {
metric.accept(visitor);
}
}
}
@@ -0,0 +1,78 @@
package appeng.metrics.endpoint;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import appeng.metrics.Metrics;
import appeng.metrics.reporter.PrometheusReporter;
/**
* Starts an embedded HTTP-Server that will expose the Metrics in a format
* compatible with Prometheus.
*/
public class PrometheusEndpoint implements AutoCloseable {
private final HttpServer server;
private final PrometheusReporter reporter = new PrometheusReporter();
public PrometheusEndpoint(String hostname, int port) {
try {
this.server = HttpServer.create();
} catch (IOException e) {
throw new RuntimeException("Failed to create metrics HTTP-server", e);
}
InetSocketAddress address;
if (hostname.isEmpty()) {
address = new InetSocketAddress((InetAddress) null, port);
} else {
address = new InetSocketAddress(hostname, port);
}
// Default endpoint is /metrics in Prometheus
this.server.createContext("/metrics", this::handler);
try {
this.server.bind(address, 0);
} catch (IOException e) {
throw new RuntimeException("Failed to bind metrics HTTP server to " + address, e);
}
this.server.start();
}
@Override
public void close() {
this.server.stop(0);
}
private synchronized void handler(HttpExchange exchange) throws IOException {
// Update metrics
reporter.reset();
Metrics.visit(reporter);
CharSequence report = getReport();
exchange.getResponseHeaders().add("Content-Type", "text/plain; version=0.0.4");
exchange.sendResponseHeaders(200, report.length());
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody(), StandardCharsets.US_ASCII)) {
writer.append(report);
}
exchange.close();
}
private CharSequence getReport() {
return reporter.getReport();
}
}
@@ -0,0 +1,24 @@
package appeng.metrics.reporter;
import java.io.PrintStream;
import appeng.metrics.MetricVisitor;
/**
* Reports Metrics as simple key-value pairs to a {@link PrintStream} (i.e.
* {@link System#out}).
*/
public class PrintStreamReporter implements MetricVisitor {
private final PrintStream out;
public PrintStreamReporter(PrintStream out) {
this.out = out;
}
@Override
public void visitGauge(String id, Number value) {
out.println(id + '=' + value);
}
}
@@ -0,0 +1,54 @@
package appeng.metrics.reporter;
import appeng.metrics.MetricVisitor;
/**
* Reports the metrics in a format compatible with Prometheus.
* <p>
* See <a href=
* "https://prometheus.io/docs/instrumenting/exposition_formats/">Prometheus
* Docs</a>
*/
public class PrometheusReporter implements MetricVisitor {
private final StringBuilder response = new StringBuilder();
@Override
public void visitGauge(String id, Number value) {
printType(id, "gauge");
appendId(id);
response.append(' ').append(value.toString()).append('\n');
}
private void printType(String id, String type) {
response.append("# TYPE ");
appendId(id);
response.append(' ').append(type).append('\n');
}
public void reset() {
response.setLength(0);
}
public int length() {
return response.length();
}
public CharSequence getReport() {
return response;
}
private void appendId(String id) {
response.append("appeng_");
// Sanitize to [a-zA-Z0-9:_]
for (int i = 0; i < id.length(); i++) {
char ch = id.charAt(i);
if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == ':' || ch == '_') {
response.append(ch);
} else {
response.append('_');
}
}
}
}
+2 -1
View File
@@ -19,6 +19,7 @@
package appeng.server; package appeng.server;
import appeng.server.subcommands.ChunkLogger; import appeng.server.subcommands.ChunkLogger;
import appeng.server.subcommands.MetricsCommand;
import appeng.server.subcommands.SpatialStorageCommand; import appeng.server.subcommands.SpatialStorageCommand;
import appeng.server.subcommands.Supporters; import appeng.server.subcommands.Supporters;
import appeng.server.subcommands.TestMeteoritesCommand; import appeng.server.subcommands.TestMeteoritesCommand;
@@ -27,7 +28,7 @@ import appeng.server.subcommands.TestOreGenCommand;
public enum Commands { public enum Commands {
Chunklogger(4, new ChunkLogger(), false), Supporters(0, new Supporters(), false), Chunklogger(4, new ChunkLogger(), false), Supporters(0, new Supporters(), false),
TestOreGen(4, new TestOreGenCommand(), true), TestMeteorites(4, new TestMeteoritesCommand(), true), TestOreGen(4, new TestOreGenCommand(), true), TestMeteorites(4, new TestMeteoritesCommand(), true),
Spatial(4, new SpatialStorageCommand(), false); Spatial(4, new SpatialStorageCommand(), false), Metrics(4, new MetricsCommand(), false);
public final int level; public final int level;
public final ISubCommand command; public final ISubCommand command;
@@ -0,0 +1,25 @@
package appeng.server.subcommands;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.command.CommandSource;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.text.StringTextComponent;
import appeng.metrics.Metrics;
import appeng.metrics.reporter.PrintStreamReporter;
import appeng.server.ISubCommand;
/**
* A simple way of printing out AE2's metrics on the server console.
*/
public class MetricsCommand implements ISubCommand {
@Override
public void call(MinecraftServer srv, CommandContext<CommandSource> ctx, CommandSource sender) {
System.out.println("-------- AE2 Metrics:");
Metrics.visit(new PrintStreamReporter(System.out));
System.out.println("-------- END");
sender.sendFeedback(new StringTextComponent("Metrics reported to server console..."), false);
}
}