From 9cb4b41ce0c5ad2d9e4a6ee03a1fdf2f4ad37543 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Sun, 7 Sep 2025 08:07:56 +0100 Subject: [PATCH 1/2] Implementing JFR, Logging and Open Telemetry providers for monitoring UCP --- ojdbc-provider-observability/pom.xml | 48 ++- ojdbc-provider-observability/prometheus.yml | 18 + .../jfr/core/JFRUCPEventListenerProvider.java | 55 +++ .../observability/jfr/core/UCPBaseEvent.java | 77 ++++ .../jfr/core/UCPEventFactory.java | 77 ++++ .../connection/ConnectionBorrowedEvent.java | 16 + .../connection/ConnectionClosedEvent.java | 16 + .../connection/ConnectionCreatedEvent.java | 16 + .../connection/ConnectionReturnedEvent.java | 16 + .../events/lifecycle/PoolCreatedEvent.java | 17 + .../events/lifecycle/PoolDestroyedEvent.java | 14 + .../events/lifecycle/PoolStartedEvent.java | 16 + .../events/lifecycle/PoolStartingEvent.java | 16 + .../events/lifecycle/PoolStoppedEvent.java | 14 + .../events/maintenance/PoolPurgedEvent.java | 16 + .../events/maintenance/PoolRecycledEvent.java | 16 + .../maintenance/PoolRefreshedEvent.java | 17 + .../maintenance/PoolRestartedEvent.java | 14 + .../maintenance/PoolRestartingEvent.java | 16 + .../LoggingUCPEventListenerProvider.java | 55 +++ .../logging/UCPEventPrinter.java | 70 ++++ .../otel/OpenTelemetryConfig.java | 54 +++ ...OpenTelemetryUCPEventListenerProvider.java | 344 ++++++++++++++++++ ...e.ucp.events.core.UCPEventListenerProvider | 3 + .../JfrAndLoggingStressTestUCP.java | 176 +++++++++ .../observability/OtelStressTestUCP.java | 287 +++++++++++++++ 26 files changed, 1480 insertions(+), 4 deletions(-) create mode 100644 ojdbc-provider-observability/prometheus.yml create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartedEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartingEvent.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/LoggingUCPEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/UCPEventPrinter.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OpenTelemetryConfig.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelOpenTelemetryUCPEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider create mode 100644 ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JfrAndLoggingStressTestUCP.java create mode 100644 ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelStressTestUCP.java diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 8a7601cb..ed4ea8f9 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -22,8 +22,11 @@ - com.oracle.database.jdbc - ojdbc11 + com.oracle + ojdbc + 11.2.0.4 + system + /Users/abdessamadelaaissaoui/Desktop/ojdbc11.jar io.opentelemetry @@ -51,6 +54,43 @@ tests test-jar + + com.oracle + ucp + 11.2.0.4 + system + /Users/abdessamadelaaissaoui/Desktop/ucp11.jar + + + com.oracle.database.security + oraclepki + 23.3.0.23.09 + + + com.oracle.database.ha + ons + 23.9.0.25.07 + + + io.opentelemetry + opentelemetry-api + 1.32.0 + + + io.opentelemetry + opentelemetry-sdk + 1.32.0 + + + io.opentelemetry + opentelemetry-exporter-prometheus + 1.32.0-alpha + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + 1.32.0 + @@ -58,9 +98,9 @@ org.apache.maven.plugins maven-surefire-plugin - diff --git a/ojdbc-provider-observability/prometheus.yml b/ojdbc-provider-observability/prometheus.yml new file mode 100644 index 00000000..5d0aa281 --- /dev/null +++ b/ojdbc-provider-observability/prometheus.yml @@ -0,0 +1,18 @@ +# Global configuration +global: + scrape_interval: 15s # Scrape targets every 15 seconds + evaluation_interval: 15s # Evaluate rules every 15 seconds + +# Scrape configuration +scrape_configs: + # Job to scrape your UCP application + - job_name: 'ucp-application' + static_configs: + - targets: ['localhost:8080'] # Your app's metrics endpoint + scrape_interval: 5s # Scrape every 5 seconds for testing + metrics_path: '/metrics' # Path to metrics endpoint + + # Job to scrape Prometheus itself (optional) + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9091'] # ✅ Changed from 9090 to 9091 \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java new file mode 100644 index 00000000..06e8d996 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java @@ -0,0 +1,55 @@ +package oracle.ucp.provider.observability.jfr.core; + +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; +import oracle.ucp.events.core.UCPEventListenerProvider; + +import java.util.Map; + +/** + * Provider that supplies a UCP event listener for recording JFR events. + * Integrates UCP events with Java Flight Recorder for low-overhead monitoring. + */ +public final class JFRUCPEventListenerProvider implements UCPEventListenerProvider { + + private final UCPEventListener listener; + + /** + * Singleton listener that records UCP events as JFR events. + * Thread-safe and optimized for minimal overhead. + */ + public static final UCPEventListener TRACE_EVENT_LISTENER = new UCPEventListener() { + @Override + public void onUCPEvent(EventType eventType, UCPEventContext context) { + UCPEventFactory.recordEvent(eventType, context); + } + }; + + /** + * Creates a new provider instance. + */ + public JFRUCPEventListenerProvider() { + this.listener = TRACE_EVENT_LISTENER; + } + + /** + * Returns the provider's unique identifier. + * + * @return "jfr-ucp-listener" + */ + @Override + public String getName() { + return "jfr-ucp-listener"; + } + + /** + * Returns the JFR recording listener instance. + * + * @param config configuration map (ignored) + * @return the JFR event listener + */ + @Override + public UCPEventListener getListener(Map config) { + return listener; + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java new file mode 100644 index 00000000..579a2d10 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java @@ -0,0 +1,77 @@ +package oracle.ucp.provider.observability.jfr.core; + +import jdk.jfr.*; +import oracle.ucp.events.core.UCPEventContext; + +import java.util.Objects; + +/** + * Abstract base class for UCP JFR events providing common fields and initialization. + * All UCP events extend this class to inherit standard pool metrics and metadata. + */ +@Category("UCP Events") +@Description("Base UCP Event") +public abstract class UCPBaseEvent extends Event { + + /** Name of the connection pool */ + @Label("Pool Name") + protected String poolName; + + /** Event timestamp in milliseconds since epoch */ + @Label("Timestamp") + protected long timestamp; + + /** Maximum configured pool size */ + @Label("Max Pool Size") + protected int maxPoolSize; + + /** Minimum configured pool size */ + @Label("Min Pool Size") + protected int minPoolSize; + + /** Current count of borrowed connections */ + @Label("Borrowed Connections") + protected int borrowedConnections; + + /** Current count of available connections */ + @Label("Available Connections") + protected int availableConnections; + + /** Total active connections (borrowed + available) */ + @Label("Total Connections") + protected int totalConnections; + + /** Lifetime count of closed connections */ + @Label("Closed Connections") + protected int closedConnections; + + /** Lifetime count of created connections */ + @Label("Created Connections") + protected int createdConnections; + + /** Average connection wait time in milliseconds */ + @Label("Average Wait Time (ms)") + @Timespan(Timespan.MILLISECONDS) + protected long avgWaitTime; + + /** + * Initializes common fields from UCP event context. + * + * @param ctx event context containing pool metrics + * @throws NullPointerException if ctx is null + */ + protected void initCommonFields(UCPEventContext ctx) { + Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); + + this.poolName = ctx.poolName(); + this.timestamp = ctx.timestamp(); + this.maxPoolSize = ctx.maxPoolSize(); + this.minPoolSize = ctx.minPoolSize(); + this.borrowedConnections = ctx.borrowedConnectionsCount(); + this.availableConnections = ctx.availableConnectionsCount(); + this.totalConnections = ctx.totalConnections(); + this.closedConnections = ctx.closedConnections(); + this.createdConnections = ctx.createdConnections(); + this.avgWaitTime = ctx.getAverageConnectionWaitTime(); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java new file mode 100644 index 00000000..7acc816b --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java @@ -0,0 +1,77 @@ +package oracle.ucp.provider.observability.jfr.core; + +import jdk.jfr.Event; +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; +import oracle.ucp.provider.observability.jfr.events.connection.*; +import oracle.ucp.provider.observability.jfr.events.lifecycle.*; +import oracle.ucp.provider.observability.jfr.events.maintenance.*; + +/** + * Factory for creating and recording JFR events from UCP operations. + * Maps UCP event types to specific JFR event classes and handles recording. + */ +public class UCPEventFactory { + + /** + * Creates a JFR event instance for the specified UCP event type. + * + * @param type UCP event type + * @param ctx event context with pool metrics + * @return configured JFR event ready for recording + * @throws IllegalStateException if event type is unrecognized + * @throws NullPointerException if parameters are null + */ + public static Event createEvent(UCPEventListener.EventType type, UCPEventContext ctx) { + switch (type) { + // Pool Lifecycle Events + case POOL_CREATED: + return new PoolCreatedEvent(ctx); + case POOL_STARTING: + return new PoolStartingEvent(ctx); + case POOL_STARTED: + return new PoolStartedEvent(ctx); + case POOL_STOPPED: + return new PoolStoppedEvent(ctx); + case POOL_RESTARTING: + return new PoolRestartingEvent(ctx); + case POOL_RESTARTED: + return new PoolRestartedEvent(ctx); + case POOL_DESTROYED: + return new PoolDestroyedEvent(ctx); + + // Connection Lifecycle Events + case CONNECTION_CREATED: + return new ConnectionCreatedEvent(ctx); + case CONNECTION_BORROWED: + return new ConnectionBorrowedEvent(ctx); + case CONNECTION_RETURNED: + return new ConnectionReturnedEvent(ctx); + case CONNECTION_CLOSED: + return new ConnectionClosedEvent(ctx); + + // Maintenance Operations + case POOL_REFRESHED: + return new PoolRefreshedEvent(ctx); + case POOL_RECYCLED: + return new PoolRecycledEvent(ctx); + case POOL_PURGED: + return new PoolPurgedEvent(ctx); + + default: + throw new IllegalStateException("Unexpected event type: " + type); + } + } + + /** + * Creates and immediately records a JFR event for the UCP operation. + * + * @param type UCP event type to record + * @param ctx event context with pool metrics + * @throws NullPointerException if parameters are null + */ + public static void recordEvent(UCPEventListener.EventType type, UCPEventContext ctx) { + Event event = createEvent(type, ctx); + event.commit(); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java new file mode 100644 index 00000000..3172e8a2 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.connection; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.ConnectionBorrowed") +@Label("Connection Borrowed") +@Category({"UCP Events","Connection Lifecycle Events"}) +public class ConnectionBorrowedEvent extends UCPBaseEvent { + public ConnectionBorrowedEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java new file mode 100644 index 00000000..fcc06111 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.connection; + +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.ConnectionClosed") +@Label("Connection Closed") +@Category({"UCP Events","Connection Lifecycle Events"}) +public class ConnectionClosedEvent extends UCPBaseEvent { + public ConnectionClosedEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java new file mode 100644 index 00000000..f3abaf7f --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.connection; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.ConnectionCreated") +@Label("Connection Created") +@Category({"UCP Events","Connection Lifecycle Events"}) +public class ConnectionCreatedEvent extends UCPBaseEvent { + public ConnectionCreatedEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java new file mode 100644 index 00000000..d27bb182 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.connection; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.ConnectionReturned") +@Label("Connection Returned") +@Category({"UCP Events","Connection Lifecycle Events"}) +public class ConnectionReturnedEvent extends UCPBaseEvent { + public ConnectionReturnedEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java new file mode 100644 index 00000000..e369f96c --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java @@ -0,0 +1,17 @@ +package oracle.ucp.provider.observability.jfr.events.lifecycle; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolCreated") +@Label("Pool Created") +@Category({"UCP Events","Pool Lifecycle Events"}) +public class PoolCreatedEvent extends UCPBaseEvent { + public PoolCreatedEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java new file mode 100644 index 00000000..97309299 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java @@ -0,0 +1,14 @@ +package oracle.ucp.provider.observability.jfr.events.lifecycle; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolDestroyed") +@Label("Pool Destroyed") +@Category({"UCP Events","Pool Lifecycle Events"}) +public class PoolDestroyedEvent extends UCPBaseEvent { + public PoolDestroyedEvent(UCPEventContext ctx) { initCommonFields(ctx); } +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java new file mode 100644 index 00000000..1b38d79f --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.lifecycle; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolStarted") +@Label("Pool Started") +@Category({"UCP Events","Pool Lifecycle Events"}) +public class PoolStartedEvent extends UCPBaseEvent { + public PoolStartedEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java new file mode 100644 index 00000000..6456a8c9 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.lifecycle; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolStarting") +@Label("Pool Starting") +@Category({"UCP Events","Pool Lifecycle Events"}) +public class PoolStartingEvent extends UCPBaseEvent { + public PoolStartingEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java new file mode 100644 index 00000000..ad313a23 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java @@ -0,0 +1,14 @@ +package oracle.ucp.provider.observability.jfr.events.lifecycle; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolStopped") +@Label("Pool Stopped") +@Category({"UCP Events","Pool Lifecycle Events"}) +public class PoolStoppedEvent extends UCPBaseEvent { + public PoolStoppedEvent(UCPEventContext ctx) { initCommonFields(ctx); } +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java new file mode 100644 index 00000000..447fbaf8 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.maintenance; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolPurged") +@Label("Pool Purged") +@Category({"UCP Events","Maintenance Operations Events"}) +public class PoolPurgedEvent extends UCPBaseEvent { + public PoolPurgedEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java new file mode 100644 index 00000000..fb84e799 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.maintenance; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolRecycled") +@Label("Pool Recycled") +@Category({"UCP Events","Maintenance Operations Events"}) +public class PoolRecycledEvent extends UCPBaseEvent { + public PoolRecycledEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java new file mode 100644 index 00000000..1e2b15f5 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java @@ -0,0 +1,17 @@ +package oracle.ucp.provider.observability.jfr.events.maintenance; + + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolRefreshed") +@Label("Pool Refreshed") +@Category({"UCP Events","Maintenance Operations Events"}) +public class PoolRefreshedEvent extends UCPBaseEvent { + public PoolRefreshedEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartedEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartedEvent.java new file mode 100644 index 00000000..c37e03ba --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartedEvent.java @@ -0,0 +1,14 @@ +package oracle.ucp.provider.observability.jfr.events.maintenance; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolRestarted") +@Label("Pool Restarted") +@Category({"UCP Events","Pool Lifecycle Events"}) +public class PoolRestartedEvent extends UCPBaseEvent { + public PoolRestartedEvent(UCPEventContext ctx) { initCommonFields(ctx); } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartingEvent.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartingEvent.java new file mode 100644 index 00000000..8a916521 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartingEvent.java @@ -0,0 +1,16 @@ +package oracle.ucp.provider.observability.jfr.events.maintenance; + +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; +import jdk.jfr.Category; +import jdk.jfr.Label; +import jdk.jfr.Name; +import oracle.ucp.events.core.UCPEventContext; + +@Name("ucp.PoolRestarting") +@Label("Pool Restarting") +@Category({"UCP Events","Pool Lifecycle Events"}) +public class PoolRestartingEvent extends UCPBaseEvent { + public PoolRestartingEvent(UCPEventContext ctx) { + initCommonFields(ctx); + } +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/LoggingUCPEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/LoggingUCPEventListenerProvider.java new file mode 100644 index 00000000..3282b707 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/LoggingUCPEventListenerProvider.java @@ -0,0 +1,55 @@ +package oracle.ucp.provider.observability.logging; + +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; +import oracle.ucp.events.core.UCPEventListenerProvider; + +import java.util.Map; + +/** + * Provider that supplies a UCP event listener for logging events to standard output. + * Uses UCPEventPrinter for human-readable formatted output. + */ +public final class LoggingUCPEventListenerProvider implements UCPEventListenerProvider { + + private final UCPEventListener listener; + + /** + * Singleton listener that logs all UCP events using UCPEventPrinter. + * Thread-safe and prints detailed formatted output for all event types. + */ + public static final UCPEventListener TRACE_EVENT_LISTENER = new UCPEventListener() { + @Override + public void onUCPEvent(EventType eventType, UCPEventContext context) { + UCPEventPrinter.PRINT_EVENT.accept(eventType, context); + } + }; + + /** + * Creates a new provider instance. + */ + public LoggingUCPEventListenerProvider() { + this.listener = TRACE_EVENT_LISTENER; + } + + /** + * Returns the provider's unique identifier. + * + * @return "logging-ucp-listener" + */ + @Override + public String getName() { + return "logging-ucp-listener"; + } + + /** + * Returns the logging listener instance. + * + * @param config configuration map (ignored) + * @return the logging event listener + */ + @Override + public UCPEventListener getListener(Map config) { + return listener; + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/UCPEventPrinter.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/UCPEventPrinter.java new file mode 100644 index 00000000..dbd8bfce --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/UCPEventPrinter.java @@ -0,0 +1,70 @@ +package oracle.ucp.provider.observability.logging; + +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; + +import java.util.function.BiConsumer; + +/** + * Utility for formatting and printing UCP events to standard output. + * Provides human-readable event representations with consistent formatting. + */ +public class UCPEventPrinter { + + /** Separator line for event output */ + private static final String SEPARATOR = "========================================="; + + /** Format string for aligned event detail lines */ + private static final String LINE_FORMAT = "%-30s %s%n"; + + /** + * Ready-to-use consumer that prints events to System.out. + * Handles null contexts gracefully and uses detailed formatting for all events. + */ + public static final BiConsumer PRINT_EVENT = + (eventType, context) -> { + if (context == null) { + System.out.println("[WARNING] Received null event context"); + return; + } + printStandardEvent(eventType, context); + }; + + /** + * Prints events in detailed, formatted output with pool metrics. + * + * @param eventType type of event being printed + * @param context event context containing pool data + */ + private static void printStandardEvent(UCPEventListener.EventType eventType, + UCPEventContext context) { + StringBuilder sb = new StringBuilder("\n") + .append(SEPARATOR).append("\n") + .append(String.format(" UCP Event: %s%n", eventType)) + .append(SEPARATOR).append("\n") + .append(formatLine("Pool Name:", context.poolName())) + .append(formatLine("Timestamp:", context.formattedTimestamp())) + .append(formatLine("Max Pool Size:", context.maxPoolSize())) + .append(formatLine("Min Pool Size:", context.minPoolSize())) + .append(formatLine("Borrowed Connections:", context.borrowedConnectionsCount())) + .append(formatLine("Available Connections:", context.availableConnectionsCount())) + .append(formatLine("Total Active Connections:", context.totalConnections())) + .append(formatLine("Average Connection WaitTime:", + context.getAverageConnectionWaitTime() + " ms")) + .append(formatLine("Connections Created:", context.createdConnections())) + .append(formatLine("Connections Closed:", context.closedConnections())) + .append(SEPARATOR).append("\n\n"); + System.out.print(sb.toString()); + } + + /** + * Formats a label-value pair with consistent alignment. + * + * @param label description label (left-aligned) + * @param value corresponding value + * @return formatted string with alignment + */ + private static String formatLine(String label, Object value) { + return String.format(LINE_FORMAT, label, value); + } +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OpenTelemetryConfig.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OpenTelemetryConfig.java new file mode 100644 index 00000000..81413e45 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OpenTelemetryConfig.java @@ -0,0 +1,54 @@ +package oracle.ucp.provider.observability.otel; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; + +/** + * Configuration utility for initializing OpenTelemetry with Prometheus exporter. + * Sets up metrics collection and HTTP endpoint for scraping. + */ +public class OpenTelemetryConfig { + + /** + * Initializes OpenTelemetry with Prometheus HTTP server on port 8080. + * Sets up the global OpenTelemetry instance for UCP providers to use. + */ + public static void initialize() { + try { + // Create Prometheus HTTP server + PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder() + .setPort(8080) + .setHost("localhost") + .build(); + + // Build OpenTelemetry SDK with Prometheus exporter + OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder() + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(prometheusServer) + .build() + ) + .build(); + + // Set as global instance + GlobalOpenTelemetry.set(openTelemetry); + + System.out.println("✅ OpenTelemetry initialized successfully!"); + System.out.println("📊 Metrics endpoint: http://localhost:8080/metrics"); + + } catch (Exception e) { + System.err.println("❌ Failed to initialize OpenTelemetry: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Shuts down OpenTelemetry gracefully. + * Should be called during application shutdown. + */ + public static void shutdown() { + System.out.println("🛑 Shutting down OpenTelemetry..."); + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelOpenTelemetryUCPEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelOpenTelemetryUCPEventListenerProvider.java new file mode 100644 index 00000000..03bf3afe --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelOpenTelemetryUCPEventListenerProvider.java @@ -0,0 +1,344 @@ +package oracle.ucp.provider.observability.otel; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.*; +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; +import oracle.ucp.events.core.UCPEventListenerProvider; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Provider that supplies a UCP event listener for recording OpenTelemetry metrics. + * Converts UCP events into metrics for integration with observability platforms. + */ +public final class OtelOpenTelemetryUCPEventListenerProvider implements UCPEventListenerProvider { + + private final UCPEventListener listener; + + /** + * Singleton listener that records UCP events as OpenTelemetry metrics. + * Thread-safe and generates gauges, counters, and histograms. + */ + public static final UCPEventListener TRACE_EVENT_LISTENER = new OtelOpenTelemetryUCPEventListener(); + + /** + * Creates a new provider instance. + */ + public OtelOpenTelemetryUCPEventListenerProvider() { + this.listener = TRACE_EVENT_LISTENER; + } + + /** + * Returns the provider's unique identifier. + * + * @return "opentelemetry-ucp-listener" + */ + @Override + public String getName() { + return "opentelemetry-ucp-listener"; + } + + /** + * Returns the OpenTelemetry listener instance. + * + * @param config configuration map (ignored) + * @return the OpenTelemetry event listener + */ + @Override + public UCPEventListener getListener(Map config) { + return listener; + } + + /** + * Internal listener that converts UCP events into OpenTelemetry metrics. + * Records pool state as gauges, event occurrences as counters, and performance as histograms. + */ + private static class OtelOpenTelemetryUCPEventListener implements UCPEventListener { + private static final long serialVersionUID = 1L; + + private final Meter meter = GlobalOpenTelemetry.getMeter("oracle.ucp.events"); + + // Attribute keys for metric labels + private static final AttributeKey POOL_NAME_KEY = AttributeKey.stringKey("pool_name"); + private static final AttributeKey EVENT_TYPE_KEY = AttributeKey.stringKey("event_type"); + + // Gauge metrics for current state + private final ObservableDoubleGauge maxPoolSizeGauge; + private final ObservableDoubleGauge minPoolSizeGauge; + private final ObservableDoubleGauge borrowedConnectionsGauge; + private final ObservableDoubleGauge availableConnectionsGauge; + private final ObservableDoubleGauge totalConnectionsGauge; + private final ObservableDoubleGauge closedConnectionsGauge; + private final ObservableDoubleGauge createdConnectionsGauge; + private final ObservableDoubleGauge averageWaitTimeGauge; + + // Counter metrics for event occurrences + private final LongCounter poolCreatedCounter; + private final LongCounter poolStartingCounter; + private final LongCounter poolStartedCounter; + private final LongCounter poolStoppedCounter; + private final LongCounter poolRestartingCounter; + private final LongCounter poolRestartedCounter; + private final LongCounter poolDestroyedCounter; + private final LongCounter connectionCreatedCounter; + private final LongCounter connectionBorrowedCounter; + private final LongCounter connectionReturnedCounter; + private final LongCounter connectionClosedCounter; + private final LongCounter poolRefreshedCounter; + private final LongCounter poolRecycledCounter; + private final LongCounter poolPurgedCounter; + + // Histogram metrics for performance + private final DoubleHistogram connectionWaitTimeHistogram; + + // Cache for storing latest context per pool for gauge callbacks + private final Map latestContextByPool = new ConcurrentHashMap<>(); + + /** + * Initializes all OpenTelemetry metric instruments. + */ + public OtelOpenTelemetryUCPEventListener() { + // Initialize gauge metrics + this.maxPoolSizeGauge = meter.gaugeBuilder("ucp_max_pool_size") + .setDescription("Configured maximum size of the connection pool") + .setUnit("connections") + .buildWithCallback(this::recordMaxPoolSize); + + this.minPoolSizeGauge = meter.gaugeBuilder("ucp_min_pool_size") + .setDescription("Configured minimum size of the connection pool") + .setUnit("connections") + .buildWithCallback(this::recordMinPoolSize); + + this.borrowedConnectionsGauge = meter.gaugeBuilder("ucp_borrowed_connections") + .setDescription("Current number of borrowed connections") + .setUnit("connections") + .buildWithCallback(this::recordBorrowedConnections); + + this.availableConnectionsGauge = meter.gaugeBuilder("ucp_available_connections") + .setDescription("Current number of available idle connections") + .setUnit("connections") + .buildWithCallback(this::recordAvailableConnections); + + this.totalConnectionsGauge = meter.gaugeBuilder("ucp_total_connections") + .setDescription("Total number of active connections") + .setUnit("connections") + .buildWithCallback(this::recordTotalConnections); + + this.closedConnectionsGauge = meter.gaugeBuilder("ucp_closed_connections_total") + .setDescription("Lifetime count of closed connections") + .setUnit("connections") + .buildWithCallback(this::recordClosedConnections); + + this.createdConnectionsGauge = meter.gaugeBuilder("ucp_created_connections_total") + .setDescription("Lifetime count of created connections") + .setUnit("connections") + .buildWithCallback(this::recordCreatedConnections); + + this.averageWaitTimeGauge = meter.gaugeBuilder("ucp_average_wait_time_ms") + .setDescription("Average connection wait time") + .setUnit("ms") + .buildWithCallback(this::recordAverageWaitTime); + + // Initialize counter metrics + this.poolCreatedCounter = meter.counterBuilder("ucp_pool_created_total") + .setDescription("Total pool creation events") + .setUnit("events") + .build(); + + this.poolStartingCounter = meter.counterBuilder("ucp_pool_starting_total") + .setDescription("Total pool starting events") + .setUnit("events") + .build(); + + this.poolStartedCounter = meter.counterBuilder("ucp_pool_started_total") + .setDescription("Total pool started events") + .setUnit("events") + .build(); + + this.poolStoppedCounter = meter.counterBuilder("ucp_pool_stopped_total") + .setDescription("Total pool stopped events") + .setUnit("events") + .build(); + + this.poolRestartingCounter = meter.counterBuilder("ucp_pool_restarting_total") + .setDescription("Total pool restarting events") + .setUnit("events") + .build(); + + this.poolRestartedCounter = meter.counterBuilder("ucp_pool_restarted_total") + .setDescription("Total pool restarted events") + .setUnit("events") + .build(); + + this.poolDestroyedCounter = meter.counterBuilder("ucp_pool_destroyed_total") + .setDescription("Total pool destroyed events") + .setUnit("events") + .build(); + + this.connectionCreatedCounter = meter.counterBuilder("ucp_connection_created_total") + .setDescription("Total connection creation events") + .setUnit("events") + .build(); + + this.connectionBorrowedCounter = meter.counterBuilder("ucp_connection_borrowed_total") + .setDescription("Total connection borrowed events") + .setUnit("events") + .build(); + + this.connectionReturnedCounter = meter.counterBuilder("ucp_connection_returned_total") + .setDescription("Total connection returned events") + .setUnit("events") + .build(); + + this.connectionClosedCounter = meter.counterBuilder("ucp_connection_closed_total") + .setDescription("Total connection closed events") + .setUnit("events") + .build(); + + this.poolRefreshedCounter = meter.counterBuilder("ucp_pool_refreshed_total") + .setDescription("Total pool refresh events") + .setUnit("events") + .build(); + + this.poolRecycledCounter = meter.counterBuilder("ucp_pool_recycled_total") + .setDescription("Total pool recycle events") + .setUnit("events") + .build(); + + this.poolPurgedCounter = meter.counterBuilder("ucp_pool_purged_total") + .setDescription("Total pool purge events") + .setUnit("events") + .build(); + + // Initialize histogram metrics + this.connectionWaitTimeHistogram = meter.histogramBuilder("ucp_connection_wait_time_ms") + .setDescription("Distribution of connection wait times") + .setUnit("ms") + .build(); + } + + @Override + public void onUCPEvent(EventType eventType, UCPEventContext context) { + if (context == null || eventType == null) { + return; + } + + // Update latest context cache for gauge callbacks + latestContextByPool.put(context.poolName(), context); + + Attributes poolAttributes = Attributes.of(POOL_NAME_KEY, context.poolName()); + + // Record event occurrence counter based on event type + switch (eventType) { + case POOL_CREATED: + poolCreatedCounter.add(1, poolAttributes); + break; + case POOL_STARTING: + poolStartingCounter.add(1, poolAttributes); + break; + case POOL_STARTED: + poolStartedCounter.add(1, poolAttributes); + break; + case POOL_STOPPED: + poolStoppedCounter.add(1, poolAttributes); + break; + case POOL_RESTARTING: + poolRestartingCounter.add(1, poolAttributes); + break; + case POOL_RESTARTED: + poolRestartedCounter.add(1, poolAttributes); + break; + case POOL_DESTROYED: + poolDestroyedCounter.add(1, poolAttributes); + break; + case CONNECTION_CREATED: + connectionCreatedCounter.add(1, poolAttributes); + break; + case CONNECTION_BORROWED: + connectionBorrowedCounter.add(1, poolAttributes); + if (context.getAverageConnectionWaitTime() > 0) { + connectionWaitTimeHistogram.record(context.getAverageConnectionWaitTime(), poolAttributes); + } + break; + case CONNECTION_RETURNED: + connectionReturnedCounter.add(1, poolAttributes); + break; + case CONNECTION_CLOSED: + connectionClosedCounter.add(1, poolAttributes); + break; + case POOL_REFRESHED: + poolRefreshedCounter.add(1, poolAttributes); + break; + case POOL_RECYCLED: + poolRecycledCounter.add(1, poolAttributes); + break; + case POOL_PURGED: + poolPurgedCounter.add(1, poolAttributes); + break; + default: + break; + } + } + + // Gauge callback methods + private void recordMaxPoolSize(ObservableDoubleMeasurement measurement) { + for (UCPEventContext context : latestContextByPool.values()) { + measurement.record(context.maxPoolSize(), + Attributes.of(POOL_NAME_KEY, context.poolName())); + } + } + + private void recordMinPoolSize(ObservableDoubleMeasurement measurement) { + for (UCPEventContext context : latestContextByPool.values()) { + measurement.record(context.minPoolSize(), + Attributes.of(POOL_NAME_KEY, context.poolName())); + } + } + + private void recordBorrowedConnections(ObservableDoubleMeasurement measurement) { + for (UCPEventContext context : latestContextByPool.values()) { + measurement.record(context.borrowedConnectionsCount(), + Attributes.of(POOL_NAME_KEY, context.poolName())); + } + } + + private void recordAvailableConnections(ObservableDoubleMeasurement measurement) { + for (UCPEventContext context : latestContextByPool.values()) { + measurement.record(context.availableConnectionsCount(), + Attributes.of(POOL_NAME_KEY, context.poolName())); + } + } + + private void recordTotalConnections(ObservableDoubleMeasurement measurement) { + for (UCPEventContext context : latestContextByPool.values()) { + measurement.record(context.totalConnections(), + Attributes.of(POOL_NAME_KEY, context.poolName())); + } + } + + private void recordClosedConnections(ObservableDoubleMeasurement measurement) { + for (UCPEventContext context : latestContextByPool.values()) { + measurement.record(context.closedConnections(), + Attributes.of(POOL_NAME_KEY, context.poolName())); + } + } + + private void recordCreatedConnections(ObservableDoubleMeasurement measurement) { + for (UCPEventContext context : latestContextByPool.values()) { + measurement.record(context.createdConnections(), + Attributes.of(POOL_NAME_KEY, context.poolName())); + } + } + + private void recordAverageWaitTime(ObservableDoubleMeasurement measurement) { + for (UCPEventContext context : latestContextByPool.values()) { + measurement.record(context.getAverageConnectionWaitTime(), + Attributes.of(POOL_NAME_KEY, context.poolName())); + } + } + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider new file mode 100644 index 00000000..738515ba --- /dev/null +++ b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider @@ -0,0 +1,3 @@ +oracle.ucp.provider.observability.logging.LoggingUCPEventListenerProvider +oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider +oracle.ucp.provider.observability.otel.OtelOpenTelemetryUCPEventListenerProvider \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JfrAndLoggingStressTestUCP.java b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JfrAndLoggingStressTestUCP.java new file mode 100644 index 00000000..a198ec2e --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JfrAndLoggingStressTestUCP.java @@ -0,0 +1,176 @@ +package oracle.ucp.provider.observability; + +import oracle.ucp.UniversalConnectionPool; +import oracle.ucp.UniversalConnectionPoolAdapter; +import oracle.ucp.UniversalPooledConnection; +import oracle.ucp.admin.UniversalConnectionPoolManager; +import oracle.ucp.admin.UniversalConnectionPoolManagerImpl; +import oracle.ucp.jdbc.PoolDataSource; +import oracle.ucp.jdbc.PoolDataSourceFactory; + +/** + * Stress test for JFR and logging UCP event listeners. + * Generates realistic connection pool usage patterns to validate event recording. + */ +public class JfrAndLoggingStressTestUCP { + + private static final String POOL_NAME = "test-pool"; + private static final String DB_URL = "jdbc:oracle:thin:@adb_medium?TNS_ADMIN=/Users/abdessamadelaaissaoui/Desktop/Wallet_adb/"; + private static final String DB_USER = "ADMIN"; + private static final String DB_PASSWORD = "Madara@@1234"; + + /** + * Executes comprehensive pool stress test with JFR monitoring. + * + * @param args command line arguments + * @throws Exception if test execution fails + */ + public static void main(String[] args) throws Exception { + configureTracing(); + + PoolDataSource pds = createPoolDataSource(); + UniversalConnectionPoolManager mgr = UniversalConnectionPoolManagerImpl + .getUniversalConnectionPoolManager(); + + // Pool initialization + System.out.println("=== [1] POOL INITIALIZATION ==="); + mgr.createConnectionPool((UniversalConnectionPoolAdapter) pds); + mgr.startConnectionPool(POOL_NAME); + + // Connection wave patterns + System.out.println("=== [2] CONNECTION WAVES ==="); + createConnectionWaves(mgr, POOL_NAME, 5, 15); + + // Maintenance operations + System.out.println("=== [3] MAINTENANCE OPERATIONS ==="); + performMaintenanceWithMetrics(mgr, POOL_NAME); + + // Shutdown + System.out.println("=== [4] POOL SHUTDOWN ==="); + mgr.stopConnectionPool(POOL_NAME); + mgr.destroyConnectionPool(POOL_NAME); + } + + /** + * Configures JVM tracing and logging properties. + */ + private static void configureTracing() { + System.setProperty("oracle.jdbc.Trace", "true"); + System.setProperty("java.util.logging.config.file", "./logging.properties"); + System.setProperty("oracle.ucp.wls.jta", "false"); + } + + /** + * Creates and configures the pool data source. + * + * @return configured pool data source + * @throws Exception if configuration fails + */ + private static PoolDataSource createPoolDataSource() throws Exception { + PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); + pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); + pds.setURL(DB_URL); + pds.setUser(DB_USER); + pds.setPassword(DB_PASSWORD); + pds.setConnectionPoolName(POOL_NAME); + pds.setMinPoolSize(5); + pds.setMaxPoolSize(30); + pds.setInitialPoolSize(5); + pds.setUCPEventListenerProvider("logging-ucp-listener"); + pds.setConnectionWaitTimeout(3); + return pds; + } + + /** + * Creates waves of connection borrowing and returning to stress test the pool. + * + * @param mgr connection pool manager + * @param poolName name of the pool to test + * @param waveCount number of waves to execute + * @param connectionsPerWave connections to borrow per wave + * @throws Exception if wave execution fails + */ + private static void createConnectionWaves(UniversalConnectionPoolManager mgr, + String poolName, int waveCount, + int connectionsPerWave) throws Exception { + UniversalConnectionPool pool = mgr.getConnectionPool(poolName); + + for (int wave = 1; wave <= waveCount; wave++) { + System.out.println("\n--- Starting Wave " + wave + " ---"); + + // Borrow connections + UniversalPooledConnection[] connections = new UniversalPooledConnection[connectionsPerWave]; + for (int i = 0; i < connectionsPerWave; i++) { + connections[i] = pool.borrowConnection(pool.getConnectionRetrievalInfo()); + Thread.sleep(100); + System.out.printf("Borrowed %d/%d (Active: %d, Available: %d)%n", + i + 1, connectionsPerWave, + pool.getStatistics().getBorrowedConnectionsCount(), + pool.getStatistics().getAvailableConnectionsCount()); + } + + Thread.sleep(1000); + + // Release connections + for (int i = 0; i < connectionsPerWave; i++) { + if (wave % 2 == 0) { + pool.returnConnection(connections[i]); + } else { + pool.closeConnection(connections[i]); + } + Thread.sleep(150); + System.out.printf("Released %d/%d (Active: %d, Available: %d)%n", + i + 1, connectionsPerWave, + pool.getStatistics().getBorrowedConnectionsCount(), + pool.getStatistics().getAvailableConnectionsCount()); + } + + Thread.sleep(2000); + } + } + + /** + * Performs maintenance operations on the pool and prints metrics. + * + * @param mgr connection pool manager + * @param poolName name of the pool + * @throws Exception if maintenance operations fail + */ + private static void performMaintenanceWithMetrics(UniversalConnectionPoolManager mgr, + String poolName) throws Exception { + UniversalConnectionPool pool = mgr.getConnectionPool(poolName); + + printPoolMetrics("Pre-Purge", pool); + pool.purge(); + printPoolMetrics("Post-Purge", pool); + Thread.sleep(1500); + + printPoolMetrics("Pre-Recycle", pool); + pool.recycle(); + printPoolMetrics("Post-Recycle", pool); + Thread.sleep(1500); + + printPoolMetrics("Pre-Refresh", pool); + pool.refresh(); + printPoolMetrics("Post-Refresh", pool); + Thread.sleep(1500); + } + + /** + * Prints current pool metrics for the specified phase. + * + * @param phase description of the current phase + * @param pool connection pool to analyze + */ + private static void printPoolMetrics(String phase, UniversalConnectionPool pool) { + try { + System.out.printf("[%s] Borrowed: %d, Available: %d, Total: %d%n", + phase, + pool.getStatistics().getBorrowedConnectionsCount(), + pool.getStatistics().getAvailableConnectionsCount(), + pool.getStatistics().getTotalConnectionsCount()); + } catch (Exception e) { + System.out.println("[" + phase + "] Metrics unavailable: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelStressTestUCP.java b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelStressTestUCP.java new file mode 100644 index 00000000..0e90b475 --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelStressTestUCP.java @@ -0,0 +1,287 @@ +package oracle.ucp.provider.observability; + +import oracle.ucp.UniversalConnectionPool; +import oracle.ucp.UniversalConnectionPoolAdapter; +import oracle.ucp.UniversalPooledConnection; +import oracle.ucp.admin.UniversalConnectionPoolManager; +import oracle.ucp.admin.UniversalConnectionPoolManagerImpl; +import oracle.ucp.jdbc.PoolDataSource; +import oracle.ucp.jdbc.PoolDataSourceFactory; +import oracle.ucp.provider.observability.otel.OpenTelemetryConfig; + +/** + * Comprehensive stress test for OpenTelemetry UCP event listener. + * Generates realistic usage patterns to validate metric collection including + * pool lifecycle, connection operations, and maintenance events. + */ +public class OtelStressTestUCP { + + private static final String POOL_NAME = "test-pool"; + private static final String DB_URL = "jdbc:oracle:thin:@adb_medium?TNS_ADMIN=/Users/abdessamadelaaissaoui/Desktop/Wallet_adb/"; + private static final String DB_USER = "ADMIN"; + private static final String DB_PASSWORD = "Madara@@1234"; + + // Test configuration + private static final int WAVE_COUNT = 12; + private static final int CONNECTIONS_PER_WAVE = 15; + private static final int STRESS_CYCLES = 5; + + /** + * Executes comprehensive OpenTelemetry stress test. + * + * @param args command line arguments + * @throws Exception if test execution fails + */ + public static void main(String[] args) throws Exception { + // Initialize OpenTelemetry + System.out.println("=== Initializing OpenTelemetry ==="); + OpenTelemetryConfig.initialize(); + System.out.println("OpenTelemetry ready! Metrics: http://localhost:8080/metrics\n"); + + setupPoolWithOpenTelemetry(); + UniversalConnectionPoolManager mgr = UniversalConnectionPoolManagerImpl + .getUniversalConnectionPoolManager(); + + // Phase 1: Pool lifecycle + System.out.println("=== [PHASE 1] POOL LIFECYCLE EVENTS ==="); + PoolDataSource pds = createPoolDataSource(); + mgr.createConnectionPool((UniversalConnectionPoolAdapter) pds); + Thread.sleep(1000); + mgr.startConnectionPool(POOL_NAME); + Thread.sleep(1000); + + // Phase 2: Connection stress + System.out.println("\n=== [PHASE 2] CONNECTION STRESS TESTING ==="); + for (int cycle = 1; cycle <= STRESS_CYCLES; cycle++) { + System.out.println("--- Stress Cycle " + cycle + "/" + STRESS_CYCLES + " ---"); + executeConnectionWaves(mgr, POOL_NAME); + Thread.sleep(2000); + } + + // Phase 3: Maintenance operations + System.out.println("\n=== [PHASE 3] MAINTENANCE OPERATIONS ==="); + executeMaintenanceOperations(mgr, POOL_NAME); + + // Phase 4: Pool restart + System.out.println("\n=== [PHASE 4] POOL RESTART EVENTS ==="); + mgr.stopConnectionPool(POOL_NAME); + Thread.sleep(1000); + + // Phase 5: Final burst + System.out.println("\n=== [PHASE 5] FINAL CONNECTION BURST ==="); + executeFinalBurst(mgr, POOL_NAME); + + // Phase 6: Cleanup + System.out.println("\n=== [PHASE 6] POOL DESTRUCTION ==="); + mgr.stopConnectionPool(POOL_NAME); + Thread.sleep(1000); + mgr.destroyConnectionPool(POOL_NAME); + + System.out.println("\n=== OpenTelemetry UCP Stress Test Completed ==="); + printExpectedMetrics(); + } + + /** + * Configures OpenTelemetry-specific settings. + */ + private static void setupPoolWithOpenTelemetry() { + System.setProperty("oracle.ucp.wls.jta", "false"); + System.setProperty("UCPEventListenerProvider", "opentelemetry-ucp-listener"); + System.out.println("OpenTelemetry provider configured"); + } + + /** + * Creates and configures pool data source for OpenTelemetry testing. + * + * @return configured pool data source + * @throws Exception if configuration fails + */ + private static PoolDataSource createPoolDataSource() throws Exception { + PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); + pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); + pds.setURL(DB_URL); + pds.setUser(DB_USER); + pds.setPassword(DB_PASSWORD); + pds.setConnectionPoolName(POOL_NAME); + pds.setMinPoolSize(2); + pds.setMaxPoolSize(30); + pds.setInitialPoolSize(3); + pds.setConnectionWaitTimeout(1); + + System.out.println("Pool configured: Min=2, Max=30, Initial=3"); + return pds; + } + + /** + * Executes connection waves to generate borrowing and returning events. + * + * @param mgr connection pool manager + * @param poolName name of the pool + * @throws Exception if wave execution fails + */ + private static void executeConnectionWaves(UniversalConnectionPoolManager mgr, + String poolName) throws Exception { + UniversalConnectionPool pool = mgr.getConnectionPool(poolName); + + for (int wave = 1; wave <= WAVE_COUNT; wave++) { + System.out.println(" Wave " + wave + "/" + WAVE_COUNT + + ": Borrowing " + CONNECTIONS_PER_WAVE + " connections"); + + // Borrow phase + UniversalPooledConnection[] connections = new UniversalPooledConnection[CONNECTIONS_PER_WAVE]; + for (int i = 0; i < CONNECTIONS_PER_WAVE; i++) { + try { + connections[i] = pool.borrowConnection(pool.getConnectionRetrievalInfo()); + Thread.sleep(50); + } catch (Exception e) { + System.out.println(" Connection " + (i + 1) + " failed: " + e.getMessage()); + connections[i] = null; + } + + if ((i + 1) % 4 == 0) { + printCurrentMetrics(pool, " Borrowed " + (i + 1)); + } + } + + Thread.sleep(800); + + // Release phase + for (int i = 0; i < CONNECTIONS_PER_WAVE; i++) { + if (connections[i] != null) { + if (wave % 3 == 0) { + pool.closeConnection(connections[i]); + } else { + pool.returnConnection(connections[i]); + } + Thread.sleep(30); + } + } + + printCurrentMetrics(pool, " Wave " + wave + " completed"); + Thread.sleep(500); + } + } + + /** + * Executes maintenance operations to generate maintenance events. + * + * @param mgr connection pool manager + * @param poolName name of the pool + * @throws Exception if maintenance fails + */ + private static void executeMaintenanceOperations(UniversalConnectionPoolManager mgr, + String poolName) throws Exception { + UniversalConnectionPool pool = mgr.getConnectionPool(poolName); + + // Borrow test connections + UniversalPooledConnection[] testConnections = new UniversalPooledConnection[5]; + for (int i = 0; i < 5; i++) { + try { + testConnections[i] = pool.borrowConnection(pool.getConnectionRetrievalInfo()); + System.out.println(" Borrowed test connection " + (i + 1) + "/5"); + } catch (Exception e) { + System.out.println(" Could not borrow test connection " + (i + 1)); + testConnections[i] = null; + break; + } + } + + printCurrentMetrics(pool, "Pre-maintenance baseline"); + + // Execute maintenance operations + System.out.println(" Executing PURGE operation"); + pool.purge(); + Thread.sleep(1000); + printCurrentMetrics(pool, "Post-purge"); + + System.out.println(" Executing RECYCLE operation"); + pool.recycle(); + Thread.sleep(1000); + printCurrentMetrics(pool, "Post-recycle"); + + System.out.println(" Executing REFRESH operation"); + pool.refresh(); + Thread.sleep(1000); + printCurrentMetrics(pool, "Post-refresh"); + + // Return test connections + for (UniversalPooledConnection conn : testConnections) { + if (conn != null) { + try { + pool.returnConnection(conn); + } catch (Exception e) { + System.out.println(" Could not return connection: " + e.getMessage()); + } + } + } + } + + /** + * Executes final connection burst to stress test pool limits. + * + * @param mgr connection pool manager + * @param poolName name of the pool + * @throws Exception if burst execution fails + */ + private static void executeFinalBurst(UniversalConnectionPoolManager mgr, + String poolName) throws Exception { + UniversalConnectionPool pool = mgr.getConnectionPool(poolName); + System.out.println(" Executing final connection burst"); + + UniversalPooledConnection[] burstConnections = new UniversalPooledConnection[30]; + + for (int i = 0; i < 30; i++) { + try { + burstConnections[i] = pool.borrowConnection(pool.getConnectionRetrievalInfo()); + if (i % 5 == 0) { + printCurrentMetrics(pool, " Burst progress " + (i + 1) + "/30"); + } + } catch (Exception e) { + System.out.println(" Burst connection " + (i + 1) + " failed (expected)"); + burstConnections[i] = null; + } + } + + Thread.sleep(1000); + + // Rapid release + for (int i = 0; i < 30; i++) { + if (burstConnections[i] != null) { + pool.returnConnection(burstConnections[i]); + } + } + + printCurrentMetrics(pool, "Final burst completed"); + } + + /** + * Prints current pool metrics for correlation with OpenTelemetry data. + * + * @param pool connection pool to analyze + * @param phase description of current phase + */ + private static void printCurrentMetrics(UniversalConnectionPool pool, String phase) { + try { + System.out.printf(" [%s] Borrowed: %d, Available: %d, Total: %d, Created: %d, Closed: %d%n", + phase, + pool.getStatistics().getBorrowedConnectionsCount(), + pool.getStatistics().getAvailableConnectionsCount(), + pool.getStatistics().getTotalConnectionsCount(), + pool.getStatistics().getConnectionsCreatedCount(), + pool.getStatistics().getConnectionsClosedCount()); + } catch (Exception e) { + System.out.println(" [" + phase + "] Metrics temporarily unavailable"); + } + } + + /** + * Prints expected metrics information for validation. + */ + private static void printExpectedMetrics() { + System.out.println("Expected metrics at http://localhost:8080/metrics:"); + System.out.println("- Event Counters: ucp_pool_created_total, ucp_connection_borrowed_total, etc."); + System.out.println("- State Gauges: ucp_borrowed_connections, ucp_available_connections, etc."); + System.out.println("- Performance: ucp_connection_wait_time_ms histogram"); + System.out.println("- Labels: pool_name=\"" + POOL_NAME + "\""); + } +} \ No newline at end of file From f0952721d4b127752ac2ac3737271bfe8cbd847e Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Fri, 10 Oct 2025 17:33:08 +0100 Subject: [PATCH 2/2] Implementing JFR and Open Telemetry providers for monitoring UCP(resolved comments) --- ojdbc-provider-observability/pom.xml | 100 ++-- ojdbc-provider-observability/prometheus.yml | 18 - .../LoggingUCPEventListenerProvider.java | 55 --- .../logging/UCPEventPrinter.java | 70 --- .../otel/OpenTelemetryConfig.java | 54 --- ...OpenTelemetryUCPEventListenerProvider.java | 344 -------------- .../otel/OtelUCPEventListenerProvider.java | 435 ++++++++++++++++++ ...e.ucp.events.core.UCPEventListenerProvider | 3 +- .../JFRUCPEventListenerProviderTest.java | 373 +++++++++++++++ .../JfrAndLoggingStressTestUCP.java | 176 ------- .../observability/OtelStressTestUCP.java | 287 ------------ .../provider/observability/OtelUCPTest.java | 221 +++++++++ 12 files changed, 1072 insertions(+), 1064 deletions(-) delete mode 100644 ojdbc-provider-observability/prometheus.yml delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/LoggingUCPEventListenerProvider.java delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/UCPEventPrinter.java delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OpenTelemetryConfig.java delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelOpenTelemetryUCPEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java delete mode 100644 ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JfrAndLoggingStressTestUCP.java delete mode 100644 ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelStressTestUCP.java create mode 100644 ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index ed4ea8f9..12d9195f 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -5,9 +5,9 @@ 4.0.0 - com.oracle.database.jdbc - ojdbc-extensions - 1.0.3 + com.oracle.database.jdbc + ojdbc-extensions + 1.0.3 Oracle JDBC Observability Provider @@ -15,18 +15,21 @@ ojdbc-provider-observability - 1.44.1 - 11 - 11 + 1.44.1 + 11 + 11 + + com.oracle.database.jdbc + ojdbc11 + + com.oracle - ojdbc - 11.2.0.4 - system - /Users/abdessamadelaaissaoui/Desktop/ojdbc11.jar + ucp + 11.2.0.4-SNAPSHOT io.opentelemetry @@ -49,65 +52,46 @@ test - ojdbc-provider-common - com.oracle.database.jdbc - tests - test-jar - - - com.oracle - ucp - 11.2.0.4 - system - /Users/abdessamadelaaissaoui/Desktop/ucp11.jar - - - com.oracle.database.security - oraclepki - 23.3.0.23.09 - - - com.oracle.database.ha - ons - 23.9.0.25.07 - - - io.opentelemetry - opentelemetry-api - 1.32.0 + junit + junit + 4.13.2 + test io.opentelemetry opentelemetry-sdk - 1.32.0 + ${opentelemetry.version} + test io.opentelemetry - opentelemetry-exporter-prometheus - 1.32.0-alpha + opentelemetry-sdk-testing + 1.32.0 + test - io.opentelemetry - opentelemetry-sdk-extension-autoconfigure - 1.32.0 + ojdbc-provider-common + com.oracle.database.jdbc + tests + test-jar - - - org.apache.maven.plugins - maven-surefire-plugin - - - none - alphabetical - - - + + + org.apache.maven.plugins + maven-surefire-plugin + + + none + alphabetical + + + \ No newline at end of file diff --git a/ojdbc-provider-observability/prometheus.yml b/ojdbc-provider-observability/prometheus.yml deleted file mode 100644 index 5d0aa281..00000000 --- a/ojdbc-provider-observability/prometheus.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Global configuration -global: - scrape_interval: 15s # Scrape targets every 15 seconds - evaluation_interval: 15s # Evaluate rules every 15 seconds - -# Scrape configuration -scrape_configs: - # Job to scrape your UCP application - - job_name: 'ucp-application' - static_configs: - - targets: ['localhost:8080'] # Your app's metrics endpoint - scrape_interval: 5s # Scrape every 5 seconds for testing - metrics_path: '/metrics' # Path to metrics endpoint - - # Job to scrape Prometheus itself (optional) - - job_name: 'prometheus' - static_configs: - - targets: ['localhost:9091'] # ✅ Changed from 9090 to 9091 \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/LoggingUCPEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/LoggingUCPEventListenerProvider.java deleted file mode 100644 index 3282b707..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/LoggingUCPEventListenerProvider.java +++ /dev/null @@ -1,55 +0,0 @@ -package oracle.ucp.provider.observability.logging; - -import oracle.ucp.events.core.UCPEventContext; -import oracle.ucp.events.core.UCPEventListener; -import oracle.ucp.events.core.UCPEventListenerProvider; - -import java.util.Map; - -/** - * Provider that supplies a UCP event listener for logging events to standard output. - * Uses UCPEventPrinter for human-readable formatted output. - */ -public final class LoggingUCPEventListenerProvider implements UCPEventListenerProvider { - - private final UCPEventListener listener; - - /** - * Singleton listener that logs all UCP events using UCPEventPrinter. - * Thread-safe and prints detailed formatted output for all event types. - */ - public static final UCPEventListener TRACE_EVENT_LISTENER = new UCPEventListener() { - @Override - public void onUCPEvent(EventType eventType, UCPEventContext context) { - UCPEventPrinter.PRINT_EVENT.accept(eventType, context); - } - }; - - /** - * Creates a new provider instance. - */ - public LoggingUCPEventListenerProvider() { - this.listener = TRACE_EVENT_LISTENER; - } - - /** - * Returns the provider's unique identifier. - * - * @return "logging-ucp-listener" - */ - @Override - public String getName() { - return "logging-ucp-listener"; - } - - /** - * Returns the logging listener instance. - * - * @param config configuration map (ignored) - * @return the logging event listener - */ - @Override - public UCPEventListener getListener(Map config) { - return listener; - } -} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/UCPEventPrinter.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/UCPEventPrinter.java deleted file mode 100644 index dbd8bfce..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/logging/UCPEventPrinter.java +++ /dev/null @@ -1,70 +0,0 @@ -package oracle.ucp.provider.observability.logging; - -import oracle.ucp.events.core.UCPEventContext; -import oracle.ucp.events.core.UCPEventListener; - -import java.util.function.BiConsumer; - -/** - * Utility for formatting and printing UCP events to standard output. - * Provides human-readable event representations with consistent formatting. - */ -public class UCPEventPrinter { - - /** Separator line for event output */ - private static final String SEPARATOR = "========================================="; - - /** Format string for aligned event detail lines */ - private static final String LINE_FORMAT = "%-30s %s%n"; - - /** - * Ready-to-use consumer that prints events to System.out. - * Handles null contexts gracefully and uses detailed formatting for all events. - */ - public static final BiConsumer PRINT_EVENT = - (eventType, context) -> { - if (context == null) { - System.out.println("[WARNING] Received null event context"); - return; - } - printStandardEvent(eventType, context); - }; - - /** - * Prints events in detailed, formatted output with pool metrics. - * - * @param eventType type of event being printed - * @param context event context containing pool data - */ - private static void printStandardEvent(UCPEventListener.EventType eventType, - UCPEventContext context) { - StringBuilder sb = new StringBuilder("\n") - .append(SEPARATOR).append("\n") - .append(String.format(" UCP Event: %s%n", eventType)) - .append(SEPARATOR).append("\n") - .append(formatLine("Pool Name:", context.poolName())) - .append(formatLine("Timestamp:", context.formattedTimestamp())) - .append(formatLine("Max Pool Size:", context.maxPoolSize())) - .append(formatLine("Min Pool Size:", context.minPoolSize())) - .append(formatLine("Borrowed Connections:", context.borrowedConnectionsCount())) - .append(formatLine("Available Connections:", context.availableConnectionsCount())) - .append(formatLine("Total Active Connections:", context.totalConnections())) - .append(formatLine("Average Connection WaitTime:", - context.getAverageConnectionWaitTime() + " ms")) - .append(formatLine("Connections Created:", context.createdConnections())) - .append(formatLine("Connections Closed:", context.closedConnections())) - .append(SEPARATOR).append("\n\n"); - System.out.print(sb.toString()); - } - - /** - * Formats a label-value pair with consistent alignment. - * - * @param label description label (left-aligned) - * @param value corresponding value - * @return formatted string with alignment - */ - private static String formatLine(String label, Object value) { - return String.format(LINE_FORMAT, label, value); - } -} diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OpenTelemetryConfig.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OpenTelemetryConfig.java deleted file mode 100644 index 81413e45..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OpenTelemetryConfig.java +++ /dev/null @@ -1,54 +0,0 @@ -package oracle.ucp.provider.observability.otel; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; - -/** - * Configuration utility for initializing OpenTelemetry with Prometheus exporter. - * Sets up metrics collection and HTTP endpoint for scraping. - */ -public class OpenTelemetryConfig { - - /** - * Initializes OpenTelemetry with Prometheus HTTP server on port 8080. - * Sets up the global OpenTelemetry instance for UCP providers to use. - */ - public static void initialize() { - try { - // Create Prometheus HTTP server - PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder() - .setPort(8080) - .setHost("localhost") - .build(); - - // Build OpenTelemetry SDK with Prometheus exporter - OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder() - .setMeterProvider( - SdkMeterProvider.builder() - .registerMetricReader(prometheusServer) - .build() - ) - .build(); - - // Set as global instance - GlobalOpenTelemetry.set(openTelemetry); - - System.out.println("✅ OpenTelemetry initialized successfully!"); - System.out.println("📊 Metrics endpoint: http://localhost:8080/metrics"); - - } catch (Exception e) { - System.err.println("❌ Failed to initialize OpenTelemetry: " + e.getMessage()); - e.printStackTrace(); - } - } - - /** - * Shuts down OpenTelemetry gracefully. - * Should be called during application shutdown. - */ - public static void shutdown() { - System.out.println("🛑 Shutting down OpenTelemetry..."); - } -} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelOpenTelemetryUCPEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelOpenTelemetryUCPEventListenerProvider.java deleted file mode 100644 index 03bf3afe..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelOpenTelemetryUCPEventListenerProvider.java +++ /dev/null @@ -1,344 +0,0 @@ -package oracle.ucp.provider.observability.otel; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.*; -import oracle.ucp.events.core.UCPEventContext; -import oracle.ucp.events.core.UCPEventListener; -import oracle.ucp.events.core.UCPEventListenerProvider; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Provider that supplies a UCP event listener for recording OpenTelemetry metrics. - * Converts UCP events into metrics for integration with observability platforms. - */ -public final class OtelOpenTelemetryUCPEventListenerProvider implements UCPEventListenerProvider { - - private final UCPEventListener listener; - - /** - * Singleton listener that records UCP events as OpenTelemetry metrics. - * Thread-safe and generates gauges, counters, and histograms. - */ - public static final UCPEventListener TRACE_EVENT_LISTENER = new OtelOpenTelemetryUCPEventListener(); - - /** - * Creates a new provider instance. - */ - public OtelOpenTelemetryUCPEventListenerProvider() { - this.listener = TRACE_EVENT_LISTENER; - } - - /** - * Returns the provider's unique identifier. - * - * @return "opentelemetry-ucp-listener" - */ - @Override - public String getName() { - return "opentelemetry-ucp-listener"; - } - - /** - * Returns the OpenTelemetry listener instance. - * - * @param config configuration map (ignored) - * @return the OpenTelemetry event listener - */ - @Override - public UCPEventListener getListener(Map config) { - return listener; - } - - /** - * Internal listener that converts UCP events into OpenTelemetry metrics. - * Records pool state as gauges, event occurrences as counters, and performance as histograms. - */ - private static class OtelOpenTelemetryUCPEventListener implements UCPEventListener { - private static final long serialVersionUID = 1L; - - private final Meter meter = GlobalOpenTelemetry.getMeter("oracle.ucp.events"); - - // Attribute keys for metric labels - private static final AttributeKey POOL_NAME_KEY = AttributeKey.stringKey("pool_name"); - private static final AttributeKey EVENT_TYPE_KEY = AttributeKey.stringKey("event_type"); - - // Gauge metrics for current state - private final ObservableDoubleGauge maxPoolSizeGauge; - private final ObservableDoubleGauge minPoolSizeGauge; - private final ObservableDoubleGauge borrowedConnectionsGauge; - private final ObservableDoubleGauge availableConnectionsGauge; - private final ObservableDoubleGauge totalConnectionsGauge; - private final ObservableDoubleGauge closedConnectionsGauge; - private final ObservableDoubleGauge createdConnectionsGauge; - private final ObservableDoubleGauge averageWaitTimeGauge; - - // Counter metrics for event occurrences - private final LongCounter poolCreatedCounter; - private final LongCounter poolStartingCounter; - private final LongCounter poolStartedCounter; - private final LongCounter poolStoppedCounter; - private final LongCounter poolRestartingCounter; - private final LongCounter poolRestartedCounter; - private final LongCounter poolDestroyedCounter; - private final LongCounter connectionCreatedCounter; - private final LongCounter connectionBorrowedCounter; - private final LongCounter connectionReturnedCounter; - private final LongCounter connectionClosedCounter; - private final LongCounter poolRefreshedCounter; - private final LongCounter poolRecycledCounter; - private final LongCounter poolPurgedCounter; - - // Histogram metrics for performance - private final DoubleHistogram connectionWaitTimeHistogram; - - // Cache for storing latest context per pool for gauge callbacks - private final Map latestContextByPool = new ConcurrentHashMap<>(); - - /** - * Initializes all OpenTelemetry metric instruments. - */ - public OtelOpenTelemetryUCPEventListener() { - // Initialize gauge metrics - this.maxPoolSizeGauge = meter.gaugeBuilder("ucp_max_pool_size") - .setDescription("Configured maximum size of the connection pool") - .setUnit("connections") - .buildWithCallback(this::recordMaxPoolSize); - - this.minPoolSizeGauge = meter.gaugeBuilder("ucp_min_pool_size") - .setDescription("Configured minimum size of the connection pool") - .setUnit("connections") - .buildWithCallback(this::recordMinPoolSize); - - this.borrowedConnectionsGauge = meter.gaugeBuilder("ucp_borrowed_connections") - .setDescription("Current number of borrowed connections") - .setUnit("connections") - .buildWithCallback(this::recordBorrowedConnections); - - this.availableConnectionsGauge = meter.gaugeBuilder("ucp_available_connections") - .setDescription("Current number of available idle connections") - .setUnit("connections") - .buildWithCallback(this::recordAvailableConnections); - - this.totalConnectionsGauge = meter.gaugeBuilder("ucp_total_connections") - .setDescription("Total number of active connections") - .setUnit("connections") - .buildWithCallback(this::recordTotalConnections); - - this.closedConnectionsGauge = meter.gaugeBuilder("ucp_closed_connections_total") - .setDescription("Lifetime count of closed connections") - .setUnit("connections") - .buildWithCallback(this::recordClosedConnections); - - this.createdConnectionsGauge = meter.gaugeBuilder("ucp_created_connections_total") - .setDescription("Lifetime count of created connections") - .setUnit("connections") - .buildWithCallback(this::recordCreatedConnections); - - this.averageWaitTimeGauge = meter.gaugeBuilder("ucp_average_wait_time_ms") - .setDescription("Average connection wait time") - .setUnit("ms") - .buildWithCallback(this::recordAverageWaitTime); - - // Initialize counter metrics - this.poolCreatedCounter = meter.counterBuilder("ucp_pool_created_total") - .setDescription("Total pool creation events") - .setUnit("events") - .build(); - - this.poolStartingCounter = meter.counterBuilder("ucp_pool_starting_total") - .setDescription("Total pool starting events") - .setUnit("events") - .build(); - - this.poolStartedCounter = meter.counterBuilder("ucp_pool_started_total") - .setDescription("Total pool started events") - .setUnit("events") - .build(); - - this.poolStoppedCounter = meter.counterBuilder("ucp_pool_stopped_total") - .setDescription("Total pool stopped events") - .setUnit("events") - .build(); - - this.poolRestartingCounter = meter.counterBuilder("ucp_pool_restarting_total") - .setDescription("Total pool restarting events") - .setUnit("events") - .build(); - - this.poolRestartedCounter = meter.counterBuilder("ucp_pool_restarted_total") - .setDescription("Total pool restarted events") - .setUnit("events") - .build(); - - this.poolDestroyedCounter = meter.counterBuilder("ucp_pool_destroyed_total") - .setDescription("Total pool destroyed events") - .setUnit("events") - .build(); - - this.connectionCreatedCounter = meter.counterBuilder("ucp_connection_created_total") - .setDescription("Total connection creation events") - .setUnit("events") - .build(); - - this.connectionBorrowedCounter = meter.counterBuilder("ucp_connection_borrowed_total") - .setDescription("Total connection borrowed events") - .setUnit("events") - .build(); - - this.connectionReturnedCounter = meter.counterBuilder("ucp_connection_returned_total") - .setDescription("Total connection returned events") - .setUnit("events") - .build(); - - this.connectionClosedCounter = meter.counterBuilder("ucp_connection_closed_total") - .setDescription("Total connection closed events") - .setUnit("events") - .build(); - - this.poolRefreshedCounter = meter.counterBuilder("ucp_pool_refreshed_total") - .setDescription("Total pool refresh events") - .setUnit("events") - .build(); - - this.poolRecycledCounter = meter.counterBuilder("ucp_pool_recycled_total") - .setDescription("Total pool recycle events") - .setUnit("events") - .build(); - - this.poolPurgedCounter = meter.counterBuilder("ucp_pool_purged_total") - .setDescription("Total pool purge events") - .setUnit("events") - .build(); - - // Initialize histogram metrics - this.connectionWaitTimeHistogram = meter.histogramBuilder("ucp_connection_wait_time_ms") - .setDescription("Distribution of connection wait times") - .setUnit("ms") - .build(); - } - - @Override - public void onUCPEvent(EventType eventType, UCPEventContext context) { - if (context == null || eventType == null) { - return; - } - - // Update latest context cache for gauge callbacks - latestContextByPool.put(context.poolName(), context); - - Attributes poolAttributes = Attributes.of(POOL_NAME_KEY, context.poolName()); - - // Record event occurrence counter based on event type - switch (eventType) { - case POOL_CREATED: - poolCreatedCounter.add(1, poolAttributes); - break; - case POOL_STARTING: - poolStartingCounter.add(1, poolAttributes); - break; - case POOL_STARTED: - poolStartedCounter.add(1, poolAttributes); - break; - case POOL_STOPPED: - poolStoppedCounter.add(1, poolAttributes); - break; - case POOL_RESTARTING: - poolRestartingCounter.add(1, poolAttributes); - break; - case POOL_RESTARTED: - poolRestartedCounter.add(1, poolAttributes); - break; - case POOL_DESTROYED: - poolDestroyedCounter.add(1, poolAttributes); - break; - case CONNECTION_CREATED: - connectionCreatedCounter.add(1, poolAttributes); - break; - case CONNECTION_BORROWED: - connectionBorrowedCounter.add(1, poolAttributes); - if (context.getAverageConnectionWaitTime() > 0) { - connectionWaitTimeHistogram.record(context.getAverageConnectionWaitTime(), poolAttributes); - } - break; - case CONNECTION_RETURNED: - connectionReturnedCounter.add(1, poolAttributes); - break; - case CONNECTION_CLOSED: - connectionClosedCounter.add(1, poolAttributes); - break; - case POOL_REFRESHED: - poolRefreshedCounter.add(1, poolAttributes); - break; - case POOL_RECYCLED: - poolRecycledCounter.add(1, poolAttributes); - break; - case POOL_PURGED: - poolPurgedCounter.add(1, poolAttributes); - break; - default: - break; - } - } - - // Gauge callback methods - private void recordMaxPoolSize(ObservableDoubleMeasurement measurement) { - for (UCPEventContext context : latestContextByPool.values()) { - measurement.record(context.maxPoolSize(), - Attributes.of(POOL_NAME_KEY, context.poolName())); - } - } - - private void recordMinPoolSize(ObservableDoubleMeasurement measurement) { - for (UCPEventContext context : latestContextByPool.values()) { - measurement.record(context.minPoolSize(), - Attributes.of(POOL_NAME_KEY, context.poolName())); - } - } - - private void recordBorrowedConnections(ObservableDoubleMeasurement measurement) { - for (UCPEventContext context : latestContextByPool.values()) { - measurement.record(context.borrowedConnectionsCount(), - Attributes.of(POOL_NAME_KEY, context.poolName())); - } - } - - private void recordAvailableConnections(ObservableDoubleMeasurement measurement) { - for (UCPEventContext context : latestContextByPool.values()) { - measurement.record(context.availableConnectionsCount(), - Attributes.of(POOL_NAME_KEY, context.poolName())); - } - } - - private void recordTotalConnections(ObservableDoubleMeasurement measurement) { - for (UCPEventContext context : latestContextByPool.values()) { - measurement.record(context.totalConnections(), - Attributes.of(POOL_NAME_KEY, context.poolName())); - } - } - - private void recordClosedConnections(ObservableDoubleMeasurement measurement) { - for (UCPEventContext context : latestContextByPool.values()) { - measurement.record(context.closedConnections(), - Attributes.of(POOL_NAME_KEY, context.poolName())); - } - } - - private void recordCreatedConnections(ObservableDoubleMeasurement measurement) { - for (UCPEventContext context : latestContextByPool.values()) { - measurement.record(context.createdConnections(), - Attributes.of(POOL_NAME_KEY, context.poolName())); - } - } - - private void recordAverageWaitTime(ObservableDoubleMeasurement measurement) { - for (UCPEventContext context : latestContextByPool.values()) { - measurement.record(context.getAverageConnectionWaitTime(), - Attributes.of(POOL_NAME_KEY, context.poolName())); - } - } - } -} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java new file mode 100644 index 00000000..c62d211d --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -0,0 +1,435 @@ +package oracle.ucp.provider.observability.otel; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.*; +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; +import oracle.ucp.events.core.UCPEventListenerProvider; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * OpenTelemetry provider for UCP connection pool metrics. + *

