From 937845fb744fc8616aef5e51eb4267767a0e48b1 Mon Sep 17 00:00:00 2001 From: yaansz Date: Tue, 8 Oct 2024 15:50:11 -0300 Subject: [PATCH] feat: metrics (prometheus + micrometer) --- build.gradle | 10 ++ .../controller/SocialTwitterGroup.java | 10 +- .../softawii/capivara/core/EmbedManager.java | 132 ------------------ .../capivara/events/TwitterListener.java | 9 +- .../capivara/metrics/SocialMetrics.java | 22 +++ .../capivara/metrics/VoiceMetrics.java | 69 +++++++++ .../services/TwitterParserConfigService.java | 7 +- .../capivara/services/VoiceDroneService.java | 23 ++- .../capivara/services/VoiceHiveService.java | 23 ++- src/main/resources/application.properties | 9 +- src/main/resources/social_en_US.properties | 2 + src/main/resources/social_pt_BR.properties | 1 + 12 files changed, 169 insertions(+), 148 deletions(-) delete mode 100644 src/main/java/com/softawii/capivara/core/EmbedManager.java create mode 100644 src/main/java/com/softawii/capivara/metrics/SocialMetrics.java create mode 100644 src/main/java/com/softawii/capivara/metrics/VoiceMetrics.java diff --git a/build.gradle b/build.gradle index 2276a29..3ef9454 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,16 @@ dependencies { implementation("com.github.Softawii:curupira:v1.0.0") { changing = true } + + implementation 'io.micrometer:micrometer-registry-prometheus:1.10.13' + implementation 'io.micrometer:micrometer-tracing-bridge-brave:1.0.12' + implementation ('org.springframework.boot:spring-boot-starter-actuator:2.7.0') { + exclude module: 'spring-boot-starter-logging' + } + + implementation ('org.springframework.boot:spring-boot-starter-web:2.7.0') { + exclude module: 'spring-boot-starter-logging' + } } tasks.register('deploy') { diff --git a/src/main/java/com/softawii/capivara/controller/SocialTwitterGroup.java b/src/main/java/com/softawii/capivara/controller/SocialTwitterGroup.java index f1ce515..beba864 100644 --- a/src/main/java/com/softawii/capivara/controller/SocialTwitterGroup.java +++ b/src/main/java/com/softawii/capivara/controller/SocialTwitterGroup.java @@ -33,20 +33,20 @@ public static Button generateDeleteButton(long authorId) { @DiscordCommand(name = "enable", description = "Enable the automatic Twitter link transformation service") public TextLocaleResponse enable(Guild guild) { - service.enable(guild.getIdLong()); + this.service.enable(guild.getIdLong()); return new TextLocaleResponse("social.twitter.enable.response", guild.getName()); } @DiscordCommand(name = "disable", description = "Disable the automatic Twitter link transformation service") public TextLocaleResponse disable(Guild guild) { - service.disable(guild.getIdLong()); + this.service.disable(guild.getIdLong()); return new TextLocaleResponse("social.twitter.disable.response", guild.getName()); } @DiscordButton(name = deleteBotTwitterMessage, ephemeral = true) public TextLocaleResponse delete(ButtonInteractionEvent event, @RequestInfo Member member) throws MissingPermissionsException { // Format: ButtonID:Owner - String ownerId = event.getComponentId().split(":")[1]; + String ownerId = event.getComponentId().split(":")[1]; String messageOwner = member.getId(); MessageChannelUnion channel = event.getChannel(); @@ -56,7 +56,7 @@ public TextLocaleResponse delete(ButtonInteractionEvent event, @RequestInfo Memb } channel.deleteMessageById(event.getMessageId()).queue(); - return new TextLocaleResponse("twitter.delete.response"); - } + return new TextLocaleResponse("social.twitter.delete.response"); + } } diff --git a/src/main/java/com/softawii/capivara/core/EmbedManager.java b/src/main/java/com/softawii/capivara/core/EmbedManager.java deleted file mode 100644 index afcf9f3..0000000 --- a/src/main/java/com/softawii/capivara/core/EmbedManager.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.softawii.capivara.core; - -import com.softawii.capivara.exceptions.FieldLengthException; -import com.softawii.capivara.exceptions.KeyNotFoundException; -import com.softawii.capivara.exceptions.UrlException; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; -import net.dv8tion.jda.api.interactions.components.ActionRow; -import org.springframework.stereotype.Component; - -import java.util.*; -import java.util.stream.Collectors; - -@Component -public class EmbedManager { - - private Map embeds; - - public EmbedManager() { - this.embeds = new HashMap<>(); - } - - public Map.Entry init() { - EmbedHandler handler = new EmbedHandler(); - String uuid = UUID.randomUUID().toString(); - - // It's in the map? Reset that shit bro - while (this.embeds.containsKey(uuid)) uuid = UUID.randomUUID().toString(); - - this.embeds.put(uuid, handler); - return Map.entry(uuid, handler); - } - - public EmbedHandler get(String key) throws KeyNotFoundException { - if (!this.embeds.containsKey(key)) throw new KeyNotFoundException(); - return this.embeds.get(key); - } - - public void destroy(String key) { - if (this.embeds.containsKey(key)) this.embeds.remove(key); - } - - public static class EmbedHandler { - private EmbedBuilder builder; - private String message; - private List fields; - private GuildChannel target; - private List activeRows; - - public EmbedHandler() { - this.builder = new EmbedBuilder().setTitle("Titulo muito legal!").setDescription("Descrição sensacional"); - this.fields = new ArrayList<>(); - } - - public void setTitle(String title) throws FieldLengthException { - try { - builder.setTitle(title); - } catch (IllegalArgumentException e) { - throw new FieldLengthException(e.getMessage()); - } - } - - public void setDescription(String description) throws FieldLengthException { - try { - builder.setDescription(description); - } catch (IllegalArgumentException e) { - throw new FieldLengthException(e.getMessage()); - } - } - - public void setImage(String url) throws UrlException { - try { - builder.setImage(url); - } catch (IllegalArgumentException e) { - throw new UrlException(e.getMessage()); - } - } - - public void addField(MessageEmbed.Field field) throws FieldLengthException { - if (this.fields.size() > 25) throw new FieldLengthException(); - this.fields.add(field); - } - - public void setField(MessageEmbed.Field field, int index) { - this.fields.set(index, field); - } - - public void removeField(int index) throws IndexOutOfBoundsException { - if (index < 0 || index > this.fields.size()) throw new IndexOutOfBoundsException(); - this.fields.remove(index); - } - - public List getFieldNames() { - return this.fields.stream().map(MessageEmbed.Field::getName).collect(Collectors.toList()); - } - - public GuildChannel getTarget() { - return target; - } - - public void setTarget(GuildChannel target) { - this.target = target; - } - - public List getActiveRows() { - return activeRows; - } - - public void setActiveRows(List activeRows) { - this.activeRows = activeRows; - } - - public String getMessage() { - return this.message; - } - - public void setMessage(String message) { - this.message = message; - } - - public MessageEmbed build() { - EmbedBuilder builder = new EmbedBuilder(this.builder); - - for (MessageEmbed.Field field : this.fields) { - builder.addField(field); - } - - return builder.build(); - } - } -} diff --git a/src/main/java/com/softawii/capivara/events/TwitterListener.java b/src/main/java/com/softawii/capivara/events/TwitterListener.java index 953cebd..fd61c6a 100644 --- a/src/main/java/com/softawii/capivara/events/TwitterListener.java +++ b/src/main/java/com/softawii/capivara/events/TwitterListener.java @@ -1,6 +1,7 @@ package com.softawii.capivara.events; import com.softawii.capivara.controller.SocialTwitterGroup; +import com.softawii.capivara.metrics.SocialMetrics; import com.softawii.capivara.services.TwitterParserConfigService; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Message; @@ -20,13 +21,15 @@ @Component @SuppressWarnings("unused") public class TwitterListener extends ListenerAdapter { + private final SocialMetrics metrics; private final Pattern twitterPattern; private final TwitterParserConfigService service; private static final char invisibleChar = 'â €'; // https://www.compart.com/en/unicode/U+2800 - public TwitterListener(JDA jda, TwitterParserConfigService service) { - this.service = service; + public TwitterListener(JDA jda, TwitterParserConfigService service, SocialMetrics metrics) { this.twitterPattern = Pattern.compile("^https://(twitter|x)\\.com/(?\\w+)/status/(?\\d+)([-a-zA-Z0-9()@:%_+.~#?&/=]*)$"); // https://stackoverflow.com/a/17773849 + this.service = service; + this.metrics = metrics; jda.addEventListener(this); } @@ -63,6 +66,8 @@ private void createTweetMessage(Long guildId, String replacementMessage, Message .addActionRow(SocialTwitterGroup.generateDeleteButton(originalMessage.getAuthor().getIdLong())) .setSuppressedNotifications(true) ).queue(); + + this.metrics.newTwitterParse(); } private Optional parseMessage(String twitterLink, User author) { diff --git a/src/main/java/com/softawii/capivara/metrics/SocialMetrics.java b/src/main/java/com/softawii/capivara/metrics/SocialMetrics.java new file mode 100644 index 0000000..17584d1 --- /dev/null +++ b/src/main/java/com/softawii/capivara/metrics/SocialMetrics.java @@ -0,0 +1,22 @@ +package com.softawii.capivara.metrics; + +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicLong; + +@Component +public class SocialMetrics { + + // General Count + private final AtomicLong parseCount; + + public SocialMetrics(MeterRegistry registry) { + // Injected + this.parseCount = registry.gauge("social.twitter.parse", new AtomicLong(0L)); + } + + public void newTwitterParse() { + this.parseCount.addAndGet(1L); + } +} diff --git a/src/main/java/com/softawii/capivara/metrics/VoiceMetrics.java b/src/main/java/com/softawii/capivara/metrics/VoiceMetrics.java new file mode 100644 index 0000000..f678fe1 --- /dev/null +++ b/src/main/java/com/softawii/capivara/metrics/VoiceMetrics.java @@ -0,0 +1,69 @@ +package com.softawii.capivara.metrics; + +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicLong; + +@Component +public class VoiceMetrics { + + private final MeterRegistry registry; + + // Agent + private final AtomicLong agentCount; + private final AtomicLong agentAdd; + private final AtomicLong agentRemove; + private final AtomicLong agentUpdate; + + // Master + private final AtomicLong masterCount; + private final AtomicLong masterAdd; + private final AtomicLong masterRemove; + private final AtomicLong masterUpdate; + + public VoiceMetrics(MeterRegistry registry) { + this.registry = registry; + this.agentCount = this.registry.gauge("voice.agents.active", new AtomicLong(0L)); + this.agentAdd = this.registry.gauge("voice.agents.add", new AtomicLong(0L)); + this.agentRemove = this.registry.gauge("voice.agents.remove", new AtomicLong(0L)); + this.agentUpdate = this.registry.gauge("voice.agents.update", new AtomicLong(0L)); + + this.masterCount = this.registry.gauge("voice.master.active", new AtomicLong(0L)); + this.masterAdd = this.registry.gauge("voice.master.add", new AtomicLong(0L)); + this.masterRemove = this.registry.gauge("voice.master.remove", new AtomicLong(0L)); + this.masterUpdate = this.registry.gauge("voice.master.update", new AtomicLong(0L)); + } + + public void agentCount(Long count) { + this.agentCount.set(count); + } + + public void agentCreated() { + this.agentAdd.addAndGet(1L); + } + + public void agentDestroyed() { + this.agentRemove.addAndGet(1L); + } + + public void agentUpdate() { + this.agentUpdate.addAndGet(1L); + } + + public void masterCount(Long count) { + this.masterCount.set(count); + } + + public void masterCreated() { + this.masterAdd.addAndGet(1L); + } + + public void masterDestroyed() { + this.masterRemove.addAndGet(1L); + } + + public void masterUpdate() { + this.masterUpdate.addAndGet(1L); + } +} diff --git a/src/main/java/com/softawii/capivara/services/TwitterParserConfigService.java b/src/main/java/com/softawii/capivara/services/TwitterParserConfigService.java index 959c723..c4670ac 100644 --- a/src/main/java/com/softawii/capivara/services/TwitterParserConfigService.java +++ b/src/main/java/com/softawii/capivara/services/TwitterParserConfigService.java @@ -2,6 +2,7 @@ import com.softawii.capivara.entity.TwitterParserConfig; import com.softawii.capivara.repository.TwitterParserConfigRepository; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Service; @Service @@ -22,6 +23,10 @@ public void enable(Long guildId) { } public void disable(Long guildId) { - repository.deleteById(guildId); + try { + repository.deleteById(guildId); + } catch(EmptyResultDataAccessException e) { + // Nothing + } } } diff --git a/src/main/java/com/softawii/capivara/services/VoiceDroneService.java b/src/main/java/com/softawii/capivara/services/VoiceDroneService.java index 5905950..bf439f7 100644 --- a/src/main/java/com/softawii/capivara/services/VoiceDroneService.java +++ b/src/main/java/com/softawii/capivara/services/VoiceDroneService.java @@ -2,6 +2,7 @@ import com.softawii.capivara.entity.VoiceDrone; import com.softawii.capivara.exceptions.KeyNotFoundException; +import com.softawii.capivara.metrics.VoiceMetrics; import com.softawii.capivara.repository.VoiceDroneRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -12,25 +13,37 @@ @Service public class VoiceDroneService { + private final VoiceMetrics metrics; private final VoiceDroneRepository voiceDroneRepository; - public VoiceDroneService(VoiceDroneRepository voiceDroneRepository) { + public VoiceDroneService(VoiceDroneRepository voiceDroneRepository, VoiceMetrics metrics) { this.voiceDroneRepository = voiceDroneRepository; + this.metrics = metrics; + + this.metrics.agentCount(count()); } - public VoiceDrone create(VoiceDrone voiceDrone) { + public void create(VoiceDrone voiceDrone) { if (voiceDroneRepository.existsById(voiceDrone.getChannelId())) throw new KeyAlreadyExistsException(); - return voiceDroneRepository.save(voiceDrone); + voiceDroneRepository.save(voiceDrone); + + this.metrics.agentCreated(); + this.metrics.agentCount(count()); } public void destroy(Long SnowflakeId) throws KeyNotFoundException { if (!voiceDroneRepository.existsById(SnowflakeId)) throw new KeyNotFoundException(); voiceDroneRepository.deleteById(SnowflakeId); + + this.metrics.agentDestroyed(); + this.metrics.agentCount(count()); } public void update(VoiceDrone drone) throws KeyNotFoundException { if (!voiceDroneRepository.existsById(drone.getChannelId())) throw new KeyNotFoundException(); voiceDroneRepository.save(drone); + + this.metrics.agentUpdate(); } public boolean exists(Long SnowflakeId) { @@ -45,6 +58,10 @@ public VoiceDrone findByChatId(Long snowflakeId) throws KeyNotFoundException { return voiceDroneRepository.findByChatId(snowflakeId); } + public Long count() { + return voiceDroneRepository.count(); + } + public Page findAll(Pageable request) { return voiceDroneRepository.findAll(request); } diff --git a/src/main/java/com/softawii/capivara/services/VoiceHiveService.java b/src/main/java/com/softawii/capivara/services/VoiceHiveService.java index 7a66c1d..88c095b 100644 --- a/src/main/java/com/softawii/capivara/services/VoiceHiveService.java +++ b/src/main/java/com/softawii/capivara/services/VoiceHiveService.java @@ -3,6 +3,7 @@ import com.softawii.capivara.entity.VoiceHive; import com.softawii.capivara.exceptions.ExistingDynamicCategoryException; import com.softawii.capivara.exceptions.KeyNotFoundException; +import com.softawii.capivara.metrics.VoiceMetrics; import com.softawii.capivara.repository.VoiceHiveRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -15,22 +16,35 @@ public class VoiceHiveService { private final VoiceHiveRepository voiceHiveRepository; + private final VoiceMetrics metrics; - public VoiceHiveService(VoiceHiveRepository voiceHiveRepository) { + public VoiceHiveService(VoiceHiveRepository voiceHiveRepository, VoiceMetrics metrics) { this.voiceHiveRepository = voiceHiveRepository; + this.metrics = metrics; + this.metrics.masterCount(count()); } public VoiceHive create(VoiceHive voiceHive) throws ExistingDynamicCategoryException { if (voiceHiveRepository.existsById(voiceHive.getCategoryId())) throw new ExistingDynamicCategoryException(); - return voiceHiveRepository.save(voiceHive); + VoiceHive hive = voiceHiveRepository.save(voiceHive); + + this.metrics.masterCreated(); + this.metrics.masterCount(count()); + + return hive; } public void destroy(Long SnowflakeId) throws KeyNotFoundException { Optional voiceHive = voiceHiveRepository.findById(SnowflakeId); if (voiceHive.isEmpty()) throw new KeyNotFoundException(); - - // voiceHiveRepository voiceHiveRepository.deleteById(SnowflakeId); + + this.metrics.masterDestroyed(); + this.metrics.masterCount(count()); + } + + public Long count() { + return this.voiceHiveRepository.count(); } public List findAllByGuildId(Long SnowflakeId) { @@ -39,6 +53,7 @@ public List findAllByGuildId(Long SnowflakeId) { public VoiceHive update(VoiceHive voiceHive) throws KeyNotFoundException { if (!voiceHiveRepository.existsById(voiceHive.getCategoryId())) throw new KeyNotFoundException(); + this.metrics.masterUpdate(); return voiceHiveRepository.save(voiceHive); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 84b44f6..7131a7a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,4 @@ +spring.application.name=softawii-capivara spring.datasource.url=jdbc:h2:file:./banco_h2;DB_CLOSE_DELAY=-1 spring.datasource.driverClassName=org.h2.Driver spring.jpa.database-platform=org.hibernate.dialect.H2Dialect @@ -5,4 +6,10 @@ spring.datasource.username=sa spring.datasource.password=sa hibernate.hbm2ddl.auto=update curupira.reset=false -logging.level.root=debug \ No newline at end of file +logging.level.root=debug + +management.metrics.tags.application=${spring.application.name} +management.endpoints.web.exposure.include=prometheus +management.metrics.export.prometheus.enabled=true +server.tomcat.mbeanregistry.enabled=true +management.metrics.distribution.percentiles-histogram.http.server.requests=true \ No newline at end of file diff --git a/src/main/resources/social_en_US.properties b/src/main/resources/social_en_US.properties index f5e1ce6..c7ad282 100644 --- a/src/main/resources/social_en_US.properties +++ b/src/main/resources/social_en_US.properties @@ -3,3 +3,5 @@ social.twitter.disable.response = The twitter link remapping was disabled social.error.missing_permissions = You don't have the necessary permissions to execute this command social.error.generic = An error occurred while executing this command + +social.twitter.delete.response = Message deleted successfully diff --git a/src/main/resources/social_pt_BR.properties b/src/main/resources/social_pt_BR.properties index 832333b..ec121d0 100644 --- a/src/main/resources/social_pt_BR.properties +++ b/src/main/resources/social_pt_BR.properties @@ -16,3 +16,4 @@ social.twitter.disable.response = O remapping de links do twitter foi desabilita social.error.missing_permissions = Você não tem as permissões necessárias para executar este comando social.error.generic = Ocorreu um erro ao executar este comando +social.twitter.delete.response = Mensagem removida com sucesso