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
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;
|
||||
}
|
||||
}
|
@ -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…
Reference in New Issue