[+] Metrics (#95)

* Add actuator and micrometer

* update

* [-] Remove unused import

* [O] Make code less verbose

* format

* refactor

---------

Co-authored-by: Azalea <22280294+hykilpikonna@users.noreply.github.com>
This commit is contained in:
Menci
2024-12-13 05:47:31 +08:00
committed by GitHub
parent 8434842c65
commit c5dad11e5e
15 changed files with 225 additions and 54 deletions

View File

@@ -2,9 +2,11 @@ package icu.samnyan.aqua.sega.maimai2
import ext.*
import icu.samnyan.aqua.net.utils.ApiException
import icu.samnyan.aqua.net.utils.simpleDescribe
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.maimai2.handler.*
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.spring.Metrics
import io.ktor.client.request.*
import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity
@@ -339,29 +341,42 @@ class Maimai2ServletController(
@API("/{api}")
fun handle(@PathVariable api: String, @RequestBody request: Map<String, Any>): Any {
try {
logger.info("Mai2 < $api : ${request.toJson()}") // TODO: Optimize logging
logger.info("Mai2 < $api : ${request.toJson()}") // TODO: Optimize logging
if (api !in noopEndpoint && api !in staticEndpoint && !handlers.containsKey(api)) {
logger.warn("Mai2 > $api not found")
return """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}"""
}
if (api in noopEndpoint) {
logger.info("Mai2 > $api no-op")
return """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}"""
}
// Only record the counter metrics if the API is known.
Metrics.counter("aquadx_maimai2_api_call", "api" to api).increment()
if (api in staticEndpoint) {
logger.info("Mai2 > $api static")
return staticEndpoint[api]!!
}
if (api in noopEndpoint) {
logger.info("Mai2 > $api no-op")
return """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}"""
}
return handlers[api]?.handle(request)?.let { if (it is String) it else it.toJson() }?.also {
if (api !in setOf("GetUserItemApi", "GetGameEventApi"))
logger.info("Mai2 > $api : $it")
} ?: {
logger.warn("Mai2 > $api not found")
"""{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}"""
if (api in staticEndpoint) {
logger.info("Mai2 > $api static")
return staticEndpoint[api]!!
}
return try {
Metrics.timer("aquadx_maimai2_api_latency", "api" to api).recordCallable {
handlers[api]!!.handle(request).let { if (it is String) it else it.toJson() }.also {
if (api !in setOf("GetUserItemApi", "GetGameEventApi"))
logger.info("Mai2 > $api : $it")
}
}
} catch (e: ApiException) {
// It's a bad practice to return 200 ok on error, but this is what maimai does so we have to follow
return ResponseEntity.ok().body("""{"returnCode":0,"apiName":"com.sega.maimai2servlet.api.$api","message":"${e.message?.replace("\"", "\\\"")} - ${e.code}"}""")
} catch (e: Exception) {
Metrics.counter(
"aquadx_maimai2_api_error",
"api" to api, "error" to e.simpleDescribe()
).increment()
if (e is ApiException) {
// It's a bad practice to return 200 ok on error, but this is what maimai does so we have to follow
return ResponseEntity.ok().body("""{"returnCode":0,"apiName":"com.sega.maimai2servlet.api.$api","message":"${e.message?.replace("\"", "\\\"")} - ${e.code}"}""")
} else throw e
}
}
}

View File

@@ -1,12 +1,14 @@
package icu.samnyan.aqua.sega.maimai2.handler
import ext.millis
import icu.samnyan.aqua.sega.allnet.TokenChecker
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPlaylogRepo
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.maimai2.model.request.UploadUserPlaylog
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPlaylog
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
import icu.samnyan.aqua.spring.Metrics
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import kotlin.jvm.optionals.getOrNull
@@ -24,11 +26,23 @@ class UploadUserPlaylogHandler(
companion object {
@JvmStatic
val playBacklog = mutableMapOf<Long, MutableList<BacklogEntry>>()
val VALID_GAME_IDS = setOf("SDEZ", "SDGA", "SDGB")
}
override fun handle(request: Map<String, Any>): String {
val req = mapper.convert(request, UploadUserPlaylog::class.java)
val version = tryParseGameVersion(req.userPlaylog.version)
if (version != null) {
val session = TokenChecker.getCurrentSession()
val gameId = if (session?.gameId in VALID_GAME_IDS) session!!.gameId else ""
Metrics.counter(
"aquadx_maimai2_playlog_game_version",
"game_id" to gameId, "version" to version
).increment()
}
// Save if the user is registered
val u = userDataRepository.findByCardExtId(req.userId).getOrNull()
if (u != null) playlogRepo.save(req.userPlaylog.apply { user = u })
@@ -52,4 +66,13 @@ class UploadUserPlaylogHandler(
playBacklog.filter { (_, v) -> v.isEmpty() || v[0].time - now > 300_000 }.toList()
.forEach { (k, _) -> playBacklog.remove(k) }
}
private fun tryParseGameVersion(version: Int): String? {
val major = version / 1000000
val minor = version / 1000 % 1000
if (major != 1) return null
if (minor !in 0..99) return null
// e.g. "1.30", minor should have two digits
return "$major.${minor.toString().padStart(2, '0')}"
}
}