From b66a026347eec6335aa78b27a7747775e432b489 Mon Sep 17 00:00:00 2001 From: injectives <11927660+injectives@users.noreply.github.com> Date: Tue, 8 Mar 2022 12:30:27 +0000 Subject: [PATCH] Micrometer metrics (#1137) * [WIP] Micrometer metrics instrumentation This is a work-in-progress attempt at instrumenting the driver with Micrometer as an optional alternative metrics implementation to the existing internal one. * Bring it to life. * Update Micrometer metrics This update includes general improvements, refactorings and tests. * Make MetricsAdapter a supported, public api. - Set scope of micrometer to provided - Rename MetricsProvider to MetricsAdapter (it does not provide metrics, the driver does. It adapts the drivers metrics to something external) - Make it a public interface - Remove dependencies to other internal interfaces and classes (such as BoltServerAddress and the pool itself via ServerAddress and intsuppliers) - Reduce visibility of internal classes as much as possible - Make sure that adapters are not serialized - Apply the clock to the internal adapter (an oversight from the previous iteration) * Saving a file before commiting seems to be relevant. * Not messing up the file while saven even more so. * Make interfaces internal again, provide an enum to select the provider, use global registry by default. This change keeps the existing serialization feature of config correct. It will work for everyone just using micrometer as is with the global registry. if there is a need to select the registriy, we can work on that later. * Move micrometer-core optional tag to dependency declaration * Update Config javadoc and remove imports of internal metrics classes * Update withMetricsEnabled(boolean) implementation * Enable ConfigBuilder.withMetricsAdapter documentation * Update driver/src/main/java/org/neo4j/driver/ConnectionPoolMetrics.java Co-authored-by: Gerrit Meier * Update ConnectionPoolMetrics documentation * Update MetricsAdapter documentation * Update formatting in ConfigTest * Update wording * Updated wording * Formatting * Update driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsListener.java Co-authored-by: Gerrit Meier * Update driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetrics.java Co-authored-by: Gerrit Meier * Fix compilation error * Update failing test * Fix GetConnectionPoolMetrics access * Delete redundant counters based on review feedback Co-authored-by: Tommy Ludwig <8924140+shakuzen@users.noreply.github.com> Co-authored-by: Gerrit Meier Co-authored-by: Michael Simons --- bundle/pom.xml | 5 + driver/pom.xml | 5 + .../main/java/org/neo4j/driver/Config.java | 51 ++- .../neo4j/driver/ConnectionPoolMetrics.java | 9 +- .../main/java/org/neo4j/driver/Driver.java | 4 +- .../java/org/neo4j/driver/MetricsAdapter.java | 40 +++ .../neo4j/driver/internal/DriverFactory.java | 24 +- .../neo4j/driver/internal/InternalDriver.java | 5 +- .../async/pool/ConnectionPoolImpl.java | 9 +- .../async/pool/NettyChannelTracker.java | 13 +- .../ConnectionPoolMetricsListener.java | 81 +---- .../metrics/DevNullListenerEvent.java | 35 ++ .../metrics/DevNullMetricsListener.java | 100 ++++++ .../metrics/DevNullMetricsProvider.java | 42 +++ .../metrics/DevNullPoolMetricsListener.java | 74 +++++ .../metrics/InternalAbstractMetrics.java | 124 -------- .../InternalConnectionPoolMetrics.java | 51 +-- .../internal/metrics/InternalMetrics.java | 28 +- .../metrics/InternalMetricsProvider.java | 8 +- .../internal/metrics/ListenerEvent.java | 19 +- .../internal/metrics/MetricsListener.java | 41 +-- .../internal/metrics/MetricsProvider.java | 38 +-- .../MicrometerConnectionPoolMetrics.java | 265 ++++++++++++++++ .../internal/metrics/MicrometerMetrics.java | 143 +++++++++ .../metrics/MicrometerMetricsProvider.java | 58 ++++ .../metrics/MicrometerTimerListenerEvent.java | 45 +++ .../metrics/TimeRecorderListenerEvent.java | 6 +- .../driver/internal/spi/ConnectionPool.java | 5 +- .../java/org/neo4j/driver/ConfigTest.java | 64 ++-- .../integration/ConnectionHandlingIT.java | 4 +- .../neo4j/driver/integration/MetricsIT.java | 93 ++++++ .../driver/internal/DriverFactoryTest.java | 28 +- .../driver/internal/InternalDriverTest.java | 16 +- .../internal/async/NetworkConnectionTest.java | 4 +- .../async/pool/ConnectionPoolImplIT.java | 4 +- .../async/pool/ConnectionPoolImplTest.java | 4 +- .../async/pool/NettyChannelPoolIT.java | 4 +- .../async/pool/NettyChannelTrackerTest.java | 8 +- .../RoutingTableAndConnectionPoolTest.java | 6 +- .../MicrometerConnectionPoolMetricsTest.java | 300 ++++++++++++++++++ .../MicrometerMetricsProviderTest.java | 58 ++++ .../metrics/MicrometerMetricsTest.java | 247 ++++++++++++++ .../MicrometerTimerListenerEventTest.java | 53 ++++ .../util/FailingConnectionDriverFactory.java | 7 +- .../util/io/ChannelTrackingDriverFactory.java | 4 +- pom.xml | 7 + .../requests/GetConnectionPoolMetrics.java | 20 +- 47 files changed, 1861 insertions(+), 398 deletions(-) create mode 100644 driver/src/main/java/org/neo4j/driver/MetricsAdapter.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullListenerEvent.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsListener.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsProvider.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullPoolMetricsListener.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/InternalAbstractMetrics.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetrics.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetrics.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProvider.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEvent.java create mode 100644 driver/src/test/java/org/neo4j/driver/integration/MetricsIT.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetricsTest.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProviderTest.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsTest.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEventTest.java diff --git a/bundle/pom.xml b/bundle/pom.xml index 528f655584..362cdbb6b9 100644 --- a/bundle/pom.xml +++ b/bundle/pom.xml @@ -32,6 +32,11 @@ + + io.micrometer + micrometer-core + true + org.slf4j slf4j-api diff --git a/driver/pom.xml b/driver/pom.xml index 9a772d525d..2fcd530936 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -37,6 +37,11 @@ + + io.micrometer + micrometer-core + true + org.slf4j slf4j-api diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index 7032ad5336..9a027abb07 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -36,6 +36,7 @@ import org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.net.ServerAddressResolver; +import org.neo4j.driver.util.Experimental; import org.neo4j.driver.util.Immutable; import static java.lang.String.format; @@ -96,9 +97,9 @@ public class Config implements Serializable private final RetrySettings retrySettings; private final ServerAddressResolver resolver; - private final boolean isMetricsEnabled; private final int eventLoopThreads; private final String userAgent; + private final MetricsAdapter metricsAdapter; private Config( ConfigBuilder builder ) { @@ -122,7 +123,7 @@ private Config( ConfigBuilder builder ) this.fetchSize = builder.fetchSize; this.eventLoopThreads = builder.eventLoopThreads; - this.isMetricsEnabled = builder.isMetricsEnabled; + this.metricsAdapter = builder.metricsAdapter; } /** @@ -260,7 +261,12 @@ public int eventLoopThreads() */ public boolean isMetricsEnabled() { - return isMetricsEnabled; + return this.metricsAdapter != MetricsAdapter.DEV_NULL; + } + + public MetricsAdapter metricsAdapter() + { + return this.metricsAdapter; } /** @@ -290,7 +296,7 @@ public static class ConfigBuilder private int connectionTimeoutMillis = (int) TimeUnit.SECONDS.toMillis( 30 ); private RetrySettings retrySettings = RetrySettings.DEFAULT; private ServerAddressResolver resolver; - private boolean isMetricsEnabled = false; + private MetricsAdapter metricsAdapter = MetricsAdapter.DEV_NULL; private long fetchSize = FetchSizeUtil.DEFAULT_FETCH_SIZE; private int eventLoopThreads = 0; @@ -703,13 +709,13 @@ public ConfigBuilder withResolver( ServerAddressResolver resolver ) } /** - * Enable driver metrics. The metrics can be obtained afterwards via {@link Driver#metrics()}. + * Enable driver metrics backed by internal basic implementation. The metrics can be obtained afterwards via {@link Driver#metrics()}. + * * @return this builder. */ public ConfigBuilder withDriverMetrics() { - this.isMetricsEnabled = true; - return this; + return withMetricsEnabled( true ); } /** @@ -718,7 +724,36 @@ public ConfigBuilder withDriverMetrics() */ public ConfigBuilder withoutDriverMetrics() { - this.isMetricsEnabled = false; + return withMetricsEnabled( false ); + } + + private ConfigBuilder withMetricsEnabled( boolean enabled ) + { + if ( !enabled ) + { + withMetricsAdapter( MetricsAdapter.DEV_NULL ); + } + else if ( this.metricsAdapter == null || this.metricsAdapter == MetricsAdapter.DEV_NULL ) + { + withMetricsAdapter( MetricsAdapter.DEFAULT ); + } + return this; + } + + /** + * Enable driver metrics with given {@link MetricsAdapter}. + *

+ * {@link MetricsAdapter#MICROMETER} enables implementation based on Micrometer. The metrics can be obtained + * afterwards via Micrometer means and {@link Driver#metrics()}. Micrometer must be on classpath when using this option. + *

+ * + * @param metricsAdapter the metrics adapter to use. Use {@link MetricsAdapter#DEV_NULL} to disable metrics. + * @return this builder. + */ + @Experimental + public ConfigBuilder withMetricsAdapter( MetricsAdapter metricsAdapter ) + { + this.metricsAdapter = Objects.requireNonNull( metricsAdapter, "metricsAdapter" ); return this; } diff --git a/driver/src/main/java/org/neo4j/driver/ConnectionPoolMetrics.java b/driver/src/main/java/org/neo4j/driver/ConnectionPoolMetrics.java index 548f8c5beb..3c9c55115a 100644 --- a/driver/src/main/java/org/neo4j/driver/ConnectionPoolMetrics.java +++ b/driver/src/main/java/org/neo4j/driver/ConnectionPoolMetrics.java @@ -30,8 +30,9 @@ public interface ConnectionPoolMetrics { /** - * An unique id that identifies this pool metrics. - * @return An unique name + * A unique id that identifies this pool metrics. + * + * @return A unique name */ String id(); @@ -57,9 +58,9 @@ public interface ConnectionPoolMetrics int creating(); /** - * A counter to record how many connections have been successfully created with this pool since the pool is created. + * A counter to record how many connections have been successfully created with this pool since the pool was created. * This number increases every time when a connection is successfully created. - * @return The amount of connections have ever been created by this pool. + * @return The amount of connections that have ever been created by this pool. */ long created(); diff --git a/driver/src/main/java/org/neo4j/driver/Driver.java b/driver/src/main/java/org/neo4j/driver/Driver.java index 98218ed599..b5f1d48840 100644 --- a/driver/src/main/java/org/neo4j/driver/Driver.java +++ b/driver/src/main/java/org/neo4j/driver/Driver.java @@ -148,11 +148,10 @@ public interface Driver extends AutoCloseable /** * Returns the driver metrics if metrics reporting is enabled via {@link Config.ConfigBuilder#withDriverMetrics()}. - * Otherwise a {@link ClientException} will be thrown. + * Otherwise, a {@link ClientException} will be thrown. * @return the driver metrics if enabled. * @throws ClientException if the driver metrics reporting is not enabled. */ - @Experimental Metrics metrics(); /** @@ -160,7 +159,6 @@ public interface Driver extends AutoCloseable * * @return true if the metrics reporting is enabled. */ - @Experimental boolean isMetricsEnabled(); /** diff --git a/driver/src/main/java/org/neo4j/driver/MetricsAdapter.java b/driver/src/main/java/org/neo4j/driver/MetricsAdapter.java new file mode 100644 index 0000000000..e78e93ba0a --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/MetricsAdapter.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver; + +/** + * Defines which metrics consumer to use: Should metrics be consumed and exposed via driver's default consumer or provided with one of the external facades. + */ +public enum MetricsAdapter +{ + /** + * Disables metrics. + */ + DEV_NULL, + + /** + * Consumes and publishes metrics via the driver itself. + */ + DEFAULT, + + /** + * Consumes and publishes metrics via Micrometer. Ensure that Micrometer is on classpath when using this option. + */ + MICROMETER +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java index 968db9d367..63035a0f8e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -31,6 +31,7 @@ import org.neo4j.driver.Driver; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; +import org.neo4j.driver.MetricsAdapter; import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; @@ -42,8 +43,10 @@ import org.neo4j.driver.internal.cluster.loadbalancing.LoadBalancer; import org.neo4j.driver.internal.cluster.loadbalancing.LoadBalancingStrategy; import org.neo4j.driver.internal.logging.NettyLogging; +import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; import org.neo4j.driver.internal.metrics.InternalMetricsProvider; import org.neo4j.driver.internal.metrics.MetricsProvider; +import org.neo4j.driver.internal.metrics.MicrometerMetricsProvider; import org.neo4j.driver.internal.retry.ExponentialBackoffRetryLogic; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.retry.RetrySettings; @@ -56,7 +59,6 @@ import static org.neo4j.driver.internal.Scheme.isRoutingScheme; import static org.neo4j.driver.internal.cluster.IdentityResolver.IDENTITY_RESOLVER; -import static org.neo4j.driver.internal.metrics.MetricsProvider.METRICS_DISABLED_PROVIDER; import static org.neo4j.driver.internal.util.ErrorUtil.addSuppressed; public class DriverFactory @@ -94,7 +96,7 @@ public final Driver newInstance( URI uri, AuthToken authToken, RoutingSettings r EventExecutorGroup eventExecutorGroup = bootstrap.config().group(); RetryLogic retryLogic = createRetryLogic( retrySettings, eventExecutorGroup, config.logging() ); - MetricsProvider metricsProvider = createDriverMetrics( config, createClock() ); + MetricsProvider metricsProvider = getOrCreateMetricsProvider( config, createClock() ); ConnectionPool connectionPool = createConnectionPool( authToken, securityPlan, bootstrap, metricsProvider, config, ownsEventLoopGroup, newRoutingSettings.routingContext() ); @@ -114,16 +116,24 @@ protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan return new ConnectionPoolImpl( connector, bootstrap, poolSettings, metricsProvider.metricsListener(), config.logging(), clock, ownsEventLoopGroup ); } - protected static MetricsProvider createDriverMetrics( Config config, Clock clock ) + protected static MetricsProvider getOrCreateMetricsProvider( Config config, Clock clock ) { - if( config.isMetricsEnabled() ) + MetricsAdapter metricsAdapter = config.metricsAdapter(); + // This can actually only happen when someone mocks the config + if ( metricsAdapter == null ) { - return new InternalMetricsProvider( clock, config.logging() ); + metricsAdapter = config.isMetricsEnabled() ? MetricsAdapter.DEFAULT : MetricsAdapter.DEV_NULL; } - else + switch ( metricsAdapter ) { - return METRICS_DISABLED_PROVIDER; + case DEV_NULL: + return DevNullMetricsProvider.INSTANCE; + case DEFAULT: + return new InternalMetricsProvider( clock, config.logging() ); + case MICROMETER: + return MicrometerMetricsProvider.forGlobalRegistry(); } + throw new IllegalStateException( "Unknown or unsupported MetricsAdapter: " + metricsAdapter ); } protected ChannelConnector createConnector( ConnectionSettings settings, SecurityPlan securityPlan, diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java index b2c975da1c..8d9acaa9cb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java @@ -25,12 +25,13 @@ import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; import org.neo4j.driver.Metrics; +import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.Session; import org.neo4j.driver.SessionConfig; import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.internal.async.InternalAsyncSession; import org.neo4j.driver.internal.async.NetworkSession; -import org.neo4j.driver.internal.metrics.MetricsProvider; +import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; import org.neo4j.driver.internal.reactive.InternalRxSession; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.types.InternalTypeSystem; @@ -102,7 +103,7 @@ public Metrics metrics() @Override public boolean isMetricsEnabled() { - return metricsProvider.isMetricsEnabled(); + return metricsProvider != DevNullMetricsProvider.INSTANCE; } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImpl.java index 314d38850e..db46e716a8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImpl.java @@ -47,6 +47,7 @@ import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.Futures; +import org.neo4j.driver.net.ServerAddress; import static java.lang.String.format; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setAuthorizationStateListener; @@ -159,13 +160,13 @@ public void retainAll( Set addressesToRetain ) } @Override - public int inUseConnections( BoltServerAddress address ) + public int inUseConnections( ServerAddress address ) { return nettyChannelTracker.inUseChannelCount( address ); } @Override - public int idleConnections( BoltServerAddress address ) + public int idleConnections( ServerAddress address ) { return nettyChannelTracker.idleChannelCount( address ); } @@ -284,8 +285,8 @@ private ExtendedChannelPool getOrCreatePool( BoltServerAddress address ) if ( pool == null ) { pool = newPool( address ); - // before the connection pool is added I can add the metrics for the pool. - metricsListener.putPoolMetrics( pool.id(), address, this ); + // before the connection pool is added I can register the metrics for the pool. + metricsListener.registerPoolMetrics( pool.id(), address, () -> this.inUseConnections( address ), () -> this.idleConnections( address ) ); addressToPool.put( address, pool ); } return pool; diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelTracker.java b/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelTracker.java index 341bf9a8cb..44bde86033 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelTracker.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/pool/NettyChannelTracker.java @@ -37,6 +37,7 @@ import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.metrics.ListenerEvent; import org.neo4j.driver.internal.metrics.MetricsListener; +import org.neo4j.driver.net.ServerAddress; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.poolId; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.serverAddress; @@ -46,8 +47,8 @@ public class NettyChannelTracker implements ChannelPoolHandler private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock read = lock.readLock(); private final Lock write = lock.writeLock(); - private final Map addressToInUseChannelCount = new HashMap<>(); - private final Map addressToIdleChannelCount = new HashMap<>(); + private final Map addressToInUseChannelCount = new HashMap<>(); + private final Map addressToIdleChannelCount = new HashMap<>(); private final Logger log; private final MetricsListener metricsListener; private final ChannelFutureListener closeListener = future -> channelClosed( future.channel() ); @@ -152,12 +153,12 @@ public void channelClosed( Channel channel ) metricsListener.afterClosed( poolId( channel ) ); } - public int inUseChannelCount( BoltServerAddress address ) + public int inUseChannelCount( ServerAddress address ) { return retrieveInReadLock( () -> addressToInUseChannelCount.getOrDefault( address, 0 ) ); } - public int idleChannelCount( BoltServerAddress address ) + public int idleChannelCount( ServerAddress address ) { return retrieveInReadLock( () -> addressToIdleChannelCount.getOrDefault( address, 0 ) ); } @@ -213,9 +214,9 @@ private void decrementIdle( Channel channel ) addressToIdleChannelCount.put( address, count - 1 ); } - private void increment( Channel channel, Map countMap ) + private void increment( Channel channel, Map countMap ) { - BoltServerAddress address = serverAddress( channel ); + ServerAddress address = serverAddress( channel ); Integer count = countMap.computeIfAbsent( address, k -> 0 ); countMap.put( address, count + 1 ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/ConnectionPoolMetricsListener.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/ConnectionPoolMetricsListener.java index 647694e707..3ca86713f6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/ConnectionPoolMetricsListener.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/ConnectionPoolMetricsListener.java @@ -18,17 +18,17 @@ */ package org.neo4j.driver.internal.metrics; -public interface ConnectionPoolMetricsListener +interface ConnectionPoolMetricsListener { /** * Invoked before a connection is creating. */ - void beforeCreating( ListenerEvent listenerEvent ); + void beforeCreating( ListenerEvent listenerEvent ); /** * Invoked after a connection is created successfully. */ - void afterCreated( ListenerEvent listenerEvent ); + void afterCreated( ListenerEvent listenerEvent ); /** * Invoked after a connection is failed to create due to timeout, any kind of error. @@ -42,9 +42,10 @@ public interface ConnectionPoolMetricsListener /** * Invoked before acquiring or creating a connection. + * * @param acquireEvent */ - void beforeAcquiringOrCreating( ListenerEvent acquireEvent ); + void beforeAcquiringOrCreating( ListenerEvent acquireEvent ); /** * Invoked after a connection is being acquired or created regardless weather it is successful or not. @@ -53,9 +54,10 @@ public interface ConnectionPoolMetricsListener /** * Invoked after a connection is acquired or created successfully. + * * @param acquireEvent */ - void afterAcquiredOrCreated( ListenerEvent acquireEvent ); + void afterAcquiredOrCreated( ListenerEvent acquireEvent ); /** * Invoked after it is timed out to acquire or create a connection. @@ -64,77 +66,16 @@ public interface ConnectionPoolMetricsListener /** * After a connection is acquired from the pool. + * * @param inUseEvent */ - void acquired( ListenerEvent inUseEvent ); + void acquired( ListenerEvent inUseEvent ); /** * After a connection is released back to pool. + * * @param inUseEvent */ - void released( ListenerEvent inUseEvent ); - - ConnectionPoolMetricsListener DEV_NULL_POOL_METRICS_LISTENER = new ConnectionPoolMetricsListener() - { - @Override - public void beforeCreating( ListenerEvent listenerEvent ) - { - - } - - @Override - public void afterCreated( ListenerEvent listenerEvent ) - { - - } - - @Override - public void afterFailedToCreate() - { - - } - - @Override - public void afterClosed() - { - - } - - @Override - public void beforeAcquiringOrCreating( ListenerEvent acquireEvent ) - { - - } - - @Override - public void afterAcquiringOrCreating() - { - - } - - @Override - public void afterAcquiredOrCreated( ListenerEvent acquireEvent ) - { - - } - - @Override - public void afterTimedOutToAcquireOrCreate() - { - - } - - @Override - public void acquired( ListenerEvent inUseEvent ) - { - - } - - @Override - public void released( ListenerEvent inUseEvent ) - { - - } - }; + void released( ListenerEvent inUseEvent ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullListenerEvent.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullListenerEvent.java new file mode 100644 index 0000000000..6c17eab971 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullListenerEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +enum DevNullListenerEvent implements ListenerEvent +{ + INSTANCE; + + @Override + public void start() + { + } + + @Override + public Long getSample() + { + return 0L; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsListener.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsListener.java new file mode 100644 index 0000000000..4915906800 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsListener.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import java.util.function.IntSupplier; + +import org.neo4j.driver.net.ServerAddress; + +public enum DevNullMetricsListener implements MetricsListener +{ + INSTANCE; + + @Override + public void beforeCreating( String poolId, ListenerEvent creatingEvent ) + { + } + + @Override + public void afterCreated( String poolId, ListenerEvent creatingEvent ) + { + } + + @Override + public void afterFailedToCreate( String poolId ) + { + } + + @Override + public void afterClosed( String poolId ) + { + } + + @Override + public void beforeAcquiringOrCreating( String poolId, ListenerEvent acquireEvent ) + { + } + + @Override + public void afterAcquiringOrCreating( String poolId ) + { + } + + @Override + public void afterAcquiredOrCreated( String poolId, ListenerEvent acquireEvent ) + { + } + + @Override + public void afterTimedOutToAcquireOrCreate( String poolId ) + { + } + + @Override + public void afterConnectionCreated( String poolId, ListenerEvent inUseEvent ) + { + } + + @Override + public void afterConnectionReleased( String poolId, ListenerEvent inUseEvent ) + { + } + + @Override + public ListenerEvent createListenerEvent() + { + return DevNullListenerEvent.INSTANCE; + } + + @Override + public void registerPoolMetrics( String poolId, ServerAddress serverAddress, IntSupplier inUseSupplier, IntSupplier idleSupplier ) + { + } + + @Override + public void removePoolMetrics( String poolId ) + { + } + + @Override + public String toString() + { + return "Driver metrics are not available if they are not enabled."; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsProvider.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsProvider.java new file mode 100644 index 0000000000..3cf250cb4d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import org.neo4j.driver.Metrics; +import org.neo4j.driver.exceptions.ClientException; + +public enum DevNullMetricsProvider implements MetricsProvider +{ + INSTANCE; + + @Override + public Metrics metrics() + { + // To outside users, we forbid access to the metrics API + throw new ClientException( + "Driver metrics are not enabled. You need to enable driver metrics in driver configuration in order to access them." ); + } + + @Override + public MetricsListener metricsListener() + { + // Internally we can still register callbacks to this empty metrics listener. + return DevNullMetricsListener.INSTANCE; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullPoolMetricsListener.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullPoolMetricsListener.java new file mode 100644 index 0000000000..3d4dcc74e6 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullPoolMetricsListener.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +enum DevNullPoolMetricsListener implements ConnectionPoolMetricsListener +{ + INSTANCE; + + @Override + public void beforeCreating( ListenerEvent listenerEvent ) + { + } + + @Override + public void afterCreated( ListenerEvent listenerEvent ) + { + } + + @Override + public void afterFailedToCreate() + { + } + + @Override + public void afterClosed() + { + } + + @Override + public void beforeAcquiringOrCreating( ListenerEvent acquireEvent ) + { + } + + @Override + public void afterAcquiringOrCreating() + { + } + + @Override + public void afterAcquiredOrCreated( ListenerEvent acquireEvent ) + { + } + + @Override + public void afterTimedOutToAcquireOrCreate() + { + } + + @Override + public void acquired( ListenerEvent inUseEvent ) + { + } + + @Override + public void released( ListenerEvent inUseEvent ) + { + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalAbstractMetrics.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalAbstractMetrics.java deleted file mode 100644 index 88eba87128..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalAbstractMetrics.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * 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 org.neo4j.driver.internal.metrics; - -import java.util.Collection; -import java.util.Collections; - -import org.neo4j.driver.ConnectionPoolMetrics; -import org.neo4j.driver.Metrics; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl; - -public abstract class InternalAbstractMetrics implements Metrics, MetricsListener -{ - public static final InternalAbstractMetrics DEV_NULL_METRICS = new InternalAbstractMetrics() - { - - @Override - public void beforeCreating( String poolId, ListenerEvent creatingEvent ) - { - - } - - @Override - public void afterCreated( String poolId, ListenerEvent creatingEvent ) - { - - } - - @Override - public void afterFailedToCreate( String poolId ) - { - - } - - @Override - public void afterClosed( String poolId ) - { - - } - - @Override - public void beforeAcquiringOrCreating( String poolId, ListenerEvent acquireEvent ) - { - - } - - @Override - public void afterAcquiringOrCreating( String poolId ) - { - - } - - @Override - public void afterAcquiredOrCreated( String poolId, ListenerEvent acquireEvent ) - { - - } - - @Override - public void afterTimedOutToAcquireOrCreate( String poolId ) - { - - } - - @Override - public void afterConnectionCreated( String poolId, ListenerEvent inUseEvent ) - { - - } - - @Override - public void afterConnectionReleased( String poolId, ListenerEvent inUseEvent ) - { - - } - - @Override - public ListenerEvent createListenerEvent() - { - return ListenerEvent.DEV_NULL_LISTENER_EVENT; - } - - @Override - public void putPoolMetrics( String id, BoltServerAddress address, ConnectionPoolImpl connectionPool ) - { - - } - - @Override - public void removePoolMetrics( String poolId ) - { - - } - - @Override - public Collection connectionPoolMetrics() - { - return Collections.emptySet(); - } - - @Override - public String toString() - { - return "Driver metrics not available while driver metrics is not enabled."; - } - }; -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalConnectionPoolMetrics.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalConnectionPoolMetrics.java index 073a4a1952..ba9c80fef8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalConnectionPoolMetrics.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalConnectionPoolMetrics.java @@ -21,17 +21,18 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.IntSupplier; import org.neo4j.driver.ConnectionPoolMetrics; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.spi.ConnectionPool; +import org.neo4j.driver.net.ServerAddress; import static java.lang.String.format; -public class InternalConnectionPoolMetrics implements ConnectionPoolMetrics, ConnectionPoolMetricsListener +final class InternalConnectionPoolMetrics implements ConnectionPoolMetrics, ConnectionPoolMetricsListener { - private final BoltServerAddress address; - private final ConnectionPool pool; + private final ServerAddress address; + private final IntSupplier inUseSupplier; + private final IntSupplier idleSupplier; private final AtomicLong closed = new AtomicLong(); @@ -52,18 +53,20 @@ public class InternalConnectionPoolMetrics implements ConnectionPoolMetrics, Con private final AtomicLong totalInUseCount = new AtomicLong(); private final String id; - InternalConnectionPoolMetrics( String poolId, BoltServerAddress address, ConnectionPool pool ) + InternalConnectionPoolMetrics( String poolId, ServerAddress address, IntSupplier inUseSupplier, IntSupplier idleSupplier ) { Objects.requireNonNull( address ); - Objects.requireNonNull( pool ); + Objects.requireNonNull( inUseSupplier ); + Objects.requireNonNull( idleSupplier ); this.id = poolId; this.address = address; - this.pool = pool; + this.inUseSupplier = inUseSupplier; + this.idleSupplier = idleSupplier; } @Override - public void beforeCreating( ListenerEvent connEvent ) + public void beforeCreating( ListenerEvent connEvent ) { creating.incrementAndGet(); connEvent.start(); @@ -77,13 +80,13 @@ public void afterFailedToCreate() } @Override - public void afterCreated( ListenerEvent connEvent ) + public void afterCreated( ListenerEvent connEvent ) { created.incrementAndGet(); creating.decrementAndGet(); - long elapsed = connEvent.elapsed(); + long sample = ((TimeRecorderListenerEvent) connEvent).getSample(); - totalConnectionTime.addAndGet( elapsed ); + totalConnectionTime.addAndGet( sample ); } @Override @@ -93,7 +96,7 @@ public void afterClosed() } @Override - public void beforeAcquiringOrCreating( ListenerEvent acquireEvent ) + public void beforeAcquiringOrCreating( ListenerEvent acquireEvent ) { acquireEvent.start(); acquiring.incrementAndGet(); @@ -106,12 +109,12 @@ public void afterAcquiringOrCreating() } @Override - public void afterAcquiredOrCreated( ListenerEvent acquireEvent ) + public void afterAcquiredOrCreated( ListenerEvent acquireEvent ) { acquired.incrementAndGet(); - long elapsed = acquireEvent.elapsed(); + long sample = ((TimeRecorderListenerEvent) acquireEvent).getSample(); - totalAcquisitionTime.addAndGet( elapsed ); + totalAcquisitionTime.addAndGet( sample ); } @Override @@ -121,18 +124,18 @@ public void afterTimedOutToAcquireOrCreate() } @Override - public void acquired( ListenerEvent inUseEvent ) + public void acquired( ListenerEvent inUseEvent ) { inUseEvent.start(); } @Override - public void released( ListenerEvent inUseEvent ) + public void released( ListenerEvent inUseEvent ) { totalInUseCount.incrementAndGet(); - long elapsed = inUseEvent.elapsed(); + long sample = ((TimeRecorderListenerEvent) inUseEvent).getSample(); - totalInUseTime.addAndGet( elapsed ); + totalInUseTime.addAndGet( sample ); } @Override @@ -144,13 +147,13 @@ public String id() @Override public int inUse() { - return pool.inUseConnections( address ); + return inUseSupplier.getAsInt(); } @Override public int idle() { - return pool.idleConnections( address ); + return idleSupplier.getAsInt(); } @Override @@ -230,8 +233,8 @@ public String toString() totalAcquisitionTime(), totalConnectionTime(), totalInUseTime(), totalInUseCount() ); } - // This method is for purposes testing only - public BoltServerAddress getAddress() + // This method is for testing purposes only + public ServerAddress getAddress() { return address; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetrics.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetrics.java index a83b033529..9cda445b71 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetrics.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetrics.java @@ -22,19 +22,19 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.IntSupplier; import org.neo4j.driver.ConnectionPoolMetrics; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl; +import org.neo4j.driver.Metrics; import org.neo4j.driver.internal.util.Clock; +import org.neo4j.driver.net.ServerAddress; import static java.lang.String.format; import static java.util.Collections.unmodifiableCollection; -import static org.neo4j.driver.internal.metrics.ConnectionPoolMetricsListener.DEV_NULL_POOL_METRICS_LISTENER; -public class InternalMetrics extends InternalAbstractMetrics +final class InternalMetrics implements Metrics, MetricsListener { private final Map connectionPoolMetrics; private final Clock clock; @@ -49,9 +49,9 @@ public class InternalMetrics extends InternalAbstractMetrics } @Override - public void putPoolMetrics( String poolId, BoltServerAddress serverAddress, ConnectionPoolImpl pool ) + public void registerPoolMetrics( String poolId, ServerAddress serverAddress, IntSupplier inUseSupplier, IntSupplier idleSupplier ) { - this.connectionPoolMetrics.put( poolId, new InternalConnectionPoolMetrics( poolId, serverAddress, pool ) ); + this.connectionPoolMetrics.put( poolId, new InternalConnectionPoolMetrics( poolId, serverAddress, inUseSupplier, idleSupplier ) ); } @Override @@ -61,13 +61,13 @@ public void removePoolMetrics( String id ) } @Override - public void beforeCreating( String poolId, ListenerEvent creatingEvent ) + public void beforeCreating( String poolId, ListenerEvent creatingEvent ) { poolMetrics( poolId ).beforeCreating( creatingEvent ); } @Override - public void afterCreated( String poolId, ListenerEvent creatingEvent ) + public void afterCreated( String poolId, ListenerEvent creatingEvent ) { poolMetrics( poolId ).afterCreated( creatingEvent ); } @@ -85,7 +85,7 @@ public void afterClosed( String poolId ) } @Override - public void beforeAcquiringOrCreating( String poolId, ListenerEvent acquireEvent ) + public void beforeAcquiringOrCreating( String poolId, ListenerEvent acquireEvent ) { poolMetrics( poolId ).beforeAcquiringOrCreating( acquireEvent ); } @@ -97,19 +97,19 @@ public void afterAcquiringOrCreating( String poolId ) } @Override - public void afterAcquiredOrCreated( String poolId, ListenerEvent acquireEvent ) + public void afterAcquiredOrCreated( String poolId, ListenerEvent acquireEvent ) { poolMetrics( poolId ).afterAcquiredOrCreated( acquireEvent ); } @Override - public void afterConnectionCreated( String poolId, ListenerEvent inUseEvent ) + public void afterConnectionCreated( String poolId, ListenerEvent inUseEvent ) { poolMetrics( poolId ).acquired( inUseEvent ); } @Override - public void afterConnectionReleased( String poolId, ListenerEvent inUseEvent ) + public void afterConnectionReleased( String poolId, ListenerEvent inUseEvent ) { poolMetrics( poolId ).released( inUseEvent ); } @@ -121,7 +121,7 @@ public void afterTimedOutToAcquireOrCreate( String poolId ) } @Override - public ListenerEvent createListenerEvent() + public ListenerEvent createListenerEvent() { return new TimeRecorderListenerEvent( clock ); } @@ -144,7 +144,7 @@ private ConnectionPoolMetricsListener poolMetrics( String poolId ) if ( poolMetrics == null ) { log.warn( format( "Failed to find pool metrics with id `%s` in %s.", poolId, this.connectionPoolMetrics ) ); - return DEV_NULL_POOL_METRICS_LISTENER; + return DevNullPoolMetricsListener.INSTANCE; } return poolMetrics; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetricsProvider.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetricsProvider.java index b807f055c8..0980dbf218 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetricsProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetricsProvider.java @@ -22,7 +22,7 @@ import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.Metrics; -public class InternalMetricsProvider implements MetricsProvider +public final class InternalMetricsProvider implements MetricsProvider { private final InternalMetrics metrics; @@ -42,10 +42,4 @@ public MetricsListener metricsListener() { return metrics; } - - @Override - public boolean isMetricsEnabled() - { - return true; - } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/ListenerEvent.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/ListenerEvent.java index ddbf09617d..543ee0e723 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/ListenerEvent.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/ListenerEvent.java @@ -18,23 +18,10 @@ */ package org.neo4j.driver.internal.metrics; -public interface ListenerEvent +public interface ListenerEvent { - ListenerEvent DEV_NULL_LISTENER_EVENT = new ListenerEvent() - { - @Override - public void start() - { - } - - @Override - public long elapsed() - { - return 0; - } - }; - void start(); - long elapsed(); + + T getSample(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsListener.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsListener.java index c96e018048..48e5710b2a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsListener.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsListener.java @@ -19,26 +19,27 @@ package org.neo4j.driver.internal.metrics; import java.util.concurrent.TimeUnit; +import java.util.function.IntSupplier; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.async.NetworkConnection; -import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl; import org.neo4j.driver.Config; +import org.neo4j.driver.net.ServerAddress; public interface MetricsListener { /** * Before creating a netty channel. - * @param poolId the id of the pool where the netty channel lives. + * + * @param poolId the id of the pool where the netty channel lives. * @param creatingEvent a connection listener event registered when a connection is creating. */ - void beforeCreating( String poolId, ListenerEvent creatingEvent ); + void beforeCreating( String poolId, ListenerEvent creatingEvent ); /** * After a netty channel is created successfully. + * * @param poolId the id of the pool where the netty channel lives. */ - void afterCreated( String poolId, ListenerEvent creatingEvent ); + void afterCreated( String poolId, ListenerEvent creatingEvent ); /** * After a netty channel is created with a failure. @@ -54,10 +55,11 @@ public interface MetricsListener /** * Before acquiring or creating a new netty channel from pool. - * @param poolId the id of the pool where the netty channel lives. + * + * @param poolId the id of the pool where the netty channel lives. * @param acquireEvent a pool listener event registered in pool for this acquire event. */ - void beforeAcquiringOrCreating( String poolId, ListenerEvent acquireEvent ); + void beforeAcquiringOrCreating( String poolId, ListenerEvent acquireEvent ); /** * After acquiring or creating a new netty channel from pool regardless it is successful or not. @@ -67,10 +69,11 @@ public interface MetricsListener /** * After acquiring or creating a new netty channel from pool successfully. - * @param poolId the id of the pool where the netty channel lives. + * + * @param poolId the id of the pool where the netty channel lives. * @param acquireEvent a pool listener event registered in pool for this acquire event. */ - void afterAcquiredOrCreated( String poolId, ListenerEvent acquireEvent ); + void afterAcquiredOrCreated( String poolId, ListenerEvent acquireEvent ); /** * After we failed to acquire a connection from pool within maximum connection acquisition timeout set by @@ -81,21 +84,23 @@ public interface MetricsListener /** * After acquiring or creating a new netty channel from pool successfully. - * @param poolId the id of the pool where the netty channel lives. - * @param inUseEvent a connection listener registered with a {@link NetworkConnection} when created. + * + * @param poolId the id of the pool where the netty channel lives. + * @param inUseEvent a connection listener event fired from the newly created connection. */ - void afterConnectionCreated( String poolId, ListenerEvent inUseEvent ); + void afterConnectionCreated( String poolId, ListenerEvent inUseEvent ); /** * After releasing a netty channel back to pool successfully. - * @param poolId the id of the pool where the netty channel lives. - * @param inUseEvent a connection listener registered with a {@link NetworkConnection} when destroyed. + * + * @param poolId the id of the pool where the netty channel lives. + * @param inUseEvent a connection listener event fired from the connection being released. */ - void afterConnectionReleased( String poolId, ListenerEvent inUseEvent ); + void afterConnectionReleased( String poolId, ListenerEvent inUseEvent ); - ListenerEvent createListenerEvent(); + ListenerEvent createListenerEvent(); - void putPoolMetrics( String poolId, BoltServerAddress address, ConnectionPoolImpl connectionPool ); + void registerPoolMetrics( String poolId, ServerAddress serverAddress, IntSupplier inUseSupplier, IntSupplier idleSupplier ); void removePoolMetrics( String poolId ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsProvider.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsProvider.java index 8d476fa381..a2cfb5e1b7 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsProvider.java @@ -19,39 +19,19 @@ package org.neo4j.driver.internal.metrics; import org.neo4j.driver.Metrics; -import org.neo4j.driver.exceptions.ClientException; - -import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; +/** + * An adapter that collects driver metrics via {@link MetricsListener} and publishes them via {@link Metrics} instance. + */ public interface MetricsProvider { - MetricsProvider METRICS_DISABLED_PROVIDER = new MetricsProvider() - { - @Override - public Metrics metrics() - { - // To outside users, we forbidden their access to the metrics API - throw new ClientException( "Driver metrics not enabled. To access driver metrics, " + - "you need to enabled driver metrics in the driver's configuration." ); - } - - @Override - public MetricsListener metricsListener() - { - // Internally we can still register callbacks to this empty metrics listener. - return DEV_NULL_METRICS; - } - - @Override - public boolean isMetricsEnabled() - { - return false; - } - }; - + /** + * @return The actual metrics type to use + */ Metrics metrics(); + /** + * @return A listener that will be notified on certain events so that it can collect metrics about them. + */ MetricsListener metricsListener(); - - boolean isMetricsEnabled(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetrics.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetrics.java new file mode 100644 index 0000000000..0e53c15f3b --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetrics.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntSupplier; + +import org.neo4j.driver.ConnectionPoolMetrics; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.net.ServerAddress; + +import static java.lang.String.format; + +final class MicrometerConnectionPoolMetrics implements ConnectionPoolMetricsListener, ConnectionPoolMetrics +{ + public static final String PREFIX = "neo4j.driver.connections"; + public static final String IN_USE = PREFIX + ".in.use"; + public static final String IDLE = PREFIX + ".idle"; + public static final String CREATING = PREFIX + ".creating"; + public static final String FAILED = PREFIX + ".failed"; + public static final String CLOSED = PREFIX + ".closed"; + public static final String ACQUIRING = PREFIX + ".acquiring"; + public static final String ACQUISITION_TIMEOUT = PREFIX + ".acquisition.timeout"; + public static final String ACQUISITION = PREFIX + ".acquisition"; + public static final String CREATION = PREFIX + ".creation"; + public static final String USAGE = PREFIX + ".usage"; + + private final IntSupplier inUseSupplier; + private final IntSupplier idleSupplier; + + private final String id; + + private final AtomicInteger creating = new AtomicInteger(); + private final Counter failedToCreate; + private final Counter closed; + private final AtomicInteger acquiring = new AtomicInteger(); + private final Counter timedOutToAcquire; + private final Timer totalAcquisitionTimer; + private final Timer totalConnectionTimer; + private final Timer totalInUseTimer; + + MicrometerConnectionPoolMetrics( String poolId, ServerAddress address, IntSupplier inUseSupplier, IntSupplier idleSupplier, MeterRegistry registry ) + { + this( poolId, address, inUseSupplier, idleSupplier, registry, Tags.empty() ); + } + + MicrometerConnectionPoolMetrics( String poolId, ServerAddress address, IntSupplier inUseSupplier, IntSupplier idleSupplier, MeterRegistry registry, Iterable initialTags ) + { + Objects.requireNonNull( poolId ); + Objects.requireNonNull( address ); + Objects.requireNonNull( inUseSupplier ); + Objects.requireNonNull( idleSupplier ); + Objects.requireNonNull( registry ); + + this.id = poolId; + this.inUseSupplier = inUseSupplier; + this.idleSupplier = idleSupplier; + String host = address instanceof BoltServerAddress ? ((BoltServerAddress) address).connectionHost() : address.host(); + Iterable tags = Tags.concat( initialTags, + "address", String.format( "%s:%d", host, address.port() ) ); + + Gauge.builder( IN_USE, this::inUse ).tags( tags ).register( registry ); + Gauge.builder( IDLE, this::idle ).tags( tags ).register( registry ); + Gauge.builder( CREATING, creating, AtomicInteger::get ).tags( tags ).register( registry ); + failedToCreate = Counter.builder( FAILED ).tags( tags ).register( registry ); + closed = Counter.builder( CLOSED ).tags( tags ).register( registry ); + Gauge.builder( ACQUIRING, acquiring, AtomicInteger::get ).tags( tags ).register( registry ); + timedOutToAcquire = Counter.builder( ACQUISITION_TIMEOUT ).tags( tags ).register( registry ); + totalAcquisitionTimer = Timer.builder( ACQUISITION ).tags( tags ).register( registry ); + totalConnectionTimer = Timer.builder( CREATION ).tags( tags ).register( registry ); + totalInUseTimer = Timer.builder( USAGE ).tags( tags ).register( registry ); + } + + @Override + public void beforeCreating( ListenerEvent connEvent ) + { + creating.incrementAndGet(); + connEvent.start(); + } + + @Override + public void afterFailedToCreate() + { + failedToCreate.increment(); + creating.decrementAndGet(); + } + + @Override + public void afterCreated( ListenerEvent connEvent ) + { + creating.decrementAndGet(); + Timer.Sample sample = ((MicrometerTimerListenerEvent) connEvent).getSample(); + sample.stop( totalConnectionTimer ); + } + + @Override + public void afterClosed() + { + closed.increment(); + } + + @Override + public void beforeAcquiringOrCreating( ListenerEvent acquireEvent ) + { + acquireEvent.start(); + acquiring.incrementAndGet(); + } + + @Override + public void afterAcquiringOrCreating() + { + acquiring.decrementAndGet(); + } + + @Override + public void afterAcquiredOrCreated( ListenerEvent acquireEvent ) + { + Timer.Sample sample = ((MicrometerTimerListenerEvent) acquireEvent).getSample(); + sample.stop( totalAcquisitionTimer ); + } + + @Override + public void afterTimedOutToAcquireOrCreate() + { + timedOutToAcquire.increment(); + } + + @Override + public void acquired( ListenerEvent inUseEvent ) + { + inUseEvent.start(); + } + + @Override + public void released( ListenerEvent inUseEvent ) + { + Timer.Sample sample = ((MicrometerTimerListenerEvent) inUseEvent).getSample(); + sample.stop( totalInUseTimer ); + } + + @Override + public String id() + { + return this.id; + } + + @Override + public int inUse() + { + return inUseSupplier.getAsInt(); + } + + @Override + public int idle() + { + return idleSupplier.getAsInt(); + } + + @Override + public int creating() + { + return creating.get(); + } + + @Override + public long created() + { + return totalConnectionTimer.count(); + } + + @Override + public long failedToCreate() + { + return count( failedToCreate ); + } + + @Override + public long closed() + { + return count( closed ); + } + + @Override + public int acquiring() + { + return acquiring.get(); + } + + @Override + public long acquired() + { + return totalAcquisitionTimer.count(); + } + + @Override + public long timedOutToAcquire() + { + return count( timedOutToAcquire ); + } + + @Override + public long totalAcquisitionTime() + { + return (long) totalAcquisitionTimer.totalTime( TimeUnit.MILLISECONDS ); + } + + @Override + public long totalConnectionTime() + { + return (long) totalConnectionTimer.totalTime( TimeUnit.MILLISECONDS ); + } + + @Override + public long totalInUseTime() + { + return (long) totalInUseTimer.totalTime( TimeUnit.MILLISECONDS ); + } + + @Override + public long totalInUseCount() + { + return totalInUseTimer.count(); + } + + @Override + public String toString() + { + return format( "%s=[created=%s, closed=%s, creating=%s, failedToCreate=%s, acquiring=%s, acquired=%s, " + + "timedOutToAcquire=%s, inUse=%s, idle=%s, " + + "totalAcquisitionTime=%s, totalConnectionTime=%s, totalInUseTime=%s, totalInUseCount=%s]", + id(), created(), closed(), creating(), failedToCreate(), acquiring(), acquired(), + timedOutToAcquire(), inUse(), idle(), + totalAcquisitionTime(), totalConnectionTime(), totalInUseTime(), totalInUseCount() ); + } + + private long count( Counter counter ) + { + return (long) counter.count(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetrics.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetrics.java new file mode 100644 index 0000000000..f3b4eaf134 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetrics.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import io.micrometer.core.instrument.MeterRegistry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.IntSupplier; + +import org.neo4j.driver.ConnectionPoolMetrics; +import org.neo4j.driver.Metrics; +import org.neo4j.driver.net.ServerAddress; + +final class MicrometerMetrics implements Metrics, MetricsListener +{ + private final MeterRegistry meterRegistry; + private final Map connectionPoolMetrics; + + public MicrometerMetrics( MeterRegistry meterRegistry ) + { + this.meterRegistry = meterRegistry; + this.connectionPoolMetrics = new ConcurrentHashMap<>(); + } + + @Override + public Collection connectionPoolMetrics() + { + return Collections.unmodifiableCollection( this.connectionPoolMetrics.values() ); + } + + @Override + public void beforeCreating( String poolId, ListenerEvent creatingEvent ) + { + poolMetricsListener( poolId ).beforeCreating( creatingEvent ); + } + + @Override + public void afterCreated( String poolId, ListenerEvent creatingEvent ) + { + poolMetricsListener( poolId ).afterCreated( creatingEvent ); + } + + @Override + public void afterFailedToCreate( String poolId ) + { + poolMetricsListener( poolId ).afterFailedToCreate(); + } + + @Override + public void afterClosed( String poolId ) + { + poolMetricsListener( poolId ).afterClosed(); + } + + @Override + public void beforeAcquiringOrCreating( String poolId, ListenerEvent acquireEvent ) + { + poolMetricsListener( poolId ).beforeAcquiringOrCreating( acquireEvent ); + } + + @Override + public void afterAcquiringOrCreating( String poolId ) + { + poolMetricsListener( poolId ).afterAcquiringOrCreating(); + } + + @Override + public void afterAcquiredOrCreated( String poolId, ListenerEvent acquireEvent ) + { + poolMetricsListener( poolId ).afterAcquiredOrCreated( acquireEvent ); + } + + @Override + public void afterTimedOutToAcquireOrCreate( String poolId ) + { + poolMetricsListener( poolId ).afterTimedOutToAcquireOrCreate(); + } + + @Override + public void afterConnectionCreated( String poolId, ListenerEvent inUseEvent ) + { + poolMetricsListener( poolId ).acquired( inUseEvent ); + } + + @Override + public void afterConnectionReleased( String poolId, ListenerEvent inUseEvent ) + { + poolMetricsListener( poolId ).released( inUseEvent ); + } + + @Override + public ListenerEvent createListenerEvent() + { + return new MicrometerTimerListenerEvent( this.meterRegistry ); + } + + @Override + public void registerPoolMetrics( String poolId, ServerAddress address, IntSupplier inUseSupplier, IntSupplier idleSupplier ) + { + this.connectionPoolMetrics.put( poolId, new MicrometerConnectionPoolMetrics( poolId, address, inUseSupplier, idleSupplier, this.meterRegistry ) ); + } + + // For testing purposes only + void putPoolMetrics( String poolId, ConnectionPoolMetrics poolMetrics ) + { + this.connectionPoolMetrics.put( poolId, poolMetrics ); + } + + @Override + public void removePoolMetrics( String poolId ) + { + this.connectionPoolMetrics.remove( poolId ); + } + + private ConnectionPoolMetricsListener poolMetricsListener( String poolId ) + { + ConnectionPoolMetricsListener poolMetrics = (ConnectionPoolMetricsListener) this.connectionPoolMetrics.get( poolId ); + if ( poolMetrics == null ) + { + return DevNullPoolMetricsListener.INSTANCE; + } + return poolMetrics; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProvider.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProvider.java new file mode 100644 index 0000000000..3b766a6bfd --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import io.micrometer.core.instrument.MeterRegistry; + +import org.neo4j.driver.Metrics; + +/** + * An adapter to bridge between driver metrics and Micrometer {@link MeterRegistry meter registry}. + */ +public final class MicrometerMetricsProvider implements MetricsProvider +{ + private final MicrometerMetrics metrics; + + public static MetricsProvider forGlobalRegistry() + { + return of( io.micrometer.core.instrument.Metrics.globalRegistry ); + } + + public static MetricsProvider of( MeterRegistry meterRegistry ) + { + return new MicrometerMetricsProvider( meterRegistry ); + } + + private MicrometerMetricsProvider( MeterRegistry meterRegistry ) + { + this.metrics = new MicrometerMetrics( meterRegistry ); + } + + @Override + public Metrics metrics() + { + return this.metrics; + } + + @Override + public MetricsListener metricsListener() + { + return this.metrics; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEvent.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEvent.java new file mode 100644 index 0000000000..9258666783 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEvent.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; + +final class MicrometerTimerListenerEvent implements ListenerEvent +{ + private final MeterRegistry meterRegistry; + private Timer.Sample sample; + + public MicrometerTimerListenerEvent( MeterRegistry meterRegistry ) + { + this.meterRegistry = meterRegistry; + } + + @Override + public void start() + { + this.sample = Timer.start( this.meterRegistry ); + } + + @Override + public Timer.Sample getSample() + { + return this.sample; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/TimeRecorderListenerEvent.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/TimeRecorderListenerEvent.java index d9cdfba7ae..c286de7001 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/TimeRecorderListenerEvent.java +++ b/driver/src/main/java/org/neo4j/driver/internal/metrics/TimeRecorderListenerEvent.java @@ -20,10 +20,10 @@ import org.neo4j.driver.internal.util.Clock; -public class TimeRecorderListenerEvent implements ListenerEvent +final class TimeRecorderListenerEvent implements ListenerEvent { - private long startTime; private final Clock clock; + private long startTime; TimeRecorderListenerEvent( Clock clock ) { @@ -37,7 +37,7 @@ public void start() } @Override - public long elapsed() + public Long getSample() { return clock.millis() - startTime; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionPool.java b/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionPool.java index 0bd91db8fe..f86242b9ee 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionPool.java +++ b/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionPool.java @@ -22,6 +22,7 @@ import java.util.concurrent.CompletionStage; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.net.ServerAddress; public interface ConnectionPool { @@ -31,9 +32,9 @@ public interface ConnectionPool void retainAll( Set addressesToRetain ); - int inUseConnections( BoltServerAddress address ); + int inUseConnections( ServerAddress address ); - int idleConnections( BoltServerAddress address ); + int idleConnections( ServerAddress address ); CompletionStage close(); diff --git a/driver/src/test/java/org/neo4j/driver/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/ConfigTest.java index a68f5da3b6..64bdbe95d3 100644 --- a/driver/src/test/java/org/neo4j/driver/ConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/ConfigTest.java @@ -362,33 +362,61 @@ void shouldErrorWithInvalidUserAgent() assertThrows( IllegalArgumentException.class, () -> Config.builder().withUserAgent( "" ).build() ); } + @Test + void shouldNotHaveMeterRegistryByDefault() + { + Config config = Config.builder().build(); + MetricsAdapter metricsAdapter = config.metricsAdapter(); + + assertEquals( MetricsAdapter.DEV_NULL, metricsAdapter ); + assertFalse( config.isMetricsEnabled() ); + } + + @Test + void shouldNotAcceptNullMeterRegistry() + { + Config.ConfigBuilder builder = Config.builder(); + assertThrows( NullPointerException.class, () -> builder.withMetricsAdapter( null ) ); + } + + @Test + void shouldSetMetricsAdapter() + { + Config config = Config.builder() + .withMetricsAdapter( MetricsAdapter.DEFAULT ) + .build(); + MetricsAdapter metricsAdapter = config.metricsAdapter(); + + assertEquals( MetricsAdapter.DEFAULT, metricsAdapter ); + assertTrue( config.isMetricsEnabled() ); + } + @Nested class SerializationTest { - @Test void shouldSerialize() throws Exception { Config config = Config.builder() - .withMaxConnectionPoolSize( 123 ) - .withConnectionTimeout( 6543L, TimeUnit.MILLISECONDS ) - .withConnectionAcquisitionTimeout( 5432L, TimeUnit.MILLISECONDS ) - .withConnectionLivenessCheckTimeout( 4321L, TimeUnit.MILLISECONDS ) - .withMaxConnectionLifetime( 4711, TimeUnit.MILLISECONDS ) - .withMaxTransactionRetryTime( 3210L, TimeUnit.MILLISECONDS ) - .withFetchSize( 9876L ) - .withEventLoopThreads( 4 ) - .withoutEncryption() - .withTrustStrategy( Config.TrustStrategy.trustCustomCertificateSignedBy( new File( "doesntMatter" )) ) - .withUserAgent( "user-agent" ) - .withDriverMetrics() - .withRoutingTablePurgeDelay( 50000, TimeUnit.MILLISECONDS ) - .withLeakedSessionsLogging() - .build(); + .withMaxConnectionPoolSize( 123 ) + .withConnectionTimeout( 6543L, TimeUnit.MILLISECONDS ) + .withConnectionAcquisitionTimeout( 5432L, TimeUnit.MILLISECONDS ) + .withConnectionLivenessCheckTimeout( 4321L, TimeUnit.MILLISECONDS ) + .withMaxConnectionLifetime( 4711, TimeUnit.MILLISECONDS ) + .withMaxTransactionRetryTime( 3210L, TimeUnit.MILLISECONDS ) + .withFetchSize( 9876L ) + .withEventLoopThreads( 4 ) + .withoutEncryption() + .withTrustStrategy( Config.TrustStrategy.trustCustomCertificateSignedBy( new File( "doesntMatter" ) ) ) + .withUserAgent( "user-agent" ) + .withDriverMetrics() + .withRoutingTablePurgeDelay( 50000, TimeUnit.MILLISECONDS ) + .withLeakedSessionsLogging() + .withMetricsAdapter( MetricsAdapter.MICROMETER ) + .build(); Config verify = TestUtil.serializeAndReadBack( config, Config.class ); - assertEquals( config.maxConnectionPoolSize(), verify.maxConnectionPoolSize() ); assertEquals( config.connectionTimeoutMillis(), verify.connectionTimeoutMillis() ); assertEquals( config.connectionAcquisitionTimeoutMillis(), verify.connectionAcquisitionTimeoutMillis() ); @@ -406,6 +434,7 @@ void shouldSerialize() throws Exception assertEquals( config.trustStrategy().revocationStrategy(), verify.trustStrategy().revocationStrategy() ); assertEquals( config.userAgent(), verify.userAgent() ); assertEquals( config.isMetricsEnabled(), verify.isMetricsEnabled() ); + assertEquals( config.metricsAdapter(), verify.metricsAdapter() ); assertEquals( config.routingSettings().routingTablePurgeDelayMs(), verify.routingSettings().routingTablePurgeDelayMs() ); assertEquals( config.logLeakedSessions(), verify.logLeakedSessions() ); } @@ -413,7 +442,6 @@ void shouldSerialize() throws Exception @Test void shouldSerializeSerializableLogging() throws IOException, ClassNotFoundException { - Config config = Config.builder().withLogging( Logging.javaUtilLogging( Level.ALL ) ).build(); Config verify = TestUtil.serializeAndReadBack( config, Config.class ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java index b45be04669..551910430b 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java @@ -54,6 +54,7 @@ import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.metrics.MetricsProvider; +import org.neo4j.driver.internal.metrics.DevNullMetricsListener; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.security.SecurityPlanImpl; @@ -81,7 +82,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.neo4j.driver.Values.parameters; -import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V4; import static org.neo4j.driver.util.TestUtil.await; @@ -496,7 +496,7 @@ private static class MemorizingConnectionPool extends ConnectionPoolImpl MemorizingConnectionPool( ChannelConnector connector, Bootstrap bootstrap, PoolSettings settings, Logging logging, Clock clock, boolean ownsEventLoopGroup ) { - super( connector, bootstrap, settings, DEV_NULL_METRICS, logging, clock, ownsEventLoopGroup ); + super( connector, bootstrap, settings, DevNullMetricsListener.INSTANCE, logging, clock, ownsEventLoopGroup ); } void startMemorizing() diff --git a/driver/src/test/java/org/neo4j/driver/integration/MetricsIT.java b/driver/src/test/java/org/neo4j/driver/integration/MetricsIT.java new file mode 100644 index 0000000000..4c777a90ef --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/integration/MetricsIT.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.integration; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; + +import org.neo4j.driver.Config; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.MetricsAdapter; +import org.neo4j.driver.QueryRunner; +import org.neo4j.driver.Result; +import org.neo4j.driver.internal.metrics.MicrometerMetricsProvider; +import org.neo4j.driver.util.DatabaseExtension; +import org.neo4j.driver.util.ParallelizableIT; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.neo4j.driver.Values.parameters; + +@ParallelizableIT +class MetricsIT +{ + @RegisterExtension + static final DatabaseExtension neo4j = new DatabaseExtension(); + + private Driver driver; + private MeterRegistry meterRegistry = new SimpleMeterRegistry(); + + @BeforeEach + void createDriver() + { + driver = GraphDatabase.driver( neo4j.uri(), neo4j.authToken(), + Config.builder().withMetricsAdapter( MetricsAdapter.MICROMETER ).build() ); + } + + @AfterEach + void closeDriver() + { + driver.close(); + } + + @Test + void driverMetricsUpdatedWithDriverUse() + { + Result result = createNodesInNewSession( 12 ); + // assert in use + Timer acquisitionTimer = meterRegistry.get( "neo4j.driver.connections.acquisition" ).timer(); + Timer creationTimer = meterRegistry.get( "neo4j.driver.connections.creation" ).timer(); + Timer usageTimer = meterRegistry.get( "neo4j.driver.connections.usage" ).timer(); + assertEquals( 1, acquisitionTimer.count() ); + assertEquals( 1, creationTimer.count() ); + assertEquals( 0, usageTimer.count() ); + + result.consume(); + // assert released + assertEquals( 1, acquisitionTimer.count() ); + assertEquals( 1, creationTimer.count() ); + assertEquals( 1, usageTimer.count() ); + } + + private Result createNodesInNewSession( int nodesToCreate ) + { + return createNodes( nodesToCreate, driver.session() ); + } + + private Result createNodes( int nodesToCreate, QueryRunner queryRunner ) + { + return queryRunner.run( "UNWIND range(1, $nodesToCreate) AS i CREATE (n {index: i}) RETURN n", + parameters( "nodesToCreate", nodesToCreate ) ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java index 8ce2979f7c..9f4c77c002 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java @@ -18,6 +18,7 @@ */ package org.neo4j.driver.internal; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.netty.bootstrap.Bootstrap; import io.netty.util.concurrent.EventExecutorGroup; import org.junit.jupiter.api.Test; @@ -25,6 +26,7 @@ import org.junit.jupiter.params.provider.MethodSource; import java.net.URI; +import java.util.Optional; import java.util.stream.Stream; import org.neo4j.driver.AuthToken; @@ -32,6 +34,7 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.Logging; +import org.neo4j.driver.MetricsAdapter; import org.neo4j.driver.SessionConfig; import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; import org.neo4j.driver.internal.async.NetworkSession; @@ -39,8 +42,10 @@ import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.cluster.loadbalancing.LoadBalancer; +import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; import org.neo4j.driver.internal.metrics.InternalMetricsProvider; import org.neo4j.driver.internal.metrics.MetricsProvider; +import org.neo4j.driver.internal.metrics.MicrometerMetricsProvider; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.security.SecurityPlan; @@ -51,6 +56,7 @@ import org.neo4j.driver.internal.util.Clock; import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.junit.MatcherAssert.assertThat; @@ -64,7 +70,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.Config.defaultConfig; -import static org.neo4j.driver.internal.metrics.MetricsProvider.METRICS_DISABLED_PROVIDER; import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.internal.util.Matchers.clusterDriver; @@ -152,9 +157,9 @@ void shouldNotCreateDriverMetrics() Config config = mock( Config.class ); when( config.isMetricsEnabled() ).thenReturn( false ); // When - MetricsProvider provider = DriverFactory.createDriverMetrics( config, Clock.SYSTEM ); + MetricsProvider provider = DriverFactory.getOrCreateMetricsProvider( config, Clock.SYSTEM ); // Then - assertThat( provider, is( METRICS_DISABLED_PROVIDER ) ); + assertThat( provider, is(equalTo( DevNullMetricsProvider.INSTANCE ) ) ); } @Test @@ -165,12 +170,25 @@ void shouldCreateDriverMetricsIfMonitoringEnabled() when( config.isMetricsEnabled() ).thenReturn( true ); when( config.logging() ).thenReturn( Logging.none() ); // When - MetricsProvider provider = DriverFactory.createDriverMetrics( config, Clock.SYSTEM ); + MetricsProvider provider = DriverFactory.getOrCreateMetricsProvider( config, Clock.SYSTEM ); // Then - assertThat( provider.isMetricsEnabled(), is( true ) ); assertThat( provider instanceof InternalMetricsProvider, is( true ) ); } + @Test + void shouldCreateMicrometerDriverMetricsIfMonitoringEnabled() + { + // Given + Config config = mock( Config.class ); + when( config.isMetricsEnabled() ).thenReturn( true ); + when( config.metricsAdapter() ).thenReturn( MetricsAdapter.MICROMETER ); + when( config.logging() ).thenReturn( Logging.none() ); + // When + MetricsProvider provider = DriverFactory.getOrCreateMetricsProvider( config, Clock.SYSTEM ); + // Then + assertThat( provider instanceof MicrometerMetricsProvider, is( true ) ); + } + @ParameterizedTest @MethodSource( "testUris" ) void shouldCreateAppropriateDriverType( String uri ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java index 69af2458f1..4893526544 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java @@ -24,22 +24,22 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Metrics; +import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; +import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.internal.metrics.InternalMetrics; -import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.Clock; import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.internal.metrics.MetricsProvider.METRICS_DISABLED_PROVIDER; import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.util.TestUtil.await; @@ -103,11 +103,11 @@ void shouldThrowClientExceptionIfMetricsNotEnabled() throws Throwable ClientException error = assertThrows( ClientException.class, driver::metrics ); // Then - assertTrue( error.getMessage().contains( "Driver metrics not enabled." ) ); + assertTrue( error.getMessage().contains( "Driver metrics are not enabled." ) ); } @Test - void shouldReturnMetricsIfMetricsEnabled() throws Throwable + void shouldReturnMetricsIfMetricsEnabled() { // Given InternalDriver driver = newDriver( true ); @@ -116,12 +116,12 @@ void shouldReturnMetricsIfMetricsEnabled() throws Throwable Metrics metrics = driver.metrics(); // Then we shall have no problem to get the metrics - assertTrue( metrics instanceof InternalMetrics ); + assertNotNull( metrics ); } private static InternalDriver newDriver( SessionFactory sessionFactory ) { - return new InternalDriver( SecurityPlanImpl.insecure(), sessionFactory, METRICS_DISABLED_PROVIDER, DEV_NULL_LOGGING ); + return new InternalDriver( SecurityPlanImpl.insecure(), sessionFactory, DevNullMetricsProvider.INSTANCE, DEV_NULL_LOGGING ); } private static SessionFactory sessionFactoryMock() @@ -140,7 +140,7 @@ private static InternalDriver newDriver( boolean isMetricsEnabled ) config = Config.builder().withDriverMetrics().build(); } - MetricsProvider metricsProvider = DriverFactory.createDriverMetrics( config, Clock.SYSTEM ); + MetricsProvider metricsProvider = DriverFactory.getOrCreateMetricsProvider( config, Clock.SYSTEM ); return new InternalDriver( SecurityPlanImpl.insecure(), sessionFactory, metricsProvider, DEV_NULL_LOGGING ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/NetworkConnectionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/NetworkConnectionTest.java index a6fd53fc27..c67018f113 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/NetworkConnectionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/NetworkConnectionTest.java @@ -42,6 +42,7 @@ import org.neo4j.driver.internal.async.pool.ExtendedChannelPool; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.metrics.DevNullMetricsListener; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.internal.util.FakeClock; import org.neo4j.driver.internal.util.ServerVersion; @@ -63,7 +64,6 @@ import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; -import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; import static org.neo4j.driver.internal.util.Iterables.single; import static org.neo4j.driver.util.DaemonThreadFactory.daemon; import static org.neo4j.driver.util.TestUtil.DEFAULT_TEST_PROTOCOL_VERSION; @@ -645,7 +645,7 @@ private static NetworkConnection newConnection( Channel channel ) private static NetworkConnection newConnection( Channel channel, ExtendedChannelPool pool ) { - return new NetworkConnection( channel, pool, new FakeClock(), DEV_NULL_METRICS, DEV_NULL_LOGGING ); + return new NetworkConnection( channel, pool, new FakeClock(), DevNullMetricsListener.INSTANCE, DEV_NULL_LOGGING ); } private static void assertConnectionReleasedError( IllegalStateException e ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java index 5c219e33fd..c3caebc761 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java @@ -35,6 +35,7 @@ import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.metrics.DevNullMetricsListener; import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.FakeClock; @@ -50,7 +51,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; import static org.neo4j.driver.util.TestUtil.await; @ParallelizableIT @@ -151,7 +151,7 @@ private ConnectionPoolImpl newPool() throws Exception DEV_NULL_LOGGING, clock, RoutingContext.EMPTY, DefaultDomainNameResolver.getInstance() ); PoolSettings poolSettings = newSettings(); Bootstrap bootstrap = BootstrapFactory.newBootstrap( 1 ); - return new ConnectionPoolImpl( connector, bootstrap, poolSettings, DEV_NULL_METRICS, DEV_NULL_LOGGING, clock, true ); + return new ConnectionPoolImpl( connector, bootstrap, poolSettings, DevNullMetricsListener.INSTANCE, DEV_NULL_LOGGING, clock, true ); } private static PoolSettings newSettings() { diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplTest.java index d18aa10395..b49d6bec82 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.ExecutionException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.metrics.DevNullMetricsListener; import org.neo4j.driver.internal.util.FakeClock; import static java.util.Arrays.asList; @@ -41,7 +42,6 @@ import static org.neo4j.driver.internal.BoltServerAddress.LOCAL_DEFAULT; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.authorizationStateListener; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; class ConnectionPoolImplTest { @@ -144,7 +144,7 @@ private static TestConnectionPool newConnectionPool( NettyChannelTracker nettyCh private static TestConnectionPool newConnectionPool( NettyChannelTracker nettyChannelTracker, NettyChannelHealthChecker nettyChannelHealthChecker ) { - return new TestConnectionPool( mock( Bootstrap.class ), nettyChannelTracker, nettyChannelHealthChecker, newSettings(), DEV_NULL_METRICS, + return new TestConnectionPool( mock( Bootstrap.class ), nettyChannelTracker, nettyChannelHealthChecker, newSettings(), DevNullMetricsListener.INSTANCE, DEV_NULL_LOGGING, new FakeClock(), true ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java index 52d8d935d6..63af5b70a2 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java @@ -39,6 +39,7 @@ import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.metrics.DevNullMetricsListener; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.FakeClock; @@ -57,7 +58,6 @@ import static org.mockito.Mockito.verify; import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; import static org.neo4j.driver.util.TestUtil.await; @ParallelizableIT @@ -156,7 +156,7 @@ void shouldLimitNumberOfConcurrentConnections() throws Exception @Test void shouldTrackActiveChannels() throws Exception { - NettyChannelTracker tracker = new NettyChannelTracker( DEV_NULL_METRICS, new ImmediateSchedulingEventExecutor(), DEV_NULL_LOGGING ); + NettyChannelTracker tracker = new NettyChannelTracker( DevNullMetricsListener.INSTANCE, new ImmediateSchedulingEventExecutor(), DEV_NULL_LOGGING ); poolHandler = tracker; pool = newPool( neo4j.authToken() ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelTrackerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelTrackerTest.java index 0923db4aae..70612084e4 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelTrackerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelTrackerTest.java @@ -28,6 +28,7 @@ import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; +import org.neo4j.driver.internal.metrics.DevNullMetricsListener; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -41,12 +42,11 @@ import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setProtocolVersion; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerAddress; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; -import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; class NettyChannelTrackerTest { private final BoltServerAddress address = BoltServerAddress.LOCAL_DEFAULT; - private final NettyChannelTracker tracker = new NettyChannelTracker( DEV_NULL_METRICS, mock( ChannelGroup.class ), DEV_NULL_LOGGING ); + private final NettyChannelTracker tracker = new NettyChannelTracker( DevNullMetricsListener.INSTANCE, mock( ChannelGroup.class ), DEV_NULL_LOGGING ); @Test void shouldIncrementIdleCountWhenChannelCreated() @@ -194,7 +194,7 @@ void shouldAddChannelToGroupWhenChannelCreated() Channel channel = newChannel(); Channel anotherChannel = newChannel(); ChannelGroup group = mock( ChannelGroup.class ); - NettyChannelTracker tracker = new NettyChannelTracker( DEV_NULL_METRICS, group, DEV_NULL_LOGGING ); + NettyChannelTracker tracker = new NettyChannelTracker( DevNullMetricsListener.INSTANCE, group, DEV_NULL_LOGGING ); tracker.channelCreated( channel, null ); tracker.channelCreated( anotherChannel, null ); @@ -211,7 +211,7 @@ void shouldDelegateToProtocolPrepareToClose() ChannelGroup group = mock( ChannelGroup.class ); when( group.iterator() ).thenReturn( new Arrays.Iterator<>( new Channel[]{channel, anotherChannel} ) ); - NettyChannelTracker tracker = new NettyChannelTracker( DEV_NULL_METRICS, group, DEV_NULL_LOGGING ); + NettyChannelTracker tracker = new NettyChannelTracker( DevNullMetricsListener.INSTANCE, group, DEV_NULL_LOGGING ); tracker.prepareToCloseChannels(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java index f11b5f487b..48356abf69 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java @@ -42,6 +42,7 @@ import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logging; +import org.neo4j.driver.internal.metrics.MetricsListener; import org.neo4j.driver.exceptions.FatalDiscoveryException; import org.neo4j.driver.exceptions.ProtocolException; import org.neo4j.driver.internal.BoltServerAddress; @@ -57,7 +58,7 @@ import org.neo4j.driver.internal.cluster.RoutingTable; import org.neo4j.driver.internal.cluster.RoutingTableRegistry; import org.neo4j.driver.internal.cluster.RoutingTableRegistryImpl; -import org.neo4j.driver.internal.metrics.InternalAbstractMetrics; +import org.neo4j.driver.internal.metrics.DevNullMetricsListener; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Clock; @@ -77,7 +78,6 @@ import static org.neo4j.driver.internal.DatabaseNameUtil.database; import static org.neo4j.driver.internal.cluster.RediscoveryUtil.contextWithDatabase; import static org.neo4j.driver.internal.cluster.RoutingSettings.STALE_ROUTING_TABLE_PURGE_DELAY_MS; -import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS; import static org.neo4j.driver.util.TestUtil.await; class RoutingTableAndConnectionPoolTest @@ -323,7 +323,7 @@ private void acquireAndReleaseConnections( LoadBalancer loadBalancer ) throws In private ConnectionPool newConnectionPool() { - InternalAbstractMetrics metrics = DEV_NULL_METRICS; + MetricsListener metrics = DevNullMetricsListener.INSTANCE; PoolSettings poolSettings = new PoolSettings( 10, 5000, -1, -1 ); Bootstrap bootstrap = BootstrapFactory.newBootstrap( 1 ); NettyChannelTracker channelTracker = new NettyChannelTracker( metrics, bootstrap.config().group().next(), logging ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetricsTest.java b/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetricsTest.java new file mode 100644 index 0000000000..ea9891b3c0 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetricsTest.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntSupplier; + +import org.neo4j.driver.ConnectionPoolMetrics; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.spi.ConnectionPool; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +class MicrometerConnectionPoolMetricsTest +{ + static final String ID = "id"; + + MicrometerConnectionPoolMetrics metrics; + BoltServerAddress address; + ConnectionPool pool; + MeterRegistry registry; + AtomicInteger inUse = new AtomicInteger(0); + IntSupplier inUseSupplier = inUse::get; + AtomicInteger idle = new AtomicInteger(0); + IntSupplier idleSupplier = idle::get; + + @BeforeEach + void beforeEach() + { + address = new BoltServerAddress( "host", "127.0.0.1", 7687 ); + pool = mock( ConnectionPool.class ); + registry = new SimpleMeterRegistry(); + metrics = new MicrometerConnectionPoolMetrics( ID, address, inUseSupplier, idleSupplier, registry ); + } + + @Test + void shouldIncrementCreatingAndStartTimerOnBeforeCreating() + { + // GIVEN + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.creating() ).willReturn( 1 ); + ListenerEvent event = mock( ListenerEvent.class ); + + // WHEN + metrics.beforeCreating( event ); + + // THEN + verifyMetrics( expectedMetrics, metrics ); + then( event ).should().start(); + } + + @Test + void shouldIncrementFailedToCreateAndDecrementCreatingOnAfterFailedToCreate() + { + // GIVEN + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.failedToCreate() ).willReturn( 1L ); + given( expectedMetrics.creating() ).willReturn( -1 ); + + // WHEN + metrics.afterFailedToCreate(); + + // THEN + verifyMetrics( expectedMetrics, metrics ); + } + + @Test + void shouldDecrementCreatingAndIncrementCreatedAndStopTimerOnAfterCreated() + { + // GIVEN + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.creating() ).willReturn( -1 ); + given( expectedMetrics.created() ).willReturn( 1L ); + Timer timer = registry.get( MicrometerConnectionPoolMetrics.CREATION ).timer(); + long timerCount = timer.count(); + MicrometerTimerListenerEvent event = new MicrometerTimerListenerEvent( registry ); + event.start(); + + // WHEN + metrics.afterCreated( event ); + + // THEN + verifyMetrics( expectedMetrics, metrics ); + assertEquals( timerCount + 1, timer.count() ); + } + + @Test + void shouldIncrementClosedOnAfterClosed() + { + // GIVEN + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.closed() ).willReturn( 1L ); + + // WHEN + metrics.afterClosed(); + + // THEN + verifyMetrics( expectedMetrics, metrics ); + } + + @Test + void shouldStartTimerAndIncrementAcquiringOnBeforeAcquiringOrCreating() + { + // GIVEN + ListenerEvent event = mock( ListenerEvent.class ); + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.acquiring() ).willReturn( 1 ); + + // WHEN + metrics.beforeAcquiringOrCreating( event ); + + // THEN + then( event ).should().start(); + verifyMetrics( expectedMetrics, metrics ); + } + + @Test + void shouldDecrementAcquiringOnAfterAcquiringOrCreating() + { + // GIVEN + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.acquiring() ).willReturn( -1 ); + + // WHEN + metrics.afterAcquiringOrCreating(); + + // THEN + verifyMetrics( expectedMetrics, metrics ); + } + + @Test + void shouldIncrementAcquiredAndStopTimerOnAfterAcquiredOrCreated() + { + // GIVEN + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.acquired() ).willReturn( 1L ); + Timer timer = registry.get( MicrometerConnectionPoolMetrics.ACQUISITION ).timer(); + long timerCount = timer.count(); + MicrometerTimerListenerEvent event = new MicrometerTimerListenerEvent( registry ); + event.start(); + + // WHEN + metrics.afterAcquiredOrCreated( event ); + + // THEN + verifyMetrics( expectedMetrics, metrics ); + assertEquals( timerCount + 1, timer.count() ); + } + + @Test + void shouldIncrementTimedOutToAcquireOnAfterTimedOutToAcquireOrCreate() + { + // GIVEN + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.timedOutToAcquire() ).willReturn( 1L ); + + // WHEN + metrics.afterTimedOutToAcquireOrCreate(); + + // THEN + verifyMetrics( expectedMetrics, metrics ); + } + + @Test + void shouldStartTimerOnAcquired() + { + // GIVEN + ListenerEvent event = mock( ListenerEvent.class ); + + // WHEN + metrics.acquired( event ); + + // THEN + then( event ).should().start(); + } + + @Test + void shouldIncrementReleasedAndStopTimerOnReleased() + { + // GIVEN + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.totalInUseCount() ).willReturn( 1L ); + Timer timer = registry.get( MicrometerConnectionPoolMetrics.USAGE ).timer(); + long timerCount = timer.count(); + MicrometerTimerListenerEvent event = new MicrometerTimerListenerEvent( registry ); + event.start(); + + // WHEN + metrics.released( event ); + + // THEN + verifyMetrics( expectedMetrics, metrics ); + assertEquals( timerCount + 1, timer.count() ); + } + + @Test + void shouldUseInUseSupplier() + { + try + { + // GIVEN + int expected = 5; + inUse.compareAndSet( 0, expected ); + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.inUse() ).willReturn( expected ); + + // WHEN + int actual = metrics.inUse(); + + // THEN + assertEquals( expected, actual ); + verifyMetrics( expectedMetrics, metrics ); + } finally + { + inUse.set( 0 ); + } + } + + @Test + void shouldUseIdleSupplier() + { + try + { + // GIVEN + int expected = 5; + idle.compareAndSet( 0, expected ); + ConnectionPoolMetrics expectedMetrics = mock( ConnectionPoolMetrics.class ); + given( expectedMetrics.idle() ).willReturn( expected ); + + // WHEN + int actual = metrics.idle(); + + // THEN + assertEquals( expected, actual ); + verifyMetrics( expectedMetrics, metrics ); + } finally + { + idle.set( 0 ); + } + } + + void verifyMetrics( ConnectionPoolMetrics expected, ConnectionPoolMetrics actual ) + { + assertEquals( ID, actual.id() ); + assertEquals( expected.inUse(), actual.inUse() ); + assertEquals( expected.inUse(), registry.get( MicrometerConnectionPoolMetrics.IN_USE ).gauge().value() ); + assertEquals( expected.idle(), actual.idle() ); + assertEquals( expected.idle(), registry.get( MicrometerConnectionPoolMetrics.IDLE ).gauge().value() ); + assertEquals( expected.creating(), actual.creating() ); + assertEquals( expected.creating(), registry.get( MicrometerConnectionPoolMetrics.CREATING ).gauge().value() ); + assertEquals( expected.created(), actual.created() ); + assertEquals( expected.created(), registry.get( MicrometerConnectionPoolMetrics.CREATION ).timer().count() ); + assertEquals( expected.failedToCreate(), actual.failedToCreate() ); + assertEquals( expected.failedToCreate(), registry.get( MicrometerConnectionPoolMetrics.FAILED ).counter().count() ); + assertEquals( expected.closed(), actual.closed() ); + assertEquals( expected.closed(), registry.get( MicrometerConnectionPoolMetrics.CLOSED ).counter().count() ); + assertEquals( expected.acquiring(), actual.acquiring() ); + assertEquals( expected.acquiring(), registry.get( MicrometerConnectionPoolMetrics.ACQUIRING ).gauge().value() ); + assertEquals( expected.acquired(), actual.acquired() ); + assertEquals( expected.acquired(), registry.get( MicrometerConnectionPoolMetrics.ACQUISITION ).timer().count() ); + assertEquals( expected.timedOutToAcquire(), actual.timedOutToAcquire() ); + assertEquals( expected.timedOutToAcquire(), registry.get( MicrometerConnectionPoolMetrics.ACQUISITION_TIMEOUT ).counter().count() ); + assertEquals( expected.totalAcquisitionTime(), actual.totalAcquisitionTime() ); + assertEquals( expected.totalAcquisitionTime(), + (long) registry.get( MicrometerConnectionPoolMetrics.ACQUISITION ).timer().totalTime( TimeUnit.MILLISECONDS ) ); + assertEquals( expected.totalConnectionTime(), actual.totalConnectionTime() ); + assertEquals( expected.totalConnectionTime(), + (long) registry.get( MicrometerConnectionPoolMetrics.CREATION ).timer().totalTime( TimeUnit.MILLISECONDS ) ); + assertEquals( expected.totalInUseTime(), actual.totalInUseTime() ); + assertEquals( expected.totalInUseTime(), (long) registry.get( MicrometerConnectionPoolMetrics.USAGE ).timer().totalTime( TimeUnit.MILLISECONDS ) ); + assertEquals( expected.totalInUseCount(), actual.totalInUseCount() ); + assertEquals( expected.totalInUseCount(), registry.get( MicrometerConnectionPoolMetrics.USAGE ).timer().count() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProviderTest.java b/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProviderTest.java new file mode 100644 index 0000000000..9d4a8bd694 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProviderTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.neo4j.driver.Metrics; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MicrometerMetricsProviderTest +{ + MetricsProvider provider; + + @BeforeEach + void beforeEach() + { + provider = MicrometerMetricsProvider.forGlobalRegistry(); + } + + @Test + void shouldReturnMicrometerMetricsOnMetrics() + { + // GIVEN & WHEN + Metrics metrics = provider.metrics(); + + // THEN + assertTrue( metrics instanceof MicrometerMetrics ); + } + + @Test + void shouldReturnMicrometerMetricsOnMetricsListener() + { + // GIVEN & WHEN + MetricsListener listener = provider.metricsListener(); + + // THEN + assertTrue( listener instanceof MicrometerMetrics ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsTest.java b/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsTest.java new file mode 100644 index 0000000000..f4dae3fe02 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsTest.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Collection; + +import org.neo4j.driver.ConnectionPoolMetrics; +import org.neo4j.driver.internal.BoltServerAddress; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +class MicrometerMetricsTest +{ + static final String ID = "id"; + + MicrometerMetrics metrics; + MeterRegistry registry; + ConnectionPoolMetrics poolMetrics; + ConnectionPoolMetricsListener poolMetricsListener; + + @BeforeEach + void beforeEach() + { + registry = new SimpleMeterRegistry(); + metrics = new MicrometerMetrics( registry ); + poolMetricsListener = mock( ConnectionPoolMetricsListener.class, Mockito.withSettings().extraInterfaces( ConnectionPoolMetrics.class ) ); + poolMetrics = (ConnectionPoolMetrics) poolMetricsListener; + } + + @Test + void shouldReturnEmptyConnectionPoolMetrics() + { + // GIVEN & WHEN + Collection collection = metrics.connectionPoolMetrics(); + + // THEN + assertTrue( collection.isEmpty() ); + } + + @Test + void shouldDelegateBeforeCreating() + { + // GIVEN + ListenerEvent event = mock( ListenerEvent.class ); + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.beforeCreating( ID, event ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().beforeCreating( event ); + } + + @Test + void shouldDelegateAfterCreated() + { + // GIVEN + ListenerEvent event = mock( ListenerEvent.class ); + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.afterCreated( ID, event ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().afterCreated( event ); + } + + @Test + void shouldDelegateAfterFailedToCreate() + { + // GIVEN + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.afterFailedToCreate( ID ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().afterFailedToCreate(); + } + + @Test + void shouldDelegateAfterClosed() + { + // GIVEN + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.afterClosed( ID ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().afterClosed(); + } + + @Test + void shouldDelegateBeforeAcquiringOrCreating() + { + // GIVEN + ListenerEvent event = mock( ListenerEvent.class ); + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.beforeAcquiringOrCreating( ID, event ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().beforeAcquiringOrCreating( event ); + } + + @Test + void shouldDelegateAfterAcquiringOrCreating() + { + // GIVEN + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.afterAcquiringOrCreating( ID ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().afterAcquiringOrCreating(); + } + + @Test + void shouldDelegateAfterAcquiredOrCreated() + { + // GIVEN + ListenerEvent event = mock( ListenerEvent.class ); + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.afterAcquiredOrCreated( ID, event ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().afterAcquiredOrCreated( event ); + } + + @Test + void shouldDelegateAfterTimedOutToAcquireOrCreate() + { + // GIVEN + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.afterTimedOutToAcquireOrCreate( ID ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().afterTimedOutToAcquireOrCreate(); + } + + @Test + void shouldDelegateAfterConnectionCreated() + { + // GIVEN + ListenerEvent event = mock( ListenerEvent.class ); + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.afterConnectionCreated( ID, event ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().acquired( event ); + } + + @Test + void shouldDelegateAfterConnectionReleased() + { + // GIVEN + ListenerEvent event = mock( ListenerEvent.class ); + metrics.putPoolMetrics( ID, poolMetrics ); + + // WHEN + metrics.afterConnectionReleased( ID, event ); + + // THEN + assertEquals( 1, metrics.connectionPoolMetrics().size() ); + then( poolMetricsListener ).should().released( event ); + } + + @Test + void shouldCreateListenerEvent() + { + // GIVEN & WHEN + ListenerEvent event = metrics.createListenerEvent(); + + // THEN + assertTrue( event instanceof MicrometerTimerListenerEvent ); + } + + @Test + void shouldPutPoolMetrics() + { + // GIVEN + int size = metrics.connectionPoolMetrics().size(); + + // WHEN + metrics.registerPoolMetrics( ID, BoltServerAddress.LOCAL_DEFAULT, () -> 23, () -> 42 ); + + // THEN + assertEquals( size + 1, metrics.connectionPoolMetrics().size() ); + } + + @Test + void shouldRemovePoolMetrics() + { + // GIVEN + metrics.putPoolMetrics( ID, poolMetrics ); + int size = metrics.connectionPoolMetrics().size(); + + // WHEN + metrics.removePoolMetrics( ID ); + + // THEN + assertEquals( size - 1, metrics.connectionPoolMetrics().size() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEventTest.java b/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEventTest.java new file mode 100644 index 0000000000..4513ae532b --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEventTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * 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 org.neo4j.driver.internal.metrics; + +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class MicrometerTimerListenerEventTest +{ + MicrometerTimerListenerEvent event; + + @BeforeEach + void beforeEach() + { + event = new MicrometerTimerListenerEvent( new SimpleMeterRegistry() ); + } + + @Test + void shouldCreateTimerSampleOnStartAndReturnOnGetSample() + { + // GIVEN + Timer.Sample initialSample = event.getSample(); + + // WHEN + event.start(); + + // THEN + Timer.Sample sample = event.getSample(); + assertNull( initialSample ); + assertNotNull( sample ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/FailingConnectionDriverFactory.java b/driver/src/test/java/org/neo4j/driver/internal/util/FailingConnectionDriverFactory.java index 57bb9b3c5b..a9790538a9 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/FailingConnectionDriverFactory.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/FailingConnectionDriverFactory.java @@ -27,16 +27,17 @@ import org.neo4j.driver.AuthToken; import org.neo4j.driver.Config; +import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.Message; -import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.spi.ResponseHandler; +import org.neo4j.driver.net.ServerAddress; public class FailingConnectionDriverFactory extends DriverFactory { @@ -82,13 +83,13 @@ public void retainAll( Set addressesToRetain ) } @Override - public int inUseConnections( BoltServerAddress address ) + public int inUseConnections( ServerAddress address ) { return delegate.inUseConnections( address ); } @Override - public int idleConnections( BoltServerAddress address ) + public int idleConnections( ServerAddress address ) { return delegate.idleConnections( address ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactory.java b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactory.java index cad9053fd3..893ddc28fe 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactory.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactory.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.Config; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; import org.neo4j.driver.internal.async.connection.BootstrapFactory; @@ -35,8 +37,6 @@ import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.DriverFactoryWithClock; -import org.neo4j.driver.AuthToken; -import org.neo4j.driver.Config; public class ChannelTrackingDriverFactory extends DriverFactoryWithClock { diff --git a/pom.xml b/pom.xml index e3687223aa..44d053c697 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ 2.13.1 1.18.22 21.3.1 + 1.8.2 @@ -119,6 +120,12 @@ slf4j-api ${slf4j-api.version} + + io.micrometer + micrometer-core + ${micrometer.version} + provided + diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetConnectionPoolMetrics.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetConnectionPoolMetrics.java index bc69c22ea2..63cc06e252 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetConnectionPoolMetrics.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetConnectionPoolMetrics.java @@ -26,12 +26,14 @@ import neo4j.org.testkit.backend.messages.responses.TestkitResponse; import reactor.core.publisher.Mono; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.neo4j.driver.Metrics; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.metrics.InternalConnectionPoolMetrics; +import org.neo4j.driver.net.ServerAddress; @Getter @Setter @@ -63,11 +65,21 @@ private ConnectionPoolMetrics getConnectionPoolMetrics( TestkitState testkitStat Metrics metrics = driverHolder.getDriver().metrics(); org.neo4j.driver.ConnectionPoolMetrics poolMetrics = metrics.connectionPoolMetrics().stream() - .map( InternalConnectionPoolMetrics.class::cast ) .filter( pm -> { - BoltServerAddress address = new BoltServerAddress( data.getAddress() ); - BoltServerAddress poolAddress = pm.getAddress(); + // Brute forcing the access via reflections avoid having the InternalConnectionPoolMetrics a public class + ServerAddress poolAddress; + try + { + Method m = pm.getClass().getDeclaredMethod("getAddress"); + m.setAccessible( true ); + poolAddress = (ServerAddress) m.invoke( pm ); + } + catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e ) + { + return false; + } + ServerAddress address = new BoltServerAddress( data.getAddress() ); return address.host().equals( poolAddress.host() ) && address.port() == poolAddress.port(); } ) .findFirst()