diff --git a/Jenkinsfile b/Jenkinsfile
index 5faa616cd7f..233fe52a9c1 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -141,17 +141,6 @@ pipeline {
mvnCmd("$BUILD_PROPERTIES_PARAMS test -Dgroups=api")
}
}
- stage('Publish Coverage') {
- when {
- expression {
- params.SKIP_TEST_WITH_COVERAGE == false
- }
- }
- steps {
- recordCoverage(tools: [[parser: 'JACOCO']],sourceCodeRetention: 'MODIFIED')
- junit allowEmptyResults: true, testResults: '**/target/surefire-reports/*.xml'
- }
- }
stage('Sonarqube Analysis') {
environment {
diff --git a/common/pom.xml b/common/pom.xml
index 70b5f1d2878..26593e52213 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -143,7 +143,10 @@
commons-lang
commons-lang
-
+
+ io.prometheus
+ simpleclient
+
nekohtml
diff --git a/common/src/main/java/com/zimbra/common/metric/CarbonioCollector.java b/common/src/main/java/com/zimbra/common/metric/CarbonioCollector.java
new file mode 100644
index 00000000000..8cfe0a024e8
--- /dev/null
+++ b/common/src/main/java/com/zimbra/common/metric/CarbonioCollector.java
@@ -0,0 +1,14 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Zextras
+ *
+ * SPDX-License-Identifier: CC0-1.0
+ */
+package com.zimbra.common.metric;
+
+import io.prometheus.client.Collector;
+
+/**
+ * Carbonio Collector
+ */
+public abstract class CarbonioCollector extends Collector {
+}
diff --git a/common/src/main/java/com/zimbra/common/util/HttpConnectionManagerMetricsExport.java b/common/src/main/java/com/zimbra/common/util/HttpConnectionManagerMetricsExport.java
new file mode 100644
index 00000000000..0b2d5596b28
--- /dev/null
+++ b/common/src/main/java/com/zimbra/common/util/HttpConnectionManagerMetricsExport.java
@@ -0,0 +1,68 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Zextras
+ *
+ * SPDX-License-Identifier: CC0-1.0
+ */
+package com.zimbra.common.util;
+
+import com.zimbra.common.metric.CarbonioCollector;
+import io.prometheus.client.GaugeMetricFamily;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.pool.PoolStats;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * For export HttpConnectionPools metrics
+ *
+ */
+public class HttpConnectionManagerMetricsExport extends CarbonioCollector {
+
+ private static final String POOL = "pool";
+
+ /**
+ * @return List
+ */
+ @Override
+ public List collect() {
+ List mfs = new ArrayList<>();
+ GaugeMetricFamily internalGaugeMetricFamily = getGaugeMetricFamily("internal");
+
+ setMetrics(ZimbraHttpConnectionManager.getInternalHttpConnMgr(), internalGaugeMetricFamily);
+ mfs.add(internalGaugeMetricFamily);
+
+ GaugeMetricFamily externalGaugeMetricFamily = getGaugeMetricFamily("external");
+ setMetrics(ZimbraHttpConnectionManager.getExternalHttpConnMgr(), externalGaugeMetricFamily);
+ mfs.add(externalGaugeMetricFamily);
+ return mfs;
+ }
+
+ /**
+ * get Gauge Metric Family
+ */
+ GaugeMetricFamily getGaugeMetricFamily(String type) {
+ return new GaugeMetricFamily(
+ "http_connection_" + POOL + "_" + type,
+ "Metrics for " + type + " " + POOL,
+ Collections.singletonList(POOL));
+ }
+
+ /**
+ * set metrics
+ */
+ void setMetrics(ZimbraHttpConnectionManager zimbraHttpConnectionManager, GaugeMetricFamily gaugeMetricFamily) {
+ PoolStats poolStats = null;
+ if (zimbraHttpConnectionManager != null && zimbraHttpConnectionManager.getHttpConnMgr() instanceof PoolingHttpClientConnectionManager) {
+ poolStats = ((PoolingHttpClientConnectionManager) zimbraHttpConnectionManager.getHttpConnMgr()).getTotalStats();
+ }
+ if (poolStats == null) {
+ return;
+ }
+ gaugeMetricFamily.addMetric(Collections.singletonList("available"), poolStats.getAvailable());
+ gaugeMetricFamily.addMetric(Collections.singletonList("leased"), poolStats.getLeased());
+ gaugeMetricFamily.addMetric(Collections.singletonList("max"), poolStats.getMax());
+ gaugeMetricFamily.addMetric(Collections.singletonList("pending"), poolStats.getPending());
+ }
+}
diff --git a/common/src/test/java/com/zimbra/common/util/HttpConnectionManagerMetricsExportTest.java b/common/src/test/java/com/zimbra/common/util/HttpConnectionManagerMetricsExportTest.java
new file mode 100644
index 00000000000..84408d6bd45
--- /dev/null
+++ b/common/src/test/java/com/zimbra/common/util/HttpConnectionManagerMetricsExportTest.java
@@ -0,0 +1,39 @@
+package com.zimbra.common.util;
+
+import io.prometheus.client.GaugeMetricFamily;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+
+class HttpConnectionManagerMetricsExportTest {
+
+ private HttpConnectionManagerMetricsExport export;
+
+ @BeforeEach
+ void setUp() {
+ export = new HttpConnectionManagerMetricsExport();
+ }
+ @Test
+ void test_collect() {
+ Assertions.assertDoesNotThrow(() ->export.collect());
+ }
+
+ @Test
+ void test_setMetrics_when_zimbraHttpConnectionManager_is_null_then_do_nothing() {
+ GaugeMetricFamily gaugeMetricFamily = Mockito.mock(GaugeMetricFamily.class);
+ export.setMetrics(null, gaugeMetricFamily);
+ Mockito.verify(gaugeMetricFamily, Mockito.times(0))
+ .addMetric(Mockito.anyList(), Mockito.anyInt());
+ }
+
+ @Test
+ void test_setMetrics_when_zimbraHttpConnectionManager_is_not_instanceof_PoolingHttpClientConnectionManager_then_do_nothing() {
+ GaugeMetricFamily gaugeMetricFamily = Mockito.mock(GaugeMetricFamily.class);
+ ZimbraHttpConnectionManager zimbraHttpConnectionManager = Mockito.mock(ZimbraHttpConnectionManager.class);
+ export.setMetrics(zimbraHttpConnectionManager, gaugeMetricFamily);
+ Mockito.verify(gaugeMetricFamily, Mockito.times(0))
+ .addMetric(Mockito.anyList(), Mockito.anyInt());
+ }
+}
diff --git a/store/src/main/java/com/zextras/mailbox/metric/CarbonioMetricRegisterer.java b/store/src/main/java/com/zextras/mailbox/metric/CarbonioMetricRegisterer.java
new file mode 100644
index 00000000000..e0925def99e
--- /dev/null
+++ b/store/src/main/java/com/zextras/mailbox/metric/CarbonioMetricRegisterer.java
@@ -0,0 +1,26 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Zextras
+ *
+ * SPDX-License-Identifier: CC0-1.0
+ */
+package com.zextras.mailbox.metric;
+
+import com.zimbra.common.util.HttpConnectionManagerMetricsExport;
+import io.prometheus.client.CollectorRegistry;
+import io.prometheus.client.hotspot.DefaultExports;
+
+/**
+ * For register DefaultExports and project specific exports
+ */
+public final class CarbonioMetricRegisterer {
+
+ private CarbonioMetricRegisterer() {}
+ /**
+ *
+ * @param registry CollectorRegistry
+ */
+ public static void register(CollectorRegistry registry) {
+ DefaultExports.register(registry);
+ registry.register(new HttpConnectionManagerMetricsExport());
+ }
+}
diff --git a/store/src/main/java/com/zextras/mailbox/metric/Metrics.java b/store/src/main/java/com/zextras/mailbox/metric/Metrics.java
index 6d3bc9dd292..e2a716d9fd5 100644
--- a/store/src/main/java/com/zextras/mailbox/metric/Metrics.java
+++ b/store/src/main/java/com/zextras/mailbox/metric/Metrics.java
@@ -10,6 +10,7 @@
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
+import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;
/**
@@ -20,12 +21,21 @@
* @since 23.4.0
* @author davidefrison
*/
-public class Metrics {
+public final class Metrics {
public static final CollectorRegistry COLLECTOR_REGISTRY = new CollectorRegistry();
+ private Metrics() {}
/**
* Binds a Prometheus-compatible registry to the registry
*/
public static final MeterRegistry METER_REGISTRY =
new PrometheusMeterRegistry(PrometheusConfig.DEFAULT, COLLECTOR_REGISTRY, Clock.SYSTEM);
+
+ /**
+ * registers collector
+ */
+ public static void register(Collector collector) {
+ COLLECTOR_REGISTRY.register(collector);
+ }
+
}
diff --git a/store/src/main/java/com/zextras/mailbox/servlet/MetricsServletModule.java b/store/src/main/java/com/zextras/mailbox/servlet/MetricsServletModule.java
index 3655cdcdb1e..06f989880a4 100644
--- a/store/src/main/java/com/zextras/mailbox/servlet/MetricsServletModule.java
+++ b/store/src/main/java/com/zextras/mailbox/servlet/MetricsServletModule.java
@@ -8,6 +8,7 @@
import com.google.inject.Provides;
import com.google.inject.servlet.ServletModule;
+import com.zextras.mailbox.metric.CarbonioMetricRegisterer;
import com.zextras.mailbox.metric.Metrics;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exporter.MetricsServlet;
@@ -42,9 +43,8 @@ public MetricsServlet provideMetricsServlet(CollectorRegistry collectorRegistry)
@Provides
@Singleton
public CollectorRegistry provideCollector() {
- // TODO: it would be nice to get all collectors and register them programmatically
- final CollectorRegistry metricRegistry = Metrics.COLLECTOR_REGISTRY;
- DefaultExports.register(metricRegistry);
- return metricRegistry;
+ CarbonioMetricRegisterer.register(Metrics.COLLECTOR_REGISTRY);
+ return Metrics.COLLECTOR_REGISTRY;
}
+
}
diff --git a/store/src/test/java/com/zextras/mailbox/domain/usecase/metric/CarbonioMetricRegistererTest.java b/store/src/test/java/com/zextras/mailbox/domain/usecase/metric/CarbonioMetricRegistererTest.java
new file mode 100644
index 00000000000..15c20af9e56
--- /dev/null
+++ b/store/src/test/java/com/zextras/mailbox/domain/usecase/metric/CarbonioMetricRegistererTest.java
@@ -0,0 +1,92 @@
+package com.zextras.mailbox.domain.usecase.metric;
+
+import com.zextras.mailbox.metric.CarbonioMetricRegisterer;
+import com.zimbra.common.util.HttpConnectionManagerMetricsExport;
+import io.prometheus.client.CollectorRegistry;
+import io.prometheus.client.hotspot.BufferPoolsExports;
+import io.prometheus.client.hotspot.ClassLoadingExports;
+import io.prometheus.client.hotspot.GarbageCollectorExports;
+import io.prometheus.client.hotspot.MemoryAllocationExports;
+import io.prometheus.client.hotspot.MemoryPoolsExports;
+import io.prometheus.client.hotspot.StandardExports;
+import io.prometheus.client.hotspot.ThreadExports;
+import io.prometheus.client.hotspot.VersionInfoExports;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class CarbonioMetricRegistererTest {
+
+
+ @Test
+ void test_register_registers_StandardExports() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(StandardExports.class));
+
+ }
+
+ @Test
+ void test_register_registers_MemoryPoolsExports() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(MemoryPoolsExports.class));
+ }
+
+ @Test
+ void test_register_registers_MemoryAllocationExports() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(MemoryAllocationExports.class));
+ }
+
+ @Test
+ void test_register_registers_BufferPoolsExports() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(BufferPoolsExports.class));
+ }
+
+ @Test
+ void test_register_registers_GarbageCollectorExports() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(GarbageCollectorExports.class));
+ }
+
+ @Test
+ void test_register_registers_ThreadExports() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(ThreadExports.class));
+ }
+
+ @Test
+ void test_register_registers_ClassLoadingExports() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(ClassLoadingExports.class));
+ }
+
+ @Test
+ void test_register_registers_VersionInfoExports() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(VersionInfoExports.class));
+ }
+
+ @Test
+ void test_register_registers_HttpConnectionManagerMetricsExport() {
+ CollectorRegistry collectorRegistry = Mockito.mock(CollectorRegistry.class);
+ CarbonioMetricRegisterer.register(collectorRegistry);
+ Mockito.verify(collectorRegistry, Mockito.times(1))
+ .register(Mockito.any(HttpConnectionManagerMetricsExport.class));
+ }
+}
diff --git a/store/src/test/java/com/zextras/mailbox/domain/usecase/metric/MetricsTest.java b/store/src/test/java/com/zextras/mailbox/domain/usecase/metric/MetricsTest.java
new file mode 100644
index 00000000000..bf50b83a49d
--- /dev/null
+++ b/store/src/test/java/com/zextras/mailbox/domain/usecase/metric/MetricsTest.java
@@ -0,0 +1,17 @@
+package com.zextras.mailbox.domain.usecase.metric;
+
+import com.zextras.mailbox.metric.Metrics;
+import io.prometheus.client.Collector;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class MetricsTest {
+
+ @Test
+ void test_register() {
+ Collector collector = Mockito.mock(Collector.class);
+ Metrics.register(collector);
+ Assertions.assertDoesNotThrow(() -> Metrics.COLLECTOR_REGISTRY.unregister(collector));
+ }
+}
diff --git a/store/src/test/java/com/zextras/mailbox/servlet/MetricsServletModuleTest.java b/store/src/test/java/com/zextras/mailbox/servlet/MetricsServletModuleTest.java
new file mode 100644
index 00000000000..5b74df0c977
--- /dev/null
+++ b/store/src/test/java/com/zextras/mailbox/servlet/MetricsServletModuleTest.java
@@ -0,0 +1,13 @@
+package com.zextras.mailbox.servlet;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class MetricsServletModuleTest {
+
+ @Test
+ void test_provideCollector() {
+ MetricsServletModule module = new MetricsServletModule();
+ Assertions.assertNotNull(module.provideCollector());
+ }
+}