diff --git a/pom.xml b/pom.xml
index 380347b..01110f9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,8 +112,23 @@
httpclient5
5.3.1
+
+ org.springframework.boot
+ spring-boot-actuator-autoconfigure
+
-
+
+
+ com.influxdb
+ influxdb-spring
+ 7.0.0
+ compile
+
+
+ com.influxdb
+ influxdb-client-java
+ 7.0.0
+
diff --git a/src/main/java/xyz/mcutils/backend/common/Timer.java b/src/main/java/xyz/mcutils/backend/common/Timer.java
new file mode 100644
index 0000000..c07a7d9
--- /dev/null
+++ b/src/main/java/xyz/mcutils/backend/common/Timer.java
@@ -0,0 +1,19 @@
+package xyz.mcutils.backend.common;
+
+public class Timer {
+
+ /**
+ * Schedules a task to run after a delay.
+ *
+ * @param runnable the task to run
+ * @param delay the delay before the task runs
+ */
+ public static void scheduleRepeating(Runnable runnable, long delay, long period) {
+ new java.util.Timer().scheduleAtFixedRate(new java.util.TimerTask() {
+ @Override
+ public void run() {
+ runnable.run();
+ }
+ }, delay, period);
+ }
+}
diff --git a/src/main/java/xyz/mcutils/backend/log/TransactionLogger.java b/src/main/java/xyz/mcutils/backend/log/TransactionLogger.java
index e971d6a..641e6ee 100644
--- a/src/main/java/xyz/mcutils/backend/log/TransactionLogger.java
+++ b/src/main/java/xyz/mcutils/backend/log/TransactionLogger.java
@@ -3,6 +3,7 @@ package xyz.mcutils.backend.log;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
@@ -12,6 +13,9 @@ import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import xyz.mcutils.backend.common.IPUtils;
+import xyz.mcutils.backend.service.MetricService;
+import xyz.mcutils.backend.service.metric.metrics.RequestsPerRouteMetric;
+import xyz.mcutils.backend.service.metric.metrics.TotalRequestsMetric;
import java.util.Arrays;
import java.util.HashMap;
@@ -21,6 +25,10 @@ import java.util.Map.Entry;
@ControllerAdvice
@Slf4j(topic = "Req Transaction")
public class TransactionLogger implements ResponseBodyAdvice
*/
+@Repository
public interface PlayerSkinPartCacheRepository extends CrudRepository { }
\ No newline at end of file
diff --git a/src/main/java/xyz/mcutils/backend/service/MetricService.java b/src/main/java/xyz/mcutils/backend/service/MetricService.java
new file mode 100644
index 0000000..3c8b231
--- /dev/null
+++ b/src/main/java/xyz/mcutils/backend/service/MetricService.java
@@ -0,0 +1,121 @@
+package xyz.mcutils.backend.service;
+
+import com.influxdb.client.WriteApiBlocking;
+import com.influxdb.spring.influx.InfluxDB2AutoConfiguration;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import xyz.mcutils.backend.common.Timer;
+import xyz.mcutils.backend.repository.MetricsRepository;
+import xyz.mcutils.backend.service.metric.Metric;
+import xyz.mcutils.backend.service.metric.metrics.RequestsPerRouteMetric;
+import xyz.mcutils.backend.service.metric.metrics.TotalRequestsMetric;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Service @Log4j2
+public class MetricService {
+ /**
+ * The metrics that are registered.
+ */
+ private final Map, Metric>> metrics = new HashMap<>();
+
+ /**
+ * The interval in which the metrics are saved.
+ */
+ private final long saveInterval = TimeUnit.MINUTES.toMillis(1);
+
+ /**
+ * The interval in which the metrics are written to InfluxDB.
+ */
+ private final long writeInfluxInterval = TimeUnit.SECONDS.toMillis(30);
+
+ private final WriteApiBlocking influxWriteApi;
+ private final MetricsRepository metricsRepository;
+
+ @Autowired
+ public MetricService(InfluxDB2AutoConfiguration influxAutoConfiguration, MetricsRepository metricsRepository) {
+ this.influxWriteApi = influxAutoConfiguration.influxDBClient().getWriteApiBlocking();
+ this.metricsRepository = metricsRepository;
+
+ // Register the metrics
+ registerMetric(new TotalRequestsMetric());
+ registerMetric(new RequestsPerRouteMetric());
+
+ // Load the metrics from Redis
+ loadMetrics();
+
+ Timer.scheduleRepeating(this::saveMetrics, saveInterval, saveInterval);
+ Timer.scheduleRepeating(this::writeToInflux, writeInfluxInterval, writeInfluxInterval);
+ }
+
+ /**
+ * Register a metric.
+ *
+ * @param metric the metric to register
+ */
+ public void registerMetric(Metric> metric) {
+ if (metrics.containsKey(metric.getClass())) {
+ throw new IllegalArgumentException("A metric with the class " + metric.getClass().getName() + " is already registered");
+ }
+ metrics.put(metric.getClass(), metric);
+ }
+
+ /**
+ * Get a metric by its class.
+ *
+ * @param clazz the class of the metric
+ * @return the metric
+ * @throws IllegalArgumentException if there is no metric with the class registered
+ */
+ public Metric> getMetric(Class> clazz) throws IllegalArgumentException {
+ if (!metrics.containsKey(clazz)) {
+ throw new IllegalArgumentException("No metric with the class " + clazz.getName() + " is registered");
+ }
+ return metrics.get(clazz);
+ }
+
+ /**
+ * Load all metrics from Redis.
+ */
+ public void loadMetrics() {
+ log.info("Loading metrics");
+ for (Metric> metric : metricsRepository.findAll()) {
+ metrics.put(metric.getClass(), metric);
+ }
+ log.info("Loaded {} metrics", metrics.size());
+ }
+
+ /**
+ * Save all metrics to Redis.
+ */
+ private void saveMetrics() {
+ log.info("Saving metrics to Redis");
+ for (Metric> metric : metrics.values()) {
+ saveMetric(metric);
+ }
+ log.info("Saved {} metrics", metrics.size());
+ }
+
+ /**
+ * Save a metric to Redis.
+ *
+ * @param metric the metric to save
+ */
+ private void saveMetric(Metric> metric) {
+ metricsRepository.save(metric); // Save the metric to the repository
+ }
+
+ /**
+ * Push all metrics to InfluxDB.
+ */
+ private void writeToInflux() {
+ log.info("Writing metrics to InfluxDB");
+ for (Metric> metric : metrics.values()) {
+ influxWriteApi.writePoint(metric.toPoint());
+ }
+ log.info("Wrote {} metrics", metrics.size());
+ }
+}
diff --git a/src/main/java/xyz/mcutils/backend/service/metric/Metric.java b/src/main/java/xyz/mcutils/backend/service/metric/Metric.java
new file mode 100644
index 0000000..7a5ed34
--- /dev/null
+++ b/src/main/java/xyz/mcutils/backend/service/metric/Metric.java
@@ -0,0 +1,32 @@
+package xyz.mcutils.backend.service.metric;
+
+import com.influxdb.annotations.Measurement;
+import com.influxdb.client.write.Point;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.redis.core.RedisHash;
+
+@AllArgsConstructor
+@Getter @Setter
+@RedisHash(value = "metric")
+@Measurement(name = "metric")
+public abstract class Metric {
+ /**
+ * The id of the metric.
+ */
+ @Id private String id;
+
+ /**
+ * The value of the metric.
+ */
+ private T value;
+
+ /**
+ * Gets this point as a {@link Point}.
+ *
+ * @return the point
+ */
+ public abstract Point toPoint();
+}
diff --git a/src/main/java/xyz/mcutils/backend/service/metric/impl/IntegerMetric.java b/src/main/java/xyz/mcutils/backend/service/metric/impl/IntegerMetric.java
new file mode 100644
index 0000000..d8c3a2b
--- /dev/null
+++ b/src/main/java/xyz/mcutils/backend/service/metric/impl/IntegerMetric.java
@@ -0,0 +1,49 @@
+package xyz.mcutils.backend.service.metric.impl;
+
+import com.influxdb.client.write.Point;
+import xyz.mcutils.backend.service.metric.Metric;
+
+public class IntegerMetric extends Metric {
+
+ public IntegerMetric(String id) {
+ super(id, 0);
+ }
+
+ /**
+ * Increment the value of this metric.
+ *
+ * @param amount the amount to increment by
+ */
+ public void increment(int amount) {
+ setValue(getValue() + amount);
+ }
+
+ /**
+ * Increment the value of this metric by 1.
+ */
+ public void increment() {
+ increment(1);
+ }
+
+ /**
+ * Decrement the value of this metric.
+ *
+ * @param amount the amount to decrement by
+ */
+ public void decrement(int amount) {
+ setValue(getValue() - amount);
+ }
+
+ /**
+ * Decrement the value of this metric by 1.
+ */
+ public void decrement() {
+ decrement(1);
+ }
+
+ @Override
+ public Point toPoint() {
+ return Point.measurement(getId())
+ .addField("value", getValue());
+ }
+}
diff --git a/src/main/java/xyz/mcutils/backend/service/metric/impl/MapMetric.java b/src/main/java/xyz/mcutils/backend/service/metric/impl/MapMetric.java
new file mode 100644
index 0000000..1f52a8a
--- /dev/null
+++ b/src/main/java/xyz/mcutils/backend/service/metric/impl/MapMetric.java
@@ -0,0 +1,23 @@
+package xyz.mcutils.backend.service.metric.impl;
+
+import com.influxdb.client.write.Point;
+import xyz.mcutils.backend.service.metric.Metric;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MapMetric extends Metric