+ * This provider converts Oracle UCP events into OpenTelemetry metrics + * following database client semantic conventions. Users must configure + * the OpenTelemetry SDK separately - this provider only generates + * metrics. + *

+ * + *

Configuration

+ *
{@code
+ * PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
+ * pds.setUCPEventListenerProvider("otel-ucp-listener");
+ * }
+ * + *

Generated Metrics

+ * + *

Gauges (7)

+ *
    + *
  • {@code db.client.connections.used} - Currently in-use + * connections (state=used)
  • + *
  • {@code db.client.connections.idle} - Currently idle connections + * (state=idle)
  • + *
  • {@code db.client.connections.count} - Total connections + * (borrowed + idle)
  • + *
  • {@code db.client.connections.max} - Maximum pool size
  • + *
  • {@code db.client.connections.min} - Minimum pool size
  • + *
  • {@code db.client.connections.created} - Total connections + * created since pool inception
  • + *
  • {@code db.client.connections.closed} - Total connections closed + * since pool inception
  • + *
+ * + *

Counters (14)

+ *
    + *
  • {@code db.client.connection.pool.created} - Pool creation + * events
  • + *
  • {@code db.client.connection.pool.starting} - Pool starting + * events
  • + *
  • {@code db.client.connection.pool.started} - Pool started + * events
  • + *
  • {@code db.client.connection.pool.stopped} - Pool stopped + * events
  • + *
  • {@code db.client.connection.pool.restarting} - Pool restarting + * events
  • + *
  • {@code db.client.connection.pool.restarted} - Pool restarted + * events
  • + *
  • {@code db.client.connection.pool.destroyed} - Pool destruction + * events
  • + *
  • {@code db.client.connection.created} - Connection creation + * events
  • + *
  • {@code db.client.connection.borrowed} - Connection borrow + * events
  • + *
  • {@code db.client.connection.returned} - Connection return + * events
  • + *
  • {@code db.client.connection.closed} - Connection close + * events
  • + *
  • {@code db.client.connection.pool.refreshed} - Pool refresh + * operations
  • + *
  • {@code db.client.connection.pool.recycled} - Pool recycle + * operations
  • + *
  • {@code db.client.connection.pool.purged} - Pool purge + * operations
  • + *
