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()