Alternative Prometheus metrics Histogram instead of locking Summary. (#1265)

* Alternative Prometheus metrics Histogram instead of locking Summary.

Add PrometheusHistogramMetricsTracker.java that uses an internal
Histogram instead of Summary. A summary is quite lock heavy.
See: https://github.com/prometheus/client_java/issues/328

* Fix incorrect magnitudes of buckets.
pull/1343/head
Vincent van Donselaar 6 years ago committed by Brett Wooldridge
parent 2d82b6f00a
commit efa25c3aaa

@ -0,0 +1,97 @@
/*
* Copyright (C) 2013 Brett Wooldridge
*
* 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 com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.IMetricsTracker;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter;
import io.prometheus.client.Histogram;
/**
* Alternative Prometheus metrics tracker using a Histogram instead of Summary
* <p>
* This is an alternative metrics tracker that doesn't use a {@link io.prometheus.client.Summary}. Summaries require
* heavy locks that might cause performance issues. Source: https://github.com/prometheus/client_java/issues/328
*
* @see PrometheusMetricsTracker
*/
class PrometheusHistogramMetricsTracker implements IMetricsTracker
{
private static final Counter CONNECTION_TIMEOUT_COUNTER = Counter.build()
.name("hikaricp_connection_timeout_total")
.labelNames("pool")
.help("Connection timeout total count")
.create();
private static final Histogram ELAPSED_ACQUIRED_HISTOGRAM =
registerHistogram("hikaricp_connection_acquired_nanos", "Connection acquired time (ns)", 1_000);
private static final Histogram ELAPSED_BORROWED_HISTOGRAM =
registerHistogram("hikaricp_connection_usage_millis", "Connection usage (ms)", 1);
private static final Histogram ELAPSED_CREATION_HISTOGRAM =
registerHistogram("hikaricp_connection_creation_millis", "Connection creation (ms)", 1);
private final Counter.Child connectionTimeoutCounterChild;
private static Histogram registerHistogram(String name, String help, double bucketStart) {
return Histogram.build()
.name(name)
.labelNames("pool")
.help(help)
.exponentialBuckets(bucketStart, 2.0, 11)
.create();
}
private final Histogram.Child elapsedAcquiredHistogramChild;
private final Histogram.Child elapsedBorrowedHistogramChild;
private final Histogram.Child elapsedCreationHistogramChild;
PrometheusHistogramMetricsTracker(String poolName, CollectorRegistry collectorRegistry) {
registerMetrics(collectorRegistry);
this.connectionTimeoutCounterChild = CONNECTION_TIMEOUT_COUNTER.labels(poolName);
this.elapsedAcquiredHistogramChild = ELAPSED_ACQUIRED_HISTOGRAM.labels(poolName);
this.elapsedBorrowedHistogramChild = ELAPSED_BORROWED_HISTOGRAM.labels(poolName);
this.elapsedCreationHistogramChild = ELAPSED_CREATION_HISTOGRAM.labels(poolName);
}
private void registerMetrics(CollectorRegistry collectorRegistry) {
CONNECTION_TIMEOUT_COUNTER.register(collectorRegistry);
ELAPSED_ACQUIRED_HISTOGRAM.register(collectorRegistry);
ELAPSED_BORROWED_HISTOGRAM.register(collectorRegistry);
ELAPSED_CREATION_HISTOGRAM.register(collectorRegistry);
}
@Override
public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos) {
elapsedAcquiredHistogramChild.observe(elapsedAcquiredNanos);
}
@Override
public void recordConnectionUsageMillis(long elapsedBorrowedMillis) {
elapsedBorrowedHistogramChild.observe(elapsedBorrowedMillis);
}
@Override
public void recordConnectionCreatedMillis(long connectionCreatedMillis) {
elapsedCreationHistogramChild.observe(connectionCreatedMillis);
}
@Override
public void recordConnectionTimeout() {
connectionTimeoutCounterChild.inc();
}
}