+ * + *

Histograms (1)

+ *
    + *
  • {@code db.client.connections.wait_time} - Connection + * acquisition wait time (milliseconds)
  • + *
+ * + *

Attributes

+ *
    + *
  • {@code pool.name} - Name of the connection pool
  • + *
  • {@code state} - Connection state (used/idle) for connection + * state gauges
  • + *
+ * + *

Requirements

+ *
    + *
  • Java 11 or higher
  • + *
  • OpenTelemetry API 1.0+
  • + *
  • OpenTelemetry SDK must be configured separately by the + * user
  • + *
+ * + * @since 1.0 + */ +public final class OtelUCPEventListenerProvider + implements UCPEventListenerProvider { + + private static final UCPEventListener LISTENER = + new OtelUCPEventListener(); + + @Override + public String getName() { + return "otel-ucp-listener"; + } + + @Override + public UCPEventListener getListener(Map config) { + return LISTENER; + } + + /** + * Internal listener that converts UCP events to OpenTelemetry + * metrics. Thread-safe and handles all 14 UCP event types. + */ + private static final class OtelUCPEventListener + implements UCPEventListener { + private static final long serialVersionUID = 1L; + + private final Meter meter = + GlobalOpenTelemetry.getMeter("oracle.ucp"); + + private static final AttributeKey POOL_NAME = + AttributeKey.stringKey("pool.name"); + private static final AttributeKey STATE = + AttributeKey.stringKey("state"); + + private final Map contextCache = + new ConcurrentHashMap(); + + private final ObservableLongGauge usedConnectionsGauge; + private final ObservableLongGauge idleConnectionsGauge; + private final ObservableLongGauge totalConnectionsGauge; + private final ObservableLongGauge maxConnectionsGauge; + private final ObservableLongGauge minConnectionsGauge; + private final ObservableLongGauge totalCreatedGauge; + private final ObservableLongGauge totalClosedGauge; + + private final LongCounter poolCreatedCounter; + private final LongCounter poolStartingCounter; + private final LongCounter poolStartedCounter; + private final LongCounter poolStoppedCounter; + private final LongCounter poolRestartingCounter; + private final LongCounter poolRestartedCounter; + private final LongCounter poolDestroyedCounter; + private final LongCounter connectionCreatedCounter; + private final LongCounter connectionBorrowedCounter; + private final LongCounter connectionReturnedCounter; + private final LongCounter connectionClosedCounter; + private final LongCounter poolRefreshedCounter; + private final LongCounter poolRecycledCounter; + private final LongCounter poolPurgedCounter; + + private final LongHistogram waitTimeHistogram; + + OtelUCPEventListener() { + this.usedConnectionsGauge = + meter.gaugeBuilder("db.client.connections.used") + .setDescription( + "The number of connections that are currently in use") + .setUnit("{connection}") + .ofLongs() + .buildWithCallback( + new Consumer() { + @Override + public void accept(ObservableLongMeasurement measurement) { + for (UCPEventContext ctx : contextCache.values()) { + measurement.record(ctx.borrowedConnectionsCount(), + Attributes.of(POOL_NAME, ctx.poolName(), STATE, + "used")); + } + } + }); + + this.idleConnectionsGauge = + meter.gaugeBuilder("db.client.connections.idle") + .setDescription( + "The number of available connections for use") + .setUnit("{connection}") + .ofLongs() + .buildWithCallback( + new Consumer() { + @Override + public void accept(ObservableLongMeasurement measurement) { + for (UCPEventContext ctx : contextCache.values()) { + measurement.record(ctx.availableConnectionsCount(), + Attributes.of(POOL_NAME, ctx.poolName(), STATE, + "idle")); + } + } + }); + + this.totalConnectionsGauge = + meter.gaugeBuilder("db.client.connections.count") + .setDescription( + "The total number of connections (idle + used)") + .setUnit("{connection}") + .ofLongs() + .buildWithCallback( + new Consumer() { + @Override + public void accept(ObservableLongMeasurement measurement) { + for (UCPEventContext ctx : contextCache.values()) { + measurement.record(ctx.totalConnections(), + Attributes.of(POOL_NAME, ctx.poolName())); + } + } + }); + + this.maxConnectionsGauge = + meter.gaugeBuilder("db.client.connections.max") + .setDescription("The maximum size of the pool") + .setUnit("{connection}") + .ofLongs() + .buildWithCallback( + new Consumer() { + @Override + public void accept(ObservableLongMeasurement measurement) { + for (UCPEventContext ctx : contextCache.values()) { + measurement.record(ctx.maxPoolSize(), + Attributes.of(POOL_NAME, ctx.poolName())); + } + } + }); + + this.minConnectionsGauge = + meter.gaugeBuilder("db.client.connections.min") + .setDescription("The minimum size of the pool") + .setUnit("{connection}") + .ofLongs() + .buildWithCallback( + new Consumer() { + @Override + public void accept(ObservableLongMeasurement measurement) { + for (UCPEventContext ctx : contextCache.values()) { + measurement.record(ctx.minPoolSize(), + Attributes.of(POOL_NAME, ctx.poolName())); + } + } + }); + + this.totalCreatedGauge = + meter.gaugeBuilder("db.client.connections.created") + .setDescription("The total number of connections created") + .setUnit("{connection}") + .ofLongs() + .buildWithCallback( + new Consumer() { + @Override + public void accept(ObservableLongMeasurement measurement) { + for (UCPEventContext ctx : contextCache.values()) { + measurement.record(ctx.createdConnections(), + Attributes.of(POOL_NAME, ctx.poolName())); + } + } + }); + + this.totalClosedGauge = + meter.gaugeBuilder("db.client.connections.closed") + .setDescription("The total number of connections closed") + .setUnit("{connection}") + .ofLongs() + .buildWithCallback( + new Consumer() { + @Override + public void accept(ObservableLongMeasurement measurement) { + for (UCPEventContext ctx : contextCache.values()) { + measurement.record(ctx.closedConnections(), + Attributes.of(POOL_NAME, ctx.poolName())); + } + } + }); + + this.poolCreatedCounter = + meter.counterBuilder("db.client.connection.pool.created") + .setDescription("Number of connection pool creation events") + .setUnit("{event}") + .build(); + + this.poolStartingCounter = + meter.counterBuilder("db.client.connection.pool.starting") + .setDescription("Number of pool starting events") + .setUnit("{event}") + .build(); + + this.poolStartedCounter = + meter.counterBuilder("db.client.connection.pool.started") + .setDescription("Number of pool started events") + .setUnit("{event}") + .build(); + + this.poolStoppedCounter = + meter.counterBuilder("db.client.connection.pool.stopped") + .setDescription("Number of pool stopped events") + .setUnit("{event}") + .build(); + + this.poolRestartingCounter = + meter.counterBuilder("db.client.connection.pool.restarting") + .setDescription("Number of pool restarting events") + .setUnit("{event}") + .build(); + + this.poolRestartedCounter = + meter.counterBuilder("db.client.connection.pool.restarted") + .setDescription("Number of pool restarted events") + .setUnit("{event}") + .build(); + + this.poolDestroyedCounter = + meter.counterBuilder("db.client.connection.pool.destroyed") + .setDescription( + "Number of connection pool destruction events") + .setUnit("{event}") + .build(); + + this.connectionCreatedCounter = + meter.counterBuilder("db.client.connection.created") + .setDescription("Number of connection creation events") + .setUnit("{event}") + .build(); + + this.connectionBorrowedCounter = + meter.counterBuilder("db.client.connection.borrowed") + .setDescription("Number of connection borrowed events") + .setUnit("{event}") + .build(); + + this.connectionReturnedCounter = + meter.counterBuilder("db.client.connection.returned") + .setDescription("Number of connection returned events") + .setUnit("{event}") + .build(); + + this.connectionClosedCounter = + meter.counterBuilder("db.client.connection.closed") + .setDescription("Number of connection closed events") + .setUnit("{event}") + .build(); + + this.poolRefreshedCounter = + meter.counterBuilder("db.client.connection.pool.refreshed") + .setDescription("Number of pool refresh operations") + .setUnit("{operation}") + .build(); + + this.poolRecycledCounter = + meter.counterBuilder("db.client.connection.pool.recycled") + .setDescription("Number of pool recycle operations") + .setUnit("{operation}") + .build(); + + this.poolPurgedCounter = + meter.counterBuilder("db.client.connection.pool.purged") + .setDescription("Number of pool purge operations") + .setUnit("{operation}") + .build(); + + this.waitTimeHistogram = + meter.histogramBuilder("db.client.connections.wait_time") + .setDescription( + "The time it took to obtain an open connection from the pool") + .setUnit("ms") + .ofLongs() + .build(); + } + + @Override + public void onUCPEvent(EventType eventType, UCPEventContext context) { + if (context == null || eventType == null) { + return; + } + + contextCache.put(context.poolName(), context); + + Attributes attrs = Attributes.of(POOL_NAME, context.poolName()); + + switch (eventType) { + case POOL_CREATED: + poolCreatedCounter.add(1, attrs); + break; + case POOL_STARTING: + poolStartingCounter.add(1, attrs); + break; + case POOL_STARTED: + poolStartedCounter.add(1, attrs); + break; + case POOL_STOPPED: + poolStoppedCounter.add(1, attrs); + break; + case POOL_RESTARTING: + poolRestartingCounter.add(1, attrs); + break; + case POOL_RESTARTED: + poolRestartedCounter.add(1, attrs); + break; + case POOL_DESTROYED: + poolDestroyedCounter.add(1, attrs); + break; + case CONNECTION_CREATED: + connectionCreatedCounter.add(1, attrs); + break; + case CONNECTION_BORROWED: + connectionBorrowedCounter.add(1, attrs); + long waitTime = context.getAverageConnectionWaitTime(); + if (waitTime > 0) { + waitTimeHistogram.record(waitTime, attrs); + } + break; + case CONNECTION_RETURNED: + connectionReturnedCounter.add(1, attrs); + break; + case CONNECTION_CLOSED: + connectionClosedCounter.add(1, attrs); + break; + case POOL_REFRESHED: + poolRefreshedCounter.add(1, attrs); + break; + case POOL_RECYCLED: + poolRecycledCounter.add(1, attrs); + break; + case POOL_PURGED: + poolPurgedCounter.add(1, attrs); + break; + } + } + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider index 738515ba..27deb021 100644 --- a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider +++ b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider @@ -1,3 +1,2 @@ -oracle.ucp.provider.observability.logging.LoggingUCPEventListenerProvider oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider -oracle.ucp.provider.observability.otel.OtelOpenTelemetryUCPEventListenerProvider \ No newline at end of file +oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java new file mode 100644 index 00000000..8ea22a3a --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java @@ -0,0 +1,373 @@ +package oracle.ucp.provider.observability; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.RecordingState; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingFile; +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; +import oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider; +import oracle.ucp.provider.observability.jfr.core.UCPEventFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +import static oracle.ucp.events.core.UCPEventListener.EventType; +import static org.junit.Assert.*; + +public class JFRUCPEventListenerProviderTest { + + private JFRUCPEventListenerProvider provider; + private UCPEventListener listener; + private Recording recording; + + @Before + public void setup() { + provider = new JFRUCPEventListenerProvider(); + listener = provider.getListener(null); + + recording = new Recording(); + recording.enable("ucp.*"); + recording.start(); + } + + @After + public void cleanup() { + if (recording != null) { + try { + if (recording.getState() == RecordingState.RUNNING) { + recording.stop(); + } + } catch (IllegalStateException e) { + // Already stopped, ignore + } finally { + recording.close(); + } + } + } + + @Test + public void testProviderName() { + assertEquals("jfr-ucp-listener", provider.getName()); + } + + @Test + public void testProviderReturnsListener() { + assertNotNull("Provider should return a listener", listener); + } + + @Test + public void testProviderReturnsSameListenerInstance() { + UCPEventListener listener1 = provider.getListener(null); + UCPEventListener listener2 = provider.getListener(new HashMap<>()); + assertSame("Provider should return same listener instance", listener1, + listener2); + } + + @Test + public void testProviderReturnsSingletonListener() { + assertSame("Listener should be singleton TRACE_EVENT_LISTENER", + JFRUCPEventListenerProvider.TRACE_EVENT_LISTENER, listener); + } + + @Test + public void testListenerAcceptsEvents() { + UCPEventContext ctx = createTestContext("pool1", 1, 1, 10, 2); + + listener.onUCPEvent(EventType.POOL_CREATED, ctx); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + listener.onUCPEvent(EventType.CONNECTION_RETURNED, ctx); + } + + @Test + public void testAllEventTypesAccepted() { + EventType[] allEvents = { + EventType.POOL_CREATED, EventType.POOL_STARTING, + EventType.POOL_STARTED, EventType.POOL_STOPPED, + EventType.POOL_RESTARTING, EventType.POOL_RESTARTED, + EventType.POOL_DESTROYED, EventType.CONNECTION_CREATED, + EventType.CONNECTION_BORROWED, EventType.CONNECTION_RETURNED, + EventType.CONNECTION_CLOSED, EventType.POOL_REFRESHED, + EventType.POOL_RECYCLED, EventType.POOL_PURGED + }; + + UCPEventContext ctx = createTestContext("test-pool", 1, 1, 10, 2); + + for (EventType event : allEvents) { + listener.onUCPEvent(event, ctx); + } + } + + @Test + public void testEventFactoryCreatesEvents() { + UCPEventContext ctx = createTestContext("pool1", 1, 1, 10, 2); + + Event event = UCPEventFactory.createEvent(EventType.POOL_CREATED, ctx); + assertNotNull("Factory should create event", event); + assertTrue("Event should be a JFR Event", event instanceof Event); + } + + @Test + public void testEventFactoryCreatesAllEventTypes() { + EventType[] allEvents = { + EventType.POOL_CREATED, EventType.POOL_STARTING, + EventType.POOL_STARTED, EventType.POOL_STOPPED, + EventType.POOL_RESTARTING, EventType.POOL_RESTARTED, + EventType.POOL_DESTROYED, EventType.CONNECTION_CREATED, + EventType.CONNECTION_BORROWED, EventType.CONNECTION_RETURNED, + EventType.CONNECTION_CLOSED, EventType.POOL_REFRESHED, + EventType.POOL_RECYCLED, EventType.POOL_PURGED + }; + + UCPEventContext ctx = createTestContext("test-pool", 1, 1, 10, 2); + + for (EventType eventType : allEvents) { + Event event = UCPEventFactory.createEvent(eventType, ctx); + assertNotNull("Factory should create event for " + eventType, event); + } + } + + @Test(expected = NullPointerException.class) + public void testEventFactoryRejectsNullContext() { + UCPEventFactory.createEvent(EventType.POOL_CREATED, null); + } + + @Test(expected = NullPointerException.class) + public void testEventFactoryRejectsNullEventType() { + UCPEventContext ctx = createTestContext("pool1", 1, 1, 10, 2); + UCPEventFactory.createEvent(null, ctx); + } + + @Test + public void testRecordEventCommitsEvent() throws IOException { + UCPEventContext ctx = createTestContext("record-test-pool", 5, 3, 10, + 2); + + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, ctx); + + if (recording.getState() == RecordingState.RUNNING) { + recording.stop(); + } + Path recordingFile = Files.createTempFile("ucp-test", ".jfr"); + recording.dump(recordingFile); + + List events = RecordingFile.readAllEvents(recordingFile); + List ucpEvents = events.stream() + .filter(e -> e.getEventType().getName().startsWith("ucp.")) + .collect(Collectors.toList()); + + assertTrue("Should have recorded at least one UCP event", + ucpEvents.size() > 0); + + Files.deleteIfExists(recordingFile); + } + + @Test + public void testRecordedEventContainsPoolName() throws IOException { + UCPEventContext ctx = createTestContext("test-pool-name", 1, 1, 10, 2); + + UCPEventFactory.recordEvent(EventType.POOL_CREATED, ctx); + + if (recording.getState() == RecordingState.RUNNING) { + recording.stop(); + } + Path recordingFile = Files.createTempFile("ucp-test", ".jfr"); + recording.dump(recordingFile); + + List events = RecordingFile.readAllEvents(recordingFile); + RecordedEvent ucpEvent = events.stream() + .filter(e -> e.getEventType().getName().equals("ucp.PoolCreated")) + .findFirst() + .orElse(null); + + assertNotNull("Should find PoolCreated event", ucpEvent); + assertEquals("test-pool-name", ucpEvent.getString("poolName")); + + Files.deleteIfExists(recordingFile); + } + + @Test + public void testRecordedEventContainsMetrics() throws IOException { + UCPEventContext ctx = createTestContext("metrics-pool", 5, 3, 10, 2); + + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, ctx); + + if (recording.getState() == RecordingState.RUNNING) { + recording.stop(); + } + Path recordingFile = Files.createTempFile("ucp-test", ".jfr"); + recording.dump(recordingFile); + + List events = RecordingFile.readAllEvents(recordingFile); + RecordedEvent ucpEvent = events.stream() + .filter(e -> + e.getEventType().getName().equals("ucp.ConnectionBorrowed")) + .findFirst() + .orElse(null); + + assertNotNull("Should find ConnectionBorrowed event", ucpEvent); + assertEquals("metrics-pool", ucpEvent.getString("poolName")); + assertEquals(5, ucpEvent.getInt("borrowedConnections")); + assertEquals(3, ucpEvent.getInt("availableConnections")); + assertEquals(8, ucpEvent.getInt("totalConnections")); + assertEquals(10, ucpEvent.getInt("maxPoolSize")); + assertEquals(2, ucpEvent.getInt("minPoolSize")); + + Files.deleteIfExists(recordingFile); + } + + @Test + public void testEmptyPoolNameAccepted() { + UCPEventContext ctx = createTestContext("", 1, 1, 10, 2); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + } + + @Test + public void testVeryLongPoolNameAccepted() { + String longName = new String(new char[1000]).replace('\0', 'a'); + UCPEventContext ctx = createTestContext(longName, 1, 1, 10, 2); + listener.onUCPEvent(EventType.POOL_CREATED, ctx); + } + + @Test + public void testZeroValuesAccepted() { + UCPEventContext ctx = createTestContext("pool1", 0, 0, 0, 0); + listener.onUCPEvent(EventType.POOL_CREATED, ctx); + } + + @Test + public void testLargeValuesAccepted() { + UCPEventContext ctx = createTestContext("pool1", + Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 0); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + } + + @Test + public void testMultiplePoolsAccepted() { + listener.onUCPEvent(EventType.POOL_CREATED, + createTestContext("pool1", 1, 1, 10, 2)); + listener.onUCPEvent(EventType.POOL_CREATED, + createTestContext("pool2", 2, 2, 20, 4)); + listener.onUCPEvent(EventType.POOL_CREATED, + createTestContext("pool3", 3, 3, 30, 6)); + } + + @Test + public void testConcurrentAccess() throws InterruptedException { + Thread[] threads = new Thread[5]; + for (int i = 0; i < 5; i++) { + final int threadId = i; + threads[i] = new Thread(() -> { + for (int j = 0; j < 10; j++) { + UCPEventContext ctx = createTestContext("pool" + threadId, j, j, + 10, 2); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + } + }); + threads[i].start(); + } + + for (Thread thread : threads) { + thread.join(); + } + } + + @Test + public void testRapidFireEvents() { + UCPEventContext ctx = createTestContext("rapid-pool", 1, 1, 10, 2); + + for (int i = 0; i < 1000; i++) { + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + } + } + + @Test + public void testAllLifecycleEventsInSequence() { + UCPEventContext ctx = createTestContext("lifecycle-pool", 1, 1, 10, 2); + + listener.onUCPEvent(EventType.POOL_CREATED, ctx); + listener.onUCPEvent(EventType.POOL_STARTING, ctx); + listener.onUCPEvent(EventType.POOL_STARTED, ctx); + listener.onUCPEvent(EventType.CONNECTION_CREATED, ctx); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + listener.onUCPEvent(EventType.CONNECTION_RETURNED, ctx); + listener.onUCPEvent(EventType.POOL_REFRESHED, ctx); + listener.onUCPEvent(EventType.POOL_RECYCLED, ctx); + listener.onUCPEvent(EventType.POOL_PURGED, ctx); + listener.onUCPEvent(EventType.CONNECTION_CLOSED, ctx); + listener.onUCPEvent(EventType.POOL_RESTARTING, ctx); + listener.onUCPEvent(EventType.POOL_RESTARTED, ctx); + listener.onUCPEvent(EventType.POOL_STOPPED, ctx); + listener.onUCPEvent(EventType.POOL_DESTROYED, ctx); + } + + private UCPEventContext createTestContext(String poolName, int borrowed, + int available, int max, int min) { + return new UCPEventContext() { + @Override + public String poolName() { + return poolName; + } + + @Override + public long timestamp() { + return System.currentTimeMillis(); + } + + @Override + public int borrowedConnectionsCount() { + return borrowed; + } + + @Override + public int availableConnectionsCount() { + return available; + } + + @Override + public int totalConnections() { + return borrowed + available; + } + + @Override + public int maxPoolSize() { + return max; + } + + @Override + public int minPoolSize() { + return min; + } + + @Override + public long getAverageConnectionWaitTime() { + return 0; + } + + @Override + public int createdConnections() { + return borrowed + available; + } + + @Override + public int closedConnections() { + return 0; + } + + @Override + public String formattedTimestamp() { + return new java.text.SimpleDateFormat( + "MMMM dd, yyyy HH:mm:ss.SSS z") + .format(new java.util.Date(timestamp())); + } + }; + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JfrAndLoggingStressTestUCP.java b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JfrAndLoggingStressTestUCP.java deleted file mode 100644 index a198ec2e..00000000 --- a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JfrAndLoggingStressTestUCP.java +++ /dev/null @@ -1,176 +0,0 @@ -package oracle.ucp.provider.observability; - -import oracle.ucp.UniversalConnectionPool; -import oracle.ucp.UniversalConnectionPoolAdapter; -import oracle.ucp.UniversalPooledConnection; -import oracle.ucp.admin.UniversalConnectionPoolManager; -import oracle.ucp.admin.UniversalConnectionPoolManagerImpl; -import oracle.ucp.jdbc.PoolDataSource; -import oracle.ucp.jdbc.PoolDataSourceFactory; - -/** - * Stress test for JFR and logging UCP event listeners. - * Generates realistic connection pool usage patterns to validate event recording. - */ -public class JfrAndLoggingStressTestUCP { - - private static final String POOL_NAME = "test-pool"; - private static final String DB_URL = "jdbc:oracle:thin:@adb_medium?TNS_ADMIN=/Users/abdessamadelaaissaoui/Desktop/Wallet_adb/"; - private static final String DB_USER = "ADMIN"; - private static final String DB_PASSWORD = "Madara@@1234"; - - /** - * Executes comprehensive pool stress test with JFR monitoring. - * - * @param args command line arguments - * @throws Exception if test execution fails - */ - public static void main(String[] args) throws Exception { - configureTracing(); - - PoolDataSource pds = createPoolDataSource(); - UniversalConnectionPoolManager mgr = UniversalConnectionPoolManagerImpl - .getUniversalConnectionPoolManager(); - - // Pool initialization - System.out.println("=== [1] POOL INITIALIZATION ==="); - mgr.createConnectionPool((UniversalConnectionPoolAdapter) pds); - mgr.startConnectionPool(POOL_NAME); - - // Connection wave patterns - System.out.println("=== [2] CONNECTION WAVES ==="); - createConnectionWaves(mgr, POOL_NAME, 5, 15); - - // Maintenance operations - System.out.println("=== [3] MAINTENANCE OPERATIONS ==="); - performMaintenanceWithMetrics(mgr, POOL_NAME); - - // Shutdown - System.out.println("=== [4] POOL SHUTDOWN ==="); - mgr.stopConnectionPool(POOL_NAME); - mgr.destroyConnectionPool(POOL_NAME); - } - - /** - * Configures JVM tracing and logging properties. - */ - private static void configureTracing() { - System.setProperty("oracle.jdbc.Trace", "true"); - System.setProperty("java.util.logging.config.file", "./logging.properties"); - System.setProperty("oracle.ucp.wls.jta", "false"); - } - - /** - * Creates and configures the pool data source. - * - * @return configured pool data source - * @throws Exception if configuration fails - */ - private static PoolDataSource createPoolDataSource() throws Exception { - PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); - pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); - pds.setURL(DB_URL); - pds.setUser(DB_USER); - pds.setPassword(DB_PASSWORD); - pds.setConnectionPoolName(POOL_NAME); - pds.setMinPoolSize(5); - pds.setMaxPoolSize(30); - pds.setInitialPoolSize(5); - pds.setUCPEventListenerProvider("logging-ucp-listener"); - pds.setConnectionWaitTimeout(3); - return pds; - } - - /** - * Creates waves of connection borrowing and returning to stress test the pool. - * - * @param mgr connection pool manager - * @param poolName name of the pool to test - * @param waveCount number of waves to execute - * @param connectionsPerWave connections to borrow per wave - * @throws Exception if wave execution fails - */ - private static void createConnectionWaves(UniversalConnectionPoolManager mgr, - String poolName, int waveCount, - int connectionsPerWave) throws Exception { - UniversalConnectionPool pool = mgr.getConnectionPool(poolName); - - for (int wave = 1; wave <= waveCount; wave++) { - System.out.println("\n--- Starting Wave " + wave + " ---"); - - // Borrow connections - UniversalPooledConnection[] connections = new UniversalPooledConnection[connectionsPerWave]; - for (int i = 0; i < connectionsPerWave; i++) { - connections[i] = pool.borrowConnection(pool.getConnectionRetrievalInfo()); - Thread.sleep(100); - System.out.printf("Borrowed %d/%d (Active: %d, Available: %d)%n", - i + 1, connectionsPerWave, - pool.getStatistics().getBorrowedConnectionsCount(), - pool.getStatistics().getAvailableConnectionsCount()); - } - - Thread.sleep(1000); - - // Release connections - for (int i = 0; i < connectionsPerWave; i++) { - if (wave % 2 == 0) { - pool.returnConnection(connections[i]); - } else { - pool.closeConnection(connections[i]); - } - Thread.sleep(150); - System.out.printf("Released %d/%d (Active: %d, Available: %d)%n", - i + 1, connectionsPerWave, - pool.getStatistics().getBorrowedConnectionsCount(), - pool.getStatistics().getAvailableConnectionsCount()); - } - - Thread.sleep(2000); - } - } - - /** - * Performs maintenance operations on the pool and prints metrics. - * - * @param mgr connection pool manager - * @param poolName name of the pool - * @throws Exception if maintenance operations fail - */ - private static void performMaintenanceWithMetrics(UniversalConnectionPoolManager mgr, - String poolName) throws Exception { - UniversalConnectionPool pool = mgr.getConnectionPool(poolName); - - printPoolMetrics("Pre-Purge", pool); - pool.purge(); - printPoolMetrics("Post-Purge", pool); - Thread.sleep(1500); - - printPoolMetrics("Pre-Recycle", pool); - pool.recycle(); - printPoolMetrics("Post-Recycle", pool); - Thread.sleep(1500); - - printPoolMetrics("Pre-Refresh", pool); - pool.refresh(); - printPoolMetrics("Post-Refresh", pool); - Thread.sleep(1500); - } - - /** - * Prints current pool metrics for the specified phase. - * - * @param phase description of the current phase - * @param pool connection pool to analyze - */ - private static void printPoolMetrics(String phase, UniversalConnectionPool pool) { - try { - System.out.printf("[%s] Borrowed: %d, Available: %d, Total: %d%n", - phase, - pool.getStatistics().getBorrowedConnectionsCount(), - pool.getStatistics().getAvailableConnectionsCount(), - pool.getStatistics().getTotalConnectionsCount()); - } catch (Exception e) { - System.out.println("[" + phase + "] Metrics unavailable: " + e.getMessage()); - } - } -} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelStressTestUCP.java b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelStressTestUCP.java deleted file mode 100644 index 0e90b475..00000000 --- a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelStressTestUCP.java +++ /dev/null @@ -1,287 +0,0 @@ -package oracle.ucp.provider.observability; - -import oracle.ucp.UniversalConnectionPool; -import oracle.ucp.UniversalConnectionPoolAdapter; -import oracle.ucp.UniversalPooledConnection; -import oracle.ucp.admin.UniversalConnectionPoolManager; -import oracle.ucp.admin.UniversalConnectionPoolManagerImpl; -import oracle.ucp.jdbc.PoolDataSource; -import oracle.ucp.jdbc.PoolDataSourceFactory; -import oracle.ucp.provider.observability.otel.OpenTelemetryConfig; - -/** - * Comprehensive stress test for OpenTelemetry UCP event listener. - * Generates realistic usage patterns to validate metric collection including - * pool lifecycle, connection operations, and maintenance events. - */ -public class OtelStressTestUCP { - - private static final String POOL_NAME = "test-pool"; - private static final String DB_URL = "jdbc:oracle:thin:@adb_medium?TNS_ADMIN=/Users/abdessamadelaaissaoui/Desktop/Wallet_adb/"; - private static final String DB_USER = "ADMIN"; - private static final String DB_PASSWORD = "Madara@@1234"; - - // Test configuration - private static final int WAVE_COUNT = 12; - private static final int CONNECTIONS_PER_WAVE = 15; - private static final int STRESS_CYCLES = 5; - - /** - * Executes comprehensive OpenTelemetry stress test. - * - * @param args command line arguments - * @throws Exception if test execution fails - */ - public static void main(String[] args) throws Exception { - // Initialize OpenTelemetry - System.out.println("=== Initializing OpenTelemetry ==="); - OpenTelemetryConfig.initialize(); - System.out.println("OpenTelemetry ready! Metrics: http://localhost:8080/metrics\n"); - - setupPoolWithOpenTelemetry(); - UniversalConnectionPoolManager mgr = UniversalConnectionPoolManagerImpl - .getUniversalConnectionPoolManager(); - - // Phase 1: Pool lifecycle - System.out.println("=== [PHASE 1] POOL LIFECYCLE EVENTS ==="); - PoolDataSource pds = createPoolDataSource(); - mgr.createConnectionPool((UniversalConnectionPoolAdapter) pds); - Thread.sleep(1000); - mgr.startConnectionPool(POOL_NAME); - Thread.sleep(1000); - - // Phase 2: Connection stress - System.out.println("\n=== [PHASE 2] CONNECTION STRESS TESTING ==="); - for (int cycle = 1; cycle <= STRESS_CYCLES; cycle++) { - System.out.println("--- Stress Cycle " + cycle + "/" + STRESS_CYCLES + " ---"); - executeConnectionWaves(mgr, POOL_NAME); - Thread.sleep(2000); - } - - // Phase 3: Maintenance operations - System.out.println("\n=== [PHASE 3] MAINTENANCE OPERATIONS ==="); - executeMaintenanceOperations(mgr, POOL_NAME); - - // Phase 4: Pool restart - System.out.println("\n=== [PHASE 4] POOL RESTART EVENTS ==="); - mgr.stopConnectionPool(POOL_NAME); - Thread.sleep(1000); - - // Phase 5: Final burst - System.out.println("\n=== [PHASE 5] FINAL CONNECTION BURST ==="); - executeFinalBurst(mgr, POOL_NAME); - - // Phase 6: Cleanup - System.out.println("\n=== [PHASE 6] POOL DESTRUCTION ==="); - mgr.stopConnectionPool(POOL_NAME); - Thread.sleep(1000); - mgr.destroyConnectionPool(POOL_NAME); - - System.out.println("\n=== OpenTelemetry UCP Stress Test Completed ==="); - printExpectedMetrics(); - } - - /** - * Configures OpenTelemetry-specific settings. - */ - private static void setupPoolWithOpenTelemetry() { - System.setProperty("oracle.ucp.wls.jta", "false"); - System.setProperty("UCPEventListenerProvider", "opentelemetry-ucp-listener"); - System.out.println("OpenTelemetry provider configured"); - } - - /** - * Creates and configures pool data source for OpenTelemetry testing. - * - * @return configured pool data source - * @throws Exception if configuration fails - */ - private static PoolDataSource createPoolDataSource() throws Exception { - PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); - pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); - pds.setURL(DB_URL); - pds.setUser(DB_USER); - pds.setPassword(DB_PASSWORD); - pds.setConnectionPoolName(POOL_NAME); - pds.setMinPoolSize(2); - pds.setMaxPoolSize(30); - pds.setInitialPoolSize(3); - pds.setConnectionWaitTimeout(1); - - System.out.println("Pool configured: Min=2, Max=30, Initial=3"); - return pds; - } - - /** - * Executes connection waves to generate borrowing and returning events. - * - * @param mgr connection pool manager - * @param poolName name of the pool - * @throws Exception if wave execution fails - */ - private static void executeConnectionWaves(UniversalConnectionPoolManager mgr, - String poolName) throws Exception { - UniversalConnectionPool pool = mgr.getConnectionPool(poolName); - - for (int wave = 1; wave <= WAVE_COUNT; wave++) { - System.out.println(" Wave " + wave + "/" + WAVE_COUNT + - ": Borrowing " + CONNECTIONS_PER_WAVE + " connections"); - - // Borrow phase - UniversalPooledConnection[] connections = new UniversalPooledConnection[CONNECTIONS_PER_WAVE]; - for (int i = 0; i < CONNECTIONS_PER_WAVE; i++) { - try { - connections[i] = pool.borrowConnection(pool.getConnectionRetrievalInfo()); - Thread.sleep(50); - } catch (Exception e) { - System.out.println(" Connection " + (i + 1) + " failed: " + e.getMessage()); - connections[i] = null; - } - - if ((i + 1) % 4 == 0) { - printCurrentMetrics(pool, " Borrowed " + (i + 1)); - } - } - - Thread.sleep(800); - - // Release phase - for (int i = 0; i < CONNECTIONS_PER_WAVE; i++) { - if (connections[i] != null) { - if (wave % 3 == 0) { - pool.closeConnection(connections[i]); - } else { - pool.returnConnection(connections[i]); - } - Thread.sleep(30); - } - } - - printCurrentMetrics(pool, " Wave " + wave + " completed"); - Thread.sleep(500); - } - } - - /** - * Executes maintenance operations to generate maintenance events. - * - * @param mgr connection pool manager - * @param poolName name of the pool - * @throws Exception if maintenance fails - */ - private static void executeMaintenanceOperations(UniversalConnectionPoolManager mgr, - String poolName) throws Exception { - UniversalConnectionPool pool = mgr.getConnectionPool(poolName); - - // Borrow test connections - UniversalPooledConnection[] testConnections = new UniversalPooledConnection[5]; - for (int i = 0; i < 5; i++) { - try { - testConnections[i] = pool.borrowConnection(pool.getConnectionRetrievalInfo()); - System.out.println(" Borrowed test connection " + (i + 1) + "/5"); - } catch (Exception e) { - System.out.println(" Could not borrow test connection " + (i + 1)); - testConnections[i] = null; - break; - } - } - - printCurrentMetrics(pool, "Pre-maintenance baseline"); - - // Execute maintenance operations - System.out.println(" Executing PURGE operation"); - pool.purge(); - Thread.sleep(1000); - printCurrentMetrics(pool, "Post-purge"); - - System.out.println(" Executing RECYCLE operation"); - pool.recycle(); - Thread.sleep(1000); - printCurrentMetrics(pool, "Post-recycle"); - - System.out.println(" Executing REFRESH operation"); - pool.refresh(); - Thread.sleep(1000); - printCurrentMetrics(pool, "Post-refresh"); - - // Return test connections - for (UniversalPooledConnection conn : testConnections) { - if (conn != null) { - try { - pool.returnConnection(conn); - } catch (Exception e) { - System.out.println(" Could not return connection: " + e.getMessage()); - } - } - } - } - - /** - * Executes final connection burst to stress test pool limits. - * - * @param mgr connection pool manager - * @param poolName name of the pool - * @throws Exception if burst execution fails - */ - private static void executeFinalBurst(UniversalConnectionPoolManager mgr, - String poolName) throws Exception { - UniversalConnectionPool pool = mgr.getConnectionPool(poolName); - System.out.println(" Executing final connection burst"); - - UniversalPooledConnection[] burstConnections = new UniversalPooledConnection[30]; - - for (int i = 0; i < 30; i++) { - try { - burstConnections[i] = pool.borrowConnection(pool.getConnectionRetrievalInfo()); - if (i % 5 == 0) { - printCurrentMetrics(pool, " Burst progress " + (i + 1) + "/30"); - } - } catch (Exception e) { - System.out.println(" Burst connection " + (i + 1) + " failed (expected)"); - burstConnections[i] = null; - } - } - - Thread.sleep(1000); - - // Rapid release - for (int i = 0; i < 30; i++) { - if (burstConnections[i] != null) { - pool.returnConnection(burstConnections[i]); - } - } - - printCurrentMetrics(pool, "Final burst completed"); - } - - /** - * Prints current pool metrics for correlation with OpenTelemetry data. - * - * @param pool connection pool to analyze - * @param phase description of current phase - */ - private static void printCurrentMetrics(UniversalConnectionPool pool, String phase) { - try { - System.out.printf(" [%s] Borrowed: %d, Available: %d, Total: %d, Created: %d, Closed: %d%n", - phase, - pool.getStatistics().getBorrowedConnectionsCount(), - pool.getStatistics().getAvailableConnectionsCount(), - pool.getStatistics().getTotalConnectionsCount(), - pool.getStatistics().getConnectionsCreatedCount(), - pool.getStatistics().getConnectionsClosedCount()); - } catch (Exception e) { - System.out.println(" [" + phase + "] Metrics temporarily unavailable"); - } - } - - /** - * Prints expected metrics information for validation. - */ - private static void printExpectedMetrics() { - System.out.println("Expected metrics at http://localhost:8080/metrics:"); - System.out.println("- Event Counters: ucp_pool_created_total, ucp_connection_borrowed_total, etc."); - System.out.println("- State Gauges: ucp_borrowed_connections, ucp_available_connections, etc."); - System.out.println("- Performance: ucp_connection_wait_time_ms histogram"); - System.out.println("- Labels: pool_name=\"" + POOL_NAME + "\""); - } -} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java new file mode 100644 index 00000000..7c3c9427 --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java @@ -0,0 +1,221 @@ +package oracle.ucp.provider.observability; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; +import oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; + +import static oracle.ucp.events.core.UCPEventListener.EventType; +import static org.junit.Assert.*; + +public class OtelUCPTest { + + private InMemoryMetricReader metricReader; + private OtelUCPEventListenerProvider provider; + private UCPEventListener listener; + + @Before + public void setup() { + GlobalOpenTelemetry.resetForTest(); + + metricReader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = SdkMeterProvider.builder() + .registerMetricReader(metricReader) + .build(); + + OpenTelemetrySdk.builder() + .setMeterProvider(meterProvider) + .buildAndRegisterGlobal(); + + provider = new OtelUCPEventListenerProvider(); + listener = provider.getListener(null); + } + + @After + public void cleanup() { + GlobalOpenTelemetry.resetForTest(); + } + + @Test + public void testProviderName() { + assertEquals("otel-ucp-listener", provider.getName()); + } + + @Test + public void testProviderReturnsListener() { + assertNotNull("Provider should return a listener", listener); + } + + @Test + public void testProviderReturnsSameListenerInstance() { + UCPEventListener listener1 = provider.getListener(null); + UCPEventListener listener2 = provider.getListener(new HashMap<>()); + assertSame("Provider should return same listener instance", listener1, + listener2); + } + + @Test + public void testListenerAcceptsEvents() { + UCPEventContext ctx = createTestContext("pool1", 1, 1, 10, 2); + + listener.onUCPEvent(EventType.POOL_CREATED, ctx); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + listener.onUCPEvent(EventType.CONNECTION_RETURNED, ctx); + } + + @Test + public void testAllEventTypesAccepted() { + EventType[] allEvents = { + EventType.POOL_CREATED, EventType.POOL_STARTING, + EventType.POOL_STARTED, EventType.POOL_STOPPED, + EventType.POOL_RESTARTING, EventType.POOL_RESTARTED, + EventType.POOL_DESTROYED, EventType.CONNECTION_CREATED, + EventType.CONNECTION_BORROWED, EventType.CONNECTION_RETURNED, + EventType.CONNECTION_CLOSED, EventType.POOL_REFRESHED, + EventType.POOL_RECYCLED, EventType.POOL_PURGED + }; + + UCPEventContext ctx = createTestContext("test-pool", 1, 1, 10, 2); + + for (EventType event : allEvents) { + listener.onUCPEvent(event, ctx); + } + } + + @Test + public void testNullContextIgnored() { + listener.onUCPEvent(EventType.CONNECTION_BORROWED, null); + } + + @Test + public void testNullEventTypeIgnored() { + UCPEventContext ctx = createTestContext("pool1", 1, 1, 10, 2); + listener.onUCPEvent(null, ctx); + } + + @Test + public void testEmptyPoolNameAccepted() { + UCPEventContext ctx = createTestContext("", 1, 1, 10, 2); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + } + + @Test + public void testVeryLongPoolNameAccepted() { + String longName = new String(new char[1000]).replace('\0', 'a'); + UCPEventContext ctx = createTestContext(longName, 1, 1, 10, 2); + listener.onUCPEvent(EventType.POOL_CREATED, ctx); + } + + @Test + public void testZeroValuesAccepted() { + UCPEventContext ctx = createTestContext("pool1", 0, 0, 0, 0); + listener.onUCPEvent(EventType.POOL_CREATED, ctx); + } + + @Test + public void testLargeValuesAccepted() { + UCPEventContext ctx = createTestContext("pool1", + Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 0); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + } + + @Test + public void testMultiplePoolsAccepted() { + listener.onUCPEvent(EventType.POOL_CREATED, + createTestContext("pool1", 1, 1, 10, 2)); + listener.onUCPEvent(EventType.POOL_CREATED, + createTestContext("pool2", 2, 2, 20, 4)); + listener.onUCPEvent(EventType.POOL_CREATED, + createTestContext("pool3", 3, 3, 30, 6)); + } + + @Test + public void testConcurrentAccess() throws InterruptedException { + Thread[] threads = new Thread[5]; + for (int i = 0; i < 5; i++) { + final int threadId = i; + threads[i] = new Thread(() -> { + for (int j = 0; j < 10; j++) { + UCPEventContext ctx = createTestContext("pool" + threadId, j, j, + 10, 2); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + } + }); + threads[i].start(); + } + + for (Thread thread : threads) { + thread.join(); + } + } + + private UCPEventContext createTestContext(String poolName, int borrowed, + int available, int max, int min) { + return new UCPEventContext() { + @Override + public String poolName() { + return poolName; + } + + @Override + public long timestamp() { + return System.currentTimeMillis(); + } + + @Override + public int borrowedConnectionsCount() { + return borrowed; + } + + @Override + public int availableConnectionsCount() { + return available; + } + + @Override + public int totalConnections() { + return borrowed + available; + } + + @Override + public int maxPoolSize() { + return max; + } + + @Override + public int minPoolSize() { + return min; + } + + @Override + public long getAverageConnectionWaitTime() { + return 0; + } + + @Override + public int createdConnections() { + return borrowed + available; + } + + @Override + public int closedConnections() { + return 0; + } + + @Override + public String formattedTimestamp() { + return new java.text.SimpleDateFormat( + "MMMM dd, yyyy HH:mm:ss.SSS z") + .format(new java.util.Date(timestamp())); + } + }; + } +} \ No newline at end of file