init repo
This commit is contained in:
29
core/build.gradle
Normal file
29
core/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
group 'ru.dmitriymx'
|
||||
version '1.0-SNAPSHOT'
|
||||
jar.archiveBaseName.set(project.name)
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor('org.projectlombok:lombok:1.18.20')
|
||||
compileOnly('org.projectlombok:lombok:1.18.20')
|
||||
|
||||
compileOnly('io.netty:netty-transport-native-epoll:4.1.24.Final')
|
||||
compileOnly('io.netty:netty-codec-http:4.1.24.Final')
|
||||
// compileOnly('org.slf4j:slf4j-api:1.7.32')
|
||||
|
||||
api('com.typesafe:config:1.4.1')
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.dmitriymx.minecraft.config;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
|
||||
public interface ConfigProvider {
|
||||
|
||||
Config get();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ru.dmitriymx.minecraft.logger;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class FormattingPair {
|
||||
|
||||
private final String message;
|
||||
private final Throwable throwable;
|
||||
|
||||
public FormattingPair(String message) {
|
||||
this.message = message;
|
||||
this.throwable = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ru.dmitriymx.minecraft.logger;
|
||||
|
||||
import ru.dmitriymx.minecraft.utils.StringFormatter;
|
||||
|
||||
public abstract class LoggerAdapter {
|
||||
|
||||
public abstract void debug(String message);
|
||||
public abstract void debug(String message, Throwable throwable);
|
||||
public abstract void info(String message);
|
||||
public abstract void warn(String message);
|
||||
public abstract void error(String message);
|
||||
public abstract void error(String message, Throwable throwable);
|
||||
|
||||
public void debug(String pattern, Object... objects) {
|
||||
FormattingPair formattingPair = LoggerFormatter.arrayFormat(pattern, objects);
|
||||
if (formattingPair.getThrowable() != null) {
|
||||
debug(formattingPair.getMessage(), formattingPair.getThrowable());
|
||||
} else {
|
||||
debug(formattingPair.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void info(String pattern, Object... objects) {
|
||||
info(StringFormatter.arrayFormat(pattern, objects));
|
||||
}
|
||||
|
||||
public void warn(String pattern, Object... objects) {
|
||||
warn(StringFormatter.arrayFormat(pattern, objects));
|
||||
}
|
||||
|
||||
public void error(String pattern, Object... objects) {
|
||||
FormattingPair formattingPair = LoggerFormatter.arrayFormat(pattern, objects);
|
||||
if (formattingPair.getThrowable() != null) {
|
||||
error(formattingPair.getMessage(), formattingPair.getThrowable());
|
||||
} else {
|
||||
error(formattingPair.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package ru.dmitriymx.minecraft.logger;
|
||||
|
||||
import ru.dmitriymx.minecraft.utils.StringFormatter;
|
||||
|
||||
/**
|
||||
* Copy-Paste from org.slf4j.helpers.MessageFormatter
|
||||
*/
|
||||
public final class LoggerFormatter {
|
||||
|
||||
public static FormattingPair arrayFormat(String messagePattern, Object[] argArray) {
|
||||
Object[] args;
|
||||
Throwable throwableCandidate = getThrowableCandidate(argArray);
|
||||
if (throwableCandidate != null) {
|
||||
args = trimmedCopy(argArray);
|
||||
} else {
|
||||
args = argArray;
|
||||
}
|
||||
|
||||
return arrayFormat(messagePattern, args, throwableCandidate);
|
||||
}
|
||||
|
||||
public static FormattingPair arrayFormat(String messagePattern, Object[] argArray, Throwable throwable) {
|
||||
if (messagePattern == null) {
|
||||
return new FormattingPair(null, throwable);
|
||||
}
|
||||
|
||||
if (argArray == null) {
|
||||
return new FormattingPair(messagePattern);
|
||||
}
|
||||
|
||||
return new FormattingPair(StringFormatter.arrayFormat(messagePattern, argArray), throwable);
|
||||
}
|
||||
|
||||
private static Throwable getThrowableCandidate(Object[] argArray) {
|
||||
if (argArray == null || argArray.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object lastEntry = argArray[argArray.length - 1];
|
||||
if (lastEntry instanceof Throwable) {
|
||||
return (Throwable) lastEntry;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Object[] trimmedCopy(final Object[] argArray) {
|
||||
if (argArray == null || argArray.length == 0) {
|
||||
throw new IllegalStateException("non-sensical empty or null argument array");
|
||||
}
|
||||
|
||||
int trimmedLen = argArray.length - 1;
|
||||
Object[] trimmed = new Object[trimmedLen];
|
||||
|
||||
if (trimmedLen > 0) {
|
||||
System.arraycopy(argArray, 0, trimmed, 0, trimmedLen);
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package ru.dmitriymx.minecraft.metrics;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import ru.dmitriymx.minecraft.logger.LoggerAdapter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class HttpHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final FullHttpResponse responseNotFound;
|
||||
|
||||
private final String endpoint;
|
||||
private final LoggerAdapter logger;
|
||||
private final MinecraftInfoProvider minecraftInfoProvider;
|
||||
|
||||
public HttpHandler(String endpoint, LoggerAdapter logger, MinecraftInfoProvider minecraftInfoProvider) {
|
||||
this.endpoint = endpoint;
|
||||
this.logger = logger;
|
||||
this.minecraftInfoProvider = minecraftInfoProvider;
|
||||
|
||||
ByteBuf byteBuf = Unpooled.wrappedBuffer("404: Not Found".getBytes(StandardCharsets.UTF_8));
|
||||
this.responseNotFound = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND, byteBuf);
|
||||
this.responseNotFound.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
|
||||
this.responseNotFound.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (!(msg instanceof HttpRequest)) {
|
||||
if (!(msg instanceof LastHttpContent)) {
|
||||
logger.debug("Incoming request is unknown");
|
||||
logger.debug("request class: {}", msg.getClass());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
channelRead1(ctx, (HttpRequest) msg);
|
||||
}
|
||||
|
||||
private void channelRead1(ChannelHandlerContext ctx, HttpRequest request) {
|
||||
HttpResponse response;
|
||||
|
||||
if (request.method() != HttpMethod.GET) {
|
||||
logger.warn("Incoming request method \"{}\" not allowed", request.method());
|
||||
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.METHOD_NOT_ALLOWED);
|
||||
} else {
|
||||
QueryStringDecoder queryString = new QueryStringDecoder(request.uri());
|
||||
logger.debug("uri.path: {}", queryString.path());
|
||||
|
||||
if (!queryString.path().equals(endpoint)) {
|
||||
response = responseNotFound;
|
||||
} else {
|
||||
ByteBuf buffer = Unpooled.wrappedBuffer(metrics().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buffer);
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
|
||||
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buffer.readableBytes());
|
||||
}
|
||||
}
|
||||
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
private String metrics() {
|
||||
return players() + tps();
|
||||
}
|
||||
|
||||
private String players() {
|
||||
return "# HELP mc_players_online Online players\n" +
|
||||
"# TYPE mc_players_online gauge\n" +
|
||||
"mc_players_online " + minecraftInfoProvider.playersOnline() + '\n';
|
||||
}
|
||||
|
||||
private String tps() {
|
||||
return "# HELP mc_tps TPS\n" +
|
||||
"# TYPE mc_tps gauge\n" +
|
||||
"mc_tps " + minecraftInfoProvider.tps() + '\n';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package ru.dmitriymx.minecraft.metrics;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import ru.dmitriymx.minecraft.logger.LoggerAdapter;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class MetricsServer {
|
||||
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final String endpoint;
|
||||
private final LoggerAdapter loggerAdapter;
|
||||
private final MinecraftInfoProvider minecraftInfoProvider;
|
||||
|
||||
private EventLoopGroup bossGroup;
|
||||
private EventLoopGroup workerGroup;
|
||||
|
||||
@SneakyThrows
|
||||
public void start() {
|
||||
bossGroup = createEventLoopGroup();
|
||||
workerGroup = createEventLoopGroup();
|
||||
|
||||
buildServerBootstrap().bind(host, port)
|
||||
.sync().channel().closeFuture();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void stop() {
|
||||
workerGroup.shutdownGracefully().sync();
|
||||
bossGroup.shutdownGracefully().sync();
|
||||
}
|
||||
|
||||
private EventLoopGroup createEventLoopGroup() {
|
||||
if (Epoll.isAvailable()) {
|
||||
return new EpollEventLoopGroup(1);
|
||||
} else {
|
||||
return new NioEventLoopGroup(1);
|
||||
}
|
||||
}
|
||||
|
||||
private ServerBootstrap buildServerBootstrap() {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(new HttpServerCodec())
|
||||
.addLast(new HttpHandler(endpoint, loggerAdapter, minecraftInfoProvider));
|
||||
}
|
||||
});
|
||||
|
||||
return bootstrap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.dmitriymx.minecraft.metrics;
|
||||
|
||||
public interface MinecraftInfoProvider {
|
||||
|
||||
int playersOnline();
|
||||
|
||||
double tps();
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package ru.dmitriymx.minecraft.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Copy-Paste from org.slf4j.helpers.MessageFormatter
|
||||
*/
|
||||
public final class StringFormatter {
|
||||
private static final String EMPTY = "";
|
||||
private static final char DELIM_START = '{';
|
||||
private static final String DELIM_STR = "{}";
|
||||
private static final char ESCAPE_CHAR = '\\';
|
||||
|
||||
public static String arrayFormat(String messagePattern, Object[] argArray) {
|
||||
if (messagePattern == null) {
|
||||
return EMPTY;
|
||||
} else if (argArray == null) {
|
||||
return messagePattern;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(messagePattern.length() + 50);
|
||||
|
||||
int k = 0;
|
||||
for (int i = 0; i < argArray.length; i++) {
|
||||
int idx = messagePattern.indexOf(DELIM_STR, k);
|
||||
|
||||
if (idx == -1) {
|
||||
// no more variables
|
||||
if (k == 0) { // this is a simple string
|
||||
return messagePattern;
|
||||
} else { // add the tail string which contains no variables and return
|
||||
// the result.
|
||||
sb.append(messagePattern, k, messagePattern.length());
|
||||
return sb.toString();
|
||||
}
|
||||
} else {
|
||||
if (isEscapedDelimeter(messagePattern, idx)) {
|
||||
if (!isDoubleEscaped(messagePattern, idx)) {
|
||||
i--; // DELIM_START was escaped, thus should not be incremented
|
||||
sb.append(messagePattern, k, idx - 1);
|
||||
sb.append(DELIM_START);
|
||||
k = idx + 1;
|
||||
} else {
|
||||
// The escape character preceding the delimiter start is
|
||||
// itself escaped: "abc x:\\{}"
|
||||
// we have to consume one backward slash
|
||||
sb.append(messagePattern, k, idx - 1);
|
||||
deeplyAppendParameter(sb, argArray[i], new HashMap<>());
|
||||
k = idx + 2;
|
||||
}
|
||||
} else {
|
||||
sb.append(messagePattern, k, idx);
|
||||
deeplyAppendParameter(sb, argArray[i], new HashMap<>());
|
||||
k = idx + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
// append the characters following the last {} pair.
|
||||
sb.append(messagePattern, k, messagePattern.length());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {
|
||||
if (delimeterStartIndex == 0) {
|
||||
return false;
|
||||
}
|
||||
char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
|
||||
return potentialEscape == ESCAPE_CHAR;
|
||||
}
|
||||
|
||||
private static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {
|
||||
return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR;
|
||||
}
|
||||
|
||||
// special treatment of array values was suggested by 'lizongbo'
|
||||
private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {
|
||||
if (o == null) {
|
||||
sbuf.append("null");
|
||||
return;
|
||||
}
|
||||
if (!o.getClass().isArray()) {
|
||||
safeObjectAppend(sbuf, o);
|
||||
} else {
|
||||
// check for primitive array types because they
|
||||
// unfortunately cannot be cast to Object[]
|
||||
if (o instanceof boolean[]) {
|
||||
booleanArrayAppend(sbuf, (boolean[]) o);
|
||||
} else if (o instanceof byte[]) {
|
||||
byteArrayAppend(sbuf, (byte[]) o);
|
||||
} else if (o instanceof char[]) {
|
||||
charArrayAppend(sbuf, (char[]) o);
|
||||
} else if (o instanceof short[]) {
|
||||
shortArrayAppend(sbuf, (short[]) o);
|
||||
} else if (o instanceof int[]) {
|
||||
intArrayAppend(sbuf, (int[]) o);
|
||||
} else if (o instanceof long[]) {
|
||||
longArrayAppend(sbuf, (long[]) o);
|
||||
} else if (o instanceof float[]) {
|
||||
floatArrayAppend(sbuf, (float[]) o);
|
||||
} else if (o instanceof double[]) {
|
||||
doubleArrayAppend(sbuf, (double[]) o);
|
||||
} else {
|
||||
objectArrayAppend(sbuf, (Object[]) o, seenMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void safeObjectAppend(StringBuilder sbuf, Object o) {
|
||||
try {
|
||||
String oAsString = o.toString();
|
||||
sbuf.append(oAsString);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
|
||||
sbuf.append('[');
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
|
||||
sbuf.append('[');
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void charArrayAppend(StringBuilder sbuf, char[] a) {
|
||||
sbuf.append('[');
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
|
||||
sbuf.append('[');
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void intArrayAppend(StringBuilder sbuf, int[] a) {
|
||||
sbuf.append('[');
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void longArrayAppend(StringBuilder sbuf, long[] a) {
|
||||
sbuf.append('[');
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
|
||||
sbuf.append('[');
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
|
||||
sbuf.append('[');
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sbuf.append(a[i]);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
|
||||
private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {
|
||||
sbuf.append('[');
|
||||
if (!seenMap.containsKey(a)) {
|
||||
seenMap.put(a, null);
|
||||
int len = a.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
deeplyAppendParameter(sbuf, a[i], seenMap);
|
||||
if (i != len - 1) {
|
||||
sbuf.append(", ");
|
||||
}
|
||||
}
|
||||
// allow repeats in siblings
|
||||
seenMap.remove(a);
|
||||
} else {
|
||||
sbuf.append("...");
|
||||
}
|
||||
sbuf.append(']');
|
||||
}
|
||||
}
|
||||
5
core/src/main/resources/assets/config-default.conf
Normal file
5
core/src/main/resources/assets/config-default.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
server {
|
||||
host: "0.0.0.0"
|
||||
port: 9225
|
||||
endpoint: "/metrics"
|
||||
}
|
||||
Reference in New Issue
Block a user