diff --git a/bom/pom.xml b/bom/pom.xml index 5c19b11eb2..19326091e1 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -32,6 +32,16 @@ neo4j-java-driver-all ${project.version} + + org.neo4j.driver + neo4j-java-driver-observation-metrics + ${project.version} + + + org.neo4j.driver + neo4j-java-driver-observation-micrometer + ${project.version} + org.neo4j.bolt neo4j-bolt-connection-bom diff --git a/bundle/pom.xml b/bundle/pom.xml index 8b3cf2a1e6..fafc2f80d0 100644 --- a/bundle/pom.xml +++ b/bundle/pom.xml @@ -20,7 +20,7 @@ org.neo4j.driver ${project.basedir}/.. - ,-try + ,-try,-module false @@ -34,11 +34,6 @@ - - io.micrometer - micrometer-core - true - org.slf4j slf4j-api @@ -93,7 +88,6 @@ org.apache.maven.plugins maven-resources-plugin - 3.2.0 copy-appCtx diff --git a/bundle/src/main/jpms/module-info.java b/bundle/src/main/jpms/module-info.java index 4802452d0b..effcf52a4c 100644 --- a/bundle/src/main/jpms/module-info.java +++ b/bundle/src/main/jpms/module-info.java @@ -29,10 +29,14 @@ exports org.neo4j.driver.util; exports org.neo4j.driver.exceptions; exports org.neo4j.driver.exceptions.value; + exports org.neo4j.driver.mapping; + exports org.neo4j.driver.observation; + exports org.neo4j.driver.internal.observation to + org.neo4j.driver.observation.metrics, + org.neo4j.driver.observation.micrometer; requires transitive java.logging; requires transitive org.reactivestreams; - requires static micrometer.core; requires static org.graalvm.nativeimage; requires static org.slf4j; requires static java.management; diff --git a/driver/clirr-ignored-differences.xml b/driver/clirr-ignored-differences.xml index 5b4a2945f3..2cc0e45eec 100644 --- a/driver/clirr-ignored-differences.xml +++ b/driver/clirr-ignored-differences.xml @@ -848,5 +848,62 @@ java.util.Optional rawClassification() + + org/neo4j/driver/Config + 7002 + boolean isMetricsEnabled() + + + + org/neo4j/driver/Config + 7002 + org.neo4j.driver.MetricsAdapter metricsAdapter() + + + + org/neo4j/driver/Config$ConfigBuilder + 7002 + org.neo4j.driver.Config$ConfigBuilder withDriverMetrics() + + + + org/neo4j/driver/Config$ConfigBuilder + 7002 + org.neo4j.driver.Config$ConfigBuilder withMetricsAdapter(org.neo4j.driver.MetricsAdapter) + + + + org/neo4j/driver/Config$ConfigBuilder + 7002 + org.neo4j.driver.Config$ConfigBuilder withoutDriverMetrics() + + + + org/neo4j/driver/ConnectionPoolMetrics + 8001 + + + + org/neo4j/driver/Driver + 7002 + boolean isMetricsEnabled() + + + + org/neo4j/driver/Driver + 7002 + org.neo4j.driver.Metrics metrics() + + + + org/neo4j/driver/Metrics + 8001 + + + + org/neo4j/driver/MetricsAdapter + 8001 + + diff --git a/driver/pom.xml b/driver/pom.xml index 8f282631fa..f71cb6953b 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -19,7 +19,7 @@ ${project.basedir}/.. ${basedir}/target/classes-without-jpms - ,-try + ,-try,-module --add-reads org.neo4j.driver=io.netty.common --add-reads org.neo4j.driver=org.bouncycastle.provider --add-reads org.neo4j.driver=org.bouncycastle.pkix --add-opens org.neo4j.driver/org.neo4j.driver.internal.util=ALL-UNNAMED --add-opens org.neo4j.driver/org.neo4j.driver.internal.async=ALL-UNNAMED --add-reads org.neo4j.driver=io.netty.common --add-reads org.neo4j.driver=org.bouncycastle.provider --add-reads org.neo4j.driver=org.bouncycastle.pkix --add-reads org.neo4j.driver=org.bouncycastle.provider --add-reads org.neo4j.driver=org.bouncycastle.pkix @@ -55,11 +55,6 @@ - - io.micrometer - micrometer-core - true - org.slf4j slf4j-api @@ -176,7 +171,6 @@ org.apache.maven.plugins maven-resources-plugin - 3.3.0 copy-classes-excluding-jpms diff --git a/driver/src/main/java/module-info.java b/driver/src/main/java/module-info.java index 3ca3badbcf..a93e0a0641 100644 --- a/driver/src/main/java/module-info.java +++ b/driver/src/main/java/module-info.java @@ -30,6 +30,10 @@ exports org.neo4j.driver.exceptions; exports org.neo4j.driver.exceptions.value; exports org.neo4j.driver.mapping; + exports org.neo4j.driver.observation; + exports org.neo4j.driver.internal.observation to + org.neo4j.driver.observation.metrics, + org.neo4j.driver.observation.micrometer; requires org.neo4j.bolt.connection; requires org.neo4j.bolt.connection.pooled; @@ -37,7 +41,6 @@ requires reactor.core; requires transitive java.logging; requires transitive org.reactivestreams; - requires static micrometer.core; requires static org.graalvm.nativeimage; requires static org.slf4j; requires static java.management; diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index a51c9d282c..32c7b0b651 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -36,10 +36,13 @@ import org.neo4j.driver.internal.InternalNotificationConfig; import org.neo4j.driver.internal.RoutingSettings; import org.neo4j.driver.internal.SecuritySettings; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.retry.ExponentialBackoffRetryLogic; import org.neo4j.driver.net.ServerAddressResolver; -import org.neo4j.driver.util.Experimental; +import org.neo4j.driver.observation.ObservationProvider; import org.neo4j.driver.util.Immutable; +import org.neo4j.driver.util.Preview; import org.neo4j.driver.util.Resource; /** @@ -145,10 +148,6 @@ public final class Config implements Serializable { */ @SuppressWarnings("deprecation") private final NotificationConfig notificationConfig; - /** - * The {@link MetricsAdapter}. - */ - private final MetricsAdapter metricsAdapter; /** * Specify if telemetry collection is disabled. @@ -156,6 +155,12 @@ public final class Config implements Serializable { * By default, the driver will send anonymous usage statistics to the server it connects to if the server requests those. */ private final boolean telemetryDisabled; + /** + * The {@link ObservationProvider} if configured. + * @since 6.0.0 + */ + @Preview(name = "Observability") + private final transient ObservationProvider observationProvider; private Config(ConfigBuilder builder) { this.logging = builder.logging; @@ -177,8 +182,8 @@ private Config(ConfigBuilder builder) { this.notificationConfig = builder.notificationConfig; this.eventLoopThreads = builder.eventLoopThreads; - this.metricsAdapter = builder.metricsAdapter; this.telemetryDisabled = builder.telemetryDisabled; + this.observationProvider = builder.observationProvider; } /** @@ -345,6 +350,7 @@ public Optional minimumNotificationSeverity() { /** * Returns a set of disabled notification classifications. + * * @return the {@link Set} of disabled {@link NotificationClassification} * @since 5.22.0 */ @@ -367,24 +373,6 @@ public int eventLoopThreads() { return eventLoopThreads; } - /** - * Returns whether the metrics is enabled or not on this driver. - * - * @return if the metrics is enabled or not on this driver - */ - public boolean isMetricsEnabled() { - return this.metricsAdapter != MetricsAdapter.DEV_NULL; - } - - /** - * Returns the {@link MetricsAdapter}. - * - * @return the metrics adapter - */ - public MetricsAdapter metricsAdapter() { - return this.metricsAdapter; - } - /** * Returns the user_agent configured for this driver. * @@ -406,6 +394,16 @@ public boolean isTelemetryDisabled() { return telemetryDisabled; } + /** + * Returns the {@link ObservationProvider} if it is configured. + * @return an {@link Optional} with {@link ObservationProvider} if configured or {@link Optional#empty()} otherwise + * @since 6.0.0 + */ + @Preview(name = "Observability") + public Optional observationProvider() { + return Optional.ofNullable(observationProvider); + } + /** * Used to build new config instances */ @@ -425,9 +423,9 @@ public static final class ConfigBuilder { private int connectionTimeoutMillis = (int) TimeUnit.SECONDS.toMillis(30); private long maxTransactionRetryTimeMillis = ExponentialBackoffRetryLogic.DEFAULT_MAX_RETRY_TIME_MS; private ServerAddressResolver resolver; - private MetricsAdapter metricsAdapter = MetricsAdapter.DEV_NULL; private long fetchSize = 1000; private int eventLoopThreads = 0; + private ObservationProvider observationProvider; @SuppressWarnings("deprecation") private NotificationConfig notificationConfig = NotificationConfig.defaultConfig(); @@ -744,47 +742,21 @@ public ConfigBuilder withResolver(ServerAddressResolver resolver) { } /** - * Enable driver metrics backed by internal basic implementation. The metrics can be obtained afterwards via {@link Driver#metrics()}. - * - * @return this builder. - */ - public ConfigBuilder withDriverMetrics() { - return withMetricsEnabled(true); - } - - /** - * Disable driver metrics. When disabled, driver metrics cannot be accessed via {@link Driver#metrics()}. - * - * @return this builder. + * Sets the {@link ObservationProvider} that the driver should use. + * @param observationProvider the {@link ObservationProvider} or {@code null} to disable + * @return this builder + * @since 6.0.0 */ - public ConfigBuilder withoutDriverMetrics() { - 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); + @Preview(name = "Observability") + public ConfigBuilder withObservationProvider(ObservationProvider observationProvider) { + this.observationProvider = + Objects.requireNonNullElseGet(observationProvider, NoopObservationProvider::getInstance); + if (!(observationProvider instanceof DriverObservationProvider)) { + throw new IllegalArgumentException("Unssupported observation provider"); } 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; - } - /** * Configure the event loop thread count. This specifies how many threads the driver can use to handle network I/O events * and user's events in driver's I/O threads. By default, 2 * NumberOfProcessors amount of threads will be used instead. diff --git a/driver/src/main/java/org/neo4j/driver/Driver.java b/driver/src/main/java/org/neo4j/driver/Driver.java index 5b9841c89e..d9e1faf93e 100644 --- a/driver/src/main/java/org/neo4j/driver/Driver.java +++ b/driver/src/main/java/org/neo4j/driver/Driver.java @@ -17,7 +17,6 @@ package org.neo4j.driver; import java.util.concurrent.CompletionStage; -import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.UnsupportedFeatureException; /** @@ -256,22 +255,6 @@ default T session(Class sessionClass, SessionConfig s */ CompletionStage closeAsync(); - /** - * Returns the driver metrics if metrics reporting is enabled via {@link Config.ConfigBuilder#withDriverMetrics()}. - * Otherwise, a {@link ClientException} will be thrown. - * - * @return the driver metrics if enabled. - * @throws ClientException if the driver metrics reporting is not enabled. - */ - Metrics metrics(); - - /** - * Returns true if the driver metrics reporting is enabled via {@link Config.ConfigBuilder#withDriverMetrics()}, otherwise false. - * - * @return true if the metrics reporting is enabled. - */ - boolean isMetricsEnabled(); - /** * This verifies if the driver can connect to a remote server or a cluster * by establishing a network connection with the remote and possibly exchanging a few data before closing the connection. 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 f0ade37812..5a81378818 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -40,7 +40,6 @@ import org.neo4j.bolt.connection.DefaultDomainNameResolver; import org.neo4j.bolt.connection.DomainNameResolver; import org.neo4j.bolt.connection.LoggingProvider; -import org.neo4j.bolt.connection.MetricsListener; import org.neo4j.bolt.connection.NotificationConfig; import org.neo4j.bolt.connection.RoutedBoltConnectionParameters; import org.neo4j.bolt.connection.pooled.PooledBoltConnectionSource; @@ -53,20 +52,18 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.Logging; -import org.neo4j.driver.MetricsAdapter; import org.neo4j.driver.exceptions.AuthTokenManagerExecutionException; import org.neo4j.driver.internal.adaptedbolt.AdaptingDriverBoltConnectionSource; import org.neo4j.driver.internal.adaptedbolt.BoltAuthTokenManager; import org.neo4j.driver.internal.adaptedbolt.BoltConnectionProviderFactoryLoader; +import org.neo4j.driver.internal.adaptedbolt.BoltObservationProvider; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.adaptedbolt.ErrorMapper; import org.neo4j.driver.internal.adaptedbolt.SingleRoutedBoltConnectionSource; import org.neo4j.driver.internal.boltlistener.BoltConnectionListener; import org.neo4j.driver.internal.homedb.HomeDatabaseCache; -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.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.retry.ExponentialBackoffRetryLogic; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; @@ -132,14 +129,11 @@ public final Driver newInstance( @SuppressWarnings("deprecation") var retryLogic = createRetryLogic(config.maxTransactionRetryTimeMillis(), retryExecutor, config.logging()); - var metricsProvider = getOrCreateMetricsProvider(config, createClock()); - return createDriver( uri, securityPlanManager, eventLoopGroup, retryLogic, - metricsProvider, config, authTokenManager, rediscoverySupplier, @@ -157,27 +151,12 @@ private static ScheduledExecutorService retryScheduledExecutorService() { }); } - @SuppressWarnings("deprecation") - protected static MetricsProvider getOrCreateMetricsProvider(Config config, Clock clock) { - var metricsAdapter = config.metricsAdapter(); - // This can actually only happen when someone mocks the config - if (metricsAdapter == null) { - metricsAdapter = config.isMetricsEnabled() ? MetricsAdapter.DEFAULT : MetricsAdapter.DEV_NULL; - } - return switch (metricsAdapter) { - case DEV_NULL -> DevNullMetricsProvider.INSTANCE; - case DEFAULT -> new InternalMetricsProvider(clock, config.logging()); - case MICROMETER -> MicrometerMetricsProvider.forGlobalRegistry(); - }; - } - @SuppressWarnings("deprecation") private InternalDriver createDriver( URI uri, BoltSecurityPlanManager securityPlanManager, ScheduledExecutorService eventLoopGroup, RetryLogic retryLogic, - MetricsProvider metricsProvider, Config config, AuthTokenManager authTokenManager, Supplier rediscoverySupplier, @@ -186,6 +165,8 @@ private InternalDriver createDriver( try { var homeDatabaseCache = HomeDatabaseCache.newInstance(Scheme.isRoutingScheme(uri.getScheme())); var valueFactory = BoltValueFactory.getInstance(); + var observationProvider = (DriverObservationProvider) + config.observationProvider().orElseGet(NoopObservationProvider::getInstance); boltConnectionProvider = createDriverBoltConnectionProvider( uri, config, @@ -195,7 +176,7 @@ private InternalDriver createDriver( DriverInfoUtil.boltAgent(), config.userAgent(), config.connectionTimeoutMillis(), - metricsProvider.metricsListener(), + observationProvider, authTokenManager, securityPlanManager::plan, NotificationConfigMapper.map(config.notificationConfig()), @@ -206,8 +187,9 @@ private InternalDriver createDriver( retryLogic, config, authTokenManager, - homeDatabaseCache); - var driver = createDriver(securityPlanManager, sessionFactory, metricsProvider, config); + homeDatabaseCache, + observationProvider); + var driver = createDriver(securityPlanManager, sessionFactory, config); var log = config.logging().getLog(getClass()); log.info("Driver instance %s created for server uri '%s'", driver.hashCode(), uri); return driver; @@ -236,7 +218,7 @@ private DriverBoltConnectionSource createDriverBoltConnectionProvider( BoltAgent boltAgent, String userAgent, int connectTimeoutMillis, - MetricsListener metricsListener, + DriverObservationProvider observationProvider, AuthTokenManager authTokenManager, SecurityPlanSupplier securityPlanSupplier, NotificationConfig notificationConfig, @@ -254,14 +236,18 @@ private DriverBoltConnectionSource createDriverBoltConnectionProvider( boltAgent, userAgent, connectTimeoutMillis, - metricsListener, + new BoltObservationProvider(observationProvider), clock, boltAuthTokenManager, securityPlanSupplier, notificationConfig, boltConnectionProviderFactory); return new AdaptingDriverBoltConnectionSource( - boltConnectionProvider, errorMapper, boltValueFactory, Scheme.isRoutingScheme(uri.getScheme())); + boltConnectionProvider, + errorMapper, + boltValueFactory, + Scheme.isRoutingScheme(uri.getScheme()), + observationProvider); } @SuppressWarnings("deprecation") @@ -274,7 +260,7 @@ protected BoltConnectionSource createBoltConnect BoltAgent boltAgent, String userAgent, int connectTimeoutMillis, - MetricsListener metricsListener, + BoltObservationProvider observationProvider, Clock clock, org.neo4j.bolt.connection.pooled.AuthTokenManager authTokenManager, SecurityPlanSupplier securityPlanSupplier, @@ -302,7 +288,7 @@ protected BoltConnectionSource createBoltConnect boltAgent, userAgent, connectTimeoutMillis, - metricsListener, + observationProvider, authTokenManager, securityPlanSupplier, notificationConfig, @@ -319,7 +305,7 @@ protected BoltConnectionSource createBoltConnect boltAgent, userAgent, connectTimeoutMillis, - metricsListener); + observationProvider); } else { boltConnectionSource = new SingleRoutedBoltConnectionSource(pooledSourceSupplierFactory.create(uri, null)); } @@ -337,7 +323,7 @@ private RoutedBoltConnectionSource createRoutedBoltConnectionProvider( BoltAgent boltAgent, String userAgent, int connectTimeoutMillis, - MetricsListener metricsListener) { + BoltObservationProvider observationProvider) { var boltServerAddressResolver = createBoltServerAddressResolver(config); var rediscovery = rediscoverySupplier != null ? rediscoverySupplier.get() : null; return new RoutedBoltConnectionSource( @@ -349,7 +335,8 @@ private RoutedBoltConnectionSource createRoutedBoltConnectionProvider( clock, loggingProvider, uri, - List.of(AuthTokenManagerExecutionException.class)); + List.of(AuthTokenManagerExecutionException.class), + observationProvider); } private BoltConnectionSourceFactory createPooledBoltConnectionSource( @@ -362,7 +349,7 @@ private BoltConnectionSourceFactory createPooledBoltConnectionSource( BoltAgent boltAgent, String userAgent, int connectTimeoutMillis, - MetricsListener metricsListener, + BoltObservationProvider observationProvider, org.neo4j.bolt.connection.pooled.AuthTokenManager authTokenManager, SecurityPlanSupplier securityPlanSupplier, NotificationConfig notificationConfig, @@ -374,6 +361,7 @@ private BoltConnectionSourceFactory createPooledBoltConnectionSource( clock, loggingProvider, config.eventLoopThreads(), + observationProvider, boltConnectionProviderFactory); var listeningBoltConnectionProvider = BoltConnectionListener.listeningBoltConnectionProvider( boltConnectionProvider, boltConnectionListener); @@ -388,7 +376,7 @@ private BoltConnectionSourceFactory createPooledBoltConnectionSource( config.connectionAcquisitionTimeoutMillis(), config.maxConnectionLifetimeMillis(), config.idleTimeBeforeConnectionTest(), - metricsListener, + observationProvider, routingContextAddress, boltAgent, userAgent, @@ -403,6 +391,7 @@ private BoltConnectionProvider createBoltConnectionProvider( Clock clock, LoggingProvider loggingProvider, int eventLoopThreads, + BoltObservationProvider observationProvider, BoltConnectionProviderFactory boltConnectionProviderFactory) { var additionalConfig = new HashMap(); additionalConfig.put("clock", clock); @@ -416,7 +405,7 @@ private BoltConnectionProvider createBoltConnectionProvider( additionalConfig.put("localAddress", localAddress); } return boltConnectionProviderFactory.create( - loggingProvider, BoltValueFactory.getInstance(), null, additionalConfig); + loggingProvider, BoltValueFactory.getInstance(), observationProvider, additionalConfig); } @SuppressWarnings("SameReturnValue") @@ -431,12 +420,14 @@ protected SocketAddress localAddress() { */ @SuppressWarnings("deprecation") protected InternalDriver createDriver( - BoltSecurityPlanManager securityPlanManager, - SessionFactory sessionFactory, - MetricsProvider metricsProvider, - Config config) { + BoltSecurityPlanManager securityPlanManager, SessionFactory sessionFactory, Config config) { return new InternalDriver( - securityPlanManager, sessionFactory, metricsProvider, config.isTelemetryDisabled(), config.logging()); + securityPlanManager, + sessionFactory, + config.isTelemetryDisabled(), + config.logging(), + (DriverObservationProvider) + config.observationProvider().orElseGet(NoopObservationProvider::getInstance)); } /** @@ -457,9 +448,16 @@ protected SessionFactory createSessionFactory( RetryLogic retryLogic, Config config, AuthTokenManager authTokenManager, - HomeDatabaseCache homeDatabaseCache) { + HomeDatabaseCache homeDatabaseCache, + DriverObservationProvider observationProvider) { return new SessionFactoryImpl( - securityPlanManager, connectionProvider, retryLogic, config, authTokenManager, homeDatabaseCache); + securityPlanManager, + connectionProvider, + retryLogic, + config, + authTokenManager, + homeDatabaseCache, + observationProvider); } /** diff --git a/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java b/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java index 0c59a0644b..b830735d13 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java @@ -17,17 +17,18 @@ package org.neo4j.driver.internal; import java.util.concurrent.CompletionStage; +import org.neo4j.driver.internal.observation.Observation; public interface FailableCursor { /** * Discarding all unconsumed records and returning failure if there is any pull errors. */ - CompletionStage discardAllFailureAsync(); + CompletionStage discardAllFailureAsync(Observation parentObservation); /** * Pulling all unconsumed records into memory and returning failure if there is any pull errors. */ - CompletionStage pullAllFailureAsync(); + CompletionStage pullAllFailureAsync(Observation parentObservation); CompletionStage consumed(); } 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 a656d92073..3092a46645 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java @@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull; import static org.neo4j.driver.internal.util.Futures.completedWithNull; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicBoolean; @@ -32,7 +33,6 @@ import org.neo4j.driver.ExecutableQuery; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; -import org.neo4j.driver.Metrics; import org.neo4j.driver.Query; import org.neo4j.driver.QueryConfig; import org.neo4j.driver.Session; @@ -42,8 +42,7 @@ import org.neo4j.driver.exceptions.UnsupportedFeatureException; import org.neo4j.driver.internal.async.InternalAsyncSession; import org.neo4j.driver.internal.async.NetworkSession; -import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; -import org.neo4j.driver.internal.metrics.MetricsProvider; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; import org.neo4j.driver.internal.util.Futures; @@ -57,6 +56,7 @@ public class InternalDriver implements Driver { BookmarkManagers.defaultManager(BookmarkManagerConfig.builder().build()); private final BoltSecurityPlanManager securityPlanManager; private final SessionFactory sessionFactory; + private final DriverObservationProvider observationProvider; @SuppressWarnings("deprecation") private final Logger log; @@ -64,19 +64,18 @@ public class InternalDriver implements Driver { private final boolean telemetryDisabled; private final AtomicBoolean closed = new AtomicBoolean(false); - private final MetricsProvider metricsProvider; InternalDriver( BoltSecurityPlanManager securityPlanManager, SessionFactory sessionFactory, - MetricsProvider metricsProvider, boolean telemetryDisabled, - @SuppressWarnings("deprecation") Logging logging) { + @SuppressWarnings("deprecation") Logging logging, + DriverObservationProvider observationProvider) { this.securityPlanManager = securityPlanManager; this.sessionFactory = sessionFactory; - this.metricsProvider = metricsProvider; this.log = logging.getLog(getClass()); this.telemetryDisabled = telemetryDisabled; + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override @@ -97,15 +96,15 @@ public T session( requireNonNull(sessionClass, "sessionConfig must not be null"); T session; if (Session.class.isAssignableFrom(sessionClass)) { - session = (T) new InternalSession(newSession(sessionConfig, sessionAuthToken)); + session = (T) new InternalSession(newSession(sessionConfig, sessionAuthToken), observationProvider); } else if (AsyncSession.class.isAssignableFrom(sessionClass)) { - session = (T) new InternalAsyncSession(newSession(sessionConfig, sessionAuthToken)); + session = (T) new InternalAsyncSession(newSession(sessionConfig, sessionAuthToken), observationProvider); } else if (org.neo4j.driver.reactive.ReactiveSession.class.isAssignableFrom(sessionClass)) { session = (T) new org.neo4j.driver.internal.reactive.InternalReactiveSession( - newSession(sessionConfig, sessionAuthToken)); + newSession(sessionConfig, sessionAuthToken), observationProvider); } else if (org.neo4j.driver.reactivestreams.ReactiveSession.class.isAssignableFrom(sessionClass)) { session = (T) new org.neo4j.driver.internal.reactivestreams.InternalReactiveSession( - newSession(sessionConfig, sessionAuthToken)); + newSession(sessionConfig, sessionAuthToken), observationProvider); } else { throw new IllegalArgumentException( String.format("Unsupported session type '%s'", sessionClass.getCanonicalName())); @@ -113,16 +112,6 @@ public T session( return session; } - @Override - public Metrics metrics() { - return metricsProvider.metrics(); - } - - @Override - public boolean isMetricsEnabled() { - return metricsProvider != DevNullMetricsProvider.INSTANCE; - } - @Override public boolean isEncrypted() { assertOpen(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java b/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java index 918bf47a52..121708606e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java @@ -17,8 +17,10 @@ package org.neo4j.driver.internal; import static java.util.Collections.emptyMap; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observe; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.neo4j.bolt.connection.TelemetryApi; import org.neo4j.driver.AccessMode; @@ -32,14 +34,18 @@ import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.Futures; public class InternalSession extends AbstractQueryRunner implements Session { private final NetworkSession session; + private final DriverObservationProvider observationProvider; - public InternalSession(NetworkSession session) { + public InternalSession(NetworkSession session, DriverObservationProvider observationProvider) { this.session = session; + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override @@ -59,13 +65,16 @@ public Result run(String query, Map parameters, TransactionConfi @Override public Result run(Query query, TransactionConfig config) { - var cursor = Futures.blockingGet( - session.runAsync(query, config), - () -> terminateConnectionOnThreadInterrupt("Thread interrupted while running query in session")); - - // query executed, it is safe to obtain a connection in a blocking way - var connection = Futures.getNow(session.connectionAsync()); - return new InternalResult(connection, cursor); + var runObservation = observationProvider.sessionRun(Session.class, query.text(), query.parameters()); + return observe(runObservation, () -> { + var cursor = Futures.blockingGet( + session.runAsync(query, config, runObservation, Result.class), + () -> terminateConnectionOnThreadInterrupt("Thread interrupted while running query in session")); + + // query executed, it is safe to obtain a connection in a blocking way + var connection = Futures.getNow(session.connectionAsync()); + return new InternalResult(connection, cursor); + }); } @Override @@ -75,9 +84,12 @@ public boolean isOpen() { @Override public void close() { - Futures.blockingGet( - session.closeAsync(), - () -> terminateConnectionOnThreadInterrupt("Thread interrupted while closing the session")); + var closeObservation = observationProvider.sessionClose(Session.class); + observe( + closeObservation, + () -> Futures.blockingGet( + session.closeAsync(closeObservation), + () -> terminateConnectionOnThreadInterrupt("Thread interrupted while closing the session"))); } @Override @@ -91,10 +103,14 @@ public Transaction beginTransaction(TransactionConfig config) { } public Transaction beginTransaction(TransactionConfig config, String txType) { - var tx = Futures.blockingGet( - session.beginTransactionAsync(config, txType, new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION)), - () -> terminateConnectionOnThreadInterrupt("Thread interrupted while starting a transaction")); - return new InternalTransaction(tx); + var beginTransaction = observationProvider.beginTransaction(Transaction.class); + return observe(beginTransaction, () -> { + var tx = Futures.blockingGet( + session.beginTransactionAsync( + config, txType, new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION), beginTransaction), + () -> terminateConnectionOnThreadInterrupt("Thread interrupted while starting a transaction")); + return new InternalTransaction(tx, observationProvider, null); + }); } @Override @@ -140,8 +156,9 @@ private T transaction( // it is unsafe to execute retries in the event loop threads because this can cause a deadlock // event loop thread will bock and wait for itself to read some data var apiTelemetryWork = new ApiTelemetryWork(telemetryApi); - return session.retryLogic().retry(() -> { - try (var tx = beginTransaction(mode, config, apiTelemetryWork, flush)) { + var executeObservation = observationProvider.sessionExecute(Session.class, mode); + return observe(executeObservation, () -> session.retryLogic().retry(() -> { + try (var tx = beginTransaction(mode, config, apiTelemetryWork, flush, executeObservation)) { var result = work.execute(new DelegatingTransactionContext(tx)); if (result instanceof Result) { @@ -162,15 +179,19 @@ private T transaction( } return result; } - }); + })); } - private Transaction beginTransaction( - AccessMode mode, TransactionConfig config, ApiTelemetryWork apiTelemetryWork, boolean flush) { + private InternalTransaction beginTransaction( + AccessMode mode, + TransactionConfig config, + ApiTelemetryWork apiTelemetryWork, + boolean flush, + Observation parentObservation) { var tx = Futures.blockingGet( - session.beginTransactionAsync(mode, config, null, apiTelemetryWork, flush), + session.beginTransactionAsync(mode, config, null, apiTelemetryWork, flush, parentObservation), () -> terminateConnectionOnThreadInterrupt("Thread interrupted while starting a transaction")); - return new InternalTransaction(tx); + return new InternalTransaction(tx, observationProvider, parentObservation); } private void terminateConnectionOnThreadInterrupt(String reason) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/InternalTransaction.java index ef328e5378..f8babc3cf3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalTransaction.java @@ -16,46 +16,74 @@ */ package org.neo4j.driver.internal; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observe; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; import org.neo4j.driver.Query; import org.neo4j.driver.Result; import org.neo4j.driver.Transaction; import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.util.Futures; public class InternalTransaction extends AbstractQueryRunner implements Transaction { private final UnmanagedTransaction tx; + private final DriverObservationProvider observationProvider; + private final Observation parentObservation; - public InternalTransaction(UnmanagedTransaction tx) { + public InternalTransaction( + UnmanagedTransaction tx, DriverObservationProvider observationProvider, Observation parentObservation) { this.tx = tx; + this.observationProvider = Objects.requireNonNull(observationProvider); + this.parentObservation = parentObservation; } @Override public void commit() { - Futures.blockingGet( - tx.commitAsync(), - () -> terminateConnectionOnThreadInterrupt("Thread interrupted while committing the transaction")); + observeWithParentOrSupplier( + parentObservation, + () -> observationProvider.transactionCommit(Transaction.class), + (observation) -> Futures.blockingGet( + tx.commitAsync(observation), + () -> terminateConnectionOnThreadInterrupt( + "Thread interrupted while committing the transaction"))); } @Override public void rollback() { - Futures.blockingGet( - tx.rollbackAsync(), - () -> terminateConnectionOnThreadInterrupt("Thread interrupted while rolling back the transaction")); + observeWithParentOrSupplier( + parentObservation, + () -> observationProvider.transactionRollback(Transaction.class), + (observation) -> Futures.blockingGet( + tx.rollbackAsync(observation), + () -> terminateConnectionOnThreadInterrupt( + "Thread interrupted while rolling back the transaction"))); } @Override public void close() { - Futures.blockingGet( - tx.closeAsync(), - () -> terminateConnectionOnThreadInterrupt("Thread interrupted while closing the transaction")); + observeWithParentOrSupplier( + parentObservation, + () -> observationProvider.transactionClose(Transaction.class), + (observation) -> Futures.blockingGet( + tx.closeAsync(observation), + () -> terminateConnectionOnThreadInterrupt( + "Thread interrupted while closing the transaction"))); } @Override public Result run(Query query) { - var cursor = Futures.blockingGet( - tx.runAsync(query), - () -> terminateConnectionOnThreadInterrupt("Thread interrupted while running query in transaction")); - return new InternalResult(null, cursor); + var runObservation = observationProvider.transactionRun(Transaction.class, query.text(), query.parameters()); + return observe(runObservation, () -> { + var cursor = Futures.blockingGet( + tx.runAsync(query, runObservation, Result.class), + () -> terminateConnectionOnThreadInterrupt( + "Thread interrupted while running query in transaction")); + return new InternalResult(null, cursor); + }); } @Override @@ -69,9 +97,9 @@ public boolean isOpen() { * Terminates the transaction by sending the Bolt {@code RESET} message and waiting for its response as long as the * transaction has not already been terminated, is not closed or closing. * - * @since 5.11 * @throws org.neo4j.driver.exceptions.ClientException if the transaction is closed or is closing * @see org.neo4j.driver.exceptions.TransactionTerminatedException + * @since 5.11 */ public void terminate() { Futures.blockingGet( @@ -82,4 +110,14 @@ public void terminate() { private void terminateConnectionOnThreadInterrupt(String reason) { tx.connection().forceClose(reason); } + + private static void observeWithParentOrSupplier( + Observation parentObservation, Supplier observationSupplier, Consumer runnable) { + if (parentObservation != null) { + runnable.accept(parentObservation); + } else { + var observation = observationSupplier.get(); + observe(observation, () -> runnable.accept(observation)); + } + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java index 720fef697b..af973099fb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java @@ -39,6 +39,7 @@ import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.homedb.HomeDatabaseCache; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; @@ -54,6 +55,7 @@ public class SessionFactoryImpl implements SessionFactory { private final long defaultFetchSize; private final AuthTokenManager authTokenManager; private final HomeDatabaseCache homeDatabaseCache; + private final DriverObservationProvider observationProvider; @SuppressWarnings("deprecation") SessionFactoryImpl( @@ -62,7 +64,8 @@ public class SessionFactoryImpl implements SessionFactory { RetryLogic retryLogic, Config config, AuthTokenManager authTokenManager, - HomeDatabaseCache homeDatabaseCache) { + HomeDatabaseCache homeDatabaseCache, + DriverObservationProvider observationProvider) { this.securityPlanManager = Objects.requireNonNull(securityPlanManager); this.connectionSource = connectionSource; this.leakedSessionsLoggingEnabled = config.logLeakedSessions(); @@ -71,6 +74,7 @@ public class SessionFactoryImpl implements SessionFactory { this.defaultFetchSize = config.fetchSize(); this.authTokenManager = authTokenManager; this.homeDatabaseCache = Objects.requireNonNull(homeDatabaseCache); + this.observationProvider = Objects.requireNonNull(observationProvider); } @SuppressWarnings("deprecation") @@ -172,7 +176,8 @@ private NetworkSession createSession( authToken, telemetryDisabled, authTokenManager, - homeDatabaseCache) + homeDatabaseCache, + observationProvider) : new NetworkSession( connectionProvider, retryLogic, @@ -187,7 +192,8 @@ private NetworkSession createSession( authToken, telemetryDisabled, authTokenManager, - homeDatabaseCache); + homeDatabaseCache, + observationProvider); } public DriverBoltConnectionSource getConnectionSource() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnection.java index 2140f1a07b..8a6dcd3782 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnection.java @@ -16,37 +16,65 @@ */ package org.neo4j.driver.internal.adaptedbolt; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import org.neo4j.bolt.connection.AuthInfo; import org.neo4j.bolt.connection.BoltConnection; import org.neo4j.bolt.connection.BoltProtocolVersion; import org.neo4j.bolt.connection.BoltServerAddress; import org.neo4j.bolt.connection.message.Message; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; +import org.neo4j.driver.internal.observation.util.ObservationUtil; import org.neo4j.driver.internal.value.BoltValueFactory; final class AdaptingDriverBoltConnection implements DriverBoltConnection { private final BoltConnection connection; private final ErrorMapper errorMapper; private final BoltValueFactory boltValueFactory; + private final List messageNames = new ArrayList<>(); + private final DriverObservationProvider observationProvider; AdaptingDriverBoltConnection( - BoltConnection connection, ErrorMapper errorMapper, BoltValueFactory boltValueFactory) { + BoltConnection connection, + ErrorMapper errorMapper, + BoltValueFactory boltValueFactory, + DriverObservationProvider observationProvider) { this.connection = Objects.requireNonNull(connection); this.errorMapper = Objects.requireNonNull(errorMapper); this.boltValueFactory = Objects.requireNonNull(boltValueFactory); + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override - public CompletionStage writeAndFlush(DriverResponseHandler handler, List messages) { + public CompletionStage writeAndFlush( + DriverResponseHandler handler, List messages, Observation parentObservation) { + var handleObservation = ObservationUtil.scoped(observationProvider, parentObservation, () -> observationProvider + .boltHandle(appendAndClearMessages(messages)) + .start()); return connection - .writeAndFlush(new AdaptingDriverResponseHandler(handler, errorMapper, boltValueFactory), messages) - .exceptionally(errorMapper::mapAndThrow); + .writeAndFlush( + new AdaptingDriverResponseHandler(handler, errorMapper, boltValueFactory, handleObservation), + messages, + new BoltObservation(handleObservation)) + .exceptionally(throwable -> { + var mappedThrowable = errorMapper.map(throwable); + handleObservation.error(mappedThrowable); + handleObservation.stop(); + if (mappedThrowable instanceof RuntimeException runtimeException) { + throw runtimeException; + } else { + throw new CompletionException(mappedThrowable); + } + }); } @Override public CompletionStage write(List messages) { + appendMessages(messages); return connection.write(messages).exceptionally(errorMapper::mapAndThrow); } @@ -94,4 +122,15 @@ public boolean serverSideRoutingEnabled() { public BoltValueFactory valueFactory() { return boltValueFactory; } + + private synchronized List appendAndClearMessages(List messages) { + appendMessages(messages); + var messageNames = List.copyOf(this.messageNames); + this.messageNames.clear(); + return messageNames; + } + + private synchronized void appendMessages(List messages) { + messages.forEach(message -> messageNames.add(message.name())); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnectionSource.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnectionSource.java index 9a3a66524c..24da0d3590 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnectionSource.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnectionSource.java @@ -16,10 +16,14 @@ */ package org.neo4j.driver.internal.adaptedbolt; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.scoped; + import java.util.Objects; import java.util.concurrent.CompletionStage; import org.neo4j.bolt.connection.BoltConnectionSource; import org.neo4j.bolt.connection.RoutedBoltConnectionParameters; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.value.BoltValueFactory; public class AdaptingDriverBoltConnectionSource implements DriverBoltConnectionSource { @@ -27,28 +31,33 @@ public class AdaptingDriverBoltConnectionSource implements DriverBoltConnectionS private final ErrorMapper errorMapper; private final BoltValueFactory boltValueFactory; private final boolean routed; + private final DriverObservationProvider observationProvider; public AdaptingDriverBoltConnectionSource( BoltConnectionSource delegate, ErrorMapper errorMapper, BoltValueFactory boltValueFactory, - boolean routed) { + boolean routed, + DriverObservationProvider observationProvider) { this.delegate = Objects.requireNonNull(delegate); this.errorMapper = Objects.requireNonNull(errorMapper); this.boltValueFactory = Objects.requireNonNull(boltValueFactory); this.routed = routed; + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override - public CompletionStage getConnection(RoutedBoltConnectionParameters parameters) { - return delegate.getConnection(parameters) + public CompletionStage getConnection( + RoutedBoltConnectionParameters parameters, Observation parentObservation) { + return scoped(observationProvider, parentObservation, () -> delegate.getConnection(parameters)) .exceptionally(errorMapper::mapAndThrow) .thenApply(boltConnection -> new AdaptingDriverBoltConnection( boltConnection, routed || boltConnection.serverSideRoutingEnabled() ? new RoutedErrorMapper(boltConnection.serverAddress(), parameters.accessMode()) : errorMapper, - boltValueFactory)); + boltValueFactory, + observationProvider)); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverResponseHandler.java index 91e1369c37..eb73213a0f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverResponseHandler.java @@ -20,6 +20,17 @@ import java.util.Map; import java.util.Objects; import org.neo4j.bolt.connection.ResponseHandler; +import org.neo4j.bolt.connection.message.BeginMessage; +import org.neo4j.bolt.connection.message.CommitMessage; +import org.neo4j.bolt.connection.message.DiscardMessage; +import org.neo4j.bolt.connection.message.LogoffMessage; +import org.neo4j.bolt.connection.message.LogonMessage; +import org.neo4j.bolt.connection.message.PullMessage; +import org.neo4j.bolt.connection.message.ResetMessage; +import org.neo4j.bolt.connection.message.RollbackMessage; +import org.neo4j.bolt.connection.message.RouteMessage; +import org.neo4j.bolt.connection.message.RunMessage; +import org.neo4j.bolt.connection.message.TelemetryMessage; import org.neo4j.bolt.connection.summary.BeginSummary; import org.neo4j.bolt.connection.summary.CommitSummary; import org.neo4j.bolt.connection.summary.DiscardSummary; @@ -32,32 +43,42 @@ import org.neo4j.bolt.connection.summary.RunSummary; import org.neo4j.bolt.connection.summary.TelemetrySummary; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.observation.BoltHandleObservation; import org.neo4j.driver.internal.value.BoltValueFactory; final class AdaptingDriverResponseHandler implements ResponseHandler { private final DriverResponseHandler delegate; private final ErrorMapper errorMapper; private final BoltValueFactory boltValueFactory; + private final BoltHandleObservation observation; AdaptingDriverResponseHandler( - DriverResponseHandler delegate, ErrorMapper errorMapper, BoltValueFactory boltValueFactory) { + DriverResponseHandler delegate, + ErrorMapper errorMapper, + BoltValueFactory boltValueFactory, + BoltHandleObservation observation) { this.delegate = Objects.requireNonNull(delegate); this.errorMapper = Objects.requireNonNull(errorMapper); this.boltValueFactory = Objects.requireNonNull(boltValueFactory); + this.observation = Objects.requireNonNull(observation); } @Override public void onError(Throwable throwable) { - delegate.onError(errorMapper.map(throwable)); + throwable = errorMapper.map(throwable); + observation.error(throwable); + delegate.onError(throwable); } @Override public void onBeginSummary(BeginSummary summary) { + observation.onSummary(BeginMessage.NAME); delegate.onBeginSummary(summary); } @Override public void onRunSummary(RunSummary summary) { + observation.onSummary(RunMessage.NAME); delegate.onRunSummary(summary); } @@ -68,6 +89,7 @@ public void onRecord(List fields) { @Override public void onPullSummary(PullSummary summary) { + observation.onSummary(PullMessage.NAME); delegate.onPullSummary(new org.neo4j.driver.internal.adaptedbolt.summary.PullSummary() { @Override public boolean hasMore() { @@ -83,51 +105,61 @@ public Map metadata() { @Override public void onDiscardSummary(DiscardSummary summary) { + observation.onSummary(DiscardMessage.NAME); delegate.onDiscardSummary(() -> boltValueFactory.toDriverMap(summary.metadata())); } @Override public void onCommitSummary(CommitSummary summary) { + observation.onSummary(CommitMessage.NAME); delegate.onCommitSummary(summary); } @Override public void onRollbackSummary(RollbackSummary summary) { + observation.onSummary(RollbackMessage.NAME); delegate.onRollbackSummary(summary); } @Override public void onResetSummary(ResetSummary summary) { + observation.onSummary(ResetMessage.NAME); delegate.onResetSummary(summary); } @Override public void onRouteSummary(RouteSummary summary) { + observation.onSummary(RouteMessage.NAME); delegate.onRouteSummary(summary); } @Override public void onLogoffSummary(LogoffSummary summary) { + observation.onSummary(LogoffMessage.NAME); delegate.onLogoffSummary(summary); } @Override public void onLogonSummary(LogonSummary summary) { + observation.onSummary(LogonMessage.NAME); delegate.onLogonSummary(summary); } @Override public void onTelemetrySummary(TelemetrySummary summary) { + observation.onSummary(TelemetryMessage.NAME); delegate.onTelemetrySummary(summary); } @Override public void onIgnored() { + observation.onIgnored(); delegate.onIgnored(); } @Override public void onComplete() { + observation.stop(); delegate.onComplete(); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltExchangeObservationImpl.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltExchangeObservationImpl.java new file mode 100644 index 0000000000..788f1f1acd --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltExchangeObservationImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.adaptedbolt; + +import org.neo4j.bolt.connection.observation.BoltExchangeObservation; + +final class BoltExchangeObservationImpl extends BoltObservation implements BoltExchangeObservation { + private final org.neo4j.driver.internal.observation.BoltExchangeObservation delegate; + + BoltExchangeObservationImpl(org.neo4j.driver.internal.observation.BoltExchangeObservation delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public BoltExchangeObservation onWrite(String messageName) { + delegate.onWrite(messageName); + return this; + } + + @Override + public BoltExchangeObservation onRecord() { + delegate.onRecord(); + return this; + } + + @Override + public BoltExchangeObservation onSummary(String messageName) { + delegate.onSummary(messageName); + return this; + } + + @Override + public BoltExchangeObservation error(Throwable error) { + delegate.error(error); + return this; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltObservation.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltObservation.java new file mode 100644 index 0000000000..24721992a9 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltObservation.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.adaptedbolt; + +import java.util.Objects; +import org.neo4j.bolt.connection.observation.Observation; + +class BoltObservation implements Observation { + private final org.neo4j.driver.internal.observation.Observation delegate; + + BoltObservation(org.neo4j.driver.internal.observation.Observation delegate) { + this.delegate = Objects.requireNonNull(delegate); + } + + @Override + public Observation error(Throwable error) { + delegate.error(error); + return this; + } + + @Override + public void stop() { + delegate.stop(); + } + + org.neo4j.driver.internal.observation.Observation delegate() { + return delegate; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + var that = (BoltObservation) o; + return Objects.equals(delegate, that.delegate); + } + + @Override + public int hashCode() { + return Objects.hashCode(delegate); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltObservationProvider.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltObservationProvider.java new file mode 100644 index 0000000000..a7dceb434d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/BoltObservationProvider.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.adaptedbolt; + +import java.net.URI; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import org.neo4j.bolt.connection.BoltProtocolVersion; +import org.neo4j.bolt.connection.observation.BoltExchangeObservation; +import org.neo4j.bolt.connection.observation.HttpExchangeObservation; +import org.neo4j.bolt.connection.observation.ImmutableObservation; +import org.neo4j.bolt.connection.observation.Observation; +import org.neo4j.bolt.connection.pooled.observation.PoolObservationProvider; +import org.neo4j.driver.internal.observation.DriverObservationProvider; + +public final class BoltObservationProvider implements PoolObservationProvider { + private final DriverObservationProvider delegate; + + public BoltObservationProvider(DriverObservationProvider delegate) { + this.delegate = Objects.requireNonNull(delegate); + } + + @Override + public Observation connectionPoolCreate(String id, URI uri, int maxSize) { + return new BoltObservation( + delegate.connectionPoolCreate(id, uri, maxSize).start()); + } + + @Override + public Observation connectionPoolClose(String id, URI uri) { + return new BoltObservation(delegate.connectionPoolClose(id, uri).start()); + } + + @Override + public Observation pooledConnectionCreate(String id, URI uri) { + return new BoltObservation(delegate.pooledConnectionCreate(id, uri).start()); + } + + @Override + public Observation pooledConnectionClose(String id, URI uri) { + return new BoltObservation(delegate.pooledConnectionClose(id, uri).start()); + } + + @Override + public Observation pooledConnectionAcquire(String id, URI uri) { + return new BoltObservation(delegate.pooledConnectionAcquire(id, uri).start()); + } + + @Override + public Observation pooledConnectionInUse(ImmutableObservation parentObsevation, String id, URI uri) { + return supplyInScope( + parentObsevation, + () -> new BoltObservation( + delegate.pooledConnectionInUse(id, uri).start())); + } + + @Override + public BoltExchangeObservation boltExchange( + ImmutableObservation parentObsevation, + String host, + int port, + BoltProtocolVersion boltVersion, + BiConsumer setter) { + return supplyInScope( + parentObsevation, + () -> new BoltExchangeObservationImpl( + delegate.boltExchange(host, port, boltVersion, setter).start())); + } + + @Override + public HttpExchangeObservation httpExchange( + ImmutableObservation parentObsevation, + URI uri, + String method, + String uriTemplate, + BiConsumer setter) { + return supplyInScope( + parentObsevation, + () -> new HttpExchangeObservationImpl( + delegate.httpExchange(uri, method, uriTemplate, setter).start())); + } + + @Override + public Observation scopedObservation() { + var observation = delegate.scopedObservation(); + return observation != null ? new BoltObservation(observation) : null; + } + + @Override + public T supplyInScope(ImmutableObservation observation, Supplier supplier) { + if (observation instanceof BoltObservation boltObservation) { + var scopedObservation = scopedObservation(); + if (observation.equals(scopedObservation)) { + return supplier.get(); + } else { + try (var scope = boltObservation.delegate().openScope()) { + return supplier.get(); + } + } + } else { + return supplier.get(); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnection.java index 1c995908b6..cf993bdea8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnection.java @@ -22,14 +22,17 @@ import org.neo4j.bolt.connection.BoltProtocolVersion; import org.neo4j.bolt.connection.BoltServerAddress; import org.neo4j.bolt.connection.message.Message; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.value.BoltValueFactory; public interface DriverBoltConnection { - default CompletionStage writeAndFlush(DriverResponseHandler handler, Message messages) { - return writeAndFlush(handler, List.of(messages)); + default CompletionStage writeAndFlush( + DriverResponseHandler handler, Message messages, Observation parentObservation) { + return writeAndFlush(handler, List.of(messages), parentObservation); } - CompletionStage writeAndFlush(DriverResponseHandler handler, List messages); + CompletionStage writeAndFlush( + DriverResponseHandler handler, List messages, Observation parentObservation); CompletionStage write(List messages); diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnectionSource.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnectionSource.java index b0d9e4a0fe..74ad48fd46 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnectionSource.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnectionSource.java @@ -18,9 +18,11 @@ import java.util.concurrent.CompletionStage; import org.neo4j.bolt.connection.RoutedBoltConnectionParameters; +import org.neo4j.driver.internal.observation.Observation; public interface DriverBoltConnectionSource { - CompletionStage getConnection(RoutedBoltConnectionParameters parameters); + CompletionStage getConnection( + RoutedBoltConnectionParameters parameters, Observation parentObservation); CompletionStage verifyConnectivity(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/HttpExchangeObservationImpl.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/HttpExchangeObservationImpl.java new file mode 100644 index 0000000000..245061db79 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/HttpExchangeObservationImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.adaptedbolt; + +import java.util.List; +import java.util.Map; +import org.neo4j.bolt.connection.observation.HttpExchangeObservation; + +final class HttpExchangeObservationImpl extends BoltObservation implements HttpExchangeObservation { + private final org.neo4j.driver.internal.observation.HttpExchangeObservation delegate; + + HttpExchangeObservationImpl(org.neo4j.driver.internal.observation.HttpExchangeObservation delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public HttpExchangeObservation onHeaders(Map> headers) { + delegate.onHeaders(headers); + return this; + } + + @Override + public HttpExchangeObservation onResponse(Response response) { + delegate.onResponse(new org.neo4j.driver.internal.observation.HttpExchangeObservation.Response() { + @Override + public int statusCode() { + return response.statusCode(); + } + + @Override + public Map> headers() { + return response.headers(); + } + + @Override + public String httpVersion() { + return response.httpVersion(); + } + }); + return this; + } + + @Override + public HttpExchangeObservation error(Throwable error) { + delegate.error(error); + return this; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/BoltConnectionWithAuthTokenManager.java b/driver/src/main/java/org/neo4j/driver/internal/async/BoltConnectionWithAuthTokenManager.java index 9a53b00ba3..1dd1a35ea2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/BoltConnectionWithAuthTokenManager.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/BoltConnectionWithAuthTokenManager.java @@ -25,6 +25,7 @@ import org.neo4j.driver.exceptions.SecurityRetryableException; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.value.BoltValueFactory; @@ -37,8 +38,10 @@ public BoltConnectionWithAuthTokenManager(DriverBoltConnection delegate, AuthTok } @Override - public CompletionStage writeAndFlush(DriverResponseHandler handler, List messages) { - return delegate.writeAndFlush(new ErrorMappingResponseHandler(handler, this::mapSecurityError), messages); + public CompletionStage writeAndFlush( + DriverResponseHandler handler, List messages, Observation parentObservation) { + return delegate.writeAndFlush( + new ErrorMappingResponseHandler(handler, this::mapSecurityError), messages, parentObservation); } private Throwable mapSecurityError(Throwable throwable) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/DelegatingBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/async/DelegatingBoltConnection.java index 5b16db0a29..fe68463729 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/DelegatingBoltConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/DelegatingBoltConnection.java @@ -25,6 +25,7 @@ import org.neo4j.bolt.connection.message.Message; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.value.BoltValueFactory; public abstract class DelegatingBoltConnection implements DriverBoltConnection { @@ -35,8 +36,9 @@ protected DelegatingBoltConnection(DriverBoltConnection delegate) { } @Override - public CompletionStage writeAndFlush(DriverResponseHandler handler, List messages) { - return delegate.writeAndFlush(handler, messages); + public CompletionStage writeAndFlush( + DriverResponseHandler handler, List messages, Observation parentObservation) { + return delegate.writeAndFlush(handler, messages, parentObservation); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java index 0aa035dae3..ad5b268617 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java @@ -17,10 +17,13 @@ package org.neo4j.driver.internal.async; import static java.util.Collections.emptyMap; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeAsync; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.scoped; import static org.neo4j.driver.internal.util.Futures.completedWithNull; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -35,14 +38,18 @@ import org.neo4j.driver.async.ResultCursor; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.GqlStatusError; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.Futures; public class InternalAsyncSession extends AsyncAbstractQueryRunner implements AsyncSession { private final NetworkSession session; + private final DriverObservationProvider observationProvider; - public InternalAsyncSession(NetworkSession session) { + public InternalAsyncSession(NetworkSession session, DriverObservationProvider observationProvider) { this.session = session; + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override @@ -63,12 +70,14 @@ public CompletionStage runAsync( @Override public CompletionStage runAsync(Query query, TransactionConfig config) { - return session.runAsync(query, config); + var runObservation = observationProvider.sessionRun(AsyncSession.class, query.text(), query.parameters()); + return observeAsync(runObservation, () -> session.runAsync(query, config, runObservation, ResultCursor.class)); } @Override public CompletionStage closeAsync() { - return session.closeAsync(); + var closeObservation = observationProvider.sessionClose(AsyncSession.class); + return observeAsync(closeObservation, () -> session.closeAsync(closeObservation)); } @Override @@ -78,8 +87,10 @@ public CompletionStage beginTransactionAsync() { @Override public CompletionStage beginTransactionAsync(TransactionConfig config) { - return session.beginTransactionAsync(config, new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION)) - .thenApply(InternalAsyncTransaction::new); + var beginObservation = observationProvider.beginTransaction(AsyncTransaction.class); + return observeAsync(beginObservation, () -> session.beginTransactionAsync( + config, new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION), beginObservation) + .thenApply(tx -> new InternalAsyncTransaction(tx, observationProvider, null))); } @Override @@ -102,32 +113,34 @@ public Set lastBookmarks() { private CompletionStage transactionAsync( AccessMode mode, AsyncTransactionCallback> work, TransactionConfig config) { var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.MANAGED_TRANSACTION); - return session.retryLogic().retryAsync(() -> { + var executeObservation = observationProvider.sessionExecute(AsyncSession.class, mode); + return observeAsync(executeObservation, () -> session.retryLogic().retryAsync(() -> { var resultFuture = new CompletableFuture(); - var txFuture = session.beginTransactionAsync(mode, config, apiTelemetryWork); + var txFuture = session.beginTransactionAsync(mode, config, apiTelemetryWork, executeObservation); txFuture.whenComplete((tx, completionError) -> { var error = Futures.completionExceptionCause(completionError); if (error != null) { resultFuture.completeExceptionally(error); } else { - executeWork(resultFuture, tx, work); + executeWork(resultFuture, tx, work, executeObservation); } }); return resultFuture; - }); + })); } private void executeWork( CompletableFuture resultFuture, UnmanagedTransaction tx, - AsyncTransactionCallback> work) { - var workFuture = safeExecuteWork(tx, work); + AsyncTransactionCallback> work, + Observation parentObservation) { + var workFuture = safeExecuteWork(tx, work, parentObservation); workFuture.whenComplete((result, completionError) -> { var error = Futures.completionExceptionCause(completionError); if (error != null) { - closeTxAfterFailedTransactionWork(tx, resultFuture, error); + closeTxAfterFailedTransactionWork(tx, resultFuture, error, parentObservation); } else if (result instanceof ResultCursor) { var message = String.format( "%s is not a valid return value, it should be consumed before producing a return value", @@ -139,20 +152,25 @@ private void executeWork( message, GqlStatusError.DIAGNOSTIC_RECORD, null); - closeTxAfterFailedTransactionWork(tx, resultFuture, error); + closeTxAfterFailedTransactionWork(tx, resultFuture, error, parentObservation); } else { - closeTxAfterSucceededTransactionWork(tx, resultFuture, result); + closeTxAfterSucceededTransactionWork(tx, resultFuture, result, parentObservation); } }); } private CompletionStage safeExecuteWork( - UnmanagedTransaction tx, AsyncTransactionCallback> work) { + UnmanagedTransaction tx, AsyncTransactionCallback> work, Observation parentObservation) { // given work might fail in both async and sync way // async failure will result in a failed future being returned // sync failure will result in an exception being thrown try { - var result = work.execute(new DelegatingAsyncTransactionContext(new InternalAsyncTransaction(tx))); + var result = scoped( + observationProvider, + parentObservation, + () -> work.execute(new DelegatingAsyncTransactionContext( + new InternalAsyncTransaction(tx, observationProvider, parentObservation)))); + ; // protect from given transaction function returning null return result == null ? completedWithNull() : result; @@ -163,8 +181,11 @@ private CompletionStage safeExecuteWork( } private void closeTxAfterFailedTransactionWork( - UnmanagedTransaction tx, CompletableFuture resultFuture, Throwable error) { - tx.closeAsync().whenComplete((ignored, rollbackError) -> { + UnmanagedTransaction tx, + CompletableFuture resultFuture, + Throwable error, + Observation parentObservation) { + tx.closeAsync(parentObservation).whenComplete((ignored, rollbackError) -> { if (rollbackError != null) { error.addSuppressed(rollbackError); } @@ -173,8 +194,8 @@ private void closeTxAfterFailedTransactionWork( } private void closeTxAfterSucceededTransactionWork( - UnmanagedTransaction tx, CompletableFuture resultFuture, T result) { - tx.closeAsync(true).whenComplete((ignored, completionError) -> { + UnmanagedTransaction tx, CompletableFuture resultFuture, T result, Observation parentObservation) { + tx.closeAsync(true, parentObservation).whenComplete((ignored, completionError) -> { var commitError = Futures.completionExceptionCause(completionError); if (commitError != null) { resultFuture.completeExceptionally(commitError); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncTransaction.java index 9e3c78ca89..82d570b32d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncTransaction.java @@ -16,32 +16,43 @@ */ package org.neo4j.driver.internal.async; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeAsync; + +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.neo4j.driver.Query; import org.neo4j.driver.async.AsyncTransaction; import org.neo4j.driver.async.ResultCursor; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; public class InternalAsyncTransaction extends AsyncAbstractQueryRunner implements AsyncTransaction { private final UnmanagedTransaction tx; + private final DriverObservationProvider observationProvider; - public InternalAsyncTransaction(UnmanagedTransaction tx) { + public InternalAsyncTransaction( + UnmanagedTransaction tx, DriverObservationProvider observationProvider, Observation parentObservation) { this.tx = tx; + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override public CompletionStage commitAsync() { - return tx.commitAsync(); + var commitObservation = observationProvider.transactionCommit(AsyncTransaction.class); + return observeAsync(commitObservation, () -> tx.commitAsync(commitObservation)); } @Override public CompletionStage rollbackAsync() { - return tx.rollbackAsync(); + var rollbackObservation = observationProvider.transactionRollback(AsyncTransaction.class); + return observeAsync(rollbackObservation, () -> tx.rollbackAsync(rollbackObservation)); } @Override public CompletionStage closeAsync() { - return tx.closeAsync(); + var closeObservation = observationProvider.transactionClose(AsyncTransaction.class); + return observeAsync(closeObservation, () -> tx.closeAsync(closeObservation)); } @Override @@ -51,7 +62,9 @@ public CompletionStage isOpenAsync() { @Override public CompletionStage runAsync(Query query) { - return tx.runAsync(query); + var runObservation = + observationProvider.transactionRun(AsyncTransaction.class, query.text(), query.parameters()); + return observeAsync(runObservation, () -> tx.runAsync(query, runObservation, ResultCursor.class)); } public boolean isOpen() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java index 05e4541d20..f16ce28a9e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java @@ -31,6 +31,7 @@ import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.homedb.HomeDatabaseCache; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.util.Futures; @@ -51,7 +52,8 @@ public LeakLoggingNetworkSession( AuthToken overrideAuthToken, boolean telemetryDisabled, AuthTokenManager authTokenManager, - HomeDatabaseCache homeDatabaseCache) { + HomeDatabaseCache homeDatabaseCache, + DriverObservationProvider observationProvider) { super( connectionProvider, retryLogic, @@ -66,7 +68,8 @@ public LeakLoggingNetworkSession( overrideAuthToken, telemetryDisabled, authTokenManager, - homeDatabaseCache); + homeDatabaseCache, + observationProvider); this.stackTrace = captureStackTrace(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java index cf37c34c27..7cb0aa7420 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java @@ -80,6 +80,9 @@ import org.neo4j.driver.internal.homedb.HomeDatabaseCache; import org.neo4j.driver.internal.homedb.HomeDatabaseCacheKey; import org.neo4j.driver.internal.logging.PrefixedLogger; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; @@ -112,6 +115,7 @@ public class NetworkSession { private final AuthTokenManager authTokenManager; private final HomeDatabaseCache homeDatabaseCache; private final HomeDatabaseCacheKey homeDatabaseKey; + private final DriverObservationProvider observationProvider; public NetworkSession( DriverBoltConnectionSource boltConnectionProvider, @@ -127,7 +131,8 @@ public NetworkSession( AuthToken overrideAuthToken, boolean telemetryDisabled, AuthTokenManager authTokenManager, - HomeDatabaseCache homeDatabaseCache) { + HomeDatabaseCache homeDatabaseCache, + DriverObservationProvider observationProvider) { Objects.requireNonNull(bookmarks, "bookmarks may not be null"); Objects.requireNonNull(bookmarkManager, "bookmarkManager may not be null"); this.boltConnectionProvider = Objects.requireNonNull(boltConnectionProvider); @@ -149,12 +154,14 @@ public NetworkSession( this.authTokenManager = authTokenManager; this.homeDatabaseCache = Objects.requireNonNull(homeDatabaseCache); this.homeDatabaseKey = HomeDatabaseCacheKey.of(overrideAuthToken, impersonatedUser); + this.observationProvider = Objects.requireNonNull(observationProvider); } - public CompletionStage runAsync(Query query, TransactionConfig config) { + public CompletionStage runAsync( + Query query, TransactionConfig config, Observation parentObservation, Class resultType) { ensureSessionIsOpen(); var disposable = ensureNoOpenTxBeforeRunningQuery() - .thenCompose(ignore -> acquireConnection(mode)) + .thenCompose(ignore -> acquireConnection(mode, parentObservation)) .thenCompose(connection -> { var parameters = query.parameters().asMap(Values::value); var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.AUTO_COMMIT_TRANSACTION); @@ -167,7 +174,9 @@ public CompletionStage runAsync(Query query, TransactionConfig con true, null, this::handleDatabaseName, - null); + null, + observationProvider, + resultType); var cursorStage = CompletableFuture.completedStage(null) .thenCompose(ignored -> { var messages = new ArrayList(3); @@ -176,7 +185,7 @@ public CompletionStage runAsync(Query query, TransactionConfig con .ifPresent(messages::add); messages.add(newRunMessage(connection, query, parameters, config)); messages.add(Messages.pull(-1, fetchSize)); - return connection.writeAndFlush(resultCursor, messages); + return connection.writeAndFlush(resultCursor, messages, parentObservation); }) .thenCompose(ignored -> resultCursor.resultCursor()) .handle((resultCursorImpl, throwable) -> { @@ -205,10 +214,13 @@ public CompletionStage runAsync(Query query, TransactionConfig con } public CompletionStage runRx( - Query query, TransactionConfig config, CompletionStage cursorPublishStage) { + Query query, + TransactionConfig config, + CompletionStage cursorPublishStage, + Observation parentObservation) { ensureSessionIsOpen(); var newResultCursorStage = ensureNoOpenTxBeforeRunningQuery() - .thenCompose(ignore -> acquireConnection(mode)) + .thenCompose(ignore -> acquireConnection(mode, parentObservation)) .thenCompose(connection -> { var parameters = query.parameters().asMap(Values::value); var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.AUTO_COMMIT_TRANSACTION); @@ -223,7 +235,7 @@ public CompletionStage runRx( .getTelemetryMessageIfEnabled(connection) .ifPresent(messages::add); messages.add(newRunMessage(connection, query, parameters, config)); - return connection.writeAndFlush(responseHandler, messages); + return connection.writeAndFlush(responseHandler, messages, parentObservation); }) .thenCompose(ignored -> responseHandler.cursorFuture) .handle((resultCursor, throwable) -> { @@ -255,18 +267,21 @@ public CompletionStage runRx( } public CompletionStage beginTransactionAsync( - TransactionConfig config, ApiTelemetryWork apiTelemetryWork) { - return beginTransactionAsync(mode, config, null, apiTelemetryWork, true); + TransactionConfig config, ApiTelemetryWork apiTelemetryWork, Observation parentObservation) { + return beginTransactionAsync(mode, config, null, apiTelemetryWork, true, parentObservation); } public CompletionStage beginTransactionAsync( - TransactionConfig config, String txType, ApiTelemetryWork apiTelemetryWork) { - return this.beginTransactionAsync(mode, config, txType, apiTelemetryWork, true); + TransactionConfig config, String txType, ApiTelemetryWork apiTelemetryWork, Observation parentObservation) { + return this.beginTransactionAsync(mode, config, txType, apiTelemetryWork, true, parentObservation); } public CompletionStage beginTransactionAsync( - org.neo4j.driver.AccessMode mode, TransactionConfig config, ApiTelemetryWork apiTelemetryWork) { - return beginTransactionAsync(mode, config, null, apiTelemetryWork, true); + org.neo4j.driver.AccessMode mode, + TransactionConfig config, + ApiTelemetryWork apiTelemetryWork, + Observation parentObservation) { + return beginTransactionAsync(mode, config, null, apiTelemetryWork, true, parentObservation); } public CompletionStage beginTransactionAsync( @@ -274,14 +289,15 @@ public CompletionStage beginTransactionAsync( TransactionConfig config, String txType, ApiTelemetryWork apiTelemetryWork, - boolean flush) { + boolean flush, + Observation parentObservation) { ensureSessionIsOpen(); apiTelemetryWork.setEnabled(!telemetryDisabled); // create a chain that acquires connection and starts a transaction var newTransactionStage = ensureNoOpenTxBeforeStartingTx() - .thenCompose(ignore -> acquireConnection(mode)) + .thenCompose(ignore -> acquireConnection(mode, parentObservation)) .thenCompose(connection -> { var tx = new UnmanagedTransaction( connection, @@ -293,8 +309,9 @@ public CompletionStage beginTransactionAsync( notificationConfig, apiTelemetryWork, this::handleDatabaseName, - logging); - return tx.beginAsync(determineBookmarks(true), config, txType, flush); + logging, + observationProvider); + return tx.beginAsync(determineBookmarks(true), config, txType, flush, parentObservation); }); // update the reference to the only known transaction @@ -338,7 +355,8 @@ public void onComplete() { future.complete(null); } }, - Messages.reset()) + Messages.reset(), + NoopObservation.getInstance()) .thenCompose(ignored -> future); } else { return completedWithNull(); @@ -373,18 +391,18 @@ public boolean isOpen() { return open.get(); } - public CompletionStage closeAsync() { + public CompletionStage closeAsync(Observation parentObservation) { if (open.compareAndSet(true, false)) { return resultCursorStage .thenCompose(cursor -> { if (cursor != null) { // there exists a cursor with potentially unconsumed error, try to extract and propagate it - return cursor.discardAllFailureAsync(); + return cursor.discardAllFailureAsync(parentObservation); } // no result cursor exists so no error exists return completedWithNull(); }) - .thenCompose(cursorError -> closeTransactionAndReleaseConnection() + .thenCompose(cursorError -> closeTransactionAndReleaseConnection(parentObservation) .thenApply(txCloseError -> { // now we have cursor error, active transaction has been closed and connection has been // released @@ -412,11 +430,12 @@ private void handleDatabaseName(String name) { homeDatabaseCache.put(homeDatabaseKey, name); } - private CompletionStage acquireConnection(AccessMode mode) { + private CompletionStage acquireConnection( + AccessMode mode, Observation parentObservation) { var overrideAuthToken = connectionContext.overrideAuthToken(); var authTokenManager = overrideAuthToken != null ? NoopAuthTokenManager.INSTANCE : this.authTokenManager; - var newConnectionStage = pulledResultCursorStage(connectionStage) - .thenCompose(ignored -> acquireAdaptedConnection(mode)) + var newConnectionStage = pulledResultCursorStage(connectionStage, parentObservation) + .thenCompose(ignored -> acquireAdaptedConnection(mode, parentObservation)) .thenApply(connection -> (DriverBoltConnection) new BoltConnectionWithAuthTokenManager(connection, authTokenManager)) .thenApply(BoltConnectionWithCloseTracking::new) @@ -458,7 +477,8 @@ private BoltConnectionWithCloseTracking mapAcquisitionError(Throwable throwable) } } - private CompletionStage acquireAdaptedConnection(AccessMode mode) { + private CompletionStage acquireAdaptedConnection( + AccessMode mode, Observation parentObservation) { var databaseName = connectionContext.databaseNameFuture().getNow(null); var impersonatedUser = connectionContext.impersonatedUser(); var minVersion = minBoltVersion(connectionContext); @@ -491,18 +511,18 @@ private CompletionStage acquireAdaptedConnection(AccessMod .withBookmarks(bookmarks) .withImpersonatedUser(impersonatedUser) .build(); - return boltConnectionProvider.getConnection(parameters); + return boltConnectionProvider.getConnection(parameters, parentObservation); } private CompletionStage pulledResultCursorStage( - CompletionStage connectionStage) { + CompletionStage connectionStage, Observation parentObservation) { return resultCursorStage .thenCompose(cursor -> { if (cursor == null) { return completedWithNull(); } // make sure previous result is fully consumed and connection is released back to the pool - return cursor.pullAllFailureAsync(); + return cursor.pullAllFailureAsync(parentObservation); }) .thenCompose(error -> { if (error == null) { @@ -520,12 +540,12 @@ private CompletionStage pulledResultCursorStage( }); } - private CompletionStage closeTransactionAndReleaseConnection() { + private CompletionStage closeTransactionAndReleaseConnection(Observation parentObservation) { return existingTransactionOrNull() .thenCompose(tx -> { if (tx != null) { // there exists an open transaction, let's close it and propagate the error, if any - return tx.closeAsync() + return tx.closeAsync(parentObservation) .thenApply(ignore -> (Throwable) null) .exceptionally(Function.identity()); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java b/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java index 3aee3200db..8a37766823 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java @@ -25,6 +25,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.neo4j.driver.internal.FailableCursor; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.util.Futures; public class ResultCursorsHolder { @@ -45,20 +46,20 @@ void add(CompletionStage cursorStage) { }); } - CompletionStage retrieveNotConsumedError() { + CompletionStage retrieveNotConsumedError(Observation parentObservation) { List> cursorStages; synchronized (this) { cursorStages = List.copyOf(this.cursorStages); } - var failures = retrieveAllFailures(cursorStages); + var failures = retrieveAllFailures(cursorStages, parentObservation); return CompletableFuture.allOf(failures).thenApply(ignore -> findFirstFailure(failures)); } @SuppressWarnings("unchecked") private static CompletableFuture[] retrieveAllFailures( - List> cursorStages) { + List> cursorStages, Observation parentObservation) { return cursorStages.stream() - .map(ResultCursorsHolder::retrieveFailure) + .map(result -> retrieveFailure(result, parentObservation)) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new); } @@ -73,9 +74,11 @@ private static Throwable findFirstFailure(CompletableFuture[] complet .orElse(null); } - private static CompletionStage retrieveFailure(CompletionStage cursorStage) { + private static CompletionStage retrieveFailure( + CompletionStage cursorStage, Observation parentObservation) { return cursorStage .exceptionally(cursor -> null) - .thenCompose(cursor -> cursor == null ? completedWithNull() : cursor.discardAllFailureAsync()); + .thenCompose(cursor -> + cursor == null ? completedWithNull() : cursor.discardAllFailureAsync(parentObservation)); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/TerminationAwareBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/async/TerminationAwareBoltConnection.java index 53ea10ac93..6464b1d186 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/TerminationAwareBoltConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/TerminationAwareBoltConnection.java @@ -28,6 +28,7 @@ import org.neo4j.driver.Logging; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.util.Futures; final class TerminationAwareBoltConnection extends DelegatingBoltConnection { @@ -52,10 +53,10 @@ public TerminationAwareBoltConnection( this.throwableConsumer = Objects.requireNonNull(throwableConsumer); } - public CompletionStage reset() { + public CompletionStage reset(Observation parentObservation) { var future = new CompletableFuture(); var thisVal = this; - executor.execute(ignored -> resetBolt(future)).whenComplete((ignored, throwable) -> { + executor.execute(ignored -> resetBolt(future, parentObservation)).whenComplete((ignored, throwable) -> { if (throwable != null) { throwableConsumer.accept(throwable); future.completeExceptionally(throwable); @@ -64,7 +65,7 @@ public CompletionStage reset() { return future; } - private CompletionStage resetBolt(CompletableFuture future) { + private CompletionStage resetBolt(CompletableFuture future, Observation parentObservation) { return delegate.writeAndFlush( new DriverResponseHandler() { Throwable throwable = null; @@ -85,21 +86,27 @@ public void onComplete() { } } }, - List.of(Messages.reset())); + List.of(Messages.reset()), + parentObservation); } @Override - public CompletionStage writeAndFlush(DriverResponseHandler handler, List messages) { - return executor.execute(causeOfTermination -> flushBolt(causeOfTermination, handler, messages)); + public CompletionStage writeAndFlush( + DriverResponseHandler handler, List messages, Observation parentObservation) { + return executor.execute( + causeOfTermination -> flushBolt(causeOfTermination, handler, messages, parentObservation)); } private CompletionStage flushBolt( - Throwable causeOfTermination, DriverResponseHandler handler, List messages) { + Throwable causeOfTermination, + DriverResponseHandler handler, + List messages, + Observation parentObservation) { if (causeOfTermination == null) { log.trace("This connection is active, will flush"); var terminationAwareResponseHandler = new TerminationAwareResponseHandler(logging, handler, executor, throwableConsumer); - return delegate.writeAndFlush(terminationAwareResponseHandler, messages) + return delegate.writeAndFlush(terminationAwareResponseHandler, messages, parentObservation) .handle((ignored, flushThrowable) -> { flushThrowable = Futures.completionExceptionCause(flushThrowable); if (flushThrowable != null) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/UnmanagedTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/async/UnmanagedTransaction.java index 4d162331ec..9b7cbf23b2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/UnmanagedTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/UnmanagedTransaction.java @@ -65,6 +65,9 @@ import org.neo4j.driver.internal.cursor.ResultCursorImpl; import org.neo4j.driver.internal.cursor.RxResultCursor; import org.neo4j.driver.internal.cursor.RxResultCursorImpl; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.ErrorUtil; import org.neo4j.driver.internal.util.Futures; @@ -125,6 +128,7 @@ private enum State { private final ApiTelemetryWork apiTelemetryWork; private final Consumer databaseNameConsumer; + private final DriverObservationProvider observationProvider; private Message[] beginMessages; public UnmanagedTransaction( @@ -137,7 +141,8 @@ public UnmanagedTransaction( NotificationConfig notificationConfig, ApiTelemetryWork apiTelemetryWork, Consumer databaseNameConsumer, - @SuppressWarnings("deprecation") Logging logging) { + @SuppressWarnings("deprecation") Logging logging, + DriverObservationProvider observationProvider) { this( connection, databaseName, @@ -149,7 +154,8 @@ public UnmanagedTransaction( notificationConfig, apiTelemetryWork, databaseNameConsumer, - logging); + logging, + observationProvider); } protected UnmanagedTransaction( @@ -163,7 +169,8 @@ protected UnmanagedTransaction( NotificationConfig notificationConfig, ApiTelemetryWork apiTelemetryWork, Consumer databaseNameConsumer, - @SuppressWarnings("deprecation") Logging logging) { + @SuppressWarnings("deprecation") Logging logging, + DriverObservationProvider observationProvider) { this.logging = logging; this.connection = new TerminationAwareBoltConnection(logging, connection, this, this::markTerminated); this.databaseName = databaseName; @@ -175,11 +182,16 @@ protected UnmanagedTransaction( this.notificationConfig = notificationConfig; this.apiTelemetryWork = apiTelemetryWork; this.databaseNameConsumer = Objects.requireNonNull(databaseNameConsumer); + this.observationProvider = Objects.requireNonNull(observationProvider); } // flush = false is only supported for async mode with a single subsequent run public CompletionStage beginAsync( - Set initialBookmarks, TransactionConfig config, String txType, boolean flush) { + Set initialBookmarks, + TransactionConfig config, + String txType, + boolean flush, + Observation parentObservation) { var bookmarks = initialBookmarks.stream().map(Bookmark::value).collect(Collectors.toSet()); return CompletableFuture.completedStage(null) .thenApply(ignored -> { @@ -200,7 +212,7 @@ public CompletionStage beginAsync( if (flush) { var responseHandler = new BeginResponseHandler(apiTelemetryWork, databaseNameConsumer); connection - .writeAndFlush(responseHandler, messages) + .writeAndFlush(responseHandler, messages, parentObservation) .thenCompose(ignored -> responseHandler.summaryFuture) .whenComplete((summary, throwable) -> { if (throwable != null) { @@ -230,23 +242,23 @@ public CompletionStage beginAsync( }); } - public CompletionStage closeAsync() { - return closeAsync(false); + public CompletionStage closeAsync(Observation parentObservation) { + return closeAsync(false, parentObservation); } - public CompletionStage closeAsync(boolean commit) { - return closeAsync(commit, true); + public CompletionStage closeAsync(boolean commit, Observation parentObservation) { + return closeAsync(commit, true, parentObservation); } - public CompletionStage commitAsync() { - return closeAsync(true, false); + public CompletionStage commitAsync(Observation parentObservation) { + return closeAsync(true, false, parentObservation); } - public CompletionStage rollbackAsync() { - return closeAsync(false, false); + public CompletionStage rollbackAsync(Observation parentObservation) { + return closeAsync(false, false, parentObservation); } - public CompletionStage runAsync(Query query) { + public CompletionStage runAsync(Query query, Observation parentObservation, Class resultType) { ensureCanRunQueries(); var parameters = query.parameters().asMap(Values::value); var resultCursor = new ResultCursorImpl( @@ -257,12 +269,14 @@ public CompletionStage runAsync(Query query) { false, beginFuture, databaseNameConsumer, - apiTelemetryWork); + apiTelemetryWork, + observationProvider, + resultType); var flushStage = CompletableFuture.completedStage(null).thenCompose(ignored -> { var messages = List.of( Messages.run(query.text(), connection.valueFactory().toBoltMap(parameters)), Messages.pull(-1, fetchSize)); - return connection.writeAndFlush(resultCursor, messages); + return connection.writeAndFlush(resultCursor, messages, parentObservation); }); return beginFuture.thenCompose(ignored -> { var cursorStage = flushStage @@ -273,14 +287,15 @@ public CompletionStage runAsync(Query query) { }); } - public CompletionStage runRx(Query query) { + public CompletionStage runRx(Query query, Observation parentObservation) { ensureCanRunQueries(); var parameters = query.parameters().asMap(Values::value); var responseHandler = new RunRxResponseHandler(logging, apiTelemetryWork, beginFuture, connection, query); var flushStage = CompletableFuture.completedStage(null) .thenCompose(runMessage -> connection.writeAndFlush( responseHandler, - Messages.run(query.text(), connection.valueFactory().toBoltMap(parameters)))); + Messages.run(query.text(), connection.valueFactory().toBoltMap(parameters)), + parentObservation)); return beginFuture.thenCompose(ignored -> { var cursorStage = flushStage.thenCompose(flushResult -> responseHandler.cursorFuture); resultCursors.add(cursorStage); @@ -345,7 +360,7 @@ public CompletionStage terminateAsync() { return terminationStage != null ? terminationStage : completedFuture(null); } else { markTerminated(null); - terminationStage = connection.reset(); + terminationStage = connection.reset(NoopObservation.getInstance()); return terminationStage; } } @@ -408,7 +423,7 @@ private void ensureCanRunQueries() { }); } - private CompletionStage doCommitAsync(Throwable cursorFailure) { + private CompletionStage doCommitAsync(Throwable cursorFailure, Observation parentObservation) { ClientException exception = executeWithLock( lock, () -> state == State.TERMINATED @@ -428,7 +443,7 @@ private CompletionStage doCommitAsync(Throwable cursorFailure) { var commitSummary = new CompletableFuture(); var responseHandler = new BasicResponseHandler(); connection - .writeAndFlush(responseHandler, Messages.commit()) + .writeAndFlush(responseHandler, Messages.commit(), parentObservation) .thenCompose(ignored -> responseHandler.summaries()) .whenComplete((summaries, throwable) -> { if (throwable != null) { @@ -459,14 +474,14 @@ private CompletionStage doCommitAsync(Throwable cursorFailure) { } } - private CompletionStage doRollbackAsync() { + private CompletionStage doRollbackAsync(Observation parentObservation) { if (executeWithLock(lock, () -> state) == State.TERMINATED) { return completedWithNull(); } else { var rollbackFuture = new CompletableFuture(); var responseHandler = new BasicResponseHandler(); connection - .writeAndFlush(responseHandler, Messages.rollback()) + .writeAndFlush(responseHandler, Messages.rollback(), parentObservation) .thenCompose(ignored -> responseHandler.summaries()) .whenComplete((summaries, throwable) -> { if (throwable != null) { @@ -526,7 +541,8 @@ private CompletionStage handleTransactionCompletion(boolean commitAttempt, } @SuppressWarnings("DuplicatedCode") - private CompletionStage closeAsync(boolean commit, boolean completeWithNullIfNotOpen) { + private CompletionStage closeAsync( + boolean commit, boolean completeWithNullIfNotOpen, Observation parentObservation) { var stage = executeWithLock(lock, () -> { CompletionStage resultStage = null; if (completeWithNullIfNotOpen && !isOpen()) { @@ -588,13 +604,15 @@ private CompletionStage closeAsync(boolean commit, boolean completeWithNul Function> targetAction; if (commit) { targetFuture = commitFuture; - targetAction = throwable -> doCommitAsync(throwable).handle(handleCommitOrRollback(throwable)); + targetAction = throwable -> + doCommitAsync(throwable, parentObservation).handle(handleCommitOrRollback(throwable)); } else { targetFuture = rollbackFuture; - targetAction = throwable -> doRollbackAsync().handle(handleCommitOrRollback(throwable)); + targetAction = + throwable -> doRollbackAsync(parentObservation).handle(handleCommitOrRollback(throwable)); } resultCursors - .retrieveNotConsumedError() + .retrieveNotConsumedError(parentObservation) .thenCompose(targetAction) .handle((ignored, throwable) -> handleTransactionCompletion(commit, throwable)) .thenCompose(Function.identity()) diff --git a/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnection.java index afa78ebf5a..45b241bd3d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnection.java @@ -28,6 +28,7 @@ import org.neo4j.bolt.connection.BoltServerAddress; import org.neo4j.bolt.connection.ResponseHandler; import org.neo4j.bolt.connection.message.Message; +import org.neo4j.bolt.connection.observation.ImmutableObservation; final class ListeningBoltConnection implements BoltConnection { private final BoltConnection delegate; @@ -39,8 +40,9 @@ public ListeningBoltConnection(BoltConnection delegate, BoltConnectionListener b } @Override - public CompletionStage writeAndFlush(ResponseHandler handler, List messages) { - return delegate.writeAndFlush(handler, messages); + public CompletionStage writeAndFlush( + ResponseHandler handler, List messages, ImmutableObservation parentObservation) { + return delegate.writeAndFlush(handler, messages, parentObservation); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnectionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnectionProvider.java index bdbf08db0e..af1b3b0170 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnectionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnectionProvider.java @@ -26,6 +26,7 @@ import org.neo4j.bolt.connection.BoltProtocolVersion; import org.neo4j.bolt.connection.NotificationConfig; import org.neo4j.bolt.connection.SecurityPlan; +import org.neo4j.bolt.connection.observation.ImmutableObservation; final class ListeningBoltConnectionProvider implements BoltConnectionProvider { private final BoltConnectionProvider delegate; @@ -47,7 +48,8 @@ public CompletionStage connect( SecurityPlan securityPlan, AuthToken authToken, BoltProtocolVersion minVersion, - NotificationConfig notificationConfig) { + NotificationConfig notificationConfig, + ImmutableObservation parentObservation) { return delegate.connect( uri, routingContextAddress, @@ -57,7 +59,8 @@ public CompletionStage connect( securityPlan, authToken, minVersion, - notificationConfig) + notificationConfig, + parentObservation) .thenApply(boltConnection -> { boltConnection = new ListeningBoltConnection(boltConnection, boltConnectionListener); boltConnectionListener.onOpen(boltConnection); diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/DisposableResultCursorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/DisposableResultCursorImpl.java index f0e3927d1a..81e8d7e0a0 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/DisposableResultCursorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/DisposableResultCursorImpl.java @@ -28,6 +28,7 @@ import org.neo4j.driver.Record; import org.neo4j.driver.async.ResultCursor; import org.neo4j.driver.internal.FailableCursor; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.summary.ResultSummary; public class DisposableResultCursorImpl implements ResultCursor, FailableCursor { @@ -96,14 +97,14 @@ boolean isDisposed() { } @Override - public CompletionStage discardAllFailureAsync() { + public CompletionStage discardAllFailureAsync(Observation parentObservation) { isDisposed = true; - return delegate.discardAllFailureAsync(); + return delegate.discardAllFailureAsync(parentObservation); } @Override - public CompletionStage pullAllFailureAsync() { - return delegate.pullAllFailureAsync(); + public CompletionStage pullAllFailureAsync(Observation parentObservation) { + return delegate.pullAllFailureAsync(parentObservation); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/ResultCursorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/ResultCursorImpl.java index b948b07894..4070a3f73b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/ResultCursorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/ResultCursorImpl.java @@ -17,6 +17,7 @@ package org.neo4j.driver.internal.cursor; import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeAsyncStarted; import static org.neo4j.driver.internal.types.InternalTypeSystem.TYPE_SYSTEM; import java.util.ArrayDeque; @@ -50,6 +51,9 @@ import org.neo4j.driver.internal.adaptedbolt.summary.DiscardSummary; import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.util.MetadataExtractor; @@ -77,6 +81,8 @@ public class ResultCursorImpl extends AbstractRecordStateResponseHandler private final ApiTelemetryWork apiTelemetryWork; private final CompletableFuture consumedFuture = new CompletableFuture<>(); private final Consumer databaseNameConsumer; + private final DriverObservationProvider observationProvider; + private final Class resultType; private RunSummary runSummary; private State state; @@ -90,6 +96,8 @@ public class ResultCursorImpl extends AbstractRecordStateResponseHandler private ResultSummary summary; private Throwable error; private boolean errorExposed; + private Observation pendingObservation; + private boolean observeBoltOnly; private enum State { READY, @@ -107,7 +115,9 @@ public ResultCursorImpl( boolean closeOnSummary, CompletableFuture beginFuture, Consumer databaseNameConsumer, - ApiTelemetryWork apiTelemetryWork) { + ApiTelemetryWork apiTelemetryWork, + DriverObservationProvider observationProvider, + Class resultType) { this.boltConnection = Objects.requireNonNull(boltConnection); this.legacyNotifications = new BoltProtocolVersion(5, 5).compareTo(boltConnection.protocolVersion()) > 0; updateRecordState(RecordState.REQUESTED); @@ -119,6 +129,8 @@ public ResultCursorImpl( this.beginFuture = beginFuture; this.apiTelemetryWork = apiTelemetryWork; this.databaseNameConsumer = Objects.requireNonNull(databaseNameConsumer); + this.observationProvider = Objects.requireNonNull(observationProvider); + this.resultType = Objects.requireNonNull(resultType); } public CompletionStage resultCursor() { @@ -130,9 +142,12 @@ public synchronized List keys() { return runSummary.keys(); } - @SuppressWarnings("DuplicatedCode") @Override public synchronized CompletionStage consumeAsync() { + return consumeAsync(null); + } + + private synchronized CompletionStage consumeAsync(Observation parentObservation) { if (apiCallInProgress) { var message = "API calls to result cursor must be sequential."; return CompletableFuture.failedStage(new ClientException( @@ -146,12 +161,23 @@ public synchronized CompletionStage consumeAsync() { CompletionStage summaryFt = switch (state) { case READY -> { + Observation consumeObservation; + Observation boltObservation; + if (parentObservation != null) { + consumeObservation = NoopObservation.getInstance(); + boltObservation = parentObservation; + } else { + consumeObservation = observationProvider + .resultConsume(resultType) + .start(); + boltObservation = consumeObservation; + } apiCallInProgress = true; summaryFuture = new CompletableFuture<>(); var future = summaryFuture; state = State.DISCARDING; boltConnection - .writeAndFlush(this, Messages.discard(runSummary.queryId(), -1)) + .writeAndFlush(this, Messages.discard(runSummary.queryId(), -1), boltObservation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); CompletableFuture summaryFuture; @@ -166,9 +192,16 @@ public synchronized CompletionStage consumeAsync() { summaryFuture.completeExceptionally(error); } }); - yield future; + yield observeAsyncStarted(consumeObservation, () -> future); } case STREAMING -> { + if (parentObservation != null) { + pendingObservation = parentObservation; + observeBoltOnly = true; + } else { + pendingObservation = observationProvider.resultConsume(resultType); + observeBoltOnly = false; + } apiCallInProgress = true; summaryFuture = new CompletableFuture<>(); yield summaryFuture; @@ -224,13 +257,15 @@ var record = records.poll(); // buffer is empty return switch (state) { case READY -> { + var nextObservation = + observationProvider.resultNext(resultType).start(); apiCallInProgress = true; recordFuture = new CompletableFuture<>(); var result = recordFuture; state = State.STREAMING; updateRecordState(RecordState.NO_RECORD); boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize), nextObservation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); CompletableFuture recordFuture; @@ -245,9 +280,11 @@ var record = records.poll(); recordFuture.completeExceptionally(error); } }); - yield result; + yield observeAsyncStarted(nextObservation, () -> result); } case STREAMING -> { + pendingObservation = observationProvider.resultNext(resultType); + observeBoltOnly = false; apiCallInProgress = true; recordFuture = new CompletableFuture<>(); yield recordFuture; @@ -288,13 +325,15 @@ var record = records.peek(); // buffer is empty return switch (state) { case READY -> { + var peekObservation = + observationProvider.resultPeek(resultType).start(); apiCallInProgress = true; peekFuture = new CompletableFuture<>(); var future = peekFuture; state = State.STREAMING; updateRecordState(RecordState.NO_RECORD); boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize), peekObservation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); if (error != null) { @@ -309,9 +348,11 @@ var record = records.peek(); recordFuture.completeExceptionally(error); } }); - yield future; + yield observeAsyncStarted(peekObservation, () -> future); } case STREAMING -> { + pendingObservation = observationProvider.resultPeek(resultType); + observeBoltOnly = false; apiCallInProgress = true; peekFuture = new CompletableFuture<>(); yield peekFuture; @@ -355,6 +396,8 @@ public synchronized CompletionStage singleAsync() { return switch (state) { case READY -> { if (records.isEmpty()) { + var singleObservation = + observationProvider.resultSingle(resultType).start(); apiCallInProgress = true; recordFuture = new CompletableFuture<>(); secondRecordFuture = new CompletableFuture<>(); @@ -374,7 +417,7 @@ public synchronized CompletionStage singleAsync() { state = State.STREAMING; updateRecordState(RecordState.NO_RECORD); boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize), singleObservation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); if (error != null) { @@ -393,7 +436,7 @@ public synchronized CompletionStage singleAsync() { secondRecordFuture.completeExceptionally(error); } }); - yield singleFuture; + yield observeAsyncStarted(singleObservation, () -> singleFuture); } else { // records is not empty and the state is READY, meaning the result is not exhausted yield CompletableFuture.failedStage( @@ -402,6 +445,8 @@ public synchronized CompletionStage singleAsync() { } } case STREAMING -> { + pendingObservation = observationProvider.resultSingle(resultType); + observeBoltOnly = false; apiCallInProgress = true; if (records.isEmpty()) { recordFuture = new CompletableFuture<>(); @@ -496,13 +541,14 @@ public synchronized CompletionStage> listAsync() { } return switch (state) { case READY -> { + var listObservation = observationProvider.resultList(resultType).start(); apiCallInProgress = true; recordsFuture = new CompletableFuture<>(); var future = recordsFuture; state = State.STREAMING; updateRecordState(RecordState.NO_RECORD); boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), -1)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), -1), listObservation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); CompletableFuture> recordsFuture; @@ -517,9 +563,11 @@ public synchronized CompletionStage> listAsync() { recordsFuture.completeExceptionally(error); } }); - yield future; + yield observeAsyncStarted(listObservation, () -> future); } case STREAMING -> { + pendingObservation = observationProvider.resultList(resultType); + observeBoltOnly = false; apiCallInProgress = true; recordsFuture = new CompletableFuture<>(); yield recordsFuture; @@ -601,6 +649,7 @@ var record = new InternalRecord(runSummary.keys(), fields); peekFuture = this.peekFuture; this.peekFuture = null; if (peekFuture != null) { + clearPendingObservation(); apiCallInProgress = false; records.add(record); } else { @@ -610,11 +659,13 @@ var record = new InternalRecord(runSummary.keys(), fields); secondRecordFuture = this.secondRecordFuture; if (recordFuture == null) { if (secondRecordFuture != null) { + clearPendingObservation(); apiCallInProgress = false; this.secondRecordFuture = null; } records.add(record); } else { + clearPendingObservation(); if (secondRecordFuture == null) { apiCallInProgress = false; } @@ -760,10 +811,11 @@ public void onPullSummary(PullSummary summary) { synchronized (this) { if (this.peekFuture != null) { // peek is pending, keep streaming + var observation = getAndClearPendingObservation(this.peekFuture); state = State.STREAMING; updateRecordState(RecordState.NO_RECORD); boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize), observation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); if (error != null) { @@ -780,10 +832,11 @@ public void onPullSummary(PullSummary summary) { }); } else if (this.recordFuture != null) { // next is pending, keep streaming + var observation = getAndClearPendingObservation(this.recordFuture); state = State.STREAMING; updateRecordState(RecordState.NO_RECORD); boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), fetchSize), observation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); if (error != null) { @@ -809,10 +862,11 @@ public void onPullSummary(PullSummary summary) { } else { if (this.recordsFuture != null) { // list is pending, stream all + var observation = getAndClearPendingObservation(this.recordsFuture); state = State.STREAMING; updateRecordState(RecordState.NO_RECORD); boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), -1)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), -1), observation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); if (error != null) { @@ -829,9 +883,10 @@ public void onPullSummary(PullSummary summary) { }); } else if (this.summaryFuture != null) { // consume is pending, discard all + var observation = getAndClearPendingObservation(this.summaryFuture); state = State.DISCARDING; boltConnection - .writeAndFlush(this, Messages.discard(runSummary.queryId(), -1)) + .writeAndFlush(this, Messages.discard(runSummary.queryId(), -1), observation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); CompletableFuture summaryFuture; @@ -1172,13 +1227,13 @@ public void onComplete() { } @Override - public synchronized CompletionStage discardAllFailureAsync() { - return consumeAsync().handle((summary, error) -> error); + public synchronized CompletionStage discardAllFailureAsync(Observation parentObservation) { + return consumeAsync(parentObservation).handle((summary, error) -> error); } @SuppressWarnings("DuplicatedCode") @Override - public CompletionStage pullAllFailureAsync() { + public CompletionStage pullAllFailureAsync(Observation parentObservation) { synchronized (this) { if (apiCallInProgress) { var message = "API calls to result cursor must be sequential."; @@ -1197,7 +1252,7 @@ public CompletionStage pullAllFailureAsync() { state = State.STREAMING; updateRecordState(RecordState.NO_RECORD); boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), -1)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), -1), parentObservation) .whenComplete((ignored, throwable) -> { var error = Futures.completionExceptionCause(throwable); CompletableFuture summaryFuture; @@ -1247,4 +1302,28 @@ private CompletionStage stageExposingError(T value) { } return CompletableFuture.completedStage(value); } + + private synchronized void clearPendingObservation() { + pendingObservation = null; + } + + private synchronized Observation getAndClearPendingObservation(CompletionStage observable) { + if (pendingObservation == null) { + return NoopObservation.getInstance(); + } else { + var observation = pendingObservation; + pendingObservation = null; + if (!observeBoltOnly) { + observation.start(); + observable.whenComplete((ignored, throwable) -> { + if (throwable != null) { + observation.error(Futures.completionExceptionCause(throwable)); + } + observation.stop(); + }); + } + observeBoltOnly = false; + return observation; + } + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursor.java index cc4ce9e7de..1d5010f2c9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursor.java @@ -21,15 +21,16 @@ import java.util.function.BiConsumer; import org.neo4j.driver.Record; import org.neo4j.driver.internal.FailableCursor; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.summary.ResultSummary; import org.reactivestreams.Subscription; public interface RxResultCursor extends Subscription, FailableCursor { List keys(); - void installRecordConsumer(BiConsumer recordConsumer); + void installRecordConsumer(BiConsumer recordConsumer, Observation observation); - CompletionStage summaryAsync(); + CompletionStage summaryAsync(Observation observation); boolean isDone(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java index fe4824e0f2..d5c934dd7a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java @@ -47,6 +47,8 @@ import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; import org.neo4j.driver.internal.adaptedbolt.summary.DiscardSummary; import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.util.MetadataExtractor; import org.neo4j.driver.summary.GqlStatusObject; @@ -108,6 +110,7 @@ public Optional databaseName() { private PullSummary pullSummary; private DiscardSummary discardSummary; private Throwable error; + private Observation recordsObservation; private enum State { READY, @@ -159,7 +162,7 @@ public boolean isDone() { } @Override - public void installRecordConsumer(BiConsumer recordConsumer) { + public void installRecordConsumer(BiConsumer recordConsumer, Observation observation) { Objects.requireNonNull(recordConsumer); if (summaryExposed) { throw newResultConsumedError(); @@ -168,6 +171,7 @@ public void installRecordConsumer(BiConsumer recordConsumer) synchronized (this) { if (this.recordConsumer == null) { this.recordConsumer = safeRecordConsumer(recordConsumer); + this.recordsObservation = Objects.requireNonNull(observation); log.trace("[%d] Record consumer installed", hashCode()); if (runError != null) { handleError(runError); @@ -192,7 +196,7 @@ public void request(long n) { var request = appendDemand(n); state = State.STREAMING; runnable = () -> boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), request)) + .writeAndFlush(this, Messages.pull(runSummary.queryId(), request), recordsObservation) .whenComplete((ignored, throwable) -> { throwable = Futures.completionExceptionCause(throwable); if (throwable != null) { @@ -217,7 +221,7 @@ public void cancel() { synchronized (this) { log.trace("[%d] Cancellation requested in %s state", hashCode(), state); switch (state) { - case READY -> runnable = setupDiscardRunnable(); + case READY -> runnable = setupDiscardRunnable(recordsObservation); case STREAMING -> discardPending = true; case DISCARDING, FAILED, SUCCEEDED -> {} } @@ -226,7 +230,7 @@ public void cancel() { } @Override - public CompletionStage summaryAsync() { + public CompletionStage summaryAsync(Observation observation) { var runnable = NOOP_RUNNABLE; synchronized (this) { log.trace("[%d] Summary requested in %s state", hashCode(), state); @@ -241,7 +245,7 @@ public CompletionStage summaryAsync() { handleError(runError); runnable = this::onComplete; } else { - runnable = setupDiscardRunnable(); + runnable = setupDiscardRunnable(observation); } } case STREAMING -> discardPending = true; @@ -285,7 +289,8 @@ public void onComplete() { } } }, - Messages.reset()) + Messages.reset(), + NoopObservation.getInstance()) .whenComplete((ignored, throwable) -> { throwable = Futures.completionExceptionCause(throwable); if (throwable != null) { @@ -354,17 +359,17 @@ public synchronized void onDiscardSummary(DiscardSummary summary) { } @Override - public synchronized CompletionStage discardAllFailureAsync() { + public synchronized CompletionStage discardAllFailureAsync(Observation parentObservation) { log.trace("[%d] Discard all requested", hashCode()); var summaryExposed = this.summaryExposed; var runErrorExposed = this.runErrorExposed; - return summaryAsync() + return summaryAsync(parentObservation) .thenApply(ignored -> (Throwable) null) .exceptionally(throwable -> runErrorExposed || summaryExposed ? null : throwable); } @Override - public synchronized CompletionStage pullAllFailureAsync() { + public synchronized CompletionStage pullAllFailureAsync(Observation parentObservation) { log.trace("[%d] Pull all failure requested", hashCode()); var unfinishedState = switch (state) { @@ -376,7 +381,7 @@ public synchronized CompletionStage pullAllFailureAsync() { new TransactionNestingException( "You cannot run another query or begin a new transaction in the same session before you've fully consumed the previous run result.")); } - return discardAllFailureAsync(); + return discardAllFailureAsync(parentObservation); } private synchronized long appendDemand(long n) { @@ -405,10 +410,10 @@ private synchronized void decrementDemand() { log.trace("[%d] Decremented demand, outstanding is %d", hashCode(), outstandingDemand); } - private synchronized Runnable setupDiscardRunnable() { + private synchronized Runnable setupDiscardRunnable(Observation observation) { state = State.DISCARDING; return () -> boltConnection - .writeAndFlush(this, Messages.discard(runSummary.queryId(), -1)) + .writeAndFlush(this, Messages.discard(runSummary.queryId(), -1), observation) .whenComplete((ignored, throwable) -> { throwable = Futures.completionExceptionCause(throwable); if (throwable != null) { @@ -427,7 +432,7 @@ private synchronized Runnable setupCompletionRunnableWithPullSummary() { discardPending = false; state = State.DISCARDING; runnable = () -> boltConnection - .writeAndFlush(this, Messages.discard(runSummary.queryId(), -1)) + .writeAndFlush(this, Messages.discard(runSummary.queryId(), -1), NoopObservation.getInstance()) .whenComplete((ignored, flushThrowable) -> { var error = Futures.completionExceptionCause(flushThrowable); if (error != null) { @@ -440,7 +445,10 @@ private synchronized Runnable setupCompletionRunnableWithPullSummary() { if (demand != 0) { state = State.STREAMING; runnable = () -> boltConnection - .writeAndFlush(this, Messages.pull(runSummary.queryId(), demand > 0 ? demand : -1)) + .writeAndFlush( + this, + Messages.pull(runSummary.queryId(), demand > 0 ? demand : -1), + recordsObservation) .whenComplete((ignored, flushThrowable) -> { var error = Futures.completionExceptionCause(flushThrowable); if (error != null) { 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 deleted file mode 100644 index ad9fc33fa6..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/ConnectionPoolMetricsListener.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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.bolt.connection.ListenerEvent; - -interface ConnectionPoolMetricsListener { - /** - * Invoked before a connection is creating. - */ - void beforeCreating(ListenerEvent listenerEvent); - - /** - * Invoked after a connection is created successfully. - */ - void afterCreated(ListenerEvent listenerEvent); - - /** - * Invoked after a connection is failed to create due to timeout, any kind of error. - */ - void afterFailedToCreate(); - - /** - * Invoked after a connection is closed. - */ - void afterClosed(); - - /** - * Invoked before acquiring or creating a connection. - * - * @param acquireEvent the event - */ - void beforeAcquiringOrCreating(ListenerEvent acquireEvent); - - /** - * Invoked after a connection is being acquired or created regardless weather it is successful or not. - */ - void afterAcquiringOrCreating(); - - /** - * Invoked after a connection is acquired or created successfully. - * - * @param acquireEvent the event - */ - void afterAcquiredOrCreated(ListenerEvent acquireEvent); - - /** - * Invoked after it is timed out to acquire or create a connection. - */ - void afterTimedOutToAcquireOrCreate(); - - /** - * After a connection is acquired from the pool. - * - * @param inUseEvent the event - */ - void acquired(ListenerEvent inUseEvent); - - /** - * After a connection is released back to pool. - * - * @param inUseEvent the event - */ - void released(ListenerEvent inUseEvent); -} 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 deleted file mode 100644 index 8112c0975c..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsListener.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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.bolt.connection.BoltServerAddress; -import org.neo4j.bolt.connection.ListenerEvent; -import org.neo4j.bolt.connection.MetricsListener; - -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, BoltServerAddress 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 deleted file mode 100644 index 903039bb12..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullMetricsProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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.bolt.connection.MetricsListener; -import org.neo4j.driver.Metrics; -import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.GqlStatusError; - -public enum DevNullMetricsProvider implements MetricsProvider { - INSTANCE; - - @Override - public Metrics metrics() { - // To outside users, we forbid access to the metrics API - var message = - "Driver metrics are not enabled. You need to enable driver metrics in driver configuration in order to access them."; - throw new ClientException( - GqlStatusError.UNKNOWN.getStatus(), - GqlStatusError.UNKNOWN.getStatusDescription(message), - "N/A", - message, - GqlStatusError.DIAGNOSTIC_RECORD, - null); - } - - @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 deleted file mode 100644 index 945237acf0..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullPoolMetricsListener.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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.bolt.connection.ListenerEvent; - -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/InternalMetrics.java b/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetrics.java deleted file mode 100644 index 5f55e20236..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetrics.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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 static java.lang.String.format; -import static java.util.Collections.unmodifiableCollection; - -import java.time.Clock; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.IntSupplier; -import org.neo4j.bolt.connection.BoltServerAddress; -import org.neo4j.bolt.connection.ListenerEvent; -import org.neo4j.bolt.connection.MetricsListener; -import org.neo4j.driver.ConnectionPoolMetrics; -import org.neo4j.driver.Logger; -import org.neo4j.driver.Logging; -import org.neo4j.driver.Metrics; - -final class InternalMetrics implements Metrics, MetricsListener { - private final Map connectionPoolMetrics; - private final Clock clock; - - @SuppressWarnings("deprecation") - private final Logger log; - - InternalMetrics(Clock clock, @SuppressWarnings("deprecation") Logging logging) { - Objects.requireNonNull(clock); - this.connectionPoolMetrics = new ConcurrentHashMap<>(); - this.clock = clock; - this.log = logging.getLog(getClass()); - } - - @Override - public void registerPoolMetrics( - String poolId, BoltServerAddress serverAddress, IntSupplier inUseSupplier, IntSupplier idleSupplier) { - this.connectionPoolMetrics.put( - poolId, new InternalConnectionPoolMetrics(poolId, serverAddress, inUseSupplier, idleSupplier)); - } - - @Override - public void removePoolMetrics(String id) { - this.connectionPoolMetrics.remove(id); - } - - @Override - public void beforeCreating(String poolId, ListenerEvent creatingEvent) { - poolMetrics(poolId).beforeCreating(creatingEvent); - } - - @Override - public void afterCreated(String poolId, ListenerEvent creatingEvent) { - poolMetrics(poolId).afterCreated(creatingEvent); - } - - @Override - public void afterFailedToCreate(String poolId) { - poolMetrics(poolId).afterFailedToCreate(); - } - - @Override - public void afterClosed(String poolId) { - poolMetrics(poolId).afterClosed(); - } - - @Override - public void beforeAcquiringOrCreating(String poolId, ListenerEvent acquireEvent) { - poolMetrics(poolId).beforeAcquiringOrCreating(acquireEvent); - } - - @Override - public void afterAcquiringOrCreating(String poolId) { - poolMetrics(poolId).afterAcquiringOrCreating(); - } - - @Override - public void afterAcquiredOrCreated(String poolId, ListenerEvent acquireEvent) { - poolMetrics(poolId).afterAcquiredOrCreated(acquireEvent); - } - - @Override - public void afterConnectionCreated(String poolId, ListenerEvent inUseEvent) { - poolMetrics(poolId).acquired(inUseEvent); - } - - @Override - public void afterConnectionReleased(String poolId, ListenerEvent inUseEvent) { - poolMetrics(poolId).released(inUseEvent); - } - - @Override - public void afterTimedOutToAcquireOrCreate(String poolId) { - poolMetrics(poolId).afterTimedOutToAcquireOrCreate(); - } - - @Override - public ListenerEvent createListenerEvent() { - return new TimeRecorderListenerEvent(clock); - } - - @Override - public Collection connectionPoolMetrics() { - return unmodifiableCollection(this.connectionPoolMetrics.values()); - } - - @Override - public String toString() { - return format("PoolMetrics=%s", connectionPoolMetrics); - } - - private ConnectionPoolMetricsListener poolMetrics(String poolId) { - var poolMetrics = (InternalConnectionPoolMetrics) this.connectionPoolMetrics.get(poolId); - if (poolMetrics == null) { - log.warn(format("Failed to find pool metrics with id `%s` in %s.", poolId, this.connectionPoolMetrics)); - return DevNullPoolMetricsListener.INSTANCE; - } - return poolMetrics; - } -} 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 deleted file mode 100644 index 6efa483d59..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetrics.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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 static java.lang.String.format; - -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.bolt.connection.BoltServerAddress; -import org.neo4j.bolt.connection.ListenerEvent; -import org.neo4j.driver.ConnectionPoolMetrics; - -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, - BoltServerAddress address, - IntSupplier inUseSupplier, - IntSupplier idleSupplier, - MeterRegistry registry) { - this(poolId, address, inUseSupplier, idleSupplier, registry, Tags.empty()); - } - - MicrometerConnectionPoolMetrics( - String poolId, - BoltServerAddress 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; - Iterable tags = - Tags.concat(initialTags, "address", String.format("%s:%d", address.connectionHost(), 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(); - var 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) { - var 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) { - var 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 deleted file mode 100644 index 17d14edd18..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetrics.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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.bolt.connection.BoltServerAddress; -import org.neo4j.bolt.connection.ListenerEvent; -import org.neo4j.bolt.connection.MetricsListener; -import org.neo4j.driver.ConnectionPoolMetrics; -import org.neo4j.driver.Metrics; - -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, BoltServerAddress 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) { - var 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 deleted file mode 100644 index 5e0202407c..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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.bolt.connection.MetricsListener; -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 deleted file mode 100644 index 465031fb28..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEvent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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 org.neo4j.bolt.connection.ListenerEvent; - -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/observation/BoltExchangeObservation.java similarity index 58% rename from driver/src/main/java/org/neo4j/driver/internal/metrics/TimeRecorderListenerEvent.java rename to driver/src/main/java/org/neo4j/driver/internal/observation/BoltExchangeObservation.java index cfc2a2bf30..c5292f0115 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/TimeRecorderListenerEvent.java +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/BoltExchangeObservation.java @@ -14,26 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.metrics; +package org.neo4j.driver.internal.observation; -import java.time.Clock; -import org.neo4j.bolt.connection.ListenerEvent; +public interface BoltExchangeObservation extends Observation { -final class TimeRecorderListenerEvent implements ListenerEvent { - private final Clock clock; - private long startTime; + @Override + BoltExchangeObservation start(); - TimeRecorderListenerEvent(Clock clock) { - this.clock = clock; - } + BoltExchangeObservation onWrite(String messageName); - @Override - public void start() { - startTime = clock.millis(); - } + BoltExchangeObservation onRecord(); + + BoltExchangeObservation onSummary(String messageName); @Override - public Long getSample() { - return clock.millis() - startTime; - } + BoltExchangeObservation error(Throwable error); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullListenerEvent.java b/driver/src/main/java/org/neo4j/driver/internal/observation/BoltHandleObservation.java similarity index 69% rename from driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullListenerEvent.java rename to driver/src/main/java/org/neo4j/driver/internal/observation/BoltHandleObservation.java index 0a9a3a812c..9a94a6e00e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/DevNullListenerEvent.java +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/BoltHandleObservation.java @@ -14,18 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.metrics; +package org.neo4j.driver.internal.observation; -import org.neo4j.bolt.connection.ListenerEvent; +public interface BoltHandleObservation extends Observation { + @Override + BoltHandleObservation start(); -enum DevNullListenerEvent implements ListenerEvent { - INSTANCE; + BoltHandleObservation onIgnored(); - @Override - public void start() {} + BoltHandleObservation onSummary(String messageName); @Override - public Long getSample() { - return 0L; - } + BoltHandleObservation error(Throwable error); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/DriverObservationProvider.java b/driver/src/main/java/org/neo4j/driver/internal/observation/DriverObservationProvider.java new file mode 100644 index 0000000000..29020bfa0c --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/DriverObservationProvider.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +import java.net.URI; +import java.util.List; +import java.util.function.BiConsumer; +import org.neo4j.bolt.connection.BoltProtocolVersion; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.BaseSession; +import org.neo4j.driver.observation.ObservationProvider; +import org.neo4j.driver.types.MapAccessor; + +public interface DriverObservationProvider extends ObservationProvider { + Observation sessionRun(Class sessionType, String query, MapAccessor parameters); + + Observation beginTransaction(Class transactionType); + + Observation sessionExecute(Class sessionType, AccessMode mode); + + Observation sessionClose(Class sessionType); + + Observation transactionRun(Class transactionType, String query, MapAccessor parameters); + + Observation transactionCommit(Class transactionType); + + Observation transactionRollback(Class transactionType); + + Observation transactionClose(Class transactionType); + + Observation resultPeek(Class resultType); + + Observation resultNext(Class resultType); + + Observation resultSingle(Class resultType); + + Observation resultList(Class resultType); + + Observation resultConsume(Class resultType); + + Observation resultRecords(Class resultType); + + Observation connectionPoolCreate(String id, URI uri, int maxSize); + + Observation connectionPoolClose(String id, URI uri); + + Observation pooledConnectionCreate(String id, URI uri); + + Observation pooledConnectionClose(String id, URI uri); + + Observation pooledConnectionAcquire(String id, URI uri); + + Observation pooledConnectionInUse(String id, URI uri); + + BoltHandleObservation boltHandle(List messageTypes); + + BoltExchangeObservation boltExchange( + String host, int port, BoltProtocolVersion boltVersion, BiConsumer setter); + + HttpExchangeObservation httpExchange(URI uri, String method, String uriTemplate, BiConsumer setter); + + Observation scopedObservation(); +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/HttpExchangeObservation.java b/driver/src/main/java/org/neo4j/driver/internal/observation/HttpExchangeObservation.java new file mode 100644 index 0000000000..2839aa2400 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/HttpExchangeObservation.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +import java.util.List; +import java.util.Map; + +public interface HttpExchangeObservation extends Observation { + + @Override + HttpExchangeObservation start(); + + HttpExchangeObservation onHeaders(Map> headers); + + HttpExchangeObservation onResponse(Response response); + + @Override + HttpExchangeObservation error(Throwable error); + + interface Response { + + int statusCode(); + + Map> headers(); + + String httpVersion(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/NoopBoltExchangeObservation.java b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopBoltExchangeObservation.java new file mode 100644 index 0000000000..0890e4caa9 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopBoltExchangeObservation.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +public final class NoopBoltExchangeObservation extends NoopObservation implements BoltExchangeObservation { + private static final NoopBoltExchangeObservation INSTANCE = new NoopBoltExchangeObservation(); + + public static NoopBoltExchangeObservation getInstance() { + return INSTANCE; + } + + private NoopBoltExchangeObservation() {} + + @Override + public BoltExchangeObservation start() { + return this; + } + + @Override + public BoltExchangeObservation onWrite(String messageName) { + return this; + } + + @Override + public BoltExchangeObservation onRecord() { + return this; + } + + @Override + public BoltExchangeObservation onSummary(String messageName) { + return this; + } + + @Override + public BoltExchangeObservation error(Throwable error) { + return this; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/NoopBoltHandleObservation.java b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopBoltHandleObservation.java new file mode 100644 index 0000000000..51b4dc9ade --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopBoltHandleObservation.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +public final class NoopBoltHandleObservation extends NoopObservation implements BoltHandleObservation { + private static final NoopBoltHandleObservation INSTANCE = new NoopBoltHandleObservation(); + + public static NoopBoltHandleObservation getInstance() { + return INSTANCE; + } + + private NoopBoltHandleObservation() {} + + @Override + public BoltHandleObservation start() { + return this; + } + + @Override + public BoltHandleObservation onIgnored() { + return this; + } + + @Override + public BoltHandleObservation onSummary(String messageName) { + return this; + } + + @Override + public BoltHandleObservation error(Throwable error) { + return this; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/NoopHttpExchangeObservation.java b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopHttpExchangeObservation.java new file mode 100644 index 0000000000..b359707ede --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopHttpExchangeObservation.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +import java.util.List; +import java.util.Map; + +public final class NoopHttpExchangeObservation extends NoopObservation implements HttpExchangeObservation { + private static final NoopHttpExchangeObservation INSTANCE = new NoopHttpExchangeObservation(); + + public static NoopHttpExchangeObservation getInstance() { + return INSTANCE; + } + + private NoopHttpExchangeObservation() {} + + @Override + public HttpExchangeObservation start() { + return this; + } + + @Override + public HttpExchangeObservation onHeaders(Map> headers) { + return this; + } + + @Override + public HttpExchangeObservation onResponse(Response response) { + return this; + } + + @Override + public HttpExchangeObservation error(Throwable error) { + return this; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservation.java b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservation.java new file mode 100644 index 0000000000..c92aabcc06 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservation.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +public class NoopObservation implements Observation { + private static final NoopObservation INSTANCE = new NoopObservation(); + + public static NoopObservation getInstance() { + return INSTANCE; + } + + public NoopObservation() {} + + @Override + public Observation start() { + return this; + } + + @Override + public Observation error(Throwable error) { + return this; + } + + @Override + public void stop() {} + + @Override + public Scope openScope() { + return NoopScope.INSTANCE; + } + + public static final class NoopScope implements Scope { + private static final NoopScope INSTANCE = new NoopScope(); + + public static NoopScope getInstance() { + return INSTANCE; + } + + @Override + public void close() {} + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservationProvider.java b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservationProvider.java new file mode 100644 index 0000000000..6cd4b29716 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/NoopObservationProvider.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +import java.net.URI; +import java.util.List; +import java.util.function.BiConsumer; +import org.neo4j.bolt.connection.BoltProtocolVersion; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.BaseSession; +import org.neo4j.driver.types.MapAccessor; + +public class NoopObservationProvider implements DriverObservationProvider { + private static final NoopObservationProvider INSTANCE = new NoopObservationProvider(); + + public static NoopObservationProvider getInstance() { + return INSTANCE; + } + + public NoopObservationProvider() {} + + @Override + public Observation sessionRun(Class sessionType, String query, MapAccessor parameters) { + return NoopObservation.getInstance(); + } + + @Override + public Observation beginTransaction(Class transactionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation sessionExecute(Class sessionType, AccessMode mode) { + return NoopObservation.getInstance(); + } + + @Override + public Observation sessionClose(Class sessionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation transactionRun(Class transactionType, String query, MapAccessor parameters) { + return NoopObservation.getInstance(); + } + + @Override + public Observation transactionCommit(Class transactionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation transactionRollback(Class transactionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation transactionClose(Class transactionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultPeek(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultNext(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultSingle(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultList(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultConsume(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultRecords(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation connectionPoolCreate(String id, URI uri, int maxSize) { + return NoopObservation.getInstance(); + } + + @Override + public Observation connectionPoolClose(String id, URI uri) { + return NoopObservation.getInstance(); + } + + @Override + public Observation pooledConnectionCreate(String id, URI uri) { + return NoopObservation.getInstance(); + } + + @Override + public Observation pooledConnectionClose(String id, URI uri) { + return NoopObservation.getInstance(); + } + + @Override + public Observation pooledConnectionAcquire(String id, URI uri) { + return NoopObservation.getInstance(); + } + + @Override + public Observation pooledConnectionInUse(String id, URI uri) { + return NoopObservation.getInstance(); + } + + @Override + public BoltHandleObservation boltHandle(List messageTypes) { + return NoopBoltHandleObservation.getInstance(); + } + + @Override + public BoltExchangeObservation boltExchange( + String host, int port, BoltProtocolVersion boltVersion, BiConsumer setter) { + return NoopBoltExchangeObservation.getInstance(); + } + + @Override + public HttpExchangeObservation httpExchange( + URI uri, String method, String uriTemplate, BiConsumer setter) { + return NoopHttpExchangeObservation.getInstance(); + } + + @Override + public Observation scopedObservation() { + return null; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/Observation.java b/driver/src/main/java/org/neo4j/driver/internal/observation/Observation.java new file mode 100644 index 0000000000..b3bb42f545 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/Observation.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +import reactor.util.context.Context; + +public interface Observation { + Observation start(); + + Observation error(Throwable error); + + void stop(); + + default Context writeReactiveContext(Context context) { + return context; + } + + Scope openScope(); + + interface Scope extends AutoCloseable { + @Override + void close(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/observation/util/ObservationUtil.java b/driver/src/main/java/org/neo4j/driver/internal/observation/util/ObservationUtil.java new file mode 100644 index 0000000000..c3bfe1c2ad --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/observation/util/ObservationUtil.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.util; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; +import org.neo4j.driver.internal.util.Futures; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public final class ObservationUtil { + public static void observe(Observation observation, Runnable runnable) { + observe(observation, () -> { + runnable.run(); + return null; + }); + } + + public static T observe(Observation observation, Supplier supplier) { + observation.start(); + try (var scope = observation.openScope()) { + return supplier.get(); + } catch (Throwable e) { + observation.error(e); + throw e; + } finally { + observation.stop(); + } + } + + public static T scoped( + DriverObservationProvider observationProvider, Observation observation, Supplier supplier) { + var scopedObservation = observationProvider.scopedObservation(); + if (scopedObservation == null || !scopedObservation.equals(observation)) { + try (var scope = observation.openScope()) { + return supplier.get(); + } + } else { + return supplier.get(); + } + } + + public static CompletionStage observeAsync(Observation observation, Supplier> supplier) { + observation.start(); + return observeAsyncStarted(observation, supplier); + } + + public static CompletionStage observeAsyncStarted( + Observation observation, Supplier> supplier) { + CompletionStage observed; + try (var scope = observation.openScope()) { + observed = supplier.get(); + } catch (Throwable e) { + observation.error(e); + observation.stop(); + throw e; + } + return observed.whenComplete((result, throwable) -> { + if (throwable != null) { + observation.error(Futures.completionExceptionCause(throwable)); + } + observation.stop(); + }); + } + + public static Publisher observeStreamsWithoutStart( + Observation observation, Publisher publisher, boolean stopOnCancel) { + if (publisher instanceof Mono mono) { + return mono.doOnError(observation::error) + .doOnCancel(() -> { + if (stopOnCancel) { + observation.stop(); + } + }) + .doOnNext(ignored -> observation.stop()); + } else { + return Flux.from(publisher) + .doOnError(observation::error) + .doOnCancel(() -> { + if (stopOnCancel) { + observation.stop(); + } + }) + .doOnComplete(observation::stop); + } + } + + public static Publisher observeStreams(Observation observation, Publisher publisher) { + return observeStreams(observation, publisher, true, false); + } + + public static Publisher observeStreams( + Observation observation, Publisher publisher, boolean startOnRequest, boolean stopOnCancel) { + if (!startOnRequest) { + observation.start(); + } + var started = new AtomicBoolean(); + if (publisher instanceof Mono mono) { + return mono.doOnRequest(ignored -> { + if (startOnRequest && !started.get()) { + observation.start(); + started.set(true); + } + }) + .doOnError(throwable -> { + observation.error(throwable); + observation.stop(); + }) + .doOnCancel(() -> { + if (stopOnCancel) { + observation.stop(); + } + }) + .doOnSuccess(ignored -> observation.stop()); + } else { + return Flux.from(publisher) + .doOnRequest(ignored -> { + if (startOnRequest && !started.get()) { + observation.start(); + started.set(true); + } + }) + .doOnError(observation::error) + .doOnCancel(() -> { + if (stopOnCancel) { + observation.stop(); + } + }) + .doOnComplete(observation::stop); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/AbstractReactiveSession.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/AbstractReactiveSession.java index 0b79278dd8..7f8e0c5079 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/AbstractReactiveSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/AbstractReactiveSession.java @@ -16,6 +16,7 @@ */ package org.neo4j.driver.internal.reactive; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; import static org.neo4j.driver.internal.reactive.RxUtils.createEmptyPublisher; import static org.neo4j.driver.internal.reactive.RxUtils.createSingleItemPublisher; @@ -27,6 +28,7 @@ import java.util.function.Function; import org.neo4j.bolt.connection.TelemetryApi; import org.neo4j.driver.AccessMode; +import org.neo4j.driver.BaseSession; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; @@ -36,6 +38,8 @@ import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.async.UnmanagedTransaction; import org.neo4j.driver.internal.cursor.RxResultCursor; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.reactivestreams.ReactiveResult; @@ -56,19 +60,20 @@ public AbstractReactiveSession(NetworkSession session) { protected abstract S createTransaction(UnmanagedTransaction unmanagedTransaction); - protected abstract Publisher closeTransaction(S transaction, boolean commit); + protected abstract Publisher closeTransaction(S transaction, boolean commit, Observation parentObservation); - Publisher doBeginTransaction(TransactionConfig config, ApiTelemetryWork apiTelemetryWork) { - return doBeginTransaction(config, null, apiTelemetryWork); + Publisher doBeginTransaction( + TransactionConfig config, ApiTelemetryWork apiTelemetryWork, Observation parentObservation) { + return doBeginTransaction(config, null, apiTelemetryWork, parentObservation); } @SuppressWarnings("DuplicatedCode") protected Publisher doBeginTransaction( - TransactionConfig config, String txType, ApiTelemetryWork apiTelemetryWork) { + TransactionConfig config, String txType, ApiTelemetryWork apiTelemetryWork, Observation parentObservation) { return createSingleItemPublisher( () -> { var txFuture = new CompletableFuture(); - session.beginTransactionAsync(config, txType, apiTelemetryWork) + session.beginTransactionAsync(config, txType, apiTelemetryWork, parentObservation) .whenComplete((tx, completionError) -> { if (tx != null) { txFuture.complete(createTransaction(tx)); @@ -80,16 +85,20 @@ protected Publisher doBeginTransaction( }, () -> new IllegalStateException( "Unexpected condition, begin transaction call has completed successfully with transaction being null"), - tx -> Mono.fromDirect(closeTransaction(tx, false)).subscribe()); + tx -> Mono.fromDirect(closeTransaction(tx, false, parentObservation)) + .subscribe()); } @SuppressWarnings("DuplicatedCode") private Publisher beginTransaction( - AccessMode mode, TransactionConfig config, ApiTelemetryWork apiTelemetryWork) { + AccessMode mode, + TransactionConfig config, + ApiTelemetryWork apiTelemetryWork, + Observation parentObservation) { return createSingleItemPublisher( () -> { var txFuture = new CompletableFuture(); - session.beginTransactionAsync(mode, config, apiTelemetryWork) + session.beginTransactionAsync(mode, config, apiTelemetryWork, parentObservation) .whenComplete((tx, completionError) -> { if (tx != null) { txFuture.complete(createTransaction(tx)); @@ -101,11 +110,17 @@ private Publisher beginTransaction( }, () -> new IllegalStateException( "Unexpected condition, begin transaction call has completed successfully with transaction being null"), - tx -> Mono.fromDirect(closeTransaction(tx, false)).subscribe()); + tx -> Mono.fromDirect(closeTransaction(tx, false, parentObservation)) + .subscribe()); } protected Publisher runTransaction( - AccessMode mode, Function> work, TransactionConfig config) { + AccessMode mode, + Function> work, + TransactionConfig config, + Class sessionType, + DriverObservationProvider observationProvider) { + var executeObservation = observationProvider.sessionExecute(sessionType, mode); work = work.andThen(publisher -> Flux.from(publisher).handle((value, sink) -> { if (value instanceof ReactiveResult) { var message = String.format( @@ -136,13 +151,15 @@ protected Publisher runTransaction( })); var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.MANAGED_TRANSACTION); + var repeatableWork = Flux.usingWhen( - beginTransaction(mode, config, apiTelemetryWork), - work, - tx -> closeTransaction(tx, true), - (tx, error) -> closeTransaction(tx, false), - (tx) -> closeTransaction(tx, false)); - return session.retryLogic().retryRx(repeatableWork); + beginTransaction(mode, config, apiTelemetryWork, executeObservation), + work, + tx -> closeTransaction(tx, true, executeObservation), + (tx, error) -> closeTransaction(tx, false, executeObservation), + (tx) -> closeTransaction(tx, false, executeObservation)) + .contextWrite(executeObservation::writeReactiveContext); + return observeStreams(executeObservation, session.retryLogic().retryRx(repeatableWork)); } private void releaseConnectionBeforeReturning(CompletableFuture returnFuture, Throwable completionError) { @@ -166,12 +183,16 @@ public Set lastBookmarks() { return session.lastBookmarks(); } - protected Publisher run(Query query, TransactionConfig config, Function cursorToResult) { + protected Publisher run( + Query query, + TransactionConfig config, + Function cursorToResult, + Observation parentObservation) { var cursorPublishFuture = new CompletableFuture(); var cursorReference = new AtomicReference(); return createSingleItemPublisher( - () -> runAsStage(query, config, cursorPublishFuture) + () -> runAsStage(query, config, cursorPublishFuture, parentObservation) .thenApply(cursor -> { cursorReference.set(cursor); return cursor; @@ -195,10 +216,13 @@ protected Publisher run(Query query, TransactionConfig config, Function runAsStage( - Query query, TransactionConfig config, CompletionStage finalStage) { + Query query, + TransactionConfig config, + CompletionStage finalStage, + Observation parentObservation) { CompletionStage cursorStage; try { - cursorStage = session.runRx(query, config, finalStage); + cursorStage = session.runRx(query, config, finalStage, parentObservation); } catch (Throwable t) { cursorStage = CompletableFuture.failedFuture(t); } @@ -233,7 +257,7 @@ private CompletionStage releaseConnectionAndRethrow(Throwable throwable) }); } - protected Publisher doClose() { - return createEmptyPublisher(session::closeAsync); + protected Publisher doClose(Observation parentObservation) { + return createEmptyPublisher(() -> session.closeAsync(parentObservation)); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/AbstractReactiveTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/AbstractReactiveTransaction.java index 1fc1fea469..546a2fcf7c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/AbstractReactiveTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/AbstractReactiveTransaction.java @@ -19,6 +19,7 @@ import static org.neo4j.driver.internal.reactive.RxUtils.createEmptyPublisher; import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.observation.Observation; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -29,23 +30,23 @@ protected AbstractReactiveTransaction(UnmanagedTransaction tx) { this.tx = tx; } - protected Publisher doCommit() { - return createEmptyPublisher(tx::commitAsync); + protected Publisher doCommit(Observation parentObservation) { + return createEmptyPublisher(() -> tx.commitAsync(parentObservation)); } - protected Publisher doRollback() { - return createEmptyPublisher(tx::rollbackAsync); + protected Publisher doRollback(Observation parentObservation) { + return createEmptyPublisher(() -> tx.rollbackAsync(parentObservation)); } - protected Publisher doClose() { - return close(false); + protected Publisher doClose(Observation parentObservation) { + return close(false, parentObservation); } protected Publisher doIsOpen() { return Mono.just(tx.isOpen()); } - public Publisher close(boolean commit) { - return createEmptyPublisher(() -> tx.closeAsync(commit)); + public Publisher close(boolean commit, Observation parentObservation) { + return createEmptyPublisher(() -> tx.closeAsync(commit, parentObservation)); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveResult.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveResult.java index fa54ecc61f..7e1b60ed18 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveResult.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveResult.java @@ -16,15 +16,18 @@ */ package org.neo4j.driver.internal.reactive; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; import static org.neo4j.driver.internal.util.ErrorUtil.newResultConsumedError; import static reactor.adapter.JdkFlowAdapter.publisherToFlowPublisher; import static reactor.core.publisher.FluxSink.OverflowStrategy.IGNORE; import java.util.List; +import java.util.Objects; import java.util.concurrent.Flow.Publisher; import java.util.function.BiConsumer; import org.neo4j.driver.Record; import org.neo4j.driver.internal.cursor.RxResultCursor; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.reactive.ReactiveResult; import org.neo4j.driver.summary.ResultSummary; @@ -34,9 +37,11 @@ public class InternalReactiveResult implements ReactiveResult { private final RxResultCursor cursor; + private final DriverObservationProvider observationProvider; - public InternalReactiveResult(RxResultCursor cursor) { + public InternalReactiveResult(RxResultCursor cursor, DriverObservationProvider observationProvider) { this.cursor = cursor; + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override @@ -46,30 +51,35 @@ public List keys() { @Override public Publisher records() { - return publisherToFlowPublisher(Flux.create( - sink -> { - if (cursor.isDone()) { - sink.error(newResultConsumedError()); - } else { - cursor.installRecordConsumer(createRecordConsumer(sink)); - sink.onCancel(cursor::cancel); - sink.onRequest(cursor::request); - } - }, - IGNORE)); + var recordsObservation = observationProvider.resultRecords(ReactiveResult.class); + return publisherToFlowPublisher(observeStreams( + recordsObservation, + Flux.create( + sink -> { + if (cursor.isDone()) { + sink.error(newResultConsumedError()); + } else { + cursor.installRecordConsumer(createRecordConsumer(sink), recordsObservation); + sink.onCancel(cursor::cancel); + sink.onRequest(cursor::request); + } + }, + IGNORE))); } @Override public Publisher consume() { + var consumeObservation = observationProvider.resultConsume(ReactiveResult.class); return publisherToFlowPublisher( - Mono.create(sink -> cursor.summaryAsync().whenComplete((summary, summaryCompletionError) -> { - var error = Futures.completionExceptionCause(summaryCompletionError); - if (summary != null) { - sink.success(summary); - } else { - sink.error(error); - } - }))); + observeStreams(consumeObservation, Mono.create(sink -> cursor.summaryAsync(consumeObservation) + .whenComplete((summary, summaryCompletionError) -> { + var error = Futures.completionExceptionCause(summaryCompletionError); + if (summary != null) { + sink.success(summary); + } else { + sink.error(error); + } + })))); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveSession.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveSession.java index aefb44b161..fcdbc1aa8f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveSession.java @@ -16,10 +16,12 @@ */ package org.neo4j.driver.internal.reactive; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; import static reactor.adapter.JdkFlowAdapter.flowPublisherToFlux; import static reactor.adapter.JdkFlowAdapter.publisherToFlowPublisher; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Flow.Publisher; import org.neo4j.bolt.connection.TelemetryApi; @@ -29,6 +31,8 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.reactive.ReactiveResult; import org.neo4j.driver.reactive.ReactiveSession; @@ -37,28 +41,36 @@ public class InternalReactiveSession extends AbstractReactiveSession implements ReactiveSession, BaseReactiveQueryRunner { - public InternalReactiveSession(NetworkSession session) { + private final DriverObservationProvider observationProvider; + + public InternalReactiveSession(NetworkSession session, DriverObservationProvider observationProvider) { super(session); + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override protected ReactiveTransaction createTransaction(UnmanagedTransaction unmanagedTransaction) { - return new InternalReactiveTransaction(unmanagedTransaction); + return new InternalReactiveTransaction(unmanagedTransaction, observationProvider); } @Override - protected org.reactivestreams.Publisher closeTransaction(ReactiveTransaction transaction, boolean commit) { - return ((InternalReactiveTransaction) transaction).close(commit); + protected org.reactivestreams.Publisher closeTransaction( + ReactiveTransaction transaction, boolean commit, Observation parentObservation) { + return ((InternalReactiveTransaction) transaction).close(commit, parentObservation); } @Override public Publisher beginTransaction(TransactionConfig config) { - return beginTransaction(config, null, new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION)); + var beginObservation = observationProvider.beginTransaction(ReactiveTransaction.class); + return publisherToFlowPublisher(observeStreams( + beginObservation, + beginTransaction( + config, null, new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION), beginObservation))); } - public Publisher beginTransaction( - TransactionConfig config, String txType, ApiTelemetryWork apiTelemetryWork) { - return publisherToFlowPublisher(doBeginTransaction(config, txType, apiTelemetryWork)); + public org.reactivestreams.Publisher beginTransaction( + TransactionConfig config, String txType, ApiTelemetryWork apiTelemetryWork, Observation parentObservation) { + return doBeginTransaction(config, txType, apiTelemetryWork, parentObservation); } @Override @@ -67,7 +79,9 @@ public Publisher executeRead( return publisherToFlowPublisher(runTransaction( AccessMode.READ, tx -> flowPublisherToFlux(callback.execute(new DelegatingReactiveTransactionContext(tx))), - config)); + config, + ReactiveSession.class, + observationProvider)); } @Override @@ -76,7 +90,9 @@ public Publisher executeWrite( return publisherToFlowPublisher(runTransaction( AccessMode.WRITE, tx -> flowPublisherToFlux(callback.execute(new DelegatingReactiveTransactionContext(tx))), - config)); + config, + ReactiveSession.class, + observationProvider)); } @Override @@ -86,7 +102,10 @@ public Publisher run(Query query) { @Override public Publisher run(Query query, TransactionConfig config) { - return publisherToFlowPublisher(run(query, config, InternalReactiveResult::new)); + var runObservation = observationProvider.sessionRun(ReactiveSession.class, query.text(), query.parameters()); + return publisherToFlowPublisher(observeStreams( + runObservation, + run(query, config, result -> new InternalReactiveResult(result, observationProvider), runObservation))); } @Override @@ -96,6 +115,7 @@ public Set lastBookmarks() { @Override public Publisher close() { - return publisherToFlowPublisher(doClose()); + var closeObservation = observationProvider.sessionClose(ReactiveSession.class); + return publisherToFlowPublisher(observeStreams(closeObservation, doClose(closeObservation))); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveTransaction.java index 067b2329d4..adf911ede4 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalReactiveTransaction.java @@ -16,62 +16,78 @@ */ package org.neo4j.driver.internal.reactive; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreamsWithoutStart; import static reactor.adapter.JdkFlowAdapter.publisherToFlowPublisher; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Flow.Publisher; import org.neo4j.driver.Query; import org.neo4j.driver.internal.async.UnmanagedTransaction; import org.neo4j.driver.internal.cursor.RxResultCursor; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.reactive.ReactiveResult; import org.neo4j.driver.reactive.ReactiveTransaction; import reactor.core.publisher.Mono; public class InternalReactiveTransaction extends AbstractReactiveTransaction implements ReactiveTransaction, BaseReactiveQueryRunner { - protected InternalReactiveTransaction(UnmanagedTransaction tx) { + private final DriverObservationProvider observationProvider; + + protected InternalReactiveTransaction(UnmanagedTransaction tx, DriverObservationProvider observationProvider) { super(tx); + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override @SuppressWarnings({"DuplicatedCode"}) public Publisher run(Query query) { + var runObservation = observationProvider + .transactionRun(ReactiveTransaction.class, query.text(), query.parameters()) + .start(); CompletionStage cursorStage; try { - cursorStage = tx.runRx(query); + cursorStage = tx.runRx(query, runObservation); } catch (Throwable t) { cursorStage = CompletableFuture.failedFuture(t); } - return publisherToFlowPublisher(Mono.fromCompletionStage(cursorStage) - .flatMap(cursor -> { - Mono publisher; - var runError = cursor.getRunError(); - if (runError != null) { - publisher = Mono.error(runError); - tx.markTerminated(runError); - } else { - publisher = Mono.just(cursor); - } - return publisher; - }) - .map(InternalReactiveResult::new)); + return publisherToFlowPublisher(observeStreamsWithoutStart( + runObservation, + Mono.fromCompletionStage(cursorStage) + .flatMap(cursor -> { + Mono publisher; + var runError = cursor.getRunError(); + if (runError != null) { + publisher = Mono.error(runError); + tx.markTerminated(runError); + } else { + publisher = Mono.just(cursor); + } + return publisher; + }) + .map(result -> new InternalReactiveResult(result, observationProvider)), + false)); } @Override public Publisher commit() { - return publisherToFlowPublisher(doCommit()); + var commitObservation = observationProvider.transactionCommit(ReactiveTransaction.class); + return publisherToFlowPublisher(observeStreams(commitObservation, doCommit(commitObservation))); } @Override public Publisher rollback() { - return publisherToFlowPublisher(doRollback()); + var rollbackObservation = observationProvider.transactionRollback(ReactiveTransaction.class); + return publisherToFlowPublisher(observeStreams(rollbackObservation, doRollback(rollbackObservation))); } @Override public Publisher close() { - return publisherToFlowPublisher(doClose()); + var closeObservation = observationProvider.transactionClose(ReactiveTransaction.class); + return publisherToFlowPublisher(observeStreams(closeObservation, doClose(closeObservation))); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveResult.java b/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveResult.java index 6496114ae6..ad628d3c0c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveResult.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveResult.java @@ -16,13 +16,16 @@ */ package org.neo4j.driver.internal.reactivestreams; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; import static org.neo4j.driver.internal.util.ErrorUtil.newResultConsumedError; import static reactor.core.publisher.FluxSink.OverflowStrategy.IGNORE; import java.util.List; +import java.util.Objects; import java.util.function.BiConsumer; import org.neo4j.driver.Record; import org.neo4j.driver.internal.cursor.RxResultCursor; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.reactivestreams.ReactiveResult; import org.neo4j.driver.summary.ResultSummary; @@ -33,9 +36,11 @@ public class InternalReactiveResult implements ReactiveResult { private final RxResultCursor cursor; + private final DriverObservationProvider observationProvider; - public InternalReactiveResult(RxResultCursor cursor) { + public InternalReactiveResult(RxResultCursor cursor, DriverObservationProvider observationProvider) { this.cursor = cursor; + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override @@ -45,29 +50,34 @@ public List keys() { @Override public Publisher records() { - return Flux.create( - sink -> { - if (cursor.isDone()) { - sink.error(newResultConsumedError()); - } else { - cursor.installRecordConsumer(createRecordConsumer(sink)); - sink.onCancel(cursor::cancel); - sink.onRequest(cursor::request); - } - }, - IGNORE); + var recordsObservation = observationProvider.resultRecords(ReactiveResult.class); + return observeStreams( + recordsObservation, + Flux.create( + sink -> { + if (cursor.isDone()) { + sink.error(newResultConsumedError()); + } else { + cursor.installRecordConsumer(createRecordConsumer(sink), recordsObservation); + sink.onCancel(cursor::cancel); + sink.onRequest(cursor::request); + } + }, + IGNORE)); } @Override public Publisher consume() { - return Mono.create(sink -> cursor.summaryAsync().whenComplete((summary, summaryCompletionError) -> { - var error = Futures.completionExceptionCause(summaryCompletionError); - if (summary != null) { - sink.success(summary); - } else { - sink.error(error); - } - })); + var consumeObservation = observationProvider.resultConsume(ReactiveResult.class); + return observeStreams(consumeObservation, Mono.create(sink -> cursor.summaryAsync(consumeObservation) + .whenComplete((summary, summaryCompletionError) -> { + var error = Futures.completionExceptionCause(summaryCompletionError); + if (summary != null) { + sink.success(summary); + } else { + sink.error(error); + } + }))); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveSession.java b/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveSession.java index 05006b09c7..00c8aad4de 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveSession.java @@ -16,7 +16,10 @@ */ package org.neo4j.driver.internal.reactivestreams; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; + import java.util.HashSet; +import java.util.Objects; import java.util.Set; import org.neo4j.bolt.connection.TelemetryApi; import org.neo4j.driver.AccessMode; @@ -25,6 +28,8 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.reactive.AbstractReactiveSession; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.reactivestreams.ReactiveResult; @@ -35,18 +40,22 @@ public class InternalReactiveSession extends AbstractReactiveSession implements ReactiveSession, BaseReactiveQueryRunner { - public InternalReactiveSession(NetworkSession session) { + private final DriverObservationProvider observationProvider; + + public InternalReactiveSession(NetworkSession session, DriverObservationProvider observationProvider) { super(session); + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override public ReactiveTransaction createTransaction(UnmanagedTransaction unmanagedTransaction) { - return new InternalReactiveTransaction(unmanagedTransaction); + return new InternalReactiveTransaction(unmanagedTransaction, observationProvider); } @Override - public Publisher closeTransaction(ReactiveTransaction transaction, boolean commit) { - return ((InternalReactiveTransaction) transaction).close(commit); + public Publisher closeTransaction( + ReactiveTransaction transaction, boolean commit, Observation parentObservation) { + return ((InternalReactiveTransaction) transaction).close(commit, parentObservation); } @Override @@ -56,21 +65,30 @@ public Publisher beginTransaction(TransactionConfig config) public Publisher beginTransaction( TransactionConfig config, String txType, ApiTelemetryWork apiTelemetryWork) { - return doBeginTransaction(config, txType, apiTelemetryWork); + var beginObservation = observationProvider.beginTransaction(ReactiveTransaction.class); + return observeStreams(beginObservation, doBeginTransaction(config, txType, apiTelemetryWork, beginObservation)); } @Override public Publisher executeRead( ReactiveTransactionCallback> callback, TransactionConfig config) { return runTransaction( - AccessMode.READ, tx -> callback.execute(new DelegatingReactiveTransactionContext(tx)), config); + AccessMode.READ, + tx -> callback.execute(new DelegatingReactiveTransactionContext(tx)), + config, + ReactiveSession.class, + observationProvider); } @Override public Publisher executeWrite( ReactiveTransactionCallback> callback, TransactionConfig config) { return runTransaction( - AccessMode.WRITE, tx -> callback.execute(new DelegatingReactiveTransactionContext(tx)), config); + AccessMode.WRITE, + tx -> callback.execute(new DelegatingReactiveTransactionContext(tx)), + config, + ReactiveSession.class, + observationProvider); } @Override @@ -80,7 +98,10 @@ public Publisher run(Query query) { @Override public Publisher run(Query query, TransactionConfig config) { - return run(query, config, InternalReactiveResult::new); + var runObservation = observationProvider.sessionRun(ReactiveSession.class, query.text(), query.parameters()); + Publisher publisher = + run(query, config, result -> new InternalReactiveResult(result, observationProvider), runObservation); + return observeStreams(runObservation, publisher); } @Override @@ -90,6 +111,7 @@ public Set lastBookmarks() { @Override public Publisher close() { - return doClose(); + var closeObservation = observationProvider.sessionClose(ReactiveSession.class); + return observeStreams(closeObservation, doClose(closeObservation)); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveTransaction.java index ff25c13008..14139935b6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactivestreams/InternalReactiveTransaction.java @@ -16,11 +16,16 @@ */ package org.neo4j.driver.internal.reactivestreams; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreams; +import static org.neo4j.driver.internal.observation.util.ObservationUtil.observeStreamsWithoutStart; + +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.neo4j.driver.Query; import org.neo4j.driver.internal.async.UnmanagedTransaction; import org.neo4j.driver.internal.cursor.RxResultCursor; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.reactive.AbstractReactiveTransaction; import org.neo4j.driver.reactivestreams.ReactiveResult; import org.neo4j.driver.reactivestreams.ReactiveTransaction; @@ -29,33 +34,42 @@ public class InternalReactiveTransaction extends AbstractReactiveTransaction implements ReactiveTransaction, BaseReactiveQueryRunner { - protected InternalReactiveTransaction(UnmanagedTransaction tx) { + private final DriverObservationProvider observationProvider; + + protected InternalReactiveTransaction(UnmanagedTransaction tx, DriverObservationProvider observationProvider) { super(tx); + this.observationProvider = Objects.requireNonNull(observationProvider); } @Override @SuppressWarnings({"DuplicatedCode"}) public Publisher run(Query query) { + var runObservation = observationProvider + .transactionRun(ReactiveTransaction.class, query.text(), query.parameters()) + .start(); CompletionStage cursorStage; try { - cursorStage = tx.runRx(query); + cursorStage = tx.runRx(query, runObservation); } catch (Throwable t) { cursorStage = CompletableFuture.failedFuture(t); } - return Mono.fromCompletionStage(cursorStage) - .flatMap(cursor -> { - Mono publisher; - var runError = cursor.getRunError(); - if (runError != null) { - publisher = Mono.error(runError); - tx.markTerminated(runError); - } else { - publisher = Mono.just(cursor); - } - return publisher; - }) - .map(InternalReactiveResult::new); + return observeStreamsWithoutStart( + runObservation, + Mono.fromCompletionStage(cursorStage) + .flatMap(cursor -> { + Mono publisher; + var runError = cursor.getRunError(); + if (runError != null) { + publisher = Mono.error(runError); + tx.markTerminated(runError); + } else { + publisher = Mono.just(cursor); + } + return publisher; + }) + .map(result -> new InternalReactiveResult(result, observationProvider)), + false); } /** @@ -73,17 +87,20 @@ public Publisher terminate() { @Override public Publisher commit() { - return doCommit(); + var commitObservation = observationProvider.transactionCommit(ReactiveTransaction.class); + return observeStreams(commitObservation, doCommit(commitObservation)); } @Override public Publisher rollback() { - return doRollback(); + var rollbackObservation = observationProvider.transactionRollback(ReactiveTransaction.class); + return observeStreams(rollbackObservation, doRollback(rollbackObservation)); } @Override public Publisher close() { - return doClose(); + var closeObservation = observationProvider.transactionClose(ReactiveTransaction.class); + return observeStreams(closeObservation, doClose(closeObservation)); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/svm/MicrometerSubstitutions.java b/driver/src/main/java/org/neo4j/driver/internal/svm/MicrometerSubstitutions.java deleted file mode 100644 index 3671caf404..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/svm/MicrometerSubstitutions.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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.svm; - -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; -import java.time.Clock; -import org.neo4j.driver.Config; -import org.neo4j.driver.MetricsAdapter; -import org.neo4j.driver.internal.DriverFactory; -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; - -@TargetClass(DriverFactory.class) -final class Target_org_neo4j_driver_internal_DriverFactory { - - /** - * Substitutes metrics adapter in such a way that it falls back to off when Micrometer is not available. - * - * @param config Drivers config - * @param clock Clock to use - * @return A metrics provider, never null - */ - @Substitute - @SuppressWarnings({"ProtectedMemberInFinalClass", "deprecation"}) - protected static MetricsProvider getOrCreateMetricsProvider(Config config, Clock clock) { - var metricsAdapter = config.metricsAdapter(); - if (metricsAdapter == null) { - metricsAdapter = config.isMetricsEnabled() ? MetricsAdapter.DEFAULT : MetricsAdapter.DEV_NULL; - } - switch (metricsAdapter) { - case DEV_NULL -> { - return DevNullMetricsProvider.INSTANCE; - } - case DEFAULT -> { - return new InternalMetricsProvider(clock, config.logging()); - } - case MICROMETER -> { - try { - @SuppressWarnings("unused") - var metricsClass = Class.forName("io.micrometer.core.instrument.Metrics"); - return MicrometerMetricsProvider.forGlobalRegistry(); - } catch (ClassNotFoundException e) { - return DevNullMetricsProvider.INSTANCE; - } - } - } - throw new IllegalStateException("Unknown or unsupported MetricsAdapter: " + metricsAdapter); - } -} - -class MicrometerSubstitutions {} diff --git a/driver/src/main/java/org/neo4j/driver/observation/ObservationProvider.java b/driver/src/main/java/org/neo4j/driver/observation/ObservationProvider.java new file mode 100644 index 0000000000..8d652a05bc --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/observation/ObservationProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation; + +import org.neo4j.driver.util.Preview; + +/** + * An observation provider that is used by the driver to manage observations. + *

+ * Implementations of this interface are shipped as separate modules that should be used with the driver. This + * interface MUST NOT be implemented directly. + * + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface ObservationProvider {} diff --git a/driver/src/test/java/org/neo4j/driver/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/ConfigTest.java index 1ba4246217..f4f4c2adc4 100644 --- a/driver/src/test/java/org/neo4j/driver/ConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/ConfigTest.java @@ -43,7 +43,9 @@ import org.neo4j.driver.internal.logging.DevNullLogging; import org.neo4j.driver.internal.logging.JULogging; import org.neo4j.driver.internal.logging.Slf4jLogging; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.net.ServerAddressResolver; +import org.neo4j.driver.observation.ObservationProvider; import org.neo4j.driver.testutil.TestUtil; class ConfigTest { @@ -353,30 +355,6 @@ void shouldErrorWithInvalidUserAgent() { () -> Config.builder().withUserAgent("").build()); } - @Test - void shouldNotHaveMeterRegistryByDefault() { - var config = Config.builder().build(); - var metricsAdapter = config.metricsAdapter(); - - assertEquals(MetricsAdapter.DEV_NULL, metricsAdapter); - assertFalse(config.isMetricsEnabled()); - } - - @Test - void shouldNotAcceptNullMeterRegistry() { - var builder = Config.builder(); - assertThrows(NullPointerException.class, () -> builder.withMetricsAdapter(null)); - } - - @Test - void shouldSetMetricsAdapter() { - var config = Config.builder().withMetricsAdapter(MetricsAdapter.DEFAULT).build(); - var metricsAdapter = config.metricsAdapter(); - - assertEquals(MetricsAdapter.DEFAULT, metricsAdapter); - assertTrue(config.isMetricsEnabled()); - } - @Test void shouldSetRoutingTablePurgeDelayMillis() { // GIVEN @@ -422,10 +400,8 @@ void shouldSerialize() throws Exception { .withoutEncryption() .withTrustStrategy(Config.TrustStrategy.trustCustomCertificateSignedBy(new File("doesntMatter"))) .withUserAgent("user-agent") - .withDriverMetrics() .withRoutingTablePurgeDelay(50000, TimeUnit.MILLISECONDS) .withLeakedSessionsLogging() - .withMetricsAdapter(MetricsAdapter.MICROMETER) .withNotificationConfig(NotificationConfig.defaultConfig() .enableMinimumSeverity(NotificationSeverity.WARNING) .disableCategories( @@ -455,8 +431,6 @@ void shouldSerialize() throws Exception { config.trustStrategy().revocationCheckingStrategy(), verify.trustStrategy().revocationCheckingStrategy()); assertEquals(config.userAgent(), verify.userAgent()); - assertEquals(config.isMetricsEnabled(), verify.isMetricsEnabled()); - assertEquals(config.metricsAdapter(), verify.metricsAdapter()); assertEquals(config.maxTransactionRetryTimeMillis(), verify.maxTransactionRetryTimeMillis()); assertEquals(config.logLeakedSessions(), verify.logLeakedSessions()); assertEquals( @@ -566,4 +540,26 @@ void shouldSetDisabledNotificationClassifications() { assertEquals(Set.of(NotificationClassification.SECURITY), config.disabledNotificationClassifications()); } + + @Test + void shouldHaveNoObservationProviderByDefault() { + assertTrue(Config.defaultConfig().observationProvider().isEmpty()); + } + + @Test + void shouldSetObservationProvider() { + var observationProvider = mock(DriverObservationProvider.class); + var config = + Config.builder().withObservationProvider(observationProvider).build(); + + assertEquals(observationProvider, config.observationProvider().orElse(null)); + } + + @Test + void shouldRejectUnknownObservationProvider() { + var observationProvider = mock(ObservationProvider.class); + + assertThrows( + IllegalArgumentException.class, () -> Config.builder().withObservationProvider(observationProvider)); + } } diff --git a/driver/src/test/java/org/neo4j/driver/ParametersTest.java b/driver/src/test/java/org/neo4j/driver/ParametersTest.java index ce7fdffca1..e069edda86 100644 --- a/driver/src/test/java/org/neo4j/driver/ParametersTest.java +++ b/driver/src/test/java/org/neo4j/driver/ParametersTest.java @@ -42,6 +42,7 @@ import org.neo4j.driver.internal.InternalSession; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.retry.RetryLogic; class ParametersTest { @@ -117,7 +118,8 @@ private Session mockedSession() { null, false, mock(AuthTokenManager.class), - mock()); - return new InternalSession(session); + mock(), + NoopObservationProvider.getInstance()); + return new InternalSession(session, NoopObservationProvider.getInstance()); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/MetricsIT.java b/driver/src/test/java/org/neo4j/driver/integration/MetricsIT.java deleted file mode 100644 index a4b497cb74..0000000000 --- a/driver/src/test/java/org/neo4j/driver/integration/MetricsIT.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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 static org.junit.jupiter.api.Assertions.assertEquals; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.neo4j.driver.Config; -import org.neo4j.driver.Driver; -import org.neo4j.driver.MetricsAdapter; -import org.neo4j.driver.testutil.DatabaseExtension; -import org.neo4j.driver.testutil.ParallelizableIT; - -@ParallelizableIT -class MetricsIT { - @RegisterExtension - static final DatabaseExtension neo4j = new DatabaseExtension(); - - private final MeterRegistry meterRegistry = Metrics.globalRegistry; - private final long fetchSize = 5; - - private Driver driver; - - @BeforeEach - void createDriver() { - Metrics.addRegistry(new SimpleMeterRegistry()); - var config = Config.builder() - .withFetchSize(fetchSize) - .withMetricsAdapter(MetricsAdapter.MICROMETER) - .build(); - driver = neo4j.customDriver(config); - } - - @AfterEach - void closeDriver() { - driver.close(); - } - - @Test - void driverMetricsUpdatedWithDriverUse() { - try (var session = driver.session()) { - var result = session.run("UNWIND range(1, $limit) AS x RETURN x", Map.of("limit", fetchSize + 1)); - // assert in use - var acquisitionTimer = - meterRegistry.get("neo4j.driver.connections.acquisition").timer(); - var creationTimer = - meterRegistry.get("neo4j.driver.connections.creation").timer(); - var usageTimer = meterRegistry.get("neo4j.driver.connections.usage").timer(); - assertEquals(1, acquisitionTimer.count()); - assertEquals(1, creationTimer.count()); - assertEquals(0, usageTimer.count()); - - result.consume(); - // todo chain close futures to fix this - Thread.sleep(1000); - // assert released - assertEquals(1, acquisitionTimer.count()); - assertEquals(1, creationTimer.count()); - assertEquals(1, usageTimer.count()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } -} diff --git a/driver/src/test/java/org/neo4j/driver/integration/UnmanagedTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/UnmanagedTransactionIT.java index 74517bf9ef..36d97c402a 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/UnmanagedTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/UnmanagedTransactionIT.java @@ -42,6 +42,7 @@ import org.neo4j.driver.internal.InternalDriver; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.observation.NoopObservation; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.testutil.DatabaseExtension; import org.neo4j.driver.testutil.ParallelizableIT; @@ -61,7 +62,7 @@ void setUp() { @AfterEach void tearDown() { - session.closeAsync(); + session.closeAsync(NoopObservation.getInstance()); } private UnmanagedTransaction beginTransaction() { @@ -70,24 +71,28 @@ private UnmanagedTransaction beginTransaction() { private UnmanagedTransaction beginTransaction(NetworkSession session) { var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION); - return await(session.beginTransactionAsync(TransactionConfig.empty(), apiTelemetryWork)); + return await(session.beginTransactionAsync( + TransactionConfig.empty(), apiTelemetryWork, NoopObservation.getInstance())); } private ResultCursor sessionRun(NetworkSession session, Query query) { - return await(session.runAsync(query, TransactionConfig.empty())); + return await( + session.runAsync(query, TransactionConfig.empty(), NoopObservation.getInstance(), ResultCursor.class)); } private void txRun(UnmanagedTransaction tx, String query) { - await(tx.runAsync(new Query(query))); + await(tx.runAsync(new Query(query), NoopObservation.getInstance(), ResultCursor.class)); } @Test void shouldDoNothingWhenCommittedSecondTime() { var tx = beginTransaction(); - assertNull(await(tx.commitAsync())); + assertNull(await(tx.commitAsync(NoopObservation.getInstance()))); - assertTrue(tx.commitAsync().toCompletableFuture().isDone()); + assertTrue(tx.commitAsync(NoopObservation.getInstance()) + .toCompletableFuture() + .isDone()); assertFalse(tx.isOpen()); } @@ -95,9 +100,9 @@ void shouldDoNothingWhenCommittedSecondTime() { void shouldFailToCommitAfterRollback() { var tx = beginTransaction(); - assertNull(await(tx.rollbackAsync())); + assertNull(await(tx.rollbackAsync(NoopObservation.getInstance()))); - var e = assertThrows(ClientException.class, () -> await(tx.commitAsync())); + var e = assertThrows(ClientException.class, () -> await(tx.commitAsync(NoopObservation.getInstance()))); assertEquals("Can't commit, transaction has been rolled back", e.getMessage()); assertFalse(tx.isOpen()); } @@ -108,7 +113,8 @@ void shouldFailToCommitAfterTermination() { tx.markTerminated(null); - var e = assertThrows(TransactionTerminatedException.class, () -> await(tx.commitAsync())); + var e = assertThrows( + TransactionTerminatedException.class, () -> await(tx.commitAsync(NoopObservation.getInstance()))); assertThat(e.getMessage(), startsWith("Transaction can't be committed")); } @@ -116,9 +122,11 @@ void shouldFailToCommitAfterTermination() { void shouldDoNothingWhenRolledBackSecondTime() { var tx = beginTransaction(); - assertNull(await(tx.rollbackAsync())); + assertNull(await(tx.rollbackAsync(NoopObservation.getInstance()))); - assertTrue(tx.rollbackAsync().toCompletableFuture().isDone()); + assertTrue(tx.rollbackAsync(NoopObservation.getInstance()) + .toCompletableFuture() + .isDone()); assertFalse(tx.isOpen()); } @@ -126,9 +134,9 @@ void shouldDoNothingWhenRolledBackSecondTime() { void shouldFailToRollbackAfterCommit() { var tx = beginTransaction(); - assertNull(await(tx.commitAsync())); + assertNull(await(tx.commitAsync(NoopObservation.getInstance()))); - var e = assertThrows(ClientException.class, () -> await(tx.rollbackAsync())); + var e = assertThrows(ClientException.class, () -> await(tx.rollbackAsync(NoopObservation.getInstance()))); assertEquals("Can't rollback, transaction has been committed", e.getMessage()); assertFalse(tx.isOpen()); } @@ -139,7 +147,7 @@ void shouldRollbackAfterTermination() { tx.markTerminated(null); - assertNull(await(tx.rollbackAsync())); + assertNull(await(tx.rollbackAsync(NoopObservation.getInstance()))); assertFalse(tx.isOpen()); } @@ -162,14 +170,16 @@ void shouldBePossibleToRunMoreTransactionsAfterOneIsTerminated() { var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION); // commit should fail, make session forget about this transaction and release the connection to the pool - var e = assertThrows(TransactionTerminatedException.class, () -> await(tx1.commitAsync())); + var e = assertThrows( + TransactionTerminatedException.class, () -> await(tx1.commitAsync(NoopObservation.getInstance()))); assertThat(e.getMessage(), startsWith("Transaction can't be committed")); - await(session.beginTransactionAsync(TransactionConfig.empty(), apiTelemetryWork) - .thenCompose(tx -> tx.runAsync(new Query("CREATE (:Node {id: 42})")) + await(session.beginTransactionAsync(TransactionConfig.empty(), apiTelemetryWork, NoopObservation.getInstance()) + .thenCompose(tx -> tx.runAsync( + new Query("CREATE (:Node {id: 42})"), NoopObservation.getInstance(), ResultCursor.class) .thenCompose(ResultCursor::consumeAsync) .thenApply(ignore -> tx)) - .thenCompose(UnmanagedTransaction::commitAsync)); + .thenCompose(tx -> tx.commitAsync(NoopObservation.getInstance()))); assertEquals(1, countNodesWithId(42)); } diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/InternalReactiveSessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/InternalReactiveSessionIT.java index 3a6b86421c..982243afc2 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/InternalReactiveSessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/InternalReactiveSessionIT.java @@ -27,6 +27,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.bolt.connection.TelemetryApi; import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.internal.observation.NoopObservation; import org.neo4j.driver.internal.reactive.InternalReactiveSession; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; @@ -59,7 +60,8 @@ void shouldAcceptTxTypeWhenAvailable(String txType) { // GIVEN var txConfig = TransactionConfig.empty(); var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION); - var txMono = Mono.fromDirect(flowPublisherToFlux(session.beginTransaction(txConfig, txType, apiTelemetryWork))); + var txMono = Mono.fromDirect( + session.beginTransaction(txConfig, txType, apiTelemetryWork, NoopObservation.getInstance())); Function> txUnit = tx -> Mono.fromDirect(flowPublisherToFlux(tx.run("RETURN 1"))) .flatMap(result -> Mono.fromDirect(flowPublisherToFlux(result.consume()))); diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/ReactiveSessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/ReactiveSessionIT.java index b55db08815..29ceba41e5 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/ReactiveSessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/ReactiveSessionIT.java @@ -23,6 +23,7 @@ import static reactor.adapter.JdkFlowAdapter.flowPublisherToFlux; import static reactor.adapter.JdkFlowAdapter.publisherToFlowPublisher; +import java.net.URI; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -33,6 +34,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Flow; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; @@ -41,13 +43,16 @@ import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.driver.Config; -import org.neo4j.driver.ConnectionPoolMetrics; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.NoopObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.reactive.ReactiveResult; import org.neo4j.driver.reactive.ReactiveSession; import org.neo4j.driver.testutil.DatabaseExtension; +import org.neo4j.driver.testutil.LoggingUtil; import org.neo4j.driver.testutil.ParallelizableIT; import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; @@ -82,7 +87,30 @@ void shouldErrorWhenReactiveResultIsReturned(Function()); - var config = Config.builder().withDriverMetrics().build(); + var inUse = new AtomicInteger(); + var observationProvider = new NoopObservationProvider() { + @Override + public Observation pooledConnectionInUse(String id, URI uri) { + return new NoopObservation() { + @Override + public Observation start() { + inUse.incrementAndGet(); + return super.start(); + } + + @Override + public void stop() { + inUse.decrementAndGet(); + super.stop(); + } + }; + } + }; + @SuppressWarnings("deprecation") + var config = Config.builder() + .withLogging(LoggingUtil.boltLogging(messages)) + .withObservationProvider(observationProvider) + .build(); try (var driver = neo4j.customDriver(config)) { // verify the database is available as runs may not report errors due to the subscription cancellation driver.verifyConnectivity(); @@ -130,18 +158,15 @@ public void onComplete() { var timeout = Instant.now().plus(5, ChronoUnit.MINUTES); var totalInUseConnections = -1; while (Instant.now().isBefore(timeout)) { - totalInUseConnections = driver.metrics().connectionPoolMetrics().stream() - .map(ConnectionPoolMetrics::inUse) - .mapToInt(Integer::intValue) - .sum(); + totalInUseConnections = inUse.get(); if (totalInUseConnections == 0) { return; } Thread.sleep(100); } fail(String.format( - "not all connections have been released\n%d are still in use\nlatest metrics: %s\nmessage log: \n%s", - totalInUseConnections, driver.metrics().connectionPoolMetrics(), String.join("\n", messages))); + "not all connections have been released\n%d are still in use\nmessage log: \n%s", + totalInUseConnections, String.join("\n", messages))); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/ReactiveStreamsSessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/ReactiveStreamsSessionIT.java index 177868cf4d..81e75daaa9 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/ReactiveStreamsSessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/ReactiveStreamsSessionIT.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V4; +import java.net.URI; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -30,6 +31,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; @@ -38,9 +40,11 @@ import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.neo4j.driver.Config; -import org.neo4j.driver.ConnectionPoolMetrics; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.NoopObservationProvider; +import org.neo4j.driver.internal.observation.Observation; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.reactivestreams.ReactiveResult; import org.neo4j.driver.reactivestreams.ReactiveSession; @@ -81,10 +85,29 @@ void shouldErrorWhenReactiveResultIsReturned(Function()); + var inUse = new AtomicInteger(); + var observationProvider = new NoopObservationProvider() { + @Override + public Observation pooledConnectionInUse(String id, URI uri) { + return new NoopObservation() { + @Override + public Observation start() { + inUse.incrementAndGet(); + return super.start(); + } + + @Override + public void stop() { + inUse.decrementAndGet(); + super.stop(); + } + }; + } + }; @SuppressWarnings("deprecation") var config = Config.builder() - .withDriverMetrics() .withLogging(LoggingUtil.boltLogging(messages)) + .withObservationProvider(observationProvider) .build(); try (var driver = neo4j.customDriver(config)) { // verify the database is available as runs may not report errors due to the subscription cancellation @@ -125,18 +148,15 @@ protected void hookOnNext(@NonNull ReactiveResult result) { var timeout = Instant.now().plus(5, ChronoUnit.MINUTES); var totalInUseConnections = -1; while (Instant.now().isBefore(timeout)) { - totalInUseConnections = driver.metrics().connectionPoolMetrics().stream() - .map(ConnectionPoolMetrics::inUse) - .mapToInt(Integer::intValue) - .sum(); + totalInUseConnections = inUse.get(); if (totalInUseConnections == 0) { return; } Thread.sleep(100); } fail(String.format( - "not all connections have been released\n%d are still in use\nlatest metrics: %s\nmessage log: \n%s", - totalInUseConnections, driver.metrics().connectionPoolMetrics(), String.join("\n", messages))); + "not all connections have been released\n%d are still in use\nmessage log: \n%s", + totalInUseConnections, String.join("\n", messages))); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/CustomSecurityPlanTest.java b/driver/src/test/java/org/neo4j/driver/internal/CustomSecurityPlanTest.java index 45546650ef..40a8f8d0a0 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/CustomSecurityPlanTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/CustomSecurityPlanTest.java @@ -29,7 +29,6 @@ import org.mockito.Mockito; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; -import org.neo4j.driver.internal.metrics.MetricsProvider; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.security.StaticAuthTokenManager; @@ -66,13 +65,10 @@ private static class SecurityPlanCapturingDriverFactory extends DriverFactory { @Override protected InternalDriver createDriver( - BoltSecurityPlanManager securityPlanManager, - SessionFactory sessionFactory, - MetricsProvider metricsProvider, - Config config) { + BoltSecurityPlanManager securityPlanManager, SessionFactory sessionFactory, Config config) { capturedSecurityPlans.add( securityPlanManager.plan().toCompletableFuture().join()); - return super.createDriver(securityPlanManager, sessionFactory, metricsProvider, config); + return super.createDriver(securityPlanManager, sessionFactory, config); } } } 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 f667493f52..e48b64c51d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java @@ -17,9 +17,7 @@ package org.neo4j.driver.internal; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -32,7 +30,6 @@ import java.net.URI; import java.net.URISyntaxException; -import java.time.Clock; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -43,16 +40,12 @@ import org.neo4j.driver.AuthTokens; 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.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.homedb.HomeDatabaseCache; -import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; -import org.neo4j.driver.internal.metrics.InternalMetricsProvider; -import org.neo4j.driver.internal.metrics.MicrometerMetricsProvider; +import org.neo4j.driver.internal.observation.DriverObservationProvider; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; import org.neo4j.driver.internal.security.StaticAuthTokenManager; @@ -106,43 +99,6 @@ void shouldNotVerifyConnectivity(String uri) { } } - @Test - void shouldNotCreateDriverMetrics() { - // Given - var config = Config.builder().withoutDriverMetrics().build(); - // When - var provider = DriverFactory.getOrCreateMetricsProvider(config, Clock.systemUTC()); - // Then - assertThat(provider, is(equalTo(DevNullMetricsProvider.INSTANCE))); - } - - @Test - void shouldCreateDriverMetricsIfMonitoringEnabled() { - // Given - @SuppressWarnings("deprecation") - var config = - Config.builder().withDriverMetrics().withLogging(Logging.none()).build(); - // When - var provider = DriverFactory.getOrCreateMetricsProvider(config, Clock.systemUTC()); - // Then - assertThat(provider instanceof InternalMetricsProvider, is(true)); - } - - @Test - void shouldCreateMicrometerDriverMetricsIfMonitoringEnabled() { - // Given - @SuppressWarnings("deprecation") - var config = Config.builder() - .withDriverMetrics() - .withMetricsAdapter(MetricsAdapter.MICROMETER) - .withLogging(Logging.none()) - .build(); - // When - var provider = DriverFactory.getOrCreateMetricsProvider(config, Clock.systemUTC()); - // Then - assertThat(provider instanceof MicrometerMetricsProvider, is(true)); - } - @ParameterizedTest @ValueSource(strings = {"neo4j", "neo4j+s", "neo4j+ssc", "bolt", "bolt+s", "bolt+ssc"}) void shouldAcceptValidSchemes(String input) throws URISyntaxException { @@ -205,9 +161,16 @@ protected SessionFactory createSessionFactory( RetryLogic retryLogic, Config config, AuthTokenManager authTokenManager, - HomeDatabaseCache homeDatabaseCache) { + HomeDatabaseCache homeDatabaseCache, + DriverObservationProvider observationProvider) { var sessionFactory = super.createSessionFactory( - securityPlanManager, connectionProvider, retryLogic, config, authTokenManager, homeDatabaseCache); + securityPlanManager, + connectionProvider, + retryLogic, + config, + authTokenManager, + homeDatabaseCache, + observationProvider); capturedSessionFactory = sessionFactory; return sessionFactory; } @@ -227,7 +190,8 @@ protected SessionFactory createSessionFactory( RetryLogic retryLogic, Config config, AuthTokenManager authTokenManager, - HomeDatabaseCache homeDatabaseCache) { + HomeDatabaseCache homeDatabaseCache, + DriverObservationProvider observationProvider) { return sessionFactory; } } 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 2d5b1e555b..e94065345c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalDriverTest.java @@ -21,7 +21,6 @@ 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.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -29,15 +28,13 @@ import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.testutil.TestUtil.await; -import java.time.Clock; import java.util.Collections; import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Test; import org.neo4j.driver.Config; import org.neo4j.driver.QueryConfig; -import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; class InternalDriverTest { @@ -86,36 +83,10 @@ void shouldThrowWhenUnableToVerifyConnectivity() { assertEquals(e.getMessage(), "Hello"); } - @Test - @SuppressWarnings("resource") - void shouldThrowClientExceptionIfMetricsNotEnabled() { - // Given - var driver = newDriver(false); - - // When - var error = assertThrows(ClientException.class, driver::metrics); - - // Then - assertTrue(error.getMessage().contains("Driver metrics are not enabled.")); - } - - @Test - @SuppressWarnings("resource") - void shouldReturnMetricsIfMetricsEnabled() { - // Given - var driver = newDriver(true); - - // When - var metrics = driver.metrics(); - - // Then we shall have no problem to get the metrics - assertNotNull(metrics); - } - @Test void shouldCreateExecutableQuery() { // Given - var driver = newDriver(true); + var driver = newDriver(); var query = "string"; // When @@ -133,9 +104,9 @@ private static InternalDriver newDriver(SessionFactory sessionFactory) { return new InternalDriver( BoltSecurityPlanManager.insecure(), sessionFactory, - DevNullMetricsProvider.INSTANCE, true, - DEV_NULL_LOGGING); + DEV_NULL_LOGGING, + NoopObservationProvider.getInstance()); } private static SessionFactory sessionFactoryMock() { @@ -144,15 +115,15 @@ private static SessionFactory sessionFactoryMock() { return sessionFactory; } - private static InternalDriver newDriver(boolean isMetricsEnabled) { + private static InternalDriver newDriver() { var sessionFactory = sessionFactoryMock(); var config = Config.defaultConfig(); - if (isMetricsEnabled) { - config = Config.builder().withDriverMetrics().build(); - } - var metricsProvider = DriverFactory.getOrCreateMetricsProvider(config, Clock.systemUTC()); return new InternalDriver( - BoltSecurityPlanManager.insecure(), sessionFactory, metricsProvider, true, DEV_NULL_LOGGING); + BoltSecurityPlanManager.insecure(), + sessionFactory, + true, + DEV_NULL_LOGGING, + NoopObservationProvider.getInstance()); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java index a81f9ba53c..c58916ec9b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java @@ -55,6 +55,7 @@ import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; import org.neo4j.driver.internal.cursor.DisposableResultCursorImpl; import org.neo4j.driver.internal.cursor.ResultCursorImpl; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.value.NullValue; import org.neo4j.driver.util.Pair; @@ -345,7 +346,17 @@ private Result createResult(int numberOfRecords) { when(connection.protocolVersion()).thenReturn(new BoltProtocolVersion(4, 3)); when(connection.serverAgent()).thenReturn("Neo4j/4.2.5"); - var resultCursor = new ResultCursorImpl(connection, query, -1, ignored -> {}, false, null, ignored -> {}, null); + var resultCursor = new ResultCursorImpl( + connection, + query, + -1, + ignored -> {}, + false, + null, + ignored -> {}, + null, + NoopObservationProvider.getInstance(), + Result.class); var runSummary = mock(RunSummary.class); given(runSummary.keys()).willReturn(asList("k1", "k2")); resultCursor.onRunSummary(runSummary); diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java index 89257bf95f..b4e5ddd76e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java @@ -40,6 +40,7 @@ import org.neo4j.driver.TransactionContext; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; @@ -50,7 +51,7 @@ public class InternalSessionTest { @BeforeEach void beforeEach() { networkSession = mock(NetworkSession.class); - session = new InternalSession(networkSession); + session = new InternalSession(networkSession, NoopObservationProvider.getInstance()); } @ParameterizedTest @@ -98,12 +99,12 @@ void shouldDelegateBeginWithType() { var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION); ArgumentMatcher apiMatcher = argument -> apiTelemetryWork.telemetryApi().equals(argument.telemetryApi()); - given(networkSession.beginTransactionAsync(eq(config), eq(type), argThat(apiMatcher))) + given(networkSession.beginTransactionAsync(eq(config), eq(type), argThat(apiMatcher), any())) .willReturn(completedFuture(mock(UnmanagedTransaction.class))); internalSession.beginTransaction(config, type); - then(networkSession).should().beginTransactionAsync(eq(config), eq(type), argThat(apiMatcher)); + then(networkSession).should().beginTransactionAsync(eq(config), eq(type), argThat(apiMatcher), any()); } static List executeVariations() { diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java index 16213eda04..4ed7e33118 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java @@ -58,6 +58,7 @@ import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.testutil.TestUtil; @@ -70,7 +71,7 @@ class InternalTransactionTest { void setUp() { connection = connectionMock(new BoltProtocolVersion(4, 0)); var connectionProvider = mock(DriverBoltConnectionSource.class); - given(connectionProvider.getConnection(any())).willReturn(CompletableFuture.completedFuture(connection)); + given(connectionProvider.getConnection(any(), any())).willReturn(CompletableFuture.completedFuture(connection)); setupConnectionAnswers(connection, List.of(new TestUtil.MessageHandler() { @Override public List> messageTypes() { @@ -83,7 +84,8 @@ public void handle(DriverResponseHandler handler) { handler.onComplete(); } })); - var session = new InternalSession(newSession(connectionProvider, Collections.emptySet())); + var session = new InternalSession( + newSession(connectionProvider, Collections.emptySet()), NoopObservationProvider.getInstance()); tx = session.beginTransaction(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java index a5ac49180c..be835cf683 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java @@ -29,6 +29,7 @@ import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; import org.neo4j.driver.internal.util.FixedRetryLogic; @@ -73,6 +74,7 @@ private static SessionFactory newSessionFactory(Config config) { new FixedRetryLogic(0), config, mock(AuthTokenManager.class), - mock()); + mock(), + NoopObservationProvider.getInstance()); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java index 4f638e5615..73f6de6f75 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java @@ -87,6 +87,7 @@ import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.util.FixedRetryLogic; import org.neo4j.driver.internal.value.IntegerValue; @@ -103,14 +104,14 @@ void setUp() { connection = connectionMock(new BoltProtocolVersion(4, 0)); given(connection.close()).willReturn(completedFuture(null)); connectionProvider = mock(DriverBoltConnectionSource.class); - given(connectionProvider.getConnection(any())) + given(connectionProvider.getConnection(any(), any())) .willAnswer((Answer>) invocation -> { var parameters = (RoutedBoltConnectionParameters) invocation.getArguments()[0]; parameters.databaseNameListener().accept(parameters.databaseName()); return completedFuture(connection); }); session = newSession(connectionProvider); - asyncSession = new InternalAsyncSession(session); + asyncSession = new InternalAsyncSession(session, NoopObservationProvider.getInstance()); } private static Stream>> allSessionRunMethods() { @@ -271,7 +272,7 @@ void shouldCloseSession() { @Test void shouldReturnBookmark() { session = newSession(connectionProvider, Collections.singleton(Bookmark.from("Bookmark1"))); - asyncSession = new InternalAsyncSession(session); + asyncSession = new InternalAsyncSession(session, NoopObservationProvider.getInstance()); assertThat(asyncSession.lastBookmarks(), equalTo(session.lastBookmarks())); } @@ -282,7 +283,7 @@ void shouldDelegateExecuteReadToRetryLogic(ExecuteVariation executeVariation) throws ExecutionException, InterruptedException { // GIVEN var networkSession = mock(NetworkSession.class); - AsyncSession session = new InternalAsyncSession(networkSession); + AsyncSession session = new InternalAsyncSession(networkSession, NoopObservationProvider.getInstance()); var logic = mock(RetryLogic.class); var expected = ""; given(networkSession.retryLogic()).willReturn(logic); @@ -342,7 +343,7 @@ public void handle(DriverResponseHandler handler) { var e = assertThrows(Exception.class, () -> executeTransaction(asyncSession, transactionMode, work)); assertEquals(error, e); - verify(connectionProvider).getConnection(any()); + verify(connectionProvider).getConnection(any(), any()); verifyBegin(connection); verifyRollbackTx(connection); } @@ -391,7 +392,7 @@ public void handle(DriverResponseHandler handler) { RetryLogic retryLogic = new FixedRetryLogic(retries); session = newSession(connectionProvider, retryLogic); - asyncSession = new InternalAsyncSession(session); + asyncSession = new InternalAsyncSession(session, NoopObservationProvider.getInstance()); var work = spy(new TxWork(failures, new SessionExpiredException(""))); int answer = executeTransaction(asyncSession, mode, work); @@ -440,7 +441,7 @@ public void handle(DriverResponseHandler handler) { RetryLogic retryLogic = new FixedRetryLogic(retries); session = newSession(connectionProvider, retryLogic); - asyncSession = new InternalAsyncSession(session); + asyncSession = new InternalAsyncSession(session, NoopObservationProvider.getInstance()); var work = spy(new TxWork(43)); int answer = executeTransaction(asyncSession, mode, work); @@ -482,7 +483,7 @@ public void handle(DriverResponseHandler handler) { RetryLogic retryLogic = new FixedRetryLogic(retries); session = newSession(connectionProvider, retryLogic); - asyncSession = new InternalAsyncSession(session); + asyncSession = new InternalAsyncSession(session, NoopObservationProvider.getInstance()); var work = spy(new TxWork(failures, new SessionExpiredException("Oh!"))); @@ -496,7 +497,8 @@ public void handle(DriverResponseHandler handler) { .writeAndFlush( any(), ArgumentMatchers.>argThat( - messages -> messages.size() == 1 && messages.get(0) instanceof CommitMessage)); + messages -> messages.size() == 1 && messages.get(0) instanceof CommitMessage), + any()); verifyRollbackTx(connection, times(failures)); } @@ -532,7 +534,7 @@ public void handle(DriverResponseHandler handler) { RetryLogic retryLogic = new FixedRetryLogic(retries); session = newSession(connectionProvider, retryLogic); - asyncSession = new InternalAsyncSession(session); + asyncSession = new InternalAsyncSession(session, NoopObservationProvider.getInstance()); var work = spy(new TxWork(42)); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java index 84545c93d6..c3659bb168 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java @@ -68,6 +68,7 @@ import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.testutil.TestUtil; @@ -79,14 +80,14 @@ class InternalAsyncTransactionTest { void setUp() { connection = connectionMock(new BoltProtocolVersion(4, 0)); var connectionProvider = mock(DriverBoltConnectionSource.class); - given(connectionProvider.getConnection(any())) + given(connectionProvider.getConnection(any(), any())) .willAnswer((Answer>) invocation -> { var parameters = (RoutedBoltConnectionParameters) invocation.getArguments()[0]; parameters.databaseNameListener().accept(parameters.databaseName()); return completedFuture(connection); }); var networkSession = newSession(connectionProvider); - session = new InternalAsyncSession(networkSession); + session = new InternalAsyncSession(networkSession, NoopObservationProvider.getInstance()); } private static Stream>> allSessionRunMethods() { @@ -294,7 +295,7 @@ void shouldDelegateIsOpenAsync() throws ExecutionException, InterruptedException var utx = mock(UnmanagedTransaction.class); var expected = false; given(utx.isOpen()).willReturn(expected); - var tx = new InternalAsyncTransaction(utx); + var tx = new InternalAsyncTransaction(utx, NoopObservationProvider.getInstance(), null); // WHEN boolean actual = tx.isOpenAsync().toCompletableFuture().get(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java index 79217ffd02..e4c12c8125 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java @@ -52,10 +52,13 @@ import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.async.ResultCursor; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.FixedRetryLogic; import org.neo4j.driver.testutil.TestUtil; @@ -84,7 +87,11 @@ public void handle(DriverResponseHandler handler) { })); given(connection.close()).willReturn(completedFuture(null)); var session = newSession(logging, connection); - session.runAsync(new Query("query"), TransactionConfig.empty()) + session.runAsync( + new Query("query"), + TransactionConfig.empty(), + NoopObservation.getInstance(), + ResultCursor.class) .toCompletableFuture() .join() .consumeAsync() @@ -120,7 +127,7 @@ public void handle(DriverResponseHandler handler) { var session = newSession(logging, connection); // begin transaction to make session obtain a connection var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION); - session.beginTransactionAsync(TransactionConfig.empty(), apiTelemetryWork) + session.beginTransactionAsync(TransactionConfig.empty(), apiTelemetryWork, NoopObservation.getInstance()) .toCompletableFuture() .join(); @@ -163,12 +170,13 @@ private static LeakLoggingNetworkSession newSession( null, true, AuthTokenManagers.basic(AuthTokens::none), - mock()); + mock(), + NoopObservationProvider.getInstance()); } private static DriverBoltConnectionSource connectionProviderMock(DriverBoltConnection connection) { var provider = mock(DriverBoltConnectionSource.class); - when(provider.getConnection(any())).thenReturn(CompletableFuture.completedFuture(connection)); + when(provider.getConnection(any(), any())).thenReturn(CompletableFuture.completedFuture(connection)); return provider; } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java index 85ad676883..dec65eff85 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java @@ -85,11 +85,13 @@ import org.neo4j.driver.Bookmark; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.async.ResultCursor; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionSource; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; +import org.neo4j.driver.internal.observation.NoopObservation; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.FixedRetryLogic; import org.neo4j.driver.internal.value.BoltValueFactory; @@ -106,7 +108,7 @@ void setUp() { given(connection.close()).willReturn(completedFuture(null)); given(connection.valueFactory()).willReturn(mock(BoltValueFactory.class)); connectionProvider = mock(DriverBoltConnectionSource.class); - given(connectionProvider.getConnection(any())) + given(connectionProvider.getConnection(any(), any())) .willAnswer((Answer>) invocation -> { var parameters = (RoutedBoltConnectionParameters) invocation.getArguments()[0]; parameters.databaseNameListener().accept(parameters.databaseName()); @@ -118,7 +120,8 @@ void setUp() { @Test void shouldFlushOnRunAsync() { setupSuccessfulAutocommitRunAndPull(connection); - await(session.runAsync(new Query("RETURN 1"), TransactionConfig.empty())); + await(session.runAsync( + new Query("RETURN 1"), TransactionConfig.empty(), NoopObservation.getInstance(), ResultCursor.class)); verifyAutocommitRunAndPull(connection, "RETURN 1"); } @@ -126,7 +129,11 @@ void shouldFlushOnRunAsync() { @Test void shouldFlushOnRunRx() { setupSuccessfulAutocommitRunAndPull(connection); - await(session.runRx(new Query("RETURN 1"), TransactionConfig.empty(), CompletableFuture.completedStage(null))); + await(session.runRx( + new Query("RETURN 1"), + TransactionConfig.empty(), + CompletableFuture.completedStage(null), + NoopObservation.getInstance())); verifyAutocommitRunRx(connection, "RETURN 1"); } @@ -171,7 +178,7 @@ public void handle(DriverResponseHandler handler) { handler.onComplete(); } })); - await(beginTransaction(session).closeAsync()); + await(beginTransaction(session).closeAsync(NoopObservation.getInstance())); // When var tx = beginTransaction(session); @@ -221,7 +228,7 @@ public void handle(DriverResponseHandler handler) { handler.onComplete(); } })); - await(beginTransaction(session).closeAsync()); + await(beginTransaction(session).closeAsync(NoopObservation.getInstance())); Mockito.reset(connection); setupSuccessfulAutocommitRunAndPull(connection); given(connection.valueFactory()).willReturn(mock(BoltValueFactory.class)); @@ -290,7 +297,7 @@ void acquiresNewConnectionForRun() { run(session, query); - verify(connectionProvider).getConnection(any()); + verify(connectionProvider).getConnection(any(), any()); } @Test @@ -308,7 +315,7 @@ void releasesOpenConnectionUsedForRunWhenSessionIsClosed() { void resetDoesNothingWhenNoTransactionAndNoConnection() { await(session.resetAsync()); - verify(connectionProvider, never()).getConnection(any()); + verify(connectionProvider, never()).getConnection(any(), any()); } @Test @@ -317,7 +324,7 @@ void closeWithoutConnection() { close(session); - verify(connectionProvider, never()).getConnection(any()); + verify(connectionProvider, never()).getConnection(any(), any()); } @Test @@ -326,7 +333,7 @@ void acquiresNewConnectionForBeginTx() { var tx = beginTransaction(session); assertNotNull(tx); - verify(connectionProvider).getConnection(any()); + verify(connectionProvider).getConnection(any(), any()); } @Test @@ -365,7 +372,7 @@ public void handle(DriverResponseHandler handler) { var bookmarks = session.lastBookmarks(); assertTrue(bookmarks.isEmpty()); - await(tx.commitAsync()); + await(tx.commitAsync(NoopObservation.getInstance())); assertEquals(Collections.singleton(bookmarkAfterCommit), session.lastBookmarks()); } @@ -412,13 +419,13 @@ public void handle(DriverResponseHandler handler) { } })); var tx = beginTransaction(session); - verify(connectionProvider).getConnection(any()); + verify(connectionProvider).getConnection(any(), any()); verifyBegin(connection); var query = "RETURN 42"; - await(tx.runAsync(new Query(query))); + await(tx.runAsync(new Query(query), NoopObservation.getInstance(), ResultCursor.class)); verifyRunAndPull(connection, query); - await(tx.closeAsync()); + await(tx.closeAsync(NoopObservation.getInstance())); verify(connection).close(); } @@ -435,7 +442,8 @@ void bookmarkIsPropagatedFromSession() { .writeAndFlush( any(), ArgumentMatchers.>argThat( - messages -> messages.size() == 1 && messages.get(0) instanceof BeginMessage)); + messages -> messages.size() == 1 && messages.get(0) instanceof BeginMessage), + any()); } @Test @@ -477,13 +485,13 @@ public void handle(DriverResponseHandler handler) { })); var tx1 = beginTransaction(session); - await(tx1.commitAsync()); + await(tx1.commitAsync(NoopObservation.getInstance())); assertEquals(Collections.singleton(bookmark1), session.lastBookmarks()); var tx2 = beginTransaction(session); verifyBegin(connection, times(2)); verifyCommitTx(connection); - await(tx2.commitAsync()); + await(tx2.commitAsync(NoopObservation.getInstance())); assertEquals(Collections.singleton(bookmark2), session.lastBookmarks()); } @@ -504,7 +512,7 @@ private void accessModeUsedToAcquireConnections(AccessMode mode) { var session2 = newSession(connectionProvider, mode); beginTransaction(session2); var argument = ArgumentCaptor.forClass(RoutedBoltConnectionParameters.class); - verify(connectionProvider).getConnection(argument.capture()); + verify(connectionProvider).getConnection(argument.capture(), any()); assertEquals( switch (mode) { case READ -> org.neo4j.bolt.connection.AccessMode.READ; @@ -531,7 +539,7 @@ void shouldHaveEmptyLastBookmarksInitially() { void shouldDoNothingWhenClosingWithoutAcquiredConnection() { var error = new RuntimeException("Hi"); Mockito.reset(connectionProvider); - given(connectionProvider.getConnection(any())).willReturn(failedFuture(error)); + given(connectionProvider.getConnection(any(), any())).willReturn(failedFuture(error)); var e = assertThrows(Exception.class, () -> run(session, "RETURN 1")); assertEquals(error, e); @@ -543,7 +551,7 @@ void shouldDoNothingWhenClosingWithoutAcquiredConnection() { void shouldRunAfterRunFailure() { var error = new RuntimeException("Hi"); Mockito.reset(connectionProvider); - given(connectionProvider.getConnection(any())) + given(connectionProvider.getConnection(any(), any())) .willReturn(failedFuture(error)) .willAnswer((Answer>) invocation -> { var parameters = (RoutedBoltConnectionParameters) invocation.getArguments()[0]; @@ -560,7 +568,7 @@ void shouldRunAfterRunFailure() { run(session, query); - verify(connectionProvider, times(2)).getConnection(any()); + verify(connectionProvider, times(2)).getConnection(any(), any()); verifyAutocommitRunAndPull(connection, query); } @@ -571,14 +579,15 @@ void shouldRunAfterBeginTxFailureOnBookmark() { given(connection1.writeAndFlush( any(), ArgumentMatchers.>argThat( - messages -> messages.size() == 1 && messages.get(0) instanceof BeginMessage))) + messages -> messages.size() == 1 && messages.get(0) instanceof BeginMessage), + any())) .willReturn(CompletableFuture.failedStage(error)); given(connection1.close()).willReturn(CompletableFuture.completedStage(null)); var connection2 = connectionMock(new BoltProtocolVersion(5, 0)); given(connection2.close()).willReturn(CompletableFuture.completedStage(null)); Mockito.reset(connectionProvider); - given(connectionProvider.getConnection(any())) + given(connectionProvider.getConnection(any(), any())) .willAnswer((Answer>) invocation -> { var parameters = (RoutedBoltConnectionParameters) invocation.getArguments()[0]; parameters.databaseNameListener().accept(parameters.databaseName()); @@ -600,7 +609,7 @@ void shouldRunAfterBeginTxFailureOnBookmark() { run(session, query); - verify(connectionProvider, times(2)).getConnection(any()); + verify(connectionProvider, times(2)).getConnection(any(), any()); verifyBegin(connection1); verifyAutocommitRunAndPull(connection2, "RETURN 2"); } @@ -612,7 +621,8 @@ void shouldBeginTxAfterBeginTxFailureOnBookmark() { given(connection1.writeAndFlush( any(), ArgumentMatchers.>argThat( - messages -> messages.size() == 1 && messages.get(0) instanceof BeginMessage))) + messages -> messages.size() == 1 && messages.get(0) instanceof BeginMessage), + any())) .willReturn(CompletableFuture.failedStage(error)); given(connection1.close()).willReturn(CompletableFuture.completedStage(null)); var connection2 = connectionMock(new BoltProtocolVersion(5, 0)); @@ -630,7 +640,7 @@ public void handle(DriverResponseHandler handler) { })); Mockito.reset(connectionProvider); - given(connectionProvider.getConnection(any())) + given(connectionProvider.getConnection(any(), any())) .willAnswer((Answer>) invocation -> { var parameters = (RoutedBoltConnectionParameters) invocation.getArguments()[0]; parameters.databaseNameListener().accept(parameters.databaseName()); @@ -650,7 +660,7 @@ public void handle(DriverResponseHandler handler) { beginTransaction(session); - verify(connectionProvider, times(2)).getConnection(any()); + verify(connectionProvider, times(2)).getConnection(any(), any()); verifyBegin(connection1); verifyBegin(connection2); } @@ -659,7 +669,7 @@ public void handle(DriverResponseHandler handler) { void shouldBeginTxAfterRunFailureToAcquireConnection() { var error = new RuntimeException("Hi"); Mockito.reset(connectionProvider); - given(connectionProvider.getConnection(any())) + given(connectionProvider.getConnection(any(), any())) .willReturn(failedFuture(error)) .willAnswer((Answer>) invocation -> { var parameters = (RoutedBoltConnectionParameters) invocation.getArguments()[0]; @@ -673,13 +683,14 @@ void shouldBeginTxAfterRunFailureToAcquireConnection() { beginTransaction(session); - verify(connectionProvider, times(2)).getConnection(any()); + verify(connectionProvider, times(2)).getConnection(any(), any()); then(connection) .should() .writeAndFlush( any(), ArgumentMatchers.>argThat( - messages -> messages.size() == 1 && messages.get(0) instanceof BeginMessage)); + messages -> messages.size() == 1 && messages.get(0) instanceof BeginMessage), + any()); } @Test @@ -714,11 +725,11 @@ public void handle(DriverResponseHandler handler) { var tx = beginTransaction(session); assertTrue(tx.isOpen()); - then(connection).should(never()).writeAndFlush(any(), any(ResetMessage.class)); + then(connection).should(never()).writeAndFlush(any(), any(ResetMessage.class), any()); await(session.resetAsync()); - then(connection).should().writeAndFlush(any(), eq(List.of(Messages.reset()))); + then(connection).should().writeAndFlush(any(), eq(List.of(Messages.reset())), any()); } @ParameterizedTest @@ -756,15 +767,19 @@ public void handle(DriverResponseHandler handler) { if (telemetryDisabled) { then(connection) .should(never()) - .writeAndFlush(any(), ArgumentMatchers.>argThat(messages -> messages.stream() - .anyMatch(msg -> msg instanceof TelemetryMessage))); + .writeAndFlush( + any(), + ArgumentMatchers.>argThat( + messages -> messages.stream().anyMatch(msg -> msg instanceof TelemetryMessage)), + any()); } else { then(connection) .should() .writeAndFlush( any(), ArgumentMatchers.>argThat(messages -> - messages.contains(Messages.telemetry(TelemetryApi.UNMANAGED_TRANSACTION)))); + messages.contains(Messages.telemetry(TelemetryApi.UNMANAGED_TRANSACTION))), + any()); } } @@ -806,15 +821,19 @@ public void handle(DriverResponseHandler handler) { if (telemetryDisabled) { then(connection) .should(never()) - .writeAndFlush(any(), ArgumentMatchers.>argThat(messages -> messages.stream() - .anyMatch(msg -> msg instanceof TelemetryMessage))); + .writeAndFlush( + any(), + ArgumentMatchers.>argThat( + messages -> messages.stream().anyMatch(msg -> msg instanceof TelemetryMessage)), + any()); } else { then(connection) .should() .writeAndFlush( any(), ArgumentMatchers.>argThat(messages -> - messages.contains(Messages.telemetry(TelemetryApi.AUTO_COMMIT_TRANSACTION)))); + messages.contains(Messages.telemetry(TelemetryApi.AUTO_COMMIT_TRANSACTION))), + any()); } } @@ -822,7 +841,8 @@ private void setupSuccessfulBegin(DriverBoltConnection connection) { given(connection.writeAndFlush( any(), ArgumentMatchers.>argThat( - argument -> argument.size() == 1 && argument.get(0) instanceof BeginMessage))) + argument -> argument.size() == 1 && argument.get(0) instanceof BeginMessage), + any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArguments()[0]; handler.onBeginSummary(mock(BeginSummary.class)); @@ -832,15 +852,17 @@ private void setupSuccessfulBegin(DriverBoltConnection connection) { } private static void run(NetworkSession session, String query) { - await(session.runAsync(new Query(query), TransactionConfig.empty())); + await(session.runAsync( + new Query(query), TransactionConfig.empty(), NoopObservation.getInstance(), ResultCursor.class)); } private static UnmanagedTransaction beginTransaction(NetworkSession session) { var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.UNMANAGED_TRANSACTION); - return await(session.beginTransactionAsync(TransactionConfig.empty(), apiTelemetryWork)); + return await(session.beginTransactionAsync( + TransactionConfig.empty(), apiTelemetryWork, NoopObservation.getInstance())); } private static void close(NetworkSession session) { - await(session.closeAsync()); + await(session.closeAsync(NoopObservation.getInstance())); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java index d12d84ea89..11db0f0340 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java @@ -36,13 +36,14 @@ import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.neo4j.driver.internal.FailableCursor; +import org.neo4j.driver.internal.observation.NoopObservation; class ResultCursorsHolderTest { @Test void shouldReturnNoErrorWhenNoCursorStages() { var holder = new ResultCursorsHolder(); - var error = await(holder.retrieveNotConsumedError()); + var error = await(holder.retrieveNotConsumedError(NoopObservation.getInstance())); assertNull(error); } @@ -62,7 +63,7 @@ void shouldReturnNoErrorWhenCursorStagesHaveNoErrors() { holder.add(cursorWithoutError()); holder.add(cursorWithoutError()); - var error = await(holder.retrieveNotConsumedError()); + var error = await(holder.retrieveNotConsumedError(NoopObservation.getInstance())); assertNull(error); } @@ -75,7 +76,7 @@ void shouldNotReturnStageErrors() { holder.add(cursorWithoutError()); holder.add(failedFuture(new IOException("Failed to do IO"))); - var error = await(holder.retrieveNotConsumedError()); + var error = await(holder.retrieveNotConsumedError(NoopObservation.getInstance())); assertNull(error); } @@ -89,7 +90,7 @@ void shouldReturnErrorWhenOneCursorFailed() { holder.add(cursorWithError(error)); holder.add(cursorWithoutError()); - var retrievedError = await(holder.retrieveNotConsumedError()); + var retrievedError = await(holder.retrieveNotConsumedError(NoopObservation.getInstance())); assertEquals(error, retrievedError); } @@ -105,7 +106,7 @@ void shouldReturnFirstError() { holder.add(cursorWithError(error2)); holder.add(cursorWithError(error3)); - assertEquals(error1, await(holder.retrieveNotConsumedError())); + assertEquals(error1, await(holder.retrieveNotConsumedError(NoopObservation.getInstance()))); } @Test @@ -118,7 +119,8 @@ void shouldWaitForAllFailuresToArrive() { holder.add(cursorWithError(error1)); holder.add(cursorWithFailureFuture(error2Future)); - var failureFuture = holder.retrieveNotConsumedError().toCompletableFuture(); + var failureFuture = + holder.retrieveNotConsumedError(NoopObservation.getInstance()).toCompletableFuture(); assertFalse(failureFuture.isDone()); error2Future.complete(null); @@ -138,22 +140,25 @@ void shouldRemoveConsumedResults() { holder.add(CompletableFuture.completedFuture(cursor)); if (i % 2 == 0) { consume.complete(null); - given(cursor.discardAllFailureAsync()) + given(cursor.discardAllFailureAsync(NoopObservation.getInstance())) .willReturn(CompletableFuture.failedFuture(new RuntimeException())); } else { - given(cursor.discardAllFailureAsync()).willReturn(CompletableFuture.completedStage(null)); + given(cursor.discardAllFailureAsync(NoopObservation.getInstance())) + .willReturn(CompletableFuture.completedStage(null)); } return cursor; }) .toList(); - holder.retrieveNotConsumedError().toCompletableFuture().join(); + holder.retrieveNotConsumedError(NoopObservation.getInstance()) + .toCompletableFuture() + .join(); for (var i = 0; i < list.size(); i++) { var cursor = list.get(i); then(cursor).should().consumed(); if (i % 2 == 1) { - then(cursor).should().discardAllFailureAsync(); + then(cursor).should().discardAllFailureAsync(NoopObservation.getInstance()); } then(cursor).shouldHaveNoMoreInteractions(); } @@ -170,7 +175,7 @@ private static CompletionStage cursorWithError(Throwable error) private static CompletionStage cursorWithFailureFuture(CompletableFuture future) { var cursor = mock(FailableCursor.class); when(cursor.consumed()).thenReturn(new CompletableFuture<>()); - when(cursor.discardAllFailureAsync()).thenReturn(future); + when(cursor.discardAllFailureAsync(NoopObservation.getInstance())).thenReturn(future); return completedFuture(cursor); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java index 0600a5f928..9844acc5ab 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java @@ -77,6 +77,7 @@ import org.neo4j.driver.Logging; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.async.ResultCursor; import org.neo4j.driver.exceptions.AuthorizationExpiredException; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ConnectionReadTimeoutException; @@ -86,6 +87,8 @@ import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.testutil.TestUtil; @@ -125,7 +128,7 @@ public void handle(DriverResponseHandler handler) { var tx = beginTx(connection); // When - await(tx.runAsync(new Query("RETURN 1"))); + await(tx.runAsync(new Query("RETURN 1"), NoopObservation.getInstance(), ResultCursor.class)); // Then verifyRunAndPull(connection, "RETURN 1"); @@ -165,7 +168,7 @@ public void handle(DriverResponseHandler handler) { var tx = beginTx(connection); // When - await(tx.runRx(new Query("RETURN 1"))); + await(tx.runRx(new Query("RETURN 1"), NoopObservation.getInstance())); // Then verifyBegin(connection); @@ -207,7 +210,7 @@ public void handle(DriverResponseHandler handler) { var tx = beginTx(connection); // When - await(tx.closeAsync()); + await(tx.closeAsync(NoopObservation.getInstance())); // Then verifyBegin(connection); @@ -298,7 +301,7 @@ public void handle(DriverResponseHandler handler) { var tx = beginTx(connection); tx.markTerminated(null); - await(tx.closeAsync()); + await(tx.closeAsync(NoopObservation.getInstance())); assertFalse(tx.isOpen()); } @@ -332,12 +335,15 @@ public void handle(DriverResponseHandler handler) { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); var bookmarks = Collections.singleton(Bookmark.from("SomeBookmark")); var txConfig = TransactionConfig.empty(); - var e = assertThrows(RuntimeException.class, () -> await(tx.beginAsync(bookmarks, txConfig, null, true))); + var e = assertThrows( + RuntimeException.class, + () -> await(tx.beginAsync(bookmarks, txConfig, null, true, NoopObservation.getInstance()))); assertEquals(error, e); verify(connection).close(); @@ -371,12 +377,13 @@ public void handle(DriverResponseHandler handler) { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); var bookmarks = Collections.singleton(Bookmark.from("SomeBookmark")); var txConfig = TransactionConfig.empty(); - await(tx.beginAsync(bookmarks, txConfig, null, true)); + await(tx.beginAsync(bookmarks, txConfig, null, true, NoopObservation.getInstance())); verify(connection, never()).close(); } @@ -397,11 +404,12 @@ void shouldReleaseConnectionWhenTerminatedAndCommitted() { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); tx.markTerminated(null); - assertThrows(TransactionTerminatedException.class, () -> await(tx.commitAsync())); + assertThrows(TransactionTerminatedException.class, () -> await(tx.commitAsync(NoopObservation.getInstance()))); assertFalse(tx.isOpen()); verify(connection).close(); @@ -427,11 +435,12 @@ void shouldNotCreateCircularExceptionWhenTerminationCauseEqualsToCursorFailure() null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); tx.markTerminated(terminationCause); - var e = assertThrows(ClientException.class, () -> await(tx.commitAsync())); + var e = assertThrows(ClientException.class, () -> await(tx.commitAsync(NoopObservation.getInstance()))); assertNoCircularReferences(e); assertEquals(terminationCause, e); } @@ -455,11 +464,12 @@ void shouldNotCreateCircularExceptionWhenTerminationCauseDifferentFromCursorFail null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); tx.markTerminated(terminationCause); - var e = assertThrows(ClientException.class, () -> await(tx.commitAsync())); + var e = assertThrows(ClientException.class, () -> await(tx.commitAsync(NoopObservation.getInstance()))); assertNoCircularReferences(e); assertEquals(1, e.getSuppressed().length); @@ -484,11 +494,13 @@ void shouldNotCreateCircularExceptionWhenTerminatedWithoutFailure() { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); tx.markTerminated(terminationCause); - var e = assertThrows(TransactionTerminatedException.class, () -> await(tx.commitAsync())); + var e = assertThrows( + TransactionTerminatedException.class, () -> await(tx.commitAsync(NoopObservation.getInstance()))); assertNoCircularReferences(e); assertEquals(terminationCause, e.getCause()); @@ -510,10 +522,11 @@ void shouldReleaseConnectionWhenTerminatedAndRolledBack() { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); tx.markTerminated(null); - await(tx.rollbackAsync()); + await(tx.rollbackAsync(NoopObservation.getInstance())); verify(connection).close(); } @@ -546,9 +559,10 @@ public void handle(DriverResponseHandler handler) { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); - await(tx.closeAsync()); + await(tx.closeAsync(NoopObservation.getInstance())); verify(connection).close(); } @@ -582,12 +596,14 @@ public void handle(DriverResponseHandler handler) { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); var bookmarks = Collections.singleton(Bookmark.from("SomeBookmark")); var txConfig = TransactionConfig.empty(); var actualException = assertThrows( - AuthorizationExpiredException.class, () -> await(tx.beginAsync(bookmarks, txConfig, null, true))); + AuthorizationExpiredException.class, + () -> await(tx.beginAsync(bookmarks, txConfig, null, true, NoopObservation.getInstance()))); assertSame(exception, actualException); verify(connection).close(); @@ -621,12 +637,14 @@ public void handle(DriverResponseHandler handler) { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); var bookmarks = Collections.singleton(Bookmark.from("SomeBookmark")); var txConfig = TransactionConfig.empty(); var actualException = assertThrows( - ConnectionReadTimeoutException.class, () -> await(tx.beginAsync(bookmarks, txConfig, null, true))); + ConnectionReadTimeoutException.class, + () -> await(tx.beginAsync(bookmarks, txConfig, null, true, NoopObservation.getInstance()))); assertSame(ConnectionReadTimeoutException.INSTANCE, actualException); verify(connection).close(); @@ -680,7 +698,8 @@ public void handle(DriverResponseHandler handler) {} null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); var initialStage = mapTransactionAction(initialAction, tx).get(); var similarStage = mapTransactionAction(similarAction, tx).get(); @@ -759,7 +778,8 @@ public void handle(DriverResponseHandler handler) { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); var originalActionStage = mapTransactionAction(initialAction, tx).get(); var conflictingActionStage = mapTransactionAction(conflictingAction, tx).get(); @@ -835,10 +855,13 @@ public void handle(DriverResponseHandler handler) { null, apiTelemetryWork, mock(), - Logging.none()); + Logging.none(), + NoopObservationProvider.getInstance()); var originalActionStage = mapTransactionAction(originalAction, tx).get(); - var closeStage = commitOnClose != null ? tx.closeAsync(commitOnClose) : tx.closeAsync(); + var closeStage = commitOnClose != null + ? tx.closeAsync(commitOnClose, NoopObservation.getInstance()) + : tx.closeAsync(NoopObservation.getInstance()); assertTrue(originalActionStage.toCompletableFuture().isDone()); assertFalse(originalActionStage.toCompletableFuture().isCompletedExceptionally()); @@ -887,7 +910,7 @@ public void handle(DriverResponseHandler handler) { await(tx.terminateAsync()); // Then - then(connection).should().writeAndFlush(any(), eq(List.of(Messages.reset()))); + then(connection).should().writeAndFlush(any(), eq(List.of(Messages.reset())), any()); } @Test @@ -977,7 +1000,9 @@ public void handle(DriverResponseHandler handler) { // When try { - tx.runAsync(new Query("RETURN 1")).toCompletableFuture().get(); + tx.runAsync(new Query("RETURN 1"), NoopObservation.getInstance(), ResultCursor.class) + .toCompletableFuture() + .get(); } catch (ExecutionException e) { actualException = e.getCause(); } @@ -1037,43 +1062,45 @@ public void handle(DriverResponseHandler handler) {} } static List transactionClosingTestParams() { - Function> asyncRun = tx -> tx.runAsync(new Query("query")); - Function> reactiveRun = tx -> tx.runRx(new Query("query")); + Function> asyncRun = + tx -> tx.runAsync(new Query("query"), NoopObservation.getInstance(), ResultCursor.class); + Function> reactiveRun = + tx -> tx.runRx(new Query("query"), NoopObservation.getInstance()); return List.of( Arguments.of(Named.of( "commit and run async", new TransactionClosingTestParams( - UnmanagedTransaction::commitAsync, + tx -> tx.commitAsync(NoopObservation.getInstance()), asyncRun, "Cannot run more queries in this transaction, it is being committed"))), Arguments.of(Named.of( "commit and run reactive", new TransactionClosingTestParams( - UnmanagedTransaction::commitAsync, + tx -> tx.commitAsync(NoopObservation.getInstance()), reactiveRun, "Cannot run more queries in this transaction, it is being committed"))), Arguments.of(Named.of( "rollback and run async", new TransactionClosingTestParams( - UnmanagedTransaction::rollbackAsync, + tx -> tx.rollbackAsync(NoopObservation.getInstance()), asyncRun, "Cannot run more queries in this transaction, it is being rolled back"))), Arguments.of(Named.of( "rollback and run reactive", new TransactionClosingTestParams( - UnmanagedTransaction::rollbackAsync, + tx -> tx.rollbackAsync(NoopObservation.getInstance()), reactiveRun, "Cannot run more queries in this transaction, it is being rolled back"))), Arguments.of(Named.of( "close and run async", new TransactionClosingTestParams( - UnmanagedTransaction::closeAsync, + tx -> tx.closeAsync(NoopObservation.getInstance()), asyncRun, "Cannot run more queries in this transaction, it is being rolled back"))), Arguments.of(Named.of( "close and run reactive", new TransactionClosingTestParams( - UnmanagedTransaction::closeAsync, + tx -> tx.closeAsync(NoopObservation.getInstance()), reactiveRun, "Cannot run more queries in this transaction, it is being rolled back")))); } @@ -1100,15 +1127,17 @@ private static UnmanagedTransaction beginTx(DriverBoltConnection connection, Set null, apiTelemetryWork, mock(), - Logging.none()); - return await(tx.beginAsync(initialBookmarks, TransactionConfig.empty(), null, true)); + Logging.none(), + NoopObservationProvider.getInstance()); + return await( + tx.beginAsync(initialBookmarks, TransactionConfig.empty(), null, true, NoopObservation.getInstance())); } private ResultCursorsHolder mockResultCursorWith(ClientException clientException) { var resultCursorsHolder = new ResultCursorsHolder(); var cursor = mock(FailableCursor.class); given(cursor.consumed()).willReturn(new CompletableFuture<>()); - doReturn(completedFuture(clientException)).when(cursor).discardAllFailureAsync(); + doReturn(completedFuture(clientException)).when(cursor).discardAllFailureAsync(any()); resultCursorsHolder.add(completedFuture(cursor)); return resultCursorsHolder; } @@ -1116,16 +1145,16 @@ private ResultCursorsHolder mockResultCursorWith(ClientException clientException private Supplier> mapTransactionAction(String actionName, UnmanagedTransaction tx) { Supplier> action; if ("commit".equals(actionName)) { - action = tx::commitAsync; + action = () -> tx.commitAsync(NoopObservation.getInstance()); } else if ("rollback".equals(actionName)) { - action = tx::rollbackAsync; + action = () -> tx.rollbackAsync(NoopObservation.getInstance()); } else if ("terminate".equals(actionName)) { action = () -> { tx.markTerminated(mock(Throwable.class)); return completedFuture(null); }; } else if ("close".equals(actionName)) { - action = tx::closeAsync; + action = () -> tx.closeAsync(NoopObservation.getInstance()); } else { throw new RuntimeException(String.format("Unknown completing action type '%s'", actionName)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/ResultCursorImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/ResultCursorImplTest.java index 54dc5af9d1..a1f13b203e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/ResultCursorImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/ResultCursorImplTest.java @@ -46,12 +46,14 @@ import org.neo4j.bolt.connection.summary.RunSummary; import org.neo4j.driver.Query; import org.neo4j.driver.Value; +import org.neo4j.driver.async.ResultCursor; import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.internal.DatabaseBookmark; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; +import org.neo4j.driver.internal.observation.NoopObservationProvider; class ResultCursorImplTest { ResultCursorImpl cursor; @@ -75,14 +77,23 @@ void beforeEach() { openMocks(this); given(connection.protocolVersion()).willReturn(new BoltProtocolVersion(5, 5)); cursor = new ResultCursorImpl( - connection, query, fetchSize, bookmarkConsumer, closeOnSummary, null, ignored -> {}, null); + connection, + query, + fetchSize, + bookmarkConsumer, + closeOnSummary, + null, + ignored -> {}, + null, + NoopObservationProvider.getInstance(), + ResultCursor.class); cursor.onRunSummary(runSummary); } @Test void shouldNextAsync() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); handler.onRecord(List.of()); @@ -110,7 +121,7 @@ void shouldFailNextAsyncOnError() { void shouldFailNextAsyncOnFlushError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); var error = new RuntimeException("message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> CompletableFuture.failedStage(error)); var future = cursor.nextAsync().toCompletableFuture(); @@ -134,7 +145,7 @@ var record = cursor.singleAsync().toCompletableFuture().join(); void shouldFailSingleAsync() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); handler.onRecord(List.of()); @@ -155,7 +166,7 @@ void shouldFailSingleAsyncOnError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); var error = new Neo4jException("code", "message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); handler.onError(error); @@ -174,7 +185,7 @@ void shouldFailSingleAsyncOnFlushError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); var error = new RuntimeException("message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> CompletableFuture.failedStage(error)); var future = cursor.singleAsync().toCompletableFuture(); @@ -187,7 +198,7 @@ void shouldFailSingleAsyncOnFlushError() { void shouldFetchMore() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); for (var i = 0; i < fetchSize; i++) { @@ -204,14 +215,14 @@ void shouldFetchMore() { assertNotNull(cursor.nextAsync().toCompletableFuture().join()); - then(connection).should(times(2)).writeAndFlush(any(), eq(Messages.pull(0, fetchSize))); + then(connection).should(times(2)).writeAndFlush(any(), eq(Messages.pull(0, fetchSize)), any()); } @Test void shouldListAsync() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); handler.onRecord(List.of()); @@ -221,7 +232,7 @@ void shouldListAsync() { }); assertEquals(1, cursor.listAsync().toCompletableFuture().join().size()); - then(connection).should().writeAndFlush(any(), eq(Messages.pull(0, -1))); + then(connection).should().writeAndFlush(any(), eq(Messages.pull(0, -1)), any()); } @Test @@ -229,7 +240,7 @@ void shouldFailListAsyncOnError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); var error = new Neo4jException("code", "message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); handler.onError(error); @@ -248,7 +259,7 @@ void shouldFailListAsyncOnFlushError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); var error = new RuntimeException("message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> CompletableFuture.failedStage(error)); var future = cursor.listAsync().toCompletableFuture(); @@ -262,7 +273,7 @@ void shouldFailPeekAsyncOnError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); var error = new Neo4jException("code", "message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); handler.onError(error); @@ -281,7 +292,7 @@ void shouldFailListPeekOnFlushError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); var error = new RuntimeException("message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> CompletableFuture.failedStage(error)); var future = cursor.peekAsync().toCompletableFuture(); @@ -295,7 +306,7 @@ void shouldFailConsumeAsyncOnError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); var error = new Neo4jException("code", "message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); handler.onError(error); @@ -314,7 +325,7 @@ void shouldFailConsumeAsyncOnFlushError() { cursor.onPullSummary(new PullSummaryImpl(true, Collections.emptyMap())); given(connection.serverAddress()).willReturn(BoltServerAddress.LOCAL_DEFAULT); var error = new RuntimeException("message"); - given(connection.writeAndFlush(any(), any(Message.class))) + given(connection.writeAndFlush(any(), any(Message.class), any())) .willAnswer((Answer>) invocation -> CompletableFuture.failedStage(error)); var future = cursor.consumeAsync().toCompletableFuture(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/RxResultCursorImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/RxResultCursorImplTest.java index 2b18e6b7df..3c753edd61 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/RxResultCursorImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/RxResultCursorImplTest.java @@ -41,6 +41,7 @@ import org.neo4j.driver.Record; import org.neo4j.driver.internal.DatabaseBookmark; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnection; +import org.neo4j.driver.internal.observation.NoopObservation; class RxResultCursorImplTest { @Mock @@ -77,11 +78,13 @@ void shouldNotifyRecordConsumerOfRunError(boolean getRunError) { BiConsumer recordConsumer = mock(BiConsumer.class); // when - cursor.installRecordConsumer(recordConsumer); + cursor.installRecordConsumer(recordConsumer, NoopObservation.getInstance()); // then then(recordConsumer).should().accept(null, runError); - assertNotNull(cursor.summaryAsync().toCompletableFuture().join()); + assertNotNull(cursor.summaryAsync(NoopObservation.getInstance()) + .toCompletableFuture() + .join()); } @ParameterizedTest @@ -97,7 +100,7 @@ void shouldReturnSummaryWithRunError(boolean getRunError) { } // when - var summary = cursor.summaryAsync().toCompletableFuture(); + var summary = cursor.summaryAsync(NoopObservation.getInstance()).toCompletableFuture(); // then assertEquals( 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 deleted file mode 100644 index 589b6043f7..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerConnectionPoolMetricsTest.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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 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; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.IntSupplier; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.neo4j.bolt.connection.BoltServerAddress; -import org.neo4j.bolt.connection.ListenerEvent; -import org.neo4j.driver.ConnectionPoolMetrics; - -class MicrometerConnectionPoolMetricsTest { - static final String ID = "id"; - - MicrometerConnectionPoolMetrics metrics; - BoltServerAddress address; - MeterRegistry registry; - final AtomicInteger inUse = new AtomicInteger(0); - final IntSupplier inUseSupplier = inUse::get; - final AtomicInteger idle = new AtomicInteger(0); - final IntSupplier idleSupplier = idle::get; - - @BeforeEach - void beforeEach() { - address = new BoltServerAddress("host", "127.0.0.1", 7687); - registry = new SimpleMeterRegistry(); - metrics = new MicrometerConnectionPoolMetrics(ID, address, inUseSupplier, idleSupplier, registry); - } - - @Test - void shouldIncrementCreatingAndStartTimerOnBeforeCreating() { - // GIVEN - var 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 - var 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 - var expectedMetrics = mock(ConnectionPoolMetrics.class); - given(expectedMetrics.creating()).willReturn(-1); - given(expectedMetrics.created()).willReturn(1L); - var timer = registry.get(MicrometerConnectionPoolMetrics.CREATION).timer(); - var timerCount = timer.count(); - var event = new MicrometerTimerListenerEvent(registry); - event.start(); - - // WHEN - metrics.afterCreated(event); - - // THEN - verifyMetrics(expectedMetrics, metrics); - assertEquals(timerCount + 1, timer.count()); - } - - @Test - void shouldIncrementClosedOnAfterClosed() { - // GIVEN - var 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); - var 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 - var expectedMetrics = mock(ConnectionPoolMetrics.class); - given(expectedMetrics.acquiring()).willReturn(-1); - - // WHEN - metrics.afterAcquiringOrCreating(); - - // THEN - verifyMetrics(expectedMetrics, metrics); - } - - @Test - void shouldIncrementAcquiredAndStopTimerOnAfterAcquiredOrCreated() { - // GIVEN - var expectedMetrics = mock(ConnectionPoolMetrics.class); - given(expectedMetrics.acquired()).willReturn(1L); - var timer = registry.get(MicrometerConnectionPoolMetrics.ACQUISITION).timer(); - var timerCount = timer.count(); - var event = new MicrometerTimerListenerEvent(registry); - event.start(); - - // WHEN - metrics.afterAcquiredOrCreated(event); - - // THEN - verifyMetrics(expectedMetrics, metrics); - assertEquals(timerCount + 1, timer.count()); - } - - @Test - void shouldIncrementTimedOutToAcquireOnAfterTimedOutToAcquireOrCreate() { - // GIVEN - var 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 - var expectedMetrics = mock(ConnectionPoolMetrics.class); - given(expectedMetrics.totalInUseCount()).willReturn(1L); - var timer = registry.get(MicrometerConnectionPoolMetrics.USAGE).timer(); - var timerCount = timer.count(); - var event = new MicrometerTimerListenerEvent(registry); - event.start(); - - // WHEN - metrics.released(event); - - // THEN - verifyMetrics(expectedMetrics, metrics); - assertEquals(timerCount + 1, timer.count()); - } - - @Test - void shouldUseInUseSupplier() { - try { - // GIVEN - var expected = 5; - inUse.compareAndSet(0, expected); - var expectedMetrics = mock(ConnectionPoolMetrics.class); - given(expectedMetrics.inUse()).willReturn(expected); - - // WHEN - var actual = metrics.inUse(); - - // THEN - assertEquals(expected, actual); - verifyMetrics(expectedMetrics, metrics); - } finally { - inUse.set(0); - } - } - - @Test - void shouldUseIdleSupplier() { - try { - // GIVEN - var expected = 5; - idle.compareAndSet(0, expected); - var expectedMetrics = mock(ConnectionPoolMetrics.class); - given(expectedMetrics.idle()).willReturn(expected); - - // WHEN - var 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 deleted file mode 100644 index e08d37c831..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsProviderTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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 static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class MicrometerMetricsProviderTest { - MetricsProvider provider; - - @BeforeEach - void beforeEach() { - provider = MicrometerMetricsProvider.forGlobalRegistry(); - } - - @Test - void shouldReturnMicrometerMetricsOnMetrics() { - // GIVEN & WHEN - var metrics = provider.metrics(); - - // THEN - assertTrue(metrics instanceof MicrometerMetrics); - } - - @Test - void shouldReturnMicrometerMetricsOnMetricsListener() { - // GIVEN & WHEN - var 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 deleted file mode 100644 index 231aad6c75..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerMetricsTest.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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 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; - -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 org.neo4j.bolt.connection.BoltServerAddress; -import org.neo4j.bolt.connection.ListenerEvent; -import org.neo4j.driver.ConnectionPoolMetrics; - -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 - var 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 - var event = metrics.createListenerEvent(); - - // THEN - assertTrue(event instanceof MicrometerTimerListenerEvent); - } - - @Test - void shouldPutPoolMetrics() { - // GIVEN - var 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); - var 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 deleted file mode 100644 index 312afb4966..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/metrics/MicrometerTimerListenerEventTest.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * 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 static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class MicrometerTimerListenerEventTest { - MicrometerTimerListenerEvent event; - - @BeforeEach - void beforeEach() { - event = new MicrometerTimerListenerEvent(new SimpleMeterRegistry()); - } - - @Test - void shouldCreateTimerSampleOnStartAndReturnOnGetSample() { - // GIVEN - var initialSample = event.getSample(); - - // WHEN - event.start(); - - // THEN - var sample = event.getSample(); - assertNull(initialSample); - assertNotNull(sample); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalReactiveSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalReactiveSessionTest.java index cf97577931..0c79d4dfab 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalReactiveSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalReactiveSessionTest.java @@ -61,6 +61,8 @@ import org.neo4j.driver.internal.async.UnmanagedTransaction; import org.neo4j.driver.internal.cursor.RxResultCursor; import org.neo4j.driver.internal.cursor.RxResultCursorImpl; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.telemetry.ApiTelemetryWork; import org.neo4j.driver.internal.util.FixedRetryLogic; @@ -102,16 +104,16 @@ void shouldDelegateRun(Function> runR RxResultCursor cursor = mock(RxResultCursorImpl.class); // Run succeeded with a cursor - when(session.runRx(any(Query.class), any(TransactionConfig.class), any())) + when(session.runRx(any(Query.class), any(TransactionConfig.class), any(), any())) .thenReturn(completedFuture(cursor)); - var rxSession = new InternalReactiveSession(session); + var rxSession = new InternalReactiveSession(session, NoopObservationProvider.getInstance()); // When var result = flowPublisherToFlux(runReturnOne.apply(rxSession)); result.subscribe(); // Then - verify(session).runRx(any(Query.class), any(TransactionConfig.class), any()); + verify(session).runRx(any(Query.class), any(TransactionConfig.class), any(), any()); StepVerifier.create(result).expectNextCount(1).verifyComplete(); } @@ -123,18 +125,18 @@ void shouldReleaseConnectionIfFailedToRun(Function error == t).verify(); - verify(session).runRx(any(Query.class), any(TransactionConfig.class), any()); + verify(session).runRx(any(Query.class), any(TransactionConfig.class), any(), any()); verify(session).releaseConnectionAsync(); } @@ -146,16 +148,16 @@ void shouldDelegateBeginTx(Function Futures.getNow(txFuture)); MatcherAssert.assertThat(t.getCause(), equalTo(error)); verify(session).releaseConnectionAsync(); @@ -190,13 +192,13 @@ void shouldRetryOnError() { var retryCount = 2; var session = mock(NetworkSession.class); var tx = mock(UnmanagedTransaction.class); - when(tx.closeAsync(false)).thenReturn(completedWithNull()); + when(tx.closeAsync(false, NoopObservation.getInstance())).thenReturn(completedWithNull()); when(session.beginTransactionAsync( - any(AccessMode.class), any(TransactionConfig.class), any(ApiTelemetryWork.class))) + any(AccessMode.class), any(TransactionConfig.class), any(ApiTelemetryWork.class), any())) .thenReturn(completedFuture(tx)); when(session.retryLogic()).thenReturn(new FixedRetryLogic(retryCount)); - var rxSession = new InternalReactiveSession(session); + var rxSession = new InternalReactiveSession(session, NoopObservationProvider.getInstance()); // When var strings = rxSession.executeRead(t -> JdkFlowAdapter.publisherToFlowPublisher( @@ -209,8 +211,8 @@ void shouldRetryOnError() { // Then verify(session, times(retryCount + 1)) .beginTransactionAsync( - any(AccessMode.class), any(TransactionConfig.class), any(ApiTelemetryWork.class)); - verify(tx, times(retryCount + 1)).closeAsync(false); + any(AccessMode.class), any(TransactionConfig.class), any(ApiTelemetryWork.class), any()); + verify(tx, times(retryCount + 1)).closeAsync(false, NoopObservation.getInstance()); } @Test @@ -219,14 +221,14 @@ void shouldObtainResultIfRetrySucceed() { var retryCount = 2; var session = mock(NetworkSession.class); var tx = mock(UnmanagedTransaction.class); - when(tx.closeAsync(false)).thenReturn(completedWithNull()); - when(tx.closeAsync(true)).thenReturn(completedWithNull()); + when(tx.closeAsync(false, NoopObservation.getInstance())).thenReturn(completedWithNull()); + when(tx.closeAsync(true, NoopObservation.getInstance())).thenReturn(completedWithNull()); when(session.beginTransactionAsync( - any(AccessMode.class), any(TransactionConfig.class), any(ApiTelemetryWork.class))) + any(AccessMode.class), any(TransactionConfig.class), any(ApiTelemetryWork.class), any())) .thenReturn(completedFuture(tx)); when(session.retryLogic()).thenReturn(new FixedRetryLogic(retryCount)); - var rxSession = new InternalReactiveSession(session); + var rxSession = new InternalReactiveSession(session, NoopObservationProvider.getInstance()); // When var count = new AtomicInteger(); @@ -246,16 +248,16 @@ void shouldObtainResultIfRetrySucceed() { // Then verify(session, times(retryCount + 1)) .beginTransactionAsync( - any(AccessMode.class), any(TransactionConfig.class), any(ApiTelemetryWork.class)); - verify(tx, times(retryCount)).closeAsync(false); - verify(tx).closeAsync(true); + any(AccessMode.class), any(TransactionConfig.class), any(ApiTelemetryWork.class), any()); + verify(tx, times(retryCount)).closeAsync(false, NoopObservation.getInstance()); + verify(tx).closeAsync(true, NoopObservation.getInstance()); } @Test void shouldDelegateBookmarks() { // Given var session = mock(NetworkSession.class); - var rxSession = new InternalReactiveSession(session); + var rxSession = new InternalReactiveSession(session, NoopObservationProvider.getInstance()); // When rxSession.lastBookmarks(); @@ -269,15 +271,15 @@ void shouldDelegateBookmarks() { void shouldDelegateClose() { // Given var session = mock(NetworkSession.class); - when(session.closeAsync()).thenReturn(completedWithNull()); - var rxSession = new InternalReactiveSession(session); + when(session.closeAsync(NoopObservation.getInstance())).thenReturn(completedWithNull()); + var rxSession = new InternalReactiveSession(session, NoopObservationProvider.getInstance()); // When var publisher = rxSession.close(); // Then StepVerifier.create(JdkFlowAdapter.flowPublisherToFlux(publisher)).verifyComplete(); - verify(session).closeAsync(); + verify(session).closeAsync(NoopObservation.getInstance()); verifyNoMoreInteractions(session); } @@ -286,7 +288,7 @@ void shouldDelegateClose() { void shouldDelegateExecuteReadToRetryLogic(ExecuteVariation executeVariation) { // GIVEN var networkSession = mock(NetworkSession.class); - ReactiveSession session = new InternalReactiveSession(networkSession); + ReactiveSession session = new InternalReactiveSession(networkSession, NoopObservationProvider.getInstance()); var logic = mock(RetryLogic.class); var expected = ""; given(networkSession.retryLogic()).willReturn(logic); @@ -312,7 +314,7 @@ void shouldDelegateLastBookmarks() { var session = mock(NetworkSession.class); var expectedBookmarks = Set.of(mock(Bookmark.class)); given(session.lastBookmarks()).willReturn(expectedBookmarks); - var reactiveSession = new InternalReactiveSession(session); + var reactiveSession = new InternalReactiveSession(session, NoopObservationProvider.getInstance()); // When var bookmarks = reactiveSession.lastBookmarks(); diff --git a/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java b/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java index 5d8155c195..8f98f43649 100644 --- a/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java @@ -85,6 +85,7 @@ import org.neo4j.driver.internal.adaptedbolt.DriverResponseHandler; import org.neo4j.driver.internal.adaptedbolt.summary.PullSummary; import org.neo4j.driver.internal.async.NetworkSession; +import org.neo4j.driver.internal.observation.NoopObservationProvider; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.util.FixedRetryLogic; import org.neo4j.driver.internal.value.BoltValueFactory; @@ -232,22 +233,26 @@ public static NetworkSession newSession( null, telemetryDisabled, mock(AuthTokenManager.class), - mock()); + mock(), + NoopObservationProvider.getInstance()); } public static void setupConnectionAnswers(DriverBoltConnection connection, List messageHandlers) { for (var messageHandler : messageHandlers) { - given(connection.writeAndFlush(any(), ArgumentMatchers.>argThat(messages -> { - if (messages == null - || messages.size() - != messageHandler.messageTypes().size()) { - return false; - } - return IntStream.range(0, messages.size()).allMatch(i -> messageHandler - .messageTypes() - .get(i) - .isAssignableFrom(messages.get(i).getClass())); - }))) + given(connection.writeAndFlush( + any(), + ArgumentMatchers.>argThat(messages -> { + if (messages == null + || messages.size() + != messageHandler.messageTypes().size()) { + return false; + } + return IntStream.range(0, messages.size()).allMatch(i -> messageHandler + .messageTypes() + .get(i) + .isAssignableFrom(messages.get(i).getClass())); + }), + any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArguments()[0]; messageHandler.handle(handler); @@ -263,16 +268,21 @@ public interface MessageHandler { } public static void verifyAutocommitRunRx(DriverBoltConnection connection, String query) { - then(connection).should().writeAndFlush(any(), ArgumentMatchers.>argThat(argument -> { - var runMessage = (RunMessage) argument.get(0); - return runMessage.query().equals(query); - })); + then(connection) + .should() + .writeAndFlush( + any(), + ArgumentMatchers.>argThat(argument -> { + var runMessage = (RunMessage) argument.get(0); + return runMessage.query().equals(query); + }), + any()); } public static void verifyRun(DriverBoltConnection connection, String query) { @SuppressWarnings("unchecked") ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); - then(connection).should(atLeastOnce()).writeAndFlush(any(), captor.capture()); + then(connection).should(atLeastOnce()).writeAndFlush(any(), captor.capture(), any()); var messages = captor.getValue(); assertInstanceOf(RunMessage.class, messages.get(0)); assertEquals(query, ((RunMessage) messages.get(0)).query()); @@ -281,7 +291,7 @@ public static void verifyRun(DriverBoltConnection connection, String query) { public static void verifyRunAndPull(DriverBoltConnection connection, String query) { @SuppressWarnings("unchecked") ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); - then(connection).should(atLeastOnce()).writeAndFlush(any(), captor.capture()); + then(connection).should(atLeastOnce()).writeAndFlush(any(), captor.capture(), any()); var messages = captor.getAllValues().get(1); assertInstanceOf(RunMessage.class, messages.get(0)); assertEquals(query, ((RunMessage) messages.get(0)).query()); @@ -289,11 +299,16 @@ public static void verifyRunAndPull(DriverBoltConnection connection, String quer } public static void verifyAutocommitRunAndPull(DriverBoltConnection connection, String query) { - then(connection).should().writeAndFlush(any(), ArgumentMatchers.>argThat(argument -> { - var runMessage = (RunMessage) argument.get(0); - var pullMessage = (PullMessage) argument.get(1); - return runMessage.query().equals(query) && pullMessage != null; - })); + then(connection) + .should() + .writeAndFlush( + any(), + ArgumentMatchers.>argThat(argument -> { + var runMessage = (RunMessage) argument.get(0); + var pullMessage = (PullMessage) argument.get(1); + return runMessage.query().equals(query) && pullMessage != null; + }), + any()); } public static void verifyCommitTx(DriverBoltConnection connection, VerificationMode mode) { @@ -301,7 +316,8 @@ public static void verifyCommitTx(DriverBoltConnection connection, VerificationM .writeAndFlush( any(), ArgumentMatchers.>argThat( - messages -> messages.size() == 1 && messages.get(0) instanceof CommitMessage)); + messages -> messages.size() == 1 && messages.get(0) instanceof CommitMessage), + any()); } public static void verifyCommitTx(DriverBoltConnection connection) { @@ -314,7 +330,8 @@ public static void verifyRollbackTx(DriverBoltConnection connection, Verificatio .writeAndFlush( any(), ArgumentMatchers.>argThat( - messages -> messages.size() == 1 && messages.get(0) instanceof RollbackMessage)); + messages -> messages.size() == 1 && messages.get(0) instanceof RollbackMessage), + any()); } public static void verifyRollbackTx(DriverBoltConnection connection) { @@ -327,7 +344,8 @@ public static void setupFailingRun(DriverBoltConnection connection, Throwable er any(), ArgumentMatchers.>argThat(messages -> messages.size() == 2 && messages.get(0) instanceof RunMessage - && messages.get(1) instanceof PullMessage))) + && messages.get(1) instanceof PullMessage), + any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); handler.onError(error); @@ -343,7 +361,7 @@ public static void verifyBegin(DriverBoltConnection connection) { public static void verifyBegin(DriverBoltConnection connection, VerificationMode mode) { @SuppressWarnings("unchecked") ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); - then(connection).should(atLeastOnce()).writeAndFlush(any(), captor.capture()); + then(connection).should(atLeastOnce()).writeAndFlush(any(), captor.capture(), any()); var messages = captor.getAllValues().get(0); assertInstanceOf(BeginMessage.class, messages.get(0)); } @@ -353,7 +371,7 @@ public static void setupFailingCommit(DriverBoltConnection connection) { } public static void setupFailingCommit(DriverBoltConnection connection, int times) { - given(connection.writeAndFlush(any(), any(CommitMessage.class))) + given(connection.writeAndFlush(any(), any(CommitMessage.class), any())) .willAnswer(new Answer>() { int invoked; @@ -376,7 +394,7 @@ public static void setupFailingRollback(DriverBoltConnection connection) { } public static void setupFailingRollback(DriverBoltConnection connection, int times) { - given(connection.writeAndFlush(any(), any(RollbackMessage.class))) + given(connection.writeAndFlush(any(), any(RollbackMessage.class), any())) .willAnswer(new Answer>() { int invoked; @@ -399,7 +417,8 @@ public static void setupSuccessfulRunAndPull(DriverBoltConnection connection) { any(), ArgumentMatchers.>argThat(messages -> messages.size() == 2 && messages.get(0) instanceof RunMessage - && messages.get(1) instanceof PullMessage))) + && messages.get(1) instanceof PullMessage), + any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); var runSummary = mock(RunSummary.class); @@ -414,15 +433,18 @@ public static void setupSuccessfulRunAndPull(DriverBoltConnection connection) { } public static void setupSuccessfulAutocommitRunAndPull(DriverBoltConnection connection) { - given(connection.writeAndFlush(any(), ArgumentMatchers.>argThat(argument -> { - if (argument.size() == 1) { - return argument.get(0) instanceof RunMessage; - } else if (argument.size() == 2) { - return argument.get(0) instanceof RunMessage && argument.get(1) instanceof PullMessage; - } else { - return false; - } - }))) + given(connection.writeAndFlush( + any(), + ArgumentMatchers.>argThat(argument -> { + if (argument.size() == 1) { + return argument.get(0) instanceof RunMessage; + } else if (argument.size() == 2) { + return argument.get(0) instanceof RunMessage && argument.get(1) instanceof PullMessage; + } else { + return false; + } + }), + any())) .willAnswer((Answer>) invocation -> { var handler = (DriverResponseHandler) invocation.getArgument(0); var runSummary = mock(RunSummary.class); diff --git a/observation/LICENSES.txt b/observation/LICENSES.txt new file mode 100644 index 0000000000..f8e0fd3292 --- /dev/null +++ b/observation/LICENSES.txt @@ -0,0 +1,5 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + + diff --git a/observation/NOTICE.txt b/observation/NOTICE.txt new file mode 100644 index 0000000000..c3bf48c6fc --- /dev/null +++ b/observation/NOTICE.txt @@ -0,0 +1,20 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://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. + +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. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + diff --git a/observation/metrics/LICENSES.txt b/observation/metrics/LICENSES.txt new file mode 100644 index 0000000000..0e40a0207a --- /dev/null +++ b/observation/metrics/LICENSES.txt @@ -0,0 +1,238 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + +------------------------------------------------------------------------------ +Apache Software License, Version 2.0 + Neo4j Bolt Connection (Provider SPI) +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + + +------------------------------------------------------------------------------ +MIT No Attribution License + reactive-streams +------------------------------------------------------------------------------ + +MIT No Attribution + +Copyright + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + diff --git a/observation/metrics/NOTICE.txt b/observation/metrics/NOTICE.txt new file mode 100644 index 0000000000..f457013801 --- /dev/null +++ b/observation/metrics/NOTICE.txt @@ -0,0 +1,26 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://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. + +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. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + +Apache Software License, Version 2.0 + Neo4j Bolt Connection (Provider SPI) + +MIT No Attribution License + reactive-streams + diff --git a/observation/metrics/pom.xml b/observation/metrics/pom.xml new file mode 100644 index 0000000000..ac4ed99782 --- /dev/null +++ b/observation/metrics/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-parent + 6.0-SNAPSHOT + ../../pom.xml + + + neo4j-java-driver-observation-metrics + + Neo4j Java Driver (Metrics) + The Neo4j Java Driver Observation module providing metrics. + + + false + + + + + org.neo4j.driver + neo4j-java-driver + ${project.version} + + + org.neo4j.bolt + neo4j-bolt-connection-netty + + + org.neo4j.bolt + neo4j-bolt-connection-pooled + + + org.neo4j.bolt + neo4j-bolt-connection-routed + + + io.projectreactor + reactor-core + + + + + org.junit.jupiter + junit-jupiter + test + + + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + diff --git a/observation/metrics/src/main/java/module-info.java b/observation/metrics/src/main/java/module-info.java new file mode 100644 index 0000000000..f2ebccb795 --- /dev/null +++ b/observation/metrics/src/main/java/module-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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. + */ +/** + * The Neo4j Java Driver Observation module providing metrics. + */ +module org.neo4j.driver.observation.metrics { + exports org.neo4j.driver.observation.metrics; + + requires transitive org.neo4j.driver; + requires org.neo4j.bolt.connection; +} diff --git a/driver/src/main/java/org/neo4j/driver/ConnectionPoolMetrics.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/ConnectionPoolMetrics.java similarity index 95% rename from driver/src/main/java/org/neo4j/driver/ConnectionPoolMetrics.java rename to observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/ConnectionPoolMetrics.java index 401998d68e..2bac350f6c 100644 --- a/driver/src/main/java/org/neo4j/driver/ConnectionPoolMetrics.java +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/ConnectionPoolMetrics.java @@ -14,17 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver; +package org.neo4j.driver.observation.metrics; import java.util.concurrent.TimeUnit; -import org.neo4j.driver.util.Experimental; +import org.neo4j.driver.Config; +import org.neo4j.driver.observation.metrics.internal.InternalConnectionPoolMetrics; +import org.neo4j.driver.util.Preview; /** * Provides connection pool metrics such as connection created, current in use etc. * The pool metrics is uniquely identified using {@link ConnectionPoolMetrics#id()}. + * @since 6.0.0 */ -@Experimental -public interface ConnectionPoolMetrics { +@Preview(name = "Observability") +public sealed interface ConnectionPoolMetrics permits InternalConnectionPoolMetrics { /** * A unique id that identifies this pool metrics. * diff --git a/driver/src/main/java/org/neo4j/driver/Metrics.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/Metrics.java similarity index 80% rename from driver/src/main/java/org/neo4j/driver/Metrics.java rename to observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/Metrics.java index f8f1e0b207..d602c75400 100644 --- a/driver/src/main/java/org/neo4j/driver/Metrics.java +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/Metrics.java @@ -14,16 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver; +package org.neo4j.driver.observation.metrics; import java.util.Collection; -import org.neo4j.driver.util.Experimental; +import org.neo4j.driver.observation.metrics.internal.InternalMetrics; +import org.neo4j.driver.util.Preview; /** * Provides driver internal metrics. + * @since 6.0.0 */ -@Experimental -public interface Metrics { +@Preview(name = "Observability") +public sealed interface Metrics permits InternalMetrics { /** * Connection pool metrics records metrics of connection pools that are currently in use. * As the connection pools are dynamically added and removed while the server topology changes, the metrics collection changes over time. diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/MetricsObservationProvider.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/MetricsObservationProvider.java new file mode 100644 index 0000000000..861d9b63b1 --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/MetricsObservationProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics; + +import java.time.Clock; +import org.neo4j.driver.Config; +import org.neo4j.driver.observation.ObservationProvider; +import org.neo4j.driver.observation.metrics.internal.DriverMetricsObservationProvider; +import org.neo4j.driver.util.Preview; + +/** + * An {@link ObservationProvider} implementation that provides {@link Metrics} implementation. + * @since 6.0.0 + */ +@Preview(name = "Observability") +public sealed interface MetricsObservationProvider extends ObservationProvider + permits DriverMetricsObservationProvider { + /** + * Creates a new {@link MetricsObservationProvider} instance. + *

+ * To enable metrics, register the returned instance using {@link org.neo4j.driver.Config.ConfigBuilder#withObservationProvider(ObservationProvider)}. + * @return the new {@link MetricsObservationProvider} instance + */ + static MetricsObservationProvider newInstance() { + return new DriverMetricsObservationProvider(Clock.systemDefaultZone()); + } + + /** + * Creates a new {@link MetricsObservationProvider} instance and registers it with the provided {@link org.neo4j.driver.Config.ConfigBuilder}. + * @param configBuilder the config builder + * @return the new {@link MetricsObservationProvider} instance + */ + static MetricsObservationProvider newInstance(Config.ConfigBuilder configBuilder) { + var provider = newInstance(); + configBuilder.withObservationProvider(provider); + return provider; + } + + /** + * Returns {@link Metrics} instance managed by this provider. + * @return the {@link Metrics} instance + */ + Metrics metrics(); +} diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/AbstractObservation.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/AbstractObservation.java new file mode 100644 index 0000000000..3d1e52ea90 --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/AbstractObservation.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics.internal; + +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.Observation; + +abstract class AbstractObservation implements Observation { + @Override + public Observation start() { + return this; + } + + @Override + public Observation error(Throwable error) { + return this; + } + + @Override + public void stop() {} + + @Override + public Scope openScope() { + return NoopObservation.NoopScope.getInstance(); + } +} diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/DriverMetricsObservationProvider.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/DriverMetricsObservationProvider.java new file mode 100644 index 0000000000..b22b369a8e --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/DriverMetricsObservationProvider.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics.internal; + +import java.net.URI; +import java.time.Clock; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import org.neo4j.bolt.connection.BoltProtocolVersion; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.BaseSession; +import org.neo4j.driver.internal.observation.BoltExchangeObservation; +import org.neo4j.driver.internal.observation.BoltHandleObservation; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.HttpExchangeObservation; +import org.neo4j.driver.internal.observation.NoopBoltExchangeObservation; +import org.neo4j.driver.internal.observation.NoopBoltHandleObservation; +import org.neo4j.driver.internal.observation.NoopHttpExchangeObservation; +import org.neo4j.driver.internal.observation.NoopObservation; +import org.neo4j.driver.internal.observation.Observation; +import org.neo4j.driver.observation.metrics.Metrics; +import org.neo4j.driver.observation.metrics.MetricsObservationProvider; +import org.neo4j.driver.types.MapAccessor; + +public final class DriverMetricsObservationProvider implements MetricsObservationProvider, DriverObservationProvider { + private final InternalMetrics metrics; + private final Clock clock; + + public DriverMetricsObservationProvider(Clock clock) { + this.clock = Objects.requireNonNull(clock); + this.metrics = new InternalMetrics(); + } + + @Override + public Metrics metrics() { + return metrics; + } + + @Override + public Observation sessionRun(Class sessionType, String query, MapAccessor parameters) { + return NoopObservation.getInstance(); + } + + @Override + public Observation beginTransaction(Class transactionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation sessionExecute(Class sessionType, AccessMode mode) { + return NoopObservation.getInstance(); + } + + @Override + public Observation sessionClose(Class sessionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation transactionRun(Class transactionType, String query, MapAccessor parameters) { + return NoopObservation.getInstance(); + } + + @Override + public Observation transactionCommit(Class transactionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation transactionRollback(Class transactionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation transactionClose(Class transactionType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultPeek(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultNext(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultSingle(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultList(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultConsume(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation resultRecords(Class resultType) { + return NoopObservation.getInstance(); + } + + @Override + public Observation connectionPoolCreate(String id, URI uri, int maxSize) { + return new PoolCreateObservation(metrics, id); + } + + @Override + public Observation connectionPoolClose(String id, URI uri) { + return new PoolCloseObservation(metrics, id); + } + + @Override + public Observation pooledConnectionCreate(String id, URI uri) { + var poolMetrics = metrics.getConnectionPoolMetrics(id); + return new PoolConnectionCreateObservation(poolMetrics, clock); + } + + @Override + public Observation pooledConnectionClose(String id, URI uri) { + var poolMetrics = metrics.getConnectionPoolMetrics(id); + return new PoolConnectionCloseObservation(poolMetrics); + } + + @Override + public Observation pooledConnectionAcquire(String id, URI uri) { + var poolMetrics = metrics.getConnectionPoolMetrics(id); + return new PoolConnectionAcquireObservation(poolMetrics, clock); + } + + @Override + public Observation pooledConnectionInUse(String id, URI uri) { + var poolMetrics = metrics.getConnectionPoolMetrics(id); + return new PoolConnectionInUseObservation(poolMetrics, clock); + } + + @Override + public BoltHandleObservation boltHandle(List messageTypes) { + return NoopBoltHandleObservation.getInstance(); + } + + @Override + public BoltExchangeObservation boltExchange( + String host, int port, BoltProtocolVersion boltVersion, BiConsumer setter) { + return NoopBoltExchangeObservation.getInstance(); + } + + @Override + public HttpExchangeObservation httpExchange( + URI uri, String method, String uriTemplate, BiConsumer setter) { + return NoopHttpExchangeObservation.getInstance(); + } + + @Override + public Observation scopedObservation() { + return NoopObservation.getInstance(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalConnectionPoolMetrics.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/InternalConnectionPoolMetrics.java similarity index 67% rename from driver/src/main/java/org/neo4j/driver/internal/metrics/InternalConnectionPoolMetrics.java rename to observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/InternalConnectionPoolMetrics.java index 1ca179f509..89f1cc53c1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalConnectionPoolMetrics.java +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/InternalConnectionPoolMetrics.java @@ -14,22 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.metrics; +package org.neo4j.driver.observation.metrics.internal; import static java.lang.String.format; -import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.function.IntSupplier; -import org.neo4j.bolt.connection.BoltServerAddress; -import org.neo4j.bolt.connection.ListenerEvent; -import org.neo4j.driver.ConnectionPoolMetrics; +import org.neo4j.driver.observation.metrics.ConnectionPoolMetrics; -final class InternalConnectionPoolMetrics implements ConnectionPoolMetrics, ConnectionPoolMetricsListener { - private final BoltServerAddress address; - private final IntSupplier inUseSupplier; - private final IntSupplier idleSupplier; +public final class InternalConnectionPoolMetrics implements ConnectionPoolMetrics { + private final AtomicInteger inUse = new AtomicInteger(); + private final AtomicInteger idle = new AtomicInteger(); private final AtomicLong closed = new AtomicLong(); @@ -50,79 +45,59 @@ final class InternalConnectionPoolMetrics implements ConnectionPoolMetrics, Conn private final AtomicLong totalInUseCount = new AtomicLong(); private final String id; - InternalConnectionPoolMetrics( - String poolId, BoltServerAddress address, IntSupplier inUseSupplier, IntSupplier idleSupplier) { - Objects.requireNonNull(address); - Objects.requireNonNull(inUseSupplier); - Objects.requireNonNull(idleSupplier); - + InternalConnectionPoolMetrics(String poolId) { this.id = poolId; - this.address = address; - this.inUseSupplier = inUseSupplier; - this.idleSupplier = idleSupplier; } - @Override - public void beforeCreating(ListenerEvent connEvent) { + public void beforeCreating() { creating.incrementAndGet(); - connEvent.start(); } - @Override public void afterFailedToCreate() { failedToCreate.incrementAndGet(); creating.decrementAndGet(); } - @Override - public void afterCreated(ListenerEvent connEvent) { + public void afterCreated(long duration) { created.incrementAndGet(); creating.decrementAndGet(); - long sample = ((TimeRecorderListenerEvent) connEvent).getSample(); - - totalConnectionTime.addAndGet(sample); + totalConnectionTime.addAndGet(duration); + idle.incrementAndGet(); } - @Override public void afterClosed() { + idle.decrementAndGet(); closed.incrementAndGet(); } - @Override - public void beforeAcquiringOrCreating(ListenerEvent acquireEvent) { - acquireEvent.start(); + public void beforeAcquiringOrCreating() { acquiring.incrementAndGet(); } - @Override public void afterAcquiringOrCreating() { acquiring.decrementAndGet(); } - @Override - public void afterAcquiredOrCreated(ListenerEvent acquireEvent) { + public void afterAcquiredOrCreated(long duration) { + acquiring.decrementAndGet(); acquired.incrementAndGet(); - long sample = ((TimeRecorderListenerEvent) acquireEvent).getSample(); - - totalAcquisitionTime.addAndGet(sample); + totalAcquisitionTime.addAndGet(duration); } - @Override public void afterTimedOutToAcquireOrCreate() { timedOutToAcquire.incrementAndGet(); } - @Override - public void acquired(ListenerEvent inUseEvent) { - inUseEvent.start(); + public void onAcquired() { + inUse.incrementAndGet(); + idle.decrementAndGet(); } - @Override - public void released(ListenerEvent inUseEvent) { + public void released(long duration) { + inUse.decrementAndGet(); + idle.incrementAndGet(); totalInUseCount.incrementAndGet(); - long sample = ((TimeRecorderListenerEvent) inUseEvent).getSample(); - - totalInUseTime.addAndGet(sample); + totalInUseTime.addAndGet(duration); } @Override @@ -132,12 +107,12 @@ public String id() { @Override public int inUse() { - return inUseSupplier.getAsInt(); + return inUse.get(); } @Override public int idle() { - return idleSupplier.getAsInt(); + return idle.get(); } @Override @@ -216,10 +191,4 @@ public String toString() { totalInUseTime(), totalInUseCount()); } - - // This is used by the Testkit backend - @SuppressWarnings("unused") - public BoltServerAddress getAddress() { - return address; - } } diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/InternalMetrics.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/InternalMetrics.java new file mode 100644 index 0000000000..95c2fc368f --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/InternalMetrics.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics.internal; + +import static java.lang.String.format; +import static java.util.Collections.unmodifiableCollection; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.neo4j.driver.observation.ObservationProvider; +import org.neo4j.driver.observation.metrics.ConnectionPoolMetrics; +import org.neo4j.driver.observation.metrics.Metrics; + +public final class InternalMetrics implements Metrics { + private final Map connectionPoolMetrics; + private ObservationProvider observationProvider; + + public InternalMetrics() { + this.connectionPoolMetrics = new ConcurrentHashMap<>(); + } + + public void registerPoolMetrics(String id) { + this.connectionPoolMetrics.put(id, new InternalConnectionPoolMetrics(id)); + } + + public void deregisterPoolMetrics(String id) { + this.connectionPoolMetrics.remove(id); + } + + public InternalConnectionPoolMetrics getConnectionPoolMetrics(String id) { + return (InternalConnectionPoolMetrics) connectionPoolMetrics.get(id); + } + + @Override + public Collection connectionPoolMetrics() { + return unmodifiableCollection(this.connectionPoolMetrics.values()); + } + + @Override + public String toString() { + return format("PoolMetrics=%s", connectionPoolMetrics); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetricsProvider.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolCloseObservation.java similarity index 55% rename from driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetricsProvider.java rename to observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolCloseObservation.java index 1318a5f309..be3f066eac 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/InternalMetricsProvider.java +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolCloseObservation.java @@ -14,27 +14,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.metrics; +package org.neo4j.driver.observation.metrics.internal; -import java.time.Clock; -import org.neo4j.bolt.connection.MetricsListener; -import org.neo4j.driver.Logging; -import org.neo4j.driver.Metrics; +import java.util.Objects; -public final class InternalMetricsProvider implements MetricsProvider { +final class PoolCloseObservation extends AbstractObservation { private final InternalMetrics metrics; + private final String id; - public InternalMetricsProvider(Clock clock, @SuppressWarnings("deprecation") Logging logging) { - this.metrics = new InternalMetrics(clock, logging); + PoolCloseObservation(InternalMetrics metrics, String id) { + this.metrics = Objects.requireNonNull(metrics); + this.id = Objects.requireNonNull(id); } @Override - public Metrics metrics() { - return metrics; - } - - @Override - public MetricsListener metricsListener() { - return metrics; + public void stop() { + metrics.deregisterPoolMetrics(id); } } diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionAcquireObservation.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionAcquireObservation.java new file mode 100644 index 0000000000..1da91fba0e --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionAcquireObservation.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics.internal; + +import java.time.Clock; +import java.util.Objects; +import java.util.concurrent.TimeoutException; +import org.neo4j.driver.internal.observation.Observation; + +final class PoolConnectionAcquireObservation extends AbstractObservation { + private final InternalConnectionPoolMetrics metrics; + private final Clock clock; + private volatile long start; + private volatile boolean failed; + + PoolConnectionAcquireObservation(InternalConnectionPoolMetrics metrics, Clock clock) { + this.metrics = Objects.requireNonNull(metrics); + this.clock = Objects.requireNonNull(clock); + } + + @Override + public Observation start() { + metrics.beforeAcquiringOrCreating(); + start = clock.millis(); + return this; + } + + @Override + public Observation error(Throwable error) { + failed = true; + if (error instanceof TimeoutException) { + metrics.afterTimedOutToAcquireOrCreate(); + } + return this; + } + + @Override + public void stop() { + if (failed) { + metrics.afterAcquiringOrCreating(); + } else { + var duration = clock.millis() - start; + metrics.afterAcquiredOrCreated(duration); + } + } +} diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionCloseObservation.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionCloseObservation.java new file mode 100644 index 0000000000..f659a59ee1 --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionCloseObservation.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics.internal; + +import java.util.Objects; + +final class PoolConnectionCloseObservation extends AbstractObservation { + private final InternalConnectionPoolMetrics metrics; + + PoolConnectionCloseObservation(InternalConnectionPoolMetrics metrics) { + this.metrics = Objects.requireNonNull(metrics); + } + + @Override + public void stop() { + metrics.afterClosed(); + } +} diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionCreateObservation.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionCreateObservation.java new file mode 100644 index 0000000000..c1ab32e9d1 --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionCreateObservation.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics.internal; + +import java.time.Clock; +import java.util.Objects; +import org.neo4j.driver.internal.observation.Observation; + +final class PoolConnectionCreateObservation extends AbstractObservation { + private final InternalConnectionPoolMetrics metrics; + private final Clock clock; + private volatile long start; + + PoolConnectionCreateObservation(InternalConnectionPoolMetrics metrics, Clock clock) { + this.metrics = Objects.requireNonNull(metrics); + this.clock = Objects.requireNonNull(clock); + } + + @Override + public Observation start() { + metrics.beforeCreating(); + start = clock.millis(); + return this; + } + + @Override + public Observation error(Throwable error) { + metrics.afterFailedToCreate(); + return this; + } + + @Override + public void stop() { + var duration = clock.millis() - start; + metrics.afterCreated(duration); + } +} diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionInUseObservation.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionInUseObservation.java new file mode 100644 index 0000000000..5914a1c0c7 --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolConnectionInUseObservation.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics.internal; + +import java.time.Clock; +import java.util.Objects; +import org.neo4j.driver.internal.observation.Observation; + +final class PoolConnectionInUseObservation extends AbstractObservation { + private final InternalConnectionPoolMetrics metrics; + private final Clock clock; + private volatile long start; + + PoolConnectionInUseObservation(InternalConnectionPoolMetrics metrics, Clock clock) { + this.metrics = Objects.requireNonNull(metrics); + this.clock = Objects.requireNonNull(clock); + } + + @Override + public Observation start() { + metrics.onAcquired(); + start = clock.millis(); + return this; + } + + @Override + public void stop() { + var duration = clock.millis() - start; + metrics.released(duration); + } +} diff --git a/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolCreateObservation.java b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolCreateObservation.java new file mode 100644 index 0000000000..827c6ae1d8 --- /dev/null +++ b/observation/metrics/src/main/java/org/neo4j/driver/observation/metrics/internal/PoolCreateObservation.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.metrics.internal; + +import java.util.Objects; + +final class PoolCreateObservation extends AbstractObservation { + private final InternalMetrics metrics; + private final String id; + + PoolCreateObservation(InternalMetrics metrics, String id) { + this.metrics = Objects.requireNonNull(metrics); + this.id = Objects.requireNonNull(id); + } + + @Override + public void stop() { + metrics.registerPoolMetrics(id); + } +} diff --git a/observation/micrometer/LICENSES.txt b/observation/micrometer/LICENSES.txt new file mode 100644 index 0000000000..05474904a2 --- /dev/null +++ b/observation/micrometer/LICENSES.txt @@ -0,0 +1,242 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + + +------------------------------------------------------------------------------ +Apache Software License, Version 2.0 + micrometer-commons + micrometer-core + micrometer-observation + Neo4j Bolt Connection (Provider SPI) + Non-Blocking Reactive Foundation for the JVM +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + + +------------------------------------------------------------------------------ +MIT No Attribution License + reactive-streams +------------------------------------------------------------------------------ + +MIT No Attribution + +Copyright + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + diff --git a/observation/micrometer/NOTICE.txt b/observation/micrometer/NOTICE.txt new file mode 100644 index 0000000000..4d9b47b6d4 --- /dev/null +++ b/observation/micrometer/NOTICE.txt @@ -0,0 +1,30 @@ +Copyright (c) "Neo4j" +Neo4j Sweden AB [https://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. + +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. + +Full license texts are found in LICENSES.txt. + + +Third-party licenses +-------------------- + +Apache Software License, Version 2.0 + micrometer-commons + micrometer-core + micrometer-observation + Neo4j Bolt Connection (Provider SPI) + Non-Blocking Reactive Foundation for the JVM + +MIT No Attribution License + reactive-streams + diff --git a/observation/micrometer/pom.xml b/observation/micrometer/pom.xml new file mode 100644 index 0000000000..45ae597b88 --- /dev/null +++ b/observation/micrometer/pom.xml @@ -0,0 +1,165 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-parent + 6.0-SNAPSHOT + ../../pom.xml + + + neo4j-java-driver-observation-micrometer + + Neo4j Java Driver (Micrometer Observation) + The Neo4j Java Driver Observation module integrating with Micrometer Observation API. + + + ${project.build.sourceDirectory}/org/neo4j/driver/observation/micrometer + .* + ${project.build.directory}/docs + false + + + + + org.neo4j.driver + neo4j-java-driver + ${project.version} + + + org.neo4j.bolt + neo4j-bolt-connection-netty + + + org.neo4j.bolt + neo4j-bolt-connection-pooled + + + org.neo4j.bolt + neo4j-bolt-connection-routed + + + + + io.micrometer + micrometer-observation + + + io.micrometer + micrometer-core + true + + + org.junit.jupiter + junit-jupiter + test + + + + + + + io.micrometer + micrometer-bom + ${micrometer.version} + pom + import + + + + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-docs + prepare-package + + copy-resources + + + ${micrometer-docs-generator.outputPath} + + + src/main/asciidoc + false + + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + generate-docs + prepare-package + + java + + + io.micrometer.docs.DocsGeneratorCommand + true + + ${micrometer-docs-generator.inputPath} + ${micrometer-docs-generator.inclusionPattern} + ${micrometer-docs-generator.outputPath} + + + + + + + io.micrometer + micrometer-docs-generator + ${micrometer-docs-generator.version} + jar + + + + + + + + + generate-html + + + + + org.asciidoctor + asciidoctor-maven-plugin + + + asciidoc-to-html + prepare-package + + process-asciidoc + + + ${project.build.directory}/docs + ${project.build.directory}/html + + + + + + + + + + + diff --git a/observation/micrometer/src/main/asciidoc/_metrics-extra.adoc b/observation/micrometer/src/main/asciidoc/_metrics-extra.adoc new file mode 100644 index 0000000000..f7499281b0 --- /dev/null +++ b/observation/micrometer/src/main/asciidoc/_metrics-extra.adoc @@ -0,0 +1,116 @@ +[[observability-metrics-extra]] +=== Observability - Metrics Extra + +Below are metrics managed by `org.neo4j.driver.observation.micrometer.ConnectionPoolMetricsHandler`. +Note that this handler MUST be registered with `io.micrometer.observation.ObservationRegistry` to enable it. + +[[observability-metrics-extra-pool-connection-count]] +==== Connection Pool Count + +____ +Shows how many connections are `used` or `idle` in the connection pool. +____ + + +**Metric name** `db.client.connection.count`. **Type** `gauge`. + + + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`db.system.name` _(required)_|The DBMS product name. It is always *neo4j*. +|`db.client.connection.pool.name` _(required)_|The pool name in the following format: *host[:port]-id*. Note that the port part is optional if default scheme port is used. +|`db.client.connection.state` _(required)_|The connection state, either `idle` or `used`. +|=== + + + +[[observability-metrics-extra-pooled-connection-creating]] +==== Pooled Connection Creating + +____ +Shows how many connections are being created by connection pool. +____ + + +**Metric name** `neo4j.db.client.connection.creating.requests`. **Type** `gauge`. + + + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`db.system.name` _(required)_|The DBMS product name. It is always *neo4j*. +|`db.client.connection.pool.name` _(required)_|The pool name in the following format: *host[:port]-id*. Note that the port part is optional if default scheme port is used. +|=== + + + +[[observability-metrics-extra-pooled-connection-pending-requests]] +==== Pooled Connection Pending Requests + +____ +Shows the number of pending requests for pooled connection. +____ + + +**Metric name** `db.client.connection.pending.requests`. **Type** `gauge`. + + + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`db.system.name` _(required)_|The DBMS product name. It is always *neo4j*. +|`db.client.connection.pool.name` _(required)_|The pool name in the following format: *host[:port]-id*. Note that the port part is optional if default scheme port is used. +|=== + + + +[[observability-metrics-extra-connection-max]] +==== Connection Pool Max + +____ +Shows the maximum connections allowed in the connection pool. +____ + + +**Metric name** `db.client.connection.max`. **Type** `gauge`. + + + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`db.system.name` _(required)_|The DBMS product name. It is always *neo4j*. +|`db.client.connection.pool.name` _(required)_|The pool name in the following format: *host[:port]-id*. Note that the port part is optional if default scheme port is used. +|=== + + + +[[observability-metrics-extra-pool-connection-timeouts]] +==== Connection Pool Timeouts + +____ +Shows how many requests failed with timeout. +____ + + +**Metric name** `db.client.connection.timeouts`. **Type** `counter`. + + + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`db.system.name` _(required)_|The DBMS product name. It is always *neo4j*. +|`db.client.connection.pool.name` _(required)_|The pool name in the following format: *host[:port]-id*. Note that the port part is optional if default scheme port is used. +|=== + + diff --git a/observation/micrometer/src/main/asciidoc/index.adoc b/observation/micrometer/src/main/asciidoc/index.adoc new file mode 100644 index 0000000000..310a425496 --- /dev/null +++ b/observation/micrometer/src/main/asciidoc/index.adoc @@ -0,0 +1,7 @@ += Neo4j Java Driver Observation Documentation +:toc: + +include::_metrics.adoc[] +include::_metrics-extra.adoc[] +include::_spans.adoc[] +include::_conventions.adoc[] diff --git a/observation/micrometer/src/main/java/module-info.java b/observation/micrometer/src/main/java/module-info.java new file mode 100644 index 0000000000..2c71e09473 --- /dev/null +++ b/observation/micrometer/src/main/java/module-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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. + */ +/** + * The Neo4j Java Driver Observation module integrating with Micrometer Observation API. + */ +@SuppressWarnings({"requires-automatic", "requires-transitive-automatic"}) +module org.neo4j.driver.observation.micrometer { + exports org.neo4j.driver.observation.micrometer; + + requires transitive org.neo4j.driver; + requires transitive micrometer.observation; + requires transitive micrometer.commons; + requires transitive static micrometer.core; + requires reactor.core; + requires org.neo4j.bolt.connection; +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractConnectionPoolContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractConnectionPoolContext.java new file mode 100644 index 0000000000..76bf5330bb --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractConnectionPoolContext.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.net.URI; +import java.util.Objects; + +abstract class AbstractConnectionPoolContext extends Observation.Context { + private final String id; + private final URI uri; + + AbstractConnectionPoolContext(String id, URI uri) { + this.id = Objects.requireNonNull(id); + this.uri = Objects.requireNonNull(uri); + } + + public String id() { + return id; + } + + public URI uri() { + return uri; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractResultContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractResultContext.java new file mode 100644 index 0000000000..89e94ade10 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractResultContext.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; + +abstract class AbstractResultContext extends Observation.Context { + private final Class resultType; + + AbstractResultContext(Class resultType) { + this.resultType = Objects.requireNonNull(resultType); + } + + public Class resultType() { + return resultType; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractSessionContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractSessionContext.java new file mode 100644 index 0000000000..e5196e8e48 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractSessionContext.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; +import org.neo4j.driver.BaseSession; + +abstract class AbstractSessionContext extends Observation.Context { + private final Class sessionType; + + AbstractSessionContext(Class sessionType) { + this.sessionType = Objects.requireNonNull(sessionType); + } + + public Class sessionType() { + return sessionType; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractTransactionContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractTransactionContext.java new file mode 100644 index 0000000000..fea078b379 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/AbstractTransactionContext.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.Objects; + +abstract class AbstractTransactionContext extends Observation.Context { + private final Class transactionType; + + AbstractTransactionContext(Class transactionType) { + this.transactionType = Objects.requireNonNull(transactionType); + } + + public Class transactionType() { + return transactionType; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeContext.java new file mode 100644 index 0000000000..809248fae4 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeContext.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.transport.Kind; +import io.micrometer.observation.transport.SenderContext; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; + +public class BoltExchangeContext extends SenderContext { + private final String host; + private final int port; + private final String boltVersion; + private volatile List messageNames; + + public BoltExchangeContext(String host, int port, String boltVersion, BiConsumer setter) { + super((carrier, key, value) -> setter.accept(key, value), Kind.CLIENT); + this.host = Objects.requireNonNull(host); + this.port = port; + this.boltVersion = Objects.requireNonNull(boltVersion); + } + + public String host() { + return host; + } + + public int port() { + return port; + } + + public String boltVersion() { + return boltVersion; + } + + public void setMessageNames(List messageNames) { + this.messageNames = messageNames; + } + + public Optional> messageNames() { + return Optional.ofNullable(messageNames); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeConvention.java new file mode 100644 index 0000000000..605933ea52 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeConvention.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; + +public interface BoltExchangeConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof BoltExchangeContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeObservationImpl.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeObservationImpl.java new file mode 100644 index 0000000000..69cd92fc36 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltExchangeObservationImpl.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import org.neo4j.driver.internal.observation.BoltExchangeObservation; + +final class BoltExchangeObservationImpl extends ObservationImpl implements BoltExchangeObservation { + private final BoltExchangeContext context; + private final List messageNames = Collections.synchronizedList(new ArrayList<>()); + + BoltExchangeObservationImpl(Observation delegate) { + super(delegate); + this.context = (BoltExchangeContext) Objects.requireNonNull(delegate.getContext()); + } + + @Override + public BoltExchangeObservation start() { + super.start(); + return this; + } + + @Override + public BoltExchangeObservation onWrite(String messageName) { + messageNames.add(messageName); + return this; + } + + @Override + public BoltExchangeObservation onRecord() { + return this; + } + + @Override + public BoltExchangeObservation onSummary(String messageName) { + delegate.event(Observation.Event.of( + "summary.%s".formatted(messageName.toLowerCase()), "%s summary".formatted(messageName))); + return this; + } + + @Override + public BoltExchangeObservation error(Throwable error) { + super.error(error); + return this; + } + + @Override + public void stop() { + context.setMessageNames(messageNames); + super.stop(); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleContext.java new file mode 100644 index 0000000000..0ef3a30f31 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleContext.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.List; +import java.util.Objects; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class BoltHandleContext extends Observation.Context { + private final List messageNames; + + public BoltHandleContext(List messageNames) { + this.messageNames = Objects.requireNonNull(messageNames); + } + + public List messageNames() { + return messageNames; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleConvention.java new file mode 100644 index 0000000000..94d8104179 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface BoltHandleConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof BoltHandleContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleObservationImpl.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleObservationImpl.java new file mode 100644 index 0000000000..cc69f46576 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BoltHandleObservationImpl.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import org.neo4j.driver.internal.observation.BoltHandleObservation; + +final class BoltHandleObservationImpl extends ObservationImpl implements BoltHandleObservation { + BoltHandleObservationImpl(Observation delegate) { + super(delegate); + } + + @Override + public BoltHandleObservation start() { + super.start(); + return this; + } + + @Override + public BoltHandleObservation onIgnored() { + delegate.event(Observation.Event.of("ignored", "IGNORED")); + return this; + } + + @Override + public BoltHandleObservation onSummary(String messageName) { + delegate.event(Observation.Event.of( + "summary.%s".formatted(messageName.toLowerCase()), "%s summary".formatted(messageName))); + return this; + } + + @Override + public BoltHandleObservation error(Throwable error) { + super.error(error); + return this; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BuilderImpl.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BuilderImpl.java new file mode 100644 index 0000000000..0d4f087b8b --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/BuilderImpl.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.ObservationRegistry; +import java.util.Objects; +import java.util.function.Predicate; + +final class BuilderImpl implements MicrometerObservationProvider.Builder { + private final ObservationRegistry observationRegistry; + private boolean alwaysIncludeQuery; + private boolean includeQueryParameters; + private boolean includeUrlScheme; + private boolean includeUrlTemplate; + private Predicate requestHeaderPredicate; + private Predicate responseHeaderPredicate; + + BuilderImpl(ObservationRegistry observationRegistry) { + this.observationRegistry = Objects.requireNonNull(observationRegistry); + } + + @Override + public MicrometerObservationProvider.Builder alwaysIncludeQuery(boolean alwaysIncludeQuery) { + this.alwaysIncludeQuery = alwaysIncludeQuery; + return this; + } + + @Override + public MicrometerObservationProvider.Builder includeQueryParameters(boolean includeQueryParameters) { + this.includeQueryParameters = includeQueryParameters; + return this; + } + + @Override + public MicrometerObservationProvider.Builder includeUrlScheme(boolean includeUrlScheme) { + this.includeUrlScheme = includeUrlScheme; + return this; + } + + @Override + public MicrometerObservationProvider.Builder includeUrlTemplate(boolean includeUrlTemplate) { + this.includeUrlTemplate = includeUrlTemplate; + return this; + } + + @Override + public MicrometerObservationProvider.Builder requestHeaderPredicate(Predicate requestHeaderPredicate) { + this.requestHeaderPredicate = requestHeaderPredicate; + return this; + } + + @Override + public MicrometerObservationProvider.Builder responseHeaderPredicate(Predicate responseHeaderPredicate) { + this.responseHeaderPredicate = responseHeaderPredicate; + return this; + } + + @Override + public MicrometerObservationProvider build() { + return new DriverMicrometerObservationProvider( + observationRegistry, + alwaysIncludeQuery, + includeQueryParameters, + includeUrlScheme, + includeUrlTemplate, + requestHeaderPredicate, + responseHeaderPredicate); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCloseContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCloseContext.java new file mode 100644 index 0000000000..611bc513b9 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCloseContext.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import java.net.URI; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class ConnectionPoolCloseContext extends AbstractConnectionPoolContext { + public ConnectionPoolCloseContext(String id, URI uri) { + super(id, uri); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCloseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCloseConvention.java new file mode 100644 index 0000000000..d7e546b852 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCloseConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface ConnectionPoolCloseConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof ConnectionPoolCloseContext; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/MetricsAdapter.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCreateContext.java similarity index 55% rename from driver/src/main/java/org/neo4j/driver/MetricsAdapter.java rename to observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCreateContext.java index 4080186966..30b1c754f2 100644 --- a/driver/src/main/java/org/neo4j/driver/MetricsAdapter.java +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCreateContext.java @@ -14,24 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver; +package org.neo4j.driver.observation.micrometer; + +import java.net.URI; +import org.neo4j.driver.util.Preview; /** - * 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. + * @since 6.0.0 */ -public enum MetricsAdapter { - /** - * Disables metrics. - */ - DEV_NULL, +@Preview(name = "Observability") +public class ConnectionPoolCreateContext extends AbstractConnectionPoolContext { + private final int maxSize; - /** - * Consumes and publishes metrics via the driver itself. - */ - DEFAULT, + public ConnectionPoolCreateContext(String id, URI uri, int maxSize) { + super(id, uri); + this.maxSize = maxSize; + } - /** - * Consumes and publishes metrics via Micrometer. Ensure that Micrometer is on classpath when using this option. - */ - MICROMETER + public int maxSize() { + return maxSize; + } } diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCreateConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCreateConvention.java new file mode 100644 index 0000000000..161d72f6db --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolCreateConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface ConnectionPoolCreateConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof ConnectionPoolCreateContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolMetricsHandler.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolMetricsHandler.java new file mode 100644 index 0000000000..e5269afd98 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ConnectionPoolMetricsHandler.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationHandler; +import java.net.URI; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public final class ConnectionPoolMetricsHandler implements ObservationHandler { + private static final String COUNT_METRIC_NAME = "db.client.connection.count"; + private static final String POOL_TAG_NAME = "db.client.connection.pool.name"; + private static final String STATE_TAG_NAME = "db.client.connection.state"; + private static final String IDLE = "idle"; + private static final String USED = "used"; + private static final String AQUIRING_METRIC_NAME = "db.client.connection.pending.requests"; + private static final String CREATING_METRIC_NAME = "neo4j.db.client.connection.creating.requests"; + private static final String DB_SYSTEM_TAG_NAME = "db.system.name"; + private static final String MAX_SIZE_METRIC_NAME = "db.client.connection.max"; + private static final String TIMEOUTS_METRIC_NAME = "db.client.connection.timeouts"; + + private final MeterRegistry meterRegistry; + private final Map idToMetrics; + + public ConnectionPoolMetricsHandler(MeterRegistry meterRegistry) { + this.meterRegistry = Objects.requireNonNull(meterRegistry); + this.idToMetrics = new ConcurrentHashMap<>(); + } + + @Override + public boolean supportsContext(Observation.Context context) { + return context instanceof AbstractConnectionPoolContext; + } + + @Override + public void onStart(Observation.Context context) { + try { + if (context instanceof AbstractConnectionPoolContext poolContext) { + var metrics = idToMetrics.get(poolContext.id()); + if (metrics == null) { + return; + } + if (context instanceof PooledConnectionAcquireContext) { + metrics.onAcquiring(); + } else if (context instanceof PooledConnectionCreateContext) { + metrics.onCreating(); + } else if (context instanceof PooledConnectionInUseContext) { + metrics.onInUseStart(); + } else if (context instanceof PooledConnectionCloseContext) { + metrics.onClosing(); + } + } + } catch (Exception ignored) { + } + } + + @Override + public void onStop(Observation.Context context) { + try { + if (context instanceof ConnectionPoolCreateContext poolCreateContext) { + if (context.getError() != null) { + return; + } + idToMetrics.computeIfAbsent( + poolCreateContext.id(), + id -> new ConnectionPoolMetrics( + meterRegistry, id, poolCreateContext.uri(), poolCreateContext.maxSize())); + } else if (context instanceof AbstractConnectionPoolContext poolContext) { + var metrics = idToMetrics.get(poolContext.id()); + if (metrics == null) { + return; + } + if (context instanceof ConnectionPoolCloseContext) { + metrics.close(); + } else if (context instanceof PooledConnectionAcquireContext) { + var error = context.getError(); + if (error != null) { + if (error instanceof TimeoutException) { + metrics.onTimeout(); + } + return; + } + metrics.onAcquired(); + } else if (context instanceof PooledConnectionCreateContext) { + if (context.getError() != null) { + metrics.onFailedToCreate(); + } else { + metrics.onCreated(); + } + } else if (context instanceof PooledConnectionInUseContext) { + metrics.onInUseStop(); + } + } + } catch (Exception ignored) { + } + } + + private static final class ConnectionPoolMetrics { + private final MeterRegistry meterRegistry; + private final AtomicInteger inUse; + private final AtomicInteger idle; + private final AtomicInteger creating; + private final AtomicInteger acquiring; + private final AtomicInteger maxSize; + + private final Gauge inUseGauge; + private final Gauge idleGauge; + private final Gauge creatingGauge; + private final Gauge acquiringGauge; + private final Gauge maxSizeGauge; + private final Counter timeouts; + + ConnectionPoolMetrics(MeterRegistry meterRegistry, String id, URI uri, int maxSize) { + this.meterRegistry = Objects.requireNonNull(meterRegistry); + + this.inUse = new AtomicInteger(0); + this.idle = new AtomicInteger(0); + this.creating = new AtomicInteger(0); + this.acquiring = new AtomicInteger(0); + this.maxSize = new AtomicInteger(maxSize); + + var baseTags = Tags.of(DB_SYSTEM_TAG_NAME, KeyValuesUtil.DB_SYSTEM_NAME, POOL_TAG_NAME, id); + + this.inUseGauge = Gauge.builder(COUNT_METRIC_NAME, this.inUse, AtomicInteger::get) + .tags(Tags.concat(baseTags, STATE_TAG_NAME, USED)) + .register(meterRegistry); + this.idleGauge = Gauge.builder(COUNT_METRIC_NAME, this.idle, AtomicInteger::get) + .tags(Tags.concat(baseTags, STATE_TAG_NAME, IDLE)) + .register(meterRegistry); + this.creatingGauge = Gauge.builder(CREATING_METRIC_NAME, this.creating, AtomicInteger::get) + .tags(baseTags) + .register(meterRegistry); + this.acquiringGauge = Gauge.builder(AQUIRING_METRIC_NAME, this.acquiring, AtomicInteger::get) + .tags(baseTags) + .register(meterRegistry); + this.maxSizeGauge = Gauge.builder(MAX_SIZE_METRIC_NAME, this.maxSize, AtomicInteger::get) + .tags(baseTags) + .register(meterRegistry); + this.timeouts = Counter.builder(TIMEOUTS_METRIC_NAME).tags(baseTags).register(meterRegistry); + } + + void onAcquiring() { + acquiring.incrementAndGet(); + } + + void onTimeout() { + this.timeouts.increment(); + } + + void onAcquired() { + acquiring.decrementAndGet(); + } + + void onCreating() { + creating.incrementAndGet(); + } + + void onFailedToCreate() { + creating.decrementAndGet(); + } + + void onCreated() { + creating.decrementAndGet(); + idle.incrementAndGet(); + } + + void onInUseStart() { + inUse.incrementAndGet(); + idle.decrementAndGet(); + } + + void onInUseStop() { + inUse.decrementAndGet(); + idle.incrementAndGet(); + } + + void onClosing() { + idle.decrementAndGet(); + } + + void close() { + meterRegistry.remove(inUseGauge); + meterRegistry.remove(idleGauge); + meterRegistry.remove(creatingGauge); + meterRegistry.remove(acquiringGauge); + meterRegistry.remove(maxSizeGauge); + meterRegistry.remove(timeouts); + } + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultBoltExchangeConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultBoltExchangeConvention.java new file mode 100644 index 0000000000..9b760ae880 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultBoltExchangeConvention.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import java.util.List; + +public class DefaultBoltExchangeConvention implements BoltExchangeConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.BoltExchangeLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + private static final KeyValue NETWORK_PROTOCOL_NAME = + Neo4jDriverDocumentation.BoltExchangeLowCardinalityKeyNames.NETWORK_PROTOCOL_NAME.withValue("bolt"); + + static final DefaultBoltExchangeConvention INSTANCE = new DefaultBoltExchangeConvention(); + + public DefaultBoltExchangeConvention() {} + + @Override + public String getName() { + return "neo4j.bolt.client.exchange.duration"; + } + + @Override + public String getContextualName(BoltExchangeContext context) { + return "bolt exchange"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(BoltExchangeContext context) { + return KeyValues.of( + DB_SYSTEM_NAME, + NETWORK_PROTOCOL_NAME, + boltVersion(context), + serverAddress(context), + serverPort(context)); + } + + @Override + public KeyValues getHighCardinalityKeyValues(BoltExchangeContext context) { + return KeyValues.of(messages(context)); + } + + private KeyValue boltVersion(BoltExchangeContext context) { + return Neo4jDriverDocumentation.BoltExchangeLowCardinalityKeyNames.NETWORK_PROTOCOL_VERSION.withValue( + context.boltVersion()); + } + + private KeyValue serverAddress(BoltExchangeContext context) { + return Neo4jDriverDocumentation.BoltExchangeLowCardinalityKeyNames.SERVER_ADDRESS.withValue(context.host()); + } + + private KeyValue serverPort(BoltExchangeContext context) { + return Neo4jDriverDocumentation.BoltExchangeLowCardinalityKeyNames.SERVER_PORT.withValue( + String.valueOf(context.port())); + } + + private KeyValue messages(BoltExchangeContext context) { + return Neo4jDriverDocumentation.BoltExchangeHighCardinalityKeyNames.MESSAGES.withValue( + context.messageNames().map(List::toString).orElse(KeyValue.NONE_VALUE)); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultBoltHandleConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultBoltHandleConvention.java new file mode 100644 index 0000000000..eb9e878473 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultBoltHandleConvention.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultBoltHandleConvention implements BoltHandleConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.BoltHandleLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultBoltHandleConvention INSTANCE = new DefaultBoltHandleConvention(); + + public DefaultBoltHandleConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.bolt.handle.duration"; + } + + @Override + public String getContextualName(BoltHandleContext context) { + return "bolt handle"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(BoltHandleContext context) { + return KeyValues.of(DB_SYSTEM_NAME); + } + + @Override + public KeyValues getHighCardinalityKeyValues(BoltHandleContext context) { + return KeyValues.of(messages(context)); + } + + private KeyValue messages(BoltHandleContext context) { + return Neo4jDriverDocumentation.BoltHandleHighCardinalityKeyNames.MESSAGES.withValue( + context.messageNames().toString()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultConnectionPoolCloseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultConnectionPoolCloseConvention.java new file mode 100644 index 0000000000..6cd67f89bf --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultConnectionPoolCloseConvention.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultConnectionPoolCloseConvention implements ConnectionPoolCloseConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.ConnectionPoolCloseLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultConnectionPoolCloseConvention INSTANCE = new DefaultConnectionPoolCloseConvention(); + + public DefaultConnectionPoolCloseConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.connection.pool.close.duration"; + } + + @Override + public String getContextualName(ConnectionPoolCloseContext context) { + return "connection pool close"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ConnectionPoolCloseContext context) { + return KeyValues.of(DB_SYSTEM_NAME, poolName(context)); + } + + private KeyValue poolName(ConnectionPoolCloseContext context) { + return Neo4jDriverDocumentation.ConnectionPoolCloseLowCardinalityKeyNames.POOL_NAME.withValue(context.id()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultConnectionPoolCreateConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultConnectionPoolCreateConvention.java new file mode 100644 index 0000000000..afc500ed76 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultConnectionPoolCreateConvention.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultConnectionPoolCreateConvention implements ConnectionPoolCreateConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.ConnectionPoolCreateLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultConnectionPoolCreateConvention INSTANCE = new DefaultConnectionPoolCreateConvention(); + + public DefaultConnectionPoolCreateConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.connection.pool.create.duration"; + } + + @Override + public String getContextualName(ConnectionPoolCreateContext context) { + return "connection pool create"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ConnectionPoolCreateContext context) { + return KeyValues.of(DB_SYSTEM_NAME, poolName(context)); + } + + private KeyValue poolName(ConnectionPoolCreateContext context) { + return Neo4jDriverDocumentation.ConnectionPoolCreateLowCardinalityKeyNames.POOL_NAME.withValue(context.id()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultHttpExchangeConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultHttpExchangeConvention.java new file mode 100644 index 0000000000..78978e1a79 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultHttpExchangeConvention.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class DefaultHttpExchangeConvention implements HttpExchangeConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + private static final KeyValue NETWORK_PROTOCOL_NAME = + Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.NETWORK_PROTOCOL_NAME.withValue("http"); + + private final boolean includeUrlScheme; + private final boolean includeUrlTemplate; + private final Predicate requestHeaderPredicate; + private final Predicate responseHeaderPredicate; + + public DefaultHttpExchangeConvention( + boolean includeUrlScheme, + boolean includeUrlTemplate, + Predicate requestHeaderPredicate, + Predicate responseHeaderPredicate) { + this.includeUrlScheme = includeUrlScheme; + this.includeUrlTemplate = includeUrlTemplate; + this.requestHeaderPredicate = requestHeaderPredicate; + this.responseHeaderPredicate = responseHeaderPredicate; + } + + @Override + public String getName() { + return "http.client.request.duration"; + } + + @Override + public String getContextualName(HttpExchangeContext context) { + return includeUrlTemplate ? "%s %s".formatted(context.method(), context.uriTemplate()) : context.method(); + } + + @Override + public KeyValues getLowCardinalityKeyValues(HttpExchangeContext context) { + return requiredLowCardinalityKeyValues(context).and(extraLowCardinalityKeyValues(context)); + } + + @Override + public KeyValues getHighCardinalityKeyValues(HttpExchangeContext context) { + return requiredHighCardinalityKeyValues(context).and(extraHighCardinalityKeyValues(context)); + } + + private KeyValues requiredLowCardinalityKeyValues(HttpExchangeContext context) { + return KeyValues.of( + DB_SYSTEM_NAME, + method(context), + serverAddress(context), + serverPort(context), + NETWORK_PROTOCOL_NAME, + errorType(context)); + } + + private KeyValues extraLowCardinalityKeyValues(HttpExchangeContext context) { + var list = new ArrayList(); + if (includeUrlScheme) { + list.add(scheme(context)); + } + if (includeUrlTemplate) { + list.add(urlTemplate(context)); + } + var response = context.response().orElse(null); + list.add(protocolVersion(response)); + list.add(responseStatus(response)); + return KeyValues.of(list); + } + + private KeyValues requiredHighCardinalityKeyValues(HttpExchangeContext context) { + return KeyValues.of(urlFull(context)); + } + + private KeyValues extraHighCardinalityKeyValues(HttpExchangeContext context) { + Stream requestHeaders = requestHeaderPredicate != null + ? context.headers() + .map(headers -> headers( + Neo4jDriverDocumentation.HttpExchangeHighCardinalityKeyNames.HTTP_REQUEST_HEADER_FORMAT + .asString(), + requestHeaderPredicate, + headers)) + .orElseGet(Stream::empty) + : Stream.empty(); + Stream responseHeaders = responseHeaderPredicate != null + ? context.response() + .map(response -> headers( + Neo4jDriverDocumentation.HttpExchangeHighCardinalityKeyNames.HTTP_RESPONSE_HEADER_FORMAT + .asString(), + responseHeaderPredicate, + response.headers())) + .orElseGet(Stream::empty) + : Stream.empty(); + return KeyValues.of(Stream.concat(requestHeaders, responseHeaders).toList()); + } + + private Stream headers(String format, Predicate predicate, Map> headers) { + return headers.entrySet().stream() + .filter(entry -> predicate.test(entry.getKey())) + .map(entry -> header(format, entry.getKey(), entry.getValue())); + } + + private KeyValue header(String format, String name, List values) { + var keyName = format.replace("", name.toLowerCase()); + var value = values.stream().map("\"%s\""::formatted).collect(Collectors.joining(", ", "[", "]")); + return KeyValue.of(keyName, value); + } + + private KeyValue method(HttpExchangeContext context) { + return Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.HTTP_REQUEST_METHOD.withValue( + context.method()); + } + + private KeyValue serverAddress(HttpExchangeContext context) { + return Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.SERVER_ADDRESS.withValue( + context.uri().getHost()); + } + + private KeyValue serverPort(HttpExchangeContext context) { + var port = context.uri().getPort(); + return Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.SERVER_PORT.withValue( + port != -1 + ? String.valueOf(port) + : switch (context.uri().getScheme()) { + case "http" -> "80"; + case "https" -> "443"; + default -> KeyValue.NONE_VALUE; + }); + } + + private KeyValue scheme(HttpExchangeContext context) { + return Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.URL_SCHEME.withValue( + context.uri().getScheme()); + } + + private KeyValue protocolVersion(HttpExchangeContext.Response response) { + return Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.NETWORK_PROTOCOL_VERSION.withValue( + response != null ? response.httpVersion() : KeyValue.NONE_VALUE); + } + + private KeyValue urlTemplate(HttpExchangeContext context) { + return Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.URL_TEMPLATE.withValue( + context.uriTemplate()); + } + + private KeyValue responseStatus(HttpExchangeContext.Response response) { + return Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.HTTP_RESPONSE_STATUS_CODE.withValue( + response != null ? String.valueOf(response.statusCode()) : KeyValue.NONE_VALUE); + } + + private KeyValue urlFull(HttpExchangeContext context) { + return Neo4jDriverDocumentation.HttpExchangeHighCardinalityKeyNames.URL_FULL.withValue( + context.uri().toString()); + } + + private KeyValue errorType(HttpExchangeContext context) { + return Neo4jDriverDocumentation.HttpExchangeLowCardinalityKeyNames.ERROR_TYPE.withValue( + context.errorType().orElse(KeyValue.NONE_VALUE)); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionAcquireConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionAcquireConvention.java new file mode 100644 index 0000000000..dcc4a53bb1 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionAcquireConvention.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultPooledConnectionAcquireConvention implements PooledConnectionAcquireConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.PooledConnectionAcquireLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultPooledConnectionAcquireConvention INSTANCE = new DefaultPooledConnectionAcquireConvention(); + + public DefaultPooledConnectionAcquireConvention() {} + + @Override + public String getName() { + return "db.client.connection.wait.time"; + } + + @Override + public String getContextualName(PooledConnectionAcquireContext context) { + return "connection wait"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(PooledConnectionAcquireContext context) { + return KeyValues.of(DB_SYSTEM_NAME, poolName(context)); + } + + private KeyValue poolName(PooledConnectionAcquireContext context) { + return Neo4jDriverDocumentation.PooledConnectionAcquireLowCardinalityKeyNames.POOL_NAME.withValue(context.id()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionCloseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionCloseConvention.java new file mode 100644 index 0000000000..2e4009cc14 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionCloseConvention.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultPooledConnectionCloseConvention implements PooledConnectionCloseConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.PooledConnectionCloseLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultPooledConnectionCloseConvention INSTANCE = new DefaultPooledConnectionCloseConvention(); + + public DefaultPooledConnectionCloseConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.connection.close.duration"; + } + + @Override + public String getContextualName(PooledConnectionCloseContext context) { + return "connection close"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(PooledConnectionCloseContext context) { + return KeyValues.of(DB_SYSTEM_NAME, poolName(context)); + } + + private KeyValue poolName(PooledConnectionCloseContext context) { + return Neo4jDriverDocumentation.PooledConnectionCloseLowCardinalityKeyNames.POOL_NAME.withValue(context.id()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionCreateConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionCreateConvention.java new file mode 100644 index 0000000000..2d88068927 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionCreateConvention.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultPooledConnectionCreateConvention implements PooledConnectionCreateConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.PooledConnectionCreateLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultPooledConnectionCreateConvention INSTANCE = new DefaultPooledConnectionCreateConvention(); + + public DefaultPooledConnectionCreateConvention() {} + + @Override + public String getName() { + return "db.client.connection.create.time"; + } + + @Override + public String getContextualName(PooledConnectionCreateContext context) { + return "connection create"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(PooledConnectionCreateContext context) { + return KeyValues.of(DB_SYSTEM_NAME, poolName(context)); + } + + private KeyValue poolName(PooledConnectionCreateContext context) { + return Neo4jDriverDocumentation.PooledConnectionCreateLowCardinalityKeyNames.POOL_NAME.withValue(context.id()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionInUseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionInUseConvention.java new file mode 100644 index 0000000000..28baf936a4 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultPooledConnectionInUseConvention.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultPooledConnectionInUseConvention implements PooledConnectionInUseConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.PooledConnectionInUseLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultPooledConnectionInUseConvention INSTANCE = new DefaultPooledConnectionInUseConvention(); + + public DefaultPooledConnectionInUseConvention() {} + + @Override + public String getName() { + return "db.client.connection.use.time"; + } + + @Override + public String getContextualName(PooledConnectionInUseContext context) { + return "connection use"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(PooledConnectionInUseContext context) { + return KeyValues.of(DB_SYSTEM_NAME, poolName(context)); + } + + private KeyValue poolName(PooledConnectionInUseContext context) { + return Neo4jDriverDocumentation.PooledConnectionInUseLowCardinalityKeyNames.POOL_NAME.withValue(context.id()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultConsumeConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultConsumeConvention.java new file mode 100644 index 0000000000..afa5fca6b7 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultConsumeConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultResultConsumeConvention implements ResultConsumeConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.ResultConsumeLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultResultConsumeConvention INSTANCE = new DefaultResultConsumeConvention(); + + public DefaultResultConsumeConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.result.consume.duration"; + } + + @Override + public String getContextualName(ResultConsumeContext context) { + return "result consume"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ResultConsumeContext context) { + return KeyValues.of(DB_SYSTEM_NAME, resultType(context)); + } + + private KeyValue resultType(ResultConsumeContext context) { + return Neo4jDriverDocumentation.ResultConsumeLowCardinalityKeyNames.RESULT_TYPE.withValue( + context.resultType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultListConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultListConvention.java new file mode 100644 index 0000000000..3e132690c7 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultListConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultResultListConvention implements ResultListConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.ResultListLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultResultListConvention INSTANCE = new DefaultResultListConvention(); + + public DefaultResultListConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.result.list.duration"; + } + + @Override + public String getContextualName(ResultListContext context) { + return "result list"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ResultListContext context) { + return KeyValues.of(DB_SYSTEM_NAME, resultType(context)); + } + + private KeyValue resultType(ResultListContext context) { + return Neo4jDriverDocumentation.ResultListLowCardinalityKeyNames.RESULT_TYPE.withValue( + context.resultType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultNextConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultNextConvention.java new file mode 100644 index 0000000000..db11c26920 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultNextConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultResultNextConvention implements ResultNextConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.ResultNextLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultResultNextConvention INSTANCE = new DefaultResultNextConvention(); + + public DefaultResultNextConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.result.next.duration"; + } + + @Override + public String getContextualName(ResultNextContext context) { + return "result next"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ResultNextContext context) { + return KeyValues.of(DB_SYSTEM_NAME, resultType(context)); + } + + private KeyValue resultType(ResultNextContext context) { + return Neo4jDriverDocumentation.ResultNextLowCardinalityKeyNames.RESULT_TYPE.withValue( + context.resultType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultPeekConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultPeekConvention.java new file mode 100644 index 0000000000..7069228937 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultPeekConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultResultPeekConvention implements ResultPeekConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.ResultPeekLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultResultPeekConvention INSTANCE = new DefaultResultPeekConvention(); + + public DefaultResultPeekConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.result.peek.duration"; + } + + @Override + public String getContextualName(ResultPeekContext context) { + return "result peek"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ResultPeekContext context) { + return KeyValues.of(DB_SYSTEM_NAME, resultType(context)); + } + + private KeyValue resultType(ResultPeekContext context) { + return Neo4jDriverDocumentation.ResultPeekLowCardinalityKeyNames.RESULT_TYPE.withValue( + context.resultType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultRecordsConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultRecordsConvention.java new file mode 100644 index 0000000000..ecd85e3084 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultRecordsConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultResultRecordsConvention implements ResultRecordsConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.ReactiveRecordsLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultResultRecordsConvention INSTANCE = new DefaultResultRecordsConvention(); + + public DefaultResultRecordsConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.result.records.duration"; + } + + @Override + public String getContextualName(ResultRecordsContext context) { + return "result records"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ResultRecordsContext context) { + return KeyValues.of(DB_SYSTEM_NAME, resultType(context)); + } + + private KeyValue resultType(ResultRecordsContext context) { + return Neo4jDriverDocumentation.ReactiveRecordsLowCardinalityKeyNames.RESULT_TYPE.withValue( + context.resultType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultSingleConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultSingleConvention.java new file mode 100644 index 0000000000..4f584e7c69 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultResultSingleConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultResultSingleConvention implements ResultSingleConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.ResultSingleLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultResultSingleConvention INSTANCE = new DefaultResultSingleConvention(); + + public DefaultResultSingleConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.result.single.duration"; + } + + @Override + public String getContextualName(ResultSingleContext context) { + return "result single"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(ResultSingleContext context) { + return KeyValues.of(DB_SYSTEM_NAME, resultType(context)); + } + + private KeyValue resultType(ResultSingleContext context) { + return Neo4jDriverDocumentation.ResultSingleLowCardinalityKeyNames.RESULT_TYPE.withValue( + context.resultType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionCloseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionCloseConvention.java new file mode 100644 index 0000000000..b44b8be6b6 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionCloseConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultSessionCloseConvention implements SessionCloseConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.SessionCloseLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultSessionCloseConvention INSTANCE = new DefaultSessionCloseConvention(); + + public DefaultSessionCloseConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.session.close.duration"; + } + + @Override + public String getContextualName(SessionCloseContext context) { + return "session close"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(SessionCloseContext context) { + return KeyValues.of(DB_SYSTEM_NAME, sessionType(context)); + } + + private KeyValue sessionType(SessionCloseContext context) { + return Neo4jDriverDocumentation.SessionCloseLowCardinalityKeyNames.SESSION_TYPE.withValue( + context.sessionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionExecuteConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionExecuteConvention.java new file mode 100644 index 0000000000..0e5903403a --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionExecuteConvention.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultSessionExecuteConvention implements SessionExecuteConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.SessionExecuteLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultSessionExecuteConvention INSTANCE = new DefaultSessionExecuteConvention(); + + public DefaultSessionExecuteConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.session.execute.duration"; + } + + @Override + public String getContextualName(SessionExecuteContext context) { + return "session execute"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(SessionExecuteContext context) { + return KeyValues.of(DB_SYSTEM_NAME, sessionType(context), sessionMode(context)); + } + + private KeyValue sessionType(SessionExecuteContext context) { + return Neo4jDriverDocumentation.SessionExecuteLowCardinalityKeyNames.SESSION_TYPE.withValue( + context.sessionType().getSimpleName()); + } + + private KeyValue sessionMode(SessionExecuteContext context) { + return Neo4jDriverDocumentation.SessionExecuteLowCardinalityKeyNames.SESSION_MODE.withValue( + context.mode().name()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionRunConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionRunConvention.java new file mode 100644 index 0000000000..afc0e33130 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultSessionRunConvention.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultSessionRunConvention implements SessionRunConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.SessionRunLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + private final boolean alwaysAddQuery; + private final boolean addParameters; + + public DefaultSessionRunConvention(boolean alwaysAddQuery, boolean addParameters) { + this.alwaysAddQuery = alwaysAddQuery; + this.addParameters = addParameters; + } + + @Override + public String getName() { + return "neo4j.db.client.session.run.duration"; + } + + @Override + public String getContextualName(SessionRunContext context) { + return "session run"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(SessionRunContext context) { + return KeyValues.of(DB_SYSTEM_NAME, sessionType(context)); + } + + @Override + public KeyValues getHighCardinalityKeyValues(SessionRunContext context) { + return KeyValuesUtil.queryAndParameters( + Neo4jDriverDocumentation.SessionRunHighCardinalityKeyNames.DB_QUERY_TEXT, + context.query(), + context.parameters(), + Neo4jDriverDocumentation.SessionRunHighCardinalityKeyNames.DB_QUERY_PARAMETER_FORMAT.asString(), + alwaysAddQuery, + addParameters); + } + + private KeyValue sessionType(SessionRunContext context) { + return Neo4jDriverDocumentation.SessionRunLowCardinalityKeyNames.SESSION_TYPE.withValue( + context.sessionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionBeginConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionBeginConvention.java new file mode 100644 index 0000000000..423b51f539 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionBeginConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultTransactionBeginConvention implements TransactionBeginConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.TransactionBeginLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultTransactionBeginConvention INSTANCE = new DefaultTransactionBeginConvention(); + + public DefaultTransactionBeginConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.transaction.begin.duration"; + } + + @Override + public String getContextualName(TransactionBeginContext context) { + return "transaction begin"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(TransactionBeginContext context) { + return KeyValues.of(DB_SYSTEM_NAME, transactionType(context)); + } + + private KeyValue transactionType(TransactionBeginContext context) { + return Neo4jDriverDocumentation.TransactionBeginLowCardinalityKeyNames.TRANSACTION_TYPE.withValue( + context.transactionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionCloseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionCloseConvention.java new file mode 100644 index 0000000000..936f24ba28 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionCloseConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultTransactionCloseConvention implements TransactionCloseConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.TransactionCloseLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultTransactionCloseConvention INSTANCE = new DefaultTransactionCloseConvention(); + + public DefaultTransactionCloseConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.transaction.close.duration"; + } + + @Override + public String getContextualName(TransactionCloseContext context) { + return "transaction close"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(TransactionCloseContext context) { + return KeyValues.of(DB_SYSTEM_NAME, transactionType(context)); + } + + private KeyValue transactionType(TransactionCloseContext context) { + return Neo4jDriverDocumentation.TransactionCloseLowCardinalityKeyNames.TRANSACTION_TYPE.withValue( + context.transactionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionCommitConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionCommitConvention.java new file mode 100644 index 0000000000..72206d218a --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionCommitConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultTransactionCommitConvention implements TransactionCommitConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.TransactionCommitLowCardinalityKeyNames.TRANSACTION_TYPE.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultTransactionCommitConvention INSTANCE = new DefaultTransactionCommitConvention(); + + public DefaultTransactionCommitConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.transaction.commit.duration"; + } + + @Override + public String getContextualName(TransactionCommitContext context) { + return "transaction commit"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(TransactionCommitContext context) { + return KeyValues.of(DB_SYSTEM_NAME, transactionType(context)); + } + + private KeyValue transactionType(TransactionCommitContext context) { + return Neo4jDriverDocumentation.TransactionCommitLowCardinalityKeyNames.TRANSACTION_TYPE.withValue( + context.transactionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionRollbackConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionRollbackConvention.java new file mode 100644 index 0000000000..d5217b27c6 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionRollbackConvention.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultTransactionRollbackConvention implements TransactionRollbackConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.TransactionRollbackLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + static final DefaultTransactionRollbackConvention INSTANCE = new DefaultTransactionRollbackConvention(); + + public DefaultTransactionRollbackConvention() {} + + @Override + public String getName() { + return "neo4j.db.client.transaction.rollback.duration"; + } + + @Override + public String getContextualName(TransactionRollbackContext context) { + return "transaction rollback"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(TransactionRollbackContext context) { + return KeyValues.of(DB_SYSTEM_NAME, transactionType(context)); + } + + private KeyValue transactionType(TransactionRollbackContext context) { + return Neo4jDriverDocumentation.TransactionRollbackLowCardinalityKeyNames.TRANSACTION_TYPE.withValue( + context.transactionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionRunConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionRunConvention.java new file mode 100644 index 0000000000..b9e7e5f2c8 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DefaultTransactionRunConvention.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class DefaultTransactionRunConvention implements TransactionRunConvention { + private static final KeyValue DB_SYSTEM_NAME = + Neo4jDriverDocumentation.TransactionRunLowCardinalityKeyNames.DB_SYSTEM_NAME.withValue( + KeyValuesUtil.DB_SYSTEM_NAME); + private final boolean alwaysAddQuery; + private final boolean addParameters; + + public DefaultTransactionRunConvention(boolean alwaysAddQuery, boolean addParameters) { + this.alwaysAddQuery = alwaysAddQuery; + this.addParameters = addParameters; + } + + @Override + public String getName() { + return "neo4j.db.client.transaction.run.duration"; + } + + @Override + public String getContextualName(TransactionRunContext context) { + return "transaction run"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(TransactionRunContext context) { + return KeyValues.of(DB_SYSTEM_NAME, transactionType(context)); + } + + @Override + public KeyValues getHighCardinalityKeyValues(TransactionRunContext context) { + return KeyValuesUtil.queryAndParameters( + Neo4jDriverDocumentation.TransactionRunHighCardinalityKeyNames.DB_QUERY_TEXT, + context.query(), + context.parameters(), + Neo4jDriverDocumentation.TransactionRunHighCardinalityKeyNames.DB_QUERY_PARAMETER_FORMAT.asString(), + alwaysAddQuery, + addParameters); + } + + private KeyValue transactionType(TransactionRunContext context) { + return Neo4jDriverDocumentation.TransactionRunLowCardinalityKeyNames.TRANSACTION_TYPE.withValue( + context.transactionType().getSimpleName()); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DriverMicrometerObservationProvider.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DriverMicrometerObservationProvider.java new file mode 100644 index 0000000000..cb338241f8 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/DriverMicrometerObservationProvider.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.ObservationConvention; +import io.micrometer.observation.ObservationRegistry; +import java.net.URI; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import java.util.function.Supplier; +import org.neo4j.bolt.connection.BoltProtocolVersion; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.BaseSession; +import org.neo4j.driver.internal.observation.BoltExchangeObservation; +import org.neo4j.driver.internal.observation.BoltHandleObservation; +import org.neo4j.driver.internal.observation.DriverObservationProvider; +import org.neo4j.driver.internal.observation.HttpExchangeObservation; +import org.neo4j.driver.internal.observation.Observation; +import org.neo4j.driver.types.MapAccessor; + +final class DriverMicrometerObservationProvider implements MicrometerObservationProvider, DriverObservationProvider { + private final ObservationRegistry observationRegistry; + private final DefaultSessionRunConvention defaultSessionRunConvention; + private final DefaultTransactionRunConvention defaultTransactionRunConvention; + private final DefaultHttpExchangeConvention defaultHttpExchangeConvention; + + DriverMicrometerObservationProvider( + ObservationRegistry observationRegistry, + boolean alwaysIncludeQuery, + boolean includeQueryParameters, + boolean includeUrlScheme, + boolean includeUrlTemplate, + Predicate requestHeaderPredicate, + Predicate responseHeaderPredicate) { + this.observationRegistry = Objects.requireNonNull(observationRegistry); + this.defaultSessionRunConvention = new DefaultSessionRunConvention(alwaysIncludeQuery, includeQueryParameters); + this.defaultTransactionRunConvention = + new DefaultTransactionRunConvention(alwaysIncludeQuery, includeQueryParameters); + this.defaultHttpExchangeConvention = new DefaultHttpExchangeConvention( + includeUrlScheme, includeUrlTemplate, requestHeaderPredicate, responseHeaderPredicate); + } + + @Override + public Observation sessionRun(Class sessionType, String query, MapAccessor parameters) { + return from(defaultSessionRunConvention, () -> new SessionRunContext(sessionType, query, parameters)); + } + + @Override + public Observation beginTransaction(Class transactionType) { + return from(DefaultTransactionBeginConvention.INSTANCE, () -> new TransactionBeginContext(transactionType)); + } + + @Override + public Observation sessionExecute(Class sessionType, AccessMode mode) { + return from(DefaultSessionExecuteConvention.INSTANCE, () -> new SessionExecuteContext(sessionType, mode)); + } + + @Override + public Observation sessionClose(Class sessionType) { + return from(DefaultSessionCloseConvention.INSTANCE, () -> new SessionCloseContext(sessionType)); + } + + @Override + public Observation transactionRun(Class transactionType, String query, MapAccessor parameters) { + return from( + defaultTransactionRunConvention, () -> new TransactionRunContext(transactionType, query, parameters)); + } + + @Override + public Observation transactionCommit(Class transactionType) { + return from(DefaultTransactionCommitConvention.INSTANCE, () -> new TransactionCommitContext(transactionType)); + } + + @Override + public Observation transactionRollback(Class transactionType) { + return from( + DefaultTransactionRollbackConvention.INSTANCE, () -> new TransactionRollbackContext(transactionType)); + } + + @Override + public Observation transactionClose(Class transactionType) { + return from(DefaultTransactionCloseConvention.INSTANCE, () -> new TransactionCloseContext(transactionType)); + } + + @Override + public Observation resultPeek(Class resultType) { + return from(DefaultResultPeekConvention.INSTANCE, () -> new ResultPeekContext(resultType)); + } + + @Override + public Observation resultNext(Class resultType) { + return from(DefaultResultNextConvention.INSTANCE, () -> new ResultNextContext(resultType)); + } + + @Override + public Observation resultSingle(Class resultType) { + return from(DefaultResultSingleConvention.INSTANCE, () -> new ResultSingleContext(resultType)); + } + + @Override + public Observation resultList(Class resultType) { + return from(DefaultResultListConvention.INSTANCE, () -> new ResultListContext(resultType)); + } + + @Override + public Observation resultConsume(Class resultType) { + return from(DefaultResultConsumeConvention.INSTANCE, () -> new ResultConsumeContext(resultType)); + } + + @Override + public Observation resultRecords(Class resultType) { + return from(DefaultResultRecordsConvention.INSTANCE, () -> new ResultRecordsContext(resultType)); + } + + @Override + public Observation connectionPoolCreate(String id, URI uri, int maxSize) { + return from( + DefaultConnectionPoolCreateConvention.INSTANCE, + () -> new ConnectionPoolCreateContext(id, uri, maxSize)); + } + + @Override + public Observation connectionPoolClose(String id, URI uri) { + return from(DefaultConnectionPoolCloseConvention.INSTANCE, () -> new ConnectionPoolCloseContext(id, uri)); + } + + @Override + public Observation pooledConnectionCreate(String id, URI uri) { + return from(DefaultPooledConnectionCreateConvention.INSTANCE, () -> new PooledConnectionCreateContext(id, uri)); + } + + @Override + public Observation pooledConnectionClose(String id, URI uri) { + return from(DefaultPooledConnectionCloseConvention.INSTANCE, () -> new PooledConnectionCloseContext(id, uri)); + } + + @Override + public Observation pooledConnectionAcquire(String id, URI uri) { + return from( + DefaultPooledConnectionAcquireConvention.INSTANCE, () -> new PooledConnectionAcquireContext(id, uri)); + } + + @Override + public Observation pooledConnectionInUse(String id, URI uri) { + return from(DefaultPooledConnectionInUseConvention.INSTANCE, () -> new PooledConnectionInUseContext(id, uri)); + } + + @Override + public BoltHandleObservation boltHandle(List messageTypes) { + return new BoltHandleObservationImpl(io.micrometer.observation.Observation.createNotStarted( + null, + DefaultBoltHandleConvention.INSTANCE, + () -> new BoltHandleContext(messageTypes), + observationRegistry)); + } + + @Override + public BoltExchangeObservation boltExchange( + String host, int port, BoltProtocolVersion boltVersion, BiConsumer setter) { + return new BoltExchangeObservationImpl(io.micrometer.observation.Observation.createNotStarted( + null, + DefaultBoltExchangeConvention.INSTANCE, + () -> new BoltExchangeContext(host, port, boltVersion.toString(), setter), + observationRegistry)); + } + + @Override + public HttpExchangeObservation httpExchange( + URI uri, String method, String uriTemplate, BiConsumer setter) { + return new HttpExchangeObservationImpl(io.micrometer.observation.Observation.createNotStarted( + null, + defaultHttpExchangeConvention, + () -> new HttpExchangeContext(uri, method, uriTemplate, setter), + observationRegistry)); + } + + @Override + public Observation scopedObservation() { + var observation = observationRegistry.getCurrentObservation(); + return observation != null ? new ObservationImpl(observation) : null; + } + + private Observation from( + ObservationConvention defaultConvention, Supplier contextSupplier) { + var observation = io.micrometer.observation.Observation.createNotStarted( + null, defaultConvention, contextSupplier, observationRegistry); + return new ObservationImpl(observation); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeContext.java new file mode 100644 index 0000000000..1597fb935c --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeContext.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.transport.Kind; +import io.micrometer.observation.transport.SenderContext; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; + +public class HttpExchangeContext extends SenderContext { + private final URI uri; + private final String method; + private final String uriTemplate; + private volatile Map> headers; + private volatile Response response; + private volatile String errorType; + + public HttpExchangeContext(URI uri, String method, String uriTemplate, BiConsumer setter) { + super((carrier, key, value) -> setter.accept(key, value), Kind.CLIENT); + this.uri = Objects.requireNonNull(uri); + this.method = Objects.requireNonNull(method); + this.uriTemplate = Objects.requireNonNull(uriTemplate); + } + + public URI uri() { + return uri; + } + + public String method() { + return method; + } + + public String uriTemplate() { + return uriTemplate; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public Optional>> headers() { + return Optional.ofNullable(headers); + } + + public void setResponse(Response response) { + this.response = response; + } + + public Optional response() { + return Optional.ofNullable(response); + } + + public void setErrorType(String errorType) { + this.errorType = errorType; + } + + public Optional errorType() { + return Optional.ofNullable(errorType); + } + + public interface Response { + int statusCode(); + + Map> headers(); + + String httpVersion(); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeConvention.java new file mode 100644 index 0000000000..4b47aae5bf --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeConvention.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; + +public interface HttpExchangeConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof HttpExchangeContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeObservationImpl.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeObservationImpl.java new file mode 100644 index 0000000000..873cce225a --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/HttpExchangeObservationImpl.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.neo4j.driver.internal.observation.HttpExchangeObservation; + +final class HttpExchangeObservationImpl extends ObservationImpl implements HttpExchangeObservation { + private final HttpExchangeContext context; + + HttpExchangeObservationImpl(Observation delegate) { + super(delegate); + this.context = (HttpExchangeContext) Objects.requireNonNull(delegate.getContext()); + } + + @Override + public HttpExchangeObservation start() { + super.start(); + return this; + } + + @Override + public HttpExchangeObservation onHeaders(Map> headers) { + context.setHeaders(headers); + return this; + } + + @Override + public HttpExchangeObservation onResponse(Response response) { + context.setResponse(new HttpExchangeContext.Response() { + @Override + public int statusCode() { + return response.statusCode(); + } + + @Override + public Map> headers() { + return response.headers(); + } + + @Override + public String httpVersion() { + return response.httpVersion(); + } + }); + return this; + } + + @Override + public HttpExchangeObservation error(Throwable error) { + context.setErrorType(error.getClass().getSimpleName()); + super.error(error); + return this; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/KeyValuesUtil.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/KeyValuesUtil.java new file mode 100644 index 0000000000..db50daed73 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/KeyValuesUtil.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import io.micrometer.common.docs.KeyName; +import java.util.ArrayList; +import org.neo4j.driver.types.MapAccessor; + +final class KeyValuesUtil { + static final String DB_SYSTEM_NAME = "neo4j"; + + static KeyValues queryAndParameters( + KeyName queryTextKeyName, + String query, + MapAccessor parameters, + String keyNameFormat, + boolean alwaysAddQuery, + boolean addParameters) { + var keyAndValues = new ArrayList(); + if (parameters.size() > 0) { + keyAndValues.add(queryTextKeyName.withValue(query)); + if (addParameters) { + keyAndValues.add(queryTextKeyName.withValue(query)); + for (var key : parameters.keys()) { + var value = parameters.get(key).toString(); + var keyName = keyNameFormat.replace("", key); + keyAndValues.add(KeyValue.of(keyName, value)); + } + } + } else if (alwaysAddQuery) { + keyAndValues.add(queryTextKeyName.withValue(query)); + } + return KeyValues.of(keyAndValues); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/MicrometerObservationProvider.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/MicrometerObservationProvider.java new file mode 100644 index 0000000000..05c561fe5c --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/MicrometerObservationProvider.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.ObservationRegistry; +import java.util.function.Predicate; +import org.neo4j.driver.observation.ObservationProvider; +import org.neo4j.driver.util.Preview; + +/** + * An {@link ObservationProvider} implementation based on Micrometer Observation API. + * + * @since 6.0.0 + */ +@Preview(name = "Observability") +public sealed interface MicrometerObservationProvider extends ObservationProvider + permits DriverMicrometerObservationProvider { + + /** + * Creates a new {@link Builder} instance. + * + * @param observationRegistry the Micrometer {@link ObservationRegistry} + * @return the new {@link Builder} instance + */ + static Builder builder(ObservationRegistry observationRegistry) { + return new BuilderImpl(observationRegistry); + } + + /** + * A builder for creating a new {@link MicrometerObservationProvider} instance. + */ + sealed interface Builder permits BuilderImpl { + /** + * Sets whether query string would always be included in high cardinality tags. + *

+ * It is {@literal false} by default, meaning only queries that have non-empty parameters are included. + * + * @param alwaysIncludeQuery {@literal true} to always include query string and {@literal false} to use the default behaviour + * @return this builder + */ + Builder alwaysIncludeQuery(boolean alwaysIncludeQuery); + + /** + * Sets whether query parameters would be included in high cardinality tags. + *

+ * It is {@literal false} by default. + * + * @param includeQueryParameters {@literal true} to include and {@literal false} to exclude + * @return this builder + */ + Builder includeQueryParameters(boolean includeQueryParameters); + + /** + * Sets whether URL scheme would be included in low cardinality tags of HTTP exchange. + *

+ * It is {@literal false} by default. + * + * @param includeUrlScheme {@literal true} to include, {@literal false} to exclude + * @return this builder + */ + Builder includeUrlScheme(boolean includeUrlScheme); + + /** + * Sets whether URL template would be included in low cardinality tags of HTTP exchange and contextual name. + *

+ * It is {@literal false} by default. + * + * @param includeUrlTemplate {@literal true} to include, {@literal false} to exclude + * @return this builder + */ + Builder includeUrlTemplate(boolean includeUrlTemplate); + + /** + * Sets a whitelist predicate for request headers of HTTP exchange. If it returns {@literal true} for a given + * HTTP header name, such header would be included in high cardinality tags. + *

+ * It is {@literal null} by default. + * + * @param requestHeaderPredicate the header predicate or {@literal null} to exclude all headers + * @return this builder + */ + Builder requestHeaderPredicate(Predicate requestHeaderPredicate); + + /** + * Sets a whitelist predicate for response headers of HTTP exchange. If it returns {@literal true} for a given + * HTTP header name, such header would be included in high cardinality tags. + *

+ * It is {@literal null} by default. + * + * @param responseHeaderPredicate the header predicate or {@literal null} to exclude all headers + * @return this builder + */ + Builder responseHeaderPredicate(Predicate responseHeaderPredicate); + + /** + * Builds a new {@link MicrometerObservationProvider} instance. + * + * @return the new {@link MicrometerObservationProvider} instance + */ + MicrometerObservationProvider build(); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/Neo4jDriverDocumentation.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/Neo4jDriverDocumentation.java new file mode 100644 index 0000000000..2ee931d616 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/Neo4jDriverDocumentation.java @@ -0,0 +1,1151 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.common.docs.KeyName; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import io.micrometer.observation.docs.ObservationDocumentation; +import org.neo4j.driver.Query; +import org.neo4j.driver.Result; +import org.neo4j.driver.Session; +import org.neo4j.driver.Transaction; +import org.neo4j.driver.TransactionCallback; +import org.neo4j.driver.reactivestreams.ReactiveResult; + +enum Neo4jDriverDocumentation implements ObservationDocumentation { + /** + * Observes {@link Session#run(Query)} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative session types. + */ + SESSION_RUN { + @Override + public Class> getDefaultConvention() { + return DefaultSessionRunConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return SessionRunLowCardinalityKeyNames.values(); + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return SessionRunHighCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Session#executeWrite(TransactionCallback)} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative session types. + */ + SESSION_EXECUTE { + @Override + public Class> getDefaultConvention() { + return DefaultSessionExecuteConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return SessionExecuteLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Session#close()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative session types. + */ + SESSION_CLOSE { + @Override + public Class> getDefaultConvention() { + return DefaultSessionCloseConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return SessionCloseLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Session#beginTransaction()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative session types. + */ + TRANSACTION_BEGIN { + @Override + public Class> getDefaultConvention() { + return DefaultTransactionBeginConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return TransactionBeginLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Transaction#run(String)} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative transaction types. + */ + TRANSACTION_RUN { + @Override + public Class> getDefaultConvention() { + return DefaultTransactionRunConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return TransactionRunLowCardinalityKeyNames.values(); + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return TransactionRunHighCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Transaction#commit()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative transaction types. + */ + TRANSACTION_COMMIT { + @Override + public Class> getDefaultConvention() { + return DefaultTransactionCommitConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return TransactionCommitLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Transaction#rollback()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative transaction types. + */ + TRANSACTION_ROLLBACK { + @Override + public Class> getDefaultConvention() { + return DefaultTransactionRollbackConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return TransactionRollbackLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Transaction#close()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative transaction types. + */ + TRANSACTION_CLOSE { + @Override + public Class> getDefaultConvention() { + return DefaultTransactionCloseConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return TransactionCloseLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Result#peek()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative result types. + *

+ * Note that only those executions that require network exchange are observed. + */ + RESULT_PEEK { + @Override + public Class> getDefaultConvention() { + return DefaultResultPeekConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return ResultPeekLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Result#next()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative result types. + *

+ * Note that only those executions that require network exchange are observed. + */ + RESULT_NEXT { + @Override + public Class> getDefaultConvention() { + return DefaultResultNextConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return ResultNextLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Result#single()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative result types. + *

+ * Note that only those executions that require network exchange are observed. + */ + RESULT_SINGLE { + @Override + public Class> getDefaultConvention() { + return DefaultResultSingleConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return ResultSingleLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Result#list()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative result types. + *

+ * Note that only those executions that require network exchange are observed. + */ + RESULT_LIST { + @Override + public Class> getDefaultConvention() { + return DefaultResultListConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return ResultListLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link ReactiveResult#records()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative result types. + *

+ * Note that only those executions that require network exchange are observed. + */ + RESULT_RECORDS { + @Override + public Class> getDefaultConvention() { + return DefaultResultRecordsConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return ReactiveRecordsLowCardinalityKeyNames.values(); + } + }, + /** + * Observes {@link Result#consume()} execution. + *

+ * This also applies to the other variants of this method, including those of the alternative result types. + *

+ * Note that only those executions that require network exchange are observed. + */ + RESULT_CONSUME { + @Override + public Class> getDefaultConvention() { + return DefaultResultConsumeConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return ResultConsumeLowCardinalityKeyNames.values(); + } + }, + /** + * Observes a new connection pool creation. + */ + CONNECTION_POOL_CREATE { + @Override + public Class> getDefaultConvention() { + return DefaultConnectionPoolCreateConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return ConnectionPoolCreateLowCardinalityKeyNames.values(); + } + }, + /** + * Observes connection pool closure. + */ + CONNECTION_POOL_CLOSE { + @Override + public Class> getDefaultConvention() { + return DefaultConnectionPoolCloseConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return ConnectionPoolCloseLowCardinalityKeyNames.values(); + } + }, + /** + * Observes a new connection creation by connection pool. + */ + POOLED_CONNECTION_CREATE { + @Override + public Class> getDefaultConvention() { + return DefaultPooledConnectionCreateConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return PooledConnectionCreateLowCardinalityKeyNames.values(); + } + }, + /** + * Observes connection acquisition from connection pool. + */ + POOLED_CONNECTION_PENDING { + @Override + public Class> getDefaultConvention() { + return DefaultPooledConnectionAcquireConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return PooledConnectionAcquireLowCardinalityKeyNames.values(); + } + }, + /** + * Observes connection usage. + *

+ * It starts when connection is acquired from connection pool and is stopped before the connection is made available + * for re-use or deleted. + */ + POOLED_CONNECTION_IN_USE { + @Override + public Class> getDefaultConvention() { + return DefaultPooledConnectionInUseConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return PooledConnectionInUseLowCardinalityKeyNames.values(); + } + }, + /** + * Observes connection closure. + */ + POOLED_CONNECTION_CLOSE { + @Override + public Class> getDefaultConvention() { + return DefaultPooledConnectionCloseConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return PooledConnectionCloseLowCardinalityKeyNames.values(); + } + }, + /** + * Observes Bolt messages handling. + *

+ * The Neo4j Java Driver uses Neo4j Bolt Connection to exchange Bolt messages with the server. This + * observation works on this integration level. It starts when messages are submitted to Bolt Connection and stops + * when the exchange is finished. This allows observations to happen from driver perspective, but it does not focus + * on the lowest implementation levels. + */ + BOLT_HANDLE { + @Override + public Class> getDefaultConvention() { + return DefaultBoltHandleConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return BoltHandleLowCardinalityKeyNames.values(); + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return BoltHandleHighCardinalityKeyNames.values(); + } + }, + /** + * Observes Bolt messages exchange. + *

+ * This observation is handled on Neo4j Bolt Connection level and it focuses on lower level of Bolt messages + * exchange. + */ + BOLT_EXCHANGE { + @Override + public Class> getDefaultConvention() { + return DefaultBoltExchangeConvention.class; + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return BoltExchangeHighCardinalityKeyNames.values(); + } + }, + /** + * Observes HTTP exchange. + *

+ * This observation focuses on HTTP exchanges that would typically be used when the driver uses HTTP scheme instead + * of Bolt. + */ + HTTP_EXCHANGE { + @Override + public Class> getDefaultConvention() { + return DefaultHttpExchangeConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return HttpExchangeLowCardinalityKeyNames.values(); + } + + @Override + public KeyName[] getHighCardinalityKeyNames() { + return HttpExchangeHighCardinalityKeyNames.values(); + } + }; + + enum SessionRunLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The session type. + */ + SESSION_TYPE { + @Override + public String asString() { + return "neo4j.session.type"; + } + } + } + + enum SessionRunHighCardinalityKeyNames implements KeyName { + /** + * The query text. It is included if the query has parameters or when explicitly enabled in the provider. + */ + DB_QUERY_TEXT { + @Override + public String asString() { + return "db.query.text"; + } + + @Override + public boolean isRequired() { + return false; + } + }, + /** + * The query parameters. The parameters are included only when explicitly enabled in the provider. + */ + DB_QUERY_PARAMETER_FORMAT { + @Override + public String asString() { + return "db.query.parameter."; + } + + @Override + public boolean isRequired() { + return false; + } + } + } + + enum SessionExecuteLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The session type. + */ + SESSION_TYPE { + @Override + public String asString() { + return "neo4j.session.type"; + } + }, + /** + * The access mode. + */ + SESSION_MODE { + @Override + public String asString() { + return "neo4j.session.mode"; + } + } + } + + enum SessionCloseLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The session type. + */ + SESSION_TYPE { + @Override + public String asString() { + return "neo4j.session.type"; + } + } + } + + enum TransactionBeginLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The transaction type. + */ + TRANSACTION_TYPE { + @Override + public String asString() { + return "neo4j.transaction.type"; + } + } + } + + enum TransactionRunLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The transaction type. + */ + TRANSACTION_TYPE { + @Override + public String asString() { + return "neo4j.transaction.type"; + } + } + } + + enum TransactionRunHighCardinalityKeyNames implements KeyName { + /** + * The query text. It is included if the query has parameters or when explicitly enabled in the provider. + */ + DB_QUERY_TEXT { + @Override + public String asString() { + return "db.query.text"; + } + + @Override + public boolean isRequired() { + return false; + } + }, + /** + * The query parameters. The parameters are included only when explicitly enabled in the provider. + */ + DB_QUERY_PARAMETER_FORMAT { + @Override + public String asString() { + return "db.query.parameter."; + } + + @Override + public boolean isRequired() { + return false; + } + } + } + + enum TransactionCommitLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The transaction type. + */ + TRANSACTION_TYPE { + @Override + public String asString() { + return "neo4j.transaction.type"; + } + } + } + + enum TransactionRollbackLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The transaction type. + */ + TRANSACTION_TYPE { + @Override + public String asString() { + return "neo4j.transaction.type"; + } + } + } + + enum TransactionCloseLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The transaction type. + */ + TRANSACTION_TYPE { + @Override + public String asString() { + return "neo4j.transaction.type"; + } + } + } + + enum ResultPeekLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The result type. + */ + RESULT_TYPE { + @Override + public String asString() { + return "neo4j.result.type"; + } + } + } + + enum ResultNextLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The result type. + */ + RESULT_TYPE { + @Override + public String asString() { + return "neo4j.result.type"; + } + } + } + + enum ResultSingleLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The result type. + */ + RESULT_TYPE { + @Override + public String asString() { + return "neo4j.result.type"; + } + } + } + + enum ResultListLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The result type. + */ + RESULT_TYPE { + @Override + public String asString() { + return "neo4j.result.type"; + } + } + } + + enum ResultConsumeLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The result type. + */ + RESULT_TYPE { + @Override + public String asString() { + return "neo4j.result.type"; + } + } + } + + enum ReactiveRecordsLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The result type. + */ + RESULT_TYPE { + @Override + public String asString() { + return "neo4j.result.type"; + } + } + } + + enum ConnectionPoolCloseLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + + /** + * The pool name in the following format: host[:port]-id. Note that the port part is optional if default scheme port is used. + */ + POOL_NAME { + @Override + public String asString() { + return "db.client.connection.pool.name"; + } + } + } + + enum PooledConnectionAcquireLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + + /** + * The pool name in the following format: host[:port]-id. Note that the port part is optional if default scheme port is used. + */ + POOL_NAME { + @Override + public String asString() { + return "db.client.connection.pool.name"; + } + } + } + + enum PooledConnectionCloseLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + + /** + * The pool name in the following format: host[:port]-id. Note that the port part is optional if default scheme port is used. + */ + POOL_NAME { + @Override + public String asString() { + return "db.client.connection.pool.name"; + } + } + } + + enum PooledConnectionCreateLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + + /** + * The pool name in the following format: host[:port]-id. Note that the port part is optional if default scheme port is used. + */ + POOL_NAME { + @Override + public String asString() { + return "db.client.connection.pool.name"; + } + } + } + + enum PooledConnectionInUseLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + + /** + * The pool name in the following format: host[:port]-id. Note that the port part is optional if default scheme port is used. + */ + POOL_NAME { + @Override + public String asString() { + return "db.client.connection.pool.name"; + } + } + } + + enum ConnectionPoolCreateLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + + /** + * The pool name in the following format: host[:port]-id. Note that the port part is optional if default scheme port is used. + */ + POOL_NAME { + @Override + public String asString() { + return "db.client.connection.pool.name"; + } + } + } + + enum BoltHandleLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + } + } + + enum BoltHandleHighCardinalityKeyNames implements KeyName { + /** + * The message names. + */ + MESSAGES { + @Override + public String asString() { + return "neo4j.bolt.messages"; + } + } + } + + enum BoltExchangeLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The network protocol name. It is always bolt. + */ + NETWORK_PROTOCOL_NAME { + @Override + public String asString() { + return "network.protocol.name"; + } + }, + /** + * The network protocol version. It always represents Bolt version. + */ + NETWORK_PROTOCOL_VERSION { + @Override + public String asString() { + return "network.protocol.version"; + } + }, + /** + * The server address. + */ + SERVER_ADDRESS { + @Override + public String asString() { + return "server.address"; + } + }, + /** + * The server port. + */ + SERVER_PORT { + @Override + public String asString() { + return "server.port"; + } + } + } + + enum BoltExchangeHighCardinalityKeyNames implements KeyName { + /** + * The message names. + */ + MESSAGES { + @Override + public String asString() { + return "neo4j.bolt.messages"; + } + } + } + + enum HttpExchangeLowCardinalityKeyNames implements KeyName { + /** + * The DBMS product name. It is always neo4j. + */ + DB_SYSTEM_NAME { + @Override + public String asString() { + return "db.system.name"; + } + }, + /** + * The HTTP request method. + */ + HTTP_REQUEST_METHOD { + @Override + public String asString() { + return "http.request.method"; + } + }, + /** + * The URL scheme. It is included only when explicitly enabled in the provider. + */ + URL_SCHEME { + @Override + public String asString() { + return "url.scheme"; + } + + @Override + public boolean isRequired() { + return false; + } + }, + /** + * The network protocol name. It is always http. + */ + NETWORK_PROTOCOL_NAME { + @Override + public String asString() { + return "network.protocol.name"; + } + }, + /** + * The network protocol version. It always represents HTTP version. + */ + NETWORK_PROTOCOL_VERSION { + @Override + public String asString() { + return "network.protocol.version"; + } + }, + /** + * The server address. + */ + SERVER_ADDRESS { + @Override + public String asString() { + return "server.address"; + } + }, + /** + * The server port. + */ + SERVER_PORT { + @Override + public String asString() { + return "server.port"; + } + }, + /** + * The URI template. It is included only when explicitly enabled in the provider. + */ + URL_TEMPLATE { + @Override + public String asString() { + return "url.template"; + } + + @Override + public boolean isRequired() { + return false; + } + }, + /** + * The HTTP response status code. + */ + HTTP_RESPONSE_STATUS_CODE { + @Override + public String asString() { + return "http.response.status.code"; + } + }, + /** + * The error type. + */ + ERROR_TYPE { + @Override + public String asString() { + return "error.type"; + } + } + } + + enum HttpExchangeHighCardinalityKeyNames implements KeyName { + /** + * The full URL. + */ + URL_FULL { + @Override + public String asString() { + return "url.full"; + } + }, + /** + * The HTTP request headers. These are included only when explicitly enabled in the provider. + */ + HTTP_REQUEST_HEADER_FORMAT { + @Override + public String asString() { + return "http.request.header."; + } + + @Override + public boolean isRequired() { + return false; + } + }, + /** + * The HTTP response headers. These are included only when explicitly enabled in the provider. + */ + HTTP_RESPONSE_HEADER_FORMAT { + @Override + public String asString() { + return "http.response.header."; + } + + @Override + public boolean isRequired() { + return false; + } + } + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ObservationImpl.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ObservationImpl.java new file mode 100644 index 0000000000..e0f005d02f --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ObservationImpl.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; +import java.util.Objects; +import org.neo4j.driver.internal.observation.Observation; +import reactor.util.context.Context; + +class ObservationImpl implements Observation { + final io.micrometer.observation.Observation delegate; + + ObservationImpl(io.micrometer.observation.Observation delegate) { + this.delegate = Objects.requireNonNull(delegate); + } + + @Override + public Observation start() { + delegate.start(); + return this; + } + + @Override + public Observation error(Throwable error) { + delegate.error(error); + return this; + } + + @Override + public void stop() { + delegate.stop(); + } + + @Override + public Context writeReactiveContext(Context context) { + return context.put(ObservationThreadLocalAccessor.KEY, delegate); + } + + @Override + public Scope openScope() { + return new ScopeImpl(delegate.openScope()); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + var that = (ObservationImpl) o; + return Objects.equals(delegate, that.delegate); + } + + @Override + public int hashCode() { + return Objects.hashCode(delegate); + } + + private record ScopeImpl(io.micrometer.observation.Observation.Scope delegate) implements Scope { + private ScopeImpl { + Objects.requireNonNull(delegate); + } + + @Override + public void close() { + delegate.close(); + } + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionAcquireContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionAcquireContext.java new file mode 100644 index 0000000000..6a1be87d76 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionAcquireContext.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import java.net.URI; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class PooledConnectionAcquireContext extends AbstractConnectionPoolContext { + public PooledConnectionAcquireContext(String id, URI uri) { + super(id, uri); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionAcquireConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionAcquireConvention.java new file mode 100644 index 0000000000..d911c0aa5a --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionAcquireConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface PooledConnectionAcquireConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof PooledConnectionAcquireContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCloseContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCloseContext.java new file mode 100644 index 0000000000..cd9b296eb1 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCloseContext.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import java.net.URI; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class PooledConnectionCloseContext extends AbstractConnectionPoolContext { + public PooledConnectionCloseContext(String id, URI uri) { + super(id, uri); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCloseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCloseConvention.java new file mode 100644 index 0000000000..687c480564 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCloseConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface PooledConnectionCloseConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof PooledConnectionCloseContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCreateContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCreateContext.java new file mode 100644 index 0000000000..3537f5d856 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCreateContext.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import java.net.URI; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class PooledConnectionCreateContext extends AbstractConnectionPoolContext { + public PooledConnectionCreateContext(String id, URI uri) { + super(id, uri); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsProvider.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCreateConvention.java similarity index 54% rename from driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsProvider.java rename to observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCreateConvention.java index fa27257b09..2eecb70e28 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/metrics/MetricsProvider.java +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionCreateConvention.java @@ -14,22 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.metrics; +package org.neo4j.driver.observation.micrometer; -import org.neo4j.bolt.connection.MetricsListener; -import org.neo4j.driver.Metrics; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; /** - * An adapter that collects driver metrics via {@link MetricsListener} and publishes them via {@link Metrics} instance. + * @since 6.0.0 */ -public interface MetricsProvider { - /** - * @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(); +@Preview(name = "Observability") +public interface PooledConnectionCreateConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof PooledConnectionCreateContext; + } } diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionInUseContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionInUseContext.java new file mode 100644 index 0000000000..03d0bb561c --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionInUseContext.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import java.net.URI; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class PooledConnectionInUseContext extends AbstractConnectionPoolContext { + public PooledConnectionInUseContext(String id, URI uri) { + super(id, uri); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionInUseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionInUseConvention.java new file mode 100644 index 0000000000..137b8949a4 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/PooledConnectionInUseConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface PooledConnectionInUseConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof PooledConnectionInUseContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultConsumeContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultConsumeContext.java new file mode 100644 index 0000000000..4c298bca1e --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultConsumeContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class ResultConsumeContext extends AbstractResultContext { + public ResultConsumeContext(Class resultType) { + super(resultType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultConsumeConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultConsumeConvention.java new file mode 100644 index 0000000000..eea1faff24 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultConsumeConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +interface ResultConsumeConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof ResultConsumeContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultListContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultListContext.java new file mode 100644 index 0000000000..98d0fe1510 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultListContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class ResultListContext extends AbstractResultContext { + public ResultListContext(Class resultType) { + super(resultType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultListConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultListConvention.java new file mode 100644 index 0000000000..f4f4f584be --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultListConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface ResultListConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof ResultListContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultNextContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultNextContext.java new file mode 100644 index 0000000000..176f85308a --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultNextContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class ResultNextContext extends AbstractResultContext { + public ResultNextContext(Class resultType) { + super(resultType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultNextConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultNextConvention.java new file mode 100644 index 0000000000..2d8ac6eae6 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultNextConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface ResultNextConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof ResultNextContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultPeekContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultPeekContext.java new file mode 100644 index 0000000000..2a12782c84 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultPeekContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class ResultPeekContext extends AbstractResultContext { + public ResultPeekContext(Class resultType) { + super(resultType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultPeekConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultPeekConvention.java new file mode 100644 index 0000000000..b990c1cd18 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultPeekConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface ResultPeekConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof ResultPeekContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultRecordsContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultRecordsContext.java new file mode 100644 index 0000000000..facdaf3401 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultRecordsContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class ResultRecordsContext extends AbstractResultContext { + public ResultRecordsContext(Class resultType) { + super(resultType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultRecordsConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultRecordsConvention.java new file mode 100644 index 0000000000..0301179447 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultRecordsConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface ResultRecordsConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof ResultRecordsContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultSingleContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultSingleContext.java new file mode 100644 index 0000000000..67d449efc1 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultSingleContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class ResultSingleContext extends AbstractResultContext { + public ResultSingleContext(Class resultType) { + super(resultType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultSingleConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultSingleConvention.java new file mode 100644 index 0000000000..b153176492 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/ResultSingleConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface ResultSingleConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof ResultSingleContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionCloseContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionCloseContext.java new file mode 100644 index 0000000000..5643c655ad --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionCloseContext.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.BaseSession; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class SessionCloseContext extends AbstractSessionContext { + public SessionCloseContext(Class sessionType) { + super(sessionType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionCloseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionCloseConvention.java new file mode 100644 index 0000000000..e73d35f2c6 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionCloseConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface SessionCloseConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof SessionCloseContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionExecuteContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionExecuteContext.java new file mode 100644 index 0000000000..e4b4ca9512 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionExecuteContext.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.BaseSession; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class SessionExecuteContext extends AbstractSessionContext { + private final AccessMode mode; + + public SessionExecuteContext(Class sessionType, AccessMode mode) { + super(sessionType); + this.mode = mode; + } + + public AccessMode mode() { + return mode; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionExecuteConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionExecuteConvention.java new file mode 100644 index 0000000000..aeb379c38f --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionExecuteConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface SessionExecuteConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof SessionExecuteContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionRunContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionRunContext.java new file mode 100644 index 0000000000..817e681fbf --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionRunContext.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import java.util.Objects; +import org.neo4j.driver.BaseSession; +import org.neo4j.driver.types.MapAccessor; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class SessionRunContext extends AbstractSessionContext { + private final String query; + private final MapAccessor parameters; + + public SessionRunContext(Class sessionType, String query, MapAccessor parameters) { + super(sessionType); + this.query = Objects.requireNonNull(query); + this.parameters = Objects.requireNonNull(parameters); + } + + public String query() { + return query; + } + + public MapAccessor parameters() { + return parameters; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionRunConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionRunConvention.java new file mode 100644 index 0000000000..18e2677d0f --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/SessionRunConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface SessionRunConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof SessionRunContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionBeginContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionBeginContext.java new file mode 100644 index 0000000000..4efbb415a7 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionBeginContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class TransactionBeginContext extends AbstractTransactionContext { + public TransactionBeginContext(Class transactionType) { + super(transactionType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionBeginConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionBeginConvention.java new file mode 100644 index 0000000000..3727648b9e --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionBeginConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface TransactionBeginConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof TransactionBeginContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCloseContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCloseContext.java new file mode 100644 index 0000000000..46faf227e7 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCloseContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class TransactionCloseContext extends AbstractTransactionContext { + public TransactionCloseContext(Class transactionType) { + super(transactionType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCloseConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCloseConvention.java new file mode 100644 index 0000000000..a5e0dab71f --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCloseConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface TransactionCloseConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof TransactionCloseContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCommitContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCommitContext.java new file mode 100644 index 0000000000..e2049beaa4 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCommitContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class TransactionCommitContext extends AbstractTransactionContext { + public TransactionCommitContext(Class transactionType) { + super(transactionType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCommitConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCommitConvention.java new file mode 100644 index 0000000000..1d0affbed1 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionCommitConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface TransactionCommitConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof TransactionCommitContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRollbackContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRollbackContext.java new file mode 100644 index 0000000000..6491ca9a01 --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRollbackContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class TransactionRollbackContext extends AbstractTransactionContext { + public TransactionRollbackContext(Class transactionType) { + super(transactionType); + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRollbackConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRollbackConvention.java new file mode 100644 index 0000000000..b4a4cec0df --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRollbackConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface TransactionRollbackConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof TransactionRollbackContext; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRunContext.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRunContext.java new file mode 100644 index 0000000000..055fbe1aac --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRunContext.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import java.util.Objects; +import org.neo4j.driver.types.MapAccessor; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public class TransactionRunContext extends AbstractTransactionContext { + private final String query; + private final MapAccessor parameters; + + public TransactionRunContext(Class transactionType, String query, MapAccessor parameters) { + super(transactionType); + this.query = Objects.requireNonNull(query); + this.parameters = Objects.requireNonNull(parameters); + } + + public String query() { + return query; + } + + public MapAccessor parameters() { + return parameters; + } +} diff --git a/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRunConvention.java b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRunConvention.java new file mode 100644 index 0000000000..584416eb2d --- /dev/null +++ b/observation/micrometer/src/main/java/org/neo4j/driver/observation/micrometer/TransactionRunConvention.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import org.neo4j.driver.util.Preview; + +/** + * @since 6.0.0 + */ +@Preview(name = "Observability") +public interface TransactionRunConvention extends ObservationConvention { + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof TransactionRunContext; + } +} diff --git a/observation/pom.xml b/observation/pom.xml new file mode 100644 index 0000000000..e4e0d0043d --- /dev/null +++ b/observation/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + + + org.neo4j.driver + neo4j-java-driver-parent + 6.0-SNAPSHOT + + + neo4j-java-driver-observation + + pom + Neo4j Java Driver (Observation) + Parent project for observation implementations. + + + metrics + micrometer + + + + scm:git:git://github.com/neo4j/neo4j-java-driver.git + scm:git:git@github.com:neo4j/neo4j-java-driver.git + https://github.com/neo4j/neo4j-java-driver + + + diff --git a/pom.xml b/pom.xml index 9c8689b550..f6d7f10e78 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ true - 6.0.2 + 7.0.0 1.0.4 @@ -61,6 +61,7 @@ 1.18.38 24.2.1 1.15.2 + 1.0.4 1.0.13.RELEASE 1.21.3 2024-12.1 @@ -72,6 +73,7 @@ bom driver bundle + observation driver-it examples testkit-backend @@ -133,12 +135,6 @@ pom import - - io.micrometer - micrometer-core - ${micrometer.version} - provided - io.projectreactor.tools blockhound @@ -406,7 +402,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.0 + 3.4.2 org.codehaus.mojo @@ -566,7 +562,7 @@ org.apache.maven.plugins maven-resources-plugin - 2.7 + 3.3.1 org.apache.maven.plugins @@ -640,6 +636,16 @@ flatten-maven-plugin 1.7.0 + + org.codehaus.mojo + exec-maven-plugin + 3.5.1 + + + org.asciidoctor + asciidoctor-maven-plugin + 3.2.0 + diff --git a/testkit-backend/pom.xml b/testkit-backend/pom.xml index 625e86afc9..fcf3b46bdc 100644 --- a/testkit-backend/pom.xml +++ b/testkit-backend/pom.xml @@ -28,6 +28,11 @@ neo4j-java-driver ${project.version} + + org.neo4j.driver + neo4j-java-driver-observation-metrics + ${project.version} + io.netty netty-handler diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/holder/DriverHolder.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/holder/DriverHolder.java index 76dc5c4fdc..972218a8e5 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/holder/DriverHolder.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/holder/DriverHolder.java @@ -18,5 +18,6 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Driver; +import org.neo4j.driver.observation.metrics.Metrics; -public record DriverHolder(Driver driver, Config config) {} +public record DriverHolder(Driver driver, Config config, Metrics metrics) {} 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 fcbbb7ddd8..7b3d244c2a 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 @@ -16,7 +16,6 @@ */ package neo4j.org.testkit.backend.messages.requests; -import java.lang.reflect.InvocationTargetException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import lombok.Getter; @@ -54,22 +53,15 @@ public Mono processReactiveStreams(TestkitState testkitState) { private ConnectionPoolMetrics getConnectionPoolMetrics(TestkitState testkitState) { var driverHolder = testkitState.getDriverHolder(data.getDriverId()); - @SuppressWarnings("resource") - var metrics = driverHolder.driver().metrics(); + var metrics = driverHolder.metrics(); var poolMetrics = metrics.connectionPoolMetrics().stream() .filter(pm -> { - // Brute forcing the access via reflections avoid having the InternalConnectionPoolMetrics a public - // class - BoltServerAddress poolAddress; - try { - var m = pm.getClass().getDeclaredMethod("getAddress"); - m.setAccessible(true); - poolAddress = (BoltServerAddress) m.invoke(pm); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - return false; - } + var id = pm.id(); + var addressParts = id.split("-")[0].split(":"); + var host = addressParts[0]; + var port = addressParts.length > 1 ? Integer.parseInt(addressParts[1]) : 7687; var address = new BoltServerAddress(data.getAddress()); - return address.host().equals(poolAddress.host()) && address.port() == poolAddress.port(); + return address.host().equals(host) && address.port() == port; }) .findFirst() .orElseThrow(() -> new IllegalArgumentException( @@ -77,7 +69,8 @@ private ConnectionPoolMetrics getConnectionPoolMetrics(TestkitState testkitState return createResponse(poolMetrics); } - private ConnectionPoolMetrics createResponse(org.neo4j.driver.ConnectionPoolMetrics poolMetrics) { + private ConnectionPoolMetrics createResponse( + org.neo4j.driver.observation.metrics.ConnectionPoolMetrics poolMetrics) { return ConnectionPoolMetrics.builder() .data(ConnectionPoolMetrics.ConnectionPoolMetricsBody.builder() .inUse(poolMetrics.inUse()) diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java index 1e5b492e79..b0f6ad60b1 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java @@ -59,6 +59,7 @@ import org.neo4j.driver.internal.security.SecurityPlans; import org.neo4j.driver.internal.security.StaticAuthTokenManager; import org.neo4j.driver.net.ServerAddressResolver; +import org.neo4j.driver.observation.metrics.MetricsObservationProvider; import reactor.core.publisher.Mono; @Setter @@ -110,7 +111,7 @@ public TestkitResponse process(TestkitState testkitState) { .ifPresent(configBuilder::withDisabledNotificationClassifications); Optional.ofNullable(data.maxConnectionLifetimeMs) .ifPresent(timeout -> configBuilder.withMaxConnectionLifetime(timeout, TimeUnit.MILLISECONDS)); - configBuilder.withDriverMetrics(); + var metrics = MetricsObservationProvider.newInstance(configBuilder).metrics(); var clientCertificateManager = Optional.ofNullable(data.getClientCertificateProviderId()) .map(testkitState::getClientCertificateManager) .or(() -> Optional.ofNullable(data.getClientCertificate()) @@ -136,7 +137,7 @@ public TestkitResponse process(TestkitState testkitState) { } catch (RuntimeException e) { return handleExceptionAsErrorResponse(testkitState, e).orElseThrow(() -> e); } - testkitState.addDriverHolder(id, new DriverHolder(driver, config)); + testkitState.addDriverHolder(id, new DriverHolder(driver, config, metrics)); return Driver.builder().data(Driver.DriverBody.builder().id(id).build()).build(); }