diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/SpringWorkspaceEventConfig.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/SpringWorkspaceEventConfig.kt index a08bf0e..e2deefe 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/SpringWorkspaceEventConfig.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/SpringWorkspaceEventConfig.kt @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Configuration import ru.vityaman.lms.botalka.app.spring.api.http.message.toMessage import ru.vityaman.lms.botalka.app.spring.api.http.message.toModel import ru.vityaman.lms.botalka.app.spring.api.http.server.WorkspaceEventMessage +import ru.vityaman.lms.botalka.app.spring.monitoring.MetricsFactory import ru.vityaman.lms.botalka.app.spring.storage.MainR2dbcConfig import ru.vityaman.lms.botalka.commons.Consumer import ru.vityaman.lms.botalka.commons.Runnable @@ -21,6 +22,7 @@ import ru.vityaman.lms.botalka.core.external.telegram.TelegramBot import ru.vityaman.lms.botalka.core.external.telegram.TelegramChat import ru.vityaman.lms.botalka.core.external.telegram.TelegramConsumer import ru.vityaman.lms.botalka.core.logging.Slf4jLog +import ru.vityaman.lms.botalka.core.logic.metered.MeteredConsumer import ru.vityaman.lms.botalka.core.model.Workspace import ru.vityaman.lms.botalka.core.tx.TxEnv import ru.vityaman.lms.botalka.storage.jooq.JooqDatabase @@ -68,12 +70,16 @@ class SpringWorkspaceEventConfig { @Bean @Qualifier(BeanName.KAFKA_CONSUMER) fun consumer( + metrics: MetricsFactory, @Value("\${broker.bootstrap-servers}") bootstrapServers: String, @Qualifier(BeanName.WORKSPACE_TOPIC) topic: KafkaTopic, - ) = BasicKafkaProducer(BasicKafkaProducer.Config(bootstrapServers), topic) - .asConsumerWithKey(Workspace.Event::id) + ) = MeteredConsumer( + metrics.consumer("kafka-workspace-event").status(), + BasicKafkaProducer(BasicKafkaProducer.Config(bootstrapServers), topic) + .asConsumerWithKey(Workspace.Event::id), + ) @Bean @Qualifier(BeanName.EVENTS) @@ -102,35 +108,39 @@ class SpringWorkspaceEventConfig { @Bean @Qualifier(BeanName.TELEGRAM_CONSUMER) fun telegramConsumer( + metrics: MetricsFactory, telegram: TelegramBot, @Value("\${external.service.telegram.admin-chat-id}") adminChatId: Long, - ) = TelegramConsumer( - telegram, - TelegramChat(adminChatId), - ) { - buildString { - append("New workspace event!\n") - append("From user with id ${it.producer}\n") - append("EventId: ${it.id}\n") - append("Kind: ") - append( - when (it) { - is Workspace.Feedback -> "Feedback" - is Workspace.Submission -> "Submission" - is Workspace.Comment -> "Comment" - }, - ) - append("\n") - append( - when (it) { - is Workspace.Feedback -> it.comment - is Workspace.Submission -> it.note - is Workspace.Comment -> it.text - }, - ) - } - } + ) = MeteredConsumer( + metrics.consumer("telegram-workspace-event").status(), + TelegramConsumer( + telegram, + TelegramChat(adminChatId), + ) { + buildString { + append("New workspace event!\n") + append("From user with id ${it.producer}\n") + append("EventId: ${it.id}\n") + append("Kind: ") + append( + when (it) { + is Workspace.Feedback -> "Feedback" + is Workspace.Submission -> "Submission" + is Workspace.Comment -> "Comment" + }, + ) + append("\n") + append( + when (it) { + is Workspace.Feedback -> it.comment + is Workspace.Submission -> it.note + is Workspace.Comment -> it.text + }, + ) + } + }, + ) @Bean @Qualifier(BeanName.EVENT_CONSUMING_ACTOR) diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/homework/SpringKafkaConsumer.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/homework/SpringKafkaConsumer.kt index 992edd7..30d9d47 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/homework/SpringKafkaConsumer.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/homework/SpringKafkaConsumer.kt @@ -4,7 +4,9 @@ import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component import ru.vityaman.lms.botalka.app.spring.event.KafkaTopicConfig +import ru.vityaman.lms.botalka.app.spring.monitoring.MetricsFactory import ru.vityaman.lms.botalka.commons.Consumer +import ru.vityaman.lms.botalka.core.logic.metered.MeteredConsumer import ru.vityaman.lms.botalka.core.model.Homework import ru.vityaman.lms.botalka.storage.kafka.BasicKafkaProducer import ru.vityaman.lms.botalka.storage.kafka.KafkaTopic @@ -12,11 +14,16 @@ import ru.vityaman.lms.botalka.storage.kafka.KafkaTopic @Component @Qualifier(SpringConfig.BeanName.KAFKA_CONSUMER) class SpringKafkaConsumer( + metrics: MetricsFactory, + @Value("\${broker.bootstrap-servers}") bootstrapServers: String, @Qualifier(KafkaTopicConfig.BeanName.HOMEWORK_TOPIC) topic: KafkaTopic, ) : Consumer by -BasicKafkaProducer(BasicKafkaProducer.Config(bootstrapServers), topic) - .asConsumerWithKey(Homework::id) +MeteredConsumer( + metrics.consumer("kafka-homework").status(), + BasicKafkaProducer(BasicKafkaProducer.Config(bootstrapServers), topic) + .asConsumerWithKey(Homework::id), +) diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/homework/SpringTelegramConsumer.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/homework/SpringTelegramConsumer.kt index f2becdf..9d4375e 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/homework/SpringTelegramConsumer.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/event/homework/SpringTelegramConsumer.kt @@ -3,28 +3,34 @@ package ru.vityaman.lms.botalka.app.spring.event.homework import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component +import ru.vityaman.lms.botalka.app.spring.monitoring.MetricsFactory import ru.vityaman.lms.botalka.commons.Consumer import ru.vityaman.lms.botalka.core.external.telegram.TelegramBot import ru.vityaman.lms.botalka.core.external.telegram.TelegramChat import ru.vityaman.lms.botalka.core.external.telegram.TelegramConsumer +import ru.vityaman.lms.botalka.core.logic.metered.MeteredConsumer import ru.vityaman.lms.botalka.core.model.Homework @Component @Qualifier(SpringConfig.BeanName.TELEGRAM_CONSUMER) class SpringTelegramConsumer( + metrics: MetricsFactory, telegram: TelegramBot, @Value("\${external.service.telegram.admin-chat-id}") adminChatId: Long, ) : Consumer by -TelegramConsumer(telegram, TelegramChat(adminChatId), { - buildString { - append("Published homework '${it.title.text}'!\n") - append("\n") - append("${it.description}\n") - append("\n") - append("MaxScore: ${it.maxScore.value}\n") - append("Deadline: ${it.deadlineMoment}\n") - append("Id: ${it.id.number}\n") - } -}) +MeteredConsumer( + metrics.consumer("telegram-homework").status(), + TelegramConsumer(telegram, TelegramChat(adminChatId), { + buildString { + append("Published homework '${it.title.text}'!\n") + append("\n") + append("${it.description}\n") + append("\n") + append("MaxScore: ${it.maxScore.value}\n") + append("Deadline: ${it.deadlineMoment}\n") + append("Id: ${it.id.number}\n") + } + }), +) diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/monitoring/MetricsFactory.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/monitoring/MetricsFactory.kt index a6daf24..83d292b 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/monitoring/MetricsFactory.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/monitoring/MetricsFactory.kt @@ -3,33 +3,38 @@ package ru.vityaman.lms.botalka.app.spring.monitoring import io.micrometer.core.instrument.Counter import io.micrometer.core.instrument.MeterRegistry import org.springframework.stereotype.Component -import ru.vityaman.lms.botalka.core.logic.metered.StatusCount @Component class MetricsFactory(private val registry: MeterRegistry) { + fun consumer(name: String) = Consumer(name) fun service(name: String) = Service(name) + inner class Consumer(private val name: String) { + @Suppress("LabeledExpression") + fun status() = counter( + "lms_consumer_accept", + "consumer" to this@Consumer.name, + ).let { status(it) } + } + inner class Service(private val name: String) { fun method(name: String) = Method(name) inner class Method(private val name: String) { @Suppress("LabeledExpression") - fun status(): StatusCount { - val count = Counter - .builder("lms_service_method_call") - .tag("service", this@Service.name) - .tag("method", this@Method.name) - .withRegistry(registry) + fun status() = counter( + "lms_service_method_call", + "service" to this@Service.name, + "method" to this@Method.name, + ).let { status(it) } + } + } - return StatusCount( - successes = MicrometerCount( - count.withTag("status", "success"), - ), - failures = MicrometerCount( - count.withTag("status", "failure"), - ), - ) - } + fun counter(name: String, vararg tags: Pair) = run { + var counter = Counter.builder(name) + for ((key, value) in tags) { + counter = counter.tag(key, value) } + counter.withRegistry(registry) } } diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/monitoring/MicrometerCount.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/monitoring/MicrometerCount.kt index d2c20c8..cab50f0 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/monitoring/MicrometerCount.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/monitoring/MicrometerCount.kt @@ -1,7 +1,9 @@ package ru.vityaman.lms.botalka.app.spring.monitoring import io.micrometer.core.instrument.Counter +import io.micrometer.core.instrument.Meter import ru.vityaman.lms.botalka.core.monitoring.Count +import ru.vityaman.lms.botalka.core.monitoring.StatusCount class MicrometerCount( private val origin: Counter, @@ -9,3 +11,8 @@ class MicrometerCount( override fun add(amount: Int) = origin.increment(amount.toDouble()) } + +fun status(provider: Meter.MeterProvider) = StatusCount( + successes = MicrometerCount(provider.withTag("status", "success")), + failures = MicrometerCount(provider.withTag("status", "failure")), +) diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/security/SpringAuthority.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/security/SpringAuthority.kt index 37d893b..a2060ba 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/security/SpringAuthority.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/app/spring/security/SpringAuthority.kt @@ -1,8 +1,59 @@ package ru.vityaman.lms.botalka.app.spring.security import org.springframework.stereotype.Component +import ru.vityaman.lms.botalka.app.spring.monitoring.MetricsFactory +import ru.vityaman.lms.botalka.app.spring.monitoring.MicrometerCount +import ru.vityaman.lms.botalka.app.spring.monitoring.status +import ru.vityaman.lms.botalka.commons.ExhaustiveMap +import ru.vityaman.lms.botalka.core.model.User import ru.vityaman.lms.botalka.core.security.persmission.Authority import ru.vityaman.lms.botalka.core.security.persmission.BasicAuthority +import ru.vityaman.lms.botalka.core.security.persmission.MeteredAuthority +import ru.vityaman.lms.botalka.core.security.persmission.Permission.Kind.GetEvents +import ru.vityaman.lms.botalka.core.security.persmission.Permission.Kind.PostHomework +import ru.vityaman.lms.botalka.core.security.persmission.Permission.Kind.ResolvePromotion +import ru.vityaman.lms.botalka.core.security.persmission.Permission.Kind.SendFeedback +import ru.vityaman.lms.botalka.core.security.persmission.Permission.Kind.SendSubmission @Component -class SpringAuthority : Authority by BasicAuthority() +class SpringAuthority( + metrics: MetricsFactory, +) : Authority by +MeteredAuthority( + run { + MeteredAuthority.Metrics( + role = { kind: String -> + MicrometerCount( + metrics + .counter("lms_authorization_user_role") + .withTag("kind", kind), + ) + }.let { + ExhaustiveMap.from { + when (it) { + User.Role.ADMIN -> it("admin") + User.Role.TEACHER -> it("teacher") + User.Role.STUDENT -> it("student") + } + } + }, + permission = { kind: String -> + metrics.counter( + "lms_authorization_permission", + "kind" to kind, + ).let { cnt -> status(cnt) } + }.let { + ExhaustiveMap.from { + when (it) { + ResolvePromotion -> it("resolve-promotion") + GetEvents -> it("get-events") + SendFeedback -> it("send-feedback") + SendSubmission -> it("send-submission") + PostHomework -> it("post-homework") + } + } + }, + ) + }, + BasicAuthority(), +) diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/commons/ExhaustiveMap.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/commons/ExhaustiveMap.kt new file mode 100644 index 0000000..2bde054 --- /dev/null +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/commons/ExhaustiveMap.kt @@ -0,0 +1,35 @@ +package ru.vityaman.lms.botalka.commons + +import java.util.EnumMap +import kotlin.enums.enumEntries + +@OptIn(ExperimentalStdlibApi::class) +interface ExhaustiveMap { + operator fun get(key: K): V + + companion object { + inline fun , V> from( + vararg pairs: Pair, + ): ExhaustiveMap = object : ExhaustiveMap { + private val origin = pairs + .associateTo(EnumMap(K::class.java)) { (l, r) -> l to r } + + init { + val keys = pairs.map { it.first } + require(keys.distinct().size == keys.size) + require(enumEntries().size == keys.size) + } + + override operator fun get(key: K): V = origin[key]!! + } + + inline fun , V> from( + mapping: (K) -> V, + ): ExhaustiveMap { + val entries = enumEntries() + return Array(entries.size) { + entries[it] to mapping(entries[it]) + }.let { from(pairs = it) } + } + } +} diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logging/Log.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logging/Log.kt index 5875219..4603cc7 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logging/Log.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logging/Log.kt @@ -3,4 +3,5 @@ package ru.vityaman.lms.botalka.core.logging interface Log { fun info(message: String) fun warn(message: String) + fun debug(message: String) } diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logging/Slf4jLog.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logging/Slf4jLog.kt index 8d77c6b..c678831 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logging/Slf4jLog.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logging/Slf4jLog.kt @@ -5,11 +5,12 @@ import org.slf4j.LoggerFactory class Slf4jLog(name: String) : Log { private val origin = LoggerFactory.getLogger(name) - override fun info(message: String) { + override fun info(message: String) = origin.info(message) - } - override fun warn(message: String) { + override fun warn(message: String) = origin.warn(message) - } + + override fun debug(message: String) = + origin.debug(message) } diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingAuthService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingAuthService.kt index 764f067..00f4b9b 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingAuthService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingAuthService.kt @@ -12,13 +12,13 @@ class LoggingAuthService( ) : AuthService { override suspend fun signUp(draft: User.Draft, credentials: T): AuthUser = runCatching { origin.signUp(draft, credentials) } - .onSuccess { log.info("User with id ${it.user.id} signed up") } - .onFailure { log.warn("Failed to sign up") } + .onSuccess { log.debug("User with id ${it.user.id} signed up") } + .onFailure { log.debug("Failed to sign up") } .getOrThrow() override suspend fun signIn(credentials: T): AccessToken = runCatching { origin.signIn(credentials) } - .onSuccess { log.info("Signed in") } - .onFailure { log.warn("Failed to sign in") } + .onSuccess { log.debug("Signed in") } + .onFailure { log.debug("Failed to sign in") } .getOrThrow() } diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingHomeworkService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingHomeworkService.kt index 6431843..5cb65f7 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingHomeworkService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingHomeworkService.kt @@ -10,14 +10,14 @@ class LoggingHomeworkService( ) : HomeworkService { override suspend fun create(homework: Homework.Draft): Homework = runCatching { origin.create(homework) } - .onSuccess { log.info("Created homework with id ${it.id}") } - .onFailure { log.warn("Failed to create a homework") } + .onSuccess { log.debug("Created homework with id ${it.id}") } + .onFailure { log.debug("Failed to create a homework") } .getOrThrow() override suspend fun getById(id: Homework.Id): Homework? = runCatching { origin.getById(id) } .onFailure { - log.warn("Failed to get homework with id $id: ${it.message}") + log.debug("Failed to get homework with id $id: ${it.message}") } .getOrThrow() } diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingPromotionService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingPromotionService.kt index 53e6881..72f4a48 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingPromotionService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingPromotionService.kt @@ -17,33 +17,33 @@ class LoggingPromotionService( append("Created a promotion request with id ${it.id} ") append("of user with id ${promotion.user} to ") append("${promotion.role}") - }.let { log.info(it) } + }.let { log.debug(it) } } .onFailure { buildString { append("Failed to request promotion of user with ") append("id ${promotion.user} to ${promotion.role}") - }.let { log.warn(it) } + }.let { log.debug(it) } } .getOrThrow() override suspend fun approve(id: PromotionRequest.Id) = runCatching { origin.approve(id) } .onSuccess { - log.info("Promotion request with id $id was approved") + log.debug("Promotion request with id $id was approved") } .onFailure { - log.warn("Failed to approve promotion request with is $id") + log.debug("Failed to approve promotion request with is $id") } .getOrThrow() override suspend fun reject(id: PromotionRequest.Id) = runCatching { origin.reject(id) } .onSuccess { - log.info("Promotion request with id $id was rejected") + log.debug("Promotion request with id $id was rejected") } .onFailure { - log.warn("Failed to reject promotion request with is id $id") + log.debug("Failed to reject promotion request with is id $id") } .getOrThrow() } diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingRatingService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingRatingService.kt index b59492a..3a974d0 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingRatingService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingRatingService.kt @@ -10,6 +10,6 @@ class LoggingRatingService( ) : RatingService { override suspend fun grades(): Grades = runCatching { origin.grades() } - .onSuccess { log.info("Got grades for ${it.size} students") } + .onSuccess { log.debug("Got grades for ${it.size} students") } .getOrThrow() } diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingUserService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingUserService.kt index 655d5b3..395e8e5 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingUserService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingUserService.kt @@ -10,14 +10,14 @@ class LoggingUserService( ) : UserService { override suspend fun create(user: User.Draft): User = runCatching { origin.create(user) } - .onSuccess { log.info("Created user with id ${it.id}") } - .onFailure { log.warn("Failed to create user: ${it.message}") } + .onSuccess { log.debug("Created user with id ${it.id}") } + .onFailure { log.debug("Failed to create user: ${it.message}") } .getOrThrow() override suspend fun getById(id: User.Id): User? = runCatching { origin.getById(id) } - .onSuccess { it ?: log.warn("User with id $id not found") } - .onFailure { log.warn("Failed to get user with id $id") } + .onSuccess { it ?: log.debug("User with id $id not found") } + .onFailure { log.debug("Failed to get user with id $id") } .getOrThrow() override suspend fun getByAlias(alias: User.Alias): User? = @@ -25,12 +25,12 @@ class LoggingUserService( override suspend fun promote(id: User.Id, role: User.Role) = runCatching { origin.promote(id, role) } - .onSuccess { log.info("User with id $id promoted to $role") } + .onSuccess { log.debug("User with id $id promoted to $role") } .onFailure { buildString { append("Failed to promote user with ") append("id $id to $role: ${it.message}") - }.let { log.warn(it) } + }.let { log.debug(it) } } .getOrThrow() } diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingWorkspaceService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingWorkspaceService.kt index 285218d..d4561f3 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingWorkspaceService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/logging/LoggingWorkspaceService.kt @@ -12,12 +12,12 @@ class LoggingWorkspaceService( ) : WorkspaceService { override fun events(id: Workspace.Id): Flow = runCatching { origin.events(id) } - .onSuccess { log.info("Got events for workspace with id $id") } + .onSuccess { log.debug("Got events for workspace with id $id") } .onFailure { buildString { append("Failed to get events for workspace with id $id: ") append(it.message) - }.let { log.warn(it) } + }.let { log.debug(it) } } .getOrThrow() @@ -30,14 +30,14 @@ class LoggingWorkspaceService( buildString { append("Produced an event with id ${it.id} ") append("at workspace with id $id") - }.let { log.info(it) } + }.let { log.debug(it) } } .onFailure { buildString { append("Failed to produce and event ") append("at workspace with id $id: ") append(it.message) - }.let { log.warn(it) } + }.let { log.debug(it) } } .getOrThrow() diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredAuthService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredAuthService.kt index 9ee9f29..2a2630b 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredAuthService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredAuthService.kt @@ -3,6 +3,7 @@ package ru.vityaman.lms.botalka.core.logic.metered import ru.vityaman.lms.botalka.core.logic.AuthService import ru.vityaman.lms.botalka.core.model.AuthUser import ru.vityaman.lms.botalka.core.model.User +import ru.vityaman.lms.botalka.core.monitoring.StatusCount import ru.vityaman.lms.botalka.core.security.auth.AccessToken class MeteredAuthService( diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredConsumer.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredConsumer.kt new file mode 100644 index 0000000..7a7cbb1 --- /dev/null +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredConsumer.kt @@ -0,0 +1,15 @@ +package ru.vityaman.lms.botalka.core.logic.metered + +import ru.vityaman.lms.botalka.commons.Consumer +import ru.vityaman.lms.botalka.core.monitoring.StatusCount + +class MeteredConsumer( + private val status: StatusCount, + private val origin: Consumer, +) : Consumer { + override suspend fun accept(value: V): Unit = + runCatching { origin.accept(value) } + .onSuccess { status.successes.increment() } + .onFailure { status.failures.increment() } + .getOrThrow() +} diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredHomeworkService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredHomeworkService.kt index 89fde6c..07d0e9a 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredHomeworkService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredHomeworkService.kt @@ -2,6 +2,7 @@ package ru.vityaman.lms.botalka.core.logic.metered import ru.vityaman.lms.botalka.core.logic.HomeworkService import ru.vityaman.lms.botalka.core.model.Homework +import ru.vityaman.lms.botalka.core.monitoring.StatusCount class MeteredHomeworkService( private val metrics: Metrics, diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredPromotionService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredPromotionService.kt index 1eb38c5..8a0b0cc 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredPromotionService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredPromotionService.kt @@ -2,6 +2,7 @@ package ru.vityaman.lms.botalka.core.logic.metered import ru.vityaman.lms.botalka.core.logic.PromotionService import ru.vityaman.lms.botalka.core.model.PromotionRequest +import ru.vityaman.lms.botalka.core.monitoring.StatusCount class MeteredPromotionService( private val metrics: Metrics, diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredWorkspaceService.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredWorkspaceService.kt index feefaa6..a5ba6d4 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredWorkspaceService.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/MeteredWorkspaceService.kt @@ -2,6 +2,7 @@ package ru.vityaman.lms.botalka.core.logic.metered import ru.vityaman.lms.botalka.core.logic.WorkspaceService import ru.vityaman.lms.botalka.core.model.Workspace +import ru.vityaman.lms.botalka.core.monitoring.StatusCount class MeteredWorkspaceService( private val metrics: Metrics, diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/StatusCount.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/monitoring/StatusCount.kt similarity index 73% rename from botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/StatusCount.kt rename to botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/monitoring/StatusCount.kt index 6007f74..304d2aa 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/logic/metered/StatusCount.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/monitoring/StatusCount.kt @@ -1,6 +1,4 @@ -package ru.vityaman.lms.botalka.core.logic.metered - -import ru.vityaman.lms.botalka.core.monitoring.Count +package ru.vityaman.lms.botalka.core.monitoring class StatusCount( val successes: Count, diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/security/persmission/MeteredAuthority.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/security/persmission/MeteredAuthority.kt new file mode 100644 index 0000000..678622e --- /dev/null +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/security/persmission/MeteredAuthority.kt @@ -0,0 +1,33 @@ +package ru.vityaman.lms.botalka.core.security.persmission + +import ru.vityaman.lms.botalka.commons.ExhaustiveMap +import ru.vityaman.lms.botalka.core.model.User +import ru.vityaman.lms.botalka.core.monitoring.Count +import ru.vityaman.lms.botalka.core.monitoring.StatusCount + +class MeteredAuthority( + private val metrics: Metrics, + private val origin: Authority, +) : Authority { + override fun isAllowedTo(permission: Permission, user: User): Boolean { + for (role in User.Role.entries) { + user.roles.find { it == role }?.let { + metrics.role[it].increment() + } + } + return origin.isAllowedTo(permission, user).also { + metrics.permission[permission.kind].run { + if (it) { + successes + } else { + failures + }.increment() + } + } + } + + data class Metrics( + val role: ExhaustiveMap, + val permission: ExhaustiveMap, + ) +} diff --git a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/security/persmission/Permission.kt b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/security/persmission/Permission.kt index 8cc19b9..683379b 100644 --- a/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/security/persmission/Permission.kt +++ b/botalka/src/main/kotlin/ru/vityaman/lms/botalka/core/security/persmission/Permission.kt @@ -3,6 +3,15 @@ package ru.vityaman.lms.botalka.core.security.persmission import ru.vityaman.lms.botalka.core.model.Workspace sealed interface Permission { + val kind: Kind + get() = when (this) { + is GetEvents -> Kind.GetEvents + PostHomework -> Kind.PostHomework + ResolvePromotion -> Kind.ResolvePromotion + is SendFeedback -> Kind.SendFeedback + is SendSubmission -> Kind.SendSubmission + } + data object ResolvePromotion : Permission data class GetEvents(val id: Workspace.Id) : Permission @@ -10,4 +19,12 @@ sealed interface Permission { data class SendSubmission(val id: Workspace.Id) : Permission data object PostHomework : Permission + + enum class Kind { + ResolvePromotion, + GetEvents, + SendFeedback, + SendSubmission, + PostHomework, + } } diff --git a/infra/grafana/provisioning/dashboards/spring-monitoring_rev4.json b/infra/grafana/provisioning/dashboards/spring-monitoring_rev5.json similarity index 72% rename from infra/grafana/provisioning/dashboards/spring-monitoring_rev4.json rename to infra/grafana/provisioning/dashboards/spring-monitoring_rev5.json index 3c6149c..908535f 100644 --- a/infra/grafana/provisioning/dashboards/spring-monitoring_rev4.json +++ b/infra/grafana/provisioning/dashboards/spring-monitoring_rev5.json @@ -18,7 +18,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 4, + "id": 2, "links": [], "panels": [ { @@ -183,7 +183,7 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "drawStyle": "line", + "drawStyle": "bars", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { @@ -223,7 +223,38 @@ ] } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Error" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "5XX" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] }, "gridPos": { "h": 5, @@ -254,7 +285,7 @@ "editorMode": "code", "expr": "sum(rate(http_server_requests_seconds_count{status=~\"5..\"}[1m])) by (method, uri)", "instant": false, - "legendFormat": "__auto", + "legendFormat": "5XX", "range": true, "refId": "A" } @@ -2237,7 +2268,7 @@ }, "id": 22, "panels": [], - "title": "Service", + "title": "Logic", "type": "row" }, { @@ -2902,9 +2933,9 @@ "x": 0, "y": 72 }, - "id": 6, + "id": 42, "panels": [], - "title": "Support", + "title": "Authority", "type": "row" }, { @@ -2915,7 +2946,39 @@ "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "percent" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -2932,98 +2995,58 @@ ] } }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 5, - "x": 0, - "y": 73 - }, - "id": 7, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "group (executor_pool_core_threads) by (name)", - "instant": false, - "legendFormat": "{{label_name}}", - "range": true, - "refId": "A" - } - ], - "title": "Executor", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "failure" + }, + "properties": [ { - "color": "green", - "value": null - }, + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ { - "color": "red", - "value": 80 + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } } ] } - }, - "overrides": [] + ] }, "gridPos": { - "h": 3, - "w": 2, - "x": 5, + "h": 8, + "w": 6, + "x": 0, "y": 73 }, - "id": 13, + "id": 37, "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } }, "pluginVersion": "11.0.0", "targets": [ @@ -3033,15 +3056,15 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "max(jvm_threads_live_threads)", + "expr": "sum(rate(lms_authorization_permission_total{status=\"success\"}[1m])) by (kind)", "instant": false, - "legendFormat": "__auto", + "legendFormat": "{{label_name}}", "range": true, "refId": "A" } ], - "title": "JVM Threads", - "type": "stat" + "title": "Permission Granted", + "type": "timeseries" }, { "datasource": { @@ -3079,7 +3102,7 @@ "spanNulls": false, "stacking": { "group": "A", - "mode": "normal" + "mode": "percent" }, "thresholdsStyle": { "mode": "off" @@ -3100,15 +3123,46 @@ ] } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "failure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] }, "gridPos": { - "h": 10, - "w": 9, - "x": 7, + "h": 8, + "w": 6, + "x": 6, "y": 73 }, - "id": 14, + "id": 38, "options": { "legend": { "calcs": [], @@ -3122,6 +3176,7 @@ "sort": "none" } }, + "pluginVersion": "11.0.0", "targets": [ { "datasource": { @@ -3129,14 +3184,14 @@ "uid": "PBFA97CFB590B2093" }, "editorMode": "code", - "expr": "sum(jvm_memory_used_bytes) by (id) / 1024 / 1024", + "expr": "sum(rate(lms_authorization_permission_total{status=\"failure\"}[1m])) by (kind)", "instant": false, - "legendFormat": "__auto", + "legendFormat": "{{label_name}}", "range": true, "refId": "A" } ], - "title": "JVM Memory Used (MB)", + "title": "Permission Denied", "type": "timeseries" }, { @@ -3199,11 +3254,1195 @@ "overrides": [] }, "gridPos": { - "h": 10, - "w": 8, - "x": 16, + "h": 8, + "w": 6, + "x": 12, "y": 73 }, + "id": 39, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum (rate(lms_authorization_user_role_total[1m])) by (kind)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Roles", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 81 + }, + "id": 41, + "panels": [], + "title": "Consumer", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 82 + }, + "id": 40, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(rate(lms_consumer_accept_total{consumer=\"kafka-homework\"}[1m])) by (status)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Kafka Homework", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 82 + }, + "id": 43, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(rate(lms_consumer_accept_total{consumer=\"telegram-homework\"}[1m])) by (status)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Telegram Homework", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 5 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum(lms_consumer_accept_total{consumer=\"kafka-homework\"}) - sum(lms_consumer_accept_total{consumer=\"telegram-homework\"})" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Lag" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 82 + }, + "id": 46, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(delta(lms_consumer_accept_total{consumer=\"kafka-homework\"}[15m])) - sum(delta(lms_consumer_accept_total{consumer=\"telegram-homework\"}[15m]))", + "instant": false, + "legendFormat": "Lag", + "range": true, + "refId": "A" + } + ], + "title": "Remaining Homework", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 90 + }, + "id": 44, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(rate(lms_consumer_accept_total{consumer=\"kafka-workspace-event\"}[1m])) by (status)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Kafka Workspace Event", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 90 + }, + "id": 45, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(rate(lms_consumer_accept_total{consumer=\"telegram-workspace-event\"}[1m])) by (status)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Telegram Workspace Event", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 5 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum(lms_consumer_accept_total{consumer=\"kafka-homework\"}) - sum(lms_consumer_accept_total{consumer=\"telegram-homework\"})" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Lag" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 90 + }, + "id": 47, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(delta(lms_consumer_accept_total{consumer=\"kafka-workspace-event\"}[15m])) - sum(delta(lms_consumer_accept_total{consumer=\"telegram-workspace-event\"}[15m]))", + "instant": false, + "interval": "", + "legendFormat": "Lag", + "range": true, + "refId": "A" + } + ], + "title": "Remaining Workspace Event", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 98 + }, + "id": 6, + "panels": [], + "title": "Support", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 5, + "x": 0, + "y": 99 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "group (executor_pool_core_threads) by (name)", + "instant": false, + "legendFormat": "{{label_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Executor", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 5, + "y": 99 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "max(jvm_threads_live_threads)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "JVM Threads", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 9, + "x": 7, + "y": 99 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(jvm_memory_used_bytes) by (id) / 1024 / 1024", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "JVM Memory Used (MB)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 99 + }, "id": 8, "options": { "legend": { @@ -3324,7 +4563,7 @@ "h": 7, "w": 7, "x": 0, - "y": 76 + "y": 102 }, "id": 5, "options": { @@ -3377,6 +4616,6 @@ "timezone": "browser", "title": "Spring Monitoring", "uid": "fdp9jhcd0av40a", - "version": 2, + "version": 7, "weekStart": "" } \ No newline at end of file