This commit is contained in:
parent
56705af255
commit
6b33ac7330
14
API/pom.xml
14
API/pom.xml
@ -105,6 +105,20 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Error Reporting & Metrics -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.sentry</groupId>
|
||||||
|
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
|
||||||
|
<version>8.0.0-alpha.4</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.questdb</groupId>
|
||||||
|
<artifactId>questdb</artifactId>
|
||||||
|
<version>8.1.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.konghq</groupId>
|
<groupId>com.konghq</groupId>
|
||||||
<artifactId>unirest-java-core</artifactId>
|
<artifactId>unirest-java-core</artifactId>
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package me.braydon.tether.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
public interface IOperatingSystemMXBean {
|
||||||
|
/**
|
||||||
|
* Get the system's CPU usage for the entire system.
|
||||||
|
* If the value is 0.0, all cores are idle. If the
|
||||||
|
* value is 1.0, all cores were 100% utilized. If
|
||||||
|
* the value is negative, the usage is not available.
|
||||||
|
*
|
||||||
|
* @return the system cpu load
|
||||||
|
*/
|
||||||
|
double getSystemCpuLoad();
|
||||||
|
}
|
44
API/src/main/java/me/braydon/tether/common/IPUtils.java
Normal file
44
API/src/main/java/me/braydon/tether/common/IPUtils.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package me.braydon.tether.common;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class IPUtils {
|
||||||
|
private static final String[] IP_HEADERS = new String[] {
|
||||||
|
"CF-Connecting-IP",
|
||||||
|
"X-Forwarded-For"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the real IP from the given request.
|
||||||
|
*
|
||||||
|
* @param request the request
|
||||||
|
* @return the real IP
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static String getRealIp(@NonNull HttpServletRequest request) {
|
||||||
|
String ip = request.getRemoteAddr();
|
||||||
|
for (String headerName : IP_HEADERS) {
|
||||||
|
String header = request.getHeader(headerName);
|
||||||
|
if (header == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!header.contains(",")) { // Handle single IP
|
||||||
|
ip = header;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Handle multiple IPs
|
||||||
|
String[] ips = header.split(",");
|
||||||
|
for (String ipHeader : ips) {
|
||||||
|
ip = ipHeader;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,9 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.braydon.tether.common.IPUtils;
|
import me.braydon.tether.common.IPUtils;
|
||||||
|
import me.braydon.tether.metric.impl.RequestsMetric;
|
||||||
|
import me.braydon.tether.service.MetricsService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
@ -24,11 +27,21 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
|||||||
@ControllerAdvice
|
@ControllerAdvice
|
||||||
@Slf4j(topic = "Req/Res Transaction")
|
@Slf4j(topic = "Req/Res Transaction")
|
||||||
public class RequestLogger implements ResponseBodyAdvice<Object> {
|
public class RequestLogger implements ResponseBodyAdvice<Object> {
|
||||||
|
/**
|
||||||
|
* The metrics service to use.
|
||||||
|
*/
|
||||||
|
@NonNull private final MetricsService metricsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public RequestLogger(@NonNull MetricsService metricsService) {
|
||||||
|
this.metricsService = metricsService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
|
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
|
||||||
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||||
@ -36,6 +49,11 @@ public class RequestLogger implements ResponseBodyAdvice<Object> {
|
|||||||
HttpServletRequest request = ((ServletServerHttpRequest) rawRequest).getServletRequest();
|
HttpServletRequest request = ((ServletServerHttpRequest) rawRequest).getServletRequest();
|
||||||
HttpServletResponse response = ((ServletServerHttpResponse) rawResponse).getServletResponse();
|
HttpServletResponse response = ((ServletServerHttpResponse) rawResponse).getServletResponse();
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
if (metricsService.isEnabled()) {
|
||||||
|
RequestsMetric.incrementCodeCount(response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
// Get the request ip ip
|
// Get the request ip ip
|
||||||
String ip = IPUtils.getRealIp(request);
|
String ip = IPUtils.getRealIp(request);
|
||||||
|
|
||||||
|
41
API/src/main/java/me/braydon/tether/metric/Metric.java
Normal file
41
API/src/main/java/me/braydon/tether/metric/Metric.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package me.braydon.tether.metric;
|
||||||
|
|
||||||
|
import io.questdb.client.Sender;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import me.braydon.tether.common.EnvironmentUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor @Setter @Getter
|
||||||
|
public abstract class Metric {
|
||||||
|
/**
|
||||||
|
* The interval (in millis) at which
|
||||||
|
* this metric should be tracked.
|
||||||
|
* <p>
|
||||||
|
* If the interval is -1, it will not
|
||||||
|
* be automatically tracked.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private final long interval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unix time of when this metric was last tracked.
|
||||||
|
*/
|
||||||
|
private long lastTrack;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Sender applyEnvColumn(@NonNull Sender sender) {
|
||||||
|
return sender.stringColumn("env", EnvironmentUtils.isProduction() ? "production" : "staging");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track this metric.
|
||||||
|
*
|
||||||
|
* @param sender the sender to use
|
||||||
|
*/
|
||||||
|
public abstract void track(@NonNull Sender sender);
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package me.braydon.tether.metric.impl;
|
||||||
|
|
||||||
|
import io.questdb.client.Sender;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import me.braydon.tether.common.IOperatingSystemMXBean;
|
||||||
|
import me.braydon.tether.metric.Metric;
|
||||||
|
|
||||||
|
import javax.management.JMX;
|
||||||
|
import javax.management.ObjectName;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This metric is responsible for tracking
|
||||||
|
* the statistics of this application.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@Log4j2(topic = "App Statistics Collector")
|
||||||
|
public final class AppStatisticsMetric extends Metric {
|
||||||
|
/**
|
||||||
|
* The JMX to use for getting the system's CPU load, if available.
|
||||||
|
*/
|
||||||
|
private IOperatingSystemMXBean jmx;
|
||||||
|
|
||||||
|
public AppStatisticsMetric() {
|
||||||
|
super(TimeUnit.SECONDS.toMillis(10L));
|
||||||
|
try {
|
||||||
|
jmx = JMX.newMXBeanProxy(
|
||||||
|
ManagementFactory.getPlatformMBeanServer(),
|
||||||
|
ObjectName.getInstance("java.lang:type=OperatingSystem"),
|
||||||
|
IOperatingSystemMXBean.class
|
||||||
|
);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("OperatingSystemMXBean is not supported by the system, the system CPU usage won't be collected", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track this metric.
|
||||||
|
*
|
||||||
|
* @param sender the sender to use
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void track(@NonNull Sender sender) {
|
||||||
|
Runtime runtime = Runtime.getRuntime();
|
||||||
|
applyEnvColumn(sender.table("app-statistics")
|
||||||
|
.doubleColumn("systemCpu", jmx.getSystemCpuLoad() * 100D)
|
||||||
|
.longColumn("usedMemory", (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024))
|
||||||
|
.longColumn("freeMemory", runtime.freeMemory() / (1024 * 1024))
|
||||||
|
.longColumn("totalMemory", runtime.maxMemory() / (1024 * 1024))
|
||||||
|
.longColumn("uptime", ManagementFactory.getRuntimeMXBean().getUptime()))
|
||||||
|
.atNow();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package me.braydon.tether.metric.impl;
|
||||||
|
|
||||||
|
import io.questdb.client.Sender;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import me.braydon.tether.metric.Metric;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
public final class RequestsMetric extends Metric {
|
||||||
|
private static final Map<Integer, Integer> codeCounts = new HashMap<>();
|
||||||
|
|
||||||
|
public RequestsMetric() {
|
||||||
|
super(TimeUnit.SECONDS.toMillis(10L));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the request
|
||||||
|
* count for the given code.
|
||||||
|
*
|
||||||
|
* @param code the request code
|
||||||
|
*/
|
||||||
|
public static void incrementCodeCount(int code) {
|
||||||
|
codeCounts.merge(code, 1, Integer::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track this metric.
|
||||||
|
*
|
||||||
|
* @param sender the sender to use
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void track(@NonNull Sender sender) {
|
||||||
|
if (codeCounts.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sender = applyEnvColumn(sender.table("requests"));
|
||||||
|
for (Map.Entry<Integer, Integer> entry : codeCounts.entrySet()) {
|
||||||
|
sender.longColumn(String.valueOf(entry.getKey()), entry.getValue());
|
||||||
|
}
|
||||||
|
sender.atNow();
|
||||||
|
codeCounts.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package me.braydon.tether.metric.impl;
|
||||||
|
|
||||||
|
import io.questdb.client.Sender;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import me.braydon.tether.metric.Metric;
|
||||||
|
import me.braydon.tether.service.DiscordService;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
public final class TrackedUsersMetric extends Metric {
|
||||||
|
/**
|
||||||
|
* The amount of recently watched users.
|
||||||
|
*/
|
||||||
|
private static int recentlyWatchedUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Discord service to use.
|
||||||
|
*/
|
||||||
|
@NonNull private final DiscordService discordService;
|
||||||
|
|
||||||
|
public TrackedUsersMetric(@NonNull DiscordService discordService) {
|
||||||
|
super(TimeUnit.SECONDS.toMillis(10L));
|
||||||
|
this.discordService = discordService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the amount of recently watched users.
|
||||||
|
*/
|
||||||
|
public static void incrementRecentlyWatchedUsers() {
|
||||||
|
recentlyWatchedUsers++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track this metric.
|
||||||
|
*
|
||||||
|
* @param sender the sender to use
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void track(@NonNull Sender sender) {
|
||||||
|
applyEnvColumn(sender.table("tracked-users")
|
||||||
|
.longColumn("current", discordService.getTrackedUsers())
|
||||||
|
.longColumn("recent", recentlyWatchedUsers))
|
||||||
|
.atNow();
|
||||||
|
recentlyWatchedUsers = 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package me.braydon.tether.metric.impl;
|
||||||
|
|
||||||
|
import io.questdb.client.Sender;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import me.braydon.tether.metric.Metric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
public final class UserLookupTimingsMetric extends Metric {
|
||||||
|
private static Sender sender;
|
||||||
|
|
||||||
|
public UserLookupTimingsMetric(@NonNull Sender sender) {
|
||||||
|
super(-1);
|
||||||
|
UserLookupTimingsMetric.sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track this metric.
|
||||||
|
*
|
||||||
|
* @param timings the timings
|
||||||
|
*/
|
||||||
|
public static void track(long timings) {
|
||||||
|
applyEnvColumn(sender.table("timings")
|
||||||
|
.stringColumn("type", "user-lookup")
|
||||||
|
.longColumn("value", timings))
|
||||||
|
.atNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track this metric.
|
||||||
|
*
|
||||||
|
* @param sender the sender to use
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void track(@NonNull Sender sender) {}
|
||||||
|
}
|
@ -14,6 +14,8 @@ import lombok.extern.log4j.Log4j2;
|
|||||||
import me.braydon.tether.exception.impl.BadRequestException;
|
import me.braydon.tether.exception.impl.BadRequestException;
|
||||||
import me.braydon.tether.exception.impl.ResourceNotFoundException;
|
import me.braydon.tether.exception.impl.ResourceNotFoundException;
|
||||||
import me.braydon.tether.exception.impl.ServiceUnavailableException;
|
import me.braydon.tether.exception.impl.ServiceUnavailableException;
|
||||||
|
import me.braydon.tether.metric.impl.TrackedUsersMetric;
|
||||||
|
import me.braydon.tether.metric.impl.UserLookupTimingsMetric;
|
||||||
import me.braydon.tether.model.response.DiscordUserResponse;
|
import me.braydon.tether.model.response.DiscordUserResponse;
|
||||||
import me.braydon.tether.model.user.CachedDiscordUser;
|
import me.braydon.tether.model.user.CachedDiscordUser;
|
||||||
import me.braydon.tether.model.user.DiscordUser;
|
import me.braydon.tether.model.user.DiscordUser;
|
||||||
@ -23,9 +25,12 @@ import net.dv8tion.jda.api.entities.Activity;
|
|||||||
import net.dv8tion.jda.api.entities.Guild;
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
import net.dv8tion.jda.api.entities.Member;
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
import net.dv8tion.jda.api.entities.SelfUser;
|
import net.dv8tion.jda.api.entities.SelfUser;
|
||||||
|
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
|
||||||
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
|
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
|
||||||
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||||
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -40,7 +45,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Log4j2(topic = "Discord")
|
@Log4j2(topic = "Discord")
|
||||||
public final class DiscordService {
|
public final class DiscordService extends ListenerAdapter {
|
||||||
/**
|
/**
|
||||||
* A cache of users retrieved from Discord.
|
* A cache of users retrieved from Discord.
|
||||||
*/
|
*/
|
||||||
@ -48,12 +53,24 @@ public final class DiscordService {
|
|||||||
.expireAfterWrite(3L, TimeUnit.MINUTES)
|
.expireAfterWrite(3L, TimeUnit.MINUTES)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The token to use for the bot.
|
||||||
|
*/
|
||||||
@Value("${discord.bot-token}")
|
@Value("${discord.bot-token}")
|
||||||
private String botToken;
|
private String botToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The token to use for the user account.
|
||||||
|
*/
|
||||||
@Value("${discord.user-account-token}")
|
@Value("${discord.user-account-token}")
|
||||||
private String userAccountToken;
|
private String userAccountToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are metrics enabled?
|
||||||
|
*/
|
||||||
|
@Value("${questdb.enabled}")
|
||||||
|
private boolean metricsEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current instance of the Discord bot.
|
* The current instance of the Discord bot.
|
||||||
*/
|
*/
|
||||||
@ -64,6 +81,13 @@ public final class DiscordService {
|
|||||||
connectBot();
|
connectBot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGuildMemberJoin(@NotNull GuildMemberJoinEvent event) {
|
||||||
|
if (metricsEnabled) {
|
||||||
|
TrackedUsersMetric.incrementRecentlyWatchedUsers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a Discord user by their snowflake.
|
* Get a Discord user by their snowflake.
|
||||||
*
|
*
|
||||||
@ -103,7 +127,11 @@ public final class DiscordService {
|
|||||||
CachedDiscordUser cachedUser = cachedUsers.getIfPresent(snowflake);
|
CachedDiscordUser cachedUser = cachedUsers.getIfPresent(snowflake);
|
||||||
boolean fromCache = cachedUser != null;
|
boolean fromCache = cachedUser != null;
|
||||||
if (cachedUser == null) { // No cache, retrieve fresh data
|
if (cachedUser == null) { // No cache, retrieve fresh data
|
||||||
|
long before = System.currentTimeMillis();
|
||||||
cachedUser = new CachedDiscordUser(getUser(snowflake, member != null), System.currentTimeMillis());
|
cachedUser = new CachedDiscordUser(getUser(snowflake, member != null), System.currentTimeMillis());
|
||||||
|
if (metricsEnabled) {
|
||||||
|
UserLookupTimingsMetric.track(System.currentTimeMillis() - before);
|
||||||
|
}
|
||||||
cachedUsers.put(snowflake, cachedUser);
|
cachedUsers.put(snowflake, cachedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +149,20 @@ public final class DiscordService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the amount of users
|
||||||
|
* the bot is tracking.
|
||||||
|
*
|
||||||
|
* @return the tracked user count
|
||||||
|
*/
|
||||||
|
public int getTrackedUsers() {
|
||||||
|
int tracked = 0;
|
||||||
|
for (Guild guild : jda.getGuilds()) {
|
||||||
|
tracked += guild.getMemberCount();
|
||||||
|
}
|
||||||
|
return tracked;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects the bot to the Discord API.
|
* Connects the bot to the Discord API.
|
||||||
*/
|
*/
|
||||||
|
102
API/src/main/java/me/braydon/tether/service/MetricsService.java
Normal file
102
API/src/main/java/me/braydon/tether/service/MetricsService.java
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package me.braydon.tether.service;
|
||||||
|
|
||||||
|
import io.questdb.client.Sender;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import me.braydon.tether.metric.Metric;
|
||||||
|
import me.braydon.tether.metric.impl.AppStatisticsMetric;
|
||||||
|
import me.braydon.tether.metric.impl.RequestsMetric;
|
||||||
|
import me.braydon.tether.metric.impl.TrackedUsersMetric;
|
||||||
|
import me.braydon.tether.metric.impl.UserLookupTimingsMetric;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@Service @Log4j2(topic = "Metrics")
|
||||||
|
public final class MetricsService {
|
||||||
|
/**
|
||||||
|
* The Discord service some metrics use.
|
||||||
|
*/
|
||||||
|
@NonNull private final DiscordService discordService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metrics to automatically track.
|
||||||
|
*/
|
||||||
|
@NonNull private final List<Metric> metrics = new LinkedList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are metrics enabled?
|
||||||
|
*/
|
||||||
|
@Value("${questdb.enabled}")
|
||||||
|
@Getter private boolean enabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URI to the metrics server.
|
||||||
|
*/
|
||||||
|
@Value("${questdb.uri}")
|
||||||
|
private String dbUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sender client.
|
||||||
|
*/
|
||||||
|
private Sender sender;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public MetricsService(@NonNull DiscordService discordService) {
|
||||||
|
this.discordService = discordService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void onInitialize() {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Initialize the sender, and schedule a task to start tracking
|
||||||
|
sender = Sender.fromConfig(dbUrl);
|
||||||
|
log.info("Sender Configured!");
|
||||||
|
|
||||||
|
// Register metrics
|
||||||
|
metrics.add(new AppStatisticsMetric());
|
||||||
|
metrics.add(new TrackedUsersMetric(discordService));
|
||||||
|
metrics.add(new RequestsMetric());
|
||||||
|
new UserLookupTimingsMetric(sender);
|
||||||
|
log.info("Tracking {} metrics...", metrics.size());
|
||||||
|
|
||||||
|
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
trackMetrics();
|
||||||
|
}
|
||||||
|
}, 1000L, 1000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track the registered metrics.
|
||||||
|
*/
|
||||||
|
private void trackMetrics() {
|
||||||
|
int points = 0;
|
||||||
|
for (Metric metric : metrics) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (metric.getInterval() == -1L || (now - metric.getLastTrack()) < metric.getInterval()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
metric.setLastTrack(now);
|
||||||
|
metric.track(sender);
|
||||||
|
points++;
|
||||||
|
}
|
||||||
|
// Record the amount of points recorded a second
|
||||||
|
Metric.applyEnvColumn(sender.table("data-points")
|
||||||
|
.longColumn("value", points))
|
||||||
|
.atNow();
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,12 @@ logging:
|
|||||||
file:
|
file:
|
||||||
path: "./logs"
|
path: "./logs"
|
||||||
|
|
||||||
|
# Sentry Configuration
|
||||||
|
sentry:
|
||||||
|
dsn: "CHANGE_ME"
|
||||||
|
tracesSampleRate: 1.0
|
||||||
|
environment: "development"
|
||||||
|
|
||||||
# Discord Configuration
|
# Discord Configuration
|
||||||
discord:
|
discord:
|
||||||
# The user account token also for general API calls
|
# The user account token also for general API calls
|
||||||
@ -16,6 +22,11 @@ discord:
|
|||||||
# The bot token for realtime API calls (online status, activities, etc)
|
# The bot token for realtime API calls (online status, activities, etc)
|
||||||
bot-token: "CHANGE_ME"
|
bot-token: "CHANGE_ME"
|
||||||
|
|
||||||
|
# QuestDB Configuration (Metrics)
|
||||||
|
questdb:
|
||||||
|
enabled: false
|
||||||
|
uri: "http::addr=localhost:9000;username=tether;password=p4$$w0rd;auto_flush_interval=5000;"
|
||||||
|
|
||||||
# Spring Configuration
|
# Spring Configuration
|
||||||
spring:
|
spring:
|
||||||
# Ignore
|
# Ignore
|
||||||
|
Loading…
x
Reference in New Issue
Block a user