diff --git a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/pom.xml b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/pom.xml
index 06c16c85a..ba228354a 100644
--- a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/pom.xml
+++ b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/pom.xml
@@ -33,6 +33,12 @@
${project.version}
+
+ io.micrometer
+ micrometer-core
+ true
+
+
org.hibernate.orm
diff --git a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java
new file mode 100644
index 000000000..36bd7460c
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java
@@ -0,0 +1,207 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.repository.jpa;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jakarta.persistence.EntityManagerFactory;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import lombok.Getter;
+import org.eclipse.persistence.sessions.Session;
+import org.eclipse.persistence.tools.profiler.PerformanceMonitor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.Scheduled;
+
+/**
+ * (Experimental) Report EclipseLink statistics to Micrometer.
+ *
+ * To be enabled:
+ *
+ * - The Spring property spring.jpa.properties.eclipselink.profiler=PerformanceMonitor shall be set - enables Eclipselink statistics
+ * collecting
+ * - By default the stdout log is disabled by setting hawkbit.jpa.statistics.dumpPeriodMS=9223372036854775807 (Long.MAX_VALUE) -
+ * i.e. effectively never. If log is required it should be set to the required period
+ * - The MeterRegistry shall be registered available - e.g. include org.springframework.boot:spring-boot-actuator-autoconfigure
+ * - (?) When using in test the metrics MAYBE shall be enabled with @AutoConfigureObservability(tracing = false)
+ *
+ *
+ * It encapsulates reporting the Eclipselink {@link PerformanceMonitor} statistics to the {@link MeterRegistry} and the Spring autoconfiguration.
+ */
+public class Statistics {
+
+ public static final String METER_PREFIX = "eclipselink.";
+
+ private static final Statistics INSTANCE = new Statistics();
+
+ private static final Pattern PATTERN = Pattern.compile("(?(Counter|Timer)+):(?[^ -]+)");
+ private static final Map REPORTED_TIMER_VALUES = new HashMap<>();
+
+ private EntityManagerFactory entityManagerFactory;
+ // if meter registry is unavailable, the statistics will not send to metrics
+ @Getter
+ private MeterRegistry meterRegistry;
+
+ private boolean flushing;
+
+ /**
+ * @return the singleton {@link Statistics} instance
+ */
+ public static Statistics getInstance() {
+ return INSTANCE;
+ }
+
+ @Autowired
+ public void setEntityManagerFactory(
+ final EntityManagerFactory entityManagerFactory,
+ @Value("${hawkbit.jpa.statistics.dumpPeriodMS:9223372036854775807}") final long dumpPeriod) {
+ this.entityManagerFactory = entityManagerFactory;
+ // set stdout log PerformanceMonitor. By default, it is Long.MAX_VALUE (9223372036854775807) which effectively disable logging
+ getPerformanceMonitor(entityManagerFactory).setDumpTime(dumpPeriod);
+ }
+
+ @Autowired
+ public void setMeterRegistry(final MeterRegistry meterRegistry) {
+ this.meterRegistry = meterRegistry;
+ }
+
+ // flushes the statistics to the meter registry (if needed)
+ public static void flush() {
+ final MeterRegistry meterRegistry = INSTANCE.meterRegistry;
+ if (meterRegistry == null) {
+ // not a bean (i.e. no performance monitoring) is enabled or no meter registry available
+ return;
+ }
+
+ synchronized (INSTANCE) {
+ if (INSTANCE.flushing) {
+ // wait for flushing
+ do {
+ try {
+ INSTANCE.wait(1000);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ } while (INSTANCE.flushing);
+ // flushed
+ return;
+ }
+ // flush
+ INSTANCE.flushing = true;
+ }
+ INSTANCE.flush0();
+ }
+
+ @Scheduled(initialDelayString = "${hawkbit.jpa.statistics.flush.fixedDelay:60000}", fixedDelayString = "${hawkbit.jpa.statistics.flush.fixedDelay:60000}")
+ void periodicFlush() {
+ if (meterRegistry == null) {
+ // meter registry available
+ return;
+ }
+
+ synchronized (this) {
+ if (flushing) {
+ // no need to wait for flushing
+ return;
+ }
+ // flush
+ flushing = true;
+ }
+ flush0();
+ }
+
+ private void flush0() {
+ final PerformanceMonitor performanceMonitor = getPerformanceMonitor(entityManagerFactory);
+ final Map opTimings = performanceMonitor.getOperationTimings();
+ opTimings.forEach((k, v) -> {
+ if (opTimings.keySet().stream().anyMatch(key -> !key.equals(k) && key.startsWith(k))) {
+ // it is a group, e.g.:
+ // Timer:ReportQuery 65,402,376
+ // Timer:ReportQuery:org.eclipse.hawkbit.repository.jpa.model.DistributionSetTypeElement:null:QueryPreparation 177,375
+ // Timer:ReportQuery:org.eclipse.hawkbit.repository.jpa.model.DistributionSetTypeElement:null:SqlGeneration 36,083
+ // Counter:ReportQuery 56
+ // Counter:ReportQuery:org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData:null 56
+ // we want to report per tag/operation, not the group - the sum could be made on the metric collector side (e.g. prometheus)
+ return;
+ }
+
+ final Matcher matcher = PATTERN.matcher(k);
+ if (matcher.matches()) {
+ final String type = matcher.group("type");
+ final StringTokenizer stringTokenizer = new StringTokenizer(matcher.group("key"), ":");
+ final String name = METER_PREFIX + stringTokenizer.nextToken();
+ if (type.equals("Counter")) {
+ final double quantity = v instanceof Double d ? d : Double.parseDouble(v.toString());
+ final Counter counter;
+ if (stringTokenizer.hasMoreTokens()) {
+ counter = meterRegistry.counter(name, "entity", stringTokenizer.nextToken());
+ } else {
+ counter = meterRegistry.counter(name);
+ }
+ counter.increment(quantity - counter.count());
+ } else { // Timer
+ final long quantity = v instanceof Long l ? l : (long) Double.parseDouble(v.toString());
+ final Timer timer;
+ if (stringTokenizer.hasMoreTokens()) {
+ final String entity = stringTokenizer.nextToken();
+ stringTokenizer.nextToken(); // skip, what is this?
+ final String subOp = stringTokenizer.hasMoreTokens() ? stringTokenizer.nextToken() : "n/a";
+ timer = meterRegistry.timer(name, "entity", entity, "subOp", subOp);
+ } else {
+ timer = meterRegistry.timer(name);
+ }
+ timer.record(quantity - REPORTED_TIMER_VALUES.getOrDefault(name, 0L), TimeUnit.NANOSECONDS);
+ REPORTED_TIMER_VALUES.put(name, quantity);
+ }
+ }
+ });
+
+ synchronized (this) {
+ if (flushing) {
+ flushing = false;
+ }
+ this.notifyAll();
+ }
+ }
+
+ private static PerformanceMonitor getPerformanceMonitor(final EntityManagerFactory entityManagerFactory) {
+ return (PerformanceMonitor) entityManagerFactory.unwrap(Session.class).getProfiler();
+ }
+
+ // autoconfigure after CompositeMeterRegistryAutoConfiguration, so when the autoconfiguration is being processed the MeterRegistry
+ // has already been registered / resolved (if it is to be registered at all) - otherwise @ConditionalOnBean(MeterRegistry.class) may not be
+ // met event if the MeterRegistry is registered (if resolved later).
+ // 'autoconfigure after' relies on this is being an AutoConfiguration
+ @AutoConfiguration(afterName = "org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration")
+ @Configuration
+ public static class StatisticsAutoConfiguration {
+
+ @ConditionalOnProperty(prefix = "spring.jpa.properties.eclipselink", name = "profiler", havingValue = "PerformanceMonitor")
+ @ConditionalOnBean(MeterRegistry.class)
+ @Bean
+ public Statistics statistics() {
+ // injects the singleton Statistics, and start scheduler
+ return Statistics.getInstance();
+ }
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..dacc68dab
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.eclipse.hawkbit.repository.jpa.Statistics.StatisticsAutoConfiguration
\ No newline at end of file
diff --git a/hawkbit-repository/hawkbit-repository-jpa-hibernate/pom.xml b/hawkbit-repository/hawkbit-repository-jpa-hibernate/pom.xml
index 6f0a57552..36c1d0f9e 100644
--- a/hawkbit-repository/hawkbit-repository-jpa-hibernate/pom.xml
+++ b/hawkbit-repository/hawkbit-repository-jpa-hibernate/pom.xml
@@ -36,6 +36,12 @@
hibernate-core
+
+ io.micrometer
+ micrometer-core
+ true
+
+
org.hibernate.orm
diff --git a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java
new file mode 100644
index 000000000..0d3733333
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.repository.jpa;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * (Experimental) Report Hibernate statistics to Micrometer.
+ *
+ * To be enabled:
+ *
+ * - The Spring property spring.jpa.properties.hibernate.generate_statistics=true shall be set - enables Hibernate statistics
+ * collecting
+ * - If don't need log in the stdout set logging.level.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=WARN
+ * - The MeterRegistry shall be registered available - e.g. include org.springframework.boot:spring-boot-actuator-autoconfigure
+ * - Hibernate reporting to micrometer shall be enabled - include org.hibernate.orm:hibernate-micrometer
+ * - (?) When using in test the metrics MAYBE shall be enabled with @AutoConfigureObservability(tracing = false)
+ *
+ */
+public class Statistics {
+
+ public static final String METER_PREFIX = "hibernate.";
+
+ private static final Statistics INSTANCE = new Statistics();
+
+ // if meter registry is unavailable, the statistics will not send to metrics
+ @Getter
+ public MeterRegistry meterRegistry;
+
+ /**
+ * @return the singleton {@link Statistics} instance
+ */
+ public static Statistics getInstance() {
+ return INSTANCE;
+ }
+
+ @Autowired
+ public void setMeterRegistry(final MeterRegistry meterRegistry) {
+ this.meterRegistry = meterRegistry;
+ }
+
+ // flushes the statistics to the meter registry (if needed)
+ public static void flush() {
+ // nothing to do for Hibernate
+ }
+
+ // autoconfigure after CompositeMeterRegistryAutoConfiguration, so when the autoconfiguration is being processed the MeterRegistry
+ // has already been registered / resolved (if it is to be registered at all) - otherwise @ConditionalOnBean(MeterRegistry.class) may not be
+ // met event if the MeterRegistry is registered (if resolved later).
+ // 'autoconfigure after' relies on this is being an AutoConfiguration
+ @AutoConfiguration(afterName = "org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration")
+ @Configuration
+ public static class StatisticsAutoConfiguration {
+
+ @ConditionalOnProperty(prefix = "spring.jpa.properties.hibernate", name = "generate_statistics", havingValue = "true")
+ @ConditionalOnBean(MeterRegistry.class)
+ @Bean
+ public Statistics statistics() {
+ // injects the singleton Statistics, and start scheduler
+ return Statistics.getInstance();
+ }
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..dacc68dab
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.eclipse.hawkbit.repository.jpa.Statistics.StatisticsAutoConfiguration
\ No newline at end of file
diff --git a/hawkbit-repository/hawkbit-repository-jpa/pom.xml b/hawkbit-repository/hawkbit-repository-jpa/pom.xml
index 8d28aca71..cfb435be2 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/pom.xml
+++ b/hawkbit-repository/hawkbit-repository-jpa/pom.xml
@@ -72,6 +72,12 @@
${project.version}
+
+ io.micrometer
+ micrometer-core
+ true
+
+
org.hibernate.orm
@@ -116,5 +122,18 @@
javax.el-api
test
+
+
+
+ org.springframework.boot
+ spring-boot-actuator-autoconfigure
+ test
+
+
+
+ org.hibernate.orm
+ hibernate-micrometer
+ test
+
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
index 1cb672e23..ea20af4d2 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
@@ -869,8 +869,7 @@ public class RepositoryApplicationConfiguration {
*/
@Bean
@ConditionalOnMissingBean
- // don't active the auto assign scheduler in test, otherwise it is hard to
- // test
+ // don't active the auto assign scheduler in test, otherwise it is hard to test
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.autoassign.scheduler", name = "enabled", matchIfMissing = true)
AutoAssignScheduler autoAssignScheduler(final SystemManagement systemManagement,
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/StatisticsUtils.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/StatisticsUtils.java
new file mode 100644
index 000000000..a6ee0072e
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/StatisticsUtils.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit.repository.jpa.utils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.eclipse.hawkbit.repository.jpa.Statistics;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * (Experimental) Utility class to get some statistics.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StatisticsUtils {
+
+ private static final ThreadLocal