@ -0,0 +1,67 @@
/*
* Copyright (C) 2016 Brett Wooldridge
*
* 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 com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.IMetricsTracker;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.PoolStats;
import io.prometheus.client.CollectorRegistry;
/**
* <pre>{@code
* HikariConfig config = new HikariConfig();
* config.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory());
* }</pre>
*/
public class PrometheusHistogramMetricsTrackerFactory implements MetricsTrackerFactory {
private HikariCPCollector collector;
private CollectorRegistry collectorRegistry;
/**
* Default Constructor. The Hikari metrics are registered to the default
* collector registry ({@code CollectorRegistry.defaultRegistry}).
*/
public PrometheusHistogramMetricsTrackerFactory() {
this.collectorRegistry = CollectorRegistry.defaultRegistry;
}
/**
* Constructor that allows to pass in a {@link CollectorRegistry} to which the
* Hikari metrics are registered.
*/
public PrometheusHistogramMetricsTrackerFactory(CollectorRegistry collectorRegistry) {
this.collectorRegistry = collectorRegistry;
}
@Override
public IMetricsTracker create(String poolName, PoolStats poolStats) {
getCollector().add(poolName, poolStats);
return new PrometheusHistogramMetricsTracker(poolName, this.collectorRegistry);
}
/**
* initialize and register collector if it isn't initialized yet
*/
private HikariCPCollector getCollector() {
if (collector == null) {
collector = new HikariCPCollector().register(this.collectorRegistry);
}
return collector;
}
}

@ -26,6 +26,9 @@ import io.prometheus.client.CollectorRegistry;
* HikariConfig config = new HikariConfig();
* config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
* }</pre>
*
* Note: the internal {@see io.prometheus.client.Summary} requires heavy locks. Consider using
* {@see PrometheusHistogramMetricsTrackerFactory} if performance plays a role and you don't need the summary per se.
*/
public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory {

@ -0,0 +1,77 @@
package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.PoolStats;
import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;
import org.junit.After;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class PrometheusHistogramMetricsTrackerFactoryTest {
@Test
public void registersToProvidedCollectorRegistry() {
CollectorRegistry collectorRegistry = new CollectorRegistry();
PrometheusHistogramMetricsTrackerFactory factory =
new PrometheusHistogramMetricsTrackerFactory(collectorRegistry);
factory.create("testpool-1", poolStats());
assertHikariMetricsAreNotPresent(CollectorRegistry.defaultRegistry);
assertHikariMetricsArePresent(collectorRegistry);
}
@Test
public void registersToDefaultCollectorRegistry() {
PrometheusHistogramMetricsTrackerFactory factory = new PrometheusHistogramMetricsTrackerFactory();
factory.create("testpool-2", poolStats());
assertHikariMetricsArePresent(CollectorRegistry.defaultRegistry);
}
@After
public void clearCollectorRegistry(){
CollectorRegistry.defaultRegistry.clear();
}
private void assertHikariMetricsArePresent(CollectorRegistry collectorRegistry) {
List<String> registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples());
assertTrue(registeredMetrics.contains("hikaricp_active_connections"));
assertTrue(registeredMetrics.contains("hikaricp_idle_connections"));
assertTrue(registeredMetrics.contains("hikaricp_pending_threads"));
assertTrue(registeredMetrics.contains("hikaricp_connections"));
assertTrue(registeredMetrics.contains("hikaricp_max_connections"));
assertTrue(registeredMetrics.contains("hikaricp_min_connections"));
}
private void assertHikariMetricsAreNotPresent(CollectorRegistry collectorRegistry) {
List<String> registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples());
assertFalse(registeredMetrics.contains("hikaricp_active_connections"));
assertFalse(registeredMetrics.contains("hikaricp_idle_connections"));
assertFalse(registeredMetrics.contains("hikaricp_pending_threads"));
assertFalse(registeredMetrics.contains("hikaricp_connections"));
assertFalse(registeredMetrics.contains("hikaricp_max_connections"));
assertFalse(registeredMetrics.contains("hikaricp_min_connections"));
}
private List<String> toMetricNames(Enumeration<Collector.MetricFamilySamples> enumeration) {
List<String> list = new ArrayList<>();
while (enumeration.hasMoreElements()) {
list.add(enumeration.nextElement().name);
}
return list;
}
private PoolStats poolStats() {
return new PoolStats(0) {
@Override
protected void update() {
// do nothing
}
};
}
}

