254 lines
8.0 KiB
Java
254 lines
8.0 KiB
Java
package com.hypixel.hytale.metrics;
|
|
|
|
import com.hypixel.hytale.codec.Codec;
|
|
import com.hypixel.hytale.codec.ExtraInfo;
|
|
import com.hypixel.hytale.codec.schema.SchemaContext;
|
|
import com.hypixel.hytale.codec.schema.config.Schema;
|
|
import com.hypixel.hytale.logger.HytaleLogger;
|
|
import com.hypixel.hytale.logger.backend.HytaleFileHandler;
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
|
import java.io.BufferedWriter;
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.time.LocalDateTime;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.concurrent.locks.StampedLock;
|
|
import java.util.function.Function;
|
|
import javax.annotation.CheckForNull;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
import org.bson.BsonDocument;
|
|
import org.bson.BsonValue;
|
|
import org.bson.codecs.BsonDocumentCodec;
|
|
import org.bson.codecs.EncoderContext;
|
|
import org.bson.json.JsonMode;
|
|
import org.bson.json.JsonWriter;
|
|
import org.bson.json.JsonWriterSettings;
|
|
|
|
public class MetricsRegistry<T> implements Codec<T> {
|
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
|
public static final JsonWriterSettings JSON_SETTINGS = JsonWriterSettings.builder()
|
|
.outputMode(JsonMode.STRICT)
|
|
.indent(false)
|
|
.newLineCharacters("\n")
|
|
.int64Converter((value, writer) -> writer.writeNumber(Long.toString(value)))
|
|
.build();
|
|
private static final EncoderContext ENCODER_CONTEXT = EncoderContext.builder().build();
|
|
private static final BsonDocumentCodec BSON_DOCUMENT_CODEC = new BsonDocumentCodec();
|
|
@Nullable
|
|
private final Function<T, MetricProvider> appendFunc;
|
|
private final StampedLock lock = new StampedLock();
|
|
private final Map<String, MetricsRegistry.Metric<T, ?>> map = new Object2ObjectLinkedOpenHashMap();
|
|
|
|
public MetricsRegistry() {
|
|
this.appendFunc = null;
|
|
}
|
|
|
|
public MetricsRegistry(Function<T, MetricProvider> appendFunc) {
|
|
this.appendFunc = appendFunc;
|
|
}
|
|
|
|
public MetricsRegistry<T> register(String id, MetricsRegistry<Void> metricsRegistry) {
|
|
long stamp = this.lock.writeLock();
|
|
|
|
try {
|
|
if (this.map.putIfAbsent(id, new MetricsRegistry.Metric<>(null, metricsRegistry)) != null) {
|
|
throw new IllegalArgumentException("Metric already registered: " + id);
|
|
}
|
|
} finally {
|
|
this.lock.unlockWrite(stamp);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
public <R> MetricsRegistry<T> register(String id, Function<T, R> func, Codec<R> codec) {
|
|
long stamp = this.lock.writeLock();
|
|
|
|
try {
|
|
if (this.map.putIfAbsent(id, new MetricsRegistry.Metric<>(func, codec)) != null) {
|
|
throw new IllegalArgumentException("Metric already registered: " + id);
|
|
}
|
|
} finally {
|
|
this.lock.unlockWrite(stamp);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
public <R extends MetricProvider> MetricsRegistry<T> register(String id, @Nonnull Function<T, R> func) {
|
|
return this.register(id, func.andThen(r -> r == null ? null : r.toMetricResults()), MetricResults.CODEC);
|
|
}
|
|
|
|
@Deprecated
|
|
public <R> MetricsRegistry<T> register(String id, Function<T, R> func, Function<R, MetricsRegistry<R>> codecFunc) {
|
|
long stamp = this.lock.writeLock();
|
|
|
|
try {
|
|
if (this.map.putIfAbsent(id, new MetricsRegistry.Metric<>(func, codecFunc)) != null) {
|
|
throw new IllegalArgumentException("Metric already registered: " + id);
|
|
}
|
|
} finally {
|
|
this.lock.unlockWrite(stamp);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public T decode(BsonValue bsonValue, ExtraInfo extraInfo) {
|
|
throw new UnsupportedOperationException("Not implemented");
|
|
}
|
|
|
|
@Override
|
|
public BsonValue encode(T t, ExtraInfo extraInfo) {
|
|
BsonDocument document = new BsonDocument();
|
|
long stamp = this.lock.readLock();
|
|
|
|
try {
|
|
for (Entry<String, MetricsRegistry.Metric<T, ?>> entry : this.map.entrySet()) {
|
|
String key = entry.getKey();
|
|
BsonValue value = entry.getValue().encode(t, extraInfo);
|
|
if (value != null) {
|
|
document.put(key, value);
|
|
}
|
|
}
|
|
} finally {
|
|
this.lock.unlockRead(stamp);
|
|
}
|
|
|
|
if (this.appendFunc != null) {
|
|
MetricProvider metricProvider = this.appendFunc.apply(t);
|
|
if (metricProvider != null) {
|
|
MetricResults metricResults = metricProvider.toMetricResults();
|
|
if (metricResults != null) {
|
|
document.putAll(metricResults.getBson());
|
|
}
|
|
}
|
|
}
|
|
|
|
return document;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public Schema toSchema(@Nonnull SchemaContext context) {
|
|
throw new UnsupportedOperationException("Not implemented");
|
|
}
|
|
|
|
@Nonnull
|
|
public MetricResults toMetricResults(T t) {
|
|
return new MetricResults(this.dumpToBson(t).asDocument());
|
|
}
|
|
|
|
public BsonValue dumpToBson(T t) {
|
|
ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
|
|
BsonDocument bson = this.encode(t, extraInfo).asDocument();
|
|
extraInfo.getValidationResults().logOrThrowValidatorExceptions(LOGGER);
|
|
return bson;
|
|
}
|
|
|
|
@Nonnull
|
|
public Path dumpToJson(T t) throws IOException {
|
|
Path path = createDumpPath(".dump.json");
|
|
this.dumpToJson(path, t);
|
|
return path;
|
|
}
|
|
|
|
public void dumpToJson(@Nonnull Path path, T t) throws IOException {
|
|
BsonValue bson = this.dumpToBson(t);
|
|
|
|
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
|
|
BSON_DOCUMENT_CODEC.encode(new JsonWriter(writer, JSON_SETTINGS), bson.asDocument(), ENCODER_CONTEXT);
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public static Path createDumpPath(@Nullable String ext) throws IOException {
|
|
return createDumpPath((String)null, ext);
|
|
}
|
|
|
|
@Nonnull
|
|
public static Path createDumpPath(@Nonnull Path dir, @Nullable String ext) {
|
|
return createDatePath(dir, null, ext);
|
|
}
|
|
|
|
@Nonnull
|
|
public static Path createDumpPath(@Nullable String prefix, @Nullable String ext) throws IOException {
|
|
Path path = Paths.get("dumps");
|
|
if (!Files.exists(path)) {
|
|
Files.createDirectories(path);
|
|
}
|
|
|
|
return createDatePath(path, prefix, ext);
|
|
}
|
|
|
|
@Nonnull
|
|
public static Path createDatePath(@Nonnull Path dir, @Nullable String prefix, @Nullable String suffix) {
|
|
String name = HytaleFileHandler.LOG_FILE_DATE_FORMAT.format(LocalDateTime.now());
|
|
if (prefix != null) {
|
|
name = prefix + name;
|
|
}
|
|
|
|
Path file = suffix != null ? dir.resolve(name + suffix) : dir.resolve(name);
|
|
int i = 0;
|
|
|
|
while (Files.exists(file)) {
|
|
if (suffix != null) {
|
|
file = dir.resolve(name + "_" + i++ + suffix);
|
|
} else {
|
|
file = dir.resolve(name + "_" + i++);
|
|
}
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
private static class Metric<T, R> {
|
|
@Nullable
|
|
private final Function<T, R> func;
|
|
@CheckForNull
|
|
private final Codec<R> codec;
|
|
@CheckForNull
|
|
private final Function<R, MetricsRegistry<R>> codecFunc;
|
|
|
|
public Metric(@Nullable Function<T, R> func, @Nullable Codec<R> codec) {
|
|
this.func = func;
|
|
this.codec = codec;
|
|
this.codecFunc = null;
|
|
}
|
|
|
|
public Metric(@Nullable Function<T, R> func, @Nullable Function<R, MetricsRegistry<R>> codecFunc) {
|
|
this.func = func;
|
|
this.codec = null;
|
|
this.codecFunc = codecFunc;
|
|
}
|
|
|
|
@Nullable
|
|
public BsonValue encode(T t, ExtraInfo extraInfo) {
|
|
if (this.func == null) {
|
|
assert this.codec != null;
|
|
|
|
return this.codec.encode(null, extraInfo);
|
|
} else {
|
|
R value = this.func.apply(t);
|
|
return value == null ? null : this.getCodec(value).encode(value, extraInfo);
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public Codec<R> getCodec(R value) {
|
|
if (this.codec != null) {
|
|
return this.codec;
|
|
} else {
|
|
assert this.codecFunc != null;
|
|
|
|
return this.codecFunc.apply(value);
|
|
}
|
|
}
|
|
}
|
|
}
|