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()); + } +}