Skip to content

Commit fc1293b

Browse files
Merge pull request #155 from AxonIQ/feature/entity-metrics-capture
Capture per-entity metrics on Repository and EventStore
2 parents 502929f + a76d67d commit fc1293b

17 files changed

Lines changed: 556 additions & 71 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2022-2026. AxonIQ B.V.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.axoniq.platform.framework.api.metrics
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty
20+
21+
data class EntityStatisticIdentifier(
22+
@JsonProperty("n")
23+
val entityName: String,
24+
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2022-2026. AxonIQ B.V.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.axoniq.platform.framework.api.metrics
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty
20+
21+
data class EntityStatistics(
22+
@JsonProperty("c")
23+
val count: Double,
24+
@JsonProperty("f")
25+
val failed: Double,
26+
@JsonProperty("t")
27+
val timer: StatisticDistribution?,
28+
@JsonProperty("m")
29+
val metrics: Map<String, StatisticDistribution>,
30+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2022-2026. AxonIQ B.V.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.axoniq.platform.framework.api.metrics
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty
20+
21+
data class EntityStatisticsWithIdentifier(
22+
@JsonProperty("e")
23+
val entity: EntityStatisticIdentifier,
24+
@JsonProperty("s")
25+
val statistics: EntityStatistics,
26+
)

framework-client-api/src/main/java/io/axoniq/platform/framework/api/metrics/StatisticReport.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2025. AxonIQ B.V.
2+
* Copyright (c) 2022-2026. AxonIQ B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,4 +25,6 @@ data class StatisticReport(
2525
val dispatchers: List<DispatcherStatisticsWithIdentifier>,
2626
@JsonProperty("a")
2727
val aggregates: List<AggregateStatisticsWithIdentifier>,
28+
@JsonProperty("e")
29+
val entities: List<EntityStatisticsWithIdentifier> = emptyList(),
2830
)

framework-client/src/main/java/io/axoniq/platform/framework/AxoniqPlatformConfigurerEnhancer.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import io.axoniq.platform.framework.messaging.AxoniqPlatformCommandBus;
3838
import io.axoniq.platform.framework.messaging.AxoniqPlatformQueryBus;
3939
import io.axoniq.platform.framework.messaging.HandlerMetricsRegistry;
40+
import io.axoniq.platform.framework.modelling.EntityMetricsRegistry;
4041
import org.axonframework.common.configuration.ComponentDefinition;
4142
import org.axonframework.common.configuration.ComponentRegistry;
4243
import org.axonframework.common.configuration.Configuration;
@@ -135,12 +136,16 @@ public void enhance(ComponentRegistry registry) {
135136
// The start handler will allow for eager creation
136137
.onStart(Phase.EXTERNAL_CONNECTIONS, c -> {
137138
}))
139+
.registerComponent(ComponentDefinition
140+
.ofType(EntityMetricsRegistry.class)
141+
.withBuilder(c -> new EntityMetricsRegistry()))
138142
.registerComponent(ComponentDefinition
139143
.ofType(HandlerMetricsRegistry.class)
140144
.withBuilder(c -> new HandlerMetricsRegistry(
141145
c.getComponent(AxoniqConsoleRSocketClient.class),
142146
c.getComponent(PlatformClientConnectionService.class),
143-
c.getComponent(AxoniqPlatformConfiguration.class))))
147+
c.getComponent(AxoniqPlatformConfiguration.class),
148+
c.getComponent(EntityMetricsRegistry.class))))
144149
.registerComponent(ComponentDefinition
145150
.ofType(ApplicationThreadDumpProvider.class)
146151
.withBuilder(c -> new ApplicationThreadDumpProvider()))
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (c) 2022-2026. AxonIQ B.V.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.axoniq.platform.framework.eventsourcing
18+
19+
import io.axoniq.platform.framework.api.metrics.PreconfiguredMetric
20+
import io.axoniq.platform.framework.messaging.HandlerMeasurement
21+
import io.axoniq.platform.framework.messaging.HandlerMeasurement.Companion.RESOURCE_KEY
22+
import io.axoniq.platform.framework.messaging.HandlerMetricsRegistry
23+
import io.axoniq.platform.framework.messaging.toInformation
24+
import io.axoniq.platform.framework.modelling.CurrentEntityContext
25+
import io.axoniq.platform.framework.modelling.EntityMetricsRegistry
26+
import org.axonframework.common.infra.ComponentDescriptor
27+
import org.axonframework.eventsourcing.eventstore.AppendCondition
28+
import org.axonframework.eventsourcing.eventstore.EventStorageEngine
29+
import org.axonframework.eventsourcing.eventstore.SourcingCondition
30+
import org.axonframework.eventsourcing.eventstore.TaggedEventMessage
31+
import org.axonframework.messaging.core.MessageStream
32+
import org.axonframework.messaging.core.unitofwork.ProcessingContext
33+
import org.axonframework.messaging.eventhandling.EventMessage
34+
import org.axonframework.messaging.eventhandling.processing.streaming.token.TrackingToken
35+
import org.axonframework.messaging.eventstreaming.StreamingCondition
36+
import java.time.Instant
37+
import java.util.concurrent.CompletableFuture
38+
import java.util.concurrent.TimeUnit
39+
40+
class AxoniqPlatformEventStorageEngine(
41+
private val delegate: EventStorageEngine,
42+
private val registry: HandlerMetricsRegistry,
43+
private val entityMetricsRegistry: EntityMetricsRegistry,
44+
) : EventStorageEngine {
45+
46+
override fun appendEvents(condition: AppendCondition, context: ProcessingContext?, events: List<TaggedEventMessage<*>>): CompletableFuture<EventStorageEngine.AppendTransaction<*>> {
47+
// First, report dispatches
48+
val container = context?.getResource(RESOURCE_KEY)
49+
if (container == null) {
50+
events.forEach { tm ->
51+
val event: EventMessage = tm.event()
52+
registry.registerMessageDispatchedWithoutHandling(event.toInformation())
53+
}
54+
} else {
55+
events.forEach { tm ->
56+
val event: EventMessage = tm.event()
57+
container.reportMessageDispatched(event.toInformation())
58+
}
59+
}
60+
if (context == null) {
61+
return delegate.appendEvents(condition, context, events)
62+
}
63+
// Second, measure commit time if measurement is ongoing
64+
val startTime = System.nanoTime()
65+
val currentEntity = context.getResource(CurrentEntityContext.RESOURCE_KEY)
66+
return delegate.appendEvents(condition, context, events)
67+
.whenComplete { _, _ ->
68+
val endTime = System.nanoTime()
69+
HandlerMeasurement.onContext(context) {
70+
it.registerMetricValue(PreconfiguredMetric.EVENT_COMMIT_TIME, endTime - startTime)
71+
}
72+
currentEntity?.let {
73+
entityMetricsRegistry.registerAdditionalTimer(
74+
it,
75+
EntityMetricsRegistry.METRIC_EVENT_COMMIT_TIME,
76+
endTime - startTime,
77+
TimeUnit.NANOSECONDS,
78+
)
79+
}
80+
}
81+
}
82+
83+
override fun appendEvents(condition: AppendCondition, context: ProcessingContext?, vararg events: TaggedEventMessage<*>): CompletableFuture<EventStorageEngine.AppendTransaction<*>> {
84+
return appendEvents(condition, context, events.toList())
85+
}
86+
87+
override fun source(condition: SourcingCondition): MessageStream<EventMessage> {
88+
return delegate.source(condition)
89+
}
90+
91+
override fun stream(condition: StreamingCondition): MessageStream<EventMessage> {
92+
return delegate.stream(condition)
93+
}
94+
95+
override fun firstToken(): CompletableFuture<TrackingToken> = delegate.firstToken()
96+
97+
override fun latestToken(): CompletableFuture<TrackingToken> = delegate.latestToken()
98+
99+
override fun tokenAt(at: Instant): CompletableFuture<TrackingToken> = delegate.tokenAt(at)
100+
101+
override fun describeTo(descriptor: ComponentDescriptor) {
102+
descriptor.describeWrapperOf(delegate)
103+
}
104+
}

0 commit comments

Comments
 (0)