Compare commits
1 Commits
textures-next
...
metrics
| Author | SHA1 | Date | |
|---|---|---|---|
| 53f7046f93 |
@@ -18,6 +18,7 @@
|
||||
|
||||
package appeng.client.render.cablebus;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
@@ -51,12 +52,21 @@ import net.minecraftforge.client.model.data.IModelData;
|
||||
import appeng.api.parts.IPartModel;
|
||||
import appeng.api.util.AECableType;
|
||||
import appeng.api.util.AEColor;
|
||||
import appeng.metrics.Metrics;
|
||||
|
||||
public class CableBusBakedModel implements IBakedModel {
|
||||
|
||||
// The number of quads overall that will be cached
|
||||
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 CableBuilder cableBuilder;
|
||||
@@ -74,6 +84,7 @@ public class CableBusBakedModel implements IBakedModel {
|
||||
this.partModels = partModels;
|
||||
this.particleTexture = particleTexture;
|
||||
this.cableModelCache = CacheBuilder.newBuilder()//
|
||||
.recordStats()//
|
||||
.maximumWeight(CACHE_QUAD_COUNT)//
|
||||
.weigher((Weigher<CableBusRenderState, List<BakedQuad>>) (key, value) -> value.size())//
|
||||
.build(new CacheLoader<CableBusRenderState, List<BakedQuad>>() {
|
||||
@@ -84,6 +95,7 @@ public class CableBusBakedModel implements IBakedModel {
|
||||
return model;
|
||||
}
|
||||
});
|
||||
cacheForMetrics = new WeakReference<>(cableModelCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -404,6 +404,10 @@ public final class AEConfig {
|
||||
return COMMON.improvedFluidMultiplier.get().floatValue();
|
||||
}
|
||||
|
||||
public int getPrometheusMetricsServerPort() {
|
||||
return COMMON.prometheusMetricsServer.get();
|
||||
}
|
||||
|
||||
// Setters keep visibility as low as possible.
|
||||
|
||||
private static class ClientConfig {
|
||||
@@ -474,6 +478,7 @@ public final class AEConfig {
|
||||
public final BooleanValue removeCrashingItemsOnLoad;
|
||||
public final ConfigValue<Integer> formationPlaneEntityLimit;
|
||||
public final ConfigValue<Integer> craftingCalculationTimePerTick;
|
||||
public final ConfigValue<Integer> prometheusMetricsServer;
|
||||
|
||||
// Spatial IO/Dimension
|
||||
public final ConfigValue<Double> spatialPowerExponent;
|
||||
@@ -558,6 +563,12 @@ public final class AEConfig {
|
||||
.define("removeCrashingItemsOnLoad", false);
|
||||
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");
|
||||
formationPlaneEntityLimit = builder.comment("TODO").define("formationPlaneEntityLimit", 128);
|
||||
builder.pop();
|
||||
|
||||
@@ -66,6 +66,7 @@ import appeng.entity.TinyTNTPrimedEntity;
|
||||
import appeng.entity.TinyTNTPrimedRenderer;
|
||||
import appeng.hooks.TickHandler;
|
||||
import appeng.integration.Integrations;
|
||||
import appeng.metrics.endpoint.PrometheusEndpoint;
|
||||
import appeng.parts.PartPlacement;
|
||||
import appeng.server.ServerHelper;
|
||||
|
||||
@@ -90,6 +91,8 @@ public final class AppEng {
|
||||
}
|
||||
INSTANCE = this;
|
||||
|
||||
startMetricsServer();
|
||||
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, AEConfig.CLIENT_SPEC);
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, AEConfig.COMMON_SPEC);
|
||||
|
||||
@@ -148,6 +151,20 @@ public final class AppEng {
|
||||
registerNetworkHandler();
|
||||
|
||||
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)
|
||||
|
||||
@@ -157,4 +157,8 @@ final class StorageData extends WorldSavedData implements IWorldGridStorageData
|
||||
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.world.server.ServerWorld;
|
||||
|
||||
import appeng.metrics.Metrics;
|
||||
import appeng.services.CompassService;
|
||||
import appeng.services.compass.CompassThreadFactory;
|
||||
|
||||
/**
|
||||
* Singleton access to anything related to world-based data.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* different worlds.
|
||||
@@ -53,8 +54,15 @@ public final class WorldData implements IWorldData {
|
||||
@Nullable
|
||||
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 IWorldGridStorageData storageData;
|
||||
private final StorageData storageData;
|
||||
private final IWorldCompassData compassData;
|
||||
|
||||
private WorldData(@Nonnull final ServerWorld overworld) {
|
||||
@@ -80,7 +88,6 @@ public final class WorldData implements IWorldData {
|
||||
|
||||
/**
|
||||
* @return ae2 data related to a specific world
|
||||
*
|
||||
* @deprecated do not use singletons which are dependent on specific world state
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -102,7 +109,7 @@ public final class WorldData implements IWorldData {
|
||||
|
||||
/**
|
||||
* Requires to start up from external from here
|
||||
*
|
||||
* <p>
|
||||
* drawback of the singleton build style
|
||||
*/
|
||||
public static void onServerStarting(MinecraftServer server) {
|
||||
|
||||
@@ -57,6 +57,7 @@ import appeng.core.AppEng;
|
||||
import appeng.core.sync.packets.PaintedEntityPacket;
|
||||
import appeng.crafting.CraftingJob;
|
||||
import appeng.me.Grid;
|
||||
import appeng.metrics.Metrics;
|
||||
import appeng.tile.AEBaseTileEntity;
|
||||
import appeng.util.IWorldCallable;
|
||||
import appeng.util.Platform;
|
||||
@@ -77,6 +78,11 @@ public class TickHandler {
|
||||
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) {
|
||||
eventBus.addListener(INSTANCE::onServerTick);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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('_');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
package appeng.server;
|
||||
|
||||
import appeng.server.subcommands.ChunkLogger;
|
||||
import appeng.server.subcommands.MetricsCommand;
|
||||
import appeng.server.subcommands.SpatialStorageCommand;
|
||||
import appeng.server.subcommands.Supporters;
|
||||
import appeng.server.subcommands.TestMeteoritesCommand;
|
||||
@@ -27,7 +28,7 @@ import appeng.server.subcommands.TestOreGenCommand;
|
||||
public enum Commands {
|
||||
Chunklogger(4, new ChunkLogger(), false), Supporters(0, new Supporters(), false),
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user