All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.axoniq.console.framework.messaging.extensions.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2022-2024. AxonIQ B.V.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *    
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.axoniq.console.framework.messaging

import io.axoniq.console.framework.api.metrics.HandlerStatisticsMetricIdentifier
import io.axoniq.console.framework.api.metrics.HandlerType
import io.axoniq.console.framework.api.metrics.MessageIdentifier
import io.axoniq.console.framework.api.metrics.StatisticDistribution
import io.micrometer.core.instrument.distribution.HistogramSnapshot
import io.micrometer.core.instrument.distribution.ValueAtPercentile
import org.axonframework.commandhandling.CommandMessage
import org.axonframework.deadline.DeadlineMessage
import org.axonframework.eventhandling.EventMessage
import org.axonframework.messaging.Message
import org.axonframework.messaging.unitofwork.UnitOfWork
import org.axonframework.modelling.command.AggregateLifecycle
import org.axonframework.modelling.command.AggregateScopeDescriptor
import org.axonframework.queryhandling.QueryMessage
import org.axonframework.queryhandling.SubscriptionQueryUpdateMessage
import org.slf4j.LoggerFactory
import java.util.concurrent.TimeUnit

private val logger = LoggerFactory.getLogger("axoniq-console-extensions")

fun Message<*>.toInformation() = MessageIdentifier(
    when (this) {
        is DeadlineMessage<*> -> DeadlineMessage::class.java.simpleName
        is CommandMessage -> CommandMessage::class.java.simpleName
        is EventMessage -> EventMessage::class.java.simpleName
        is QueryMessage<*, *> -> QueryMessage::class.java.simpleName
        is SubscriptionQueryUpdateMessage<*> -> SubscriptionQueryUpdateMessage::class.java.simpleName
        else -> this::class.java.simpleName
    },
    when (this) {
        is CommandMessage -> this.commandName.toSimpleName()
        is QueryMessage<*, *> -> this.queryName.toSimpleName()
        is DeadlineMessage<*> -> this.deadlineName.toSimpleName()
        else -> this.payloadType.name.toSimpleName()
    }
)

fun String.toSimpleName() = split(".").last()

fun UnitOfWork<*>.extractHandler(declaringClassName: String, processingGroup: String?): HandlerStatisticsMetricIdentifier? = try {
    val isAggregate = message is CommandMessage<*> && isAggregateLifecycleActive()

    val processorName = AxoniqConsoleSpanFactory.currentSpan()?.processorName
    val isProcessor = processorName != null

    val component = when {
        isAggregate -> (AggregateLifecycle.describeCurrentScope() as AggregateScopeDescriptor).type
        isProcessor -> createCombinedProcessorAndGroupIdentifier(processorName!!, processingGroup)
        else -> declaringClassName
    }
    val type = when {
        isAggregate -> HandlerType.Aggregate
        isProcessor -> HandlerType.EventProcessor
        else -> HandlerType.Message
    }
    HandlerStatisticsMetricIdentifier(
        type = type,
        component = component,
        message = message.toInformation(),
    )
} catch (e: Exception) {
    logger.debug("Could not extract handler from AxonIQ Console invocation. Skipping registration of message.", e)
    null
}

/**
 * If the processing group name differents from the processor, we can assume multiple groups are under the same one.
 * Using the [::] operator to separate the processor name from the processing group, we can filter in the UI.
 */
fun createCombinedProcessorAndGroupIdentifier(processorName: String, processingGroup: String?): String {
    if (processingGroup == null || processingGroup == processorName) {
        return processorName
    }
    return "$processorName::$processingGroup"
}


fun isAggregateLifecycleActive(): Boolean {
    return try {
        val scope = AggregateLifecycle.describeCurrentScope()
        scope is AggregateScopeDescriptor
    } catch (e: Exception) {
        false
    }
}


fun HistogramSnapshot.toDistribution(): StatisticDistribution {
    val percentiles = percentileValues()
    return StatisticDistribution(
        min = percentiles.ofPercentile(0.01),
        percentile90 = percentiles.ofPercentile(0.90),
        percentile95 = percentiles.ofPercentile(0.95),
        median = percentiles.ofPercentile(0.50),
        mean = mean(TimeUnit.MILLISECONDS),
        max = percentiles.ofPercentile(1.00),
    )
}

fun Array.ofPercentile(percentile: Double): Double {
    return this.firstOrNull { pc -> pc.percentile() == percentile }
        ?.value(TimeUnit.MILLISECONDS)!!
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy