diff --git a/api/build.gradle b/api/build.gradle
index a42f8be..b268a15 100644
--- a/api/build.gradle
+++ b/api/build.gradle
@@ -11,4 +11,8 @@ dependencies {
api 'team.unnamed:creative-api:1.7.3'
api 'team.unnamed:creative-serializer-minecraft:1.7.3'
api 'team.unnamed:creative-server:1.7.3'
+
+ api 'org.sql2o:sql2o:1.8.0'
+ api 'com.clickhouse:clickhouse-jdbc:0.7.1'
+ api 'org.lz4:lz4-java:1.8.0'
}
diff --git a/api/src/main/java/ru/dragonestia/msb3/api/metrics/PlayerEventMetrics.java b/api/src/main/java/ru/dragonestia/msb3/api/metrics/PlayerEventMetrics.java
new file mode 100644
index 0000000..e54f37f
--- /dev/null
+++ b/api/src/main/java/ru/dragonestia/msb3/api/metrics/PlayerEventMetrics.java
@@ -0,0 +1,50 @@
+package ru.dragonestia.msb3.api.metrics;
+
+import com.google.gson.Gson;
+import lombok.extern.log4j.Log4j2;
+import net.minestom.server.entity.Player;
+import org.jetbrains.annotations.Nullable;
+import ru.dragonestia.msb3.api.module.ClickhouseMetricsModule;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Log4j2
+public class PlayerEventMetrics {
+
+ private static final int MAX_ALERTS = 5;
+ private static final AtomicInteger alertAboutModuleDisabled = new AtomicInteger(0);
+ private static final Gson GSON = new Gson();
+
+ private PlayerEventMetrics() {}
+
+ public static void push(Player player, String eventName, @Nullable Object details) {
+ var sql2o = ClickhouseMetricsModule.getSql2o();
+
+ if (sql2o == null) {
+ var alerts = alertAboutModuleDisabled.incrementAndGet();
+ if (alerts > MAX_ALERTS) return;
+
+ log.warn("ClickhouseMetricsModule is not initialized. Skipping sending metrics [{}/{}]", alerts, MAX_ALERTS);
+
+ if (alerts == MAX_ALERTS) {
+ log.warn("Disabling logs with warnings about ClickhouseMetricsModule");
+ }
+ return;
+ }
+
+ CompletableFuture.runAsync(() -> {
+ log.debug("Sending event '{}' for player '{}'({}) with data '{}'", eventName, player.getUsername(), player.getUuid(), GSON.toJson(details));
+ try (var con = sql2o.open()) {
+ con.createQuery("insert into events (uuid, player_name, timestamp, event_type, details) values (:uuid, :player_name, now(), :event_type, :details)", false)
+ .addParameter("uuid", player.getUuid())
+ .addParameter("player_name", player.getUsername())
+ .addParameter("event_type", eventName)
+ .addParameter("details", GSON.toJson(details))
+ .executeUpdate();
+ } catch (Exception ex) {
+ log.error(ex, ex);
+ }
+ });
+ }
+}
diff --git a/api/src/main/java/ru/dragonestia/msb3/api/module/ClickhouseMetricsModule.java b/api/src/main/java/ru/dragonestia/msb3/api/module/ClickhouseMetricsModule.java
new file mode 100644
index 0000000..52c41ac
--- /dev/null
+++ b/api/src/main/java/ru/dragonestia/msb3/api/module/ClickhouseMetricsModule.java
@@ -0,0 +1,21 @@
+package ru.dragonestia.msb3.api.module;
+
+import lombok.Getter;
+import lombok.extern.log4j.Log4j2;
+import org.sql2o.Sql2o;
+
+@Log4j2
+public class ClickhouseMetricsModule {
+
+ @Getter private static Sql2o sql2o;
+
+ private ClickhouseMetricsModule() {}
+
+ public static synchronized void init(String url, String username, String password) {
+ if (sql2o != null) return;
+
+ sql2o = new Sql2o(url, username, password);
+
+ log.info("Enabled Clickhouse metrics module");
+ }
+}
diff --git a/clickhouse/config.xml b/clickhouse/config.xml
new file mode 100644
index 0000000..2a54396
--- /dev/null
+++ b/clickhouse/config.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/clickhouse/docker-compose.yml b/clickhouse/docker-compose.yml
new file mode 100644
index 0000000..574391b
--- /dev/null
+++ b/clickhouse/docker-compose.yml
@@ -0,0 +1,11 @@
+version: '3'
+
+services:
+ clickhouse:
+ image: yandex/clickhouse-server
+ ports:
+ - '8123:8123'
+ volumes:
+ - './users.xml:/etc/clickhouse-server/users.d/users.xml'
+ - './init.sql:/docker-entrypoint-initdb.d/crate_database.sql'
+ - './config.xml:/etc/clickhouse-server/users.d/config.xml'
\ No newline at end of file
diff --git a/clickhouse/init.sql b/clickhouse/init.sql
new file mode 100644
index 0000000..626dfdb
--- /dev/null
+++ b/clickhouse/init.sql
@@ -0,0 +1,11 @@
+create database if not exists msb3;
+
+create table if not exists msb3.events
+(
+ uuid UUID,
+ player_name String(32),
+ timestamp DateTime default now(),
+ event_type LowCardinality(String),
+ details Nullable(String)
+) engine = ReplacingMergeTree partition by toYYYYMM(timestamp)
+primary key (uuid, event_type, timestamp);
diff --git a/clickhouse/users.xml b/clickhouse/users.xml
new file mode 100644
index 0000000..c075cc5
--- /dev/null
+++ b/clickhouse/users.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ ::/1
+ 127.0.0.1
+
+ default
+ default
+
+
+ example
+ default
+ default
+ msb3
+
+
+
\ No newline at end of file