From 66df771cb78781f6e76cdd9d7401b09c4e5aeb30 Mon Sep 17 00:00:00 2001 From: dalbodeule <11470513+dalbodeule@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:52:41 +0900 Subject: [PATCH] [feature] add some api, etc.. - add some api - add /metrics routing --- .../chzzk_bot/chatbot/chzzk/ChzzkHandler.kt | 3 ++ common/build.gradle.kts | 5 ++ .../mori/chzzk_bot/common/metrics/Metrics.kt | 25 ++++++++++ src/main/kotlin/space/mori/chzzk_bot/Main.kt | 2 + webserver/build.gradle.kts | 6 +-- .../space/mori/chzzk_bot/webserver/Main.kt | 22 +++++++++ .../chzzk_bot/webserver/routes/ApiRoutes.kt | 48 ++++++++++++++++++- .../webserver/routes/MetricRoutes.kt | 36 ++++++++++++++ 8 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 common/src/main/kotlin/space/mori/chzzk_bot/common/metrics/Metrics.kt create mode 100644 webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/MetricRoutes.kt diff --git a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkHandler.kt b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkHandler.kt index 2503000..a816786 100644 --- a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkHandler.kt +++ b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkHandler.kt @@ -9,6 +9,7 @@ import space.mori.chzzk_bot.chatbot.chzzk.Connector.getChannel import space.mori.chzzk_bot.chatbot.discord.Discord import space.mori.chzzk_bot.chatbot.utils.refreshAccessToken import space.mori.chzzk_bot.common.events.* +import space.mori.chzzk_bot.common.metrics.Metrics import space.mori.chzzk_bot.common.models.User import space.mori.chzzk_bot.common.services.LiveStatusService import space.mori.chzzk_bot.common.services.TimerConfigService @@ -293,6 +294,7 @@ class UserHandler( logger.info("ChzzkChat connecting... ${channel.channelName} - ${channel.channelId}") streamStartTime = LocalDateTime.now() + Metrics.streaming++ if(!_isActive) { _isActive = true @@ -329,6 +331,7 @@ class UserHandler( listener?.unsubscribeAsync(ChzzkSessionSubscriptionType.CHAT)?.join() listener?.disconnectAsync()?.join() _isActive = false + Metrics.streaming--; CoroutineScope(Dispatchers.Default).launch { val events = listOf( diff --git a/common/build.gradle.kts b/common/build.gradle.kts index b8eca0d..6308444 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -37,6 +37,11 @@ dependencies { // https://mvnrepository.com/artifact/com.google.code.gson/gson implementation("com.google.code.gson:gson:2.11.0") + api("io.micrometer:micrometer-registry-prometheus:1.15.1") + + // https://mvnrepository.com/artifact/io.insert-koin/koin-core + api("io.insert-koin:koin-core:4.0.0") + testImplementation(kotlin("test")) } diff --git a/common/src/main/kotlin/space/mori/chzzk_bot/common/metrics/Metrics.kt b/common/src/main/kotlin/space/mori/chzzk_bot/common/metrics/Metrics.kt new file mode 100644 index 0000000..e11a401 --- /dev/null +++ b/common/src/main/kotlin/space/mori/chzzk_bot/common/metrics/Metrics.kt @@ -0,0 +1,25 @@ +package space.mori.chzzk_bot.common.metrics + +import io.micrometer.core.instrument.Gauge +import io.micrometer.prometheusmetrics.PrometheusConfig +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry +import space.mori.chzzk_bot.common.services.UserService + +object Metrics { + val registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT) + + var streamer = UserService.getAllUsers().size.toDouble() + val streamerGauge: Gauge = Gauge.builder("streamer_gauge", this) { streamer } + .description("Current All Streamer Count") + .register(registry) + + var activeStreamer = UserService.getAllUsers().filter { !it.isDisabled }.size.toDouble() + val activateGauge: Gauge = Gauge.builder("active_streamer_gauge", this) { streamer } + .description("Current Active Streamer Count") + .register(registry) + + var streaming: Double = 0.0 + val streamingGauge: Gauge = Gauge.builder("streaming_gauge", this) { streaming } + .description("Current Streaming Streamer Count") + .register(registry) +} \ No newline at end of file diff --git a/src/main/kotlin/space/mori/chzzk_bot/Main.kt b/src/main/kotlin/space/mori/chzzk_bot/Main.kt index d3f42b9..be10e93 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/Main.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/Main.kt @@ -12,6 +12,7 @@ import space.mori.chzzk_bot.chatbot.discord.Discord import space.mori.chzzk_bot.chatbot.chzzk.Connector as ChzzkConnector import space.mori.chzzk_bot.common.Connector import space.mori.chzzk_bot.common.events.CoroutinesEventBus +import space.mori.chzzk_bot.common.metrics.Metrics import space.mori.chzzk_bot.webserver.start import space.mori.chzzk_bot.webserver.stop import java.util.concurrent.TimeUnit @@ -25,6 +26,7 @@ val logger: Logger = LoggerFactory.getLogger("main") fun main(args: Array) { val dispatcher = module { single { CoroutinesEventBus() } + single { Metrics.registry } } startKoin { modules(dispatcher) diff --git a/webserver/build.gradle.kts b/webserver/build.gradle.kts index 8867b5f..af7f705 100644 --- a/webserver/build.gradle.kts +++ b/webserver/build.gradle.kts @@ -34,16 +34,16 @@ dependencies { // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect implementation("org.jetbrains.kotlin:kotlin-reflect:2.0.21") - // https://mvnrepository.com/artifact/io.insert-koin/koin-core - implementation("io.insert-koin:koin-core:4.0.0") - // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic implementation("ch.qos.logback:logback-classic:1.5.12") // https://mvnrepository.com/artifact/io.github.cdimascio/dotenv-kotlin implementation("io.github.cdimascio:dotenv-kotlin:6.4.2") + implementation("io.ktor:ktor-server-metrics-micrometer:$ktorVersion") + implementation(project(":common")) + implementation("io.ktor:ktor-server-metrics-micrometer:3.1.3") testImplementation(kotlin("test")) } diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt index f48b1f1..5036719 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt @@ -11,6 +11,7 @@ import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.engine.* +import io.ktor.server.metrics.micrometer.MicrometerMetrics import io.ktor.server.netty.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.cors.routing.* @@ -19,9 +20,16 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.sessions.* import io.ktor.server.websocket.* +import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics +import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics +import io.micrometer.core.instrument.binder.system.ProcessorMetrics +import io.micrometer.prometheusmetrics.PrometheusConfig +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry import kotlinx.coroutines.delay import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import org.koin.core.context.startKoin +import org.koin.dsl.module import org.koin.java.KoinJavaComponent.inject import space.mori.chzzk_bot.common.events.CoroutinesEventBus import space.mori.chzzk_bot.common.events.UserRegisterEvent @@ -232,6 +240,18 @@ val server = embeddedServer(Netty, port = 8080, ) { } } + val appMicrometerRegistry: PrometheusMeterRegistry by inject(PrometheusMeterRegistry::class.java) + + install(MicrometerMetrics) { + registry = appMicrometerRegistry + + meterBinders = listOf( + JvmMemoryMetrics(), + JvmGcMetrics(), + ProcessorMetrics() + ) + } + apiRoutes() apiSongRoutes() apiCommandRoutes() @@ -242,6 +262,8 @@ val server = embeddedServer(Netty, port = 8080, ) { wsSongRoutes() wsSongListRoutes() + metricRoutes() + swaggerUI("swagger-ui/index.html", "openapi/documentation.yaml") { options { version = "1.2.0" diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiRoutes.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiRoutes.kt index c2a5e0d..f9e3db4 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiRoutes.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiRoutes.kt @@ -1,6 +1,7 @@ package space.mori.chzzk_bot.webserver.routes import io.ktor.http.* +import io.ktor.server.request.receive import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.sessions.* @@ -15,6 +16,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.withTimeoutOrNull import space.mori.chzzk_bot.common.events.ChzzkUserFindEvent import space.mori.chzzk_bot.common.events.ChzzkUserReceiveEvent +import space.mori.chzzk_bot.webserver.routes.GuildSettings @Serializable data class GetUserDTO( @@ -146,4 +148,48 @@ fun Routing.apiRoutes() { call.respond(HttpStatusCode.OK, returnUsers) } } -} \ No newline at end of file + + route("/settings") { + get { + val session = call.sessions.get() + if(session == null) { + call.respondText("No session found", status = HttpStatusCode.Unauthorized) + return@get + } + val user = UserService.getUser(session.id) + if(user == null) { + call.respondText("No user found", status = HttpStatusCode.NotFound) + return@get + } + + call.respond(HttpStatusCode.OK, IUserSettingsDTO( + user.isDisabled, + user.isDisableStartupMsg + )) + } + + post { + val session = call.sessions.get() + val body: IUserSettingsDTO = call.receive() + if(session == null) { + call.respondText("No session found", status = HttpStatusCode.Unauthorized) + return@post + } + val user = UserService.getUser(session.id) + if(user == null) { + call.respondText("No user found", status = HttpStatusCode.NotFound) + return@post + } + + UserService.setIsDisabled(user, body.isBotDisabled) + UserService.setIsStartupDisabled(user, body.isBotMsgDisabled) + + call.respond(HttpStatusCode.OK, body) + } + } +} + +data class IUserSettingsDTO( + val isBotDisabled: Boolean, + val isBotMsgDisabled: Boolean +) \ No newline at end of file diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/MetricRoutes.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/MetricRoutes.kt new file mode 100644 index 0000000..b1169f7 --- /dev/null +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/MetricRoutes.kt @@ -0,0 +1,36 @@ +package space.mori.chzzk_bot.webserver.routes + +import io.ktor.server.application.ApplicationStopped +import io.ktor.server.response.respondText +import io.ktor.server.routing.Routing +import io.ktor.server.routing.get +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import org.koin.java.KoinJavaComponent.inject +import space.mori.chzzk_bot.common.events.CoroutinesEventBus +import space.mori.chzzk_bot.common.events.UserRegisterEvent +import space.mori.chzzk_bot.common.metrics.Metrics +import kotlin.getValue + + +val metricScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) +fun Routing.metricRoutes() { + environment.monitor.subscribe(ApplicationStopped) { + metricScope.cancel() + } + + val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java) + val registry: PrometheusMeterRegistry by inject(PrometheusMeterRegistry::class.java) + + dispatcher.subscribe(UserRegisterEvent::class) { + Metrics.streamer++ + } + + get("/metrics") { + call.respondText(registry.scrape()) + } + +} \ No newline at end of file