@ -0,0 +1,144 @@
/*
* Copyright (C) 2013, 2014 Brett Wooldridge
*
* 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 com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.prometheus.client.CollectorRegistry;
import org.junit.Before;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLTransientConnectionException;
import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
public class PrometheusHistogramMetricsTrackerTest {
private CollectorRegistry collectorRegistry;
private static final String POOL_LABEL_NAME = "pool";
@Before
public void setupCollectorRegistry(){
this.collectorRegistry = new CollectorRegistry();
}
@Test
public void recordConnectionTimeout() throws Exception {
HikariConfig config = newHikariConfig();
config.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(collectorRegistry));
config.setJdbcUrl("jdbc:h2:mem:");
config.setMaximumPoolSize(2);
config.setConnectionTimeout(250);
String[] labelNames = {POOL_LABEL_NAME};
String[] labelValues = {config.getPoolName()};
try (HikariDataSource hikariDataSource = new HikariDataSource(config)) {
try (Connection connection1 = hikariDataSource.getConnection();
Connection connection2 = hikariDataSource.getConnection()) {
try (Connection connection3 = hikariDataSource.getConnection()) {
} catch (SQLTransientConnectionException ignored) {
}
}
Double total = collectorRegistry.getSampleValue(
"hikaricp_connection_timeout_total",
labelNames,
labelValues
);
assertThat(total, is(1.0));
}
}
@Test
public void connectionAcquisitionMetrics() {
checkSummaryMetricFamily("hikaricp_connection_acquired_nanos");
}
@Test
public void connectionUsageMetrics() {
checkSummaryMetricFamily("hikaricp_connection_usage_millis");
}
@Test
public void connectionCreationMetrics() {
checkSummaryMetricFamily("hikaricp_connection_creation_millis");
}
@Test
public void testMultiplePoolName() throws Exception {
String[] labelNames = {POOL_LABEL_NAME};
HikariConfig config = newHikariConfig();
config.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(collectorRegistry));
config.setPoolName("first");
config.setJdbcUrl("jdbc:h2:mem:");
config.setMaximumPoolSize(2);
config.setConnectionTimeout(250);
String[] labelValues1 = {config.getPoolName()};
try (HikariDataSource ignored = new HikariDataSource(config)) {
assertThat(collectorRegistry.getSampleValue(
"hikaricp_connection_timeout_total",
labelNames,
labelValues1), is(0.0));
CollectorRegistry collectorRegistry2 = new CollectorRegistry();
HikariConfig config2 = newHikariConfig();
config2.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(collectorRegistry2));
config2.setPoolName("second");
config2.setJdbcUrl("jdbc:h2:mem:");
config2.setMaximumPoolSize(4);
config2.setConnectionTimeout(250);
String[] labelValues2 = {config2.getPoolName()};
try (HikariDataSource ignored2 = new HikariDataSource(config2)) {
assertThat(collectorRegistry2.getSampleValue(
"hikaricp_connection_timeout_total",
labelNames,
labelValues2), is(0.0));
}
}
}
private void checkSummaryMetricFamily(String metricName) {
HikariConfig config = newHikariConfig();
config.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(collectorRegistry));
config.setJdbcUrl("jdbc:h2:mem:");
try (HikariDataSource ignored = new HikariDataSource(config)) {
Double count = collectorRegistry.getSampleValue(
metricName + "_count",
new String[]{POOL_LABEL_NAME},
new String[]{config.getPoolName()}
);
assertNotNull(count);
Double sum = collectorRegistry.getSampleValue(
metricName + "_sum",
new String[]{POOL_LABEL_NAME},
new String[]{config.getPoolName()}
);
assertNotNull(sum);
}
}
}
Loading…
Cancel
Save