From 6db2af28b30124b79e9316e89cde0931b6a13d9d Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Thu, 23 Jan 2025 11:41:28 +0100 Subject: [PATCH 01/82] First commit --- ojdbc-provider-observability/pom.xml | 44 +++++ .../ObservabilityTraceEventListener.java | 67 +++++++ ...servabilityTraceEventListenerProvider.java | 71 ++++++++ .../ObservabilityConfiguration.java | 67 +++++++ .../ObservabilityConfigurationMBean.java | 12 ++ .../observability/tracers/JFRTracer.java | 89 ++++++++++ .../observability/tracers/OTelTracer.java | 167 ++++++++++++++++++ .../tracers/ObservabilityTracer.java | 13 ++ pom.xml | 15 ++ 9 files changed, 545 insertions(+) create mode 100644 ojdbc-provider-observability/pom.xml create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml new file mode 100644 index 00000000..2bec1e23 --- /dev/null +++ b/ojdbc-provider-observability/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + com.oracle.database.jdbc + ojdbc-extensions + 1.0.2 + + + com.oracle.database.jdbc + ojdbc-provider-observability + 1.0.2 + + + 1.44.1 + 11 + 11 + + + + + com.oracle.database.jdbc + ojdbc11 + + + io.opentelemetry + opentelemetry-api + ${opentelemetry.version} + + + org.mockito + mockito-core + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java new file mode 100644 index 00000000..d68c7a8a --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -0,0 +1,67 @@ +package oracle.jdbc.provider.observability; + +import java.util.EnumMap; + +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.tracers.JFRTracer; +import oracle.jdbc.provider.observability.tracers.OTelTracer; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; + +public class ObservabilityTraceEventListener implements TraceEventListener { + + + public enum Tracers { + OTEL(new OTelTracer()), + JFR(new JFRTracer()); + + private ObservabilityTracer tracer; + + Tracers(ObservabilityTracer tracer) { + this.tracer = tracer; + } + + public ObservabilityTracer getTracer() { + return tracer; + } + } + + @Override + public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { + EnumMap currentUserContext = getCurrentUserContext(userContext); + for (Tracers tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { + Object newUserContext = tracer.getTracer().traceRoudtrip(sequence, traceContext, currentUserContext.get(tracer)); + currentUserContext.put(tracer, newUserContext); + } + return currentUserContext; + } + + + @Override + public Object onExecutionEventReceived(JdbcExecutionEvent event, Object userContext, Object... params) { + EnumMap currentUserContext = getCurrentUserContext(userContext); + for (Tracers tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { + Object newUserContext = tracer.getTracer().traceExecutionEvent(event, currentUserContext.get(tracer), params); + currentUserContext.put(tracer, newUserContext); + } + return currentUserContext; + } + + @Override + public boolean isDesiredEvent(JdbcExecutionEvent event) { + // Accept all events + return true; + } + + @SuppressWarnings("unchecked") + private EnumMap getCurrentUserContext(Object userContext) { + EnumMap currentUserContext; + if (userContext != null && (userContext instanceof EnumMap)) { + currentUserContext = (EnumMap) userContext; + } else { + currentUserContext = new EnumMap(Tracers.class); + } + return currentUserContext; + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java new file mode 100644 index 00000000..93987b8e --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -0,0 +1,71 @@ +package oracle.jdbc.provider.observability; + +import java.lang.management.ManagementFactory; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanException; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.ReflectionException; + +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.spi.TraceEventListenerProvider; + +public class ObservabilityTraceEventListenerProvider implements TraceEventListenerProvider { + + private static final String PROVIDER_NAME = "observability-trace-event-listener-provider"; + private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.extension.opentelemetry:type=ObservabilityTraceEventListener"; + + private static final MBeanServer server = ManagementFactory.getPlatformMBeanServer();; + private static ObjectName objectName; + + Logger logger = Logger.getLogger(ObservabilityTraceEventListenerProvider.class.getName()); + + static { + try { + objectName = new ObjectName(MBEAN_OBJECT_NAME); + } catch (MalformedObjectNameException e) { + objectName = null; + } + } + + @Override + public TraceEventListener getTraceEventListener(Map map) { + ObservabilityTraceEventListener observabilityBean; + try { + if (objectName != null && server.isRegistered(objectName)) { + observabilityBean = (ObservabilityTraceEventListener) server + .instantiate(ObservabilityTraceEventListener.class.getName()); + return observabilityBean; + } + } catch (ReflectionException | MBeanException e) { + logger.log(Level.WARNING, "Could not retrieve MBean from server", e); + } + observabilityBean = new ObservabilityTraceEventListener(); + try { + server.registerMBean(observabilityBean, objectName); + } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { + logger.log(Level.WARNING, "Could not register MBean", e); + } + return observabilityBean; + } + + @Override + public String getName() { + return PROVIDER_NAME; + } + + @Override + public Collection getParameters() { + return Collections.emptyList(); + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java new file mode 100644 index 00000000..8d5be1f4 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -0,0 +1,67 @@ +package oracle.jdbc.provider.observability.configuration; + +import java.util.EnumSet; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; + +public class ObservabilityConfiguration implements ObservabilityConfigurationMBean { + + private final ReentrantLock observabilityConfiguraitonLock = new ReentrantLock(); + + private static final ObservabilityConfiguration INSTANCE = new ObservabilityConfiguration(); + + private ObservabilityConfiguration() { } + + private boolean sensitiveDataEnabled; + private String tracers; + + private EnumSet enabledTracers; + + @Override + public String getEnabledTracers() { + try { + observabilityConfiguraitonLock.lock(); + return tracers; + } finally { + observabilityConfiguraitonLock.unlock(); + } + } + + @Override + public void setEnabledTracers(String tracers) { + try { + enabledTracers.clear(); + String[] items = tracers.split(","); + for (String item : items) { + if (item != null) { + enabledTracers.add(ObservabilityTraceEventListener.Tracers.valueOf(item.toUpperCase())); + } + } + this.tracers = enabledTracers.stream().map((item) -> item.toString()).collect(Collectors.joining(",")); + } finally { + observabilityConfiguraitonLock.unlock(); + } + + } + + @Override + public boolean getSensitiveDataEnabled() { + return sensitiveDataEnabled; + } + + @Override + public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { + this.sensitiveDataEnabled = sensitiveDataEnabled; + } + + public static ObservabilityConfiguration getInstance() { + return INSTANCE; + } + + public EnumSet getEnabledTracersSet() { + return enabledTracers.clone(); + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java new file mode 100644 index 00000000..3aedf3fc --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java @@ -0,0 +1,12 @@ +package oracle.jdbc.provider.observability.configuration; + +public interface ObservabilityConfigurationMBean { + + public String getEnabledTracers(); + + public void setEnabledTracers(String tracers); + + public boolean getSensitiveDataEnabled(); + + public void setSensitiveDataEnabled(boolean sensitiveDataEnabled); +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java new file mode 100644 index 00000000..4e9736a0 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java @@ -0,0 +1,89 @@ +package oracle.jdbc.provider.observability.tracers; + +import java.util.ArrayList; +import java.util.List; + +import oracle.jdbc.DatabaseFunction; +import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; +import oracle.jdbc.TraceEventListener.Sequence; +import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import jdk.jfr.AnnotationElement; +import jdk.jfr.Event; +import jdk.jfr.EventFactory; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.Category; +import jdk.jfr.ValueDescriptor; + +public class JFRTracer implements ObservabilityTracer{ + + public JFRTracer() {} + + @Override + public Object traceRoudtrip(Sequence sequence, TraceContext traceContext, Object userContext) { + if (sequence.equals(Sequence.BEFORE)) { + Event event = createEvent( + traceContext.databaseFunction()); + event.begin(); + return event; + } else { + if (userContext != null) { + Event event = (Event) userContext; + event.set(0, traceContext.getConnectionId()); + event.set(1, traceContext.databaseOperation()); + event.set(2, traceContext.tenant()); + event.set(3, traceContext.getSqlId()); + if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { + event.set(4, traceContext.originalSqlText()); + event.set(5, traceContext.actualSqlText()); + event.set(6, traceContext.user()); + } + event.end(); + event.commit(); + } + } + return null; + } + + @Override + public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { + return null; + } + + /** + * Creates an event for a given database operation. + * @param databaseFunction The database function originating the round trip. + * @return returns a Java Flight Recorder Event containing the following + * fields: + *
    + *
  • ConnectionID
  • + *
  • DatabaseOperation
  • + *
  • OriginalSqlText
  • + *
  • ActualSqlText
  • + *
  • User
  • + *
+ */ + private static Event createEvent(DatabaseFunction databaseFunction) { + String eventName = "oracle.jdbc.provider.jfr.roundtrip." + databaseFunction.toString(); + List eventAnnotations = new ArrayList(); + eventAnnotations + .add(new AnnotationElement(Name.class, eventName)); + eventAnnotations.add(new AnnotationElement(Label.class, databaseFunction.getDescription())); + eventAnnotations.add(new AnnotationElement(Category.class, new String[] { "Oracle JDBC", "Round trips" })); + + List fields = new ArrayList(); + fields.add(new ValueDescriptor(String.class, "Connection_ID")); + fields.add(new ValueDescriptor(String.class, "Database_operation")); + fields.add(new ValueDescriptor(String.class, "Database_tenant")); + fields.add(new ValueDescriptor(String.class, "SQL_ID")); + fields.add(new ValueDescriptor(String.class, "Original_SQL_text")); + fields.add(new ValueDescriptor(String.class, "Actual_SQL_text")); + fields.add(new ValueDescriptor(String.class, "Database_user")); + + EventFactory f = EventFactory.create(eventAnnotations, fields); + return f.newEvent(); + } + + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java new file mode 100644 index 00000000..5aba96c8 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java @@ -0,0 +1,167 @@ +package oracle.jdbc.provider.observability.tracers; + +import java.sql.SQLException; +import java.time.Instant; +import java.util.EnumMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.security.auth.login.Configuration; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; +import oracle.jdbc.TraceEventListener.Sequence; +import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; + +public class OTelTracer implements ObservabilityTracer { + + private static final String TRACE_KEY = "clientcontext.ora$opentelem$tracectx"; + + private Tracer tracer; + + // Number of parameters expected for each execution event + private static final Map EXECUTION_EVENTS_PARAMETERS = new EnumMap( + JdbcExecutionEvent.class) { + { + put(JdbcExecutionEvent.AC_REPLAY_STARTED, 3); + put(JdbcExecutionEvent.AC_REPLAY_SUCCESSFUL, 3); + put(JdbcExecutionEvent.VIP_RETRY, 8); + } + }; + + private static Logger logger = Logger.getLogger(OTelTracer.class.getName()); + + @Override + public Object traceRoudtrip(Sequence sequence, TraceContext traceContext, Object userContext) { +if (sequence == Sequence.BEFORE) { + // Create the Span before the round-trip. + final Span span = initAndGetSpan(traceContext, traceContext.databaseOperation()); + try (Scope ignored = span.makeCurrent()) { + traceContext.setClientInfo(TRACE_KEY, getTraceValue(span)); + } catch (Exception ex) { + logger.log(Level.WARNING, ex.getMessage(), ex); + } + // Return the Span instance to the driver. The driver holds this instance and + // supplies it + // as user context parameter on the next round-trip call. + return span; + } else { + // End the Span after the round-trip. + if (userContext instanceof Span) { + final Span span = (Span) userContext; + span.setStatus(traceContext.isCompletedExceptionally() ? StatusCode.ERROR : StatusCode.OK); + endSpan(span); + } + return null; + } + } + + @Override + public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { +if (EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + if (event == TraceEventListener.JdbcExecutionEvent.VIP_RETRY) { + SpanBuilder spanBuilder = tracer + .spanBuilder(event.getDescription()) + .setAttribute("Error message", params[0].toString()) + .setAttribute("VIP Address", params[7].toString()); + // Add sensitive information (URL and SQL) if it is enabled + if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { + logger.log(Level.FINEST, "Sensitive information on"); + spanBuilder.setAttribute("Protocol", params[1].toString()) + .setAttribute("Host", params[2].toString()) + .setAttribute("Port", params[3].toString()) + .setAttribute("Service name", params[4].toString()) + .setAttribute("SID", params[5].toString()) + .setAttribute("Connection data", params[6].toString()); + } + return spanBuilder.startSpan(); + } else if (event == TraceEventListener.JdbcExecutionEvent.AC_REPLAY_STARTED + || event == TraceEventListener.JdbcExecutionEvent.AC_REPLAY_SUCCESSFUL) { + SpanBuilder spanBuilder = tracer + .spanBuilder(event.getDescription()) + .setAttribute("Error Message", params[0].toString()) + .setAttribute("Error code", ((SQLException) params[1]).getErrorCode()) + .setAttribute("SQL state", ((SQLException) params[1]).getSQLState()) + .setAttribute("Current replay retry count", params[2].toString()); + return spanBuilder.startSpan(); + } else { + logger.log(Level.WARNING, "Unknown event received : " + event.toString()); + } + } else { + // log wrong number of parameters returned for execution event + logger.log(Level.WARNING, "Wrong number of parameters received for event " + event.toString()); + } + return null; + } + + private Span initAndGetSpan(TraceContext traceContext, String spanName) { + /* + * If this is in the context of current span, the following becomes a nested or + * child span to the current span. I.e. the current span in context becomes + * parent to this child span. + */ + SpanBuilder spanBuilder = tracer + .spanBuilder(spanName) + .setAttribute("thread.id", Thread.currentThread().getId()) + .setAttribute("thread.name", Thread.currentThread().getName()) + .setAttribute("Connection ID", traceContext.getConnectionId()) + .setAttribute("Database Operation", traceContext.databaseOperation()) + .setAttribute("Database User", traceContext.user()) + .setAttribute("Database Tenant", traceContext.tenant()) + .setAttribute("SQL ID", traceContext.getSqlId()); + + // Add sensitive information (URL and SQL) if it is enabled + if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { + logger.log(Level.FINEST, "Sensitive information on"); + spanBuilder.setAttribute("Original SQL Text", traceContext.originalSqlText()) + .setAttribute("Actual SQL Text", traceContext.actualSqlText()); + } + + // Indicates that the span covers server-side handling of an RPC or other remote + // request. + return spanBuilder.setSpanKind(SpanKind.SERVER).startSpan(); + + } + + private void endSpan(Span span) { + span.end(Instant.now()); + } + + private String getTraceValue(Span span) { + final String traceParent = initAndGetTraceParent(span); + final String traceState = initAndGetTraceState(span); + return traceParent + traceState; + } + + private String initAndGetTraceParent(Span span) { + final SpanContext spanContext = span.getSpanContext(); + // The current specification assumes the version is set to 00. + final String version = "00"; + final String traceId = spanContext.getTraceId(); + // parent-id is known as the span-id + final String parentId = spanContext.getSpanId(); + final String traceFlags = spanContext.getTraceFlags().toString(); + + return String.format("traceparent: %s-%s-%s-%s\r\n", + version, traceId, parentId, traceFlags); + } + + private String initAndGetTraceState(Span span) { + final TraceState traceState = span.getSpanContext().getTraceState(); + final StringBuilder stringBuilder = new StringBuilder(); + + traceState.forEach((k, v) -> stringBuilder.append(k).append("=").append(v)); + return String.format("tracestate: %s\r\n", stringBuilder); + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java new file mode 100644 index 00000000..54e07c72 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java @@ -0,0 +1,13 @@ +package oracle.jdbc.provider.observability.tracers; + +import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; +import oracle.jdbc.TraceEventListener.Sequence; +import oracle.jdbc.TraceEventListener.TraceContext; + +public interface ObservabilityTracer { + + Object traceRoudtrip(Sequence sequence, TraceContext traceContext, Object userContext); + + Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params); + +} diff --git a/pom.xml b/pom.xml index 7a26317b..f51eda68 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,15 @@ 8 + + javac-release-jfr + + [9,) + + + 11 + + @@ -62,6 +71,7 @@ ojdbc-provider-samples ojdbc-provider-opentelemetry ojdbc-provider-jackson-oson + ojdbc-provider-observability @@ -76,6 +86,11 @@ ojdbc8 ${jdbc.version} + + com.oracle.database.jdbc + ojdbc11 + ${jdbc.version} + com.oracle.database.security oraclepki From cf6b82af399f660de8d756117fefe85a43caad22 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 3 Feb 2025 13:47:01 +0100 Subject: [PATCH 02/82] Added static Events and tests --- .github/workflows/run-tests.yaml | 7 + .../example-test.properties | 86 ++ ojdbc-provider-observability/pom.xml | 12 + .../ObservabilityTraceEventListener.java | 2 +- ...servabilityTraceEventListenerProvider.java | 19 +- ...enTelemetryTraceEventListenerProvider.java | 20 + .../ObservabilityConfiguration.java | 19 +- .../tracers/JFREventFactory.java | 866 ++++++++++++++++++ .../observability/tracers/JFRTracer.java | 34 +- .../observability/tracers/OTelTracer.java | 10 +- .../tracers/ObservabilityTracer.java | 2 +- ...oracle.jdbc.spi.TraceEventListenerProvider | 2 + .../ObservabilityTestProperties.java | 7 + .../ObservabilityTraceEventListenerTest.java | 189 ++++ 14 files changed, 1228 insertions(+), 47 deletions(-) create mode 100644 ojdbc-provider-observability/example-test.properties create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java create mode 100644 ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider create mode 100644 ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java create mode 100644 ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 2150346b..ffaaedf7 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -119,6 +119,12 @@ jobs: JACKSON_OSON_PASSWORD=${{ secrets.TEST_JACKSON_OSON_PASSWORD }}\n " >> ojdbc-provider-jackson-oson/test.properties + # Generate ojdbc-provider-observability/test.properties + echo -e "OBSERVABILITY_URL=${{ secrets.TEST_JACKSON_OSON_URL }}\n + OBSERVABILITY_USERNAME=${{ secrets.TEST_JACKSON_OSON_USERNAME }}\n + OBSERVABILITY_PASSWORD=${{ secrets.TEST_JACKSON_OSON_PASSWORD }}\n + " >> ojdbc-provider-observability/test.properties + - name: Run tests with Maven run: mvn -B test --file pom.xml @@ -133,3 +139,4 @@ jobs: rm ojdbc-provider-azure/test.properties rm ojdbc-provider-jackson-oson/test.properties + rm ojdbc-provider-observability/test.properties diff --git a/ojdbc-provider-observability/example-test.properties b/ojdbc-provider-observability/example-test.properties new file mode 100644 index 00000000..feafff3b --- /dev/null +++ b/ojdbc-provider-observability/example-test.properties @@ -0,0 +1,86 @@ +################################################################################ +# Copyright (c) 2024 Oracle and/or its affiliates. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or data +# (collectively the "Software"), free of charge and under any and all copyright +# rights in the Software, and any and all patent rights owned or freely +# licensable by each licensor hereunder covering either (i) the unmodified +# Software as contributed to or provided by such licensor, or (ii) the Larger +# Works (as defined below), to deal in both +# +# (a) the Software, and +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software (each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# The above copyright notice and either this complete permission notice or at +# a minimum a reference to the UPL must be included in all copies or +# substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +################################################################################ + +# This file provides examples of properties that configure tests in this +# module. +# +# QUICK GUIDE TO RUNNING TESTS: +# 1. Create a copy of this file named "test.properties": +# cp example-test.properties test.properties +# 2. In test.properties, replace example values with real values (the file is +# .gitignore'd, so sensitive info won't be checked in to the repo) +# 3. Comment out any lines for which a value can not be provided (tests are +# skipped if no value is configured). +# 4. mvn clean verify +# +# CONFIGURING TEST PROPERTIES +# Test properties are read from a properties file by the TestProperties class. +# The TestProperties class can be found in: +# ojdbc-provider-common/src/testFixtures/java/oracle/jdbc/provider/TestProperties.java +# The default behavior of TestProperties is to read a file named +# "test.properties" in the current directory. A non-default location may be +# specified as a JVM system property: +# mvn clean verify -Doracle.jdbc.provider.TestProperties=/path/to/my-test.properties +# +# MAINTAINING THIS FILE +# Project maintainers should add an example to this file anytime they write a +# test which requires a new property. Not doing so will inflict pain and +# suffering upon our fellow programmers, and will also lead to increased +# maintenance costs. +# +# IGNORING UNCONFIGURED PROPERTIES +# No test should cause a build failure due to an unconfigured property. +# Using JUnit terminology: A test should "abort" rather than "fail" when a +# property is not configured. This means that the test does not pass, but it +# does not cause the build to fail either. +# Methods of the TestProperties class will automatically abort a test if a +# property is not configured. The org.junit.jupiter.api.Assumptions class may +# also be used directly to abort a test. +# There is NO environment in which ALL tests can be run. Some tests may +# require authentication as a managed identity in an Azure VM, while other +# tests require authentication as an instance principal in an OCI compute +# instance; These environments are mutually exclusive. This is one reason why +# tests can not fail the build if a required property is not set. +# A more practical reason is that developers may not need to run all tests if +# their changes are isolated to single module. For instance, a developer +# working on an OCI provider should not need to set up an Azure tenancy to test +# their changes. + +OBSERVABILITY_URL= +OBSERVABILITY_USERNAME= +OBSERVABILITY_PASSWORD= diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 2bec1e23..4b90faad 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -39,6 +39,18 @@ junit-jupiter-engine test + + org.junit.jupiter + junit-jupiter-params + test + + + ojdbc-provider-common + com.oracle.database.jdbc + 1.0.2 + tests + test-jar + \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java index d68c7a8a..b371c4b0 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -30,7 +30,7 @@ public ObservabilityTracer getTracer() { public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { EnumMap currentUserContext = getCurrentUserContext(userContext); for (Tracers tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { - Object newUserContext = tracer.getTracer().traceRoudtrip(sequence, traceContext, currentUserContext.get(tracer)); + Object newUserContext = tracer.getTracer().traceRoundtrip(sequence, traceContext, currentUserContext.get(tracer)); currentUserContext.put(tracer, newUserContext); } return currentUserContext; diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java index 93987b8e..8ea1163f 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -17,12 +17,13 @@ import javax.management.ReflectionException; import oracle.jdbc.TraceEventListener; +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; import oracle.jdbc.spi.TraceEventListenerProvider; public class ObservabilityTraceEventListenerProvider implements TraceEventListenerProvider { private static final String PROVIDER_NAME = "observability-trace-event-listener-provider"; - private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.extension.opentelemetry:type=ObservabilityTraceEventListener"; + private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.extension.observability:type=ObservabilityConfiguration"; private static final MBeanServer server = ManagementFactory.getPlatformMBeanServer();; private static ObjectName objectName; @@ -39,23 +40,13 @@ public class ObservabilityTraceEventListenerProvider implements TraceEventListen @Override public TraceEventListener getTraceEventListener(Map map) { - ObservabilityTraceEventListener observabilityBean; try { - if (objectName != null && server.isRegistered(objectName)) { - observabilityBean = (ObservabilityTraceEventListener) server - .instantiate(ObservabilityTraceEventListener.class.getName()); - return observabilityBean; - } - } catch (ReflectionException | MBeanException e) { - logger.log(Level.WARNING, "Could not retrieve MBean from server", e); - } - observabilityBean = new ObservabilityTraceEventListener(); - try { - server.registerMBean(observabilityBean, objectName); + if (!server.isRegistered(objectName)) + server.registerMBean(ObservabilityConfiguration.getInstance(), objectName); } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { logger.log(Level.WARNING, "Could not register MBean", e); } - return observabilityBean; + return new ObservabilityTraceEventListener(); } @Override diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java new file mode 100644 index 00000000..649ef25d --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java @@ -0,0 +1,20 @@ +package oracle.jdbc.provider.observability; + +import java.util.Collection; +import java.util.Collections; + +public class OpenTelemetryTraceEventListenerProvider extends ObservabilityTraceEventListenerProvider { + + private static final String PROVIDER_NAME = "open-telemetry-trace-event-listener-provider"; + + @Override + public String getName() { + return PROVIDER_NAME; + } + + @Override + public Collection getParameters() { + return Collections.emptyList(); + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java index 8d5be1f4..e0404fe2 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -8,16 +8,22 @@ public class ObservabilityConfiguration implements ObservabilityConfigurationMBean { - private final ReentrantLock observabilityConfiguraitonLock = new ReentrantLock(); + private static final ReentrantLock observabilityConfiguraitonLock = new ReentrantLock(); - private static final ObservabilityConfiguration INSTANCE = new ObservabilityConfiguration(); + private static final ObservabilityConfiguration INSTANCE; + + static { + INSTANCE = new ObservabilityConfiguration(); + //INSTANCE.setSensitiveDataEnabled(true); + INSTANCE.setEnabledTracers(System.getProperty("oracle.jdbc.provider.observability.tracer", "OTEL,JFR")); + } private ObservabilityConfiguration() { } private boolean sensitiveDataEnabled; private String tracers; - private EnumSet enabledTracers; + private EnumSet enabledTracers = EnumSet.noneOf(ObservabilityTraceEventListener.Tracers.class); @Override public String getEnabledTracers() { @@ -32,6 +38,7 @@ public String getEnabledTracers() { @Override public void setEnabledTracers(String tracers) { try { + observabilityConfiguraitonLock.lock(); enabledTracers.clear(); String[] items = tracers.split(","); for (String item : items) { @@ -61,7 +68,11 @@ public static ObservabilityConfiguration getInstance() { } public EnumSet getEnabledTracersSet() { - return enabledTracers.clone(); + if (enabledTracers != null) { + return enabledTracers.clone(); + } else { + return null; + } } } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java new file mode 100644 index 00000000..3c2f0551 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java @@ -0,0 +1,866 @@ +package oracle.jdbc.provider.observability.tracers; + +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.Category; +import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; + +public class JFREventFactory { + + public static RoundTripEvent createJFREvent(TraceContext traceContext) { + switch (traceContext.databaseFunction()) { + case ADVANCED_QUEUING_12C_EMON_DEQUEUE: + return new AdvancedQueuing12cEminDequeueEvent(traceContext); + case ADVANCED_QUEUING_ARRAY_ENQUEUE_DEQUEUE: + return new AdvancedQueuingArrayEnqueueDequeue(traceContext); + case ADVANCED_QUEUING_DEQUEUE_V8: + return new AdvancedQueuingDequeueV8(traceContext); + case ADVANCED_QUEUING_ENQUEUE: + return new AdvancedQueuingEnqueue(traceContext); + case ADVANCED_QUEUING_GET_PROPAGATION_STATUS: + return new AdvancedQueuingGetPropagationStatus(traceContext); + case ADVANCED_QUEUING_LISTEN: + return new AdvancedQueuingListen(traceContext); + case ADVANCED_QUEUING_SESSION_GET_RPC_1: + return new AdvancedQueuingSessionGetRPC1(traceContext); + case ADVANCED_QUEUING_SESSION_GET_RPC_2: + return new AdvancedQueuingSessionGetRPC2(traceContext); + case ADVANCED_QUEUING_SHARED_DEQUEUE: + return new AdvancedQueuingSharedDequeue(traceContext); + case ADVANCED_QUEUING_SHARED_ENQUEUE: + return new AdvancedQueuingSharedEnqueue(traceContext); + case APP_REPLAY: + return new AppReplay(traceContext); + case AUTH_CALL: + return new AuthCall(traceContext); + case AUTO_COMMIT_OFF: + return new AutoCommitOff(traceContext); + case AUTO_COMMIT_ON: + return new AutoCommitOn(traceContext); + case CANCEL_ALL: + return new CancelAll(traceContext); + case CANCEL_OPERATION: + return new CancelOperation(traceContext); + case CHUNCK_INFO: + return new ChunkInfo(traceContext); + case CLIENT_FEATURES: + return new ClientFeatures(traceContext); + case CLIENT_QUERY_CACHE_IDS: + return new ClientQueryCacheIds(traceContext); + case CLIENT_QUERY_CACHE_STATS_UPDATE: + return new ClientQueryCacheStatsUpdate(traceContext); + case CLOSE_ALL_CURSOR: + return new CloseAllCursor(traceContext); + case CLOSE_CURSOR: + return new CloseCursor(traceContext); + case COMMIT: + return new Commit(traceContext); + case DB12C_NOTIFICATION_RCV: + return new DB12cNotificationRCV(traceContext); + case DBNS_SAGAS: + return new DBNSSagas(traceContext); + case DESCRIBE_ANY_V8: + return new DescribeAnyV8(traceContext); + case DESCRIBE_ARRAY: + return new DescribeArray(traceContext); + case DESCRIBE_QUERY_CALL: + return new DescribeQueryCall(traceContext); + case DIRECT_PATH_LOAD_STREAM: + return new DirectPathLoadStream(traceContext); + case DIRECT_PATH_MISC_OP: + return new DirectPathMISCOp(traceContext); + case DIRECT_PATH_PREPARE: + return new DirectPathPrepare(traceContext); + case DISTRIBUTED_TRANS_MGR_RPC: + return new DistributedTransMGRRPC(traceContext); + case EXECUTE_QUERY: + return new ExecuteQuery(traceContext); + case EXTENSIBLE_SECURITY_SESSION_CREATE: + return new ExtensibleSecuritySessionCreate(traceContext); + case EXTENSIBLE_SECURITY_SESSION_PIGGYBACK: + return new ExtensibleSecuritySessionPiggyback(traceContext); + case EXTENSIBLE_SECURITY_SESSION_ROUNDTRIP: + return new ExtensibleSecuritySessionRoundtrip(traceContext); + case FAST_UPI_CALLS: + return new FastUPICalls(traceContext); + case FETCH_ROW: + return new FetchRow(traceContext); + case GET_VERSION: + return new GetVersion(traceContext); + case KERNEL_PROGRAMMATIC_NOTIFICATION: + return new KernelProgrammaticNotification(traceContext); + case KEY_VALUE: + return new KeyValue(traceContext); + case LOB_FILE_CALL: + return new LOBFileCall(traceContext); + case LOGOFF: + return new LogOff(traceContext); + case LOGON_CHALLENGE_RESPONSE_1: + return new LogonChallengeResponse1(traceContext); + case LOGON_CHALLENGE_RESPONSE_2: + return new LogonChallengeResponse2(traceContext); + case OEXFEN: + return new OEXFEN(traceContext); + case OPEN_CURSOR: + return new OpenCursor(traceContext); + case OSQL7: + return new OSQL7(traceContext); + case OSTART: + return new OStart(traceContext); + case OSTOP: + return new OStop(traceContext); + case PARAMETER_PUT_SPFILE: + return new ParameterPutSPFile(traceContext); + case PING: + return new Ping(traceContext); + case PIPELINE_END: + return new PipelineEnd(traceContext); + case PIPELINE_PIGGYBACK_BEGIN: + return new PipelinePiggybackBegin(traceContext); + case PIPELINE_PIGGYBACK_OP: + return new PipelinePiggybackOp(traceContext); + case ROLLBACK: + return new Rollback(traceContext); + case SESSION_KEY: + return new SessionKey(traceContext); + case SESSION_STATE_OPS: + return new SessionStateOps(traceContext); + case SESSION_STATE_TEMPLATE: + return new SessionStateTemplate(traceContext); + case SESSION_SWITCH_V8: + return new SessionSwitchV8(traceContext); + case TRACING_MESSAGE: + return new TracingMessage(traceContext); + case TRANSACTION_COMMIT: + return new TransactionCommit(traceContext); + case TRANSACTION_START: + return new TransactionStart(traceContext); + case TTC_DTY_ROUNDTRIP: + return new TTCDTYRoundtrip(traceContext); + case TTC_PRO_ROUNDTRIP: + return new TTCPRORoundtrip(traceContext); + case XS_ATTACH_SESSION: + return new XSAttachSession(traceContext); + case XS_CREATE_SESSION: + return new XSCreateSession(traceContext); + case XS_DESTROY_SESSION: + return new XSDestroySession(traceContext); + case XS_DETACH_SESSION: + return new XSDetachSession(traceContext); + case XS_NAMESPACE_OP: + return new XSNamespaceOp(traceContext); + case XS_NAMESPACE_OPS: + return new XSNamespaceOps(traceContext); + case XS_SET_SESSION_PARAMETER: + return new XSSetSessionParameter(traceContext); + case XS_STATE_SYNC_OP: + return new XSStateSyncOp(traceContext); + default: + return new RoundTripEvent(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_12C_EMON_DEQUEUE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuing12cEminDequeueEvent extends RoundTripEvent{ + public AdvancedQueuing12cEminDequeueEvent(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_ARRAY_ENQUEUE_DEQUEUE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingArrayEnqueueDequeue extends RoundTripEvent{ + public AdvancedQueuingArrayEnqueueDequeue(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_DEQUEUE_V8") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingDequeueV8 extends RoundTripEvent{ + public AdvancedQueuingDequeueV8(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_ENQUEUE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingEnqueue extends RoundTripEvent{ + public AdvancedQueuingEnqueue(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_GET_PROPAGATION_STATUS") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingGetPropagationStatus extends RoundTripEvent{ + public AdvancedQueuingGetPropagationStatus(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_LISTEN") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingListen extends RoundTripEvent{ + public AdvancedQueuingListen(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SESSION_GET_RPC_1") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingSessionGetRPC1 extends RoundTripEvent{ + public AdvancedQueuingSessionGetRPC1(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SESSION_GET_RPC_2") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingSessionGetRPC2 extends RoundTripEvent{ + public AdvancedQueuingSessionGetRPC2(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SHARED_DEQUEUE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingSharedDequeue extends RoundTripEvent{ + public AdvancedQueuingSharedDequeue(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SHARED_ENQUEUE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingSharedEnqueue extends RoundTripEvent{ + public AdvancedQueuingSharedEnqueue(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.APP_REPLAY") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AppReplay extends RoundTripEvent{ + public AppReplay(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.AUTH_CALL") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AuthCall extends RoundTripEvent{ + public AuthCall(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.AUTO_COMMIT_OFF") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AutoCommitOff extends RoundTripEvent{ + public AutoCommitOff(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.AUTO_COMMIT_ON") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class AutoCommitOn extends RoundTripEvent{ + public AutoCommitOn(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CANCEL_ALL") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class CancelAll extends RoundTripEvent{ + public CancelAll(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CANCEL_OPERATION") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class CancelOperation extends RoundTripEvent{ + public CancelOperation(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CHUNCK_INFO") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ChunkInfo extends RoundTripEvent{ + public ChunkInfo(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_FEATURES") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ClientFeatures extends RoundTripEvent{ + public ClientFeatures(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_QUERY_CACHE_IDS") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ClientQueryCacheIds extends RoundTripEvent{ + public ClientQueryCacheIds(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_QUERY_CACHE_STATS_UPDATE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ClientQueryCacheStatsUpdate extends RoundTripEvent{ + public ClientQueryCacheStatsUpdate(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLOSE_ALL_CURSOR") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class CloseAllCursor extends RoundTripEvent{ + public CloseAllCursor(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLOSE_CURSOR") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class CloseCursor extends RoundTripEvent{ + public CloseCursor(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.COMMIT") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class Commit extends RoundTripEvent{ + public Commit(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DB12C_NOTIFICATION_RCV") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DB12cNotificationRCV extends RoundTripEvent{ + public DB12cNotificationRCV(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DBNS_SAGAS") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DBNSSagas extends RoundTripEvent{ + public DBNSSagas(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_ANY_V8") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DescribeAnyV8 extends RoundTripEvent{ + public DescribeAnyV8(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_ARRAY") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DescribeArray extends RoundTripEvent{ + public DescribeArray(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_QUERY_CALL") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DescribeQueryCall extends RoundTripEvent{ + public DescribeQueryCall(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_LOAD_STREAM") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DirectPathLoadStream extends RoundTripEvent{ + public DirectPathLoadStream(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_MISC_OP") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DirectPathMISCOp extends RoundTripEvent{ + public DirectPathMISCOp(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_PREPARE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DirectPathPrepare extends RoundTripEvent{ + public DirectPathPrepare(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DISTRIBUTED_TRANS_MGR_RPC") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class DistributedTransMGRRPC extends RoundTripEvent{ + public DistributedTransMGRRPC(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.EXECUTE_QUERY") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ExecuteQuery extends RoundTripEvent{ + public ExecuteQuery(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_CREATE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ExtensibleSecuritySessionCreate extends RoundTripEvent{ + public ExtensibleSecuritySessionCreate(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_PIGGYBACK") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ExtensibleSecuritySessionPiggyback extends RoundTripEvent{ + public ExtensibleSecuritySessionPiggyback(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_ROUNDTRIP") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ExtensibleSecuritySessionRoundtrip extends RoundTripEvent{ + public ExtensibleSecuritySessionRoundtrip(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.FAST_UPI_CALLS") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class FastUPICalls extends RoundTripEvent{ + public FastUPICalls(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.FETCH_ROW") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class FetchRow extends RoundTripEvent{ + public FetchRow(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.GET_VERSION") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class GetVersion extends RoundTripEvent{ + public GetVersion(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.KERNEL_PROGRAMMATIC_NOTIFICATION") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class KernelProgrammaticNotification extends RoundTripEvent{ + public KernelProgrammaticNotification(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.KEY_VALUE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class KeyValue extends RoundTripEvent{ + public KeyValue(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.LOB_FILE_CALL") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class LOBFileCall extends RoundTripEvent{ + public LOBFileCall(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.LOGOFF") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class LogOff extends RoundTripEvent{ + public LogOff(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.LOGON_CHALLENGE_RESPONSE_1") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class LogonChallengeResponse1 extends RoundTripEvent{ + public LogonChallengeResponse1(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.LOGON_CHALLENGE_RESPONSE_2") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class LogonChallengeResponse2 extends RoundTripEvent{ + public LogonChallengeResponse2(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OEXFEN") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class OEXFEN extends RoundTripEvent{ + public OEXFEN(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OPEN_CURSOR") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class OpenCursor extends RoundTripEvent{ + public OpenCursor(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OSQL7") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class OSQL7 extends RoundTripEvent{ + public OSQL7(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OSTART") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class OStart extends RoundTripEvent{ + public OStart(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OSTOP") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class OStop extends RoundTripEvent{ + public OStop(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PARAMETER_PUT_SPFILE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class ParameterPutSPFile extends RoundTripEvent{ + public ParameterPutSPFile(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PING") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class Ping extends RoundTripEvent{ + public Ping(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_END") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class PipelineEnd extends RoundTripEvent{ + public PipelineEnd(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_PIGGYBACK_BEGIN") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class PipelinePiggybackBegin extends RoundTripEvent{ + public PipelinePiggybackBegin(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_PIGGYBACK_OP") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class PipelinePiggybackOp extends RoundTripEvent{ + public PipelinePiggybackOp(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ROLLBACK") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class Rollback extends RoundTripEvent{ + public Rollback(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_KEY") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class SessionKey extends RoundTripEvent{ + public SessionKey(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_STATE_OPS") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class SessionStateOps extends RoundTripEvent{ + public SessionStateOps(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_STATE_TEMPLATE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class SessionStateTemplate extends RoundTripEvent{ + public SessionStateTemplate(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_SWITCH_V8") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class SessionSwitchV8 extends RoundTripEvent{ + public SessionSwitchV8(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TRACING_MESSAGE") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class TracingMessage extends RoundTripEvent{ + public TracingMessage(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TRANSACTION_COMMIT") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class TransactionCommit extends RoundTripEvent{ + public TransactionCommit(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TRANSACTION_START") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class TransactionStart extends RoundTripEvent{ + public TransactionStart(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TTC_DTY_ROUNDTRIP") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class TTCDTYRoundtrip extends RoundTripEvent{ + public TTCDTYRoundtrip(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TTC_PRO_ROUNDTRIP") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class TTCPRORoundtrip extends RoundTripEvent{ + public TTCPRORoundtrip(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_ATTACH_SESSION") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class XSAttachSession extends RoundTripEvent{ + public XSAttachSession(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_CREATE_SESSION") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class XSCreateSession extends RoundTripEvent{ + public XSCreateSession(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_DESTROY_SESSION") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class XSDestroySession extends RoundTripEvent{ + public XSDestroySession(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_DETACH_SESSION") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class XSDetachSession extends RoundTripEvent{ + public XSDetachSession(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_NAMESPACE_OP") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class XSNamespaceOp extends RoundTripEvent{ + public XSNamespaceOp(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_NAMESPACE_OPS") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class XSNamespaceOps extends RoundTripEvent{ + public XSNamespaceOps(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_SET_SESSION_PARAMETER") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class XSSetSessionParameter extends RoundTripEvent{ + public XSSetSessionParameter(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_STATE_SYNC_OP") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class XSStateSyncOp extends RoundTripEvent{ + public XSStateSyncOp(TraceContext traceContext) { + super(traceContext); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class RoundTripEvent extends Event { + + public RoundTripEvent(TraceContext traceContext) { + setValues(traceContext); + } + + public void setValues(TraceContext traceContext) { + this.connectionID = traceContext.getConnectionId(); + this.databaseOperation = traceContext.databaseOperation(); + this.tenant = traceContext.tenant(); + this.sqlID = traceContext.getSqlId(); + if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { + this.originalSQLText = traceContext.originalSqlText(); + this.actualSQLText = traceContext.actualSqlText(); + this.databaseUser = traceContext.user(); + } + } + + @Label("Connection ID") + String connectionID; + + @Label("Database operation") + String databaseOperation; + + @Label("Database tenant") + String tenant; + + @Label("SQL ID") + String sqlID; + + @Label("Original SQL text") + String originalSQLText; + + @Label("Actual SQL text") + String actualSQLText; + + @Label("Database user") + String databaseUser; + + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java index 4e9736a0..68347ac8 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java @@ -1,44 +1,24 @@ package oracle.jdbc.provider.observability.tracers; -import java.util.ArrayList; -import java.util.List; - -import oracle.jdbc.DatabaseFunction; import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.Sequence; import oracle.jdbc.TraceEventListener.TraceContext; -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import jdk.jfr.AnnotationElement; -import jdk.jfr.Event; -import jdk.jfr.EventFactory; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.Category; -import jdk.jfr.ValueDescriptor; +import oracle.jdbc.provider.observability.tracers.JFREventFactory.RoundTripEvent; public class JFRTracer implements ObservabilityTracer{ public JFRTracer() {} @Override - public Object traceRoudtrip(Sequence sequence, TraceContext traceContext, Object userContext) { + public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext) { if (sequence.equals(Sequence.BEFORE)) { - Event event = createEvent( - traceContext.databaseFunction()); + RoundTripEvent event = JFREventFactory.createJFREvent(traceContext); event.begin(); return event; } else { if (userContext != null) { - Event event = (Event) userContext; - event.set(0, traceContext.getConnectionId()); - event.set(1, traceContext.databaseOperation()); - event.set(2, traceContext.tenant()); - event.set(3, traceContext.getSqlId()); - if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { - event.set(4, traceContext.originalSqlText()); - event.set(5, traceContext.actualSqlText()); - event.set(6, traceContext.user()); - } + RoundTripEvent event = (RoundTripEvent) userContext; + event.setValues(traceContext); event.end(); event.commit(); } @@ -51,7 +31,7 @@ public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, return null; } - /** + /** * Creates an event for a given database operation. * @param databaseFunction The database function originating the round trip. * @return returns a Java Flight Recorder Event containing the following @@ -64,6 +44,7 @@ public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, *
  • User
  • * */ + /* private static Event createEvent(DatabaseFunction databaseFunction) { String eventName = "oracle.jdbc.provider.jfr.roundtrip." + databaseFunction.toString(); List eventAnnotations = new ArrayList(); @@ -84,6 +65,7 @@ private static Event createEvent(DatabaseFunction databaseFunction) { EventFactory f = EventFactory.create(eventAnnotations, fields); return f.newEvent(); } +*/ } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java index 5aba96c8..9bbdafa8 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java @@ -9,6 +9,7 @@ import javax.security.auth.login.Configuration; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanContext; @@ -29,6 +30,13 @@ public class OTelTracer implements ObservabilityTracer { private Tracer tracer; + public OTelTracer() { + this(GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName())); + } + + public OTelTracer(Tracer tracer) { + this.tracer = tracer; + } // Number of parameters expected for each execution event private static final Map EXECUTION_EVENTS_PARAMETERS = new EnumMap( JdbcExecutionEvent.class) { @@ -42,7 +50,7 @@ public class OTelTracer implements ObservabilityTracer { private static Logger logger = Logger.getLogger(OTelTracer.class.getName()); @Override - public Object traceRoudtrip(Sequence sequence, TraceContext traceContext, Object userContext) { + public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext) { if (sequence == Sequence.BEFORE) { // Create the Span before the round-trip. final Span span = initAndGetSpan(traceContext, traceContext.databaseOperation()); diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java index 54e07c72..98f9218a 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java @@ -6,7 +6,7 @@ public interface ObservabilityTracer { - Object traceRoudtrip(Sequence sequence, TraceContext traceContext, Object userContext); + Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext); Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params); diff --git a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider new file mode 100644 index 00000000..b1c86eef --- /dev/null +++ b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider @@ -0,0 +1,2 @@ +oracle.jdbc.provider.observability.ObservabilityTraceEventListenerProvider +oracle.jdbc.provider.observability.OpenTelemetryTraceEventListenerProvider \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java new file mode 100644 index 00000000..d2e58eda --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java @@ -0,0 +1,7 @@ +package oracle.jdbc.provider.observability; + +public enum ObservabilityTestProperties { + OBSERVABILITY_URL, + OBSERVABILITY_USERNAME, + OBSERVABILITY_PASSWORD +} diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java new file mode 100644 index 00000000..b598908f --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -0,0 +1,189 @@ +package oracle.jdbc.provider.observability; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.atLeastOnce; + +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.MeterBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerProvider; +import jdk.jfr.Configuration; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingFile; +import oracle.jdbc.DatabaseFunction; +import oracle.jdbc.provider.TestProperties; +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; + +public class ObservabilityTraceEventListenerTest { + String url = TestProperties.getOrAbort(ObservabilityTestProperties.OBSERVABILITY_URL); + String userName = TestProperties.getOrAbort(ObservabilityTestProperties.OBSERVABILITY_USERNAME); + String password = TestProperties.getOrAbort(ObservabilityTestProperties.OBSERVABILITY_PASSWORD); + + // JFR + private static final String SESSION_KEY = "oracle.jdbc.provider.observability.RoundTrip.SESSION_KEY"; + private static final String AUTH_CALL = "oracle.jdbc.provider.observability.RoundTrip.AUTH_CALL"; + private static final String EXECUTE_QUERY = "oracle.jdbc.provider.observability.RoundTrip.EXECUTE_QUERY"; + private static final String LOGOFF = "oracle.jdbc.provider.observability.RoundTrip.LOGOFF"; + + // OTEL + private static final OpenTelemetry openTelemetry = Mockito.mock(OpenTelemetry.class); + private static final Span span = Mockito.mock(Span.class); + private static final SpanContext spanContext = Mockito.mock(SpanContext.class); + private static final TraceFlags traceFlags = Mockito.mock(TraceFlags.class); + private static final TraceState traceState = Mockito.mock(TraceState.class); + private static final SpanBuilder spanBuilder = Mockito.mock(SpanBuilder.class); + private static final TracerProvider tracerProvider = Mockito.mock(TracerProvider.class); + private static final Tracer tracer = Mockito.mock(Tracer.class); + private static final MeterBuilder meterBuilder = Mockito.mock(MeterBuilder.class); + + static { + GlobalOpenTelemetry.set(openTelemetry); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { + + ObservabilityConfiguration.getInstance().setEnabledTracers("JFR"); + ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(sensitiveDataEnabled); + Configuration configuration = Configuration.getConfiguration("default"); + try (Recording recording = new Recording(configuration)) { + recording.start(); + try (Connection connection = DriverManager.getConnection(url, userName, password); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { + while (resultSet.next()) { + System.out.println(resultSet.getString(1)); + } + } + recording.stop(); + recording.dump(Path.of("dump" + sensitiveDataEnabled + ".jfr")); + + try (RecordingFile recordingFile = new RecordingFile(Path.of("dump" + sensitiveDataEnabled + ".jfr"))) { + while (recordingFile.hasMoreEvents()) { + RecordedEvent event = recordingFile.readEvent(); + if (event.getEventType().getCategoryNames().contains("Round trips")) { + switch (event.getEventType().getName()) { + case SESSION_KEY: + assertNotNull(event.getString("connectionID")); + assertNotNull(event.getString("databaseOperation")); +// assertNull(event.getString("tenant")); + assertNull(event.getString("sqlID")); + assertNull(event.getString("originalSQLText")); + assertNull(event.getString("actualSQLText")); + assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); + break; + case AUTH_CALL: + assertNotNull(event.getString("connectionID")); + assertNotNull(event.getString("databaseOperation")); +// assertNull(event.getString("tenant")); + assertNull(event.getString("sqlID")); + assertNull(event.getString("originalSQLText")); + assertNull(event.getString("actualSQLText")); + assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); + + break; + case EXECUTE_QUERY: + assertNotNull(event.getString("connectionID")); + assertNotNull(event.getString("databaseOperation")); +// assertNotNull(event.getString("tenant")); + assertNotNull(event.getString("sqlID")); + assertEquals(sensitiveDataEnabled, event.getString("originalSQLText") != null); + assertEquals(sensitiveDataEnabled, event.getString("actualSQLText") != null); + assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); + break; + case LOGOFF: + assertNotNull(event.getString("connectionID")); + assertNotNull(event.getString("databaseOperation")); +// assertNotNull(event.getString("tenant")); + assertNull(event.getString("sqlID")); + assertNull(event.getString("originalSQLText")); + assertNull(event.getString("actualSQLText")); + assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); + break; + default: + fail("Unexpected event"); + } + } + } + } + } + + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { + configureOTEL(); + ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); + ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(sensitiveDataEnabled); + try (Connection connection = DriverManager.getConnection(url, userName, password); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { + + while (resultSet.next()) { + System.out.println(resultSet.getString(1)); + } + } + + Mockito.verify(tracer, atLeastOnce()).spanBuilder(DatabaseFunction.SESSION_KEY.getDescription()); + Mockito.verify(tracer, atLeastOnce()).spanBuilder(DatabaseFunction.AUTH_CALL.getDescription()); + Mockito.verify(tracer, atLeastOnce()).spanBuilder(DatabaseFunction.EXECUTE_QUERY.getDescription()); + Mockito.verify(tracer, atLeastOnce()).spanBuilder(DatabaseFunction.LOGOFF.getDescription()); + Mockito.verify(spanBuilder, atLeastOnce()).startSpan(); + Mockito.verify(spanBuilder, Mockito.atLeast(4)).setAttribute("thread.id", Thread.currentThread().getId()); + Mockito.verify(spanBuilder, Mockito.atLeast(4)).setAttribute("thread.name", Thread.currentThread().getName()); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.SESSION_KEY.getDescription()); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.AUTH_CALL.getDescription()); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.EXECUTE_QUERY.getDescription()); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.LOGOFF.getDescription()); + if (sensitiveDataEnabled) { + Mockito.verify(spanBuilder, Mockito.times(4)).setAttribute("Database User", userName); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); + } + + + } + + private static void configureOTEL() { + Mockito.reset(spanBuilder, tracer); + Mockito.when(openTelemetry.getTracerProvider()).thenReturn(tracerProvider); + Mockito.when(tracerProvider.get(Mockito.anyString())).thenReturn(tracer); + Mockito.when(openTelemetry.meterBuilder(Mockito.anyString())).thenReturn(meterBuilder); + Mockito.when(spanContext.getTraceFlags()).thenReturn(traceFlags); + Mockito.when(spanContext.getTraceState()).thenReturn(traceState); + Mockito.when(span.getSpanContext()).thenReturn(spanContext); + Mockito.when(spanBuilder.setAttribute(Mockito.anyString(), Mockito.anyString())).thenReturn(spanBuilder); + Mockito.when(spanBuilder.setAttribute(Mockito.anyString(), Mockito.anyLong())).thenReturn(spanBuilder); + Mockito.when(spanBuilder.setAttribute(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(spanBuilder); + Mockito.when(spanBuilder.setAttribute(Mockito.anyString(), Mockito.any())).thenReturn(spanBuilder); + Mockito.when(spanBuilder.setSpanKind(Mockito.any(SpanKind.class))).thenReturn(spanBuilder); + Mockito.when(spanBuilder.startSpan()).thenReturn(span); + Mockito.when(tracer.spanBuilder(Mockito.anyString())).thenReturn(spanBuilder); + } + +} From 5f5fdb8101dce9b973031d7be1d34f9b6ded9948 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 3 Feb 2025 14:08:35 +0100 Subject: [PATCH 03/82] Replaced set by list --- .../ObservabilityTraceEventListener.java | 24 +++++++++---------- .../ObservabilityConfiguration.java | 13 ++++------ .../ObservabilityTraceEventListenerTest.java | 8 +++---- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java index b371c4b0..c3b0c555 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -1,7 +1,5 @@ package oracle.jdbc.provider.observability; -import java.util.EnumMap; - import oracle.jdbc.TraceEventListener; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; import oracle.jdbc.provider.observability.tracers.JFRTracer; @@ -28,10 +26,10 @@ public ObservabilityTracer getTracer() { @Override public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { - EnumMap currentUserContext = getCurrentUserContext(userContext); + Object[] currentUserContext = getCurrentUserContext(userContext); for (Tracers tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { - Object newUserContext = tracer.getTracer().traceRoundtrip(sequence, traceContext, currentUserContext.get(tracer)); - currentUserContext.put(tracer, newUserContext); + Object newUserContext = tracer.getTracer().traceRoundtrip(sequence, traceContext, currentUserContext[tracer.ordinal()]); + currentUserContext[tracer.ordinal()] = newUserContext; } return currentUserContext; } @@ -39,10 +37,10 @@ public Object roundTrip(Sequence sequence, TraceContext traceContext, Object use @Override public Object onExecutionEventReceived(JdbcExecutionEvent event, Object userContext, Object... params) { - EnumMap currentUserContext = getCurrentUserContext(userContext); + Object[] currentUserContext = getCurrentUserContext(userContext); for (Tracers tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { - Object newUserContext = tracer.getTracer().traceExecutionEvent(event, currentUserContext.get(tracer), params); - currentUserContext.put(tracer, newUserContext); + Object newUserContext = tracer.getTracer().traceExecutionEvent(event, currentUserContext[tracer.ordinal()], params); + currentUserContext[tracer.ordinal()] = newUserContext; } return currentUserContext; } @@ -54,12 +52,12 @@ public boolean isDesiredEvent(JdbcExecutionEvent event) { } @SuppressWarnings("unchecked") - private EnumMap getCurrentUserContext(Object userContext) { - EnumMap currentUserContext; - if (userContext != null && (userContext instanceof EnumMap)) { - currentUserContext = (EnumMap) userContext; + private Object[] getCurrentUserContext(Object userContext) { + Object[] currentUserContext; + if (userContext != null && (userContext instanceof Object[])) { + currentUserContext = (Object[]) userContext; } else { - currentUserContext = new EnumMap(Tracers.class); + currentUserContext = new Object[Tracers.values().length]; } return currentUserContext; } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java index e0404fe2..c6555983 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -1,6 +1,7 @@ package oracle.jdbc.provider.observability.configuration; -import java.util.EnumSet; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -23,7 +24,7 @@ private ObservabilityConfiguration() { } private boolean sensitiveDataEnabled; private String tracers; - private EnumSet enabledTracers = EnumSet.noneOf(ObservabilityTraceEventListener.Tracers.class); + private List enabledTracers = new ArrayList<>(); @Override public String getEnabledTracers() { @@ -67,12 +68,8 @@ public static ObservabilityConfiguration getInstance() { return INSTANCE; } - public EnumSet getEnabledTracersSet() { - if (enabledTracers != null) { - return enabledTracers.clone(); - } else { - return null; - } + public List getEnabledTracersSet() { + return enabledTracers; } } diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java index b598908f..d1db3a6c 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import java.nio.file.Path; @@ -12,8 +13,6 @@ import java.sql.ResultSet; import java.sql.Statement; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; @@ -61,6 +60,7 @@ public class ObservabilityTraceEventListenerTest { static { GlobalOpenTelemetry.set(openTelemetry); + configureOTEL(); } @ParameterizedTest @@ -137,7 +137,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { @ParameterizedTest @ValueSource(booleans = {true, false}) public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { - configureOTEL(); + Mockito.clearInvocations(tracer, spanBuilder); ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(sensitiveDataEnabled); try (Connection connection = DriverManager.getConnection(url, userName, password); @@ -165,12 +165,12 @@ public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); } + Mockito.verify(span, atLeast(4)).end(Mockito.any()); } private static void configureOTEL() { - Mockito.reset(spanBuilder, tracer); Mockito.when(openTelemetry.getTracerProvider()).thenReturn(tracerProvider); Mockito.when(tracerProvider.get(Mockito.anyString())).thenReturn(tracer); Mockito.when(openTelemetry.meterBuilder(Mockito.anyString())).thenReturn(meterBuilder); From 2546b01818b8a61384c7945a646d97e3bb1ff343 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 3 Feb 2025 14:25:05 +0100 Subject: [PATCH 04/82] Changes to tests --- .../observability/ObservabilityTraceEventListenerTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java index d1db3a6c..a5336863 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -76,7 +76,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { while (resultSet.next()) { - System.out.println(resultSet.getString(1)); + assertEquals("OK", resultSet.getString(1)); } } recording.stop(); @@ -140,12 +140,13 @@ public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.clearInvocations(tracer, spanBuilder); ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(sensitiveDataEnabled); - try (Connection connection = DriverManager.getConnection(url, userName, password); + String otelUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider"; + try (Connection connection = DriverManager.getConnection(otelUrl, userName, password); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { while (resultSet.next()) { - System.out.println(resultSet.getString(1)); + assertEquals("OK", resultSet.getString(1)); } } From 50211a2f58bc44db5429b5c1c4d3ad535ba874f0 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 3 Feb 2025 15:14:38 +0100 Subject: [PATCH 05/82] Added test name --- .../observability/ObservabilityTraceEventListenerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java index a5336863..74931f55 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -63,7 +63,7 @@ public class ObservabilityTraceEventListenerTest { configureOTEL(); } - @ParameterizedTest + @ParameterizedTest(name = "JFRTraceTest - {arguments}") @ValueSource(booleans = {true, false}) public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { @@ -134,7 +134,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { } - @ParameterizedTest + @ParameterizedTest(name = "OTELTraceTest - {arguments}") @ValueSource(booleans = {true, false}) public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.clearInvocations(tracer, spanBuilder); From 535737c6884efa08c2865f0e627018f21a7acfd2 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 3 Feb 2025 15:58:56 +0100 Subject: [PATCH 06/82] Check connectionID --- .../ObservabilityTraceEventListenerTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java index 74931f55..5ac37e6c 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -33,6 +33,7 @@ import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordingFile; import oracle.jdbc.DatabaseFunction; +import oracle.jdbc.driver.OracleConnection; import oracle.jdbc.provider.TestProperties; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; @@ -70,11 +71,13 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { ObservabilityConfiguration.getInstance().setEnabledTracers("JFR"); ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(sensitiveDataEnabled); Configuration configuration = Configuration.getConfiguration("default"); + String connectionId = null; try (Recording recording = new Recording(configuration)) { recording.start(); try (Connection connection = DriverManager.getConnection(url, userName, password); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { + connectionId = ((OracleConnection)connection).getNetConnectionId(); while (resultSet.next()) { assertEquals("OK", resultSet.getString(1)); } @@ -88,7 +91,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { if (event.getEventType().getCategoryNames().contains("Round trips")) { switch (event.getEventType().getName()) { case SESSION_KEY: - assertNotNull(event.getString("connectionID")); + assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); // assertNull(event.getString("tenant")); assertNull(event.getString("sqlID")); @@ -97,7 +100,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); break; case AUTH_CALL: - assertNotNull(event.getString("connectionID")); + assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); // assertNull(event.getString("tenant")); assertNull(event.getString("sqlID")); @@ -107,7 +110,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { break; case EXECUTE_QUERY: - assertNotNull(event.getString("connectionID")); + assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); // assertNotNull(event.getString("tenant")); assertNotNull(event.getString("sqlID")); @@ -116,7 +119,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); break; case LOGOFF: - assertNotNull(event.getString("connectionID")); + assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); // assertNotNull(event.getString("tenant")); assertNull(event.getString("sqlID")); @@ -141,10 +144,11 @@ public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(sensitiveDataEnabled); String otelUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider"; + String connectionId = null; try (Connection connection = DriverManager.getConnection(otelUrl, userName, password); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { - + connectionId = ((OracleConnection)connection).getNetConnectionId(); while (resultSet.next()) { assertEquals("OK", resultSet.getString(1)); } @@ -157,6 +161,7 @@ public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.verify(spanBuilder, atLeastOnce()).startSpan(); Mockito.verify(spanBuilder, Mockito.atLeast(4)).setAttribute("thread.id", Thread.currentThread().getId()); Mockito.verify(spanBuilder, Mockito.atLeast(4)).setAttribute("thread.name", Thread.currentThread().getName()); + Mockito.verify(spanBuilder, Mockito.atLeast(1)).setAttribute("Connection ID", connectionId); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.SESSION_KEY.getDescription()); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.AUTH_CALL.getDescription()); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.EXECUTE_QUERY.getDescription()); From 65753e81b230966b3c898f346f6629401bf09766 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 10 Feb 2025 10:07:54 +0100 Subject: [PATCH 07/82] Static JFR events --- .../ObservabilityTraceEventListener.java | 15 ---------- .../ObservabilityConfiguration.java | 10 +++++-- .../tracers/JFREventFactory.java | 15 ++++++++++ .../observability/tracers/JFRTracer.java | 6 ++++ .../tracers/ObservabilityTracer.java | 28 +++++++++++++++++++ .../observability/tracers/Tracers.java | 27 ++++++++++++++++++ .../ObservabilityTraceEventListenerTest.java | 8 +++--- 7 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracers.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java index c3b0c555..e149e59c 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -9,21 +9,6 @@ public class ObservabilityTraceEventListener implements TraceEventListener { - public enum Tracers { - OTEL(new OTelTracer()), - JFR(new JFRTracer()); - - private ObservabilityTracer tracer; - - Tracers(ObservabilityTracer tracer) { - this.tracer = tracer; - } - - public ObservabilityTracer getTracer() { - return tracer; - } - } - @Override public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { Object[] currentUserContext = getCurrentUserContext(userContext); diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java index c6555983..527f09c6 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -6,7 +6,11 @@ import java.util.stream.Collectors; import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; +import oracle.jdbc.provider.observability.tracers.Tracers; +/** + * + */ public class ObservabilityConfiguration implements ObservabilityConfigurationMBean { private static final ReentrantLock observabilityConfiguraitonLock = new ReentrantLock(); @@ -24,7 +28,7 @@ private ObservabilityConfiguration() { } private boolean sensitiveDataEnabled; private String tracers; - private List enabledTracers = new ArrayList<>(); + private List enabledTracers = new ArrayList<>(); @Override public String getEnabledTracers() { @@ -44,7 +48,7 @@ public void setEnabledTracers(String tracers) { String[] items = tracers.split(","); for (String item : items) { if (item != null) { - enabledTracers.add(ObservabilityTraceEventListener.Tracers.valueOf(item.toUpperCase())); + enabledTracers.add(Tracers.valueOf(item.toUpperCase())); } } this.tracers = enabledTracers.stream().map((item) -> item.toString()).collect(Collectors.joining(",")); @@ -68,7 +72,7 @@ public static ObservabilityConfiguration getInstance() { return INSTANCE; } - public List getEnabledTracersSet() { + public List getEnabledTracersSet() { return enabledTracers; } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java index 3c2f0551..88ddec83 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java @@ -7,8 +7,23 @@ import oracle.jdbc.TraceEventListener.TraceContext; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +/** + * Factory class for creating JFR events depending on the database function. + */ public class JFREventFactory { + /** + * This class only has a static method, no public constructor needed. + */ + private JFREventFactory() { } + + /** + * An instance of {@link RoundTripEvent} for the given trace context. The type + * of round trip event depends on the database function. + * + * @param traceContext the trace context received by a TraceEventListener. + * @return the {@link RoundTripEvent} for the database function. + */ public static RoundTripEvent createJFREvent(TraceContext traceContext) { switch (traceContext.databaseFunction()) { case ADVANCED_QUEUING_12C_EMON_DEQUEUE: diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java index 68347ac8..31391fd7 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java @@ -5,8 +5,14 @@ import oracle.jdbc.TraceEventListener.TraceContext; import oracle.jdbc.provider.observability.tracers.JFREventFactory.RoundTripEvent; +/** + * {@link ObservabilityTracer} for tracing Java Flight Recorder events. + */ public class JFRTracer implements ObservabilityTracer{ + /** + * Creates a new instance. + */ public JFRTracer() {} @Override diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java index 98f9218a..18daf733 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java @@ -3,11 +3,39 @@ import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.Sequence; import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; +/** + * This interface must be implemented by all Observability tracers. + */ public interface ObservabilityTracer { + /** + * Called by {@link ObservabilityTraceEventListener} when a round trip event + * is received. + * + * @param sequence BEFORE if before the round trip, AFTER if after the round + * trip + * @param traceContext Information about the round trip. Valid only during the + * call + * @param userContext Result of previous call on this Connection or null if no + * previous call or if observability was disabled since the previous call. + * @return a user context object that is passed to the next call on this + * Connection. May be null. + */ Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext); + /** + * Called by {@link ObservabilityTraceEventListener} when an execution event + * is received. + * + * @param event the event. + * @param userContext the result of the previous call or null if no previous + * call has been made. + * @param params event specific parameters. + * @return a user context object that is passed to the next call for the same + * type of event. May be null. + */ Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params); } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracers.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracers.java new file mode 100644 index 00000000..8ac2ec10 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracers.java @@ -0,0 +1,27 @@ +package oracle.jdbc.provider.observability.tracers; + +/** + * This interface defines the constants that are used to identify the + * {@link ObservabilityTracer}. + */ +public enum Tracers { + /** + * Open Telemetry tracer. + */ + OTEL(new OTelTracer()), + + /** + * Java Flight Recorder tracer. + */ + JFR(new JFRTracer()); + + private ObservabilityTracer tracer; + + Tracers(ObservabilityTracer tracer) { + this.tracer = tracer; + } + + public ObservabilityTracer getTracer() { + return tracer; + } +} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java index 5ac37e6c..513de9ef 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -93,7 +93,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { case SESSION_KEY: assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); -// assertNull(event.getString("tenant")); + assertNull(event.getString("tenant")); assertNull(event.getString("sqlID")); assertNull(event.getString("originalSQLText")); assertNull(event.getString("actualSQLText")); @@ -102,7 +102,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { case AUTH_CALL: assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); -// assertNull(event.getString("tenant")); + assertNull(event.getString("tenant")); assertNull(event.getString("sqlID")); assertNull(event.getString("originalSQLText")); assertNull(event.getString("actualSQLText")); @@ -112,7 +112,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { case EXECUTE_QUERY: assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); -// assertNotNull(event.getString("tenant")); + assertNotNull(event.getString("tenant")); assertNotNull(event.getString("sqlID")); assertEquals(sensitiveDataEnabled, event.getString("originalSQLText") != null); assertEquals(sensitiveDataEnabled, event.getString("actualSQLText") != null); @@ -121,7 +121,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { case LOGOFF: assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); -// assertNotNull(event.getString("tenant")); + assertNotNull(event.getString("tenant")); assertNull(event.getString("sqlID")); assertNull(event.getString("originalSQLText")); assertNull(event.getString("actualSQLText")); From ce3601d0af0b49e22608f50330b09dd7a4bcad5b Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 10 Feb 2025 10:12:35 +0100 Subject: [PATCH 08/82] Merged from main --- .../observability/ObservabilityTraceEventListener.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java index e149e59c..95352da4 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -2,9 +2,7 @@ import oracle.jdbc.TraceEventListener; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.JFRTracer; -import oracle.jdbc.provider.observability.tracers.OTelTracer; -import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; +import oracle.jdbc.provider.observability.tracers.Tracers; public class ObservabilityTraceEventListener implements TraceEventListener { From bc11b39d4c87956d422aff330ada926a4e5d4f1f Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Fri, 14 Feb 2025 12:23:39 +0100 Subject: [PATCH 09/82] Documentation and tests --- ojdbc-provider-observability/README.md | 122 ++++++++++++++++++ ojdbc-provider-observability/pom.xml | 6 +- .../ObservabilityTraceEventListener.java | 50 ++++++- ...servabilityTraceEventListenerProvider.java | 35 +++-- ...enTelemetryTraceEventListenerProvider.java | 20 --- .../ObservabilityConfiguration.java | 58 ++++++++- .../ObservabilityConfigurationMBean.java | 41 +++++- .../observability/tracers/OTelTracer.java | 16 ++- .../tracers/{Tracers.java => Tracer.java} | 8 +- ...enTelemetryTraceEventListenerProvider.java | 96 ++++++++++++++ .../ObservabilityConfigurationTest.java | 73 +++++++++++ .../BackwardCompatibilityTest.java | 75 +++++++++++ ojdbc-provider-samples/pom.xml | 8 +- 13 files changed, 560 insertions(+), 48 deletions(-) create mode 100644 ojdbc-provider-observability/README.md delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java rename ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/{Tracers.java => Tracer.java} (70%) create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java create mode 100644 ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java diff --git a/ojdbc-provider-observability/README.md b/ojdbc-provider-observability/README.md new file mode 100644 index 00000000..fa942914 --- /dev/null +++ b/ojdbc-provider-observability/README.md @@ -0,0 +1,122 @@ +# Oracle JDBC Observability Provider + +This module contains a provider that adds tracing capabilities to the Oracle +JDBC driver. Two tracers are available: + * OTEL: adds Open Telemetry tracing capabilities. + * JFR: exports events to Java Flight Recorder. + +This provider implements the TraceEventListener interface provided by the JDBC +driver which will be notified whenever events are generated in the driver and +will publish these events into Open Telemetry. These events include: + * roundtrips to the database server + * AC begin and sucess + * VIP down event + +The following attributes are added the the traces for each event: + * **Roundtrips** + * Connection ID + * Database Operation + * Database User + * Database Tenant + * SQL ID + * Original SQL Text *(only present if sensitive data is enabled)* + * Actual SQL Text *(only present if sensitive data is enabled)* + * **AC begin and success** + * Error Message + * Error code + * SQL state + * Current replay retry count + * **VIP down event** + * Error message + * VIP address + * Protocol *(only present if sensitive data is enabled)* + * Host *(only present if sensitive data is enabled)* + * Port *(only present if sensitive data is enabled)* + * Service name *(only present if sensitive data is enabled)* + * SID *(only present if sensitive data is enabled)* + * Connection data *(only present if sensitive data is enabled)* + +## Installation + +This provider is distributed as single jar on the Maven Central Repository. The +jar is compiled for JDK 8, and is forward compatible with later JDK versions. +The coordinates for the latest release are: + +```xml + + com.oracle.database.jdbc + ojdbc-provider-observability + 1.0.3 + +``` + +## Usage + +To use the Oracle JDBC observability provider just add the artifact to the +application's classpath and set the following connection property: + +```java +oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider +``` + +## Configuration + +The provider can be configured by: +* using the singleton configuration class, +```java +ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL,JFR"); +ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(true); +``` +* using system properties, +```java +System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "OTEL,JFR"); +System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "true"); +``` +* or using the MBean. +```java +ObjectName objectName = new ObjectName( + "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration"); +MBeanServer server = ManagementFactory.getPlatformMBeanServer(); +server.setAttribute(objectName, new Attribute("EnabledTracers", "OTEL,JFR")); +server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", "true")); +``` + +## Backward compatibility + +### Usage + +The provider can also be used by setting the following connection property: + +```java +oracle.jdbc.provider.traceEventListener=open-telemetry-trace-event-listener-provider +``` + +When this property is used only the OTEL tracer can be used. + +### Configuration + +To ensure backward compatibility Oracle JDBC provider for Open Telemetry configuration +properties and MBean have been kept. When these properties and MBean are used only +the Open Telemetry tracer will be enabled. + +The Oracle JDBC provider for Open Telemetry can be configured using system properties +or a MBean. Two parameters can be configured: + * **Enabled**: when enabled (*true*) traces will be exported to Open + Telemetry. This property is **enabled by default**. + * **Sensitive data enabled**: when enabled (*true*) attributes containing + sensitive information like SQL statements and connection URL will be included + in the traces. This property is **disabled by default**. + +The system properties are "oracle.jdbc.provider.opentelemetry.enabled" and +"oracle.jdbc.provider.opentelemetry.sensitive-enabled" respectively and the MBean +with object name "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener" +exposes two attributes "Enabled" and "SensitiveDataEnabled". + + The sample code below shows how to retrieve the value of an attribute: +```java +ObjectName objectName = new ObjectName( + "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener"); +MBeanServer server = ManagementFactory.getPlatformMBeanServer(); +boolean isEnabled = Boolean.valueOf(server.getAttribute(objectName, "Enabled").toString()) + .booleanValue(); +``` \ No newline at end of file diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 4b90faad..fe07046e 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -3,15 +3,17 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + com.oracle.database.jdbc ojdbc-extensions - 1.0.2 + 1.0.3 + Oracle JDBC Observability Provider com.oracle.database.jdbc ojdbc-provider-observability - 1.0.2 + 1.0.3 1.44.1 diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java index 95352da4..a9559c8e 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -2,15 +2,56 @@ import oracle.jdbc.TraceEventListener; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.Tracers; +import oracle.jdbc.provider.observability.tracers.Tracer; +/** + *

    + * TraceEventListener implementaiton that receives notifications whenever events + * are generated in the driver and publishes these events different tracers + * depending on the configuraiton. + *

    + *

    + * These events include: + *

    + *
      + *
    • roundtrips to the database server
    • + *
    • AC begin and success
    • + *
    • VIP down event
    • + *
    + *

    + * The available tracers are defined in the enumeration {@link Tracer}, and + * can be enabled using the method + * {@link ObservabilityConfiguration#setEnabledTracers(String)}. The method + * {@link ObservabilityConfiguration#setSensitiveDataEnabled(boolean)} allows + * to enabled/disable exporting sensitive data to the tracers. + *

    + */ public class ObservabilityTraceEventListener implements TraceEventListener { + /** + * Private constructor. + */ + private ObservabilityTraceEventListener() { } + + /** + * Singleton instance. + */ + private static final ObservabilityTraceEventListener INSTANCE = + new ObservabilityTraceEventListener(); + + /** + * Returns the singleton instance of {@link ObservabilityTraceEventListener}. + * @return the singleton instance of {@link ObservabilityTraceEventListener}. + */ + public static final ObservabilityTraceEventListener getInstance() { + return INSTANCE; + } @Override public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { + if (!ObservabilityConfiguration.getInstance().getEnabled()) { return null;} Object[] currentUserContext = getCurrentUserContext(userContext); - for (Tracers tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { + for (Tracer tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { Object newUserContext = tracer.getTracer().traceRoundtrip(sequence, traceContext, currentUserContext[tracer.ordinal()]); currentUserContext[tracer.ordinal()] = newUserContext; } @@ -20,8 +61,9 @@ public Object roundTrip(Sequence sequence, TraceContext traceContext, Object use @Override public Object onExecutionEventReceived(JdbcExecutionEvent event, Object userContext, Object... params) { + if (!ObservabilityConfiguration.getInstance().getEnabled()) { return null;} Object[] currentUserContext = getCurrentUserContext(userContext); - for (Tracers tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { + for (Tracer tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { Object newUserContext = tracer.getTracer().traceExecutionEvent(event, currentUserContext[tracer.ordinal()], params); currentUserContext[tracer.ordinal()] = newUserContext; } @@ -40,7 +82,7 @@ private Object[] getCurrentUserContext(Object userContext) { if (userContext != null && (userContext instanceof Object[])) { currentUserContext = (Object[]) userContext; } else { - currentUserContext = new Object[Tracers.values().length]; + currentUserContext = new Object[Tracer.values().length]; } return currentUserContext; } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java index 8ea1163f..5c604d71 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -7,28 +7,34 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.management.Attribute; import javax.management.InstanceAlreadyExistsException; -import javax.management.MBeanException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; -import javax.management.ReflectionException; import oracle.jdbc.TraceEventListener; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; import oracle.jdbc.spi.TraceEventListenerProvider; +/** + * Implementation of Oracle JDBC {@link TraceEventListenerProvider} for + * {@link ObservabilityTraceEventListener}. + */ public class ObservabilityTraceEventListenerProvider implements TraceEventListenerProvider { private static final String PROVIDER_NAME = "observability-trace-event-listener-provider"; - private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.extension.observability:type=ObservabilityConfiguration"; + private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration"; - private static final MBeanServer server = ManagementFactory.getPlatformMBeanServer();; + protected static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; + protected static final String SENSITIVE_DATA_ENABLED = "oracle.jdbc.provider.observability.sensitiveDataEnabled"; + + protected static final MBeanServer server = ManagementFactory.getPlatformMBeanServer();; private static ObjectName objectName; - Logger logger = Logger.getLogger(ObservabilityTraceEventListenerProvider.class.getName()); + protected Logger logger = Logger.getLogger(ObservabilityTraceEventListenerProvider.class.getName()); static { try { @@ -38,15 +44,28 @@ public class ObservabilityTraceEventListenerProvider implements TraceEventListen } } + /** + * Constructs a new instance of ObservabilityTraceEventListenerProvider. This + * constructor will be called by the driver's service provider to create a new + * instance. + */ + public ObservabilityTraceEventListenerProvider() { } + @Override public TraceEventListener getTraceEventListener(Map map) { try { - if (!server.isRegistered(objectName)) - server.registerMBean(ObservabilityConfiguration.getInstance(), objectName); + if (!server.isRegistered(objectName)) { + String enabledTracers = System.getProperty(ENABLED_TRACERS, "OTEL,JFR"); + String sensitiveDataEnabled = System.getProperty(SENSITIVE_DATA_ENABLED, "false"); + ObservabilityConfiguration.getInstance().setEnabledTracers(enabledTracers); + ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(Boolean.parseBoolean(sensitiveDataEnabled)); + + server.registerMBean(ObservabilityConfiguration.getInstance(), objectName); + } } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { logger.log(Level.WARNING, "Could not register MBean", e); } - return new ObservabilityTraceEventListener(); + return ObservabilityTraceEventListener.getInstance(); } @Override diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java deleted file mode 100644 index 649ef25d..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -package oracle.jdbc.provider.observability; - -import java.util.Collection; -import java.util.Collections; - -public class OpenTelemetryTraceEventListenerProvider extends ObservabilityTraceEventListenerProvider { - - private static final String PROVIDER_NAME = "open-telemetry-trace-event-listener-provider"; - - @Override - public String getName() { - return PROVIDER_NAME; - } - - @Override - public Collection getParameters() { - return Collections.emptyList(); - } - -} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java index 527f09c6..e0dd7202 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -5,11 +5,11 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; -import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; -import oracle.jdbc.provider.observability.tracers.Tracers; +import oracle.jdbc.provider.observability.tracers.Tracer; /** - * + * Implementation of {@link ObservabilityConfigurationMBean} that allows to + * configure the Oracle JDBC Observability Provider. */ public class ObservabilityConfiguration implements ObservabilityConfigurationMBean { @@ -25,11 +25,35 @@ public class ObservabilityConfiguration implements ObservabilityConfigurationMBe private ObservabilityConfiguration() { } + private boolean enabled = true; private boolean sensitiveDataEnabled; private String tracers; - private List enabledTracers = new ArrayList<>(); + private List enabledTracers = new ArrayList<>(); + /** + * Returns true if the provider is enabled, otherwise false. + * + * @return true if the provider is enabled, otherwise false. + */ + @Override + public boolean getEnabled() { + return enabled; + } + + /** + * Enables/disables the provider. + * + * @param enabled true to enable the provider, otherwise false. + */ + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Returns a comma separated list of enabled tracers. + */ @Override public String getEnabledTracers() { try { @@ -40,6 +64,11 @@ public String getEnabledTracers() { } } + /** + * Enables the tracers. Available tracers are defined in enum {@link Tracer}. + * + * @param tracers comma separated list of enabled tracers. + */ @Override public void setEnabledTracers(String tracers) { try { @@ -48,7 +77,7 @@ public void setEnabledTracers(String tracers) { String[] items = tracers.split(","); for (String item : items) { if (item != null) { - enabledTracers.add(Tracers.valueOf(item.toUpperCase())); + enabledTracers.add(Tracer.valueOf(item.toUpperCase())); } } this.tracers = enabledTracers.stream().map((item) -> item.toString()).collect(Collectors.joining(",")); @@ -58,22 +87,39 @@ public void setEnabledTracers(String tracers) { } + /** + * Returns true if sensitive data is enabled, otherwise false. + */ @Override public boolean getSensitiveDataEnabled() { return sensitiveDataEnabled; } + /** + * Enables/disables sensitive data. + * + * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. + */ @Override public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { this.sensitiveDataEnabled = sensitiveDataEnabled; } + /** + * Returns the singleton instance of {@link ObservabilityConfiguration}. + * @return the singleton instance of {@link ObservabilityConfiguration}. + */ public static ObservabilityConfiguration getInstance() { return INSTANCE; } - public List getEnabledTracersSet() { + /** + * Returns a list of enabled {@link Tracer}. + * @return then list of nabled {@link Tracer}. + */ + public List getEnabledTracersSet() { return enabledTracers; } + } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java index 3aedf3fc..e67d7fb4 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java @@ -1,12 +1,51 @@ package oracle.jdbc.provider.observability.configuration; +import oracle.jdbc.provider.observability.tracers.Tracer; + +/** + * MBean that allows to configure the Oracle JDBC Observability Provider. + */ public interface ObservabilityConfigurationMBean { - + + /** + * Returns true if the provider is enabled, otherwise false. + * + * @return true if the provider is enabled, otherwise false. + */ + public boolean getEnabled(); + + /** + * Enables/disables the provider. + * + * @param enabled true to enable the provider, otherwise false. + */ + public void setEnabled(boolean enabled); + + /** + * Returns a comma separated list of enabled tracers. + * + * @return a comma separated list of enabled tracers. + */ public String getEnabledTracers(); + /** + * Enables the tracers. Available tracers are defined in enum {@link Tracer}. + * + * @param tracers comma separated list of enabled tracers. + */ public void setEnabledTracers(String tracers); + /** + * Returns true if sensitive data is enabled, otherwise false. + * + * @return true if sensitive data is enabled, otherwise false. + */ public boolean getSensitiveDataEnabled(); + /** + * Enables/disables sensitive data. + * + * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. + */ public void setSensitiveDataEnabled(boolean sensitiveDataEnabled); } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java index 9bbdafa8..1d84535f 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java @@ -24,20 +24,34 @@ import oracle.jdbc.TraceEventListener.TraceContext; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +/** + * Open Telemetry tracer. Exports round trip event and execution events to + * Open Telemetry. + */ public class OTelTracer implements ObservabilityTracer { private static final String TRACE_KEY = "clientcontext.ora$opentelem$tracectx"; private Tracer tracer; + /** + * Constructor. Uses {@link GlobalOpenTelemetry} to get the tracer. + */ public OTelTracer() { this(GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName())); } + /** + * Constructor. + * @param tracer Open Telemetry tracer. + */ public OTelTracer(Tracer tracer) { this.tracer = tracer; } - // Number of parameters expected for each execution event + + /** + * Map containing the number of parameters expected for each execution event + */ private static final Map EXECUTION_EVENTS_PARAMETERS = new EnumMap( JdbcExecutionEvent.class) { { diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracers.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracer.java similarity index 70% rename from ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracers.java rename to ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracer.java index 8ac2ec10..3b74d2f2 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracers.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracer.java @@ -4,7 +4,7 @@ * This interface defines the constants that are used to identify the * {@link ObservabilityTracer}. */ -public enum Tracers { +public enum Tracer { /** * Open Telemetry tracer. */ @@ -17,10 +17,14 @@ public enum Tracers { private ObservabilityTracer tracer; - Tracers(ObservabilityTracer tracer) { + Tracer(ObservabilityTracer tracer) { this.tracer = tracer; } + /** + * Returns the {@link ObservabilityTracer} for this {@link Tracer}. + * @return the {@link ObservabilityTracer}. + */ public ObservabilityTracer getTracer() { return tracer; } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java new file mode 100644 index 00000000..c02baab0 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java @@ -0,0 +1,96 @@ +package oracle.jdbc.provider.opentelemetry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.logging.Level; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; + +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; +import oracle.jdbc.provider.observability.ObservabilityTraceEventListenerProvider; +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; + +/** + *

    + * This class implements the TraceEventListenerProvider interface exposed by the + * Oracle JDBC driver. It provides TraceEventListeners of type {@link + * oracle.jdbc.provider.observability.ObservabilityTraceEventListener}. + *

    + *

    + * The provider registers a MBean (with objectName {@value #MBEAN_OBJECT_NAME}) + * that allows to configure the TraceEventListener by setting attributes. The + * following attributes are available: + *

      + *
    • Enabled: enables/disables exporting traces to Open Telemetry + * (true by default)
    • + *
    • SensitiveDataEnabled: enables/disables exporting sensiteve data + * to Open Telemetry(false by default)
    • + *
    + */ +public class OpenTelemetryTraceEventListenerProvider extends ObservabilityTraceEventListenerProvider { + + private static final String PROVIDER_NAME = "open-telemetry-trace-event-listener-provider"; + private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener"; + + /** + * Name of the property used to enable or disable this listener. + */ + public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED = "oracle.jdbc.provider.opentelemetry.enabled"; + /** + * Name of the property used to enable or disable sensitive data for this + * listener. + */ + public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; + + private static ObjectName objectName; + + static { + try { + objectName = new ObjectName(MBEAN_OBJECT_NAME); + } catch (MalformedObjectNameException e) { + objectName = null; + } + } + + /** + * Constructs a new instance of OpenTelemetryTraceEventListenerProvider. This + * constructor will be called by the driver's service provider to create a new + * instance. + */ + public OpenTelemetryTraceEventListenerProvider() { } + + @Override + public String getName() { + return PROVIDER_NAME; + } + + @Override + public Collection getParameters() { + return Collections.emptyList(); + } + + @Override + public TraceEventListener getTraceEventListener(Map map) { + try { + if (!server.isRegistered(objectName)) { + boolean enabled = Boolean.valueOf(System.getProperty(OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED, "true")); + String sensitiveDataEnabled = System.getProperty(OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "false"); + ObservabilityConfiguration.getInstance().setEnabled(enabled); + ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); + ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(Boolean.parseBoolean(sensitiveDataEnabled)); + + server.registerMBean(ObservabilityConfiguration.getInstance(), objectName); + } + } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { + logger.log(Level.WARNING, "Could not register MBean", e); + } + return ObservabilityTraceEventListener.getInstance(); + } + +} diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java new file mode 100644 index 00000000..75ba85d9 --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java @@ -0,0 +1,73 @@ +package oracle.jdbc.provider.observability; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.management.ManagementFactory; + +import javax.management.Attribute; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.jupiter.api.Test; + +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.tracers.Tracer; +import oracle.jdbc.spi.TraceEventListenerProvider; + +public class ObservabilityConfigurationTest { + + @Test + public void testConfiguration() throws Exception { + + // System properties + System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "JFR"); + System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "true"); + + TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); + provider.getTraceEventListener(null); + + assertEquals("JFR", ObservabilityConfiguration.getInstance().getEnabledTracers()); + assertEquals(true, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); + + assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); + assertEquals(Tracer.JFR, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + + // MBean + ObjectName objectName = new ObjectName( + "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration"); + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals(enabledTracers, "JFR"); + assertEquals(sensitiveDataEnabled, "true"); + + server.setAttribute(objectName, new Attribute("EnabledTracers", "OTEL,JFR")); + server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); + + assertEquals("OTEL,JFR", ObservabilityConfiguration.getInstance().getEnabledTracers()); + assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); + + assertEquals("OTEL,JFR", ObservabilityConfiguration.getInstance().getEnabledTracers()); + assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); + + assertEquals(2, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); + assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + assertEquals(Tracer.JFR, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(1)); + + // Singleton + ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); + ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(true); + + enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals("OTEL", enabledTracers); + assertEquals("true", sensitiveDataEnabled); + + assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); + assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + + } + +} diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java new file mode 100644 index 00000000..edc9ae2c --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java @@ -0,0 +1,75 @@ +package oracle.jdbc.provider.opentelemetry; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.management.ManagementFactory; + +import javax.management.Attribute; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.jupiter.api.Test; + +import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.tracers.Tracer; +import oracle.jdbc.spi.TraceEventListenerProvider; + +public class BackwardCompatibilityTest { + @Test + public void testConfiguration() throws Exception { + + // System properties + System.setProperty("oracle.jdbc.provider.opentelemetry.enabled", "true"); + System.setProperty("oracle.jdbc.provider.opentelemetry.sensitive-enabled", "true"); + + TraceEventListenerProvider provider = new OpenTelemetryTraceEventListenerProvider(); + provider.getTraceEventListener(null); + + assertEquals(true, ObservabilityConfiguration.getInstance().getEnabled()); + assertEquals("OTEL", ObservabilityConfiguration.getInstance().getEnabledTracers()); + assertEquals(true, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); + + assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); + assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + + // MBean + ObjectName objectName = new ObjectName( + "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener"); + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + String enabled = server.getAttribute(objectName, "Enabled").toString(); + String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals(enabled, "true"); + assertEquals(enabledTracers, "OTEL"); + assertEquals(sensitiveDataEnabled, "true"); + + server.setAttribute(objectName, new Attribute("Enabled", false)); + server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); + + assertEquals(false, ObservabilityConfiguration.getInstance().getEnabled()); + assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); + + assertEquals("OTEL", ObservabilityConfiguration.getInstance().getEnabledTracers()); + assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); + + assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); + assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + + // Singleton + ObservabilityConfiguration.getInstance().setEnabled(true); + ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(true); + + enabled = server.getAttribute(objectName, "Enabled").toString(); + enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals("true", enabled); + assertEquals("OTEL", enabledTracers); + assertEquals("true", sensitiveDataEnabled); + + assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); + assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + + } +} diff --git a/ojdbc-provider-samples/pom.xml b/ojdbc-provider-samples/pom.xml index f55ec768..08758a27 100644 --- a/ojdbc-provider-samples/pom.xml +++ b/ojdbc-provider-samples/pom.xml @@ -5,7 +5,7 @@ Oracle JDBC Provider Code Samples ojdbc-provider-samples - ${project.parent.version} + 1.0.3 jar @@ -23,17 +23,17 @@ com.oracle.database.jdbc ojdbc-provider-azure - ${project.parent.version} + 1.0.3 com.oracle.database.jdbc ojdbc-provider-oci - ${project.parent.version} + 1.0.3 com.oracle.database.jdbc ojdbc-provider-jackson-oson - ${project.parent.version} + 1.0.3 com.oracle.database.security From b469a80a7ef7f3f91692458f0017dcb167c70753 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Fri, 14 Feb 2025 17:25:49 +0100 Subject: [PATCH 10/82] Added execution events to JFR --- ...servabilityTraceEventListenerProvider.java | 24 +++- .../tracers/JFREventFactory.java | 129 +++++++++++++++++- .../observability/tracers/JFRTracer.java | 41 +----- .../observability/tracers/OTelTracer.java | 13 +- .../tracers/ObservabilityTracer.java | 16 +++ ...enTelemetryTraceEventListenerProvider.java | 6 + 6 files changed, 174 insertions(+), 55 deletions(-) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java index 5c604d71..50f84221 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -27,14 +27,30 @@ public class ObservabilityTraceEventListenerProvider implements TraceEventListen private static final String PROVIDER_NAME = "observability-trace-event-listener-provider"; private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration"; + private static ObjectName objectName; + + /** + * System property used to enabled/disable tracers. The value of this system property should be a comma separated list + * of {@link Tracer} to enable. By default all tracers will be enabled. + */ + private static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; + + /** + * System property used to enable/disable exporting sensitive data. Set the property to true to enable sensitive data. + * By default exporting sensitive data is disabled. + */ + private static final String SENSITIVE_DATA_ENABLED = "oracle.jdbc.provider.observability.sensitiveDataEnabled"; - protected static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; - protected static final String SENSITIVE_DATA_ENABLED = "oracle.jdbc.provider.observability.sensitiveDataEnabled"; + /** + * Logger + */ + private Logger logger = Logger.getLogger(ObservabilityTraceEventListenerProvider.class.getName()); + /** + * MBean server + */ protected static final MBeanServer server = ManagementFactory.getPlatformMBeanServer();; - private static ObjectName objectName; - protected Logger logger = Logger.getLogger(ObservabilityTraceEventListenerProvider.class.getName()); static { try { diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java index 88ddec83..eb7977c0 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java @@ -3,9 +3,14 @@ import jdk.jfr.Event; import jdk.jfr.Label; import jdk.jfr.Name; + +import java.sql.SQLException; + import jdk.jfr.Category; +import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.TraceContext; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.tracers.JFREventFactory.ExecutionEvent; /** * Factory class for creating JFR events depending on the database function. @@ -18,8 +23,8 @@ public class JFREventFactory { private JFREventFactory() { } /** - * An instance of {@link RoundTripEvent} for the given trace context. The type - * of round trip event depends on the database function. + * Creates an instance of {@link RoundTripEvent} for the given trace context. + * The type of round trip event depends on the database function. * * @param traceContext the trace context received by a TraceEventListener. * @return the {@link RoundTripEvent} for the database function. @@ -177,6 +182,29 @@ public static RoundTripEvent createJFREvent(TraceContext traceContext) { } } + /** + * Creates an instance {@link ExecutionEvent} for the given {@link + * JdbcExecutionEvent}. + * + * @param event the event. + * @param params the parameters to populate the event properties. + * @return the execution event. + */ + public static Event createExecutionEvent(JdbcExecutionEvent event, Object... params) { + switch (event) { + case AC_REPLAY_STARTED: + return new ACReplayStarted(event, params); + case AC_REPLAY_SUCCESSFUL: + return new ACReplaySuccessful(event, params); + case VIP_RETRY: + return new VIPRetry(event, params); + default: + return new ExecutionEvent(event, params); + } + } + + // Round-trip events + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_12C_EMON_DEQUEUE") @Label("Round trip") @Category({"Oracle JDBC", "Round trips"}) @@ -878,4 +906,101 @@ public void setValues(TraceContext traceContext) { } + // Execution Events + @Name("oracle.jdbc.provider.observability.ExecutionEvent.AC_REPLAY_STARTED") + @Label("AC replay started") + @Category({"Oracle JDBC", "Execution events"}) + static class ACReplayStarted extends ACReplay { + public ACReplayStarted(JdbcExecutionEvent event, Object... params) { + super(event, params); + } + } + + @Name("oracle.jdbc.provider.observability.ExecutionEvent.AC_REPLAY_SUCCESSFUL") + @Label("AC replay successful") + @Category({"Oracle JDBC", "Execution events"}) + static class ACReplaySuccessful extends ACReplay { + public ACReplaySuccessful(JdbcExecutionEvent event, Object... params) { + super(event, params); + } + } + + + @Name("oracle.jdbc.provider.observability.ExecutionEvent.AC_REPLAY") + @Label("AC replay") + @Category({"Oracle JDBC", "Execution events"}) + static class ACReplay extends ExecutionEvent { + public ACReplay(JdbcExecutionEvent event, Object... params) { + super(event, params); + if (ObservabilityTracer.EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + this.errorCode = ((SQLException) params[1]).getErrorCode(); + this.sqlState = ((SQLException) params[1]).getSQLState(); + this.currentReplayRetryCount = params[2].toString(); + } + } + @Label("Error code") + public int errorCode; + + @Label("SQL state") + public String sqlState; + + @Label("Current replay retry count") + public String currentReplayRetryCount; + } + + @Name("oracle.jdbc.provider.observability.ExecutionEvent.VIP_RETRY") + @Label("VIP retry") + @Category({"Oracle JDBC", "Round trips"}) + static class VIPRetry extends ExecutionEvent { + public VIPRetry(JdbcExecutionEvent event, Object... params) { + super(event, params); + if (ObservabilityTracer.EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + protocol = params[1].toString(); + host = params[2].toString(); + port = params[3].toString(); + serviceName = params[4].toString(); + sid = params[5].toString(); + connectionData = params[6].toString(); + vipAddress = params[7].toString(); + } + } + + @Label("The protocol") + public String protocol; + + @Label("The host") + public String host; + + @Label("The port") + public String port; + + @Label("The service name") + public String serviceName; + + @Label("The SID") + public String sid; + + @Label("The connection data") + public String connectionData; + + @Label("The VIP address") + public String vipAddress; + } + + @Name("oracle.jdbc.provider.observability.ExecutionEvent") + @Label("Execution event") + @Category({"Oracle JDBC", "Execution events"}) + static class ExecutionEvent extends Event { + public ExecutionEvent(JdbcExecutionEvent event, Object... params) { + if (ObservabilityTracer.EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + if (params != null && params.length > 0) { + this.errorMessage = params[0].toString(); + } + } + } + + @Label("Error message") + String errorMessage; + } + } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java index 31391fd7..ba233c56 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java @@ -1,5 +1,6 @@ package oracle.jdbc.provider.observability.tracers; +import jdk.jfr.Event; import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.Sequence; import oracle.jdbc.TraceEventListener.TraceContext; @@ -34,44 +35,10 @@ public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Objec @Override public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { + Event executionEvent = JFREventFactory.createExecutionEvent(event, params); + executionEvent.begin(); + executionEvent.commit(); return null; } - /** - * Creates an event for a given database operation. - * @param databaseFunction The database function originating the round trip. - * @return returns a Java Flight Recorder Event containing the following - * fields: - *
      - *
    • ConnectionID
    • - *
    • DatabaseOperation
    • - *
    • OriginalSqlText
    • - *
    • ActualSqlText
    • - *
    • User
    • - *
    - */ - /* - private static Event createEvent(DatabaseFunction databaseFunction) { - String eventName = "oracle.jdbc.provider.jfr.roundtrip." + databaseFunction.toString(); - List eventAnnotations = new ArrayList(); - eventAnnotations - .add(new AnnotationElement(Name.class, eventName)); - eventAnnotations.add(new AnnotationElement(Label.class, databaseFunction.getDescription())); - eventAnnotations.add(new AnnotationElement(Category.class, new String[] { "Oracle JDBC", "Round trips" })); - - List fields = new ArrayList(); - fields.add(new ValueDescriptor(String.class, "Connection_ID")); - fields.add(new ValueDescriptor(String.class, "Database_operation")); - fields.add(new ValueDescriptor(String.class, "Database_tenant")); - fields.add(new ValueDescriptor(String.class, "SQL_ID")); - fields.add(new ValueDescriptor(String.class, "Original_SQL_text")); - fields.add(new ValueDescriptor(String.class, "Actual_SQL_text")); - fields.add(new ValueDescriptor(String.class, "Database_user")); - - EventFactory f = EventFactory.create(eventAnnotations, fields); - return f.newEvent(); - } -*/ - - } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java index 1d84535f..8ba559c5 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java @@ -49,17 +49,6 @@ public OTelTracer(Tracer tracer) { this.tracer = tracer; } - /** - * Map containing the number of parameters expected for each execution event - */ - private static final Map EXECUTION_EVENTS_PARAMETERS = new EnumMap( - JdbcExecutionEvent.class) { - { - put(JdbcExecutionEvent.AC_REPLAY_STARTED, 3); - put(JdbcExecutionEvent.AC_REPLAY_SUCCESSFUL, 3); - put(JdbcExecutionEvent.VIP_RETRY, 8); - } - }; private static Logger logger = Logger.getLogger(OTelTracer.class.getName()); @@ -90,7 +79,7 @@ public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Objec @Override public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { -if (EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + if (EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { if (event == TraceEventListener.JdbcExecutionEvent.VIP_RETRY) { SpanBuilder spanBuilder = tracer .spanBuilder(event.getDescription()) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java index 18daf733..5e93c3fa 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java @@ -1,5 +1,8 @@ package oracle.jdbc.provider.observability.tracers; +import java.util.EnumMap; +import java.util.Map; + import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.Sequence; import oracle.jdbc.TraceEventListener.TraceContext; @@ -10,6 +13,19 @@ */ public interface ObservabilityTracer { + /** + * Map containing the number of parameters expected for each execution event + */ + static final Map EXECUTION_EVENTS_PARAMETERS + = new EnumMap(JdbcExecutionEvent.class) { + { + put(JdbcExecutionEvent.AC_REPLAY_STARTED, 3); + put(JdbcExecutionEvent.AC_REPLAY_SUCCESSFUL, 3); + put(JdbcExecutionEvent.VIP_RETRY, 8); + } + }; + + /** * Called by {@link ObservabilityTraceEventListener} when a round trip event * is received. diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java index c02baab0..46d5f676 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.Map; import java.util.logging.Level; +import java.util.logging.Logger; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanRegistrationException; @@ -48,6 +49,11 @@ public class OpenTelemetryTraceEventListenerProvider extends ObservabilityTraceE */ public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; + /** + * Logger + */ + private Logger logger = Logger.getLogger(OpenTelemetryTraceEventListenerProvider.class.getName()); + private static ObjectName objectName; static { From a6cd8746ba861df67eb03b05833c314e0fa45958 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Fri, 21 Feb 2025 11:15:01 +0100 Subject: [PATCH 11/82] Documentation changes --- .../ObservabilityTraceEventListener.java | 10 ++-- ...servabilityTraceEventListenerProvider.java | 2 +- .../ObservabilityConfiguration.java | 56 ++++++++++++++----- .../ObservabilityConfigurationMBean.java | 4 +- .../tracers/{Tracer.java => TracerType.java} | 9 ++- .../tracers/{ => jfr}/JFREventFactory.java | 4 +- .../tracers/{ => jfr}/JFRTracer.java | 5 +- .../tracers/{ => otel}/OTelTracer.java | 35 ++++++++---- .../ObservabilityConfigurationTest.java | 10 ++-- .../BackwardCompatibilityTest.java | 8 +-- 10 files changed, 95 insertions(+), 48 deletions(-) rename ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/{Tracer.java => TracerType.java} (72%) rename ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/{ => jfr}/JFREventFactory.java (99%) rename ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/{ => jfr}/JFRTracer.java (84%) rename ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/{ => otel}/OTelTracer.java (89%) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java index a9559c8e..08484e6f 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -2,7 +2,7 @@ import oracle.jdbc.TraceEventListener; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.Tracer; +import oracle.jdbc.provider.observability.tracers.TracerType; /** *

    @@ -19,7 +19,7 @@ *

  • VIP down event
  • * *

    - * The available tracers are defined in the enumeration {@link Tracer}, and + * The available tracers are defined in the enumeration {@link TracerType}, and * can be enabled using the method * {@link ObservabilityConfiguration#setEnabledTracers(String)}. The method * {@link ObservabilityConfiguration#setSensitiveDataEnabled(boolean)} allows @@ -51,7 +51,7 @@ public static final ObservabilityTraceEventListener getInstance() { public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { if (!ObservabilityConfiguration.getInstance().getEnabled()) { return null;} Object[] currentUserContext = getCurrentUserContext(userContext); - for (Tracer tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { + for (TracerType tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { Object newUserContext = tracer.getTracer().traceRoundtrip(sequence, traceContext, currentUserContext[tracer.ordinal()]); currentUserContext[tracer.ordinal()] = newUserContext; } @@ -63,7 +63,7 @@ public Object roundTrip(Sequence sequence, TraceContext traceContext, Object use public Object onExecutionEventReceived(JdbcExecutionEvent event, Object userContext, Object... params) { if (!ObservabilityConfiguration.getInstance().getEnabled()) { return null;} Object[] currentUserContext = getCurrentUserContext(userContext); - for (Tracer tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { + for (TracerType tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { Object newUserContext = tracer.getTracer().traceExecutionEvent(event, currentUserContext[tracer.ordinal()], params); currentUserContext[tracer.ordinal()] = newUserContext; } @@ -82,7 +82,7 @@ private Object[] getCurrentUserContext(Object userContext) { if (userContext != null && (userContext instanceof Object[])) { currentUserContext = (Object[]) userContext; } else { - currentUserContext = new Object[Tracer.values().length]; + currentUserContext = new Object[TracerType.values().length]; } return currentUserContext; } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java index 50f84221..4967da31 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -31,7 +31,7 @@ public class ObservabilityTraceEventListenerProvider implements TraceEventListen /** * System property used to enabled/disable tracers. The value of this system property should be a comma separated list - * of {@link Tracer} to enable. By default all tracers will be enabled. + * of {@link TracerType} to enable. By default all tracers will be enabled. */ private static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java index e0dd7202..5d0a391c 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -5,7 +5,7 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; -import oracle.jdbc.provider.observability.tracers.Tracer; +import oracle.jdbc.provider.observability.tracers.TracerType; /** * Implementation of {@link ObservabilityConfigurationMBean} that allows to @@ -29,7 +29,7 @@ private ObservabilityConfiguration() { } private boolean sensitiveDataEnabled; private String tracers; - private List enabledTracers = new ArrayList<>(); + private List enabledTracers = new ArrayList<>(); /** * Returns true if the provider is enabled, otherwise false. @@ -38,7 +38,12 @@ private ObservabilityConfiguration() { } */ @Override public boolean getEnabled() { - return enabled; + try { + observabilityConfiguraitonLock.lock(); + return enabled; + } finally { + observabilityConfiguraitonLock.unlock(); + } } /** @@ -48,7 +53,12 @@ public boolean getEnabled() { */ @Override public void setEnabled(boolean enabled) { - this.enabled = enabled; + try { + observabilityConfiguraitonLock.lock(); + this.enabled = enabled; + } finally { + observabilityConfiguraitonLock.unlock(); + } } /** @@ -65,7 +75,7 @@ public String getEnabledTracers() { } /** - * Enables the tracers. Available tracers are defined in enum {@link Tracer}. + * Enables the tracers. Available tracers are defined in enum {@link TracerType}. * * @param tracers comma separated list of enabled tracers. */ @@ -77,7 +87,7 @@ public void setEnabledTracers(String tracers) { String[] items = tracers.split(","); for (String item : items) { if (item != null) { - enabledTracers.add(Tracer.valueOf(item.toUpperCase())); + enabledTracers.add(TracerType.valueOf(item.toUpperCase())); } } this.tracers = enabledTracers.stream().map((item) -> item.toString()).collect(Collectors.joining(",")); @@ -92,7 +102,12 @@ public void setEnabledTracers(String tracers) { */ @Override public boolean getSensitiveDataEnabled() { - return sensitiveDataEnabled; + try { + observabilityConfiguraitonLock.lock(); + return sensitiveDataEnabled; + } finally { + observabilityConfiguraitonLock.unlock(); + } } /** @@ -102,7 +117,12 @@ public boolean getSensitiveDataEnabled() { */ @Override public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { - this.sensitiveDataEnabled = sensitiveDataEnabled; + try { + observabilityConfiguraitonLock.lock(); + this.sensitiveDataEnabled = sensitiveDataEnabled; + } finally { + observabilityConfiguraitonLock.unlock(); + } } /** @@ -110,15 +130,25 @@ public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { * @return the singleton instance of {@link ObservabilityConfiguration}. */ public static ObservabilityConfiguration getInstance() { - return INSTANCE; + try { + observabilityConfiguraitonLock.lock(); + return INSTANCE; + } finally { + observabilityConfiguraitonLock.unlock(); + } } /** - * Returns a list of enabled {@link Tracer}. - * @return then list of nabled {@link Tracer}. + * Returns a list of enabled {@link TracerType}. + * @return then list of nabled {@link TracerType}. */ - public List getEnabledTracersSet() { - return enabledTracers; + public List getEnabledTracersSet() { + try { + observabilityConfiguraitonLock.lock(); + return enabledTracers; + } finally { + observabilityConfiguraitonLock.unlock(); + } } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java index e67d7fb4..41457aaf 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java @@ -1,6 +1,6 @@ package oracle.jdbc.provider.observability.configuration; -import oracle.jdbc.provider.observability.tracers.Tracer; +import oracle.jdbc.provider.observability.tracers.TracerType; /** * MBean that allows to configure the Oracle JDBC Observability Provider. @@ -29,7 +29,7 @@ public interface ObservabilityConfigurationMBean { public String getEnabledTracers(); /** - * Enables the tracers. Available tracers are defined in enum {@link Tracer}. + * Enables the tracers. Available tracers are defined in enum {@link TracerType}. * * @param tracers comma separated list of enabled tracers. */ diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/TracerType.java similarity index 72% rename from ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracer.java rename to ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/TracerType.java index 3b74d2f2..fec5fea9 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/Tracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/TracerType.java @@ -1,10 +1,13 @@ package oracle.jdbc.provider.observability.tracers; +import oracle.jdbc.provider.observability.tracers.jfr.JFRTracer; +import oracle.jdbc.provider.observability.tracers.otel.OTelTracer; + /** * This interface defines the constants that are used to identify the * {@link ObservabilityTracer}. */ -public enum Tracer { +public enum TracerType { /** * Open Telemetry tracer. */ @@ -17,12 +20,12 @@ public enum Tracer { private ObservabilityTracer tracer; - Tracer(ObservabilityTracer tracer) { + TracerType(ObservabilityTracer tracer) { this.tracer = tracer; } /** - * Returns the {@link ObservabilityTracer} for this {@link Tracer}. + * Returns the {@link ObservabilityTracer} for this {@link TracerType}. * @return the {@link ObservabilityTracer}. */ public ObservabilityTracer getTracer() { diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java similarity index 99% rename from ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java rename to ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java index eb7977c0..bc73268d 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFREventFactory.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java @@ -1,4 +1,4 @@ -package oracle.jdbc.provider.observability.tracers; +package oracle.jdbc.provider.observability.tracers.jfr; import jdk.jfr.Event; import jdk.jfr.Label; @@ -10,7 +10,7 @@ import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.TraceContext; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.JFREventFactory.ExecutionEvent; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; /** * Factory class for creating JFR events depending on the database function. diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java similarity index 84% rename from ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java rename to ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java index ba233c56..f7f7ee64 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/JFRTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java @@ -1,10 +1,11 @@ -package oracle.jdbc.provider.observability.tracers; +package oracle.jdbc.provider.observability.tracers.jfr; import jdk.jfr.Event; import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.Sequence; import oracle.jdbc.TraceEventListener.TraceContext; -import oracle.jdbc.provider.observability.tracers.JFREventFactory.RoundTripEvent; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; +import oracle.jdbc.provider.observability.tracers.jfr.JFREventFactory.RoundTripEvent; /** * {@link ObservabilityTracer} for tracing Java Flight Recorder events. diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java similarity index 89% rename from ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java rename to ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index 8ba559c5..f2fe36ac 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -1,4 +1,4 @@ -package oracle.jdbc.provider.observability.tracers; +package oracle.jdbc.provider.observability.tracers.otel; import java.sql.SQLException; import java.time.Instant; @@ -23,6 +23,7 @@ import oracle.jdbc.TraceEventListener.Sequence; import oracle.jdbc.TraceEventListener.TraceContext; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; /** * Open Telemetry tracer. Exports round trip event and execution events to @@ -30,10 +31,22 @@ */ public class OTelTracer implements ObservabilityTracer { + /** + * Key used to send the current Open Telemetry Trace Context to the server + * using {@link TraceContext#setClientInfo(String, String)}. + */ private static final String TRACE_KEY = "clientcontext.ora$opentelem$tracectx"; + /** + * Open Telemetry tracer. + */ private Tracer tracer; + /** + * Logger. + */ + private static Logger logger = Logger.getLogger(OTelTracer.class.getName()); + /** * Constructor. Uses {@link GlobalOpenTelemetry} to get the tracer. */ @@ -50,11 +63,10 @@ public OTelTracer(Tracer tracer) { } - private static Logger logger = Logger.getLogger(OTelTracer.class.getName()); @Override public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext) { -if (sequence == Sequence.BEFORE) { + if (sequence == Sequence.BEFORE) { // Create the Span before the round-trip. final Span span = initAndGetSpan(traceContext, traceContext.databaseOperation()); try (Scope ignored = span.makeCurrent()) { @@ -71,7 +83,7 @@ public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Objec if (userContext instanceof Span) { final Span span = (Span) userContext; span.setStatus(traceContext.isCompletedExceptionally() ? StatusCode.ERROR : StatusCode.OK); - endSpan(span); + span.end(Instant.now()); } return null; } @@ -138,16 +150,17 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { .setAttribute("Actual SQL Text", traceContext.actualSqlText()); } - // Indicates that the span covers server-side handling of an RPC or other remote - // request. - return spanBuilder.setSpanKind(SpanKind.SERVER).startSpan(); - - } + // According to the semantic conventions the Span Kind should be CLIENT, + // used to be SERVER. + return spanBuilder.setSpanKind(SpanKind.CLIENT).startSpan(); - private void endSpan(Span span) { - span.end(Instant.now()); } + /** + * Builds the Open Telemetry trace context to be sent to the database server. + * @param span the currect spans + * @return the current trace context formatted so that the server can read it. + */ private String getTraceValue(Span span) { final String traceParent = initAndGetTraceParent(span); final String traceState = initAndGetTraceState(span); diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java index 75ba85d9..a62b152a 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.Tracer; +import oracle.jdbc.provider.observability.tracers.TracerType; import oracle.jdbc.spi.TraceEventListenerProvider; public class ObservabilityConfigurationTest { @@ -30,7 +30,7 @@ public void testConfiguration() throws Exception { assertEquals(true, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(Tracer.JFR, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + assertEquals(TracerType.JFR, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); // MBean ObjectName objectName = new ObjectName( @@ -52,8 +52,8 @@ public void testConfiguration() throws Exception { assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); assertEquals(2, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); - assertEquals(Tracer.JFR, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(1)); + assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + assertEquals(TracerType.JFR, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(1)); // Singleton ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); @@ -66,7 +66,7 @@ public void testConfiguration() throws Exception { assertEquals("true", sensitiveDataEnabled); assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); } diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java index edc9ae2c..2719e235 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test; import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.Tracer; +import oracle.jdbc.provider.observability.tracers.TracerType; import oracle.jdbc.spi.TraceEventListenerProvider; public class BackwardCompatibilityTest { @@ -30,7 +30,7 @@ public void testConfiguration() throws Exception { assertEquals(true, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); // MBean ObjectName objectName = new ObjectName( @@ -54,7 +54,7 @@ public void testConfiguration() throws Exception { assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); // Singleton ObservabilityConfiguration.getInstance().setEnabled(true); @@ -69,7 +69,7 @@ public void testConfiguration() throws Exception { assertEquals("true", sensitiveDataEnabled); assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(Tracer.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); } } From 8142aa0112098dc78b8b68c7ea9bd232953896d3 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 24 Feb 2025 11:03:28 +0100 Subject: [PATCH 12/82] Fixed tests --- ...servabilityTraceEventListenerProvider.java | 17 ---------- .../ObservabilityConfiguration.java | 32 ++++++++++++++----- .../tracers/otel/OTelTracer.java | 27 +++++----------- .../ObservabilityConfigurationTest.java | 10 +++--- 4 files changed, 38 insertions(+), 48 deletions(-) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java index 4967da31..482cae01 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -29,18 +29,6 @@ public class ObservabilityTraceEventListenerProvider implements TraceEventListen private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration"; private static ObjectName objectName; - /** - * System property used to enabled/disable tracers. The value of this system property should be a comma separated list - * of {@link TracerType} to enable. By default all tracers will be enabled. - */ - private static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; - - /** - * System property used to enable/disable exporting sensitive data. Set the property to true to enable sensitive data. - * By default exporting sensitive data is disabled. - */ - private static final String SENSITIVE_DATA_ENABLED = "oracle.jdbc.provider.observability.sensitiveDataEnabled"; - /** * Logger */ @@ -71,11 +59,6 @@ public ObservabilityTraceEventListenerProvider() { } public TraceEventListener getTraceEventListener(Map map) { try { if (!server.isRegistered(objectName)) { - String enabledTracers = System.getProperty(ENABLED_TRACERS, "OTEL,JFR"); - String sensitiveDataEnabled = System.getProperty(SENSITIVE_DATA_ENABLED, "false"); - ObservabilityConfiguration.getInstance().setEnabledTracers(enabledTracers); - ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(Boolean.parseBoolean(sensitiveDataEnabled)); - server.registerMBean(ObservabilityConfiguration.getInstance(), objectName); } } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java index 5d0a391c..8633dec8 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -13,17 +13,21 @@ */ public class ObservabilityConfiguration implements ObservabilityConfigurationMBean { - private static final ReentrantLock observabilityConfiguraitonLock = new ReentrantLock(); + /** + * System property used to enabled/disable tracers. The value of this system property should be a comma separated list + * of {@link TracerType} to enable. By default all tracers will be enabled. + */ + private static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; - private static final ObservabilityConfiguration INSTANCE; + /** + * System property used to enable/disable exporting sensitive data. Set the property to true to enable sensitive data. + * By default exporting sensitive data is disabled. + */ + private static final String SENSITIVE_DATA_ENABLED = "oracle.jdbc.provider.observability.sensitiveDataEnabled"; - static { - INSTANCE = new ObservabilityConfiguration(); - //INSTANCE.setSensitiveDataEnabled(true); - INSTANCE.setEnabledTracers(System.getProperty("oracle.jdbc.provider.observability.tracer", "OTEL,JFR")); - } + private static final ReentrantLock observabilityConfiguraitonLock = new ReentrantLock(); - private ObservabilityConfiguration() { } + private static final ObservabilityConfiguration INSTANCE; private boolean enabled = true; private boolean sensitiveDataEnabled; @@ -31,6 +35,18 @@ private ObservabilityConfiguration() { } private List enabledTracers = new ArrayList<>(); + + static { + INSTANCE = new ObservabilityConfiguration(); + } + + private ObservabilityConfiguration() { + String enabledTracers = System.getProperty(ENABLED_TRACERS, "OTEL,JFR"); + String sensitiveDataEnabled = System.getProperty(SENSITIVE_DATA_ENABLED, "false"); + setEnabledTracers(enabledTracers); + setSensitiveDataEnabled(Boolean.valueOf(sensitiveDataEnabled)); + } + /** * Returns true if the provider is enabled, otherwise false. * diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index f2fe36ac..3143c790 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -37,10 +37,7 @@ public class OTelTracer implements ObservabilityTracer { */ private static final String TRACE_KEY = "clientcontext.ora$opentelem$tracectx"; - /** - * Open Telemetry tracer. - */ - private Tracer tracer; + /** * Logger. @@ -48,21 +45,10 @@ public class OTelTracer implements ObservabilityTracer { private static Logger logger = Logger.getLogger(OTelTracer.class.getName()); /** - * Constructor. Uses {@link GlobalOpenTelemetry} to get the tracer. - */ - public OTelTracer() { - this(GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName())); - } - - /** - * Constructor. - * @param tracer Open Telemetry tracer. + * Constructor. This tracer always uses {@link GlobalOpenTelemetry} to get + * the Open Telemetry tracer. */ - public OTelTracer(Tracer tracer) { - this.tracer = tracer; - } - - + public OTelTracer() { } @Override public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext) { @@ -92,6 +78,7 @@ public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Objec @Override public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { if (EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + Tracer tracer = GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName()); if (event == TraceEventListener.JdbcExecutionEvent.VIP_RETRY) { SpanBuilder spanBuilder = tracer .spanBuilder(event.getDescription()) @@ -133,6 +120,7 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { * child span to the current span. I.e. the current span in context becomes * parent to this child span. */ + Tracer tracer = GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName()); SpanBuilder spanBuilder = tracer .spanBuilder(spanName) .setAttribute("thread.id", Thread.currentThread().getId()) @@ -146,7 +134,8 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { // Add sensitive information (URL and SQL) if it is enabled if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { logger.log(Level.FINEST, "Sensitive information on"); - spanBuilder.setAttribute("Original SQL Text", traceContext.originalSqlText()) + spanBuilder + .setAttribute("Original SQL Text", traceContext.originalSqlText()) .setAttribute("Actual SQL Text", traceContext.actualSqlText()); } diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java index a62b152a..68663c97 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java @@ -16,13 +16,15 @@ public class ObservabilityConfigurationTest { - @Test - public void testConfiguration() throws Exception { - - // System properties + // Set system properties before starting + static { System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "JFR"); System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "true"); + } + @Test + public void testConfiguration() throws Exception { + TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); provider.getTraceEventListener(null); From 01cfd1232f85de6928d443a894f8cf4a34edf6fb Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 24 Feb 2025 11:27:03 +0100 Subject: [PATCH 13/82] Observability configuration test --- .../configuration/ObservabilityConfiguration.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java index 8633dec8..a2423f13 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -27,7 +27,7 @@ public class ObservabilityConfiguration implements ObservabilityConfigurationMBe private static final ReentrantLock observabilityConfiguraitonLock = new ReentrantLock(); - private static final ObservabilityConfiguration INSTANCE; + private static final ObservabilityConfiguration INSTANCE = new ObservabilityConfiguration(); private boolean enabled = true; private boolean sensitiveDataEnabled; @@ -35,11 +35,6 @@ public class ObservabilityConfiguration implements ObservabilityConfigurationMBe private List enabledTracers = new ArrayList<>(); - - static { - INSTANCE = new ObservabilityConfiguration(); - } - private ObservabilityConfiguration() { String enabledTracers = System.getProperty(ENABLED_TRACERS, "OTEL,JFR"); String sensitiveDataEnabled = System.getProperty(SENSITIVE_DATA_ENABLED, "false"); From ad7df96a7d12607139f5aee68e78e7b715b1b6a4 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 24 Feb 2025 11:47:25 +0100 Subject: [PATCH 14/82] Added debug info --- .../observability/ObservabilityConfigurationTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java index 68663c97..7a23cdb0 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java @@ -20,14 +20,17 @@ public class ObservabilityConfigurationTest { static { System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "JFR"); System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "true"); + System.out.println(ObservabilityConfiguration.getInstance().getEnabledTracers()); } @Test public void testConfiguration() throws Exception { - + + System.out.println(ObservabilityConfiguration.getInstance().getEnabledTracers()); TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); provider.getTraceEventListener(null); + System.out.println(ObservabilityConfiguration.getInstance().getEnabledTracers()); assertEquals("JFR", ObservabilityConfiguration.getInstance().getEnabledTracers()); assertEquals(true, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); From ac08356493ecef89b2c10551b2816c94eb615dc5 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 24 Feb 2025 12:19:56 +0100 Subject: [PATCH 15/82] Run tests one by one --- ojdbc-provider-observability/pom.xml | 112 +++++++++++++++------------ 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index fe07046e..06671f9b 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -1,58 +1,68 @@ - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - com.oracle.database.jdbc - ojdbc-extensions - 1.0.3 - - - Oracle JDBC Observability Provider - com.oracle.database.jdbc - ojdbc-provider-observability - 1.0.3 + + com.oracle.database.jdbc + ojdbc-extensions + 1.0.3 + - - 1.44.1 - 11 - 11 - + Oracle JDBC Observability Provider + com.oracle.database.jdbc + ojdbc-provider-observability + 1.0.3 - - - com.oracle.database.jdbc - ojdbc11 - - - io.opentelemetry - opentelemetry-api - ${opentelemetry.version} - - - org.mockito - mockito-core - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter-params - test - - - ojdbc-provider-common - com.oracle.database.jdbc - 1.0.2 - tests - test-jar - - + + 1.44.1 + 11 + 11 + + + + com.oracle.database.jdbc + ojdbc11 + + + io.opentelemetry + opentelemetry-api + ${opentelemetry.version} + + + org.mockito + mockito-core + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + ojdbc-provider-common + com.oracle.database.jdbc + 1.0.2 + tests + test-jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + none + + + + \ No newline at end of file From 8478b83607c09e1cde7b28e1ed5ddc464c9d705e Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 24 Feb 2025 12:31:46 +0100 Subject: [PATCH 16/82] Test Order --- ojdbc-provider-observability/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 06671f9b..0050aff8 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -61,6 +61,7 @@ maven-surefire-plugin none + alphabetical From 6b03e6ff2c204cec69f205b64f10fe7c324ef743 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 24 Feb 2025 12:37:50 +0100 Subject: [PATCH 17/82] Added comment for test run configuration. --- ojdbc-provider-observability/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 0050aff8..ead8150e 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -60,6 +60,12 @@ org.apache.maven.plugins maven-surefire-plugin + none alphabetical From c8a8cdf2d934a21c70918da0e6856aaf58d1d71f Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 24 Feb 2025 15:20:24 +0100 Subject: [PATCH 18/82] Backward compatibility --- .../services/oracle.jdbc.spi.TraceEventListenerProvider | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider index b1c86eef..9b12f75b 100644 --- a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider +++ b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider @@ -1,2 +1,2 @@ oracle.jdbc.provider.observability.ObservabilityTraceEventListenerProvider -oracle.jdbc.provider.observability.OpenTelemetryTraceEventListenerProvider \ No newline at end of file +oracle.jdbc.provider.opentelemetry.OpenTelemetryTraceEventListenerProvider \ No newline at end of file From dee07b4ca9426a9582c48bffc017af42a2513838 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Tue, 25 Feb 2025 18:35:52 +0100 Subject: [PATCH 19/82] Locks and event labels --- .../ObservabilityConfiguration.java | 16 +- .../tracers/jfr/JFREventFactory.java | 146 +++++++++--------- 2 files changed, 81 insertions(+), 81 deletions(-) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java index a2423f13..4e4e7b27 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java @@ -49,8 +49,8 @@ private ObservabilityConfiguration() { */ @Override public boolean getEnabled() { + observabilityConfiguraitonLock.lock(); try { - observabilityConfiguraitonLock.lock(); return enabled; } finally { observabilityConfiguraitonLock.unlock(); @@ -64,8 +64,8 @@ public boolean getEnabled() { */ @Override public void setEnabled(boolean enabled) { + observabilityConfiguraitonLock.lock(); try { - observabilityConfiguraitonLock.lock(); this.enabled = enabled; } finally { observabilityConfiguraitonLock.unlock(); @@ -77,8 +77,8 @@ public void setEnabled(boolean enabled) { */ @Override public String getEnabledTracers() { + observabilityConfiguraitonLock.lock(); try { - observabilityConfiguraitonLock.lock(); return tracers; } finally { observabilityConfiguraitonLock.unlock(); @@ -92,8 +92,8 @@ public String getEnabledTracers() { */ @Override public void setEnabledTracers(String tracers) { + observabilityConfiguraitonLock.lock(); try { - observabilityConfiguraitonLock.lock(); enabledTracers.clear(); String[] items = tracers.split(","); for (String item : items) { @@ -113,8 +113,8 @@ public void setEnabledTracers(String tracers) { */ @Override public boolean getSensitiveDataEnabled() { + observabilityConfiguraitonLock.lock(); try { - observabilityConfiguraitonLock.lock(); return sensitiveDataEnabled; } finally { observabilityConfiguraitonLock.unlock(); @@ -128,8 +128,8 @@ public boolean getSensitiveDataEnabled() { */ @Override public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { + observabilityConfiguraitonLock.lock(); try { - observabilityConfiguraitonLock.lock(); this.sensitiveDataEnabled = sensitiveDataEnabled; } finally { observabilityConfiguraitonLock.unlock(); @@ -141,8 +141,8 @@ public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { * @return the singleton instance of {@link ObservabilityConfiguration}. */ public static ObservabilityConfiguration getInstance() { + observabilityConfiguraitonLock.lock(); try { - observabilityConfiguraitonLock.lock(); return INSTANCE; } finally { observabilityConfiguraitonLock.unlock(); @@ -154,8 +154,8 @@ public static ObservabilityConfiguration getInstance() { * @return then list of nabled {@link TracerType}. */ public List getEnabledTracersSet() { + observabilityConfiguraitonLock.lock(); try { - observabilityConfiguraitonLock.lock(); return enabledTracers; } finally { observabilityConfiguraitonLock.unlock(); diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java index bc73268d..24158877 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java @@ -206,7 +206,7 @@ public static Event createExecutionEvent(JdbcExecutionEvent event, Object... par // Round-trip events @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_12C_EMON_DEQUEUE") - @Label("Round trip") + @Label("AQ 12c emon dequeue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuing12cEminDequeueEvent extends RoundTripEvent{ public AdvancedQueuing12cEminDequeueEvent(TraceContext traceContext) { @@ -215,7 +215,7 @@ public AdvancedQueuing12cEminDequeueEvent(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_ARRAY_ENQUEUE_DEQUEUE") - @Label("Round trip") + @Label("AQ Array Enqueue/Dequeue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingArrayEnqueueDequeue extends RoundTripEvent{ public AdvancedQueuingArrayEnqueueDequeue(TraceContext traceContext) { @@ -224,7 +224,7 @@ public AdvancedQueuingArrayEnqueueDequeue(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_DEQUEUE_V8") - @Label("Round trip") + @Label("AQ Dequeue before 8.1") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingDequeueV8 extends RoundTripEvent{ public AdvancedQueuingDequeueV8(TraceContext traceContext) { @@ -233,7 +233,7 @@ public AdvancedQueuingDequeueV8(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_ENQUEUE") - @Label("Round trip") + @Label("AQ EnQueue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingEnqueue extends RoundTripEvent{ public AdvancedQueuingEnqueue(TraceContext traceContext) { @@ -242,7 +242,7 @@ public AdvancedQueuingEnqueue(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_GET_PROPAGATION_STATUS") - @Label("Round trip") + @Label("AQ get propagation status entries") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingGetPropagationStatus extends RoundTripEvent{ public AdvancedQueuingGetPropagationStatus(TraceContext traceContext) { @@ -251,7 +251,7 @@ public AdvancedQueuingGetPropagationStatus(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_LISTEN") - @Label("Round trip") + @Label("AQ Listen") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingListen extends RoundTripEvent{ public AdvancedQueuingListen(TraceContext traceContext) { @@ -260,7 +260,7 @@ public AdvancedQueuingListen(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SESSION_GET_RPC_1") - @Label("Round trip") + @Label("Session get RPC in server pool scenario") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingSessionGetRPC1 extends RoundTripEvent{ public AdvancedQueuingSessionGetRPC1(TraceContext traceContext) { @@ -269,7 +269,7 @@ public AdvancedQueuingSessionGetRPC1(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SESSION_GET_RPC_2") - @Label("Round trip") + @Label("Session get RPC in server pool scenario") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingSessionGetRPC2 extends RoundTripEvent{ public AdvancedQueuingSessionGetRPC2(TraceContext traceContext) { @@ -278,7 +278,7 @@ public AdvancedQueuingSessionGetRPC2(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SHARED_DEQUEUE") - @Label("Round trip") + @Label("AQ Sharded dequeue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingSharedDequeue extends RoundTripEvent{ public AdvancedQueuingSharedDequeue(TraceContext traceContext) { @@ -287,7 +287,7 @@ public AdvancedQueuingSharedDequeue(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SHARED_ENQUEUE") - @Label("Round trip") + @Label("AQ Sharded enqueue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingSharedEnqueue extends RoundTripEvent{ public AdvancedQueuingSharedEnqueue(TraceContext traceContext) { @@ -296,7 +296,7 @@ public AdvancedQueuingSharedEnqueue(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.APP_REPLAY") - @Label("Round trip") + @Label("Application continuity REPLAY") @Category({"Oracle JDBC", "Round trips"}) static class AppReplay extends RoundTripEvent{ public AppReplay(TraceContext traceContext) { @@ -305,7 +305,7 @@ public AppReplay(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.AUTH_CALL") - @Label("Round trip") + @Label("Generic authentication call") @Category({"Oracle JDBC", "Round trips"}) static class AuthCall extends RoundTripEvent{ public AuthCall(TraceContext traceContext) { @@ -314,7 +314,7 @@ public AuthCall(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.AUTO_COMMIT_OFF") - @Label("Round trip") + @Label("Auto commit off") @Category({"Oracle JDBC", "Round trips"}) static class AutoCommitOff extends RoundTripEvent{ public AutoCommitOff(TraceContext traceContext) { @@ -323,7 +323,7 @@ public AutoCommitOff(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.AUTO_COMMIT_ON") - @Label("Round trip") + @Label("Auto commit on") @Category({"Oracle JDBC", "Round trips"}) static class AutoCommitOn extends RoundTripEvent{ public AutoCommitOn(TraceContext traceContext) { @@ -332,7 +332,7 @@ public AutoCommitOn(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.CANCEL_ALL") - @Label("Round trip") + @Label("Cancel All") @Category({"Oracle JDBC", "Round trips"}) static class CancelAll extends RoundTripEvent{ public CancelAll(TraceContext traceContext) { @@ -341,7 +341,7 @@ public CancelAll(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.CANCEL_OPERATION") - @Label("Round trip") + @Label("Cancel the current operation") @Category({"Oracle JDBC", "Round trips"}) static class CancelOperation extends RoundTripEvent{ public CancelOperation(TraceContext traceContext) { @@ -350,7 +350,7 @@ public CancelOperation(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.CHUNCK_INFO") - @Label("Round trip") + @Label("Chunk info RPC") @Category({"Oracle JDBC", "Round trips"}) static class ChunkInfo extends RoundTripEvent{ public ChunkInfo(TraceContext traceContext) { @@ -359,7 +359,7 @@ public ChunkInfo(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_FEATURES") - @Label("Round trip") + @Label("Client features") @Category({"Oracle JDBC", "Round trips"}) static class ClientFeatures extends RoundTripEvent{ public ClientFeatures(TraceContext traceContext) { @@ -368,7 +368,7 @@ public ClientFeatures(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_QUERY_CACHE_IDS") - @Label("Round trip") + @Label("Client query cache IDs") @Category({"Oracle JDBC", "Round trips"}) static class ClientQueryCacheIds extends RoundTripEvent{ public ClientQueryCacheIds(TraceContext traceContext) { @@ -377,7 +377,7 @@ public ClientQueryCacheIds(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_QUERY_CACHE_STATS_UPDATE") - @Label("Round trip") + @Label("Client query cache statistics update") @Category({"Oracle JDBC", "Round trips"}) static class ClientQueryCacheStatsUpdate extends RoundTripEvent{ public ClientQueryCacheStatsUpdate(TraceContext traceContext) { @@ -386,7 +386,7 @@ public ClientQueryCacheStatsUpdate(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.CLOSE_ALL_CURSOR") - @Label("Round trip") + @Label("Cursor close all") @Category({"Oracle JDBC", "Round trips"}) static class CloseAllCursor extends RoundTripEvent{ public CloseAllCursor(TraceContext traceContext) { @@ -395,7 +395,7 @@ public CloseAllCursor(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.CLOSE_CURSOR") - @Label("Round trip") + @Label("Close a cursor") @Category({"Oracle JDBC", "Round trips"}) static class CloseCursor extends RoundTripEvent{ public CloseCursor(TraceContext traceContext) { @@ -404,7 +404,7 @@ public CloseCursor(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.COMMIT") - @Label("Round trip") + @Label("Commit") @Category({"Oracle JDBC", "Round trips"}) static class Commit extends RoundTripEvent{ public Commit(TraceContext traceContext) { @@ -413,7 +413,7 @@ public Commit(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DB12C_NOTIFICATION_RCV") - @Label("Round trip") + @Label("12c notification receive") @Category({"Oracle JDBC", "Round trips"}) static class DB12cNotificationRCV extends RoundTripEvent{ public DB12cNotificationRCV(TraceContext traceContext) { @@ -422,7 +422,7 @@ public DB12cNotificationRCV(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DBNS_SAGAS") - @Label("Round trip") + @Label("DBMS Sagas") @Category({"Oracle JDBC", "Round trips"}) static class DBNSSagas extends RoundTripEvent{ public DBNSSagas(TraceContext traceContext) { @@ -431,7 +431,7 @@ public DBNSSagas(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_ANY_V8") - @Label("Round trip") + @Label("V8 Describe Any") @Category({"Oracle JDBC", "Round trips"}) static class DescribeAnyV8 extends RoundTripEvent{ public DescribeAnyV8(TraceContext traceContext) { @@ -440,7 +440,7 @@ public DescribeAnyV8(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_ARRAY") - @Label("Round trip") + @Label("Array describe") @Category({"Oracle JDBC", "Round trips"}) static class DescribeArray extends RoundTripEvent{ public DescribeArray(TraceContext traceContext) { @@ -449,7 +449,7 @@ public DescribeArray(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_QUERY_CALL") - @Label("Round trip") + @Label("New describe query call") @Category({"Oracle JDBC", "Round trips"}) static class DescribeQueryCall extends RoundTripEvent{ public DescribeQueryCall(TraceContext traceContext) { @@ -458,7 +458,7 @@ public DescribeQueryCall(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_LOAD_STREAM") - @Label("Round trip") + @Label("Direct Path Load Stream") @Category({"Oracle JDBC", "Round trips"}) static class DirectPathLoadStream extends RoundTripEvent{ public DirectPathLoadStream(TraceContext traceContext) { @@ -467,7 +467,7 @@ public DirectPathLoadStream(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_MISC_OP") - @Label("Round trip") + @Label("Direct Path Misc Operations") @Category({"Oracle JDBC", "Round trips"}) static class DirectPathMISCOp extends RoundTripEvent{ public DirectPathMISCOp(TraceContext traceContext) { @@ -476,7 +476,7 @@ public DirectPathMISCOp(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_PREPARE") - @Label("Round trip") + @Label("Direct Path Prepare") @Category({"Oracle JDBC", "Round trips"}) static class DirectPathPrepare extends RoundTripEvent{ public DirectPathPrepare(TraceContext traceContext) { @@ -485,7 +485,7 @@ public DirectPathPrepare(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.DISTRIBUTED_TRANS_MGR_RPC") - @Label("Round trip") + @Label("Distributed transaction manager RPC") @Category({"Oracle JDBC", "Round trips"}) static class DistributedTransMGRRPC extends RoundTripEvent{ public DistributedTransMGRRPC(TraceContext traceContext) { @@ -494,7 +494,7 @@ public DistributedTransMGRRPC(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.EXECUTE_QUERY") - @Label("Round trip") + @Label("Execute query") @Category({"Oracle JDBC", "Round trips"}) static class ExecuteQuery extends RoundTripEvent{ public ExecuteQuery(TraceContext traceContext) { @@ -503,7 +503,7 @@ public ExecuteQuery(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_CREATE") - @Label("Round trip") + @Label("eXtensible Security Sessions Create Session") @Category({"Oracle JDBC", "Round trips"}) static class ExtensibleSecuritySessionCreate extends RoundTripEvent{ public ExtensibleSecuritySessionCreate(TraceContext traceContext) { @@ -512,7 +512,7 @@ public ExtensibleSecuritySessionCreate(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_PIGGYBACK") - @Label("Round trip") + @Label("eXtensible Security Sessions Piggyback") @Category({"Oracle JDBC", "Round trips"}) static class ExtensibleSecuritySessionPiggyback extends RoundTripEvent{ public ExtensibleSecuritySessionPiggyback(TraceContext traceContext) { @@ -521,7 +521,7 @@ public ExtensibleSecuritySessionPiggyback(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_ROUNDTRIP") - @Label("Round trip") + @Label("eXtensible Security Session Roundtrip") @Category({"Oracle JDBC", "Round trips"}) static class ExtensibleSecuritySessionRoundtrip extends RoundTripEvent{ public ExtensibleSecuritySessionRoundtrip(TraceContext traceContext) { @@ -530,7 +530,7 @@ public ExtensibleSecuritySessionRoundtrip(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.FAST_UPI_CALLS") - @Label("Round trip") + @Label("Fast UPI calls to opial7") @Category({"Oracle JDBC", "Round trips"}) static class FastUPICalls extends RoundTripEvent{ public FastUPICalls(TraceContext traceContext) { @@ -539,7 +539,7 @@ public FastUPICalls(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.FETCH_ROW") - @Label("Round trip") + @Label("Fetch a row") @Category({"Oracle JDBC", "Round trips"}) static class FetchRow extends RoundTripEvent{ public FetchRow(TraceContext traceContext) { @@ -548,7 +548,7 @@ public FetchRow(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.GET_VERSION") - @Label("Round trip") + @Label("Get Oracle version-date string in new format") @Category({"Oracle JDBC", "Round trips"}) static class GetVersion extends RoundTripEvent{ public GetVersion(TraceContext traceContext) { @@ -557,7 +557,7 @@ public GetVersion(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.KERNEL_PROGRAMMATIC_NOTIFICATION") - @Label("Round trip") + @Label("Kernel Programmatic Notification") @Category({"Oracle JDBC", "Round trips"}) static class KernelProgrammaticNotification extends RoundTripEvent{ public KernelProgrammaticNotification(TraceContext traceContext) { @@ -566,7 +566,7 @@ public KernelProgrammaticNotification(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.KEY_VALUE") - @Label("Round trip") + @Label("Client app context, namespace, attribute, values") @Category({"Oracle JDBC", "Round trips"}) static class KeyValue extends RoundTripEvent{ public KeyValue(TraceContext traceContext) { @@ -575,7 +575,7 @@ public KeyValue(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.LOB_FILE_CALL") - @Label("Round trip") + @Label("LOB and FILE related calls") @Category({"Oracle JDBC", "Round trips"}) static class LOBFileCall extends RoundTripEvent{ public LOBFileCall(TraceContext traceContext) { @@ -584,7 +584,7 @@ public LOBFileCall(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.LOGOFF") - @Label("Round trip") + @Label("Logoff of Oracle") @Category({"Oracle JDBC", "Round trips"}) static class LogOff extends RoundTripEvent{ public LogOff(TraceContext traceContext) { @@ -593,7 +593,7 @@ public LogOff(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.LOGON_CHALLENGE_RESPONSE_1") - @Label("Round trip") + @Label("First half of challenge-response logon") @Category({"Oracle JDBC", "Round trips"}) static class LogonChallengeResponse1 extends RoundTripEvent{ public LogonChallengeResponse1(TraceContext traceContext) { @@ -602,7 +602,7 @@ public LogonChallengeResponse1(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.LOGON_CHALLENGE_RESPONSE_2") - @Label("Round trip") + @Label("Second half of challenge-response logon") @Category({"Oracle JDBC", "Round trips"}) static class LogonChallengeResponse2 extends RoundTripEvent{ public LogonChallengeResponse2(TraceContext traceContext) { @@ -611,7 +611,7 @@ public LogonChallengeResponse2(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.OEXFEN") - @Label("Round trip") + @Label("OEXFEN") @Category({"Oracle JDBC", "Round trips"}) static class OEXFEN extends RoundTripEvent{ public OEXFEN(TraceContext traceContext) { @@ -620,7 +620,7 @@ public OEXFEN(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.OPEN_CURSOR") - @Label("Round trip") + @Label("Open a cursor") @Category({"Oracle JDBC", "Round trips"}) static class OpenCursor extends RoundTripEvent{ public OpenCursor(TraceContext traceContext) { @@ -629,7 +629,7 @@ public OpenCursor(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.OSQL7") - @Label("Round trip") + @Label("OSQL7") @Category({"Oracle JDBC", "Round trips"}) static class OSQL7 extends RoundTripEvent{ public OSQL7(TraceContext traceContext) { @@ -638,7 +638,7 @@ public OSQL7(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.OSTART") - @Label("Round trip") + @Label("Starts Oracle") @Category({"Oracle JDBC", "Round trips"}) static class OStart extends RoundTripEvent{ public OStart(TraceContext traceContext) { @@ -647,7 +647,7 @@ public OStart(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.OSTOP") - @Label("Round trip") + @Label("Stops Oracle") @Category({"Oracle JDBC", "Round trips"}) static class OStop extends RoundTripEvent{ public OStop(TraceContext traceContext) { @@ -656,7 +656,7 @@ public OStop(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.PARAMETER_PUT_SPFILE") - @Label("Round trip") + @Label("Put parameter using spfile (for startup)") @Category({"Oracle JDBC", "Round trips"}) static class ParameterPutSPFile extends RoundTripEvent{ public ParameterPutSPFile(TraceContext traceContext) { @@ -665,7 +665,7 @@ public ParameterPutSPFile(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.PING") - @Label("Round trip") + @Label("Ping") @Category({"Oracle JDBC", "Round trips"}) static class Ping extends RoundTripEvent{ public Ping(TraceContext traceContext) { @@ -674,7 +674,7 @@ public Ping(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_END") - @Label("Round trip") + @Label("Pipeline End") @Category({"Oracle JDBC", "Round trips"}) static class PipelineEnd extends RoundTripEvent{ public PipelineEnd(TraceContext traceContext) { @@ -683,7 +683,7 @@ public PipelineEnd(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_PIGGYBACK_BEGIN") - @Label("Round trip") + @Label("Pipeline Begin Piggyback") @Category({"Oracle JDBC", "Round trips"}) static class PipelinePiggybackBegin extends RoundTripEvent{ public PipelinePiggybackBegin(TraceContext traceContext) { @@ -692,7 +692,7 @@ public PipelinePiggybackBegin(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_PIGGYBACK_OP") - @Label("Round trip") + @Label("Pipeline Operation Piggyback") @Category({"Oracle JDBC", "Round trips"}) static class PipelinePiggybackOp extends RoundTripEvent{ public PipelinePiggybackOp(TraceContext traceContext) { @@ -701,7 +701,7 @@ public PipelinePiggybackOp(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.ROLLBACK") - @Label("Round trip") + @Label("Rollback") @Category({"Oracle JDBC", "Round trips"}) static class Rollback extends RoundTripEvent{ public Rollback(TraceContext traceContext) { @@ -710,7 +710,7 @@ public Rollback(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_KEY") - @Label("Round trip") + @Label("Get the session key") @Category({"Oracle JDBC", "Round trips"}) static class SessionKey extends RoundTripEvent{ public SessionKey(TraceContext traceContext) { @@ -719,7 +719,7 @@ public SessionKey(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_STATE_OPS") - @Label("Round trip") + @Label("Session state ops") @Category({"Oracle JDBC", "Round trips"}) static class SessionStateOps extends RoundTripEvent{ public SessionStateOps(TraceContext traceContext) { @@ -728,7 +728,7 @@ public SessionStateOps(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_STATE_TEMPLATE") - @Label("Round trip") + @Label("Session state template") @Category({"Oracle JDBC", "Round trips"}) static class SessionStateTemplate extends RoundTripEvent{ public SessionStateTemplate(TraceContext traceContext) { @@ -737,7 +737,7 @@ public SessionStateTemplate(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_SWITCH_V8") - @Label("Round trip") + @Label("V8 session switching piggyback") @Category({"Oracle JDBC", "Round trips"}) static class SessionSwitchV8 extends RoundTripEvent{ public SessionSwitchV8(TraceContext traceContext) { @@ -746,7 +746,7 @@ public SessionSwitchV8(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.TRACING_MESSAGE") - @Label("Round trip") + @Label("End to end tracing message") @Category({"Oracle JDBC", "Round trips"}) static class TracingMessage extends RoundTripEvent{ public TracingMessage(TraceContext traceContext) { @@ -755,7 +755,7 @@ public TracingMessage(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.TRANSACTION_COMMIT") - @Label("Round trip") + @Label("Transaction commit, rollback, recover") @Category({"Oracle JDBC", "Round trips"}) static class TransactionCommit extends RoundTripEvent{ public TransactionCommit(TraceContext traceContext) { @@ -764,7 +764,7 @@ public TransactionCommit(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.TRANSACTION_START") - @Label("Round trip") + @Label("Transaction start, attach, detach") @Category({"Oracle JDBC", "Round trips"}) static class TransactionStart extends RoundTripEvent{ public TransactionStart(TraceContext traceContext) { @@ -773,7 +773,7 @@ public TransactionStart(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.TTC_DTY_ROUNDTRIP") - @Label("Round trip") + @Label("Data type message exchange") @Category({"Oracle JDBC", "Round trips"}) static class TTCDTYRoundtrip extends RoundTripEvent{ public TTCDTYRoundtrip(TraceContext traceContext) { @@ -782,7 +782,7 @@ public TTCDTYRoundtrip(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.TTC_PRO_ROUNDTRIP") - @Label("Round trip") + @Label("Protocol negotiation message exchange") @Category({"Oracle JDBC", "Round trips"}) static class TTCPRORoundtrip extends RoundTripEvent{ public TTCPRORoundtrip(TraceContext traceContext) { @@ -791,7 +791,7 @@ public TTCPRORoundtrip(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.XS_ATTACH_SESSION") - @Label("Round trip") + @Label("XS Attach Session") @Category({"Oracle JDBC", "Round trips"}) static class XSAttachSession extends RoundTripEvent{ public XSAttachSession(TraceContext traceContext) { @@ -800,7 +800,7 @@ public XSAttachSession(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.XS_CREATE_SESSION") - @Label("Round trip") + @Label("XS Create Session") @Category({"Oracle JDBC", "Round trips"}) static class XSCreateSession extends RoundTripEvent{ public XSCreateSession(TraceContext traceContext) { @@ -809,7 +809,7 @@ public XSCreateSession(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.XS_DESTROY_SESSION") - @Label("Round trip") + @Label("XS Destroy Session") @Category({"Oracle JDBC", "Round trips"}) static class XSDestroySession extends RoundTripEvent{ public XSDestroySession(TraceContext traceContext) { @@ -818,7 +818,7 @@ public XSDestroySession(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.XS_DETACH_SESSION") - @Label("Round trip") + @Label("XS Detach Session") @Category({"Oracle JDBC", "Round trips"}) static class XSDetachSession extends RoundTripEvent{ public XSDetachSession(TraceContext traceContext) { @@ -827,7 +827,7 @@ public XSDetachSession(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.XS_NAMESPACE_OP") - @Label("Round trip") + @Label("XS Namespace OP") @Category({"Oracle JDBC", "Round trips"}) static class XSNamespaceOp extends RoundTripEvent{ public XSNamespaceOp(TraceContext traceContext) { @@ -836,7 +836,7 @@ public XSNamespaceOp(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.XS_NAMESPACE_OPS") - @Label("Round trip") + @Label("XS namespace OPs") @Category({"Oracle JDBC", "Round trips"}) static class XSNamespaceOps extends RoundTripEvent{ public XSNamespaceOps(TraceContext traceContext) { @@ -845,7 +845,7 @@ public XSNamespaceOps(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.XS_SET_SESSION_PARAMETER") - @Label("Round trip") + @Label("XS Set Session Parameter") @Category({"Oracle JDBC", "Round trips"}) static class XSSetSessionParameter extends RoundTripEvent{ public XSSetSessionParameter(TraceContext traceContext) { @@ -854,7 +854,7 @@ public XSSetSessionParameter(TraceContext traceContext) { } @Name("oracle.jdbc.provider.observability.RoundTrip.XS_STATE_SYNC_OP") - @Label("Round trip") + @Label("XS State Sync OP") @Category({"Oracle JDBC", "Round trips"}) static class XSStateSyncOp extends RoundTripEvent{ public XSStateSyncOp(TraceContext traceContext) { From c649e571ba3f8ba72e52739b869c669b52c62493 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Fri, 7 Mar 2025 09:29:57 +0100 Subject: [PATCH 20/82] Refactoring and added possibility to have different configuration and listeners --- ojdbc-provider-observability/README.md | 27 +- .../ObservabilityConfiguration.java | 333 ++++++++++++ .../ObservabilityConfigurationMBean.java | 96 ++++ .../ObservabilityTraceEventListener.java | 254 +++++++-- ...servabilityTraceEventListenerProvider.java | 93 ++-- ...enTelemetryTraceEventListenerProvider.java | 78 +++ .../ObservabilityConfiguration.java | 166 ------ .../ObservabilityConfigurationMBean.java | 51 -- .../tracers/ObservabilityTracer.java | 47 +- .../observability/tracers/TracerType.java | 34 -- .../tracers/jfr/JFREventFactory.java | 502 ++++++++++-------- .../observability/tracers/jfr/JFRTracer.java | 68 ++- .../tracers/otel/OTelTracer.java | 150 ++++-- ...enTelemetryTraceEventListenerProvider.java | 102 ---- ...oracle.jdbc.spi.TraceEventListenerProvider | 2 +- .../BackwardCompatibilityTest.java | 120 +++++ .../ObservabilityConfigurationTest.java | 108 ++-- .../ObservabilityTestProperties.java | 37 ++ .../ObservabilityTraceEventListenerTest.java | 63 ++- .../BackwardCompatibilityTest.java | 75 --- .../OpenTelemetryTraceEventListener.java | 12 +- .../OpenTelemetryTraceEventListenerTest.java | 8 +- 22 files changed, 1598 insertions(+), 828 deletions(-) create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/TracerType.java delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java create mode 100644 ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java delete mode 100644 ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java diff --git a/ojdbc-provider-observability/README.md b/ojdbc-provider-observability/README.md index fa942914..cc17d12f 100644 --- a/ojdbc-provider-observability/README.md +++ b/ojdbc-provider-observability/README.md @@ -9,10 +9,10 @@ This provider implements the TraceEventListener interface provided by the JDBC driver which will be notified whenever events are generated in the driver and will publish these events into Open Telemetry. These events include: * roundtrips to the database server - * AC begin and sucess + * AC begin and success * VIP down event -The following attributes are added the the traces for each event: +The following attributes are added the traces for each event: * **Roundtrips** * Connection ID * Database Operation @@ -39,7 +39,7 @@ The following attributes are added the the traces for each event: ## Installation This provider is distributed as single jar on the Maven Central Repository. The -jar is compiled for JDK 8, and is forward compatible with later JDK versions. +jar is compiled for JDK 11, and is forward compatible with later JDK versions. The coordinates for the latest release are: ```xml @@ -62,11 +62,6 @@ oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provi ## Configuration The provider can be configured by: -* using the singleton configuration class, -```java -ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL,JFR"); -ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(true); -``` * using system properties, ```java System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "OTEL,JFR"); @@ -74,12 +69,19 @@ System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "t ``` * or using the MBean. ```java -ObjectName objectName = new ObjectName( - "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration"); +ObservabilityTraceEventListener listener = ObservabilityTraceEventListener.getTraceEventListener(""); +ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); MBeanServer server = ManagementFactory.getPlatformMBeanServer(); server.setAttribute(objectName, new Attribute("EnabledTracers", "OTEL,JFR")); server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", "true")); ``` +* it is also possible to use the ObservabilityConfiguration object directly by +calling the +```java +ObservabilityConfiguration configuration = ObservabilityTraceEventListener.getObservabilityConfiguration(""); +configuration.setEnabledTracers("OTEL,JFR"); +configuration.setSensitiveDataEnabled(true); +``` ## Backward compatibility @@ -109,13 +111,12 @@ or a MBean. Two parameters can be configured: The system properties are "oracle.jdbc.provider.opentelemetry.enabled" and "oracle.jdbc.provider.opentelemetry.sensitive-enabled" respectively and the MBean -with object name "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener" exposes two attributes "Enabled" and "SensitiveDataEnabled". The sample code below shows how to retrieve the value of an attribute: ```java -ObjectName objectName = new ObjectName( - "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener"); +ObservabilityTraceEventListener listener = ObservabilityTraceEventListener.getTraceEventListener(""); +ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); MBeanServer server = ManagementFactory.getPlatformMBeanServer(); boolean isEnabled = Boolean.valueOf(server.getAttribute(objectName, "Enabled").toString()) .booleanValue(); diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java new file mode 100644 index 00000000..4f417b7d --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java @@ -0,0 +1,333 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; +import oracle.jdbc.provider.observability.tracers.jfr.JFRTracer; +import oracle.jdbc.provider.observability.tracers.otel.OTelTracer; + +/** + *

    + * Implementation of {@link ObservabilityConfigurationMBean} that allows to + * configure the Oracle JDBC Observability Provider. The system properties that + * can be used to configure the Observability Provider depend on the provider + * used: + *

    + * If {@link ObservabilityTraceEventListenerProvider} is being used: + *
      + *
    • {@link ObservabilityConfiguration#ENABLED_TRACERS}: comma separated list + * of enabled tracers, default "JFR,OTEL"
    • + *
    • {@link ObservabilityConfiguration#SENSITIVE_DATA_ENABLED}: true if + * sensitive data is enabled, default false
    • + *
    + * If {@link OpenTelemetryTraceEventListenerProvider} is being used: + *
      + *
    • {@link ObservabilityConfiguration#OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED}: + * true if OTEL tracer is enabled, otherwise false. Default true.
    • + *
    • {@link ObservabilityConfiguration#OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED}: + * true if sensitive data is enabled, default false
    • + *
    + */ +public class ObservabilityConfiguration implements ObservabilityConfigurationMBean { + + /** + *

    + * System property used to enabled/disable tracers. The value of this system property should be a comma separated list + * of tracers to enable. + *

    + *

    + * This extension implements two tracers: + *

    + *
      + *
    • OTEL: which exports traces to Open Telemetry {@link OTelTracer}
    • + *
    • JFR: which exports traces to Java Flight recorder {@link JFRTracer}
    • + *
    + *

    + * By default all tracers will be enabled. + */ + public static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; + + /** + * System property used to enable/disable exporting sensitive data. Set the property to true to enable sensitive data. + * By default exporting sensitive data is disabled. + */ + public static final String SENSITIVE_DATA_ENABLED = "oracle.jdbc.provider.observability.sensitiveDataEnabled"; + + /** + * This property is kept for backward compatibility. It is used to enable/disable the previous verison of the provider: + * "open-telemetry-trace-event-listener-provider". + */ + public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED = "oracle.jdbc.provider.opentelemetry.enabled"; + + /** + * This property is kept for backward compatibility. It allows to enable/disable sensitive data when using the previous + * version of the provider: "open-telemetry-trace-event-listener-provider". + */ + public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; + + /** + * Default values + */ + private static final String DEFAULT_ENABLED_TRACERS = "OTEL,JFR"; + private static final String DEFAULT_SENSITIVE_DATA_ENABLED = "false"; + private static final String DEFAULT_OPEN_TELEMETRY_ENABLED = "true"; + + /** + * Lock used to ensure that only one thread can access the configuration at a time. + */ + private static final ReentrantLock observabilityConfigurationLock = new ReentrantLock(); + + /** + * Indicates whether traces are enabled + */ + private boolean enabled = true; + + /** + * Indicates whether sensitive data is enabled + */ + private boolean sensitiveDataEnabled; + + /** + * List of enabled tracers + */ + private List enabledTracers = new ArrayList<>(); + + /** + * Maps registered tracer's name to its instance. + */ + Map registeredTracers = new HashMap<>(2, 1); + + + /** + * Types of configuration. For backward compatibility allows to use OTEL for + * Oracle JDBC Open Telemetry Provider configuration properties. + */ + public enum ObservabilityConfigurationType { + /** + * Use Oracle JDBC Open Telemetry Provider configuration properties + */ + OTEL, + /** + * USE Oracle JDBC Observability Provider configuration properties + */ + OBSERVABILITY + } + + /** + * Constructor + */ + public ObservabilityConfiguration() { + this(ObservabilityConfigurationType.OBSERVABILITY); + } + + /** + * Constructor used by {@link ObservabilityTraceEventListener} to create a configuration. + * + * @param configurationType indicates which system properties to use. When + * {@link ObservabilityConfigurationType#OTEL}, the previous + * verison of system properties are used. + */ + ObservabilityConfiguration(ObservabilityConfigurationType configurationType) { + String enabledTracers = DEFAULT_ENABLED_TRACERS; + String sensitiveDataEnabled = DEFAULT_SENSITIVE_DATA_ENABLED; + if (ObservabilityConfigurationType.OBSERVABILITY.equals(configurationType)) { + enabledTracers = System.getProperty(ENABLED_TRACERS, DEFAULT_ENABLED_TRACERS); + sensitiveDataEnabled = System.getProperty(SENSITIVE_DATA_ENABLED, DEFAULT_SENSITIVE_DATA_ENABLED); + } else { + String otelEnabled = System.getProperty(OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED, DEFAULT_OPEN_TELEMETRY_ENABLED); + if (otelEnabled != null) { + enabledTracers = "OTEL"; + this.enabled = Boolean.parseBoolean(otelEnabled); + } + String otelSensitiveDataEnabled = System.getProperty(OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, DEFAULT_SENSITIVE_DATA_ENABLED); + if(otelSensitiveDataEnabled != null) { + sensitiveDataEnabled = otelSensitiveDataEnabled; + } + } + + setEnabledTracers(enabledTracers); + setSensitiveDataEnabled(Boolean.parseBoolean(sensitiveDataEnabled)); + } + + /** + * Returns true if the provider is enabled, otherwise false. + * + * @return true if the provider is enabled, otherwise false. + */ + @Override + public boolean getEnabled() { + observabilityConfigurationLock.lock(); + try { + return enabled; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Enables/disables the provider. + * + * @param enabled true to enable the provider, otherwise false. + */ + @Override + public void setEnabled(boolean enabled) { + observabilityConfigurationLock.lock(); + try { + this.enabled = enabled; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Returns a comma separated list of enabled tracers. Not {@code null}. + */ + @Override + public String getEnabledTracers() { + observabilityConfigurationLock.lock(); + try { + return enabledTracers == null ? + "" : + enabledTracers.stream().collect(Collectors.joining(",")); + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Enables the tracers. + *

    + * This extension implements two tracers: + *

    + *
      + *
    • OTEL: which exports traces to Open Telemetry {@link OTelTracer}
    • + *
    • JFR: which exports traces to Java Flight recorder {@link JFRTracer}
    • + *
    + *

    + * Other tracer can be registered using the {@link ObservabilityConfiguration#registeredTracers} + * method. + *

    + * @param tracers comma separated list of enabled tracers. + */ + @Override + public void setEnabledTracers(String tracers){ + observabilityConfigurationLock.lock(); + try { + String[] items = tracers.replaceAll("\\s", tracers).split(","); + enabledTracers = Arrays.asList(items); + } finally { + observabilityConfigurationLock.unlock(); + } + + } + + /** + * Returns true if sensitive data is enabled, otherwise false. + * @return true if sensitive data is enabled, otherwise false. + */ + @Override + public boolean getSensitiveDataEnabled() { + observabilityConfigurationLock.lock(); + try { + return sensitiveDataEnabled; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Enables/disables sensitive data. + * + * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. + */ + @Override + public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { + observabilityConfigurationLock.lock(); + try { + this.sensitiveDataEnabled = sensitiveDataEnabled; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Returns a list of enabled tracers. + * @return then list of enabled tracers. + */ + public List getEnabledTracersAsList() { + observabilityConfigurationLock.lock(); + try { + return enabledTracers; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Returns the tracer registered with that name. + * @param tracerName the name of the tracer. + * @return returns the registered tracer that was registered using that name. + */ + public ObservabilityTracer getTracer(String tracerName) { + return registeredTracers.get(tracerName); + } + + /** + * Registeres a tracer. + * + * @param tracer the tracer to register + */ + public void registerTracer(ObservabilityTracer tracer) { + observabilityConfigurationLock.lock(); + try { + registeredTracers.put(tracer.getName(), tracer); + } finally { + observabilityConfigurationLock.unlock(); + } + } + + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java new file mode 100644 index 00000000..f516dafa --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java @@ -0,0 +1,96 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import oracle.jdbc.provider.observability.tracers.jfr.JFRTracer; +import oracle.jdbc.provider.observability.tracers.otel.OTelTracer; + +/** + * MBean that allows to configure the Oracle JDBC Observability Provider. + */ +public interface ObservabilityConfigurationMBean { + + /** + * Returns true if the provider is enabled, otherwise false. + * + * @return true if the provider is enabled, otherwise false. + */ + boolean getEnabled(); + + /** + * Enables/disables the provider. + * + * @param enabled true to enable the provider, otherwise false. + */ + void setEnabled(boolean enabled); + + /** + * Returns a comma separated list of enabled tracers. + * + * @return a comma separated list of enabled tracers. + */ + String getEnabledTracers(); + + /** + * Enables the tracers. + *

    + * This extension implements two tracers: + *

    + *
      + *
    • OTEL: which exports traces to Open Telemetry {@link OTelTracer}
    • + *
    • JFR: which exports traces to Java Flight recorder {@link JFRTracer}
    • + *
    + * + * @param tracers comma separated list of enabled tracers. + */ + void setEnabledTracers(String tracers); + + /** + * Returns true if sensitive data is enabled, otherwise false. + * + * @return true if sensitive data is enabled, otherwise false. + */ + boolean getSensitiveDataEnabled(); + + /** + * Enables/disables sensitive data. + * + * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. + */ + void setSensitiveDataEnabled(boolean sensitiveDataEnabled); +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java index 08484e6f..1032ef3c 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -1,14 +1,67 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability; +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; + import oracle.jdbc.TraceEventListener; -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.TracerType; +import oracle.jdbc.provider.observability.ObservabilityConfiguration.ObservabilityConfigurationType; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; +import oracle.jdbc.provider.observability.tracers.jfr.JFRTracer; +import oracle.jdbc.provider.observability.tracers.otel.OTelTracer; /** *

    - * TraceEventListener implementaiton that receives notifications whenever events + * TraceEventListener implementation that receives notifications whenever events * are generated in the driver and publishes these events different tracers - * depending on the configuraiton. + * depending on the configuration. *

    *

    * These events include: @@ -19,54 +72,145 @@ *

  • VIP down event
  • * *

    - * The available tracers are defined in the enumeration {@link TracerType}, and - * can be enabled using the method - * {@link ObservabilityConfiguration#setEnabledTracers(String)}. The method - * {@link ObservabilityConfiguration#setSensitiveDataEnabled(boolean)} allows - * to enabled/disable exporting sensitive data to the tracers. + * This extension implements two tracers: + *

    + *
      + *
    • OTEL: which exports traces to Open Telemetry
    • + *
    • JFR: which exports traces to Java Flight recorder
    • + *
    + *

    + * The {@link ObservabilityConfiguration} class allows to configure which tracers + * are enabled and whether sensitive data should be exported or not. *

    + *

    + * The {@link ObservabilityConfiguration} is a registered MBean the object name + * can be retrieved by calling {@link ObservabilityTraceEventListener#getMBeanObjectName()}. + * This MBean allows to configure the TraceEventListener by setting attributes. + * The following attributes are available: + *

    + *
      + *
    • EnabledTracers: comma separated list of tracers "OTEL,JFR" by + * default.
    • + *
    • SensitiveDataEnabled: enables/disables exporting sensiteve data + * (false by default)
    • + *
    */ public class ObservabilityTraceEventListener implements TraceEventListener { + /** + * MBean object name format + */ + private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration,name=%s"; + private static final String MBEAN_OBJECT_NAME_OLD = "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener,name=%s"; + + /** + * MBean server + */ + protected static final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); /** - * Private constructor. + * Logger */ - private ObservabilityTraceEventListener() { } + private static final Logger logger = Logger.getLogger( + ObservabilityTraceEventListener.class.getPackageName()); /** - * Singleton instance. + * Configuration for this instance of Trace Event Listener */ - private static final ObservabilityTraceEventListener INSTANCE = - new ObservabilityTraceEventListener(); + private final ObservabilityConfiguration configuration; + + + private final String mBeanObjectName; /** - * Returns the singleton instance of {@link ObservabilityTraceEventListener}. - * @return the singleton instance of {@link ObservabilityTraceEventListener}. + * Create a trace event listener identified by the given name. + * @param name the name of the trace event listener. + * @param configurationType configuration type for backward compatibility. */ - public static final ObservabilityTraceEventListener getInstance() { - return INSTANCE; + private ObservabilityTraceEventListener(String name, ObservabilityConfigurationType configurationType) { + // Create the configuration for this instance and register MBean + mBeanObjectName = ObservabilityConfigurationType.OTEL.equals(configurationType) ? + String.format(MBEAN_OBJECT_NAME_OLD, name) : + String.format(MBEAN_OBJECT_NAME, name); + this.configuration = new ObservabilityConfiguration(configurationType); + try { + final ObjectName objectName = new ObjectName(mBeanObjectName); + if (!server.isRegistered(objectName)) { + server.registerMBean(configuration, objectName); + logger.log(Level.FINEST, "MBean and tracers registered"); + } + } catch (InstanceAlreadyExistsException | MBeanRegistrationException | + NotCompliantMBeanException | MalformedObjectNameException e) { + logger.log(Level.WARNING, "Could not register MBean", e); + } + // Register known tracers + configuration.registerTracer(new OTelTracer(configuration)); + configuration.registerTracer(new JFRTracer(configuration)); } + /** + * Static map linking the name of the listener to its instance. + */ + private static final Map INSTANCES + = new ConcurrentHashMap<>(); + @Override + @SuppressWarnings("unchecked") public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { - if (!ObservabilityConfiguration.getInstance().getEnabled()) { return null;} - Object[] currentUserContext = getCurrentUserContext(userContext); - for (TracerType tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { - Object newUserContext = tracer.getTracer().traceRoundtrip(sequence, traceContext, currentUserContext[tracer.ordinal()]); - currentUserContext[tracer.ordinal()] = newUserContext; + if (!configuration.getEnabled()) { return null;} + + // Cast the userContext to the map this listener uses, or create a new one + // if it is being used for the first time. This is the return value of the + // method, and will be send back by the driver on the next event. + Map currentUserContext = userContext == null ? + new HashMap<>() : (Map)userContext; + + // loop through all the enabled tracers + for (String tracerName : configuration.getEnabledTracersAsList()) { + ObservabilityTracer tracer = configuration.getTracer(tracerName); + if (tracer != null) { + // call the tracer's round trip event with the tracer's context and store + // the new user context returned by the tracer in the user context map + Object newUserContext = tracer.traceRoundTrip(sequence, traceContext, currentUserContext.get(tracerName)); + currentUserContext.put(tracerName, newUserContext); + } else { + // the listener does not fail if the tracer is unknow, it is possible to + // enable a tracer and register it later + logger.log(Level.WARNING, "Could not find registered tracer with name: " + tracer); + } } + + // return the new user context return currentUserContext; } @Override + @SuppressWarnings("unchecked") public Object onExecutionEventReceived(JdbcExecutionEvent event, Object userContext, Object... params) { - if (!ObservabilityConfiguration.getInstance().getEnabled()) { return null;} - Object[] currentUserContext = getCurrentUserContext(userContext); - for (TracerType tracer : ObservabilityConfiguration.getInstance().getEnabledTracersSet()) { - Object newUserContext = tracer.getTracer().traceExecutionEvent(event, currentUserContext[tracer.ordinal()], params); - currentUserContext[tracer.ordinal()] = newUserContext; + if (!configuration.getEnabled()) { return null;} + + // Cast the userContext to the map this listener uses, or create a new one + // if it is being used for the first time. This is the return value of the + // method, and will be send back by the driver on the next event. + Map currentUserContext = userContext == null ? + new HashMap<>() : (Map)userContext; + + // loop through all the enabled tracers + for (String tracerName : configuration.getEnabledTracersAsList()) { + ObservabilityTracer tracer = configuration.getTracer(tracerName); + if (tracer != null) { + // call the tracer's execution event with the tracer's context and store + // the new user context returned by the tracer in the user context map + Object newUserContext = tracer.traceExecutionEvent(event, currentUserContext.get(tracerName), params); + currentUserContext.put(tracerName, newUserContext); + } else { + // the listener does not fail if the tracer is unknow, it is possible to + // enable a tracer and register it later + logger.log(Level.WARNING, "Could not find registered tracer with name: " + tracer); + } } + + // return the new user context return currentUserContext; } @@ -76,15 +220,53 @@ public boolean isDesiredEvent(JdbcExecutionEvent event) { return true; } - @SuppressWarnings("unchecked") - private Object[] getCurrentUserContext(Object userContext) { - Object[] currentUserContext; - if (userContext != null && (userContext instanceof Object[])) { - currentUserContext = (Object[]) userContext; - } else { - currentUserContext = new Object[TracerType.values().length]; + /** + * Returns the trace event listener identified by the given name. + * + * @param name the name of the listener. + * @return the trace event listener identified by the given name, or {@code + * null} if no trace event listener with that name was found. + */ + public static ObservabilityTraceEventListener getTraceEventListener(String name) { + return INSTANCES.get(name); + } + + /** + * Returns the configuration for a given listener. + * + * @param name the name of the listener. + * @return then configuration instance associated to that listener, or {@code + * null} if not trace event listener with that name was found. + */ + public static ObservabilityConfiguration getObservabilityConfiguration(String name) { + ObservabilityTraceEventListener listener = INSTANCES.get(name); + if (listener != null) { + return listener.configuration; } - return currentUserContext; + return null; + } + + /** + * Returns the MBean object name assiciated with the configuration of the + * listener. + * + * @return the MBean object name. + */ + public String getMBeanObjectName() { + return mBeanObjectName; + } + + /** + * Gets or creates an instance of {@link ObservabilityTraceEventListener} + * associated to the name. + * @param name the name of the listener instance. + * @param configurationType configuration type for backward compatibility. + * + * @return an instance of {@link ObservabilityTraceEventListener}. + */ + static ObservabilityTraceEventListener getOrCreateInstance(String name, + ObservabilityConfigurationType configurationType) { + return INSTANCES.computeIfAbsent(name, n -> new ObservabilityTraceEventListener(n, configurationType)); } } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java index 482cae01..af4f6f53 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -1,22 +1,52 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability; import java.lang.management.ManagementFactory; import java.util.Collection; import java.util.Collections; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; -import javax.management.Attribute; -import javax.management.InstanceAlreadyExistsException; -import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.NotCompliantMBeanException; -import javax.management.ObjectName; import oracle.jdbc.TraceEventListener; -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.ObservabilityConfiguration.ObservabilityConfigurationType; import oracle.jdbc.spi.TraceEventListenerProvider; /** @@ -25,46 +55,49 @@ */ public class ObservabilityTraceEventListenerProvider implements TraceEventListenerProvider { + /** + * Provider name + */ private static final String PROVIDER_NAME = "observability-trace-event-listener-provider"; - private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration"; - private static ObjectName objectName; /** - * Logger + * Name Parameter name, identifies the listener */ - private Logger logger = Logger.getLogger(ObservabilityTraceEventListenerProvider.class.getName()); + private static final String NAME_PARAMETER_NAME = "NAME"; + /** - * MBean server + * Name Parameter, identifies the listener */ - protected static final MBeanServer server = ManagementFactory.getPlatformMBeanServer();; + protected static final Parameter nameParameter = new Parameter() { + @Override + public boolean isSensitive() { + return false; + } - static { - try { - objectName = new ObjectName(MBEAN_OBJECT_NAME); - } catch (MalformedObjectNameException e) { - objectName = null; + @Override + public String name() { + return NAME_PARAMETER_NAME; } - } + + }; + /** * Constructs a new instance of ObservabilityTraceEventListenerProvider. This * constructor will be called by the driver's service provider to create a new * instance. */ - public ObservabilityTraceEventListenerProvider() { } + public ObservabilityTraceEventListenerProvider() { + + + } @Override public TraceEventListener getTraceEventListener(Map map) { - try { - if (!server.isRegistered(objectName)) { - server.registerMBean(ObservabilityConfiguration.getInstance(), objectName); - } - } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { - logger.log(Level.WARNING, "Could not register MBean", e); - } - return ObservabilityTraceEventListener.getInstance(); + String name = map.get(nameParameter).toString(); + return ObservabilityTraceEventListener.getOrCreateInstance(name, ObservabilityConfigurationType.OBSERVABILITY); } @Override @@ -74,7 +107,7 @@ public String getName() { @Override public Collection getParameters() { - return Collections.emptyList(); + return Collections.singletonList(nameParameter); } } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java new file mode 100644 index 00000000..3e3d3369 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java @@ -0,0 +1,78 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import java.util.Map; + +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.provider.observability.ObservabilityConfiguration.ObservabilityConfigurationType; + +/** + *

    + * This class implements the TraceEventListenerProvider interface exposed by the + * Oracle JDBC driver. It provides TraceEventListeners of type {@link + * oracle.jdbc.provider.observability.ObservabilityTraceEventListener}. + *

    + */ +public class OpenTelemetryTraceEventListenerProvider extends ObservabilityTraceEventListenerProvider { + + /** + * Name of the provider + */ + private static final String PROVIDER_NAME = "open-telemetry-trace-event-listener-provider"; + + /** + * Constructs a new instance of OpenTelemetryTraceEventListenerProvider. This + * constructor will be called by the driver's service provider to create a new + * instance. + */ + public OpenTelemetryTraceEventListenerProvider() { } + + @Override + public String getName() { + return PROVIDER_NAME; + } + + @Override + public TraceEventListener getTraceEventListener(Map map) { + String name = map.get(nameParameter).toString(); + return ObservabilityTraceEventListener.getOrCreateInstance(name, + ObservabilityConfigurationType.OTEL); + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java deleted file mode 100644 index 4e4e7b27..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfiguration.java +++ /dev/null @@ -1,166 +0,0 @@ -package oracle.jdbc.provider.observability.configuration; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; - -import oracle.jdbc.provider.observability.tracers.TracerType; - -/** - * Implementation of {@link ObservabilityConfigurationMBean} that allows to - * configure the Oracle JDBC Observability Provider. - */ -public class ObservabilityConfiguration implements ObservabilityConfigurationMBean { - - /** - * System property used to enabled/disable tracers. The value of this system property should be a comma separated list - * of {@link TracerType} to enable. By default all tracers will be enabled. - */ - private static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; - - /** - * System property used to enable/disable exporting sensitive data. Set the property to true to enable sensitive data. - * By default exporting sensitive data is disabled. - */ - private static final String SENSITIVE_DATA_ENABLED = "oracle.jdbc.provider.observability.sensitiveDataEnabled"; - - private static final ReentrantLock observabilityConfiguraitonLock = new ReentrantLock(); - - private static final ObservabilityConfiguration INSTANCE = new ObservabilityConfiguration(); - - private boolean enabled = true; - private boolean sensitiveDataEnabled; - private String tracers; - - private List enabledTracers = new ArrayList<>(); - - private ObservabilityConfiguration() { - String enabledTracers = System.getProperty(ENABLED_TRACERS, "OTEL,JFR"); - String sensitiveDataEnabled = System.getProperty(SENSITIVE_DATA_ENABLED, "false"); - setEnabledTracers(enabledTracers); - setSensitiveDataEnabled(Boolean.valueOf(sensitiveDataEnabled)); - } - - /** - * Returns true if the provider is enabled, otherwise false. - * - * @return true if the provider is enabled, otherwise false. - */ - @Override - public boolean getEnabled() { - observabilityConfiguraitonLock.lock(); - try { - return enabled; - } finally { - observabilityConfiguraitonLock.unlock(); - } - } - - /** - * Enables/disables the provider. - * - * @param enabled true to enable the provider, otherwise false. - */ - @Override - public void setEnabled(boolean enabled) { - observabilityConfiguraitonLock.lock(); - try { - this.enabled = enabled; - } finally { - observabilityConfiguraitonLock.unlock(); - } - } - - /** - * Returns a comma separated list of enabled tracers. - */ - @Override - public String getEnabledTracers() { - observabilityConfiguraitonLock.lock(); - try { - return tracers; - } finally { - observabilityConfiguraitonLock.unlock(); - } - } - - /** - * Enables the tracers. Available tracers are defined in enum {@link TracerType}. - * - * @param tracers comma separated list of enabled tracers. - */ - @Override - public void setEnabledTracers(String tracers) { - observabilityConfiguraitonLock.lock(); - try { - enabledTracers.clear(); - String[] items = tracers.split(","); - for (String item : items) { - if (item != null) { - enabledTracers.add(TracerType.valueOf(item.toUpperCase())); - } - } - this.tracers = enabledTracers.stream().map((item) -> item.toString()).collect(Collectors.joining(",")); - } finally { - observabilityConfiguraitonLock.unlock(); - } - - } - - /** - * Returns true if sensitive data is enabled, otherwise false. - */ - @Override - public boolean getSensitiveDataEnabled() { - observabilityConfiguraitonLock.lock(); - try { - return sensitiveDataEnabled; - } finally { - observabilityConfiguraitonLock.unlock(); - } - } - - /** - * Enables/disables sensitive data. - * - * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. - */ - @Override - public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { - observabilityConfiguraitonLock.lock(); - try { - this.sensitiveDataEnabled = sensitiveDataEnabled; - } finally { - observabilityConfiguraitonLock.unlock(); - } - } - - /** - * Returns the singleton instance of {@link ObservabilityConfiguration}. - * @return the singleton instance of {@link ObservabilityConfiguration}. - */ - public static ObservabilityConfiguration getInstance() { - observabilityConfiguraitonLock.lock(); - try { - return INSTANCE; - } finally { - observabilityConfiguraitonLock.unlock(); - } - } - - /** - * Returns a list of enabled {@link TracerType}. - * @return then list of nabled {@link TracerType}. - */ - public List getEnabledTracersSet() { - observabilityConfiguraitonLock.lock(); - try { - return enabledTracers; - } finally { - observabilityConfiguraitonLock.unlock(); - } - } - - -} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java deleted file mode 100644 index 41457aaf..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/configuration/ObservabilityConfigurationMBean.java +++ /dev/null @@ -1,51 +0,0 @@ -package oracle.jdbc.provider.observability.configuration; - -import oracle.jdbc.provider.observability.tracers.TracerType; - -/** - * MBean that allows to configure the Oracle JDBC Observability Provider. - */ -public interface ObservabilityConfigurationMBean { - - /** - * Returns true if the provider is enabled, otherwise false. - * - * @return true if the provider is enabled, otherwise false. - */ - public boolean getEnabled(); - - /** - * Enables/disables the provider. - * - * @param enabled true to enable the provider, otherwise false. - */ - public void setEnabled(boolean enabled); - - /** - * Returns a comma separated list of enabled tracers. - * - * @return a comma separated list of enabled tracers. - */ - public String getEnabledTracers(); - - /** - * Enables the tracers. Available tracers are defined in enum {@link TracerType}. - * - * @param tracers comma separated list of enabled tracers. - */ - public void setEnabledTracers(String tracers); - - /** - * Returns true if sensitive data is enabled, otherwise false. - * - * @return true if sensitive data is enabled, otherwise false. - */ - public boolean getSensitiveDataEnabled(); - - /** - * Enables/disables sensitive data. - * - * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. - */ - public void setSensitiveDataEnabled(boolean sensitiveDataEnabled); -} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java index 5e93c3fa..d0c4c208 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java @@ -1,3 +1,40 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability.tracers; import java.util.EnumMap; @@ -16,7 +53,7 @@ public interface ObservabilityTracer { /** * Map containing the number of parameters expected for each execution event */ - static final Map EXECUTION_EVENTS_PARAMETERS + Map EXECUTION_EVENTS_PARAMETERS = new EnumMap(JdbcExecutionEvent.class) { { put(JdbcExecutionEvent.AC_REPLAY_STARTED, 3); @@ -25,6 +62,12 @@ public interface ObservabilityTracer { } }; + /** + * Returns the unique name of the tracer. + * @return the unique name of the tracer. + */ + String getName(); + /** * Called by {@link ObservabilityTraceEventListener} when a round trip event @@ -39,7 +82,7 @@ public interface ObservabilityTracer { * @return a user context object that is passed to the next call on this * Connection. May be null. */ - Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext); + Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Object userContext); /** * Called by {@link ObservabilityTraceEventListener} when an execution event diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/TracerType.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/TracerType.java deleted file mode 100644 index fec5fea9..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/TracerType.java +++ /dev/null @@ -1,34 +0,0 @@ -package oracle.jdbc.provider.observability.tracers; - -import oracle.jdbc.provider.observability.tracers.jfr.JFRTracer; -import oracle.jdbc.provider.observability.tracers.otel.OTelTracer; - -/** - * This interface defines the constants that are used to identify the - * {@link ObservabilityTracer}. - */ -public enum TracerType { - /** - * Open Telemetry tracer. - */ - OTEL(new OTelTracer()), - - /** - * Java Flight Recorder tracer. - */ - JFR(new JFRTracer()); - - private ObservabilityTracer tracer; - - TracerType(ObservabilityTracer tracer) { - this.tracer = tracer; - } - - /** - * Returns the {@link ObservabilityTracer} for this {@link TracerType}. - * @return the {@link ObservabilityTracer}. - */ - public ObservabilityTracer getTracer() { - return tracer; - } -} \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java index 24158877..f42c811a 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java @@ -1,3 +1,40 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability.tracers.jfr; import jdk.jfr.Event; @@ -5,11 +42,13 @@ import jdk.jfr.Name; import java.sql.SQLException; +import java.util.logging.Logger; import jdk.jfr.Category; import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.TraceContext; -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; /** @@ -17,6 +56,12 @@ */ public class JFREventFactory { + /** + * Logger + */ + private static final Logger logger = Logger.getLogger( + ObservabilityTraceEventListener.class.getPackageName()); + /** * This class only has a static method, no public constructor needed. */ @@ -27,158 +72,160 @@ private JFREventFactory() { } * The type of round trip event depends on the database function. * * @param traceContext the trace context received by a TraceEventListener. + * @param configuration the configuration * @return the {@link RoundTripEvent} for the database function. */ - public static RoundTripEvent createJFREvent(TraceContext traceContext) { + public static RoundTripEvent createJFRRoundTripEvent(TraceContext traceContext, ObservabilityConfiguration configuration) { switch (traceContext.databaseFunction()) { case ADVANCED_QUEUING_12C_EMON_DEQUEUE: - return new AdvancedQueuing12cEminDequeueEvent(traceContext); + return new AdvancedQueuing12cEminDequeueEvent(traceContext, configuration); case ADVANCED_QUEUING_ARRAY_ENQUEUE_DEQUEUE: - return new AdvancedQueuingArrayEnqueueDequeue(traceContext); + return new AdvancedQueuingArrayEnqueueDequeue(traceContext, configuration); case ADVANCED_QUEUING_DEQUEUE_V8: - return new AdvancedQueuingDequeueV8(traceContext); + return new AdvancedQueuingDequeueV8(traceContext, configuration); case ADVANCED_QUEUING_ENQUEUE: - return new AdvancedQueuingEnqueue(traceContext); + return new AdvancedQueuingEnqueue(traceContext, configuration); case ADVANCED_QUEUING_GET_PROPAGATION_STATUS: - return new AdvancedQueuingGetPropagationStatus(traceContext); + return new AdvancedQueuingGetPropagationStatus(traceContext, configuration); case ADVANCED_QUEUING_LISTEN: - return new AdvancedQueuingListen(traceContext); + return new AdvancedQueuingListen(traceContext, configuration); case ADVANCED_QUEUING_SESSION_GET_RPC_1: - return new AdvancedQueuingSessionGetRPC1(traceContext); + return new AdvancedQueuingSessionGetRPC1(traceContext, configuration); case ADVANCED_QUEUING_SESSION_GET_RPC_2: - return new AdvancedQueuingSessionGetRPC2(traceContext); + return new AdvancedQueuingSessionGetRPC2(traceContext, configuration); case ADVANCED_QUEUING_SHARED_DEQUEUE: - return new AdvancedQueuingSharedDequeue(traceContext); + return new AdvancedQueuingSharedDequeue(traceContext, configuration); case ADVANCED_QUEUING_SHARED_ENQUEUE: - return new AdvancedQueuingSharedEnqueue(traceContext); + return new AdvancedQueuingSharedEnqueue(traceContext, configuration); case APP_REPLAY: - return new AppReplay(traceContext); + return new AppReplay(traceContext, configuration); case AUTH_CALL: - return new AuthCall(traceContext); + return new AuthCall(traceContext, configuration); case AUTO_COMMIT_OFF: - return new AutoCommitOff(traceContext); + return new AutoCommitOff(traceContext, configuration); case AUTO_COMMIT_ON: - return new AutoCommitOn(traceContext); + return new AutoCommitOn(traceContext, configuration); case CANCEL_ALL: - return new CancelAll(traceContext); + return new CancelAll(traceContext, configuration); case CANCEL_OPERATION: - return new CancelOperation(traceContext); + return new CancelOperation(traceContext, configuration); case CHUNCK_INFO: - return new ChunkInfo(traceContext); + return new ChunkInfo(traceContext, configuration); case CLIENT_FEATURES: - return new ClientFeatures(traceContext); + return new ClientFeatures(traceContext, configuration); case CLIENT_QUERY_CACHE_IDS: - return new ClientQueryCacheIds(traceContext); + return new ClientQueryCacheIds(traceContext, configuration); case CLIENT_QUERY_CACHE_STATS_UPDATE: - return new ClientQueryCacheStatsUpdate(traceContext); + return new ClientQueryCacheStatsUpdate(traceContext, configuration); case CLOSE_ALL_CURSOR: - return new CloseAllCursor(traceContext); + return new CloseAllCursor(traceContext, configuration); case CLOSE_CURSOR: - return new CloseCursor(traceContext); + return new CloseCursor(traceContext, configuration); case COMMIT: - return new Commit(traceContext); + return new Commit(traceContext, configuration); case DB12C_NOTIFICATION_RCV: - return new DB12cNotificationRCV(traceContext); + return new DB12cNotificationRCV(traceContext, configuration); case DBNS_SAGAS: - return new DBNSSagas(traceContext); + return new DBNSSagas(traceContext, configuration); case DESCRIBE_ANY_V8: - return new DescribeAnyV8(traceContext); + return new DescribeAnyV8(traceContext, configuration); case DESCRIBE_ARRAY: - return new DescribeArray(traceContext); + return new DescribeArray(traceContext, configuration); case DESCRIBE_QUERY_CALL: - return new DescribeQueryCall(traceContext); + return new DescribeQueryCall(traceContext, configuration); case DIRECT_PATH_LOAD_STREAM: - return new DirectPathLoadStream(traceContext); + return new DirectPathLoadStream(traceContext, configuration); case DIRECT_PATH_MISC_OP: - return new DirectPathMISCOp(traceContext); + return new DirectPathMISCOp(traceContext, configuration); case DIRECT_PATH_PREPARE: - return new DirectPathPrepare(traceContext); + return new DirectPathPrepare(traceContext, configuration); case DISTRIBUTED_TRANS_MGR_RPC: - return new DistributedTransMGRRPC(traceContext); + return new DistributedTransMGRRPC(traceContext, configuration); case EXECUTE_QUERY: - return new ExecuteQuery(traceContext); + return new ExecuteQuery(traceContext, configuration); case EXTENSIBLE_SECURITY_SESSION_CREATE: - return new ExtensibleSecuritySessionCreate(traceContext); + return new ExtensibleSecuritySessionCreate(traceContext, configuration); case EXTENSIBLE_SECURITY_SESSION_PIGGYBACK: - return new ExtensibleSecuritySessionPiggyback(traceContext); + return new ExtensibleSecuritySessionPiggyback(traceContext, configuration); case EXTENSIBLE_SECURITY_SESSION_ROUNDTRIP: - return new ExtensibleSecuritySessionRoundtrip(traceContext); + return new ExtensibleSecuritySessionRoundtrip(traceContext, configuration); case FAST_UPI_CALLS: - return new FastUPICalls(traceContext); + return new FastUPICalls(traceContext, configuration); case FETCH_ROW: - return new FetchRow(traceContext); + return new FetchRow(traceContext, configuration); case GET_VERSION: - return new GetVersion(traceContext); + return new GetVersion(traceContext, configuration); case KERNEL_PROGRAMMATIC_NOTIFICATION: - return new KernelProgrammaticNotification(traceContext); + return new KernelProgrammaticNotification(traceContext, configuration); case KEY_VALUE: - return new KeyValue(traceContext); + return new KeyValue(traceContext, configuration); case LOB_FILE_CALL: - return new LOBFileCall(traceContext); + return new LOBFileCall(traceContext, configuration); case LOGOFF: - return new LogOff(traceContext); + return new LogOff(traceContext, configuration); case LOGON_CHALLENGE_RESPONSE_1: - return new LogonChallengeResponse1(traceContext); + return new LogonChallengeResponse1(traceContext, configuration); case LOGON_CHALLENGE_RESPONSE_2: - return new LogonChallengeResponse2(traceContext); + return new LogonChallengeResponse2(traceContext, configuration); case OEXFEN: - return new OEXFEN(traceContext); + return new OEXFEN(traceContext, configuration); case OPEN_CURSOR: - return new OpenCursor(traceContext); + return new OpenCursor(traceContext, configuration); case OSQL7: - return new OSQL7(traceContext); + return new OSQL7(traceContext, configuration); case OSTART: - return new OStart(traceContext); + return new OStart(traceContext, configuration); case OSTOP: - return new OStop(traceContext); + return new OStop(traceContext, configuration); case PARAMETER_PUT_SPFILE: - return new ParameterPutSPFile(traceContext); + return new ParameterPutSPFile(traceContext, configuration); case PING: - return new Ping(traceContext); + return new Ping(traceContext, configuration); case PIPELINE_END: - return new PipelineEnd(traceContext); + return new PipelineEnd(traceContext, configuration); case PIPELINE_PIGGYBACK_BEGIN: - return new PipelinePiggybackBegin(traceContext); + return new PipelinePiggybackBegin(traceContext, configuration); case PIPELINE_PIGGYBACK_OP: - return new PipelinePiggybackOp(traceContext); + return new PipelinePiggybackOp(traceContext, configuration); case ROLLBACK: - return new Rollback(traceContext); + return new Rollback(traceContext, configuration); case SESSION_KEY: - return new SessionKey(traceContext); + return new SessionKey(traceContext, configuration); case SESSION_STATE_OPS: - return new SessionStateOps(traceContext); + return new SessionStateOps(traceContext, configuration); case SESSION_STATE_TEMPLATE: - return new SessionStateTemplate(traceContext); + return new SessionStateTemplate(traceContext, configuration); case SESSION_SWITCH_V8: - return new SessionSwitchV8(traceContext); + return new SessionSwitchV8(traceContext, configuration); case TRACING_MESSAGE: - return new TracingMessage(traceContext); + return new TracingMessage(traceContext, configuration); case TRANSACTION_COMMIT: - return new TransactionCommit(traceContext); + return new TransactionCommit(traceContext, configuration); case TRANSACTION_START: - return new TransactionStart(traceContext); + return new TransactionStart(traceContext, configuration); case TTC_DTY_ROUNDTRIP: - return new TTCDTYRoundtrip(traceContext); + return new TTCDTYRoundtrip(traceContext, configuration); case TTC_PRO_ROUNDTRIP: - return new TTCPRORoundtrip(traceContext); + return new TTCPRORoundtrip(traceContext, configuration); case XS_ATTACH_SESSION: - return new XSAttachSession(traceContext); + return new XSAttachSession(traceContext, configuration); case XS_CREATE_SESSION: - return new XSCreateSession(traceContext); + return new XSCreateSession(traceContext, configuration); case XS_DESTROY_SESSION: - return new XSDestroySession(traceContext); + return new XSDestroySession(traceContext, configuration); case XS_DETACH_SESSION: - return new XSDetachSession(traceContext); + return new XSDetachSession(traceContext, configuration); case XS_NAMESPACE_OP: - return new XSNamespaceOp(traceContext); + return new XSNamespaceOp(traceContext, configuration); case XS_NAMESPACE_OPS: - return new XSNamespaceOps(traceContext); + return new XSNamespaceOps(traceContext, configuration); case XS_SET_SESSION_PARAMETER: - return new XSSetSessionParameter(traceContext); + return new XSSetSessionParameter(traceContext, configuration); case XS_STATE_SYNC_OP: - return new XSStateSyncOp(traceContext); + return new XSStateSyncOp(traceContext, configuration); default: - return new RoundTripEvent(traceContext); + logger.warning("Unknown round trip received: " + traceContext.databaseFunction()); + return new RoundTripEvent(traceContext, configuration); } } @@ -199,6 +246,7 @@ public static Event createExecutionEvent(JdbcExecutionEvent event, Object... par case VIP_RETRY: return new VIPRetry(event, params); default: + logger.warning("Unknow event received: " + event); return new ExecutionEvent(event, params); } } @@ -209,8 +257,8 @@ public static Event createExecutionEvent(JdbcExecutionEvent event, Object... par @Label("AQ 12c emon dequeue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuing12cEminDequeueEvent extends RoundTripEvent{ - public AdvancedQueuing12cEminDequeueEvent(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuing12cEminDequeueEvent(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -218,8 +266,8 @@ public AdvancedQueuing12cEminDequeueEvent(TraceContext traceContext) { @Label("AQ Array Enqueue/Dequeue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingArrayEnqueueDequeue extends RoundTripEvent{ - public AdvancedQueuingArrayEnqueueDequeue(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingArrayEnqueueDequeue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -227,8 +275,8 @@ public AdvancedQueuingArrayEnqueueDequeue(TraceContext traceContext) { @Label("AQ Dequeue before 8.1") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingDequeueV8 extends RoundTripEvent{ - public AdvancedQueuingDequeueV8(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingDequeueV8(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -236,8 +284,8 @@ public AdvancedQueuingDequeueV8(TraceContext traceContext) { @Label("AQ EnQueue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingEnqueue extends RoundTripEvent{ - public AdvancedQueuingEnqueue(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingEnqueue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -245,8 +293,8 @@ public AdvancedQueuingEnqueue(TraceContext traceContext) { @Label("AQ get propagation status entries") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingGetPropagationStatus extends RoundTripEvent{ - public AdvancedQueuingGetPropagationStatus(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingGetPropagationStatus(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -254,8 +302,8 @@ public AdvancedQueuingGetPropagationStatus(TraceContext traceContext) { @Label("AQ Listen") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingListen extends RoundTripEvent{ - public AdvancedQueuingListen(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingListen(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -263,8 +311,8 @@ public AdvancedQueuingListen(TraceContext traceContext) { @Label("Session get RPC in server pool scenario") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingSessionGetRPC1 extends RoundTripEvent{ - public AdvancedQueuingSessionGetRPC1(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingSessionGetRPC1(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -272,8 +320,8 @@ public AdvancedQueuingSessionGetRPC1(TraceContext traceContext) { @Label("Session get RPC in server pool scenario") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingSessionGetRPC2 extends RoundTripEvent{ - public AdvancedQueuingSessionGetRPC2(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingSessionGetRPC2(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -281,8 +329,8 @@ public AdvancedQueuingSessionGetRPC2(TraceContext traceContext) { @Label("AQ Sharded dequeue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingSharedDequeue extends RoundTripEvent{ - public AdvancedQueuingSharedDequeue(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingSharedDequeue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -290,8 +338,8 @@ public AdvancedQueuingSharedDequeue(TraceContext traceContext) { @Label("AQ Sharded enqueue") @Category({"Oracle JDBC", "Round trips"}) static class AdvancedQueuingSharedEnqueue extends RoundTripEvent{ - public AdvancedQueuingSharedEnqueue(TraceContext traceContext) { - super(traceContext); + public AdvancedQueuingSharedEnqueue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -299,8 +347,8 @@ public AdvancedQueuingSharedEnqueue(TraceContext traceContext) { @Label("Application continuity REPLAY") @Category({"Oracle JDBC", "Round trips"}) static class AppReplay extends RoundTripEvent{ - public AppReplay(TraceContext traceContext) { - super(traceContext); + public AppReplay(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -308,8 +356,8 @@ public AppReplay(TraceContext traceContext) { @Label("Generic authentication call") @Category({"Oracle JDBC", "Round trips"}) static class AuthCall extends RoundTripEvent{ - public AuthCall(TraceContext traceContext) { - super(traceContext); + public AuthCall(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -317,8 +365,8 @@ public AuthCall(TraceContext traceContext) { @Label("Auto commit off") @Category({"Oracle JDBC", "Round trips"}) static class AutoCommitOff extends RoundTripEvent{ - public AutoCommitOff(TraceContext traceContext) { - super(traceContext); + public AutoCommitOff(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -326,8 +374,8 @@ public AutoCommitOff(TraceContext traceContext) { @Label("Auto commit on") @Category({"Oracle JDBC", "Round trips"}) static class AutoCommitOn extends RoundTripEvent{ - public AutoCommitOn(TraceContext traceContext) { - super(traceContext); + public AutoCommitOn(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -335,8 +383,8 @@ public AutoCommitOn(TraceContext traceContext) { @Label("Cancel All") @Category({"Oracle JDBC", "Round trips"}) static class CancelAll extends RoundTripEvent{ - public CancelAll(TraceContext traceContext) { - super(traceContext); + public CancelAll(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -344,8 +392,8 @@ public CancelAll(TraceContext traceContext) { @Label("Cancel the current operation") @Category({"Oracle JDBC", "Round trips"}) static class CancelOperation extends RoundTripEvent{ - public CancelOperation(TraceContext traceContext) { - super(traceContext); + public CancelOperation(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -353,8 +401,8 @@ public CancelOperation(TraceContext traceContext) { @Label("Chunk info RPC") @Category({"Oracle JDBC", "Round trips"}) static class ChunkInfo extends RoundTripEvent{ - public ChunkInfo(TraceContext traceContext) { - super(traceContext); + public ChunkInfo(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -362,8 +410,8 @@ public ChunkInfo(TraceContext traceContext) { @Label("Client features") @Category({"Oracle JDBC", "Round trips"}) static class ClientFeatures extends RoundTripEvent{ - public ClientFeatures(TraceContext traceContext) { - super(traceContext); + public ClientFeatures(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -371,8 +419,8 @@ public ClientFeatures(TraceContext traceContext) { @Label("Client query cache IDs") @Category({"Oracle JDBC", "Round trips"}) static class ClientQueryCacheIds extends RoundTripEvent{ - public ClientQueryCacheIds(TraceContext traceContext) { - super(traceContext); + public ClientQueryCacheIds(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -380,8 +428,8 @@ public ClientQueryCacheIds(TraceContext traceContext) { @Label("Client query cache statistics update") @Category({"Oracle JDBC", "Round trips"}) static class ClientQueryCacheStatsUpdate extends RoundTripEvent{ - public ClientQueryCacheStatsUpdate(TraceContext traceContext) { - super(traceContext); + public ClientQueryCacheStatsUpdate(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -389,8 +437,8 @@ public ClientQueryCacheStatsUpdate(TraceContext traceContext) { @Label("Cursor close all") @Category({"Oracle JDBC", "Round trips"}) static class CloseAllCursor extends RoundTripEvent{ - public CloseAllCursor(TraceContext traceContext) { - super(traceContext); + public CloseAllCursor(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -398,8 +446,8 @@ public CloseAllCursor(TraceContext traceContext) { @Label("Close a cursor") @Category({"Oracle JDBC", "Round trips"}) static class CloseCursor extends RoundTripEvent{ - public CloseCursor(TraceContext traceContext) { - super(traceContext); + public CloseCursor(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -407,8 +455,8 @@ public CloseCursor(TraceContext traceContext) { @Label("Commit") @Category({"Oracle JDBC", "Round trips"}) static class Commit extends RoundTripEvent{ - public Commit(TraceContext traceContext) { - super(traceContext); + public Commit(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -416,8 +464,8 @@ public Commit(TraceContext traceContext) { @Label("12c notification receive") @Category({"Oracle JDBC", "Round trips"}) static class DB12cNotificationRCV extends RoundTripEvent{ - public DB12cNotificationRCV(TraceContext traceContext) { - super(traceContext); + public DB12cNotificationRCV(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -425,8 +473,8 @@ public DB12cNotificationRCV(TraceContext traceContext) { @Label("DBMS Sagas") @Category({"Oracle JDBC", "Round trips"}) static class DBNSSagas extends RoundTripEvent{ - public DBNSSagas(TraceContext traceContext) { - super(traceContext); + public DBNSSagas(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -434,8 +482,8 @@ public DBNSSagas(TraceContext traceContext) { @Label("V8 Describe Any") @Category({"Oracle JDBC", "Round trips"}) static class DescribeAnyV8 extends RoundTripEvent{ - public DescribeAnyV8(TraceContext traceContext) { - super(traceContext); + public DescribeAnyV8(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -443,8 +491,8 @@ public DescribeAnyV8(TraceContext traceContext) { @Label("Array describe") @Category({"Oracle JDBC", "Round trips"}) static class DescribeArray extends RoundTripEvent{ - public DescribeArray(TraceContext traceContext) { - super(traceContext); + public DescribeArray(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -452,8 +500,8 @@ public DescribeArray(TraceContext traceContext) { @Label("New describe query call") @Category({"Oracle JDBC", "Round trips"}) static class DescribeQueryCall extends RoundTripEvent{ - public DescribeQueryCall(TraceContext traceContext) { - super(traceContext); + public DescribeQueryCall(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -461,8 +509,8 @@ public DescribeQueryCall(TraceContext traceContext) { @Label("Direct Path Load Stream") @Category({"Oracle JDBC", "Round trips"}) static class DirectPathLoadStream extends RoundTripEvent{ - public DirectPathLoadStream(TraceContext traceContext) { - super(traceContext); + public DirectPathLoadStream(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -470,8 +518,8 @@ public DirectPathLoadStream(TraceContext traceContext) { @Label("Direct Path Misc Operations") @Category({"Oracle JDBC", "Round trips"}) static class DirectPathMISCOp extends RoundTripEvent{ - public DirectPathMISCOp(TraceContext traceContext) { - super(traceContext); + public DirectPathMISCOp(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -479,8 +527,8 @@ public DirectPathMISCOp(TraceContext traceContext) { @Label("Direct Path Prepare") @Category({"Oracle JDBC", "Round trips"}) static class DirectPathPrepare extends RoundTripEvent{ - public DirectPathPrepare(TraceContext traceContext) { - super(traceContext); + public DirectPathPrepare(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -488,8 +536,8 @@ public DirectPathPrepare(TraceContext traceContext) { @Label("Distributed transaction manager RPC") @Category({"Oracle JDBC", "Round trips"}) static class DistributedTransMGRRPC extends RoundTripEvent{ - public DistributedTransMGRRPC(TraceContext traceContext) { - super(traceContext); + public DistributedTransMGRRPC(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -497,8 +545,8 @@ public DistributedTransMGRRPC(TraceContext traceContext) { @Label("Execute query") @Category({"Oracle JDBC", "Round trips"}) static class ExecuteQuery extends RoundTripEvent{ - public ExecuteQuery(TraceContext traceContext) { - super(traceContext); + public ExecuteQuery(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -506,8 +554,8 @@ public ExecuteQuery(TraceContext traceContext) { @Label("eXtensible Security Sessions Create Session") @Category({"Oracle JDBC", "Round trips"}) static class ExtensibleSecuritySessionCreate extends RoundTripEvent{ - public ExtensibleSecuritySessionCreate(TraceContext traceContext) { - super(traceContext); + public ExtensibleSecuritySessionCreate(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -515,8 +563,8 @@ public ExtensibleSecuritySessionCreate(TraceContext traceContext) { @Label("eXtensible Security Sessions Piggyback") @Category({"Oracle JDBC", "Round trips"}) static class ExtensibleSecuritySessionPiggyback extends RoundTripEvent{ - public ExtensibleSecuritySessionPiggyback(TraceContext traceContext) { - super(traceContext); + public ExtensibleSecuritySessionPiggyback(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -524,8 +572,8 @@ public ExtensibleSecuritySessionPiggyback(TraceContext traceContext) { @Label("eXtensible Security Session Roundtrip") @Category({"Oracle JDBC", "Round trips"}) static class ExtensibleSecuritySessionRoundtrip extends RoundTripEvent{ - public ExtensibleSecuritySessionRoundtrip(TraceContext traceContext) { - super(traceContext); + public ExtensibleSecuritySessionRoundtrip(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -533,8 +581,8 @@ public ExtensibleSecuritySessionRoundtrip(TraceContext traceContext) { @Label("Fast UPI calls to opial7") @Category({"Oracle JDBC", "Round trips"}) static class FastUPICalls extends RoundTripEvent{ - public FastUPICalls(TraceContext traceContext) { - super(traceContext); + public FastUPICalls(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -542,8 +590,8 @@ public FastUPICalls(TraceContext traceContext) { @Label("Fetch a row") @Category({"Oracle JDBC", "Round trips"}) static class FetchRow extends RoundTripEvent{ - public FetchRow(TraceContext traceContext) { - super(traceContext); + public FetchRow(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -551,8 +599,8 @@ public FetchRow(TraceContext traceContext) { @Label("Get Oracle version-date string in new format") @Category({"Oracle JDBC", "Round trips"}) static class GetVersion extends RoundTripEvent{ - public GetVersion(TraceContext traceContext) { - super(traceContext); + public GetVersion(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -560,17 +608,17 @@ public GetVersion(TraceContext traceContext) { @Label("Kernel Programmatic Notification") @Category({"Oracle JDBC", "Round trips"}) static class KernelProgrammaticNotification extends RoundTripEvent{ - public KernelProgrammaticNotification(TraceContext traceContext) { - super(traceContext); + public KernelProgrammaticNotification(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @Name("oracle.jdbc.provider.observability.RoundTrip.KEY_VALUE") - @Label("Client app context, namespace, attribute, values") + @Label("Client app context, configurationspace, attribute, values") @Category({"Oracle JDBC", "Round trips"}) static class KeyValue extends RoundTripEvent{ - public KeyValue(TraceContext traceContext) { - super(traceContext); + public KeyValue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -578,8 +626,8 @@ public KeyValue(TraceContext traceContext) { @Label("LOB and FILE related calls") @Category({"Oracle JDBC", "Round trips"}) static class LOBFileCall extends RoundTripEvent{ - public LOBFileCall(TraceContext traceContext) { - super(traceContext); + public LOBFileCall(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -587,8 +635,8 @@ public LOBFileCall(TraceContext traceContext) { @Label("Logoff of Oracle") @Category({"Oracle JDBC", "Round trips"}) static class LogOff extends RoundTripEvent{ - public LogOff(TraceContext traceContext) { - super(traceContext); + public LogOff(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -596,8 +644,8 @@ public LogOff(TraceContext traceContext) { @Label("First half of challenge-response logon") @Category({"Oracle JDBC", "Round trips"}) static class LogonChallengeResponse1 extends RoundTripEvent{ - public LogonChallengeResponse1(TraceContext traceContext) { - super(traceContext); + public LogonChallengeResponse1(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -605,8 +653,8 @@ public LogonChallengeResponse1(TraceContext traceContext) { @Label("Second half of challenge-response logon") @Category({"Oracle JDBC", "Round trips"}) static class LogonChallengeResponse2 extends RoundTripEvent{ - public LogonChallengeResponse2(TraceContext traceContext) { - super(traceContext); + public LogonChallengeResponse2(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -614,8 +662,8 @@ public LogonChallengeResponse2(TraceContext traceContext) { @Label("OEXFEN") @Category({"Oracle JDBC", "Round trips"}) static class OEXFEN extends RoundTripEvent{ - public OEXFEN(TraceContext traceContext) { - super(traceContext); + public OEXFEN(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -623,8 +671,8 @@ public OEXFEN(TraceContext traceContext) { @Label("Open a cursor") @Category({"Oracle JDBC", "Round trips"}) static class OpenCursor extends RoundTripEvent{ - public OpenCursor(TraceContext traceContext) { - super(traceContext); + public OpenCursor(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -632,8 +680,8 @@ public OpenCursor(TraceContext traceContext) { @Label("OSQL7") @Category({"Oracle JDBC", "Round trips"}) static class OSQL7 extends RoundTripEvent{ - public OSQL7(TraceContext traceContext) { - super(traceContext); + public OSQL7(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -641,8 +689,8 @@ public OSQL7(TraceContext traceContext) { @Label("Starts Oracle") @Category({"Oracle JDBC", "Round trips"}) static class OStart extends RoundTripEvent{ - public OStart(TraceContext traceContext) { - super(traceContext); + public OStart(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -650,8 +698,8 @@ public OStart(TraceContext traceContext) { @Label("Stops Oracle") @Category({"Oracle JDBC", "Round trips"}) static class OStop extends RoundTripEvent{ - public OStop(TraceContext traceContext) { - super(traceContext); + public OStop(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -659,8 +707,8 @@ public OStop(TraceContext traceContext) { @Label("Put parameter using spfile (for startup)") @Category({"Oracle JDBC", "Round trips"}) static class ParameterPutSPFile extends RoundTripEvent{ - public ParameterPutSPFile(TraceContext traceContext) { - super(traceContext); + public ParameterPutSPFile(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -668,8 +716,8 @@ public ParameterPutSPFile(TraceContext traceContext) { @Label("Ping") @Category({"Oracle JDBC", "Round trips"}) static class Ping extends RoundTripEvent{ - public Ping(TraceContext traceContext) { - super(traceContext); + public Ping(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -677,8 +725,8 @@ public Ping(TraceContext traceContext) { @Label("Pipeline End") @Category({"Oracle JDBC", "Round trips"}) static class PipelineEnd extends RoundTripEvent{ - public PipelineEnd(TraceContext traceContext) { - super(traceContext); + public PipelineEnd(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -686,8 +734,8 @@ public PipelineEnd(TraceContext traceContext) { @Label("Pipeline Begin Piggyback") @Category({"Oracle JDBC", "Round trips"}) static class PipelinePiggybackBegin extends RoundTripEvent{ - public PipelinePiggybackBegin(TraceContext traceContext) { - super(traceContext); + public PipelinePiggybackBegin(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -695,8 +743,8 @@ public PipelinePiggybackBegin(TraceContext traceContext) { @Label("Pipeline Operation Piggyback") @Category({"Oracle JDBC", "Round trips"}) static class PipelinePiggybackOp extends RoundTripEvent{ - public PipelinePiggybackOp(TraceContext traceContext) { - super(traceContext); + public PipelinePiggybackOp(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -704,8 +752,8 @@ public PipelinePiggybackOp(TraceContext traceContext) { @Label("Rollback") @Category({"Oracle JDBC", "Round trips"}) static class Rollback extends RoundTripEvent{ - public Rollback(TraceContext traceContext) { - super(traceContext); + public Rollback(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -713,8 +761,8 @@ public Rollback(TraceContext traceContext) { @Label("Get the session key") @Category({"Oracle JDBC", "Round trips"}) static class SessionKey extends RoundTripEvent{ - public SessionKey(TraceContext traceContext) { - super(traceContext); + public SessionKey(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -722,8 +770,8 @@ public SessionKey(TraceContext traceContext) { @Label("Session state ops") @Category({"Oracle JDBC", "Round trips"}) static class SessionStateOps extends RoundTripEvent{ - public SessionStateOps(TraceContext traceContext) { - super(traceContext); + public SessionStateOps(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -731,8 +779,8 @@ public SessionStateOps(TraceContext traceContext) { @Label("Session state template") @Category({"Oracle JDBC", "Round trips"}) static class SessionStateTemplate extends RoundTripEvent{ - public SessionStateTemplate(TraceContext traceContext) { - super(traceContext); + public SessionStateTemplate(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -740,8 +788,8 @@ public SessionStateTemplate(TraceContext traceContext) { @Label("V8 session switching piggyback") @Category({"Oracle JDBC", "Round trips"}) static class SessionSwitchV8 extends RoundTripEvent{ - public SessionSwitchV8(TraceContext traceContext) { - super(traceContext); + public SessionSwitchV8(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -749,8 +797,8 @@ public SessionSwitchV8(TraceContext traceContext) { @Label("End to end tracing message") @Category({"Oracle JDBC", "Round trips"}) static class TracingMessage extends RoundTripEvent{ - public TracingMessage(TraceContext traceContext) { - super(traceContext); + public TracingMessage(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -758,8 +806,8 @@ public TracingMessage(TraceContext traceContext) { @Label("Transaction commit, rollback, recover") @Category({"Oracle JDBC", "Round trips"}) static class TransactionCommit extends RoundTripEvent{ - public TransactionCommit(TraceContext traceContext) { - super(traceContext); + public TransactionCommit(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -767,8 +815,8 @@ public TransactionCommit(TraceContext traceContext) { @Label("Transaction start, attach, detach") @Category({"Oracle JDBC", "Round trips"}) static class TransactionStart extends RoundTripEvent{ - public TransactionStart(TraceContext traceContext) { - super(traceContext); + public TransactionStart(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -776,8 +824,8 @@ public TransactionStart(TraceContext traceContext) { @Label("Data type message exchange") @Category({"Oracle JDBC", "Round trips"}) static class TTCDTYRoundtrip extends RoundTripEvent{ - public TTCDTYRoundtrip(TraceContext traceContext) { - super(traceContext); + public TTCDTYRoundtrip(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -785,8 +833,8 @@ public TTCDTYRoundtrip(TraceContext traceContext) { @Label("Protocol negotiation message exchange") @Category({"Oracle JDBC", "Round trips"}) static class TTCPRORoundtrip extends RoundTripEvent{ - public TTCPRORoundtrip(TraceContext traceContext) { - super(traceContext); + public TTCPRORoundtrip(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -794,8 +842,8 @@ public TTCPRORoundtrip(TraceContext traceContext) { @Label("XS Attach Session") @Category({"Oracle JDBC", "Round trips"}) static class XSAttachSession extends RoundTripEvent{ - public XSAttachSession(TraceContext traceContext) { - super(traceContext); + public XSAttachSession(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -803,8 +851,8 @@ public XSAttachSession(TraceContext traceContext) { @Label("XS Create Session") @Category({"Oracle JDBC", "Round trips"}) static class XSCreateSession extends RoundTripEvent{ - public XSCreateSession(TraceContext traceContext) { - super(traceContext); + public XSCreateSession(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -812,8 +860,8 @@ public XSCreateSession(TraceContext traceContext) { @Label("XS Destroy Session") @Category({"Oracle JDBC", "Round trips"}) static class XSDestroySession extends RoundTripEvent{ - public XSDestroySession(TraceContext traceContext) { - super(traceContext); + public XSDestroySession(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -821,8 +869,8 @@ public XSDestroySession(TraceContext traceContext) { @Label("XS Detach Session") @Category({"Oracle JDBC", "Round trips"}) static class XSDetachSession extends RoundTripEvent{ - public XSDetachSession(TraceContext traceContext) { - super(traceContext); + public XSDetachSession(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -830,8 +878,8 @@ public XSDetachSession(TraceContext traceContext) { @Label("XS Namespace OP") @Category({"Oracle JDBC", "Round trips"}) static class XSNamespaceOp extends RoundTripEvent{ - public XSNamespaceOp(TraceContext traceContext) { - super(traceContext); + public XSNamespaceOp(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -839,8 +887,8 @@ public XSNamespaceOp(TraceContext traceContext) { @Label("XS namespace OPs") @Category({"Oracle JDBC", "Round trips"}) static class XSNamespaceOps extends RoundTripEvent{ - public XSNamespaceOps(TraceContext traceContext) { - super(traceContext); + public XSNamespaceOps(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -848,8 +896,8 @@ public XSNamespaceOps(TraceContext traceContext) { @Label("XS Set Session Parameter") @Category({"Oracle JDBC", "Round trips"}) static class XSSetSessionParameter extends RoundTripEvent{ - public XSSetSessionParameter(TraceContext traceContext) { - super(traceContext); + public XSSetSessionParameter(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -857,8 +905,8 @@ public XSSetSessionParameter(TraceContext traceContext) { @Label("XS State Sync OP") @Category({"Oracle JDBC", "Round trips"}) static class XSStateSyncOp extends RoundTripEvent{ - public XSStateSyncOp(TraceContext traceContext) { - super(traceContext); + public XSStateSyncOp(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); } } @@ -867,16 +915,16 @@ public XSStateSyncOp(TraceContext traceContext) { @Category({"Oracle JDBC", "Round trips"}) static class RoundTripEvent extends Event { - public RoundTripEvent(TraceContext traceContext) { - setValues(traceContext); + public RoundTripEvent(TraceContext traceContext, ObservabilityConfiguration configuration) { + setValues(traceContext, configuration); } - public void setValues(TraceContext traceContext) { + public void setValues(TraceContext traceContext, ObservabilityConfiguration configuration) { this.connectionID = traceContext.getConnectionId(); this.databaseOperation = traceContext.databaseOperation(); this.tenant = traceContext.tenant(); this.sqlID = traceContext.getSqlId(); - if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { + if (configuration.getSensitiveDataEnabled()) { this.originalSQLText = traceContext.originalSqlText(); this.actualSQLText = traceContext.actualSqlText(); this.databaseUser = traceContext.user(); diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java index f7f7ee64..814c4e99 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java @@ -1,9 +1,47 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability.tracers.jfr; import jdk.jfr.Event; import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.Sequence; import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.ObservabilityConfiguration; import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; import oracle.jdbc.provider.observability.tracers.jfr.JFREventFactory.RoundTripEvent; @@ -12,22 +50,38 @@ */ public class JFRTracer implements ObservabilityTracer{ + /** + * Configuraiton + */ + private final ObservabilityConfiguration configuration; + /** * Creates a new instance. + * + * @param configuration the configuraiton. */ - public JFRTracer() {} + public JFRTracer(ObservabilityConfiguration configuration) { + this.configuration = configuration; + } @Override - public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext) { + public String getName() { + return "JFR"; + } + + @Override + public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { if (sequence.equals(Sequence.BEFORE)) { - RoundTripEvent event = JFREventFactory.createJFREvent(traceContext); + // Create the event and start measuring event duration + RoundTripEvent event = JFREventFactory.createJFRRoundTripEvent(traceContext, configuration); event.begin(); return event; } else { if (userContext != null) { RoundTripEvent event = (RoundTripEvent) userContext; - event.setValues(traceContext); - event.end(); + // set event attributes + event.setValues(traceContext, configuration); + // stop the measuring event durating and commit event.commit(); } } @@ -36,10 +90,12 @@ public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Objec @Override public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { + // Create event and commit Event executionEvent = JFREventFactory.createExecutionEvent(event, params); executionEvent.begin(); executionEvent.commit(); - return null; + //Return previous user context + return userContext; } } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index 3143c790..f4dd8eb8 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -1,14 +1,46 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability.tracers.otel; import java.sql.SQLException; -import java.time.Instant; -import java.util.EnumMap; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import javax.security.auth.login.Configuration; - import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; @@ -22,7 +54,7 @@ import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.Sequence; import oracle.jdbc.TraceEventListener.TraceContext; -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.ObservabilityConfiguration; import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; /** @@ -37,40 +69,66 @@ public class OTelTracer implements ObservabilityTracer { */ private static final String TRACE_KEY = "clientcontext.ora$opentelem$tracectx"; + /** + * The trace context is sent to the server in two lines, this is the first + * line it contains the version and the span context. + */ + private static final String TRACE_FORMAT = "traceparent: %s-%s-%s-%s\r\n"; + /** + * Trace context version. + */ + private static final String TRACE_VERSION = "00"; + + /** + * Format of the second line sent to the server containing the trace context. + * It contains the trace state. + */ + private static final String TRACE_STATE_FORMAT = "tracestate: %s\r\n"; /** * Logger. */ private static Logger logger = Logger.getLogger(OTelTracer.class.getName()); + /** + * Configuraiton + */ + private final ObservabilityConfiguration configuration; + + /** * Constructor. This tracer always uses {@link GlobalOpenTelemetry} to get * the Open Telemetry tracer. + * + * @param configuration the configuration. */ - public OTelTracer() { } + public OTelTracer(ObservabilityConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public String getName() { + return "OTEL"; + } @Override - public Object traceRoundtrip(Sequence sequence, TraceContext traceContext, Object userContext) { + public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { if (sequence == Sequence.BEFORE) { // Create the Span before the round-trip. final Span span = initAndGetSpan(traceContext, traceContext.databaseOperation()); - try (Scope ignored = span.makeCurrent()) { - traceContext.setClientInfo(TRACE_KEY, getTraceValue(span)); - } catch (Exception ex) { - logger.log(Level.WARNING, ex.getMessage(), ex); - } + makeSpanCurrentAndSendContextToServer(traceContext, span); // Return the Span instance to the driver. The driver holds this instance and - // supplies it - // as user context parameter on the next round-trip call. + // supplies it as user context parameter on the next round-trip call. return span; } else { // End the Span after the round-trip. if (userContext instanceof Span) { final Span span = (Span) userContext; span.setStatus(traceContext.isCompletedExceptionally() ? StatusCode.ERROR : StatusCode.OK); - span.end(Instant.now()); + span.end(); } + logger.log(Level.WARNING, "Unknown or null user context received from the driver."); return null; } } @@ -85,7 +143,7 @@ public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, .setAttribute("Error message", params[0].toString()) .setAttribute("VIP Address", params[7].toString()); // Add sensitive information (URL and SQL) if it is enabled - if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { + if (configuration.getSensitiveDataEnabled()) { logger.log(Level.FINEST, "Sensitive information on"); spanBuilder.setAttribute("Protocol", params[1].toString()) .setAttribute("Host", params[2].toString()) @@ -94,7 +152,8 @@ public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, .setAttribute("SID", params[5].toString()) .setAttribute("Connection data", params[6].toString()); } - return spanBuilder.startSpan(); + // start and end span. + spanBuilder.startSpan().end(); } else if (event == TraceEventListener.JdbcExecutionEvent.AC_REPLAY_STARTED || event == TraceEventListener.JdbcExecutionEvent.AC_REPLAY_SUCCESSFUL) { SpanBuilder spanBuilder = tracer @@ -103,7 +162,7 @@ public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, .setAttribute("Error code", ((SQLException) params[1]).getErrorCode()) .setAttribute("SQL state", ((SQLException) params[1]).getSQLState()) .setAttribute("Current replay retry count", params[2].toString()); - return spanBuilder.startSpan(); + spanBuilder.startSpan().end(); } else { logger.log(Level.WARNING, "Unknown event received : " + event.toString()); } @@ -111,9 +170,18 @@ public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, // log wrong number of parameters returned for execution event logger.log(Level.WARNING, "Wrong number of parameters received for event " + event.toString()); } - return null; + // return the previous userContext + return userContext; } + /** + * Creates a Open Telemetry Span and sets it's attributes according to the + * trace context and the configuration. + * + * @param traceContext the trace context. + * @param spanName then span name. + * @return returns the Span. + */ private Span initAndGetSpan(TraceContext traceContext, String spanName) { /* * If this is in the context of current span, the following becomes a nested or @@ -132,7 +200,7 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { .setAttribute("SQL ID", traceContext.getSqlId()); // Add sensitive information (URL and SQL) if it is enabled - if (ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()) { + if (configuration.getSensitiveDataEnabled()) { logger.log(Level.FINEST, "Sensitive information on"); spanBuilder .setAttribute("Original SQL Text", traceContext.originalSqlText()) @@ -146,35 +214,57 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { } /** - * Builds the Open Telemetry trace context to be sent to the database server. + * Sets the span as the current Open Telemetry Span and sends context information + * to the database server. + * + * @param traceContext the trace context * @param span the currect spans - * @return the current trace context formatted so that the server can read it. */ - private String getTraceValue(Span span) { + private void makeSpanCurrentAndSendContextToServer(TraceContext traceContext, Span span) { final String traceParent = initAndGetTraceParent(span); final String traceState = initAndGetTraceState(span); - return traceParent + traceState; + + try (Scope ignored = span.makeCurrent()) { + // Send the current context to the server + traceContext.setClientInfo(TRACE_KEY, traceParent + traceState); + } catch (Exception ex) { + logger.log(Level.WARNING, "An error occured while sending the current Open Telemetry context to the server. " + + ex.getMessage(), ex); + } } + /** + * Formats the current Open Telemetry context in a format that the server can + * understand. + * + * @param span the current Span + * @return the current Open Telemetry context formatted so that the server + * can understand. + */ private String initAndGetTraceParent(Span span) { final SpanContext spanContext = span.getSpanContext(); - // The current specification assumes the version is set to 00. - final String version = "00"; final String traceId = spanContext.getTraceId(); // parent-id is known as the span-id final String parentId = spanContext.getSpanId(); final String traceFlags = spanContext.getTraceFlags().toString(); - return String.format("traceparent: %s-%s-%s-%s\r\n", - version, traceId, parentId, traceFlags); + return String.format(TRACE_FORMAT, TRACE_VERSION, traceId, parentId, traceFlags); } + /** + * Formats the current Open Telemetry Span state in a format that the server + * can understand. + * + * @param span the current Span + * @return the current Open Telemetry Span state formatted so that the server + * can understand. + */ private String initAndGetTraceState(Span span) { final TraceState traceState = span.getSpanContext().getTraceState(); final StringBuilder stringBuilder = new StringBuilder(); traceState.forEach((k, v) -> stringBuilder.append(k).append("=").append(v)); - return String.format("tracestate: %s\r\n", stringBuilder); + return String.format(TRACE_STATE_FORMAT, stringBuilder); } } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java deleted file mode 100644 index 46d5f676..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerProvider.java +++ /dev/null @@ -1,102 +0,0 @@ -package oracle.jdbc.provider.opentelemetry; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.management.InstanceAlreadyExistsException; -import javax.management.MBeanRegistrationException; -import javax.management.MalformedObjectNameException; -import javax.management.NotCompliantMBeanException; -import javax.management.ObjectName; - -import oracle.jdbc.TraceEventListener; -import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; -import oracle.jdbc.provider.observability.ObservabilityTraceEventListenerProvider; -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; - -/** - *

    - * This class implements the TraceEventListenerProvider interface exposed by the - * Oracle JDBC driver. It provides TraceEventListeners of type {@link - * oracle.jdbc.provider.observability.ObservabilityTraceEventListener}. - *

    - *

    - * The provider registers a MBean (with objectName {@value #MBEAN_OBJECT_NAME}) - * that allows to configure the TraceEventListener by setting attributes. The - * following attributes are available: - *

      - *
    • Enabled: enables/disables exporting traces to Open Telemetry - * (true by default)
    • - *
    • SensitiveDataEnabled: enables/disables exporting sensiteve data - * to Open Telemetry(false by default)
    • - *
    - */ -public class OpenTelemetryTraceEventListenerProvider extends ObservabilityTraceEventListenerProvider { - - private static final String PROVIDER_NAME = "open-telemetry-trace-event-listener-provider"; - private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener"; - - /** - * Name of the property used to enable or disable this listener. - */ - public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED = "oracle.jdbc.provider.opentelemetry.enabled"; - /** - * Name of the property used to enable or disable sensitive data for this - * listener. - */ - public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; - - /** - * Logger - */ - private Logger logger = Logger.getLogger(OpenTelemetryTraceEventListenerProvider.class.getName()); - - private static ObjectName objectName; - - static { - try { - objectName = new ObjectName(MBEAN_OBJECT_NAME); - } catch (MalformedObjectNameException e) { - objectName = null; - } - } - - /** - * Constructs a new instance of OpenTelemetryTraceEventListenerProvider. This - * constructor will be called by the driver's service provider to create a new - * instance. - */ - public OpenTelemetryTraceEventListenerProvider() { } - - @Override - public String getName() { - return PROVIDER_NAME; - } - - @Override - public Collection getParameters() { - return Collections.emptyList(); - } - - @Override - public TraceEventListener getTraceEventListener(Map map) { - try { - if (!server.isRegistered(objectName)) { - boolean enabled = Boolean.valueOf(System.getProperty(OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED, "true")); - String sensitiveDataEnabled = System.getProperty(OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "false"); - ObservabilityConfiguration.getInstance().setEnabled(enabled); - ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); - ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(Boolean.parseBoolean(sensitiveDataEnabled)); - - server.registerMBean(ObservabilityConfiguration.getInstance(), objectName); - } - } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { - logger.log(Level.WARNING, "Could not register MBean", e); - } - return ObservabilityTraceEventListener.getInstance(); - } - -} diff --git a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider index 9b12f75b..b1c86eef 100644 --- a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider +++ b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider @@ -1,2 +1,2 @@ oracle.jdbc.provider.observability.ObservabilityTraceEventListenerProvider -oracle.jdbc.provider.opentelemetry.OpenTelemetryTraceEventListenerProvider \ No newline at end of file +oracle.jdbc.provider.observability.OpenTelemetryTraceEventListenerProvider \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java new file mode 100644 index 00000000..ca3f32cc --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java @@ -0,0 +1,120 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; + +import javax.management.Attribute; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.jupiter.api.Test; + +import oracle.jdbc.spi.OracleResourceProvider.Parameter; +import oracle.jdbc.spi.TraceEventListenerProvider; + +public class BackwardCompatibilityTest { + private static final String INSTANCE_NAME = "test-instance"; + + @Test + public void testConfiguration() throws Exception { + + // System properties + System.setProperty("oracle.jdbc.provider.opentelemetry.enabled", "true"); + System.setProperty("oracle.jdbc.provider.opentelemetry.sensitive-enabled", "true"); + + TraceEventListenerProvider provider = new OpenTelemetryTraceEventListenerProvider(); + Map parameters = new HashMap<>(); + provider.getParameters().forEach(parameter -> { + parameters.put(parameter, (CharSequence)INSTANCE_NAME); + }); + ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); + + ObservabilityConfiguration configuration = ObservabilityTraceEventListener.getObservabilityConfiguration(INSTANCE_NAME); + + assertEquals(true, configuration.getEnabled()); + assertEquals("OTEL", configuration.getEnabledTracers()); + assertEquals(true, configuration.getSensitiveDataEnabled()); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + + // MBean + ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + String enabled = server.getAttribute(objectName, "Enabled").toString(); + String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals(enabled, "true"); + assertEquals(enabledTracers, "OTEL"); + assertEquals(sensitiveDataEnabled, "true"); + + server.setAttribute(objectName, new Attribute("Enabled", false)); + server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); + + assertEquals(false, configuration.getEnabled()); + assertEquals(false, configuration.getSensitiveDataEnabled()); + + assertEquals("OTEL", configuration.getEnabledTracers()); + assertEquals(false, configuration.getSensitiveDataEnabled()); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + + // Singleton + configuration.setEnabled(true); + configuration.setSensitiveDataEnabled(true); + + enabled = server.getAttribute(objectName, "Enabled").toString(); + enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals("true", enabled); + assertEquals("OTEL", enabledTracers); + assertEquals("true", sensitiveDataEnabled); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + + } +} diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java index 7a23cdb0..b9f91bd9 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java @@ -1,8 +1,47 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability; import static org.junit.jupiter.api.Assertions.assertEquals; import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; import javax.management.Attribute; import javax.management.MBeanServer; @@ -10,68 +49,73 @@ import org.junit.jupiter.api.Test; -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.TracerType; +import oracle.jdbc.TraceEventListener; import oracle.jdbc.spi.TraceEventListenerProvider; +import oracle.jdbc.spi.OracleResourceProvider.Parameter; public class ObservabilityConfigurationTest { + private static final String INSTANCE_NAME = "test-instance"; + + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + // Set system properties before starting static { System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "JFR"); System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "true"); - System.out.println(ObservabilityConfiguration.getInstance().getEnabledTracers()); } @Test public void testConfiguration() throws Exception { - - System.out.println(ObservabilityConfiguration.getInstance().getEnabledTracers()); + + // Create a TraceEventListner named test-instance TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); - provider.getTraceEventListener(null); + Map parameters = new HashMap<>(); + provider.getParameters().forEach(parameter -> { + parameters.put(parameter, (CharSequence)INSTANCE_NAME); + }); + ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); - System.out.println(ObservabilityConfiguration.getInstance().getEnabledTracers()); - assertEquals("JFR", ObservabilityConfiguration.getInstance().getEnabledTracers()); - assertEquals(true, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); + // Get the configuration object + ObservabilityConfiguration configuration = ObservabilityTraceEventListener.getObservabilityConfiguration(INSTANCE_NAME); - assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(TracerType.JFR, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + // Verify that the configuration matches the configuration set using system properties + assertEquals("JFR", configuration.getEnabledTracers()); + assertEquals(true, configuration.getSensitiveDataEnabled()); + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("JFR", configuration.getEnabledTracersAsList().get(0)); - // MBean - ObjectName objectName = new ObjectName( - "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration"); - MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + // Get the MBean for the configuration + ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); + + // Get configuration using MBean and check that it matches the configuration set using system properties String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); - assertEquals(enabledTracers, "JFR"); assertEquals(sensitiveDataEnabled, "true"); + // Update configuration using MBean server.setAttribute(objectName, new Attribute("EnabledTracers", "OTEL,JFR")); server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); - assertEquals("OTEL,JFR", ObservabilityConfiguration.getInstance().getEnabledTracers()); - assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); + // check that the values have been updated using the instance of the configuration + assertEquals("OTEL,JFR", configuration.getEnabledTracers()); + assertEquals(false, configuration.getSensitiveDataEnabled()); + assertEquals(2, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + assertEquals("JFR", configuration.getEnabledTracersAsList().get(1)); - assertEquals("OTEL,JFR", ObservabilityConfiguration.getInstance().getEnabledTracers()); - assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); - - assertEquals(2, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); - assertEquals(TracerType.JFR, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(1)); - - // Singleton - ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); - ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(true); + // Update the configuration using the instance of the configuration + configuration.setEnabledTracers("OTEL"); + configuration.setSensitiveDataEnabled(true); + // Check that the values returned by the MBean correspond to the values set using the instance enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); - assertEquals("OTEL", enabledTracers); assertEquals("true", sensitiveDataEnabled); - - assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); } diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java index d2e58eda..7a765d5a 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java @@ -1,3 +1,40 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability; public enum ObservabilityTestProperties { diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java index 513de9ef..74b4a592 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -1,8 +1,46 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ package oracle.jdbc.provider.observability; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; @@ -35,7 +73,6 @@ import oracle.jdbc.DatabaseFunction; import oracle.jdbc.driver.OracleConnection; import oracle.jdbc.provider.TestProperties; -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; public class ObservabilityTraceEventListenerTest { String url = TestProperties.getOrAbort(ObservabilityTestProperties.OBSERVABILITY_URL); @@ -68,13 +105,14 @@ public class ObservabilityTraceEventListenerTest { @ValueSource(booleans = {true, false}) public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { - ObservabilityConfiguration.getInstance().setEnabledTracers("JFR"); - ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(sensitiveDataEnabled); + System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "JFR"); + System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", String.valueOf(sensitiveDataEnabled)); Configuration configuration = Configuration.getConfiguration("default"); String connectionId = null; try (Recording recording = new Recording(configuration)) { recording.start(); - try (Connection connection = DriverManager.getConnection(url, userName, password); + String jfrUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider&oracle.jdbc.provider.traceEventListener.name=test-jfr" + sensitiveDataEnabled; + try (Connection connection = DriverManager.getConnection(jfrUrl, userName, password); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { connectionId = ((OracleConnection)connection).getNetConnectionId(); @@ -84,16 +122,17 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { } recording.stop(); recording.dump(Path.of("dump" + sensitiveDataEnabled + ".jfr")); - + try (RecordingFile recordingFile = new RecordingFile(Path.of("dump" + sensitiveDataEnabled + ".jfr"))) { + int countRoundTrips = 0; while (recordingFile.hasMoreEvents()) { RecordedEvent event = recordingFile.readEvent(); if (event.getEventType().getCategoryNames().contains("Round trips")) { + countRoundTrips++; switch (event.getEventType().getName()) { case SESSION_KEY: assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); - assertNull(event.getString("tenant")); assertNull(event.getString("sqlID")); assertNull(event.getString("originalSQLText")); assertNull(event.getString("actualSQLText")); @@ -102,7 +141,6 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { case AUTH_CALL: assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); - assertNull(event.getString("tenant")); assertNull(event.getString("sqlID")); assertNull(event.getString("originalSQLText")); assertNull(event.getString("actualSQLText")); @@ -112,7 +150,6 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { case EXECUTE_QUERY: assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); - assertNotNull(event.getString("tenant")); assertNotNull(event.getString("sqlID")); assertEquals(sensitiveDataEnabled, event.getString("originalSQLText") != null); assertEquals(sensitiveDataEnabled, event.getString("actualSQLText") != null); @@ -121,7 +158,6 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { case LOGOFF: assertEquals(connectionId, event.getString("connectionID")); assertNotNull(event.getString("databaseOperation")); - assertNotNull(event.getString("tenant")); assertNull(event.getString("sqlID")); assertNull(event.getString("originalSQLText")); assertNull(event.getString("actualSQLText")); @@ -132,6 +168,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { } } } + assertTrue(countRoundTrips > 0, "Application should have performed at least one round trip"); } } @@ -141,9 +178,9 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { @ValueSource(booleans = {true, false}) public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.clearInvocations(tracer, spanBuilder); - ObservabilityConfiguration.getInstance().setEnabledTracers("OTEL"); - ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(sensitiveDataEnabled); - String otelUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider"; + System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "OTEL"); + System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", String.valueOf(sensitiveDataEnabled)); + String otelUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider&oracle.jdbc.provider.traceEventListener.name=test-otel-" + sensitiveDataEnabled ; String connectionId = null; try (Connection connection = DriverManager.getConnection(otelUrl, userName, password); Statement statement = connection.createStatement(); @@ -171,7 +208,7 @@ public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); } - Mockito.verify(span, atLeast(4)).end(Mockito.any()); + Mockito.verify(span, atLeast(4)).end(); } diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java deleted file mode 100644 index 2719e235..00000000 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/opentelemetry/BackwardCompatibilityTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package oracle.jdbc.provider.opentelemetry; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.lang.management.ManagementFactory; - -import javax.management.Attribute; -import javax.management.MBeanServer; -import javax.management.ObjectName; - -import org.junit.jupiter.api.Test; - -import oracle.jdbc.provider.observability.configuration.ObservabilityConfiguration; -import oracle.jdbc.provider.observability.tracers.TracerType; -import oracle.jdbc.spi.TraceEventListenerProvider; - -public class BackwardCompatibilityTest { - @Test - public void testConfiguration() throws Exception { - - // System properties - System.setProperty("oracle.jdbc.provider.opentelemetry.enabled", "true"); - System.setProperty("oracle.jdbc.provider.opentelemetry.sensitive-enabled", "true"); - - TraceEventListenerProvider provider = new OpenTelemetryTraceEventListenerProvider(); - provider.getTraceEventListener(null); - - assertEquals(true, ObservabilityConfiguration.getInstance().getEnabled()); - assertEquals("OTEL", ObservabilityConfiguration.getInstance().getEnabledTracers()); - assertEquals(true, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); - - assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); - - // MBean - ObjectName objectName = new ObjectName( - "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener"); - MBeanServer server = ManagementFactory.getPlatformMBeanServer(); - String enabled = server.getAttribute(objectName, "Enabled").toString(); - String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); - String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); - - assertEquals(enabled, "true"); - assertEquals(enabledTracers, "OTEL"); - assertEquals(sensitiveDataEnabled, "true"); - - server.setAttribute(objectName, new Attribute("Enabled", false)); - server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); - - assertEquals(false, ObservabilityConfiguration.getInstance().getEnabled()); - assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); - - assertEquals("OTEL", ObservabilityConfiguration.getInstance().getEnabledTracers()); - assertEquals(false, ObservabilityConfiguration.getInstance().getSensitiveDataEnabled()); - - assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); - - // Singleton - ObservabilityConfiguration.getInstance().setEnabled(true); - ObservabilityConfiguration.getInstance().setSensitiveDataEnabled(true); - - enabled = server.getAttribute(objectName, "Enabled").toString(); - enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); - sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); - - assertEquals("true", enabled); - assertEquals("OTEL", enabledTracers); - assertEquals("true", sensitiveDataEnabled); - - assertEquals(1, ObservabilityConfiguration.getInstance().getEnabledTracersSet().size()); - assertEquals(TracerType.OTEL, ObservabilityConfiguration.getInstance().getEnabledTracersSet().get(0)); - - } -} diff --git a/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java b/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java index 00ac32c7..ddd1a7d4 100644 --- a/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java +++ b/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java @@ -73,9 +73,9 @@ * * * The system properties - * {@value #OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED} + * {@value #OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED} * and - * {@value #OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED} + * {@value #OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED} * can be used * to enable/disable this listener and the use of sensitive data by this * listener. A MBean registered by the {@link @@ -88,12 +88,12 @@ public class OpenTelemetryTraceEventListener /** * Name of the property used to enable or disable this listener. */ - public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED = "oracle.jdbc.provider.opentelemetry.enabled"; + public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED = "oracle.jdbc.provider.opentelemetry.enabled"; /** * Name of the property used to enable or disable sensitive data for this * listener. */ - public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; + public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; private static final String TRACE_KEY = "clientcontext.ora$opentelem$tracectx"; @@ -132,8 +132,8 @@ private enum Configuration { private AtomicBoolean sensitiveDataEnabled; private Configuration(boolean enabled, boolean sensitiveDataEnabled) { - String enabledStr = System.getProperty(OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED); - String sensitiveStr = System.getProperty(OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED); + String enabledStr = System.getProperty(OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED); + String sensitiveStr = System.getProperty(OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED); this.enabled = new AtomicBoolean(enabledStr == null ? enabled : Boolean.parseBoolean(enabledStr)); this.sensitiveDataEnabled = new AtomicBoolean( sensitiveStr == null ? sensitiveDataEnabled : Boolean.parseBoolean(sensitiveStr)); diff --git a/ojdbc-provider-opentelemetry/src/test/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerTest.java b/ojdbc-provider-opentelemetry/src/test/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerTest.java index 8bac137e..3fd05dba 100644 --- a/ojdbc-provider-opentelemetry/src/test/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerTest.java +++ b/ojdbc-provider-opentelemetry/src/test/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerTest.java @@ -97,8 +97,8 @@ public void setupMocks() throws Exception { @Test void testPropertiesDisabled() throws Exception { - System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED, "false"); - System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "false"); + System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED, "false"); + System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "false"); OpenTelemetryTraceEventListener traceEventListener = new OpenTelemetryTraceEventListener(tracer); Assertions.assertFalse(traceEventListener.isEnabled(), "Set to false using system property"); Assertions.assertFalse(traceEventListener.isSensitiveDataEnabled(), "Set to false using system property"); @@ -106,8 +106,8 @@ void testPropertiesDisabled() throws Exception { @Test void testPropertiesEnabled() throws Exception { - System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED, "true"); - System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "true"); + System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED, "true"); + System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "true"); OpenTelemetryTraceEventListener traceEventListener = new OpenTelemetryTraceEventListener(tracer); Assertions.assertTrue(traceEventListener.isEnabled(), "Set to false using system property"); Assertions.assertTrue(traceEventListener.isSensitiveDataEnabled(), "Set to false using system property"); From 65c7765c765bb20007ec3121bb832ef4fa7ac9b0 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Fri, 7 Mar 2025 09:33:06 +0100 Subject: [PATCH 21/82] Changed action version for deprecated action --- .github/workflows/run-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index ffaaedf7..1e5dd773 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -18,7 +18,7 @@ jobs: with: java-version: 11 - name: Cache Maven packages - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} From f6e972f4bfbdc4f45adc0e16bd9f5885f85b0e25 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Fri, 7 Mar 2025 09:41:51 +0100 Subject: [PATCH 22/82] Set version in pom files correctly --- ojdbc-provider-jackson-oson/pom.xml | 1 - ojdbc-provider-observability/pom.xml | 2 -- 2 files changed, 3 deletions(-) diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index 179e20b7..6d6f9b6f 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -21,7 +21,6 @@ com.oracle.database.jdbc ojdbc-provider-common - 1.0.3 com.oracle.database.jdbc diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index ead8150e..8a7601cb 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -13,7 +13,6 @@ Oracle JDBC Observability Provider com.oracle.database.jdbc ojdbc-provider-observability - 1.0.3 1.44.1 @@ -49,7 +48,6 @@ ojdbc-provider-common com.oracle.database.jdbc - 1.0.2 tests test-jar From 8f7b15db374e3bbfc4e37f148b29f762a86c0017 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Fri, 7 Mar 2025 09:52:22 +0100 Subject: [PATCH 23/82] Fixed tests when running in the same JVM --- .../provider/observability/ObservabilityConfigurationTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java index b9f91bd9..8c5904de 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java @@ -49,13 +49,12 @@ import org.junit.jupiter.api.Test; -import oracle.jdbc.TraceEventListener; import oracle.jdbc.spi.TraceEventListenerProvider; import oracle.jdbc.spi.OracleResourceProvider.Parameter; public class ObservabilityConfigurationTest { - private static final String INSTANCE_NAME = "test-instance"; + private static final String INSTANCE_NAME = "configuration-test-instance"; MBeanServer server = ManagementFactory.getPlatformMBeanServer(); From 1157aa3c553e2d820fc2de2be24a203f829c21bf Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Fri, 7 Mar 2025 18:28:06 +0100 Subject: [PATCH 24/82] Unique identifier and refactoring. --- ojdbc-provider-observability/README.md | 12 +- .../ObservabilityConfiguration.java | 2 +- .../ObservabilityTraceEventListener.java | 89 +++++------ ...servabilityTraceEventListenerProvider.java | 31 ++-- ...enTelemetryTraceEventListenerProvider.java | 4 +- .../observability/tracers/jfr/JFRTracer.java | 13 +- .../tracers/otel/OTelTracer.java | 10 +- .../BackwardCompatibilityTest.java | 5 +- .../ObservabilityConfigurationTest.java | 146 ++++++++++++++---- .../ObservabilityTraceEventListenerTest.java | 13 +- .../OpenTelemetryTraceEventListener.java | 2 +- 11 files changed, 216 insertions(+), 111 deletions(-) diff --git a/ojdbc-provider-observability/README.md b/ojdbc-provider-observability/README.md index cc17d12f..dcf99623 100644 --- a/ojdbc-provider-observability/README.md +++ b/ojdbc-provider-observability/README.md @@ -59,6 +59,16 @@ application's classpath and set the following connection property: oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider ``` +A unique identifier connection property allows to identify the trace event +listener. Connections setting the same unique identifier use the same trace +event listener and share the same configuration. + +```java +oracle.jdbc.provider.traceEventListener.unique_identifier= +``` + +If no unique identifier is provided, the unique idetifier "default" is used. + ## Configuration The provider can be configured by: @@ -69,7 +79,7 @@ System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "t ``` * or using the MBean. ```java -ObservabilityTraceEventListener listener = ObservabilityTraceEventListener.getTraceEventListener(""); +ObservabilityTraceEventListener listener = ObservabilityTraceEventListener.getTraceEventListener(""); ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); MBeanServer server = ManagementFactory.getPlatformMBeanServer(); server.setAttribute(objectName, new Attribute("EnabledTracers", "OTEL,JFR")); diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java index 4f417b7d..0fc81a11 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java @@ -256,7 +256,7 @@ public String getEnabledTracers() { public void setEnabledTracers(String tracers){ observabilityConfigurationLock.lock(); try { - String[] items = tracers.replaceAll("\\s", tracers).split(","); + String[] items = tracers.replaceAll("\\s", "").split(","); enabledTracers = Arrays.asList(items); } finally { observabilityConfigurationLock.unlock(); diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java index 1032ef3c..9fd33725 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -99,13 +99,18 @@ public class ObservabilityTraceEventListener implements TraceEventListener { /** * MBean object name format */ - private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration,name=%s"; - private static final String MBEAN_OBJECT_NAME_OLD = "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener,name=%s"; + private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration,uniqueIdentifier=%s"; + private static final String MBEAN_OBJECT_NAME_OTEL = "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener,uniqueIdentifier=%s"; + + /** + * Default unique identifier, if parameter not set. + */ + static final CharSequence DEFAULT_UNIQUE_IDENTIFIER = "default"; /** * MBean server */ - protected static final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + private static final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); /** * Logger @@ -118,24 +123,30 @@ public class ObservabilityTraceEventListener implements TraceEventListener { */ private final ObservabilityConfiguration configuration; + /** + * Static map linking the name of the listener to its instance. + */ + private static final Map INSTANCES + = new ConcurrentHashMap<>(); - private final String mBeanObjectName; + private ObjectName mBeanObjectName; /** * Create a trace event listener identified by the given name. - * @param name the name of the trace event listener. + * @param uniqueIdentifier the name of the trace event listener. * @param configurationType configuration type for backward compatibility. */ - private ObservabilityTraceEventListener(String name, ObservabilityConfigurationType configurationType) { + private ObservabilityTraceEventListener(String uniqueIdentifier, + ObservabilityConfigurationType configurationType) { // Create the configuration for this instance and register MBean - mBeanObjectName = ObservabilityConfigurationType.OTEL.equals(configurationType) ? - String.format(MBEAN_OBJECT_NAME_OLD, name) : - String.format(MBEAN_OBJECT_NAME, name); + final String mBeanName = ObservabilityConfigurationType.OTEL.equals(configurationType) ? + String.format(MBEAN_OBJECT_NAME_OTEL, uniqueIdentifier) : + String.format(MBEAN_OBJECT_NAME, uniqueIdentifier); this.configuration = new ObservabilityConfiguration(configurationType); try { - final ObjectName objectName = new ObjectName(mBeanObjectName); - if (!server.isRegistered(objectName)) { - server.registerMBean(configuration, objectName); + mBeanObjectName = new ObjectName(mBeanName); + if (!server.isRegistered(mBeanObjectName)) { + server.registerMBean(configuration, mBeanObjectName); logger.log(Level.FINEST, "MBean and tracers registered"); } } catch (InstanceAlreadyExistsException | MBeanRegistrationException | @@ -147,12 +158,6 @@ private ObservabilityTraceEventListener(String name, ObservabilityConfigurationT configuration.registerTracer(new JFRTracer(configuration)); } - /** - * Static map linking the name of the listener to its instance. - */ - private static final Map INSTANCES - = new ConcurrentHashMap<>(); - @Override @SuppressWarnings("unchecked") public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { @@ -220,53 +225,51 @@ public boolean isDesiredEvent(JdbcExecutionEvent event) { return true; } + /** - * Returns the trace event listener identified by the given name. + * Returns the MBean object name assiciated with the configuration of the + * listener. * - * @param name the name of the listener. - * @return the trace event listener identified by the given name, or {@code - * null} if no trace event listener with that name was found. + * @return the MBean object name. */ - public static ObservabilityTraceEventListener getTraceEventListener(String name) { - return INSTANCES.get(name); + public ObjectName getMBeanObjectName() { + return mBeanObjectName; } /** - * Returns the configuration for a given listener. + * Returns the listener's configuration. * - * @param name the name of the listener. - * @return then configuration instance associated to that listener, or {@code - * null} if not trace event listener with that name was found. + * @return the configuration instance associated to the listener. */ - public static ObservabilityConfiguration getObservabilityConfiguration(String name) { - ObservabilityTraceEventListener listener = INSTANCES.get(name); - if (listener != null) { - return listener.configuration; - } - return null; + public ObservabilityConfiguration getObservabilityConfiguration() { + return configuration; } + /** - * Returns the MBean object name assiciated with the configuration of the - * listener. + * Returns the trace event listener identified by the given unique idetifier. * - * @return the MBean object name. + * @param uniqueIdentifier the unique identifier, if no unique identifier was + * provided as a connection property, the unique identifier is "default". + * @return the trace event listener identified by the given unique idetifier, + * or {@code null} if no trace event listener with that unique idetifier was + * found. */ - public String getMBeanObjectName() { - return mBeanObjectName; + public static ObservabilityTraceEventListener getTraceEventListener(String uniqueIdentifier) { + return INSTANCES.get(uniqueIdentifier); } - /** + /** * Gets or creates an instance of {@link ObservabilityTraceEventListener} * associated to the name. - * @param name the name of the listener instance. + * @param uniqueIdentifier the name of the listener instance. * @param configurationType configuration type for backward compatibility. * * @return an instance of {@link ObservabilityTraceEventListener}. */ - static ObservabilityTraceEventListener getOrCreateInstance(String name, + static ObservabilityTraceEventListener getOrCreateInstance(String uniqueIdentifier, ObservabilityConfigurationType configurationType) { - return INSTANCES.computeIfAbsent(name, n -> new ObservabilityTraceEventListener(n, configurationType)); + return INSTANCES.computeIfAbsent(uniqueIdentifier, n -> new ObservabilityTraceEventListener(n, configurationType)); } } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java index af4f6f53..26b726a7 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -37,13 +37,9 @@ */ package oracle.jdbc.provider.observability; -import java.lang.management.ManagementFactory; import java.util.Collection; import java.util.Collections; import java.util.Map; -import java.util.logging.Logger; - -import javax.management.MBeanServer; import oracle.jdbc.TraceEventListener; import oracle.jdbc.provider.observability.ObservabilityConfiguration.ObservabilityConfigurationType; @@ -63,13 +59,13 @@ public class ObservabilityTraceEventListenerProvider implements TraceEventListen /** * Name Parameter name, identifies the listener */ - private static final String NAME_PARAMETER_NAME = "NAME"; + private static final String UNIQUE_IDENTIFIER_PARAMETER_NAME = "UNIQUE_IDENTIFIER"; /** - * Name Parameter, identifies the listener + * Unique identifier, identifies a {@link ObservabilityTraceEventListener}. */ - protected static final Parameter nameParameter = new Parameter() { + protected static final Parameter uniqueIdentifierParameter = new Parameter() { @Override public boolean isSensitive() { @@ -78,26 +74,25 @@ public boolean isSensitive() { @Override public String name() { - return NAME_PARAMETER_NAME; + return UNIQUE_IDENTIFIER_PARAMETER_NAME; } }; - - + /** * Constructs a new instance of ObservabilityTraceEventListenerProvider. This * constructor will be called by the driver's service provider to create a new * instance. */ - public ObservabilityTraceEventListenerProvider() { - - - } - + public ObservabilityTraceEventListenerProvider() { } + @Override public TraceEventListener getTraceEventListener(Map map) { - String name = map.get(nameParameter).toString(); - return ObservabilityTraceEventListener.getOrCreateInstance(name, ObservabilityConfigurationType.OBSERVABILITY); + String uniqueIdentifier = + map.getOrDefault( + uniqueIdentifierParameter, + (CharSequence)ObservabilityTraceEventListener.DEFAULT_UNIQUE_IDENTIFIER).toString(); + return ObservabilityTraceEventListener.getOrCreateInstance(uniqueIdentifier, ObservabilityConfigurationType.OBSERVABILITY); } @Override @@ -107,7 +102,7 @@ public String getName() { @Override public Collection getParameters() { - return Collections.singletonList(nameParameter); + return Collections.singletonList(uniqueIdentifierParameter); } } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java index 3e3d3369..b815bcea 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java @@ -70,8 +70,8 @@ public String getName() { @Override public TraceEventListener getTraceEventListener(Map map) { - String name = map.get(nameParameter).toString(); - return ObservabilityTraceEventListener.getOrCreateInstance(name, + String uniqueIdentifier = map.get(uniqueIdentifierParameter).toString(); + return ObservabilityTraceEventListener.getOrCreateInstance(uniqueIdentifier, ObservabilityConfigurationType.OTEL); } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java index 814c4e99..2c195506 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java @@ -37,6 +37,9 @@ */ package oracle.jdbc.provider.observability.tracers.jfr; +import java.util.logging.Level; +import java.util.logging.Logger; + import jdk.jfr.Event; import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; import oracle.jdbc.TraceEventListener.Sequence; @@ -55,6 +58,11 @@ public class JFRTracer implements ObservabilityTracer{ */ private final ObservabilityConfiguration configuration; + /** + * Logger. + */ + private static Logger logger = Logger.getLogger(JFRTracer.class.getPackageName()); + /** * Creates a new instance. * @@ -83,9 +91,12 @@ public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Objec event.setValues(traceContext, configuration); // stop the measuring event durating and commit event.commit(); + } else { + logger.log(Level.WARNING, "Unknown or null user context received from the driver on " + + "database operation: " + traceContext.databaseOperation()); } + return null; } - return null; } @Override diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index f4dd8eb8..f922004f 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -89,7 +89,7 @@ public class OTelTracer implements ObservabilityTracer { /** * Logger. */ - private static Logger logger = Logger.getLogger(OTelTracer.class.getName()); + private static Logger logger = Logger.getLogger(OTelTracer.class.getPackageName()); /** * Configuraiton @@ -123,12 +123,14 @@ public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Objec return span; } else { // End the Span after the round-trip. - if (userContext instanceof Span) { + if (userContext != null) { final Span span = (Span) userContext; span.setStatus(traceContext.isCompletedExceptionally() ? StatusCode.ERROR : StatusCode.OK); span.end(); + } else { + logger.log(Level.WARNING, "Unknown or null user context received from the driver on " + + "database operation: " + traceContext.databaseOperation()); } - logger.log(Level.WARNING, "Unknown or null user context received from the driver."); return null; } } @@ -195,7 +197,6 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { .setAttribute("thread.name", Thread.currentThread().getName()) .setAttribute("Connection ID", traceContext.getConnectionId()) .setAttribute("Database Operation", traceContext.databaseOperation()) - .setAttribute("Database User", traceContext.user()) .setAttribute("Database Tenant", traceContext.tenant()) .setAttribute("SQL ID", traceContext.getSqlId()); @@ -203,6 +204,7 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { if (configuration.getSensitiveDataEnabled()) { logger.log(Level.FINEST, "Sensitive information on"); spanBuilder + .setAttribute("Database User", traceContext.user()) .setAttribute("Original SQL Text", traceContext.originalSqlText()) .setAttribute("Actual SQL Text", traceContext.actualSqlText()); } diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java index ca3f32cc..81e4e7f9 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java @@ -68,8 +68,7 @@ public void testConfiguration() throws Exception { parameters.put(parameter, (CharSequence)INSTANCE_NAME); }); ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); - - ObservabilityConfiguration configuration = ObservabilityTraceEventListener.getObservabilityConfiguration(INSTANCE_NAME); + ObservabilityConfiguration configuration = listener.getObservabilityConfiguration(); assertEquals(true, configuration.getEnabled()); assertEquals("OTEL", configuration.getEnabledTracers()); @@ -79,7 +78,7 @@ public void testConfiguration() throws Exception { assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); // MBean - ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); + ObjectName objectName = listener.getMBeanObjectName(); MBeanServer server = ManagementFactory.getPlatformMBeanServer(); String enabled = server.getAttribute(objectName, "Enabled").toString(); String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java index 8c5904de..df8e2ab6 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java @@ -54,7 +54,9 @@ public class ObservabilityConfigurationTest { - private static final String INSTANCE_NAME = "configuration-test-instance"; + private static final String INSTANCE_NAME = "configuration-single"; + private static final String INSTANCE_NAME_1 = "configuration-test-one"; + private static final String INSTANCE_NAME_2 = "configuration-test-two"; MBeanServer server = ManagementFactory.getPlatformMBeanServer(); @@ -69,52 +71,132 @@ public void testConfiguration() throws Exception { // Create a TraceEventListner named test-instance TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); - Map parameters = new HashMap<>(); - provider.getParameters().forEach(parameter -> { - parameters.put(parameter, (CharSequence)INSTANCE_NAME); - }); - ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); + ObservabilityTraceEventListener listener = createTraceEventListener(provider, INSTANCE_NAME); // Get the configuration object - ObservabilityConfiguration configuration = ObservabilityTraceEventListener.getObservabilityConfiguration(INSTANCE_NAME); + ObservabilityConfiguration configuration = listener.getObservabilityConfiguration(); - // Verify that the configuration matches the configuration set using system properties - assertEquals("JFR", configuration.getEnabledTracers()); - assertEquals(true, configuration.getSensitiveDataEnabled()); - assertEquals(1, configuration.getEnabledTracersAsList().size()); - assertEquals("JFR", configuration.getEnabledTracersAsList().get(0)); + // Verify that listener one is configured with values from system properties + verifyConfiguration(configuration, listener.getMBeanObjectName(), "JFR", 1, true); - // Get the MBean for the configuration - ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); + // Update configuration using MBean + server.setAttribute(listener.getMBeanObjectName(), new Attribute("EnabledTracers", "OTEL, JFR")); + server.setAttribute(listener.getMBeanObjectName(), new Attribute("SensitiveDataEnabled", false)); - // Get configuration using MBean and check that it matches the configuration set using system properties - String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); - String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); - assertEquals(enabledTracers, "JFR"); - assertEquals(sensitiveDataEnabled, "true"); + // check that the values have been updated using the instance of the configuration + verifyConfiguration(configuration, listener.getMBeanObjectName(), "OTEL,JFR", 2, false); + + // Update the configuration using the instance of the configuration + configuration.setEnabledTracers("OTEL"); + configuration.setSensitiveDataEnabled(true); + + // Check that the values returned by the MBean correspond to the values set using the instance + verifyConfiguration(configuration, listener.getMBeanObjectName(), "OTEL", 1, true); + + } + + @Test + public void testConfigurationWith2Instances() throws Exception { + + // Create a TraceEventListner named test-instance + TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); + ObservabilityTraceEventListener listener1 = createTraceEventListener(provider, INSTANCE_NAME_1); + ObservabilityTraceEventListener listener2 = createTraceEventListener(provider, INSTANCE_NAME_2); + + // Verify that listener one is configured with values from system properties + verifyConfiguration(listener1.getObservabilityConfiguration(), + listener1.getMBeanObjectName(), "JFR", 1, true); + + // Verify that listener two is configured with values from system properties + verifyConfiguration(listener2.getObservabilityConfiguration(), + listener2.getMBeanObjectName(), "JFR", 1, true); + + // Update configuration one using MBean + server.setAttribute(listener1.getMBeanObjectName(), new Attribute("EnabledTracers", "OTEL,JFR")); + server.setAttribute(listener1.getMBeanObjectName(), new Attribute("SensitiveDataEnabled", false)); + + // Verify that listener one's configuration with the values set using the MBean + verifyConfiguration(listener1.getObservabilityConfiguration(), + listener1.getMBeanObjectName(), "OTEL,JFR", 2, false); + + // Verify that listener two's configuration did not change + verifyConfiguration(listener2.getObservabilityConfiguration(), + listener2.getMBeanObjectName(), "JFR", 1, true); + + // Update the configuration using the instance of the configuration + ObservabilityConfiguration configuration2 = listener2.getObservabilityConfiguration(); + configuration2.setEnabledTracers("OTEL"); + configuration2.setSensitiveDataEnabled(false); + + // Verify that listener one's configuration did not change + verifyConfiguration(listener1.getObservabilityConfiguration(), + listener1.getMBeanObjectName(), "OTEL,JFR", 2, false); + + // Verify that listener two's configuration has been updated + verifyConfiguration(listener2.getObservabilityConfiguration(), + listener2.getMBeanObjectName(), "OTEL", 1, false); + + } + + @Test + public void testDefaultUniqueIdentifier() throws Exception { + + // Create a TraceEventListner named test-instance + TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); + ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(new HashMap<>()); + + // Get the configuration object + ObservabilityConfiguration configuration = listener.getObservabilityConfiguration(); + + // Verify that listener one is configured with values from system properties + verifyConfiguration(configuration, listener.getMBeanObjectName(), "JFR", 1, true); // Update configuration using MBean - server.setAttribute(objectName, new Attribute("EnabledTracers", "OTEL,JFR")); - server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); + server.setAttribute(listener.getMBeanObjectName(), new Attribute("EnabledTracers", "OTEL, JFR")); + server.setAttribute(listener.getMBeanObjectName(), new Attribute("SensitiveDataEnabled", false)); // check that the values have been updated using the instance of the configuration - assertEquals("OTEL,JFR", configuration.getEnabledTracers()); - assertEquals(false, configuration.getSensitiveDataEnabled()); - assertEquals(2, configuration.getEnabledTracersAsList().size()); - assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); - assertEquals("JFR", configuration.getEnabledTracersAsList().get(1)); + verifyConfiguration(configuration, listener.getMBeanObjectName(), "OTEL,JFR", 2, false); // Update the configuration using the instance of the configuration configuration.setEnabledTracers("OTEL"); configuration.setSensitiveDataEnabled(true); // Check that the values returned by the MBean correspond to the values set using the instance - enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); - sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); - assertEquals("OTEL", enabledTracers); - assertEquals("true", sensitiveDataEnabled); - assertEquals(1, configuration.getEnabledTracersAsList().size()); - assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + verifyConfiguration(configuration, listener.getMBeanObjectName(), "OTEL", 1, true); + + } + + private ObservabilityTraceEventListener createTraceEventListener(TraceEventListenerProvider provider, + String instanceName) { + Map parameters = new HashMap<>(); + provider.getParameters().forEach(parameter -> { + parameters.put(parameter, (CharSequence)instanceName); + }); + return (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); + } + + private void verifyConfiguration(ObservabilityConfiguration configuration, + ObjectName mBeanObjectName, + String expectedTracers, + int expectedTracerCount, + boolean expectedSensitiveDataEnabled) throws Exception{ + + String[] expectedTracersArray = expectedTracers.split(","); + // Verify that the configuration matches the configuration set using system properties + assertEquals(expectedTracers, configuration.getEnabledTracers()); + assertEquals(expectedSensitiveDataEnabled, configuration.getSensitiveDataEnabled()); + assertEquals(expectedTracerCount, configuration.getEnabledTracersAsList().size()); + assertEquals(expectedTracerCount, expectedTracersArray.length, "Wrong number of tracers."); + for (int i = 0; i < expectedTracerCount; i++) { + assertEquals(expectedTracersArray[i], configuration.getEnabledTracersAsList().get(i)); + } + + // Get configuration using MBean and check that it matches the configuration set using system properties + String enabledTracers = server.getAttribute(mBeanObjectName, "EnabledTracers").toString(); + String sensitiveDataEnabled = server.getAttribute(mBeanObjectName, "SensitiveDataEnabled").toString(); + assertEquals(enabledTracers, expectedTracers); + assertEquals(Boolean.parseBoolean(sensitiveDataEnabled), expectedSensitiveDataEnabled); } diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java index 74b4a592..52432893 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -111,7 +111,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { String connectionId = null; try (Recording recording = new Recording(configuration)) { recording.start(); - String jfrUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider&oracle.jdbc.provider.traceEventListener.name=test-jfr" + sensitiveDataEnabled; + String jfrUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider&oracle.jdbc.provider.traceEventListener.unique_identifier=test-jfr" + sensitiveDataEnabled; try (Connection connection = DriverManager.getConnection(jfrUrl, userName, password); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { @@ -122,7 +122,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { } recording.stop(); recording.dump(Path.of("dump" + sensitiveDataEnabled + ".jfr")); - + try (RecordingFile recordingFile = new RecordingFile(Path.of("dump" + sensitiveDataEnabled + ".jfr"))) { int countRoundTrips = 0; while (recordingFile.hasMoreEvents()) { @@ -171,7 +171,7 @@ public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { assertTrue(countRoundTrips > 0, "Application should have performed at least one round trip"); } } - + } @ParameterizedTest(name = "OTELTraceTest - {arguments}") @@ -180,7 +180,7 @@ public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.clearInvocations(tracer, spanBuilder); System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "OTEL"); System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", String.valueOf(sensitiveDataEnabled)); - String otelUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider&oracle.jdbc.provider.traceEventListener.name=test-otel-" + sensitiveDataEnabled ; + String otelUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider&oracle.jdbc.provider.traceEventListener.unique_identifier=test-otel-" + sensitiveDataEnabled ; String connectionId = null; try (Connection connection = DriverManager.getConnection(otelUrl, userName, password); Statement statement = connection.createStatement(); @@ -207,10 +207,13 @@ public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.verify(spanBuilder, Mockito.times(4)).setAttribute("Database User", userName); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); + } else { + Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Database User", userName); + Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); + Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); } Mockito.verify(span, atLeast(4)).end(); - } private static void configureOTEL() { diff --git a/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java b/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java index ddd1a7d4..4c63944d 100644 --- a/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java +++ b/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java @@ -107,7 +107,7 @@ public class OpenTelemetryTraceEventListener } }; - private static Logger logger = Logger.getLogger(OpenTelemetryTraceEventListener.class.getName()); + private static Logger logger = Logger.getLogger(OpenTelemetryTraceEventListener.class.getPackageName()); private Tracer tracer; From dd9de05c36854c47dbc07096069738848c223dff Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 10 Mar 2025 11:48:04 +0100 Subject: [PATCH 25/82] Corrected copyright --- ojdbc-provider-observability/example-test.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ojdbc-provider-observability/example-test.properties b/ojdbc-provider-observability/example-test.properties index feafff3b..85a2cb3e 100644 --- a/ojdbc-provider-observability/example-test.properties +++ b/ojdbc-provider-observability/example-test.properties @@ -1,5 +1,5 @@ ################################################################################ -# Copyright (c) 2024 Oracle and/or its affiliates. +# Copyright (c) 2025 Oracle and/or its affiliates. # # The Universal Permissive License (UPL), Version 1.0 # From 9cb4b41ce0c5ad2d9e4a6ee03a1fdf2f4ad37543 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Sun, 7 Sep 2025 08:07:56 +0100 Subject: [PATCH 26/82] 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 27/82] 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 From bcc0e396ab72ad8a5be5c4e5afeaa06cba78eee9 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 15 Oct 2025 18:16:22 +0200 Subject: [PATCH 28/82] Preparing release 1.0.7 --- ojdbc-provider-aws/README.md | 2 +- ojdbc-provider-aws/pom.xml | 16 ++++++++-------- ojdbc-provider-azure/README.md | 2 +- ojdbc-provider-azure/pom.xml | 4 ++-- ojdbc-provider-common/pom.xml | 2 +- ojdbc-provider-gcp/README.md | 2 +- ojdbc-provider-gcp/pom.xml | 2 +- ojdbc-provider-hashicorp/README.md | 2 +- ojdbc-provider-hashicorp/pom.xml | 2 +- ojdbc-provider-jackson-oson/README.md | 4 ++-- ojdbc-provider-jackson-oson/pom.xml | 8 ++++---- ojdbc-provider-oci/README.md | 2 +- ojdbc-provider-oci/pom.xml | 4 ++-- ojdbc-provider-opentelemetry/README.md | 2 +- ojdbc-provider-opentelemetry/pom.xml | 4 ++-- ojdbc-provider-samples/pom.xml | 4 ++-- pom.xml | 6 +++--- 17 files changed, 34 insertions(+), 34 deletions(-) diff --git a/ojdbc-provider-aws/README.md b/ojdbc-provider-aws/README.md index 9bbd40ae..56de26c8 100644 --- a/ojdbc-provider-aws/README.md +++ b/ojdbc-provider-aws/README.md @@ -59,7 +59,7 @@ JDK versions. The coordinates for the latest release are: com.oracle.database.jdbc ojdbc-provider-aws - 1.0.6 + 1.0.7 ``` diff --git a/ojdbc-provider-aws/pom.xml b/ojdbc-provider-aws/pom.xml index 3be677d2..bdd13da6 100644 --- a/ojdbc-provider-aws/pom.xml +++ b/ojdbc-provider-aws/pom.xml @@ -10,7 +10,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 @@ -18,7 +18,7 @@ software.amazon.awssdk bom - 2.28.11 + 2.35.6 pom import @@ -74,13 +74,13 @@ software.amazon.awssdk appconfigdata
    - + - com.oracle.database.jdbc - ojdbc-provider-common - tests - test-jar - + com.oracle.database.jdbc + ojdbc-provider-common + tests + test-jar +
    org.junit.jupiter junit-jupiter-api diff --git a/ojdbc-provider-azure/README.md b/ojdbc-provider-azure/README.md index 6e468b95..d5276221 100644 --- a/ojdbc-provider-azure/README.md +++ b/ojdbc-provider-azure/README.md @@ -44,7 +44,7 @@ JDK versions. The coordinates for the latest release are: com.oracle.database.jdbc ojdbc-provider-azure - 1.0.6 + 1.0.7 ``` diff --git a/ojdbc-provider-azure/pom.xml b/ojdbc-provider-azure/pom.xml index d1492a86..5af09ab0 100644 --- a/ojdbc-provider-azure/pom.xml +++ b/ojdbc-provider-azure/pom.xml @@ -10,7 +10,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 @@ -18,7 +18,7 @@ com.azure azure-sdk-bom - 1.2.35 + 1.3.0 pom import diff --git a/ojdbc-provider-common/pom.xml b/ojdbc-provider-common/pom.xml index b79d1d9c..f5f7b262 100644 --- a/ojdbc-provider-common/pom.xml +++ b/ojdbc-provider-common/pom.xml @@ -11,7 +11,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 diff --git a/ojdbc-provider-gcp/README.md b/ojdbc-provider-gcp/README.md index 7f4db538..fea42d31 100644 --- a/ojdbc-provider-gcp/README.md +++ b/ojdbc-provider-gcp/README.md @@ -44,7 +44,7 @@ JDK versions. The coordinates for the latest release are: com.oracle.database.jdbc ojdbc-provider-gcp - 1.0.6 + 1.0.7 ``` diff --git a/ojdbc-provider-gcp/pom.xml b/ojdbc-provider-gcp/pom.xml index 7d04dd84..26729484 100644 --- a/ojdbc-provider-gcp/pom.xml +++ b/ojdbc-provider-gcp/pom.xml @@ -12,7 +12,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 diff --git a/ojdbc-provider-hashicorp/README.md b/ojdbc-provider-hashicorp/README.md index a3dc99f7..d0a65005 100644 --- a/ojdbc-provider-hashicorp/README.md +++ b/ojdbc-provider-hashicorp/README.md @@ -42,7 +42,7 @@ The coordinates for the latest release are: com.oracle.database.jdbc ojdbc-provider-hashicorp - 1.0.6 + 1.0.7 ``` diff --git a/ojdbc-provider-hashicorp/pom.xml b/ojdbc-provider-hashicorp/pom.xml index f9051092..a10a72ee 100644 --- a/ojdbc-provider-hashicorp/pom.xml +++ b/ojdbc-provider-hashicorp/pom.xml @@ -11,7 +11,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 diff --git a/ojdbc-provider-jackson-oson/README.md b/ojdbc-provider-jackson-oson/README.md index bd117ec7..2bbb2f3d 100644 --- a/ojdbc-provider-jackson-oson/README.md +++ b/ojdbc-provider-jackson-oson/README.md @@ -44,7 +44,7 @@ JDK versions. The coordinates for the latest release are: com.oracle.database.jdbc ojdbc-provider-jackson-oson - 1.0.6 + 1.0.7 ``` ### Note @@ -56,7 +56,7 @@ It can be done in maven as: com.oracle.database.jdbc ojdbc-provider-jackson-oson - 1.0.6 + 1.0.7 com.oracle.database.jdbc diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index 069ba6df..53f1ef9c 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -8,20 +8,20 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 ojdbc-provider-jackson-oson - 2.18.1 - 3.1.0 + 2.20.0 + 3.2.0 com.oracle.database.jdbc ojdbc-provider-common - 1.0.6 + 1.0.7 com.oracle.database.jdbc diff --git a/ojdbc-provider-oci/README.md b/ojdbc-provider-oci/README.md index e08d2fd3..92a8b905 100644 --- a/ojdbc-provider-oci/README.md +++ b/ojdbc-provider-oci/README.md @@ -56,7 +56,7 @@ JDK versions. The coordinates for the latest release are: com.oracle.database.jdbc ojdbc-provider-oci - 1.0.6 + 1.0.7 ``` diff --git a/ojdbc-provider-oci/pom.xml b/ojdbc-provider-oci/pom.xml index e63aac7b..a7931fb4 100644 --- a/ojdbc-provider-oci/pom.xml +++ b/ojdbc-provider-oci/pom.xml @@ -10,7 +10,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 @@ -18,7 +18,7 @@ com.oracle.oci.sdk oci-java-sdk-bom - 3.48.0 + 3.74.2 pom import diff --git a/ojdbc-provider-opentelemetry/README.md b/ojdbc-provider-opentelemetry/README.md index 9aefac57..18a94e9d 100644 --- a/ojdbc-provider-opentelemetry/README.md +++ b/ojdbc-provider-opentelemetry/README.md @@ -44,7 +44,7 @@ The coordinates for the latest release are: com.oracle.database.jdbc ojdbc-provider-opentelemetry - 1.0.6 + 1.0.7 ``` diff --git a/ojdbc-provider-opentelemetry/pom.xml b/ojdbc-provider-opentelemetry/pom.xml index 9b1a8f69..2a2b8982 100644 --- a/ojdbc-provider-opentelemetry/pom.xml +++ b/ojdbc-provider-opentelemetry/pom.xml @@ -11,11 +11,11 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 - 1.44.1 + 1.55.0 diff --git a/ojdbc-provider-samples/pom.xml b/ojdbc-provider-samples/pom.xml index de87b65a..fe28693a 100644 --- a/ojdbc-provider-samples/pom.xml +++ b/ojdbc-provider-samples/pom.xml @@ -5,13 +5,13 @@ Oracle JDBC Provider Code Samples ojdbc-provider-samples - ${project.parent.version} + 1.0.7 jar com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 diff --git a/pom.xml b/pom.xml index 30fec32d..a5203006 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 pom Extensions for the Oracle JDBC Driver @@ -72,7 +72,7 @@ com.oracle.database.jdbc ojdbc-provider-common - 1.0.6 + 1.0.7 com.oracle.database.jdbc @@ -106,7 +106,7 @@ com.oracle.database.jdbc ojdbc-provider-common - 1.0.6 + 1.0.7 tests test-jar test From 8ac378b2138464850bf1f3d04d5c14318599561f Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 15 Oct 2025 22:35:55 +0200 Subject: [PATCH 29/82] Downgraded jakarta --- ojdbc-provider-jackson-oson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index 53f1ef9c..b1b6aec0 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -14,7 +14,7 @@ ojdbc-provider-jackson-oson 2.20.0 - 3.2.0 + 3.1.0 From 719e84f6f62f98ca8da38778befc5bc7590543b0 Mon Sep 17 00:00:00 2001 From: Mouhsin Elmajdouby Date: Mon, 20 Oct 2025 21:48:54 +0100 Subject: [PATCH 30/82] implement OpenTelemetry stable semantic conventions --- ojdbc-provider-observability/README.md | 140 +++++-- .../ObservabilityConfiguration.java | 24 ++ .../ObservabilityConfigurationMBean.java | 4 + .../tracers/otel/OTelTracer.java | 348 +++++++++++++++--- .../tracers/otel/OtelSemanticConventions.java | 279 ++++++++++++++ 5 files changed, 727 insertions(+), 68 deletions(-) create mode 100644 ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java diff --git a/ojdbc-provider-observability/README.md b/ojdbc-provider-observability/README.md index dcf99623..41d4b823 100644 --- a/ojdbc-provider-observability/README.md +++ b/ojdbc-provider-observability/README.md @@ -12,29 +12,112 @@ will publish these events into Open Telemetry. These events include: * AC begin and success * VIP down event -The following attributes are added the traces for each event: - * **Roundtrips** - * Connection ID - * Database Operation - * Database User - * Database Tenant - * SQL ID - * Original SQL Text *(only present if sensitive data is enabled)* - * Actual SQL Text *(only present if sensitive data is enabled)* - * **AC begin and success** - * Error Message - * Error code - * SQL state - * Current replay retry count - * **VIP down event** - * Error message - * VIP address - * Protocol *(only present if sensitive data is enabled)* - * Host *(only present if sensitive data is enabled)* - * Port *(only present if sensitive data is enabled)* - * Service name *(only present if sensitive data is enabled)* - * SID *(only present if sensitive data is enabled)* - * Connection data *(only present if sensitive data is enabled)* +## Semantic Conventions (OpenTelemetry Tracer) + +The OpenTelemetry tracer supports both **stable** and **experimental** (legacy) +OpenTelemetry semantic conventions for Oracle Database instrumentation. + +### Semantic Convention Migration + +The OpenTelemetry tracer supports three modes controlled by the `OTEL_SEMCONV_STABILITY_OPT_IN` +environment variable: + +* **Empty/Not Set (default)** - Emits only experimental (legacy) conventions for backward compatibility +* **`database`** - Emits only the new stable Oracle Database semantic conventions +* **`database/dup`** - Emits both old and new conventions (dual mode for gradual migration) + +This configuration can be set via environment variable or changed at runtime through the MBean interface. + +See the [OpenTelemetry Database Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/database/database-spans/) +for details on the stable conventions. + +### Roundtrip Events + +#### Stable Conventions +When `OTEL_SEMCONV_STABILITY_OPT_IN=database` or `database/dup`: + +* **Required/Recommended Attributes** + * `db.system.name` - Always set to `"oracle.db"` + * `db.namespace` - Format: `{instance_name}|{database_name}|{service_name}` + * `db.operation.name` - Database operation being executed + * `db.query.summary` - Low cardinality query summary (SQL type) + * `server.address` - Database server hostname + * `server.port` - Database server port (if non-default, i.e., not 1521) + * `oracle.db.query.sql.id` - Oracle SQL_ID + * `oracle.db.session.id` - Oracle session ID + * `oracle.db.server.pid` - Oracle server process ID + * `oracle.db.shard.name` - Oracle shard name (if applicable) + * `thread.id` - Current thread ID + * `thread.name` - Current thread name + +* **Opt-In Attributes** *(only present if sensitive data is enabled)* + * `db.user` - Database user name + * `db.query.text` - Actual SQL query text + * `db.response.returned_rows` - Number of rows returned + +* **Error Attributes** *(only present on errors)* + * `error.type` - Exception class name (e.g., `java.sql.SQLSyntaxErrorException`) + * `db.response.status_code` - Oracle error code (format: `ORA-XXXXX`) + +#### Legacy Conventions +When `OTEL_SEMCONV_STABILITY_OPT_IN` is empty/not set or `database/dup`: + +* `Connection ID` +* `Database Operation` +* `Database Tenant` +* `SQL ID` +* `thread.id` +* `thread.name` +* `Database User` *(only present if sensitive data is enabled)* +* `Original SQL Text` *(only present if sensitive data is enabled)* +* `Actual SQL Text` *(only present if sensitive data is enabled)* + +### Application Continuity (AC) Replay Events + +#### Stable Conventions +When `OTEL_SEMCONV_STABILITY_OPT_IN=database` or `database/dup`: + +* `db.system.name` - Always set to `"oracle.db"` +* `error.type` - Error message that triggered replay +* `db.response.status_code` - Oracle error code (format: `ORA-XXXXX`) +* `db.operation.batch.size` - Current replay retry count + +#### Legacy Conventions +When `OTEL_SEMCONV_STABILITY_OPT_IN` is empty/not set or `database/dup`: + +* `Error Message` +* `Error code` +* `SQL state` +* `Current replay retry count` + +### VIP Retry Events + +#### Stable Conventions +When `OTEL_SEMCONV_STABILITY_OPT_IN=database` or `database/dup`: + +* `db.system.name` - Always set to `"oracle.db"` +* `error.type` - Error message that triggered VIP retry +* `server.address` - VIP address being retried + +**Opt-In VIP Debug Attributes** *(only present if sensitive data is enabled)*: +* `server.port` - Server port number +* `oracle.db.vip.protocol` - Connection protocol +* `oracle.db.vip.failed_host` - Host that failed during VIP retry +* `oracle.db.vip.service_name` - Oracle service name +* `oracle.db.vip.sid` - Oracle System Identifier (SID) +* `oracle.db.vip.connection_descriptor` - Full connection descriptor + +#### Legacy Conventions +When `OTEL_SEMCONV_STABILITY_OPT_IN` is empty/not set or `database/dup`: + +* `Error message` +* `VIP Address` +* `Protocol` *(only present if sensitive data is enabled)* +* `Host` *(only present if sensitive data is enabled)* +* `Port` *(only present if sensitive data is enabled)* +* `Service name` *(only present if sensitive data is enabled)* +* `SID` *(only present if sensitive data is enabled)* +* `Connection data` *(only present if sensitive data is enabled)* ## Installation @@ -69,6 +152,17 @@ oracle.jdbc.provider.traceEventListener.unique_identifier= If no unique identifier is provided, the unique idetifier "default" is used. +### Enabling Stable Semantic Conventions (OpenTelemetry) + +To use the new stable OpenTelemetry semantic conventions, set the environment variable: +```bash +# Use only stable conventions +export OTEL_SEMCONV_STABILITY_OPT_IN=database + +# OR use both old and new conventions (dual mode for migration) +export OTEL_SEMCONV_STABILITY_OPT_IN=database/dup +``` + ## Configuration The provider can be configured by: diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java index 0fc81a11..d39acd40 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java @@ -108,12 +108,32 @@ public class ObservabilityConfiguration implements ObservabilityConfigurationMBe */ public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; + /** + * OpenTelemetry semantic convention stability opt-in environment variable. + * Accepts a comma-separated list of values including: + * "database" (new stable conventions only), + * "database/dup" (both old and new conventions), + * or empty/null (old conventions only) + */ + public static final String OTEL_SEMCONV_STABILITY_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"; + + /** * Default values */ private static final String DEFAULT_ENABLED_TRACERS = "OTEL,JFR"; private static final String DEFAULT_SENSITIVE_DATA_ENABLED = "false"; private static final String DEFAULT_OPEN_TELEMETRY_ENABLED = "true"; + private volatile String semconvOptIn = ""; + + public String getSemconvOptIn() { + return semconvOptIn == null ? "" : semconvOptIn; + } + + public void setSemconvOptIn(String optIn) { + this.semconvOptIn = optIn == null ? "" : optIn; + } + /** * Lock used to ensure that only one thread can access the configuration at a time. @@ -173,6 +193,8 @@ public ObservabilityConfiguration() { ObservabilityConfiguration(ObservabilityConfigurationType configurationType) { String enabledTracers = DEFAULT_ENABLED_TRACERS; String sensitiveDataEnabled = DEFAULT_SENSITIVE_DATA_ENABLED; + String optIn = System.getenv(OTEL_SEMCONV_STABILITY_OPT_IN); + if (ObservabilityConfigurationType.OBSERVABILITY.equals(configurationType)) { enabledTracers = System.getProperty(ENABLED_TRACERS, DEFAULT_ENABLED_TRACERS); sensitiveDataEnabled = System.getProperty(SENSITIVE_DATA_ENABLED, DEFAULT_SENSITIVE_DATA_ENABLED); @@ -190,6 +212,8 @@ public ObservabilityConfiguration() { setEnabledTracers(enabledTracers); setSensitiveDataEnabled(Boolean.parseBoolean(sensitiveDataEnabled)); + this.setSemconvOptIn(optIn); + } /** diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java index f516dafa..fe6401ae 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java @@ -93,4 +93,8 @@ public interface ObservabilityConfigurationMBean { * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. */ void setSensitiveDataEnabled(boolean sensitiveDataEnabled); + + String getSemconvOptIn(); + void setSemconvOptIn(String optIn); + } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index f922004f..33cff330 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -38,6 +38,7 @@ package oracle.jdbc.provider.observability.tracers.otel; import java.sql.SQLException; +import java.time.Instant; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,9 +58,24 @@ import oracle.jdbc.provider.observability.ObservabilityConfiguration; import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; +import static oracle.jdbc.provider.observability.tracers.otel.OtelSemanticConventions.*; + /** * Open Telemetry tracer. Exports round trip event and execution events to - * Open Telemetry. + * Open Telemetry. with support for both stable and experimental semantic + * conventions. + *

    + *

    + * Semantic Convention Modes: + *

    + *
      + *
    • Legacy mode (default): Emits experimental conventions only when + * {@code OTEL_SEMCONV_STABILITY_OPT_IN} is not set or empty
    • + *
    • Stable mode: Emits stable conventions only when + * {@code OTEL_SEMCONV_STABILITY_OPT_IN=database}
    • + *
    • Dual mode: Emits both old and new conventions when + * {@code OTEL_SEMCONV_STABILITY_OPT_IN=database/dup}
    • + *
    */ public class OTelTracer implements ObservabilityTracer { @@ -123,13 +139,25 @@ public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Objec return span; } else { // End the Span after the round-trip. - if (userContext != null) { + if (userContext instanceof Span) { final Span span = (Span) userContext; - span.setStatus(traceContext.isCompletedExceptionally() ? StatusCode.ERROR : StatusCode.OK); - span.end(); + Boolean isErrorObj = traceContext.isCompletedExceptionally(); + boolean hasError = isErrorObj != null && isErrorObj; + span.setStatus(hasError ? StatusCode.ERROR : StatusCode.OK); + if (hasError && isStableConventionsEnabled()) { + Throwable throwable = traceContext.getThrowable(); + if (throwable != null) { + span.setAttribute(ERROR_TYPE_ATTRIBUTE, throwable.getClass().getName()); + if (throwable instanceof SQLException) { + SQLException sqlEx = (SQLException) throwable; + span.setAttribute(DB_RESPONSE_STATUS_CODE_ATTRIBUTE, String.format("ORA-%05d", sqlEx.getErrorCode())); + } + } + } + span.end(Instant.now()); } else { logger.log(Level.WARNING, "Unknown or null user context received from the driver on " + - "database operation: " + traceContext.databaseOperation()); + "database operation: " + traceContext.databaseOperation()); } return null; } @@ -139,31 +167,114 @@ public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Objec public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { if (EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { Tracer tracer = GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName()); + boolean emitStable = isStableConventionsEnabled(); + boolean emitOld = isOldConventionsEnabled(); if (event == TraceEventListener.JdbcExecutionEvent.VIP_RETRY) { - SpanBuilder spanBuilder = tracer - .spanBuilder(event.getDescription()) - .setAttribute("Error message", params[0].toString()) - .setAttribute("VIP Address", params[7].toString()); - // Add sensitive information (URL and SQL) if it is enabled - if (configuration.getSensitiveDataEnabled()) { - logger.log(Level.FINEST, "Sensitive information on"); - spanBuilder.setAttribute("Protocol", params[1].toString()) - .setAttribute("Host", params[2].toString()) - .setAttribute("Port", params[3].toString()) - .setAttribute("Service name", params[4].toString()) - .setAttribute("SID", params[5].toString()) - .setAttribute("Connection data", params[6].toString()); + SpanBuilder spanBuilder = tracer.spanBuilder(event.getDescription()); + + // Emit NEW stable conventions + if (emitStable) { + spanBuilder.setAttribute(DB_SYSTEM_ATTRIBUTE, DB_SYSTEM_VALUE_ORACLE); + + if (params[0] != null) { + spanBuilder.setAttribute(ERROR_TYPE_ATTRIBUTE, params[0].toString()); + } + if (params[7] != null) { + spanBuilder.setAttribute(SERVER_ADDRESS_ATTRIBUTE, params[7].toString()); + } + + // Add sensitive information (URL and SQL) if enabled + if (configuration.getSensitiveDataEnabled()) { + logger.log(Level.FINEST, "Sensitive information on"); + if (params[3] != null) { + spanBuilder.setAttribute(SERVER_PORT_ATTRIBUTE, params[3].toString()); + } + if (params[1] != null) { + spanBuilder.setAttribute(ORACLE_VIP_PROTOCOL_ATTRIBUTE, params[1].toString()); + } + if (params[2] != null) { + spanBuilder.setAttribute(ORACLE_VIP_FAILED_HOST_ATTRIBUTE, params[2].toString()); + } + if (params[4] != null) { + spanBuilder.setAttribute(ORACLE_VIP_SERVICE_NAME_ATTRIBUTE, params[4].toString()); + } + if (params[5] != null) { + spanBuilder.setAttribute(ORACLE_VIP_SID_ATTRIBUTE, params[5].toString()); + } + if (params[6] != null) { + spanBuilder.setAttribute(ORACLE_VIP_CONNECTION_DESCRIPTOR_ATTRIBUTE, params[6].toString()); + } + } + } + if (emitOld) { + spanBuilder.setAttribute(LEGACY_ERROR_MESSAGE_ATTRIBUTE, params[0].toString()); + + if (params[7] != null) { + spanBuilder.setAttribute(LEGACY_VIP_ADDRESS_ATTRIBUTE, params[7].toString()); + } + + // Add sensitive information (URL and connection details) if enabled + if (configuration.getSensitiveDataEnabled()) { + logger.log(Level.FINEST, "Sensitive information on"); + if (params[1] != null) { + spanBuilder.setAttribute(LEGACY_PROTOCOL_ATTRIBUTE, params[1].toString()); + } + if (params[2] != null) { + spanBuilder.setAttribute(LEGACY_HOST_ATTRIBUTE, params[2].toString()); + } + if (params[3] != null) { + spanBuilder.setAttribute(LEGACY_PORT_ATTRIBUTE, params[3].toString()); + } + if (params[4] != null) { + spanBuilder.setAttribute(LEGACY_SERVICE_NAME_ATTRIBUTE, params[4].toString()); + } + if (params[5] != null) { + spanBuilder.setAttribute(LEGACY_SID_ATTRIBUTE, params[5].toString()); + } + if (params[6] != null) { + spanBuilder.setAttribute(LEGACY_CONNECTION_DATA_ATTRIBUTE, params[6].toString()); + } + } } // start and end span. spanBuilder.startSpan().end(); } else if (event == TraceEventListener.JdbcExecutionEvent.AC_REPLAY_STARTED || event == TraceEventListener.JdbcExecutionEvent.AC_REPLAY_SUCCESSFUL) { - SpanBuilder spanBuilder = tracer - .spanBuilder(event.getDescription()) - .setAttribute("Error Message", params[0].toString()) - .setAttribute("Error code", ((SQLException) params[1]).getErrorCode()) - .setAttribute("SQL state", ((SQLException) params[1]).getSQLState()) - .setAttribute("Current replay retry count", params[2].toString()); + SpanBuilder spanBuilder = tracer.spanBuilder(event.getDescription()); + SQLException sqlEx = (SQLException) params[1]; + // Emit stable conventions + if (emitStable) { + spanBuilder.setAttribute(DB_SYSTEM_ATTRIBUTE, DB_SYSTEM_VALUE_ORACLE); + if (params[0] != null) { + spanBuilder.setAttribute(ERROR_TYPE_ATTRIBUTE, params[0].toString()); + } + if (sqlEx != null) { + spanBuilder.setAttribute(DB_RESPONSE_STATUS_CODE_ATTRIBUTE, + String.format("ORA-%05d", sqlEx.getErrorCode())); + } + if (params[2] != null) { + try { + spanBuilder.setAttribute(DB_OPERATION_BATCH_SIZE_ATTRIBUTE, + Integer.parseInt(params[2].toString())); + } catch (NumberFormatException e) { + logger.log(Level.FINE, "Could not parse retry count: " + params[2]); + } + } + } + + // Emit old conventions + if (emitOld) { + if (params[0] != null) { + spanBuilder.setAttribute(LEGACY_ERROR_MESSAGE_CAPITAL_ATTRIBUTE, params[0].toString()); + } + if (sqlEx != null) { + spanBuilder.setAttribute(LEGACY_ERROR_CODE_ATTRIBUTE, sqlEx.getErrorCode()); + spanBuilder.setAttribute(LEGACY_SQL_STATE_ATTRIBUTE, sqlEx.getSQLState()); + } + if (params[2] != null) { + spanBuilder.setAttribute(LEGACY_REPLAY_RETRY_COUNT_ATTRIBUTE, params[2].toString()); + } + } spanBuilder.startSpan().end(); } else { logger.log(Level.WARNING, "Unknown event received : " + event.toString()); @@ -177,9 +288,23 @@ public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, } /** - * Creates a Open Telemetry Span and sets it's attributes according to the - * trace context and the configuration. - * + * Creates an OpenTelemetry span with attributes based on the semantic convention mode. + *

    + * Emits span attributes according to the configured mode: + *

    + *
      + *
    • Stable conventions
    • + *
    • Legacy conventions
    • + *
    • Dual mode
    • + *
    + *

    + * Sensitive data (user, SQL text, row counts) is only included when + * {@link ObservabilityConfiguration#getSensitiveDataEnabled()} returns {@code true}. + *

    + *

    + * The span becomes a nested child if called within the context of an existing span. + *

    + * * @param traceContext the trace context. * @param spanName then span name. * @return returns the Span. @@ -191,28 +316,94 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { * parent to this child span. */ Tracer tracer = GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName()); - SpanBuilder spanBuilder = tracer - .spanBuilder(spanName) - .setAttribute("thread.id", Thread.currentThread().getId()) - .setAttribute("thread.name", Thread.currentThread().getName()) - .setAttribute("Connection ID", traceContext.getConnectionId()) - .setAttribute("Database Operation", traceContext.databaseOperation()) - .setAttribute("Database Tenant", traceContext.tenant()) - .setAttribute("SQL ID", traceContext.getSqlId()); - - // Add sensitive information (URL and SQL) if it is enabled - if (configuration.getSensitiveDataEnabled()) { - logger.log(Level.FINEST, "Sensitive information on"); + SpanBuilder spanBuilder = tracer.spanBuilder(spanName); + + boolean emitStable = isStableConventionsEnabled(); + boolean emitOld = isOldConventionsEnabled(); + + // Thread attributes are common to both old and new conventions + spanBuilder + .setAttribute(THREAD_ID_ATTRIBUTE, Thread.currentThread().getId()) + .setAttribute(THREAD_NAME_ATTRIBUTE, Thread.currentThread().getName()); + + // Emit NEW stable semantic conventions + if (emitStable) { + spanBuilder.setAttribute(DB_SYSTEM_ATTRIBUTE, DB_SYSTEM_VALUE_ORACLE); + + String namespace = buildDbNamespace(traceContext); + if (namespace != null && !namespace.isEmpty()) { + spanBuilder.setAttribute(DB_NAMESPACE_ATTRIBUTE, namespace); + } + + if (traceContext.getServerAddress() != null && !traceContext.getServerAddress().isEmpty()) { + spanBuilder.setAttribute(SERVER_ADDRESS_ATTRIBUTE, traceContext.getServerAddress()); + } + + if (traceContext.databaseOperation() != null && !traceContext.databaseOperation().isEmpty()) { + spanBuilder.setAttribute(DB_OPERATION_NAME_ATTRIBUTE, traceContext.databaseOperation()); + } + + if (traceContext.getSqlId() != null && !traceContext.getSqlId().isEmpty()) { + spanBuilder.setAttribute(ORACLE_SQL_ID_ATTRIBUTE, traceContext.getSqlId()); + } + + if (traceContext.getSessionID() != null && !traceContext.getSessionID().isEmpty()) { + spanBuilder.setAttribute(ORACLE_SESSION_ID_ATTRIBUTE, traceContext.getSessionID()); + } + + if (traceContext.getServerPID() != null && !traceContext.getServerPID().isEmpty()) { + spanBuilder.setAttribute(ORACLE_SERVER_PID_ATTRIBUTE, traceContext.getServerPID()); + } + + if (traceContext.getShardName() != null && !traceContext.getShardName().isEmpty()) { + spanBuilder.setAttribute(ORACLE_SHARD_NAME_ATTRIBUTE, traceContext.getShardName()); + } + + int serverPort = traceContext.getServerPort(); + if (serverPort > 0 && serverPort != 1521) { // 1521 is default Oracle port + spanBuilder.setAttribute(SERVER_PORT_ATTRIBUTE, serverPort); + } + + if (traceContext.getSqlType() != null && !traceContext.getSqlType().isEmpty()) { + spanBuilder.setAttribute(DB_QUERY_SUMMARY_ATTRIBUTE, traceContext.getSqlType()); + } + + // Add sensitive information (URL and SQL) if it is enabled + if (configuration.getSensitiveDataEnabled()) { + logger.log(Level.FINEST, "Sensitive information on"); + if (traceContext.user() != null && !traceContext.user().isEmpty()) { + spanBuilder.setAttribute(DB_USER_ATTRIBUTE, traceContext.user()); + } + if (traceContext.actualSqlText() != null) { + spanBuilder.setAttribute(DB_QUERY_TEXT_ATTRIBUTE, traceContext.actualSqlText()); + } + long numRows = traceContext.getNumRows(); + if (numRows > 0) { + spanBuilder.setAttribute(DB_RESPONSE_RETURNED_ROWS_ATTRIBUTE, numRows); + } + } + } + + // Emit old conventions + if (emitOld) { spanBuilder - .setAttribute("Database User", traceContext.user()) - .setAttribute("Original SQL Text", traceContext.originalSqlText()) - .setAttribute("Actual SQL Text", traceContext.actualSqlText()); + .setAttribute(LEGACY_CONNECTION_ID_ATTRIBUTE, traceContext.getConnectionId()) + .setAttribute(LEGACY_DATABASE_OPERATION_ATTRIBUTE, traceContext.databaseOperation()) + .setAttribute(LEGACY_SQL_ID_ATTRIBUTE, traceContext.getSqlId()) + .setAttribute(LEGACY_DATABASE_TENANT_ATTRIBUTE, traceContext.tenant()); + + // Add sensitive information (URL and SQL) if it is enabled + if (configuration.getSensitiveDataEnabled()) { + logger.log(Level.FINEST, "Sensitive information on"); + spanBuilder.setAttribute(LEGACY_ORIGINAL_SQL_TEXT_ATTRIBUTE, traceContext.originalSqlText()) + .setAttribute(LEGACY_ACTUAL_SQL_TEXT_ATTRIBUTE, traceContext.actualSqlText()) + .setAttribute(LEGACY_DATABASE_USER_ATTRIBUTE, traceContext.user()); + } } // According to the semantic conventions the Span Kind should be CLIENT, // used to be SERVER. return spanBuilder.setSpanKind(SpanKind.CLIENT).startSpan(); - } /** @@ -230,8 +421,7 @@ private void makeSpanCurrentAndSendContextToServer(TraceContext traceContext, Sp // Send the current context to the server traceContext.setClientInfo(TRACE_KEY, traceParent + traceState); } catch (Exception ex) { - logger.log(Level.WARNING, "An error occured while sending the current Open Telemetry context to the server. " - + ex.getMessage(), ex); + // Ignore exception } } @@ -269,4 +459,72 @@ private String initAndGetTraceState(Span span) { return String.format(TRACE_STATE_FORMAT, stringBuilder); } + /** + * Builds the db.namespace attribute according to Oracle Database + * semantic conventions. + * Format: {instance_name}|{database_name}|{service_name} + * Missing components and their separators are omitted. + */ + private String buildDbNamespace(TraceContext traceContext) { + StringBuilder ns = new StringBuilder(); + + if (traceContext.getInstanceName() != null && !traceContext.getInstanceName().isEmpty()) { + ns.append(traceContext.getInstanceName()); + } + + if (traceContext.getDatabaseName() != null && !traceContext.getDatabaseName().isEmpty()) { + if (ns.length() > 0) ns.append('|'); + ns.append(traceContext.getDatabaseName()); + } + + if (traceContext.getServiceName() != null && !traceContext.getServiceName().isEmpty()) { + if (ns.length() > 0) ns.append('|'); + ns.append(traceContext.getServiceName()); + } + + return ns.length() > 0 ? ns.toString() : null; + } + + /** + * Returns true if stable database semantic conventions should be emitted. + * This checks if OTEL_SEMCONV_STABILITY_OPT_IN contains "database" or "database/dup" + * in its comma-separated list of values. + */ + private boolean isStableConventionsEnabled() { + return hasDatabaseValue("database") || hasDatabaseValue("database/dup"); + } + + /** + * Returns true if old conventions should be emitted. + * This is true when: + * - OTEL_SEMCONV_STABILITY_OPT_IN is empty/null (default behavior), OR + * - OTEL_SEMCONV_STABILITY_OPT_IN contains "database/dup" (dual mode) + */ + private boolean isOldConventionsEnabled() { + if (configuration.getSemconvOptIn() == null || configuration.getSemconvOptIn().isEmpty()) { + return true; + } + return hasDatabaseValue("database/dup"); + } + + /** + * Helper method to check if a specific database value is present in the + * comma-separated OTEL_SEMCONV_STABILITY_OPT_IN list. + * + * @param targetValue the value to search for (e.g., "database" or "database/dup") + * @return true if the value is found in the comma-separated list + */ + private boolean hasDatabaseValue(String targetValue) { + if (configuration.getSemconvOptIn() == null || configuration.getSemconvOptIn().isEmpty()) { + return false; + } + + for (String value : configuration.getSemconvOptIn().split(",")) { + if (targetValue.equals(value.trim())) { + return true; + } + } + return false; + } + } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java new file mode 100644 index 00000000..9a8ee165 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java @@ -0,0 +1,279 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + +package oracle.jdbc.provider.observability.tracers.otel; + +/** + * OpenTelemetry semantic convention attribute keys for Oracle Database instrumentation. + */ +final class OtelSemanticConventions { + + /** + * Private constructor to prevent instantiation. + */ + private OtelSemanticConventions() { + throw new AssertionError("OtelSemanticConventions should not be instantiated"); + } + + // ======================================================================== + // Stable OpenTelemetry Database Semantic Convention Attributes + // ======================================================================== + + /** + * Attribute key for the database system identifier. + */ + static final String DB_SYSTEM_ATTRIBUTE = "db.system.name"; + + /** + * Value for {@link #DB_SYSTEM_ATTRIBUTE} representing Oracle Database. + */ + static final String DB_SYSTEM_VALUE_ORACLE = "oracle.db"; + + /** + * Attribute key for the database namespace. + */ + static final String DB_NAMESPACE_ATTRIBUTE = "db.namespace"; + + /** + * Attribute key for the database operation name. + */ + static final String DB_OPERATION_NAME_ATTRIBUTE = "db.operation.name"; + + /** + * Attribute key for low cardinality query summary. + */ + static final String DB_QUERY_SUMMARY_ATTRIBUTE = "db.query.summary"; + + /** + * Attribute key for the actual database query text. + */ + static final String DB_QUERY_TEXT_ATTRIBUTE = "db.query.text"; + + /** + * Attribute key for the database user name. + */ + static final String DB_USER_ATTRIBUTE = "db.user"; + + /** + * Attribute key for Oracle Database error number. + */ + static final String DB_RESPONSE_STATUS_CODE_ATTRIBUTE = "db.response.status_code"; + + /** + * Attribute key for the number of rows returned by the operation. + */ + static final String DB_RESPONSE_RETURNED_ROWS_ATTRIBUTE = "db.response.returned_rows"; + + /** + * Attribute key for the number of queries in a batch operation. + */ + static final String DB_OPERATION_BATCH_SIZE_ATTRIBUTE = "db.operation.batch.size"; + + /** + * Attribute key for the database host name. + */ + static final String SERVER_ADDRESS_ATTRIBUTE = "server.address"; + + /** + * Attribute key for the database server port number. + */ + static final String SERVER_PORT_ATTRIBUTE = "server.port"; + + /** + * Attribute key for the error type describing the class of error. + */ + static final String ERROR_TYPE_ATTRIBUTE = "error.type"; + + /** + * Attribute key for the current thread ID. + */ + static final String THREAD_ID_ATTRIBUTE = "thread.id"; + + /** + * Attribute key for the current thread name. + */ + static final String THREAD_NAME_ATTRIBUTE = "thread.name"; + + // ======================================================================== + // Custom Attributes + // ======================================================================== + + /** + * Attribute key for Oracle SQL statement identifier (SQL_ID). + */ + static final String ORACLE_SQL_ID_ATTRIBUTE = "oracle.db.query.sql.id"; + + /** + * Attribute key for Oracle database session ID. + */ + static final String ORACLE_SESSION_ID_ATTRIBUTE = "oracle.db.session.id"; + + /** + * Attribute key for Oracle database server process ID. + */ + static final String ORACLE_SERVER_PID_ATTRIBUTE = "oracle.db.server.pid"; + + /** + * Attribute key for Oracle shard name. + */ + static final String ORACLE_SHARD_NAME_ATTRIBUTE = "oracle.db.shard.name"; + + /** + * Attribute key for the connection protocol during VIP retry. + */ + static final String ORACLE_VIP_PROTOCOL_ATTRIBUTE = "oracle.db.vip.protocol"; + + /** + * Attribute key for the failed host during VIP retry. + */ + static final String ORACLE_VIP_FAILED_HOST_ATTRIBUTE = "oracle.db.vip.failed_host"; + + /** + * Attribute key for the service name during VIP retry. + */ + static final String ORACLE_VIP_SERVICE_NAME_ATTRIBUTE = "oracle.db.vip.service_name"; + + /** + * Attribute key for the Oracle SID during VIP retry. + */ + static final String ORACLE_VIP_SID_ATTRIBUTE = "oracle.db.vip.sid"; + + /** + * Attribute key for the connection descriptor during VIP retry. + */ + static final String ORACLE_VIP_CONNECTION_DESCRIPTOR_ATTRIBUTE = "oracle.db.vip.connection_descriptor"; + + // ======================================================================== + // Legacy Attributes (Experimental Semantic Conventions - Backward Compatibility) + // ======================================================================== + + /** + * Legacy attribute key for connection ID. + */ + static final String LEGACY_CONNECTION_ID_ATTRIBUTE = "Connection ID"; + + /** + * Legacy attribute key for database operation. + */ + static final String LEGACY_DATABASE_OPERATION_ATTRIBUTE = "Database Operation"; + + /** + * Legacy attribute key for database user. + */ + static final String LEGACY_DATABASE_USER_ATTRIBUTE = "Database User"; + + /** + * Legacy attribute key for SQL ID. + */ + static final String LEGACY_SQL_ID_ATTRIBUTE = "SQL ID"; + + /** + * Legacy attribute key for database tenant. + */ + static final String LEGACY_DATABASE_TENANT_ATTRIBUTE = "Database Tenant"; + + /** + * Legacy attribute key for original SQL text. + */ + static final String LEGACY_ORIGINAL_SQL_TEXT_ATTRIBUTE = "Original SQL Text"; + + /** + * Legacy attribute key for actual SQL text. + */ + static final String LEGACY_ACTUAL_SQL_TEXT_ATTRIBUTE = "Actual SQL Text"; + + /** + * Legacy attribute key for error message (lowercase 'm'). + */ + static final String LEGACY_ERROR_MESSAGE_ATTRIBUTE = "Error message"; + + /** + * Legacy attribute key for error message (uppercase 'M'). + */ + static final String LEGACY_ERROR_MESSAGE_CAPITAL_ATTRIBUTE = "Error Message"; + + /** + * Legacy attribute key for error code. + */ + static final String LEGACY_ERROR_CODE_ATTRIBUTE = "Error code"; + + /** + * Legacy attribute key for SQL state. + */ + static final String LEGACY_SQL_STATE_ATTRIBUTE = "SQL state"; + + /** + * Legacy attribute key for VIP address. + */ + static final String LEGACY_VIP_ADDRESS_ATTRIBUTE = "VIP Address"; + + /** + * Legacy attribute key for protocol. + */ + static final String LEGACY_PROTOCOL_ATTRIBUTE = "Protocol"; + + /** + * Legacy attribute key for host. + */ + static final String LEGACY_HOST_ATTRIBUTE = "Host"; + + /** + * Legacy attribute key for port. + */ + static final String LEGACY_PORT_ATTRIBUTE = "Port"; + + /** + * Legacy attribute key for service name. + */ + static final String LEGACY_SERVICE_NAME_ATTRIBUTE = "Service name"; + + /** + * Legacy attribute key for SID. + */ + static final String LEGACY_SID_ATTRIBUTE = "SID"; + + /** + * Legacy attribute key for connection data. + */ + static final String LEGACY_CONNECTION_DATA_ATTRIBUTE = "Connection data"; + + /** + * Legacy attribute key for replay retry count. + */ + static final String LEGACY_REPLAY_RETRY_COUNT_ATTRIBUTE = "Current replay retry count"; +} \ No newline at end of file From 63f5ba0f3b056a31e9235c4e744f7d25cde67994 Mon Sep 17 00:00:00 2001 From: Mouhsin Elmajdouby Date: Tue, 21 Oct 2025 00:04:32 +0100 Subject: [PATCH 31/82] Fix and improve javadocs && Improve thread safety --- .../ObservabilityConfiguration.java | 44 ++++++++++++++----- .../ObservabilityConfigurationMBean.java | 12 +++++ .../tracers/otel/OTelTracer.java | 1 - 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java index d39acd40..0bc24a59 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java @@ -124,15 +124,7 @@ public class ObservabilityConfiguration implements ObservabilityConfigurationMBe private static final String DEFAULT_ENABLED_TRACERS = "OTEL,JFR"; private static final String DEFAULT_SENSITIVE_DATA_ENABLED = "false"; private static final String DEFAULT_OPEN_TELEMETRY_ENABLED = "true"; - private volatile String semconvOptIn = ""; - - public String getSemconvOptIn() { - return semconvOptIn == null ? "" : semconvOptIn; - } - - public void setSemconvOptIn(String optIn) { - this.semconvOptIn = optIn == null ? "" : optIn; - } + private String semconvOptIn = ""; /** @@ -212,7 +204,7 @@ public ObservabilityConfiguration() { setEnabledTracers(enabledTracers); setSensitiveDataEnabled(Boolean.parseBoolean(sensitiveDataEnabled)); - this.setSemconvOptIn(optIn); + setSemconvOptIn(optIn); } @@ -317,6 +309,38 @@ public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { } } + /** + * Returns the OpenTelemetry semantic convention stability opt-in configuration. + * + * @return the current semantic convention mode, never {@code null} + */ + @Override + public String getSemconvOptIn() { + observabilityConfigurationLock.lock(); + try { + return semconvOptIn == null ? "" : semconvOptIn; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Sets the OpenTelemetry semantic convention stability opt-in configuration. + * + * @param optIn the semantic convention mode to set; {@code null} is + * treated as empty string + */ + @Override + public void setSemconvOptIn(String optIn) { + observabilityConfigurationLock.lock(); + try { + this.semconvOptIn = optIn == null ? "" : optIn; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** * Returns a list of enabled tracers. * @return then list of enabled tracers. diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java index fe6401ae..74ab8b39 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java @@ -94,7 +94,19 @@ public interface ObservabilityConfigurationMBean { */ void setSensitiveDataEnabled(boolean sensitiveDataEnabled); + /** + * Returns the OpenTelemetry semantic convention stability opt-in configuration. + * + * @return the current semantic convention mode, never {@code null} + */ String getSemconvOptIn(); + + /** + * Sets the OpenTelemetry semantic convention stability opt-in configuration. + * + * @param optIn the semantic convention mode to set; {@code null} is + * treated as empty string + */ void setSemconvOptIn(String optIn); } diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index 33cff330..c300c1b2 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -64,7 +64,6 @@ * Open Telemetry tracer. Exports round trip event and execution events to * Open Telemetry. with support for both stable and experimental semantic * conventions. - *

    *

    * Semantic Convention Modes: *

    From 265c48bbbcc65409d2d81d76d7196ab362dc2d50 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Fri, 31 Oct 2025 12:17:43 +0100 Subject: [PATCH 32/82] Cleaning up --- .../otel/OtelUCPEventListenerProvider.java | 83 +------------------ 1 file changed, 1 insertion(+), 82 deletions(-) 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 index c62d211d..e0b0a291 100644 --- 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 @@ -16,89 +16,8 @@ * 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. + * following database client semantic conventions. *

    - * - *

    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 { From d59055c3d48993bb7f6212eb899ad35706fb33ae Mon Sep 17 00:00:00 2001 From: Mouhsin Elmajdouby Date: Wed, 12 Nov 2025 14:56:21 +0100 Subject: [PATCH 33/82] Split db.namespace into separate attributes: service name, instance ID, and PDB && update the driver version to 23.26.0.0.0 --- ojdbc-provider-observability/README.md | 4 +- ojdbc-provider-observability/pom.xml | 1 + .../tracers/otel/OTelTracer.java | 39 +++++-------------- .../tracers/otel/OtelSemanticConventions.java | 10 +++++ .../ObservabilityTraceEventListenerTest.java | 7 ++-- 5 files changed, 28 insertions(+), 33 deletions(-) diff --git a/ojdbc-provider-observability/README.md b/ojdbc-provider-observability/README.md index 41d4b823..b9a7adba 100644 --- a/ojdbc-provider-observability/README.md +++ b/ojdbc-provider-observability/README.md @@ -38,11 +38,13 @@ When `OTEL_SEMCONV_STABILITY_OPT_IN=database` or `database/dup`: * **Required/Recommended Attributes** * `db.system.name` - Always set to `"oracle.db"` - * `db.namespace` - Format: `{instance_name}|{database_name}|{service_name}` + * `db.namespace` - Service name (or TNS alias if used) * `db.operation.name` - Database operation being executed * `db.query.summary` - Low cardinality query summary (SQL type) * `server.address` - Database server hostname * `server.port` - Database server port (if non-default, i.e., not 1521) + * `oracle.db.instance.id` - Oracle database instance identifier + * `oracle.db.pdb` - Oracle PDB name * `oracle.db.query.sql.id` - Oracle SQL_ID * `oracle.db.session.id` - Oracle session ID * `oracle.db.server.pid` - Oracle server process ID diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 8a7601cb..3d1baa3f 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -24,6 +24,7 @@ com.oracle.database.jdbc ojdbc11 + 23.26.0.0.0 io.opentelemetry diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index c300c1b2..b3fa5e09 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -329,9 +329,16 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { if (emitStable) { spanBuilder.setAttribute(DB_SYSTEM_ATTRIBUTE, DB_SYSTEM_VALUE_ORACLE); - String namespace = buildDbNamespace(traceContext); - if (namespace != null && !namespace.isEmpty()) { - spanBuilder.setAttribute(DB_NAMESPACE_ATTRIBUTE, namespace); + if (traceContext.getServiceName() != null && !traceContext.getServiceName().isEmpty()) { + spanBuilder.setAttribute(DB_NAMESPACE_ATTRIBUTE, traceContext.getServiceName()); + } + + if (traceContext.getInstanceName() != null && !traceContext.getInstanceName().isEmpty()) { + spanBuilder.setAttribute(ORACLE_INSTANCE_ID_ATTRIBUTE, traceContext.getInstanceName()); + } + + if (traceContext.getDatabaseName() != null && !traceContext.getDatabaseName().isEmpty()) { + spanBuilder.setAttribute(ORACLE_PDB_ATTRIBUTE, traceContext.getDatabaseName()); } if (traceContext.getServerAddress() != null && !traceContext.getServerAddress().isEmpty()) { @@ -458,32 +465,6 @@ private String initAndGetTraceState(Span span) { return String.format(TRACE_STATE_FORMAT, stringBuilder); } - /** - * Builds the db.namespace attribute according to Oracle Database - * semantic conventions. - * Format: {instance_name}|{database_name}|{service_name} - * Missing components and their separators are omitted. - */ - private String buildDbNamespace(TraceContext traceContext) { - StringBuilder ns = new StringBuilder(); - - if (traceContext.getInstanceName() != null && !traceContext.getInstanceName().isEmpty()) { - ns.append(traceContext.getInstanceName()); - } - - if (traceContext.getDatabaseName() != null && !traceContext.getDatabaseName().isEmpty()) { - if (ns.length() > 0) ns.append('|'); - ns.append(traceContext.getDatabaseName()); - } - - if (traceContext.getServiceName() != null && !traceContext.getServiceName().isEmpty()) { - if (ns.length() > 0) ns.append('|'); - ns.append(traceContext.getServiceName()); - } - - return ns.length() > 0 ? ns.toString() : null; - } - /** * Returns true if stable database semantic conventions should be emitted. * This checks if OTEL_SEMCONV_STABILITY_OPT_IN contains "database" or "database/dup" diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java index 9a8ee165..65ae7d14 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java @@ -148,6 +148,16 @@ private OtelSemanticConventions() { */ static final String ORACLE_SERVER_PID_ATTRIBUTE = "oracle.db.server.pid"; + /** + * Attribute key for Oracle database instance identifier. + */ + static final String ORACLE_INSTANCE_ID_ATTRIBUTE = "oracle.db.instance.id"; + + /** + * Attribute key for Oracle PDB name. + */ + static final String ORACLE_PDB_ATTRIBUTE = "oracle.db.pdb"; + /** * Attribute key for Oracle shard name. */ diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java index 52432893..7cc2d0d3 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -50,6 +50,7 @@ import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; +import java.time.Instant; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -204,15 +205,15 @@ public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.EXECUTE_QUERY.getDescription()); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.LOGOFF.getDescription()); if (sensitiveDataEnabled) { - Mockito.verify(spanBuilder, Mockito.times(4)).setAttribute("Database User", userName); + Mockito.verify(spanBuilder, Mockito.times(4)).setAttribute("Database User", userName.toUpperCase()); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); } else { - Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Database User", userName); + Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Database User", userName.toUpperCase()); Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); } - Mockito.verify(span, atLeast(4)).end(); + Mockito.verify(span, atLeast(4)).end(Mockito.any(Instant.class)); } From 0d6a4d77799dcf1c4a25a2130f781cc9663260e6 Mon Sep 17 00:00:00 2001 From: Mouhsin Elmajdouby Date: Wed, 12 Nov 2025 14:59:57 +0100 Subject: [PATCH 34/82] make the new driver version global for all providers --- ojdbc-provider-observability/pom.xml | 1 - pom.xml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 3d1baa3f..8a7601cb 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -24,7 +24,6 @@ com.oracle.database.jdbc ojdbc11 - 23.26.0.0.0 io.opentelemetry diff --git a/pom.xml b/pom.xml index 03ac4cba..784a1bfc 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 1.8 1.8 - 23.7.0.25.01 + 23.26.0.0.0 5.9.0 4.11.0 UTF-8 From 00513c3229d07c11a313b70917f851a695cb5373 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 12 Nov 2025 16:00:16 +0100 Subject: [PATCH 35/82] Version --- ojdbc-provider-observability/pom.xml | 4 ++-- pom.xml | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 8a7601cb..8cc2aba4 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -7,7 +7,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.3 + 1.0.6 Oracle JDBC Observability Provider @@ -23,7 +23,7 @@ com.oracle.database.jdbc - ojdbc11 + ojdbc8 io.opentelemetry diff --git a/pom.xml b/pom.xml index 92a7ccf8..f0518d1e 100644 --- a/pom.xml +++ b/pom.xml @@ -91,11 +91,6 @@ ${jdbc.version} provided - - com.oracle.database.jdbc - ojdbc11 - ${jdbc.version} - com.oracle.database.security oraclepki From 82c48d5dd32dcc49b6744272d021e92deb09ae72 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 12 Nov 2025 16:51:05 +0100 Subject: [PATCH 36/82] debugging test --- .../oracle/jdbc/provider/pkl/configuration/PklParserTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ojdbc-provider-pkl/src/test/java/oracle/jdbc/provider/pkl/configuration/PklParserTest.java b/ojdbc-provider-pkl/src/test/java/oracle/jdbc/provider/pkl/configuration/PklParserTest.java index 063e2367..24d934d7 100644 --- a/ojdbc-provider-pkl/src/test/java/oracle/jdbc/provider/pkl/configuration/PklParserTest.java +++ b/ojdbc-provider-pkl/src/test/java/oracle/jdbc/provider/pkl/configuration/PklParserTest.java @@ -94,6 +94,8 @@ public static void setup() throws Exception { + "}\n"; writer.write(content); writer.close(); + + System.out.println(content); } @Test From 5b1228247220d3d016d77180467b6244cbdb2251 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 12 Nov 2025 17:28:04 +0100 Subject: [PATCH 37/82] Test with older version of driver --- .../jdbc/provider/observability/tracers/otel/OTelTracer.java | 5 +++-- pom.xml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index b3fa5e09..69a212ad 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -144,7 +144,7 @@ public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Objec boolean hasError = isErrorObj != null && isErrorObj; span.setStatus(hasError ? StatusCode.ERROR : StatusCode.OK); if (hasError && isStableConventionsEnabled()) { - Throwable throwable = traceContext.getThrowable(); + Throwable throwable = null; // traceContext.getThrowable(); if (throwable != null) { span.setAttribute(ERROR_TYPE_ATTRIBUTE, throwable.getClass().getName()); if (throwable instanceof SQLException) { @@ -328,7 +328,7 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { // Emit NEW stable semantic conventions if (emitStable) { spanBuilder.setAttribute(DB_SYSTEM_ATTRIBUTE, DB_SYSTEM_VALUE_ORACLE); - +/* if (traceContext.getServiceName() != null && !traceContext.getServiceName().isEmpty()) { spanBuilder.setAttribute(DB_NAMESPACE_ATTRIBUTE, traceContext.getServiceName()); } @@ -388,6 +388,7 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { spanBuilder.setAttribute(DB_RESPONSE_RETURNED_ROWS_ATTRIBUTE, numRows); } } + */ } // Emit old conventions diff --git a/pom.xml b/pom.xml index f0518d1e..2cff6b79 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 1.8 1.8 - 23.26.0.0.0 + 23.7.0.25.01 5.9.0 4.11.0 UTF-8 From 01a3113f360d56d3d6b81d7943b7ccfdd51b307a Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 12 Nov 2025 17:36:19 +0100 Subject: [PATCH 38/82] Driver version --- ojdbc-provider-observability/README.md | 2 +- .../provider/observability/tracers/otel/OTelTracer.java | 5 ++--- ojdbc-provider-samples/pom.xml | 8 ++++---- pom.xml | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ojdbc-provider-observability/README.md b/ojdbc-provider-observability/README.md index b9a7adba..287a649f 100644 --- a/ojdbc-provider-observability/README.md +++ b/ojdbc-provider-observability/README.md @@ -131,7 +131,7 @@ The coordinates for the latest release are: com.oracle.database.jdbc ojdbc-provider-observability - 1.0.3 + 1.0.6 ``` diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index 69a212ad..b3fa5e09 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -144,7 +144,7 @@ public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Objec boolean hasError = isErrorObj != null && isErrorObj; span.setStatus(hasError ? StatusCode.ERROR : StatusCode.OK); if (hasError && isStableConventionsEnabled()) { - Throwable throwable = null; // traceContext.getThrowable(); + Throwable throwable = traceContext.getThrowable(); if (throwable != null) { span.setAttribute(ERROR_TYPE_ATTRIBUTE, throwable.getClass().getName()); if (throwable instanceof SQLException) { @@ -328,7 +328,7 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { // Emit NEW stable semantic conventions if (emitStable) { spanBuilder.setAttribute(DB_SYSTEM_ATTRIBUTE, DB_SYSTEM_VALUE_ORACLE); -/* + if (traceContext.getServiceName() != null && !traceContext.getServiceName().isEmpty()) { spanBuilder.setAttribute(DB_NAMESPACE_ATTRIBUTE, traceContext.getServiceName()); } @@ -388,7 +388,6 @@ private Span initAndGetSpan(TraceContext traceContext, String spanName) { spanBuilder.setAttribute(DB_RESPONSE_RETURNED_ROWS_ATTRIBUTE, numRows); } } - */ } // Emit old conventions diff --git a/ojdbc-provider-samples/pom.xml b/ojdbc-provider-samples/pom.xml index e2c651eb..c4491926 100644 --- a/ojdbc-provider-samples/pom.xml +++ b/ojdbc-provider-samples/pom.xml @@ -5,7 +5,7 @@ Oracle JDBC Provider Code Samples ojdbc-provider-samples - 1.0.3 + 1.0.6 jar @@ -23,12 +23,12 @@ com.oracle.database.jdbc ojdbc-provider-azure - 1.0.3 + ${project.parent.version} com.oracle.database.jdbc ojdbc-provider-oci - 1.0.3 + ${project.parent.version} com.oracle.database.jdbc @@ -38,7 +38,7 @@ com.oracle.database.jdbc ojdbc-provider-jackson-oson - 1.0.3 + ${project.parent.version} com.oracle.database.jdbc diff --git a/pom.xml b/pom.xml index 2cff6b79..f0518d1e 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 1.8 1.8 - 23.7.0.25.01 + 23.26.0.0.0 5.9.0 4.11.0 UTF-8 From 42912c5b68dd6bec4954f3bbdeafd851435773ba Mon Sep 17 00:00:00 2001 From: Mouhsin Elmajdouby Date: Mon, 19 Jan 2026 15:58:01 +0100 Subject: [PATCH 39/82] Fix AC replay attribute and improve README clarity --- ojdbc-provider-observability/README.md | 72 +++++++++---------- .../tracers/otel/OTelTracer.java | 2 +- .../tracers/otel/OtelSemanticConventions.java | 10 +-- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/ojdbc-provider-observability/README.md b/ojdbc-provider-observability/README.md index 287a649f..6b4ffc17 100644 --- a/ojdbc-provider-observability/README.md +++ b/ojdbc-provider-observability/README.md @@ -37,39 +37,39 @@ for details on the stable conventions. When `OTEL_SEMCONV_STABILITY_OPT_IN=database` or `database/dup`: * **Required/Recommended Attributes** - * `db.system.name` - Always set to `"oracle.db"` - * `db.namespace` - Service name (or TNS alias if used) - * `db.operation.name` - Database operation being executed - * `db.query.summary` - Low cardinality query summary (SQL type) - * `server.address` - Database server hostname + * `db.system.name` - Always set to `"oracle.db"` (identifies Oracle Database) + * `db.namespace` - The Oracle service name from the connection + * `db.operation.name` - Database operation being executed (e.g. `ExecuteQuery`) + * `db.query.summary` - Low cardinality SQL command type (e.g., `SELECT`, `INSERT`) + * `server.address` - Database server hostname (e.g., `db.example.com`) * `server.port` - Database server port (if non-default, i.e., not 1521) * `oracle.db.instance.id` - Oracle database instance identifier - * `oracle.db.pdb` - Oracle PDB name - * `oracle.db.query.sql.id` - Oracle SQL_ID - * `oracle.db.session.id` - Oracle session ID - * `oracle.db.server.pid` - Oracle server process ID - * `oracle.db.shard.name` - Oracle shard name (if applicable) - * `thread.id` - Current thread ID - * `thread.name` - Current thread name + * `oracle.db.pdb` - Oracle Pluggable Database (PDB) name + * `oracle.db.query.sql.id` - Oracle SQL identifier (e.g., `8vq9m5kx3n2wh`) + * `oracle.db.session.id` - Oracle session identifier (e.g., `1234`) + * `oracle.db.server.pid` - Oracle server process ID on the database host (e.g., `56789`) + * `oracle.db.shard.name` - Oracle shard database name (only populated when using Oracle Sharding) + * `thread.id` - JVM thread ID that executed the operation + * `thread.name` - JVM thread name that executed the operation * **Opt-In Attributes** *(only present if sensitive data is enabled)* - * `db.user` - Database user name - * `db.query.text` - Actual SQL query text + * `db.user` - Database username used for the connection (e.g., `APP_USER`, `SCOTT`) + * `db.query.text` - Full SQL statement text sent to the database * `db.response.returned_rows` - Number of rows returned * **Error Attributes** *(only present on errors)* * `error.type` - Exception class name (e.g., `java.sql.SQLSyntaxErrorException`) - * `db.response.status_code` - Oracle error code (format: `ORA-XXXXX`) + * `db.response.status_code` - Oracle error code in format `ORA-XXXXX` (e.g., `ORA-00942`, `ORA-01017`) #### Legacy Conventions When `OTEL_SEMCONV_STABILITY_OPT_IN` is empty/not set or `database/dup`: -* `Connection ID` -* `Database Operation` -* `Database Tenant` -* `SQL ID` -* `thread.id` -* `thread.name` +* `Connection ID` - JDBC connection identifier +* `Database Operation` - Database operation being executed (e.g., `Commit`, `ExecuteQuery`) +* `Database Tenant` - Tenant name +* `SQL ID` - Oracle SQL identifier (e.g., `8vq9m5kx3n2wh`) +* `thread.id` - JVM thread ID that executed the operation +* `thread.name` - JVM thread name that executed the operation * `Database User` *(only present if sensitive data is enabled)* * `Original SQL Text` *(only present if sensitive data is enabled)* * `Actual SQL Text` *(only present if sensitive data is enabled)* @@ -79,35 +79,35 @@ When `OTEL_SEMCONV_STABILITY_OPT_IN` is empty/not set or `database/dup`: #### Stable Conventions When `OTEL_SEMCONV_STABILITY_OPT_IN=database` or `database/dup`: -* `db.system.name` - Always set to `"oracle.db"` -* `error.type` - Error message that triggered replay -* `db.response.status_code` - Oracle error code (format: `ORA-XXXXX`) -* `db.operation.batch.size` - Current replay retry count +* `db.system.name` - Always set to `"oracle.db"` (identifies Oracle Database) +* `error.type` - The error message text that triggered the replay attempt +* `db.response.status_code` - Oracle error code that triggered replay in format `ORA-XXXXX` (e.g., `ORA-03135`, `ORA-25408`) +* `oracle.db.ac.retry_count` - Application Continuity replay attempt number #### Legacy Conventions When `OTEL_SEMCONV_STABILITY_OPT_IN` is empty/not set or `database/dup`: -* `Error Message` +* `Error Message` - Error message text that triggered the replay attempt * `Error code` * `SQL state` -* `Current replay retry count` +* `Current replay retry count` - Application Continuity replay attempt number ### VIP Retry Events #### Stable Conventions When `OTEL_SEMCONV_STABILITY_OPT_IN=database` or `database/dup`: -* `db.system.name` - Always set to `"oracle.db"` -* `error.type` - Error message that triggered VIP retry -* `server.address` - VIP address being retried +* `db.system.name` - Always set to `"oracle.db"` (identifies Oracle Database) +* `error.type` - The error message that triggered the VIP retry +* `server.address` - The VIP (Virtual IP) address that is being retried after failure **Opt-In VIP Debug Attributes** *(only present if sensitive data is enabled)*: -* `server.port` - Server port number -* `oracle.db.vip.protocol` - Connection protocol -* `oracle.db.vip.failed_host` - Host that failed during VIP retry -* `oracle.db.vip.service_name` - Oracle service name -* `oracle.db.vip.sid` - Oracle System Identifier (SID) -* `oracle.db.vip.connection_descriptor` - Full connection descriptor +* `server.port` - Database server port number being retried +* `oracle.db.vip.protocol` - Connection protocol being used +* `oracle.db.vip.failed_host` - The hostname that failed, triggering the VIP retry +* `oracle.db.vip.service_name` - Oracle service name for the failed connection +* `oracle.db.vip.sid` - Oracle System Identifier (SID) for the failed connection +* `oracle.db.vip.connection_descriptor` - Complete JDBC connection descriptor string #### Legacy Conventions When `OTEL_SEMCONV_STABILITY_OPT_IN` is empty/not set or `database/dup`: diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java index b3fa5e09..40dace29 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -253,7 +253,7 @@ public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, } if (params[2] != null) { try { - spanBuilder.setAttribute(DB_OPERATION_BATCH_SIZE_ATTRIBUTE, + spanBuilder.setAttribute(ORACLE_AC_RETRY_COUNT_ATTRIBUTE, Integer.parseInt(params[2].toString())); } catch (NumberFormatException e) { logger.log(Level.FINE, "Could not parse retry count: " + params[2]); diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java index 65ae7d14..410e6b33 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OtelSemanticConventions.java @@ -99,11 +99,6 @@ private OtelSemanticConventions() { */ static final String DB_RESPONSE_RETURNED_ROWS_ATTRIBUTE = "db.response.returned_rows"; - /** - * Attribute key for the number of queries in a batch operation. - */ - static final String DB_OPERATION_BATCH_SIZE_ATTRIBUTE = "db.operation.batch.size"; - /** * Attribute key for the database host name. */ @@ -183,6 +178,11 @@ private OtelSemanticConventions() { */ static final String ORACLE_VIP_SID_ATTRIBUTE = "oracle.db.vip.sid"; + /** + * Attribute key for Oracle Application Continuity replay retry count. + */ + static final String ORACLE_AC_RETRY_COUNT_ATTRIBUTE = "oracle.db.ac.retry_count"; + /** * Attribute key for the connection descriptor during VIP retry. */ From a922d2bfa9d10be4dbac3fa2cf01842e66779c3d Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Thu, 5 Feb 2026 14:32:55 +0100 Subject: [PATCH 40/82] Updated README --- README.md | 4 ++-- .../oracle/jdbc/provider/pkl/configuration/PklParserTest.java | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 19859448..07c7098f 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ Each module of this project contains a set of providers. The next one contains a provider for Open Telemetry:
    -
    Oracle JDBC Open Telemetry Provider
    -
    Provider for integration with Open Telemetry.
    +
    Oracle JDBC Observability Provider
    +
    Provider for integration with Open Telemetry and Java Flight Recorder.
    And the last one contains a provider for Jackson-OSON:
    diff --git a/ojdbc-provider-pkl/src/test/java/oracle/jdbc/provider/pkl/configuration/PklParserTest.java b/ojdbc-provider-pkl/src/test/java/oracle/jdbc/provider/pkl/configuration/PklParserTest.java index 24e0fe17..d8592eb0 100644 --- a/ojdbc-provider-pkl/src/test/java/oracle/jdbc/provider/pkl/configuration/PklParserTest.java +++ b/ojdbc-provider-pkl/src/test/java/oracle/jdbc/provider/pkl/configuration/PklParserTest.java @@ -94,8 +94,6 @@ public static void setup() throws Exception { + "}\n"; writer.write(content); writer.close(); - - System.out.println(content); } @Test From f056b7f5e15537236ca917ba26c446334ef1c465 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Thu, 5 Feb 2026 17:56:51 +0100 Subject: [PATCH 41/82] Update pom.xml Updated jakarta persistance API version to 3.2 --- ojdbc-provider-jackson-oson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index b1b6aec0..53f1ef9c 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -14,7 +14,7 @@ ojdbc-provider-jackson-oson 2.20.0 - 3.1.0 + 3.2.0 From 6976099e11ce2e285cf74d0d0eb85fd55cbd6646 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Thu, 5 Feb 2026 18:24:56 +0100 Subject: [PATCH 42/82] Update java version --- ojdbc-provider-jackson-oson/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index b1b6aec0..3fc53ea4 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -15,6 +15,8 @@ 2.20.0 3.1.0 + 11 + 11 From a184f37995246a20f441207e146abf0803e4efb2 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Thu, 5 Feb 2026 18:39:35 +0100 Subject: [PATCH 43/82] Change back to 3.1 --- ojdbc-provider-jackson-oson/pom.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index 93df7349..b1b6aec0 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -14,9 +14,7 @@ ojdbc-provider-jackson-oson 2.20.0 - 3.2.0 - 11 - 11 + 3.1.0 From 6a2943bd7ece128adf6d054d0f26849574150f76 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Tue, 10 Mar 2026 01:30:39 +0000 Subject: [PATCH 44/82] Cleaned up the code and applied coding standards --- ojdbc-provider-observability/pom.xml | 8 +- .../jfr/core/JFRUCPEventListenerProvider.java | 17 ++-- .../observability/jfr/core/UCPBaseEvent.java | 6 +- .../jfr/core/UCPEventFactory.java | 85 ++++++++++--------- .../connection/ConnectionBorrowedEvent.java | 11 +-- .../connection/ConnectionClosedEvent.java | 7 +- .../connection/ConnectionCreatedEvent.java | 5 +- .../connection/ConnectionReturnedEvent.java | 11 +-- .../events/lifecycle/PoolCreatedEvent.java | 12 +-- .../events/lifecycle/PoolDestroyedEvent.java | 9 +- .../events/lifecycle/PoolStartedEvent.java | 11 +-- .../events/lifecycle/PoolStartingEvent.java | 11 +-- .../events/lifecycle/PoolStoppedEvent.java | 9 +- .../events/maintenance/PoolPurgedEvent.java | 11 +-- .../events/maintenance/PoolRecycledEvent.java | 9 +- .../maintenance/PoolRefreshedEvent.java | 12 +-- .../maintenance/PoolRestartedEvent.java | 14 --- .../maintenance/PoolRestartingEvent.java | 16 ---- .../otel/OtelUCPEventListenerProvider.java | 51 +++++------ .../JFRUCPEventListenerProviderTest.java | 57 ++++++------- .../provider/observability/OtelUCPTest.java | 26 +++--- 21 files changed, 184 insertions(+), 214 deletions(-) delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartedEvent.java delete mode 100644 ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartingEvent.java diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index b262c8e6..d27d36c6 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -50,12 +50,6 @@ junit-jupiter-params test - - junit - junit - 4.13.2 - test - io.opentelemetry opentelemetry-sdk @@ -65,7 +59,7 @@ io.opentelemetry opentelemetry-sdk-testing - 1.32.0 + ${opentelemetry.version} test 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 index 06e8d996..6a5df992 100644 --- 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 @@ -10,28 +10,21 @@ * 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; +public final class JFRUCPEventListenerProvider + implements UCPEventListenerProvider { /** * 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() { + 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. * @@ -50,6 +43,6 @@ public String getName() { */ @Override public UCPEventListener getListener(Map config) { - return listener; + return TRACE_EVENT_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 index 579a2d10..a879a511 100644 --- 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 @@ -6,8 +6,9 @@ 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. + * 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") @@ -19,6 +20,7 @@ public abstract class UCPBaseEvent extends Event { /** Event timestamp in milliseconds since epoch */ @Label("Timestamp") + @Timestamp(Timestamp.MILLISECONDS_SINCE_EPOCH) protected long timestamp; /** Maximum configured pool size */ 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 index 7acc816b..b292096e 100644 --- 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 @@ -1,5 +1,7 @@ package oracle.ucp.provider.observability.jfr.core; +import java.util.Objects; + import jdk.jfr.Event; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.events.core.UCPEventListener; @@ -9,7 +11,8 @@ /** * Factory for creating and recording JFR events from UCP operations. - * Maps UCP event types to specific JFR event classes and handles recording. + * Maps UCP event types to specific JFR event classes and handles + * recording. */ public class UCPEventFactory { @@ -22,56 +25,62 @@ public class UCPEventFactory { * @throws IllegalStateException if event type is unrecognized * @throws NullPointerException if parameters are null */ - public static Event createEvent(UCPEventListener.EventType type, UCPEventContext ctx) { + public static Event createEvent( + UCPEventListener.EventType type, UCPEventContext ctx) { + Objects.requireNonNull(type, "EventType cannot be null"); 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); + // 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_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); + // 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); + // 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); + default: + throw new IllegalStateException( + "Unexpected event type: " + type); } } /** - * Creates and immediately records a JFR event for the UCP operation. + * Creates and immediately records a JFR event for the UCP + * operation. POOL_RESTARTING and POOL_RESTARTED events are + * silently ignored. * * @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) { + public static void recordEvent( + UCPEventListener.EventType type, UCPEventContext ctx) { + if (type == UCPEventListener.EventType.POOL_RESTARTING + || type == UCPEventListener.EventType.POOL_RESTARTED) { + return; + } 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 index 3172e8a2..b2fed277 100644 --- 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 @@ -1,16 +1,17 @@ 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; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.ConnectionBorrowed") @Label("Connection Borrowed") -@Category({"UCP Events","Connection Lifecycle Events"}) +@Category({"UCP Events", "Connection Lifecycle Events"}) public class ConnectionBorrowedEvent extends UCPBaseEvent { + public ConnectionBorrowedEvent(UCPEventContext ctx) { - initCommonFields(ctx); - } -} \ No newline at end of file + initCommonFields(ctx); + } +} 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 index fcc06111..27498e65 100644 --- 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 @@ -3,14 +3,15 @@ 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; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.ConnectionClosed") @Label("Connection Closed") -@Category({"UCP Events","Connection Lifecycle Events"}) +@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 index f3abaf7f..10d60c10 100644 --- 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 @@ -1,15 +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; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.ConnectionCreated") @Label("Connection Created") -@Category({"UCP Events","Connection Lifecycle Events"}) +@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 index d27bb182..ace9574a 100644 --- 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 @@ -1,16 +1,17 @@ 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; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.ConnectionReturned") @Label("Connection Returned") -@Category({"UCP Events","Connection Lifecycle Events"}) +@Category({"UCP Events", "Connection Lifecycle Events"}) public class ConnectionReturnedEvent extends UCPBaseEvent { + public ConnectionReturnedEvent(UCPEventContext ctx) { - initCommonFields(ctx); - } -} \ No newline at end of file + initCommonFields(ctx); + } +} 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 index e369f96c..f787198f 100644 --- 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 @@ -1,17 +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; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolCreated") @Label("Pool Created") -@Category({"UCP Events","Pool Lifecycle Events"}) +@Category({"UCP Events", "Pool Lifecycle Events"}) public class PoolCreatedEvent extends UCPBaseEvent { + public PoolCreatedEvent(UCPEventContext ctx) { - initCommonFields(ctx); - } -} \ No newline at end of file + initCommonFields(ctx); + } +} 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 index 97309299..95b23d72 100644 --- 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 @@ -1,14 +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.Label; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolDestroyed") @Label("Pool Destroyed") -@Category({"UCP Events","Pool Lifecycle Events"}) +@Category({"UCP Events", "Pool Lifecycle Events"}) public class PoolDestroyedEvent extends UCPBaseEvent { - public PoolDestroyedEvent(UCPEventContext ctx) { initCommonFields(ctx); } + + 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 index 1b38d79f..c76a8b8e 100644 --- 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 @@ -1,16 +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.Label; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolStarted") @Label("Pool Started") -@Category({"UCP Events","Pool Lifecycle Events"}) +@Category({"UCP Events", "Pool Lifecycle Events"}) public class PoolStartedEvent extends UCPBaseEvent { + public PoolStartedEvent(UCPEventContext ctx) { - initCommonFields(ctx); - } -} \ No newline at end of file + initCommonFields(ctx); + } +} 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 index 6456a8c9..34c97390 100644 --- 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 @@ -1,16 +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.Label; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolStarting") @Label("Pool Starting") -@Category({"UCP Events","Pool Lifecycle Events"}) +@Category({"UCP Events", "Pool Lifecycle Events"}) public class PoolStartingEvent extends UCPBaseEvent { + public PoolStartingEvent(UCPEventContext ctx) { - initCommonFields(ctx); - } -} \ No newline at end of file + initCommonFields(ctx); + } +} 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 index ad313a23..aebd3a26 100644 --- 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 @@ -1,14 +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.Label; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolStopped") @Label("Pool Stopped") -@Category({"UCP Events","Pool Lifecycle Events"}) +@Category({"UCP Events", "Pool Lifecycle Events"}) public class PoolStoppedEvent extends UCPBaseEvent { - public PoolStoppedEvent(UCPEventContext ctx) { initCommonFields(ctx); } + + 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 index 447fbaf8..3870a4b9 100644 --- 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 @@ -1,16 +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; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolPurged") @Label("Pool Purged") -@Category({"UCP Events","Maintenance Operations Events"}) +@Category({"UCP Events", "Maintenance Operations Events"}) public class PoolPurgedEvent extends UCPBaseEvent { + public PoolPurgedEvent(UCPEventContext ctx) { - initCommonFields(ctx); - } -} \ No newline at end of file + initCommonFields(ctx); + } +} 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 index fb84e799..174ccd81 100644 --- 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 @@ -1,16 +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; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolRecycled") @Label("Pool Recycled") -@Category({"UCP Events","Maintenance Operations Events"}) +@Category({"UCP Events", "Maintenance Operations Events"}) public class PoolRecycledEvent extends UCPBaseEvent { + public PoolRecycledEvent(UCPEventContext ctx) { - initCommonFields(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 index 1e2b15f5..b5d1e854 100644 --- 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 @@ -1,17 +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; +import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolRefreshed") @Label("Pool Refreshed") -@Category({"UCP Events","Maintenance Operations Events"}) +@Category({"UCP Events", "Maintenance Operations Events"}) public class PoolRefreshedEvent extends UCPBaseEvent { + public PoolRefreshedEvent(UCPEventContext ctx) { - initCommonFields(ctx); - } -} \ No newline at end of file + initCommonFields(ctx); + } +} 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 deleted file mode 100644 index c37e03ba..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartedEvent.java +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 8a916521..00000000 --- a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRestartingEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -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/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java index e0b0a291..00593ed3 100644 --- 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 @@ -37,11 +37,10 @@ public UCPEventListener getListener(Map config) { /** * Internal listener that converts UCP events to OpenTelemetry - * metrics. Thread-safe and handles all 14 UCP event types. + * metrics. Thread-safe and handles all 12 UCP event types. */ private static final class OtelUCPEventListener implements UCPEventListener { - private static final long serialVersionUID = 1L; private final Meter meter = GlobalOpenTelemetry.getMeter("oracle.ucp"); @@ -66,8 +65,6 @@ private static final class OtelUCPEventListener 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; @@ -77,7 +74,7 @@ private static final class OtelUCPEventListener private final LongCounter poolRecycledCounter; private final LongCounter poolPurgedCounter; - private final LongHistogram waitTimeHistogram; + private final ObservableLongGauge avgWaitTimeGauge; OtelUCPEventListener() { this.usedConnectionsGauge = @@ -221,18 +218,6 @@ public void accept(ObservableLongMeasurement measurement) { .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( @@ -282,13 +267,24 @@ public void accept(ObservableLongMeasurement measurement) { .setUnit("{operation}") .build(); - this.waitTimeHistogram = - meter.histogramBuilder("db.client.connections.wait_time") + this.avgWaitTimeGauge = + meter.gaugeBuilder("db.client.connections.wait_time") .setDescription( - "The time it took to obtain an open connection from the pool") + "The average wait time to obtain a connection from the pool") .setUnit("ms") .ofLongs() - .build(); + .buildWithCallback( + new Consumer() { + @Override + public void accept( + ObservableLongMeasurement measurement) { + for (UCPEventContext ctx : contextCache.values()) { + measurement.record( + ctx.getAverageConnectionWaitTime(), + Attributes.of(POOL_NAME, ctx.poolName())); + } + } + }); } @Override @@ -314,24 +310,15 @@ public void onUCPEvent(EventType eventType, UCPEventContext context) { 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); + contextCache.remove(context.poolName()); 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); @@ -348,6 +335,8 @@ public void onUCPEvent(EventType eventType, UCPEventContext context) { case POOL_PURGED: poolPurgedCounter.add(1, attrs); break; + default: + break; } } } 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 index 8ea22a3a..37ecac4d 100644 --- 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 @@ -9,19 +9,21 @@ 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 org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.Date; 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.*; +import static org.junit.jupiter.api.Assertions.*; public class JFRUCPEventListenerProviderTest { @@ -29,7 +31,7 @@ public class JFRUCPEventListenerProviderTest { private UCPEventListener listener; private Recording recording; - @Before + @BeforeEach public void setup() { provider = new JFRUCPEventListenerProvider(); listener = provider.getListener(null); @@ -39,7 +41,7 @@ public void setup() { recording.start(); } - @After + @AfterEach public void cleanup() { if (recording != null) { try { @@ -61,21 +63,21 @@ public void testProviderName() { @Test public void testProviderReturnsListener() { - assertNotNull("Provider should return a listener", listener); + assertNotNull(listener, "Provider should return a 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); + assertSame(listener1, listener2, + "Provider should return same listener instance"); } @Test public void testProviderReturnsSingletonListener() { - assertSame("Listener should be singleton TRACE_EVENT_LISTENER", - JFRUCPEventListenerProvider.TRACE_EVENT_LISTENER, listener); + assertSame(JFRUCPEventListenerProvider.TRACE_EVENT_LISTENER, listener, + "Listener should be singleton TRACE_EVENT_LISTENER"); } @Test @@ -92,7 +94,6 @@ 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, @@ -111,8 +112,8 @@ 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); + assertNotNull(event, "Factory should create event"); + assertTrue(event instanceof Event, "Event should be a JFR Event"); } @Test @@ -120,7 +121,6 @@ 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, @@ -131,19 +131,21 @@ public void testEventFactoryCreatesAllEventTypes() { for (EventType eventType : allEvents) { Event event = UCPEventFactory.createEvent(eventType, ctx); - assertNotNull("Factory should create event for " + eventType, event); + assertNotNull(event, "Factory should create event for " + eventType); } } - @Test(expected = NullPointerException.class) + @Test public void testEventFactoryRejectsNullContext() { - UCPEventFactory.createEvent(EventType.POOL_CREATED, null); + assertThrows(NullPointerException.class, + () -> UCPEventFactory.createEvent(EventType.POOL_CREATED, null)); } - @Test(expected = NullPointerException.class) + @Test public void testEventFactoryRejectsNullEventType() { UCPEventContext ctx = createTestContext("pool1", 1, 1, 10, 2); - UCPEventFactory.createEvent(null, ctx); + assertThrows(NullPointerException.class, + () -> UCPEventFactory.createEvent(null, ctx)); } @Test @@ -164,8 +166,8 @@ public void testRecordEventCommitsEvent() throws IOException { .filter(e -> e.getEventType().getName().startsWith("ucp.")) .collect(Collectors.toList()); - assertTrue("Should have recorded at least one UCP event", - ucpEvents.size() > 0); + assertTrue(ucpEvents.size() > 0, + "Should have recorded at least one UCP event"); Files.deleteIfExists(recordingFile); } @@ -188,7 +190,7 @@ public void testRecordedEventContainsPoolName() throws IOException { .findFirst() .orElse(null); - assertNotNull("Should find PoolCreated event", ucpEvent); + assertNotNull(ucpEvent, "Should find PoolCreated event"); assertEquals("test-pool-name", ucpEvent.getString("poolName")); Files.deleteIfExists(recordingFile); @@ -213,7 +215,7 @@ public void testRecordedEventContainsMetrics() throws IOException { .findFirst() .orElse(null); - assertNotNull("Should find ConnectionBorrowed event", ucpEvent); + assertNotNull(ucpEvent, "Should find ConnectionBorrowed event"); assertEquals("metrics-pool", ucpEvent.getString("poolName")); assertEquals(5, ucpEvent.getInt("borrowedConnections")); assertEquals(3, ucpEvent.getInt("availableConnections")); @@ -303,8 +305,6 @@ public void testAllLifecycleEventsInSequence() { 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); } @@ -364,9 +364,8 @@ public int closedConnections() { @Override public String formattedTimestamp() { - return new java.text.SimpleDateFormat( - "MMMM dd, yyyy HH:mm:ss.SSS z") - .format(new java.util.Date(timestamp())); + return new SimpleDateFormat("MMMM dd, yyyy HH:mm:ss.SSS z") + .format(new Date(timestamp())); } }; } 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 index 7c3c9427..c55e7163 100644 --- 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 @@ -7,14 +7,16 @@ 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 org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.HashMap; import static oracle.ucp.events.core.UCPEventListener.EventType; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class OtelUCPTest { @@ -22,7 +24,7 @@ public class OtelUCPTest { private OtelUCPEventListenerProvider provider; private UCPEventListener listener; - @Before + @BeforeEach public void setup() { GlobalOpenTelemetry.resetForTest(); @@ -39,7 +41,7 @@ public void setup() { listener = provider.getListener(null); } - @After + @AfterEach public void cleanup() { GlobalOpenTelemetry.resetForTest(); } @@ -51,15 +53,15 @@ public void testProviderName() { @Test public void testProviderReturnsListener() { - assertNotNull("Provider should return a listener", listener); + assertNotNull(listener, "Provider should return a 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); + assertSame(listener1, listener2, + "Provider should return same listener instance"); } @Test @@ -76,7 +78,6 @@ 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, @@ -212,9 +213,8 @@ public int closedConnections() { @Override public String formattedTimestamp() { - return new java.text.SimpleDateFormat( - "MMMM dd, yyyy HH:mm:ss.SSS z") - .format(new java.util.Date(timestamp())); + return new SimpleDateFormat("MMMM dd, yyyy HH:mm:ss.SSS z") + .format(new Date(timestamp())); } }; } From e5d91eb33f2286d182e17cfae5981ee2e496b446 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Tue, 10 Mar 2026 03:05:30 +0000 Subject: [PATCH 45/82] Cleaned up the code --- ojdbc-provider-observability/pom.xml | 12 ---- .../jfr/core/UCPEventFactory.java | 13 ++-- .../otel/OtelUCPEventListenerProvider.java | 5 +- .../JFRUCPEventListenerProviderTest.java | 42 +++++++++++++ .../provider/observability/OtelUCPTest.java | 61 ++++++++++++------- 5 files changed, 88 insertions(+), 45 deletions(-) diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index d27d36c6..9d8f3a5a 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -50,18 +50,6 @@ junit-jupiter-params test - - io.opentelemetry - opentelemetry-sdk - ${opentelemetry.version} - test - - - io.opentelemetry - opentelemetry-sdk-testing - ${opentelemetry.version} - test - ojdbc-provider-common com.oracle.database.jdbc 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 index b292096e..28e731ad 100644 --- 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 @@ -26,7 +26,7 @@ public class UCPEventFactory { * @throws NullPointerException if parameters are null */ public static Event createEvent( - UCPEventListener.EventType type, UCPEventContext ctx) { + UCPEventListener.EventType type, UCPEventContext ctx) { Objects.requireNonNull(type, "EventType cannot be null"); switch (type) { // Pool Lifecycle Events @@ -61,25 +61,20 @@ public static Event createEvent( default: throw new IllegalStateException( - "Unexpected event type: " + type); + "Unexpected event type: " + type); } } /** * Creates and immediately records a JFR event for the UCP - * operation. POOL_RESTARTING and POOL_RESTARTED events are - * silently ignored. + * 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) { - if (type == UCPEventListener.EventType.POOL_RESTARTING - || type == UCPEventListener.EventType.POOL_RESTARTED) { - return; - } + UCPEventListener.EventType type, UCPEventContext ctx) { Event event = createEvent(type, ctx); event.commit(); } 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 index 00593ed3..627498c5 100644 --- 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 @@ -22,8 +22,7 @@ public final class OtelUCPEventListenerProvider implements UCPEventListenerProvider { - private static final UCPEventListener LISTENER = - new OtelUCPEventListener(); + private final UCPEventListener listener = new OtelUCPEventListener(); @Override public String getName() { @@ -32,7 +31,7 @@ public String getName() { @Override public UCPEventListener getListener(Map config) { - return LISTENER; + return listener; } /** 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 index 37ecac4d..162e9df6 100644 --- 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 @@ -226,6 +226,48 @@ public void testRecordedEventContainsMetrics() throws IOException { Files.deleteIfExists(recordingFile); } + @Test + public void testRecordedEventContainsAvgWaitTime() throws IOException { + UCPEventContext ctx = new UCPEventContext() { + @Override public String poolName() { return "wait-pool"; } + @Override public long timestamp() { + return System.currentTimeMillis(); + } + @Override public int borrowedConnectionsCount() { return 1; } + @Override public int availableConnectionsCount() { return 1; } + @Override public int totalConnections() { return 2; } + @Override public int maxPoolSize() { return 10; } + @Override public int minPoolSize() { return 2; } + @Override public long getAverageConnectionWaitTime() { return 42; } + @Override public int createdConnections() { return 2; } + @Override public int closedConnections() { return 0; } + @Override public String formattedTimestamp() { + return new SimpleDateFormat("MMMM dd, yyyy HH:mm:ss.SSS z") + .format(new Date(timestamp())); + } + }; + + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, ctx); + + if (recording.getState() == RecordingState.RUNNING) { + recording.stop(); + } + Path recordingFile = Files.createTempFile("ucp-test", ".jfr"); + recording.dump(recordingFile); + + RecordedEvent ucpEvent = RecordingFile.readAllEvents(recordingFile) + .stream() + .filter(e -> e.getEventType().getName() + .equals("ucp.ConnectionBorrowed")) + .findFirst() + .orElse(null); + + assertNotNull(ucpEvent, "Should find ConnectionBorrowed event"); + assertEquals(42L, ucpEvent.getLong("avgWaitTime")); + + Files.deleteIfExists(recordingFile); + } + @Test public void testEmptyPoolNameAccepted() { UCPEventContext ctx = createTestContext("", 1, 1, 10, 2); 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 index c55e7163..f2e9b64d 100644 --- 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 @@ -1,15 +1,14 @@ 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 io.opentelemetry.api.OpenTelemetry; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.events.core.UCPEventListener; import oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import java.text.SimpleDateFormat; import java.util.Date; @@ -20,22 +19,15 @@ public class OtelUCPTest { - private InMemoryMetricReader metricReader; private OtelUCPEventListenerProvider provider; private UCPEventListener listener; @BeforeEach public void setup() { GlobalOpenTelemetry.resetForTest(); - - metricReader = InMemoryMetricReader.create(); - SdkMeterProvider meterProvider = SdkMeterProvider.builder() - .registerMetricReader(metricReader) - .build(); - - OpenTelemetrySdk.builder() - .setMeterProvider(meterProvider) - .buildAndRegisterGlobal(); + OpenTelemetry mockOTel = Mockito.mock( + OpenTelemetry.class, Mockito.RETURNS_DEEP_STUBS); + GlobalOpenTelemetry.set(mockOTel); provider = new OtelUCPEventListenerProvider(); listener = provider.getListener(null); @@ -102,12 +94,6 @@ public void testNullEventTypeIgnored() { 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'); @@ -115,6 +101,39 @@ public void testVeryLongPoolNameAccepted() { listener.onUCPEvent(EventType.POOL_CREATED, ctx); } + @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_STOPPED, ctx); + listener.onUCPEvent(EventType.POOL_DESTROYED, ctx); + } + + @Test + public void testEmptyPoolNameAccepted() { + UCPEventContext ctx = createTestContext("", 1, 1, 10, 2); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + } + @Test public void testZeroValuesAccepted() { UCPEventContext ctx = createTestContext("pool1", 0, 0, 0, 0); @@ -145,8 +164,8 @@ public void testConcurrentAccess() throws InterruptedException { final int threadId = i; threads[i] = new Thread(() -> { for (int j = 0; j < 10; j++) { - UCPEventContext ctx = createTestContext("pool" + threadId, j, j, - 10, 2); + UCPEventContext ctx = createTestContext( + "pool" + threadId, j, j, 10, 2); listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); } }); From 099995a7e977ecabbb4fd36bb79a41686a8539ff Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Tue, 10 Mar 2026 17:00:54 +0000 Subject: [PATCH 46/82] Create a separate Module Oracle UCP Observability Provider for JFR and OTEL providers for UCP --- ojdbc-provider-observability/pom.xml | 5 -- ojdbc-provider-ucp-observability/pom.xml | 66 ++++++++++++++ .../jfr/core/JFRUCPEventListenerProvider.java | 12 ++- .../observability/jfr/core/UCPBaseEvent.java | 1 - .../jfr/core/UCPEventFactory.java | 0 .../connection/ConnectionBorrowedEvent.java | 0 .../connection/ConnectionClosedEvent.java | 0 .../connection/ConnectionCreatedEvent.java | 0 .../connection/ConnectionReturnedEvent.java | 0 .../events/lifecycle/PoolCreatedEvent.java | 0 .../events/lifecycle/PoolDestroyedEvent.java | 0 .../events/lifecycle/PoolStartedEvent.java | 0 .../events/lifecycle/PoolStartingEvent.java | 0 .../events/lifecycle/PoolStoppedEvent.java | 0 .../events/maintenance/PoolPurgedEvent.java | 0 .../events/maintenance/PoolRecycledEvent.java | 0 .../maintenance/PoolRefreshedEvent.java | 0 .../otel/OtelUCPEventListenerProvider.java | 0 ...e.ucp.events.core.UCPEventListenerProvider | 2 +- .../JFRUCPEventListenerProviderTest.java | 0 .../provider/observability/OtelUCPTest.java | 0 .../provider/observability/SampleTestUCP.java | 90 +++++++++++++++++++ pom.xml | 1 + 23 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 ojdbc-provider-ucp-observability/pom.xml rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java (86%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java (97%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider (97%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java (100%) rename {ojdbc-provider-observability => ojdbc-provider-ucp-observability}/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java (100%) create mode 100644 ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/SampleTestUCP.java diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 9d8f3a5a..8cc2aba4 100644 --- a/ojdbc-provider-observability/pom.xml +++ b/ojdbc-provider-observability/pom.xml @@ -25,11 +25,6 @@ com.oracle.database.jdbc ojdbc8 - - com.oracle.database.jdbc - ucp - 23.26.1.0.0 - io.opentelemetry opentelemetry-api diff --git a/ojdbc-provider-ucp-observability/pom.xml b/ojdbc-provider-ucp-observability/pom.xml new file mode 100644 index 00000000..a8fbf8ae --- /dev/null +++ b/ojdbc-provider-ucp-observability/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + + com.oracle.database.jdbc + ojdbc-extensions + 1.0.6 + + + Oracle UCP Observability Provider + com.oracle.database.jdbc + ojdbc-provider-ucp-observability + + + 1.44.1 + 11 + 11 + + + + + com.oracle.database.jdbc + ucp + 23.26.1.0.0 + + + com.oracle.database.jdbc + ojdbc8 + + + io.opentelemetry + opentelemetry-api + ${opentelemetry.version} + + + org.mockito + mockito-core + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + none + alphabetical + + + + + diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java similarity index 86% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java index 6a5df992..32276fec 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java @@ -13,6 +13,9 @@ 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. @@ -25,6 +28,13 @@ public void onUCPEvent(EventType eventType, UCPEventContext context) { } }; + /** + * Creates a new provider instance. + */ + public JFRUCPEventListenerProvider() { + this.listener = TRACE_EVENT_LISTENER; + } + /** * Returns the provider's unique identifier. * @@ -43,6 +53,6 @@ public String getName() { */ @Override public UCPEventListener getListener(Map config) { - return TRACE_EVENT_LISTENER; + 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-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java similarity index 97% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java index a879a511..02915595 100644 --- a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java @@ -20,7 +20,6 @@ public abstract class UCPBaseEvent extends Event { /** Event timestamp in milliseconds since epoch */ @Label("Timestamp") - @Timestamp(Timestamp.MILLISECONDS_SINCE_EPOCH) protected long timestamp; /** Maximum configured pool size */ diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java diff --git a/ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java similarity index 100% rename from ojdbc-provider-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java rename to ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java diff --git a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider b/ojdbc-provider-ucp-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider similarity index 97% rename from ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider rename to ojdbc-provider-ucp-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider index 27deb021..17dc3d45 100644 --- a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider +++ b/ojdbc-provider-ucp-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider @@ -1,2 +1,2 @@ oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider -oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider \ No newline at end of file +oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider diff --git a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java similarity index 100% rename from ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java rename to ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java diff --git a/ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java similarity index 100% rename from ojdbc-provider-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java rename to ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/SampleTestUCP.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/SampleTestUCP.java new file mode 100644 index 00000000..7d8eeadd --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/SampleTestUCP.java @@ -0,0 +1,90 @@ +package oracle.ucp.provider.observability; + +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; +public class SampleTestUCP { + +public static void main(String args[]) throws Exception { + + System.setProperty("oracle.jdbc.Trace", "true"); + System.setProperty("java.util.logging.config.file", "./src_test/test/standalone/ucplogging.properties"); + System.setProperty("oracle.ucp.wls.jta", "false"); + // Configure the connection pool + PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); + + pds.setInitialPoolSize(2); + pds.setMinPoolSize(0); + pds.setMaxPoolSize(50); + pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); + pds.setURL("jdbc:oracle:thin:@adb_medium?TNS_ADMIN=/Users/abdessamadelaaissaoui/Desktop/Wallet_adb/"); + pds.setUser("ADMIN"); + pds.setPassword("Madara@@1234"); + pds.setConnectionPoolName("Abdessamad's Pool"); + + pds.setUCPEventListenerProvider("jfr-ucp-listener"); + //System.setProperty("UCPEventListenerProvider","jfr-ucp-listener"); + + UniversalConnectionPoolManager mgr = UniversalConnectionPoolManagerImpl. + getUniversalConnectionPoolManager(); + + + mgr.createConnectionPool((UniversalConnectionPoolAdapter) pds); + + mgr.startConnectionPool("Abdessamad's Pool"); + + UniversalPooledConnection conn1 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + UniversalPooledConnection conn2 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + UniversalPooledConnection conn3 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + UniversalPooledConnection conn4 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + UniversalPooledConnection conn5 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + UniversalPooledConnection conn6 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + UniversalPooledConnection conn7 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + UniversalPooledConnection conn8 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + UniversalPooledConnection conn9 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); + + + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn1); + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn2); + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn3); + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn4); + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn5); + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn6); + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn7); + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn8); + mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn9); + + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn1); + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn2); + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn3); + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn4); + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn5); + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn6); + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn7); + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn8); + mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn9); + + + pds.setURL("jdbc:oracle:thin:@adb_medium?TNS_ADMIN=/Users/abdessamadelaaissaoui/Desktop/Wallet_adb/"); + + + mgr.purgeConnectionPool("Abdessamad's Pool"); + mgr.recycleConnectionPool("Abdessamad's Pool"); + mgr.refreshConnectionPool("Abdessamad's Pool"); + + + mgr.stopConnectionPool("Abdessamad's Pool"); + + mgr.destroyConnectionPool("Abdessamad's Pool"); +}} \ No newline at end of file diff --git a/pom.xml b/pom.xml index f0518d1e..33238834 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ ojdbc-provider-gcp ojdbc-provider-jackson-oson ojdbc-provider-observability + ojdbc-provider-ucp-observability ojdbc-provider-hashicorp ojdbc-provider-pkl From e3364a4522b4549405a3eaa2437c3fb586db9c42 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Thu, 12 Mar 2026 06:58:09 +0000 Subject: [PATCH 47/82] Fixing JFR and OTEL Providers --- ojdbc-provider-ucp-observability/pom.xml | 16 +- .../observability/jfr/core/UCPBaseEvent.java | 27 +- .../jfr/core/UCPEventFactory.java | 84 ++-- .../otel/OtelUCPEventListenerProvider.java | 440 ++++++------------ ...e.ucp.events.core.UCPEventListenerProvider | 2 +- 5 files changed, 214 insertions(+), 355 deletions(-) diff --git a/ojdbc-provider-ucp-observability/pom.xml b/ojdbc-provider-ucp-observability/pom.xml index a8fbf8ae..59bfe8d7 100644 --- a/ojdbc-provider-ucp-observability/pom.xml +++ b/ojdbc-provider-ucp-observability/pom.xml @@ -5,9 +5,9 @@ 4.0.0 - com.oracle.database.jdbc - ojdbc-extensions - 1.0.6 + com.oracle.database.jdbc + ojdbc-extensions + 1.0.6 Oracle UCP Observability Provider @@ -15,9 +15,9 @@ ojdbc-provider-ucp-observability - 1.44.1 - 11 - 11 + 1.44.1 + 11 + 11 @@ -30,6 +30,10 @@ com.oracle.database.jdbc ojdbc8 + + com.oracle.database.security + oraclepki + io.opentelemetry opentelemetry-api diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java index 02915595..fd5f5108 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java @@ -10,7 +10,7 @@ * and initialization. All UCP events extend this class to inherit * standard pool metrics and metadata. */ -@Category("UCP Events") +@StackTrace(false) @Description("Base UCP Event") public abstract class UCPBaseEvent extends Event { @@ -18,9 +18,14 @@ public abstract class UCPBaseEvent extends Event { @Label("Pool Name") protected String poolName; - /** Event timestamp in milliseconds since epoch */ - @Label("Timestamp") - protected long timestamp; + /** + * + *

    Note: This is the timestamp captured by UCP at event creation time, + * preserved for correlation purposes. JFR also records its own startTime + * automatically — this field complements it rather than replacing it. + */ + @Label("UCP Timestamp (ms)") + protected long ucpTimestamp; /** Maximum configured pool size */ @Label("Max Pool Size") @@ -64,15 +69,15 @@ public abstract class UCPBaseEvent extends Event { 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.poolName = ctx.poolName(); + this.ucpTimestamp = 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.totalConnections = ctx.totalConnections(); + this.closedConnections = ctx.closedConnections(); this.createdConnections = ctx.createdConnections(); - this.avgWaitTime = ctx.getAverageConnectionWaitTime(); + this.avgWaitTime = ctx.getAverageConnectionWaitTime(); } } \ No newline at end of file diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java index 28e731ad..6ba83de9 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java @@ -1,7 +1,5 @@ package oracle.ucp.provider.observability.jfr.core; -import java.util.Objects; - import jdk.jfr.Event; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.events.core.UCPEventListener; @@ -9,6 +7,9 @@ import oracle.ucp.provider.observability.jfr.events.lifecycle.*; import oracle.ucp.provider.observability.jfr.events.maintenance.*; +import java.util.Objects; +import java.util.logging.Logger; + /** * Factory for creating and recording JFR events from UCP operations. * Maps UCP event types to specific JFR event classes and handles @@ -16,66 +17,69 @@ */ public class UCPEventFactory { + private static final Logger LOGGER = + Logger.getLogger(UCPEventFactory.class.getName()); + /** * Creates a JFR event instance for the specified UCP event type. * * @param type UCP event type - * @param ctx event context with pool metrics + * @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( + static Event createEvent( UCPEventListener.EventType type, UCPEventContext ctx) { Objects.requireNonNull(type, "EventType cannot be null"); + Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); + 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_DESTROYED: - return new PoolDestroyedEvent(ctx); + // 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_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); + // 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); + // 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); + default: + LOGGER.fine(() -> + "Unrecognized UCP EventType ignored by JFR provider: " + type); + return null; } } /** - * Creates and immediately records a JFR event for the UCP - * operation. + * Creates and records a JFR event for the given UCP operation, + * only if JFR recording is currently active. * * @param type UCP event type to record - * @param ctx event context with pool metrics + * @param ctx event context with pool metrics * @throws NullPointerException if parameters are null */ public static void recordEvent( UCPEventListener.EventType type, UCPEventContext ctx) { + Objects.requireNonNull(type, "EventType cannot be null"); + Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); + Event event = createEvent(type, ctx); - event.commit(); + + if (event == null) { + return; + } + + if (event.shouldCommit()) { + event.commit(); + } } -} +} \ No newline at end of file diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java index 627498c5..715f8ad2 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -9,20 +9,19 @@ 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. + * + *

    Each UCP event carries a snapshot of pool data at the moment it fired. + * This provider simply records that data directly into OTel instruments *

    + * */ public final class OtelUCPEventListenerProvider implements UCPEventListenerProvider { - private final UCPEventListener listener = new OtelUCPEventListener(); + private volatile UCPEventListener listener; @Override public String getName() { @@ -31,311 +30,158 @@ public String getName() { @Override public UCPEventListener getListener(Map config) { + if (listener == null) { + synchronized (this) { + if (listener == null) { + listener = new OtelUCPEventListener(); + } + } + } return listener; } - /** - * Internal listener that converts UCP events to OpenTelemetry - * metrics. Thread-safe and handles all 12 UCP event types. - */ - private static final class OtelUCPEventListener - implements UCPEventListener { + private static final class OtelUCPEventListener implements UCPEventListener { + + private static final long serialVersionUID = 1L; - private final Meter meter = - GlobalOpenTelemetry.getMeter("oracle.ucp"); + 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 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 ObservableLongGauge avgWaitTimeGauge; - - 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.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.avgWaitTimeGauge = - meter.gaugeBuilder("db.client.connections.wait_time") - .setDescription( - "The average wait time to obtain a connection from the pool") - .setUnit("ms") - .ofLongs() - .buildWithCallback( - new Consumer() { - @Override - public void accept( - ObservableLongMeasurement measurement) { - for (UCPEventContext ctx : contextCache.values()) { - measurement.record( - ctx.getAverageConnectionWaitTime(), - Attributes.of(POOL_NAME, ctx.poolName())); - } - } - }); - } + AttributeKey.stringKey("db.client.connection.pool.name"); + + private final LongCounter poolCreatedCounter = + meter.counterBuilder("db.client.connection.pool.created") + .setDescription("Number of POOL_CREATED events.") + .setUnit("{event}").build(); + + private final LongCounter poolStartingCounter = + meter.counterBuilder("db.client.connection.pool.starting") + .setDescription("Number of POOL_STARTING events.") + .setUnit("{event}").build(); + + private final LongCounter poolStartedCounter = + meter.counterBuilder("db.client.connection.pool.started") + .setDescription("Number of POOL_STARTED events.") + .setUnit("{event}").build(); + + private final LongCounter poolStoppedCounter = + meter.counterBuilder("db.client.connection.pool.stopped") + .setDescription("Number of POOL_STOPPED events.") + .setUnit("{event}").build(); + + private final LongCounter poolDestroyedCounter = + meter.counterBuilder("db.client.connection.pool.destroyed") + .setDescription("Number of POOL_DESTROYED events.") + .setUnit("{event}").build(); + + private final LongCounter poolRefreshedCounter = + meter.counterBuilder("db.client.connection.pool.refreshed") + .setDescription("Number of POOL_REFRESHED events.") + .setUnit("{event}").build(); + + private final LongCounter poolRecycledCounter = + meter.counterBuilder("db.client.connection.pool.recycled") + .setDescription("Number of POOL_RECYCLED events.") + .setUnit("{event}").build(); + + private final LongCounter poolPurgedCounter = + meter.counterBuilder("db.client.connection.pool.purged") + .setDescription("Number of POOL_PURGED events.") + .setUnit("{event}").build(); + + private final LongCounter connectionCreatedCounter = + meter.counterBuilder("db.client.connection.created") + .setDescription("Number of CONNECTION_CREATED events.") + .setUnit("{event}").build(); + + private final LongCounter connectionBorrowedCounter = + meter.counterBuilder("db.client.connection.borrowed") + .setDescription("Number of CONNECTION_BORROWED events.") + .setUnit("{event}").build(); + + private final LongCounter connectionReturnedCounter = + meter.counterBuilder("db.client.connection.returned") + .setDescription("Number of CONNECTION_RETURNED events.") + .setUnit("{event}").build(); + + private final LongCounter connectionClosedCounter = + meter.counterBuilder("db.client.connection.closed") + .setDescription("Number of CONNECTION_CLOSED events.") + .setUnit("{event}").build(); + + + private final LongHistogram borrowedConnectionsHistogram = + meter.histogramBuilder("db.client.connection.borrowed_count") + .setDescription("Snapshot of borrowed connections count at the time of the event.") + .setUnit("{connection}").ofLongs().build(); + + private final LongHistogram availableConnectionsHistogram = + meter.histogramBuilder("db.client.connection.available_count") + .setDescription("Snapshot of available connections count at the time of the event.") + .setUnit("{connection}").ofLongs().build(); + + private final LongHistogram totalConnectionsHistogram = + meter.histogramBuilder("db.client.connection.total_count") + .setDescription("Snapshot of total connections (borrowed + available) at the time of the event.") + .setUnit("{connection}").ofLongs().build(); + + private final LongHistogram createdConnectionsHistogram = + meter.histogramBuilder("db.client.connection.created_count") + .setDescription("Snapshot of cumulative created connections at the time of the event.") + .setUnit("{connection}").ofLongs().build(); + + private final LongHistogram closedConnectionsHistogram = + meter.histogramBuilder("db.client.connection.closed_count") + .setDescription("Snapshot of cumulative closed connections at the time of the event.") + .setUnit("{connection}").ofLongs().build(); + + private final LongHistogram avgWaitTimeHistogram = + meter.histogramBuilder("db.client.connection.wait_time") + .setDescription("Average connection wait time in milliseconds at the time of the event.") + .setUnit("ms").ofLongs().build(); + + private final LongHistogram maxPoolSizeHistogram = + meter.histogramBuilder("db.client.connection.max_pool_size") + .setDescription("Snapshot of configured max pool size at the time of the event.") + .setUnit("{connection}").ofLongs().build(); + + private final LongHistogram minPoolSizeHistogram = + meter.histogramBuilder("db.client.connection.min_pool_size") + .setDescription("Snapshot of configured min pool size at the time of the event.") + .setUnit("{connection}").ofLongs().build(); + @Override - public void onUCPEvent(EventType eventType, UCPEventContext context) { - if (context == null || eventType == null) { + public void onUCPEvent(EventType eventType, UCPEventContext ctx) { + if (eventType == null || ctx == null) { return; } - contextCache.put(context.poolName(), context); + Attributes attrs = Attributes.of(POOL_NAME, ctx.poolName()); - Attributes attrs = Attributes.of(POOL_NAME, context.poolName()); + borrowedConnectionsHistogram.record(ctx.borrowedConnectionsCount(), attrs); + availableConnectionsHistogram.record(ctx.availableConnectionsCount(), attrs); + totalConnectionsHistogram.record(ctx.totalConnections(), attrs); + createdConnectionsHistogram.record(ctx.createdConnections(), attrs); + closedConnectionsHistogram.record(ctx.closedConnections(), attrs); + avgWaitTimeHistogram.record(ctx.getAverageConnectionWaitTime(), attrs); + maxPoolSizeHistogram.record(ctx.maxPoolSize(), attrs); + minPoolSizeHistogram.record(ctx.minPoolSize(), attrs); 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_DESTROYED: - poolDestroyedCounter.add(1, attrs); - contextCache.remove(context.poolName()); - break; - case CONNECTION_CREATED: - connectionCreatedCounter.add(1, attrs); - break; - case CONNECTION_BORROWED: - connectionBorrowedCounter.add(1, 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; - default: - break; + 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_DESTROYED: poolDestroyedCounter.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; + case CONNECTION_CREATED: connectionCreatedCounter.add(1, attrs); break; + case CONNECTION_BORROWED: connectionBorrowedCounter.add(1, attrs); break; + case CONNECTION_RETURNED: connectionReturnedCounter.add(1, attrs); break; + case CONNECTION_CLOSED: connectionClosedCounter.add(1, attrs); break; + default: break; } } } diff --git a/ojdbc-provider-ucp-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider b/ojdbc-provider-ucp-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider index 17dc3d45..27deb021 100644 --- a/ojdbc-provider-ucp-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider +++ b/ojdbc-provider-ucp-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider @@ -1,2 +1,2 @@ oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider -oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider +oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider \ No newline at end of file From 7e62ca8235bb8e50787f41960744f5146e0e985b Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Thu, 12 Mar 2026 06:58:53 +0000 Subject: [PATCH 48/82] Added Unit Tests --- .../OtelUCPEventListenerProviderTest.java | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java new file mode 100644 index 00000000..6c7bd797 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java @@ -0,0 +1,294 @@ +package oracle.ucp.provider.observability; + +import oracle.ucp.events.core.UCPEventContext; +import oracle.ucp.events.core.UCPEventListener; +import oracle.ucp.events.core.UCPEventListener.EventType; +import oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@DisplayName("OTel UCP Event Listener Provider Tests") +class OtelUCPEventListenerProviderTest { + + private UCPEventContext mockContext; + + @BeforeEach + void setUpMockContext() { + mockContext = mock(UCPEventContext.class); + when(mockContext.poolName()).thenReturn("TestPool"); + when(mockContext.maxPoolSize()).thenReturn(50); + when(mockContext.minPoolSize()).thenReturn(2); + when(mockContext.borrowedConnectionsCount()).thenReturn(3); + when(mockContext.availableConnectionsCount()).thenReturn(5); + when(mockContext.totalConnections()).thenReturn(8); + when(mockContext.closedConnections()).thenReturn(1); + when(mockContext.createdConnections()).thenReturn(9); + when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); + } + + + @Nested + @DisplayName("OtelUCPEventListenerProvider") + class ProviderTests { + + private OtelUCPEventListenerProvider provider; + + @BeforeEach + void setUp() { + provider = new OtelUCPEventListenerProvider(); + } + + @Test + @DisplayName("getName() returns the expected provider identifier") + void testGetName() { + assertEquals("otel-ucp-listener", provider.getName()); + } + + @Test + @DisplayName("getListener() returns a non-null listener") + void testGetListenerNotNull() { + UCPEventListener listener = provider.getListener(Collections.emptyMap()); + assertNotNull(listener); + } + + @Test + @DisplayName("getListener() returns a non-null listener when config is null") + void testGetListenerWithNullConfig() { + UCPEventListener listener = provider.getListener(null); + assertNotNull(listener); + } + + @Test + @DisplayName("getListener() returns the same instance on repeated calls (singleton)") + void testGetListenerReturnsSameInstance() { + UCPEventListener listener1 = provider.getListener(Collections.emptyMap()); + UCPEventListener listener2 = provider.getListener(Collections.emptyMap()); + assertSame(listener1, listener2); + } + + @Test + @DisplayName("isDesiredEvent() returns true for all event types") + void testIsDesiredEventReturnsTrueForAll() { + UCPEventListener listener = provider.getListener(Collections.emptyMap()); + for (EventType type : EventType.values()) { + assertTrue(listener.isDesiredEvent(type), + "Expected isDesiredEvent to return true for: " + type); + } + } + } + + + @Nested + @DisplayName("UCPEventListener — null safety") + class ListenerNullSafetyTests { + + private UCPEventListener listener; + + @BeforeEach + void setUp() { + listener = new OtelUCPEventListenerProvider() + .getListener(Collections.emptyMap()); + } + + @Test + @DisplayName("onUCPEvent() does not throw when eventType is null") + void testOnUCPEventNullEventType() { + assertDoesNotThrow(() -> listener.onUCPEvent(null, mockContext)); + } + + @Test + @DisplayName("onUCPEvent() does not throw when context is null") + void testOnUCPEventNullContext() { + assertDoesNotThrow(() -> + listener.onUCPEvent(EventType.CONNECTION_BORROWED, null)); + } + + @Test + @DisplayName("onUCPEvent() does not throw when both args are null") + void testOnUCPEventBothNull() { + assertDoesNotThrow(() -> listener.onUCPEvent(null, null)); + } + } + + @Nested + @DisplayName("UCPEventListener — onUCPEvent robustness") + class ListenerRobustnessTests { + + private UCPEventListener listener; + + @BeforeEach + void setUp() { + listener = new OtelUCPEventListenerProvider() + .getListener(Collections.emptyMap()); + } + + @ParameterizedTest(name = "onUCPEvent() does not throw for EventType.{0}") + @EnumSource(EventType.class) + @DisplayName("onUCPEvent() does not throw for any known EventType") + void testOnUCPEventDoesNotThrowForAllTypes(EventType type) { + assertDoesNotThrow(() -> listener.onUCPEvent(type, mockContext)); + } + + @Test + @DisplayName("onUCPEvent() does not throw when context has zero/default values") + void testOnUCPEventWithZeroValues() { + UCPEventContext zeroContext = mock(UCPEventContext.class); + when(zeroContext.poolName()).thenReturn(""); + when(zeroContext.borrowedConnectionsCount()).thenReturn(0); + when(zeroContext.availableConnectionsCount()).thenReturn(0); + when(zeroContext.totalConnections()).thenReturn(0); + when(zeroContext.closedConnections()).thenReturn(0); + when(zeroContext.createdConnections()).thenReturn(0); + when(zeroContext.getAverageConnectionWaitTime()).thenReturn(0L); + when(zeroContext.maxPoolSize()).thenReturn(0); + when(zeroContext.minPoolSize()).thenReturn(0); + + assertDoesNotThrow(() -> + listener.onUCPEvent(EventType.CONNECTION_BORROWED, zeroContext)); + } + } + + + @Nested + @DisplayName("UCPEventListener — histogram fields read on every event") + class ListenerHistogramFieldTests { + + private UCPEventListener listener; + + @BeforeEach + void setUp() { + listener = new OtelUCPEventListenerProvider() + .getListener(Collections.emptyMap()); + } + + @Test + @DisplayName("poolName() is always read — used as metric attribute on every event") + void testPoolNameAlwaysRead() { + listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + verify(mockContext, atLeastOnce()).poolName(); + } + + @Test + @DisplayName("All 8 histogram fields are read on CONNECTION_BORROWED") + void testAllHistogramFieldsReadOnBorrow() { + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); + verify(mockContext).borrowedConnectionsCount(); + verify(mockContext).availableConnectionsCount(); + verify(mockContext).totalConnections(); + verify(mockContext).createdConnections(); + verify(mockContext).closedConnections(); + verify(mockContext).getAverageConnectionWaitTime(); + verify(mockContext).maxPoolSize(); + verify(mockContext).minPoolSize(); + } + + @Test + @DisplayName("All 8 histogram fields are read on POOL_CREATED") + void testAllHistogramFieldsReadOnPoolCreated() { + listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + verify(mockContext).borrowedConnectionsCount(); + verify(mockContext).availableConnectionsCount(); + verify(mockContext).totalConnections(); + verify(mockContext).createdConnections(); + verify(mockContext).closedConnections(); + verify(mockContext).getAverageConnectionWaitTime(); + verify(mockContext).maxPoolSize(); + verify(mockContext).minPoolSize(); + } + + @Test + @DisplayName("All 8 histogram fields are read on POOL_DESTROYED") + void testAllHistogramFieldsReadOnPoolDestroyed() { + listener.onUCPEvent(EventType.POOL_DESTROYED, mockContext); + verify(mockContext).borrowedConnectionsCount(); + verify(mockContext).availableConnectionsCount(); + verify(mockContext).totalConnections(); + verify(mockContext).createdConnections(); + verify(mockContext).closedConnections(); + verify(mockContext).getAverageConnectionWaitTime(); + verify(mockContext).maxPoolSize(); + verify(mockContext).minPoolSize(); + } + + @ParameterizedTest(name = "All histogram fields are read for EventType.{0}") + @EnumSource(EventType.class) + @DisplayName("All 8 histogram fields are read for every EventType") + void testAllHistogramFieldsReadForAllEventTypes(EventType type) { + listener.onUCPEvent(type, mockContext); + verify(mockContext).borrowedConnectionsCount(); + verify(mockContext).availableConnectionsCount(); + verify(mockContext).totalConnections(); + verify(mockContext).createdConnections(); + verify(mockContext).closedConnections(); + verify(mockContext).getAverageConnectionWaitTime(); + verify(mockContext).maxPoolSize(); + verify(mockContext).minPoolSize(); + } + } + + + @Nested + @DisplayName("UCPEventListener — multiple pool independence") + class ListenerMultiPoolTests { + + private UCPEventListener listener; + + @BeforeEach + void setUp() { + listener = new OtelUCPEventListenerProvider() + .getListener(Collections.emptyMap()); + } + + @Test + @DisplayName("Multiple pools can emit events independently without interference") + void testMultiplePoolsIndependent() { + UCPEventContext pool1Context = mock(UCPEventContext.class); + when(pool1Context.poolName()).thenReturn("Pool1"); + when(pool1Context.borrowedConnectionsCount()).thenReturn(2); + when(pool1Context.availableConnectionsCount()).thenReturn(3); + when(pool1Context.totalConnections()).thenReturn(5); + when(pool1Context.getAverageConnectionWaitTime()).thenReturn(10L); + when(pool1Context.maxPoolSize()).thenReturn(10); + when(pool1Context.minPoolSize()).thenReturn(1); + when(pool1Context.createdConnections()).thenReturn(5); + when(pool1Context.closedConnections()).thenReturn(0); + + UCPEventContext pool2Context = mock(UCPEventContext.class); + when(pool2Context.poolName()).thenReturn("Pool2"); + when(pool2Context.borrowedConnectionsCount()).thenReturn(7); + when(pool2Context.availableConnectionsCount()).thenReturn(1); + when(pool2Context.totalConnections()).thenReturn(8); + when(pool2Context.getAverageConnectionWaitTime()).thenReturn(50L); + when(pool2Context.maxPoolSize()).thenReturn(20); + when(pool2Context.minPoolSize()).thenReturn(2); + when(pool2Context.createdConnections()).thenReturn(8); + when(pool2Context.closedConnections()).thenReturn(1); + + assertDoesNotThrow(() -> { + listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool1Context); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool2Context); + listener.onUCPEvent(EventType.CONNECTION_RETURNED, pool1Context); + listener.onUCPEvent(EventType.POOL_DESTROYED, pool2Context); + }); + } + + @Test + @DisplayName("Events after POOL_DESTROYED for same pool do not throw") + void testEventsAfterPoolDestroyed() { + listener.onUCPEvent(EventType.POOL_DESTROYED, mockContext); + assertDoesNotThrow(() -> { + listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); + }); + } + } +} \ No newline at end of file From 1e239b40373a0021fe68d79a68cb29b1614ed2a7 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Thu, 12 Mar 2026 06:59:38 +0000 Subject: [PATCH 49/82] Added Unit Tests for JFR provider --- .../JFRUCPEventListenerProviderTest.java | 492 +++++------------- 1 file changed, 122 insertions(+), 370 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java index 162e9df6..33ad8d56 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java @@ -1,414 +1,166 @@ 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.events.core.UCPEventListener.EventType; import oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider; import oracle.ucp.provider.observability.jfr.core.UCPEventFactory; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.stream.Collectors; +import java.util.Collections; -import static oracle.ucp.events.core.UCPEventListener.EventType; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; -public class JFRUCPEventListenerProviderTest { +@DisplayName("JFR UCP Event Listener Provider Tests") +class JFRUCPEventListenerProviderTest { - private JFRUCPEventListenerProvider provider; - private UCPEventListener listener; - private Recording recording; + private UCPEventContext mockContext; @BeforeEach - public void setup() { - provider = new JFRUCPEventListenerProvider(); - listener = provider.getListener(null); - - recording = new Recording(); - recording.enable("ucp.*"); - recording.start(); - } - - @AfterEach - public void cleanup() { - if (recording != null) { - try { - if (recording.getState() == RecordingState.RUNNING) { - recording.stop(); - } - } catch (IllegalStateException e) { - // Already stopped, ignore - } finally { - recording.close(); - } + void setUpMockContext() { + mockContext = mock(UCPEventContext.class); + when(mockContext.poolName()).thenReturn("TestPool"); + when(mockContext.timestamp()).thenReturn(123456789L); + when(mockContext.maxPoolSize()).thenReturn(50); + when(mockContext.minPoolSize()).thenReturn(2); + when(mockContext.borrowedConnectionsCount()).thenReturn(3); + when(mockContext.availableConnectionsCount()).thenReturn(5); + when(mockContext.totalConnections()).thenReturn(8); + when(mockContext.closedConnections()).thenReturn(1); + when(mockContext.createdConnections()).thenReturn(9); + when(mockContext.getAverageConnectionWaitTime()).thenReturn(10L); + } + + @Nested + @DisplayName("JFRUCPEventListenerProvider") + class ProviderTests { + + private JFRUCPEventListenerProvider provider; + + @BeforeEach + void setUp() { + provider = new JFRUCPEventListenerProvider(); } - } - - @Test - public void testProviderName() { - assertEquals("jfr-ucp-listener", provider.getName()); - } - - @Test - public void testProviderReturnsListener() { - assertNotNull(listener, "Provider should return a listener"); - } - - @Test - public void testProviderReturnsSameListenerInstance() { - UCPEventListener listener1 = provider.getListener(null); - UCPEventListener listener2 = provider.getListener(new HashMap<>()); - assertSame(listener1, listener2, - "Provider should return same listener instance"); - } - @Test - public void testProviderReturnsSingletonListener() { - assertSame(JFRUCPEventListenerProvider.TRACE_EVENT_LISTENER, listener, - "Listener should be singleton TRACE_EVENT_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_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 + @DisplayName("getName() returns the expected provider identifier") + void testGetName() { + assertEquals("jfr-ucp-listener", provider.getName()); } - } - - @Test - public void testEventFactoryCreatesEvents() { - UCPEventContext ctx = createTestContext("pool1", 1, 1, 10, 2); - - Event event = UCPEventFactory.createEvent(EventType.POOL_CREATED, ctx); - assertNotNull(event, "Factory should create event"); - assertTrue(event instanceof Event, "Event should be a JFR Event"); - } - @Test - public void testEventFactoryCreatesAllEventTypes() { - EventType[] allEvents = { - EventType.POOL_CREATED, EventType.POOL_STARTING, - EventType.POOL_STARTED, EventType.POOL_STOPPED, - 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(event, "Factory should create event for " + eventType); + @Test + @DisplayName("getListener() returns a non-null listener") + void testGetListenerNotNull() { + UCPEventListener listener = provider.getListener(Collections.emptyMap()); + assertNotNull(listener); } - } - @Test - public void testEventFactoryRejectsNullContext() { - assertThrows(NullPointerException.class, - () -> UCPEventFactory.createEvent(EventType.POOL_CREATED, null)); - } - - @Test - public void testEventFactoryRejectsNullEventType() { - UCPEventContext ctx = createTestContext("pool1", 1, 1, 10, 2); - assertThrows(NullPointerException.class, - () -> 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(); + @Test + @DisplayName("getListener() returns a non-null listener when config is null") + void testGetListenerWithNullConfig() { + UCPEventListener listener = provider.getListener(null); + assertNotNull(listener); } - 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(ucpEvents.size() > 0, - "Should have recorded at least one UCP event"); - 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(); + @Test + @DisplayName("isDesiredEvent() returns true for all event types by default") + void testIsDesiredEventReturnsTrueForAll() { + UCPEventListener listener = provider.getListener(Collections.emptyMap()); + for (EventType type : EventType.values()) { + assertTrue(listener.isDesiredEvent(type), + "Expected isDesiredEvent to return true for: " + type); + } } - 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(ucpEvent, "Should find PoolCreated event"); - 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); + @Nested + @DisplayName("UCPEventFactory — null safety") + class EventFactoryNullSafetyTests { - if (recording.getState() == RecordingState.RUNNING) { - recording.stop(); + @Test + @DisplayName("recordEvent() throws NullPointerException when EventType is null") + void testRecordEventNullType() { + assertThrows(NullPointerException.class, () -> + UCPEventFactory.recordEvent(null, mockContext)); } - 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(ucpEvent, "Should find ConnectionBorrowed event"); - 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 testRecordedEventContainsAvgWaitTime() throws IOException { - UCPEventContext ctx = new UCPEventContext() { - @Override public String poolName() { return "wait-pool"; } - @Override public long timestamp() { - return System.currentTimeMillis(); - } - @Override public int borrowedConnectionsCount() { return 1; } - @Override public int availableConnectionsCount() { return 1; } - @Override public int totalConnections() { return 2; } - @Override public int maxPoolSize() { return 10; } - @Override public int minPoolSize() { return 2; } - @Override public long getAverageConnectionWaitTime() { return 42; } - @Override public int createdConnections() { return 2; } - @Override public int closedConnections() { return 0; } - @Override public String formattedTimestamp() { - return new SimpleDateFormat("MMMM dd, yyyy HH:mm:ss.SSS z") - .format(new Date(timestamp())); - } - }; - - UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, ctx); - - if (recording.getState() == RecordingState.RUNNING) { - recording.stop(); + @Test + @DisplayName("recordEvent() throws NullPointerException when context is null") + void testRecordEventNullContext() { + assertThrows(NullPointerException.class, () -> + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, null)); } - Path recordingFile = Files.createTempFile("ucp-test", ".jfr"); - recording.dump(recordingFile); - - RecordedEvent ucpEvent = RecordingFile.readAllEvents(recordingFile) - .stream() - .filter(e -> e.getEventType().getName() - .equals("ucp.ConnectionBorrowed")) - .findFirst() - .orElse(null); - - assertNotNull(ucpEvent, "Should find ConnectionBorrowed event"); - assertEquals(42L, ucpEvent.getLong("avgWaitTime")); - - 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)); - } + @Nested + @DisplayName("UCPEventFactory — event recording") + class EventFactoryRecordingTests { - @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(); + @ParameterizedTest(name = "recordEvent() does not throw for EventType.{0}") + @EnumSource(EventType.class) + @DisplayName("recordEvent() does not throw for any known EventType") + void testRecordEventDoesNotThrowForAllTypes(EventType type) { + assertDoesNotThrow(() -> UCPEventFactory.recordEvent(type, mockContext)); } - for (Thread thread : threads) { - thread.join(); + @Test + @DisplayName("recordEvent() does not throw when context returns zero/default values") + void testRecordEventWithZeroValues() { + UCPEventContext zeroContext = mock(UCPEventContext.class); + when(zeroContext.poolName()).thenReturn(""); + when(zeroContext.timestamp()).thenReturn(0L); + when(zeroContext.maxPoolSize()).thenReturn(0); + when(zeroContext.minPoolSize()).thenReturn(0); + when(zeroContext.borrowedConnectionsCount()).thenReturn(0); + when(zeroContext.availableConnectionsCount()).thenReturn(0); + when(zeroContext.totalConnections()).thenReturn(0); + when(zeroContext.closedConnections()).thenReturn(0); + when(zeroContext.createdConnections()).thenReturn(0); + when(zeroContext.getAverageConnectionWaitTime()).thenReturn(0L); + + assertDoesNotThrow(() -> + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, zeroContext)); } } - @Test - public void testRapidFireEvents() { - UCPEventContext ctx = createTestContext("rapid-pool", 1, 1, 10, 2); + @Nested + @DisplayName("UCPEventListener — onUCPEvent robustness") + class ListenerOnEventTests { - for (int i = 0; i < 1000; i++) { - listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + @ParameterizedTest(name = "onUCPEvent() does not throw for EventType.{0}") + @EnumSource(EventType.class) + @DisplayName("onUCPEvent() does not throw for any known EventType") + void testOnUCPEventDoesNotThrowForAllTypes(EventType type) { + JFRUCPEventListenerProvider provider = new JFRUCPEventListenerProvider(); + UCPEventListener listener = provider.getListener(Collections.emptyMap()); + assertDoesNotThrow(() -> listener.onUCPEvent(type, mockContext)); } - } - - @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_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 SimpleDateFormat("MMMM dd, yyyy HH:mm:ss.SSS z") - .format(new Date(timestamp())); - } - }; + @Test + @DisplayName("onUCPEvent() reads all context fields during event creation") + void testOnUCPEventReadsAllContextFields() { + JFRUCPEventListenerProvider provider = new JFRUCPEventListenerProvider(); + UCPEventListener listener = provider.getListener(Collections.emptyMap()); + + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); + + verify(mockContext).poolName(); + verify(mockContext).timestamp(); + verify(mockContext).maxPoolSize(); + verify(mockContext).minPoolSize(); + verify(mockContext).borrowedConnectionsCount(); + verify(mockContext).availableConnectionsCount(); + verify(mockContext).totalConnections(); + verify(mockContext).closedConnections(); + verify(mockContext).createdConnections(); + verify(mockContext).getAverageConnectionWaitTime(); + } } } \ No newline at end of file From f801dccb2c05e0537897ddb639f8d72d0327b1bd Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Thu, 12 Mar 2026 07:33:11 +0000 Subject: [PATCH 50/82] Added README.md file --- ojdbc-provider-ucp-observability/README.md | 337 +++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 ojdbc-provider-ucp-observability/README.md diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md new file mode 100644 index 00000000..6d7fcc6c --- /dev/null +++ b/ojdbc-provider-ucp-observability/README.md @@ -0,0 +1,337 @@ +# Oracle JDBC UCP Observability Providers + +Implementations of the `UCPEventListenerProvider` SPI that expose Oracle Universal Connection +Pool (UCP) telemetry through two industry-standard observability backends: + +| Provider | Listener name | Backend | +|---|---|---| +| `JFRUCPEventListenerProvider` | `jfr-ucp-listener` | Java Flight Recorder (JFR) | +| `OtelUCPEventListenerProvider` | `otel-ucp-listener` | OpenTelemetry (metrics) | + +Both providers are registered automatically via `java.util.ServiceLoader` — no code changes +are required in the application. + +--- + +## Contents + +- [Installation](#installation) +- [Activation](#activation) +- [JFR Provider](#jfr-provider) + - [Recorded event types](#recorded-event-types) + - [Enabling a JFR recording](#enabling-a-jfr-recording) + - [Analysing events in JDK Mission Control](#analysing-events-in-jdk-mission-control) +- [OpenTelemetry Provider](#opentelemetry-provider) + - [SDK initialisation requirement](#sdk-initialisation-requirement) + - [Exported metrics](#exported-metrics) + - [Prometheus / Grafana quick-start](#prometheus--grafana-quick-start) +- [Supported UCP event types](#supported-ucp-event-types) +- [Requirements](#requirements) + +--- + +## Installation + +Add the module to your Maven project: + +```xml + + com.oracle.database.jdbc + ojdbc-provider-ucp-observability + 1.0.6 + +``` +--- + +## Activation + +Each provider is activated by registering its listener name on the UCP data source **before** +the pool is started. + +### Programmatic activation + +```java +PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); +pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); +pds.setURL("jdbc:oracle:thin:@//host:1521/service"); + +// Activate your provider +pds.setUCPEventListenerProvider("jfr-ucp-listener"); +// pds.setUCPEventListenerProvider("otel-ucp-listener"); +``` + +--- + +## JFR Provider + +`JFRUCPEventListenerProvider` converts every UCP pool and connection event into a committed +JFR event. Events are emitted with zero overhead when no JFR recording is active, making the provider safe to ship in production at all times. + +### Recorded event types + +All events live under the `ucp` JFR category. + +#### Pool lifecycle + +| JFR event class | Fired when | +|---|---| +| `PoolCreatedEvent` | A new UCP pool instance is created | +| `PoolStartingEvent` | Pool initialisation begins | +| `PoolStartedEvent` | Pool is fully started and ready | +| `PoolStoppedEvent` | Pool has been stopped | +| `PoolDestroyedEvent` | Pool instance is destroyed | + +#### Connection lifecycle + +| JFR event class | Fired when | +|---|---| +| `ConnectionCreatedEvent` | A physical connection is opened to the database | +| `ConnectionBorrowedEvent` | A connection is checked out by the application | +| `ConnectionReturnedEvent` | A connection is returned to the pool | +| `ConnectionClosedEvent` | A physical connection is closed | + +#### Maintenance operations + +| JFR event class | Fired when | +|---|---| +| `PoolRefreshedEvent` | The pool replaces all connections with fresh ones | +| `PoolRecycledEvent` | The pool recycles stale connections | +| `PoolPurgedEvent` | All connections are forcibly removed from the pool | + +### Fields recorded on every event + +| Field | Type | Description | +|---|---|---| +| `ucpTimestamp` | `long` | Epoch millisecond timestamp from the UCP event context | +| `poolName` | `String` | Name of the UCP pool that fired the event | +| `maxPoolSize` | `int` | Maximum pool capacity at event time | +| `minPoolSize` | `int` | Minimum pool capacity at event time | +| `borrowedConnectionsCount` | `int` | Checked-out connections at event time | +| `availableConnectionsCount` | `int` | Idle connections at event time | +| `totalConnections` | `int` | Total physical connections at event time | +| `createdConnections` | `long` | Cumulative connections created since pool start | +| `closedConnections` | `long` | Cumulative connections closed since pool start | +| `averageConnectionWaitTime` | `long` | Average borrow wait time in milliseconds | + +### Enabling a JFR recording + +**At JVM start** — capture everything from pool creation: + +```bash +java \ + -XX:StartFlightRecording=filename=ucp.jfr,settings=profile \ + -jar myapp.jar +``` + +**At runtime via `jcmd`** — attach to a running process: + +```bash +# Start recording +jcmd JFR.start name=ucp settings=profile + +# Dump to file +jcmd JFR.dump name=ucp filename=ucp.jfr + +# Stop +jcmd JFR.stop name=ucp +``` + +To limit recording to UCP events only and reduce file size, create a custom settings file +and enable only the `ucp.*` event namespace: + +```bash +jcmd JFR.start name=ucp settings=ucp-custom.jfc +``` + +Where `ucp-custom.jfc` enables `true`. + +### Analysing events in JDK Mission Control + +Open the `.jfr` file in [JDK Mission Control (JMC)](https://www.oracle.com/java/technologies/jdk-mission-control.html). +UCP events appear under **Event Browser → ucp** and can be correlated with GC pauses, +thread activity, and I/O latency on the same timeline. + +--- + +## OpenTelemetry Provider + +`OtelUCPEventListenerProvider` publishes UCP pool and connection metrics through the +[OpenTelemetry API](https://opentelemetry.io/docs/languages/java/). The provider is +**event-driven**: every time a UCP event fires, the snapshot data it carries is recorded +directly into OTel instruments. + +The provider depends only on `opentelemetry-api` — it does **not** pull in the SDK or any +exporter. The application is responsible for initialising an OpenTelemetry SDK and +registering it with `GlobalOpenTelemetry` **before** the pool is started. + +### SDK initialisation requirement + +The provider obtains its `Meter` on first listener instantiation using +`GlobalOpenTelemetry.getMeter("oracle.ucp")`. The SDK must therefore be registered before +the pool is started. A minimal setup using the Prometheus exporter: + +```java +// 1. Register the SDK BEFORE pool creation +PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder() + .setPort(9464) + .build(); + +SdkMeterProvider meterProvider = SdkMeterProvider.builder() + .registerMetricReader(prometheusServer) + .build(); + +OpenTelemetrySdk.builder() + .setMeterProvider(meterProvider) + .buildAndRegisterGlobal(); + +// 2. Then create and start the pool +PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); +pds.setUCPEventListenerProvider("otel-ucp-listener"); +``` + +### Exported metrics + +All metrics use the `db.client.connection` prefix following +[OpenTelemetry semantic conventions for database clients](https://opentelemetry.io/docs/specs/semconv/database/database-metrics/). + +#### Histograms — snapshot fields recorded on every event + +Each UCP event carries a snapshot of pool state at the moment it fired. These eight fields +are recorded as histogram observations on **every** event regardless of type, giving a +time-series view of pool state correlated with event activity. + +| Metric name | Unit | Description | +|---|---|---| +| `db.client.connection.borrowed_count` | `{connection}` | Snapshot of checked-out connections at event time | +| `db.client.connection.available_count` | `{connection}` | Snapshot of idle connections at event time | +| `db.client.connection.total_count` | `{connection}` | Snapshot of total connections (borrowed + available) at event time | +| `db.client.connection.created_count` | `{connection}` | Cumulative connections created since pool start at event time | +| `db.client.connection.closed_count` | `{connection}` | Cumulative connections closed since pool start at event time | +| `db.client.connection.wait_time` | `ms` | Average connection wait time in milliseconds at event time | +| `db.client.connection.max_pool_size` | `{connection}` | Configured maximum pool size at event time | +| `db.client.connection.min_pool_size` | `{connection}` | Configured minimum pool size at event time | + +#### Event counters + +One counter is incremented by 1 each time the corresponding UCP event fires. + +| Metric name | Incremented on | +|---|---| +| `db.client.connection.pool.created` | `POOL_CREATED` | +| `db.client.connection.pool.starting` | `POOL_STARTING` | +| `db.client.connection.pool.started` | `POOL_STARTED` | +| `db.client.connection.pool.stopped` | `POOL_STOPPED` | +| `db.client.connection.pool.destroyed` | `POOL_DESTROYED` | +| `db.client.connection.pool.refreshed` | `POOL_REFRESHED` | +| `db.client.connection.pool.recycled` | `POOL_RECYCLED` | +| `db.client.connection.pool.purged` | `POOL_PURGED` | +| `db.client.connection.created` | `CONNECTION_CREATED` | +| `db.client.connection.borrowed` | `CONNECTION_BORROWED` | +| `db.client.connection.returned` | `CONNECTION_RETURNED` | +| `db.client.connection.closed` | `CONNECTION_CLOSED` | + +#### Common attributes + +All metrics carry the following attribute: + +| Attribute key | Example value | Description | +|---|---|---| +| `db.client.connection.pool.name` | `"MyPool"` | Name of the UCP pool that emitted the event | + +### Prometheus / Grafana quick-start + +**1. Add the Prometheus exporter (test / application scope)** + +```xml + + io.opentelemetry + opentelemetry-sdk + 1.44.1 + + + io.opentelemetry + opentelemetry-exporter-prometheus + 1.44.1-alpha + +``` + +**2. Configure Prometheus to scrape the application** + +```yaml +# prometheus.yml +scrape_configs: + - job_name: ucp + static_configs: + - targets: ["localhost:9464"] +``` + +**3. Useful Grafana PromQL queries** + +```promql +# Average borrowed connections over the last minute +rate(db_client_connection_borrowed_count_sum[1m]) + / rate(db_client_connection_borrowed_count_count[1m]) + +# Average available connections over the last minute +rate(db_client_connection_available_count_sum[1m]) + / rate(db_client_connection_available_count_count[1m]) + +# Average connection wait time over the last minute +rate(db_client_connection_wait_time_milliseconds_sum[1m]) + / rate(db_client_connection_wait_time_milliseconds_count[1m]) + +# 95th-percentile connection wait time +histogram_quantile(0.95, + rate(db_client_connection_wait_time_milliseconds_bucket[1m])) + +# Total borrow events (cumulative counter) +db_client_connection_borrowed_total + +# Borrow rate per minute +rate(db_client_connection_borrowed_total[1m]) +``` + +--- + +## Supported UCP event types + +Both providers handle all twelve event types defined by `UCPEventListener.EventType`: + +| Event type | Category | +|---|---| +| `POOL_CREATED` | Pool lifecycle | +| `POOL_STARTING` | Pool lifecycle | +| `POOL_STARTED` | Pool lifecycle | +| `POOL_STOPPED` | Pool lifecycle | +| `POOL_DESTROYED` | Pool lifecycle | +| `CONNECTION_CREATED` | Connection lifecycle | +| `CONNECTION_BORROWED` | Connection lifecycle | +| `CONNECTION_RETURNED` | Connection lifecycle | +| `CONNECTION_CLOSED` | Connection lifecycle | +| `POOL_REFRESHED` | Maintenance | +| `POOL_RECYCLED` | Maintenance | +| `POOL_PURGED` | Maintenance | + +--- + +## Requirements + +| Requirement | Minimum version | +|---|---| +| Oracle JDBC driver | 23.26.0.0.0 | +| Oracle UCP | 23.26.1.0.0 | +| Java (compile) | 11 | +| Java (runtime) | 11+ | +| OpenTelemetry API *(OTel provider only)* | 1.44.1 | + +The JFR provider has no runtime dependencies beyond the Oracle UCP jar and a JDK that +supports JFR (JDK 11+). The OpenTelemetry provider requires `opentelemetry-api` on the +classpath; the SDK and exporter are the application's responsibility. +--- + +## See also + +- [Oracle UCP Developer's Guide](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjucp/) +- [OpenTelemetry Java documentation](https://opentelemetry.io/docs/languages/java/) +- [JDK Mission Control download](https://www.oracle.com/java/technologies/jdk-mission-control.html) +- [Oracle JDBC Driver Extensions — root README](../README.md) \ No newline at end of file From 3a56781e6b50222e24112c4ce554051d887a6be0 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Thu, 12 Mar 2026 07:39:23 +0000 Subject: [PATCH 51/82] Fixed README.md file --- ojdbc-provider-ucp-observability/README.md | 64 +++++++++++++--------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md index 6d7fcc6c..61d26fc9 100644 --- a/ojdbc-provider-ucp-observability/README.md +++ b/ojdbc-provider-ucp-observability/README.md @@ -51,13 +51,17 @@ the pool is started. ### Programmatic activation ```java -PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); -pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); -pds.setURL("jdbc:oracle:thin:@//host:1521/service"); - -// Activate your provider -pds.setUCPEventListenerProvider("jfr-ucp-listener"); -// pds.setUCPEventListenerProvider("otel-ucp-listener"); +public class MyApp { + public static void main(String[] args) throws Exception { + PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); + pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); + pds.setURL("jdbc:oracle:thin:@//host:1521/service"); + + // Activate your provider + pds.setUCPEventListenerProvider("jfr-ucp-listener"); + // pds.setUCPEventListenerProvider("otel-ucp-listener"); + } +} ``` --- @@ -171,22 +175,26 @@ The provider obtains its `Meter` on first listener instantiation using the pool is started. A minimal setup using the Prometheus exporter: ```java -// 1. Register the SDK BEFORE pool creation -PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder() - .setPort(9464) - .build(); - -SdkMeterProvider meterProvider = SdkMeterProvider.builder() - .registerMetricReader(prometheusServer) - .build(); - -OpenTelemetrySdk.builder() - .setMeterProvider(meterProvider) - .buildAndRegisterGlobal(); - -// 2. Then create and start the pool -PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); -pds.setUCPEventListenerProvider("otel-ucp-listener"); +public class MyApp { + public static void main(String[] args) throws Exception { + // 1. Register the SDK BEFORE pool creation + PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder() + .setPort(9464) + .build(); + + SdkMeterProvider meterProvider = SdkMeterProvider.builder() + .registerMetricReader(prometheusServer) + .build(); + + OpenTelemetrySdk.builder() + .setMeterProvider(meterProvider) + .buildAndRegisterGlobal(); + + // 2. Then create and start the pool + PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); + pds.setUCPEventListenerProvider("otel-ucp-listener"); + } +} ``` ### Exported metrics @@ -243,16 +251,18 @@ All metrics carry the following attribute: **1. Add the Prometheus exporter (test / application scope)** ```xml - + + io.opentelemetry opentelemetry-sdk 1.44.1 - - + + io.opentelemetry opentelemetry-exporter-prometheus 1.44.1-alpha - + +
    ``` **2. Configure Prometheus to scrape the application** From 3df9c4e136516ff5c40ccdcf377d3c57ba73c03e Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Thu, 12 Mar 2026 07:40:08 +0000 Subject: [PATCH 52/82] Fixed Unit Tests --- .../provider/observability/OtelUCPTest.java | 240 ------------------ .../provider/observability/SampleTestUCP.java | 90 ------- 2 files changed, 330 deletions(-) delete mode 100644 ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java delete mode 100644 ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/SampleTestUCP.java diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java deleted file mode 100644 index f2e9b64d..00000000 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPTest.java +++ /dev/null @@ -1,240 +0,0 @@ -package oracle.ucp.provider.observability; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import oracle.ucp.events.core.UCPEventContext; -import oracle.ucp.events.core.UCPEventListener; -import oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; - -import static oracle.ucp.events.core.UCPEventListener.EventType; -import static org.junit.jupiter.api.Assertions.*; - -public class OtelUCPTest { - - private OtelUCPEventListenerProvider provider; - private UCPEventListener listener; - - @BeforeEach - public void setup() { - GlobalOpenTelemetry.resetForTest(); - OpenTelemetry mockOTel = Mockito.mock( - OpenTelemetry.class, Mockito.RETURNS_DEEP_STUBS); - GlobalOpenTelemetry.set(mockOTel); - - provider = new OtelUCPEventListenerProvider(); - listener = provider.getListener(null); - } - - @AfterEach - public void cleanup() { - GlobalOpenTelemetry.resetForTest(); - } - - @Test - public void testProviderName() { - assertEquals("otel-ucp-listener", provider.getName()); - } - - @Test - public void testProviderReturnsListener() { - assertNotNull(listener, "Provider should return a listener"); - } - - @Test - public void testProviderReturnsSameListenerInstance() { - UCPEventListener listener1 = provider.getListener(null); - UCPEventListener listener2 = provider.getListener(new HashMap<>()); - assertSame(listener1, listener2, - "Provider should return same listener instance"); - } - - @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_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 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 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_STOPPED, ctx); - listener.onUCPEvent(EventType.POOL_DESTROYED, ctx); - } - - @Test - public void testEmptyPoolNameAccepted() { - UCPEventContext ctx = createTestContext("", 1, 1, 10, 2); - listener.onUCPEvent(EventType.CONNECTION_BORROWED, 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 SimpleDateFormat("MMMM dd, yyyy HH:mm:ss.SSS z") - .format(new Date(timestamp())); - } - }; - } -} \ No newline at end of file diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/SampleTestUCP.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/SampleTestUCP.java deleted file mode 100644 index 7d8eeadd..00000000 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/SampleTestUCP.java +++ /dev/null @@ -1,90 +0,0 @@ -package oracle.ucp.provider.observability; - -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; -public class SampleTestUCP { - -public static void main(String args[]) throws Exception { - - System.setProperty("oracle.jdbc.Trace", "true"); - System.setProperty("java.util.logging.config.file", "./src_test/test/standalone/ucplogging.properties"); - System.setProperty("oracle.ucp.wls.jta", "false"); - // Configure the connection pool - PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); - - pds.setInitialPoolSize(2); - pds.setMinPoolSize(0); - pds.setMaxPoolSize(50); - pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); - pds.setURL("jdbc:oracle:thin:@adb_medium?TNS_ADMIN=/Users/abdessamadelaaissaoui/Desktop/Wallet_adb/"); - pds.setUser("ADMIN"); - pds.setPassword("Madara@@1234"); - pds.setConnectionPoolName("Abdessamad's Pool"); - - pds.setUCPEventListenerProvider("jfr-ucp-listener"); - //System.setProperty("UCPEventListenerProvider","jfr-ucp-listener"); - - UniversalConnectionPoolManager mgr = UniversalConnectionPoolManagerImpl. - getUniversalConnectionPoolManager(); - - - mgr.createConnectionPool((UniversalConnectionPoolAdapter) pds); - - mgr.startConnectionPool("Abdessamad's Pool"); - - UniversalPooledConnection conn1 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - UniversalPooledConnection conn2 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - UniversalPooledConnection conn3 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - UniversalPooledConnection conn4 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - UniversalPooledConnection conn5 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - UniversalPooledConnection conn6 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - UniversalPooledConnection conn7 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - UniversalPooledConnection conn8 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - UniversalPooledConnection conn9 = mgr.getConnectionPool("Abdessamad's Pool").borrowConnection(mgr.getConnectionPool("Abdessamad's Pool").getConnectionRetrievalInfo()); - - - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn1); - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn2); - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn3); - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn4); - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn5); - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn6); - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn7); - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn8); - mgr.getConnectionPool("Abdessamad's Pool").closeConnection(conn9); - - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn1); - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn2); - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn3); - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn4); - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn5); - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn6); - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn7); - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn8); - mgr.getConnectionPool("Abdessamad's Pool").returnConnection(conn9); - - - pds.setURL("jdbc:oracle:thin:@adb_medium?TNS_ADMIN=/Users/abdessamadelaaissaoui/Desktop/Wallet_adb/"); - - - mgr.purgeConnectionPool("Abdessamad's Pool"); - mgr.recycleConnectionPool("Abdessamad's Pool"); - mgr.refreshConnectionPool("Abdessamad's Pool"); - - - mgr.stopConnectionPool("Abdessamad's Pool"); - - mgr.destroyConnectionPool("Abdessamad's Pool"); -}} \ No newline at end of file From 58a98723e4c4504c225370c34670023f2f3e671c Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Sun, 29 Mar 2026 06:21:58 +0100 Subject: [PATCH 53/82] Removed unnecessary dependencies --- ojdbc-provider-ucp-observability/pom.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ojdbc-provider-ucp-observability/pom.xml b/ojdbc-provider-ucp-observability/pom.xml index 59bfe8d7..12a77297 100644 --- a/ojdbc-provider-ucp-observability/pom.xml +++ b/ojdbc-provider-ucp-observability/pom.xml @@ -26,14 +26,6 @@ ucp 23.26.1.0.0 - - com.oracle.database.jdbc - ojdbc8 - - - com.oracle.database.security - oraclepki - io.opentelemetry opentelemetry-api From 2aa45c9fa8fdb34f8e678c89f5c2753f7463b6e3 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Sun, 29 Mar 2026 06:22:26 +0100 Subject: [PATCH 54/82] Edited The readme.md file to reflect the new changes. --- ojdbc-provider-ucp-observability/README.md | 136 +++++++++++++-------- 1 file changed, 82 insertions(+), 54 deletions(-) diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md index 61d26fc9..7425b843 100644 --- a/ojdbc-provider-ucp-observability/README.md +++ b/ojdbc-provider-ucp-observability/README.md @@ -41,6 +41,7 @@ Add the module to your Maven project: 1.0.6 ``` + --- ## Activation @@ -69,7 +70,8 @@ public class MyApp { ## JFR Provider `JFRUCPEventListenerProvider` converts every UCP pool and connection event into a committed -JFR event. Events are emitted with zero overhead when no JFR recording is active, making the provider safe to ship in production at all times. +JFR event. Events are emitted with zero overhead when no JFR recording is active, making the +provider safe to ship in production at all times. ### Recorded event types @@ -162,7 +164,8 @@ thread activity, and I/O latency on the same timeline. `OtelUCPEventListenerProvider` publishes UCP pool and connection metrics through the [OpenTelemetry API](https://opentelemetry.io/docs/languages/java/). The provider is **event-driven**: every time a UCP event fires, the snapshot data it carries is recorded -directly into OTel instruments. +directly into OTel instruments. There are no background threads, no polling, and no cached +state — each event is self-contained. The provider depends only on `opentelemetry-api` — it does **not** pull in the SDK or any exporter. The application is responsible for initialising an OpenTelemetry SDK and @@ -179,16 +182,16 @@ public class MyApp { public static void main(String[] args) throws Exception { // 1. Register the SDK BEFORE pool creation PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder() - .setPort(9464) - .build(); + .setPort(9464) + .build(); SdkMeterProvider meterProvider = SdkMeterProvider.builder() - .registerMetricReader(prometheusServer) - .build(); + .registerMetricReader(prometheusServer) + .build(); OpenTelemetrySdk.builder() - .setMeterProvider(meterProvider) - .buildAndRegisterGlobal(); + .setMeterProvider(meterProvider) + .buildAndRegisterGlobal(); // 2. Then create and start the pool PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); @@ -197,46 +200,40 @@ public class MyApp { } ``` +For applications that already configure an OpenTelemetry SDK (e.g. via the Java agent or +Spring Boot auto-configuration) no additional setup is required. + ### Exported metrics -All metrics use the `db.client.connection` prefix following +All spec-aligned metrics use the `db.client.connection` prefix following [OpenTelemetry semantic conventions for database clients](https://opentelemetry.io/docs/specs/semconv/database/database-metrics/). -#### Histograms — snapshot fields recorded on every event +#### Spec-aligned metrics -Each UCP event carries a snapshot of pool state at the moment it fired. These eight fields -are recorded as histogram observations on **every** event regardless of type, giving a -time-series view of pool state correlated with event activity. +| Metric name | Instrument | Unit | Description | +|---|---|---|---| +| `db.client.connection.usage` | LongGauge | `{connection}` | Current connections in each state (attribute: `db.client.connection.state` = `used` \| `idle`). Recorded on every event. | +| `db.client.connection.max` | LongGauge | `{connection}` | Configured maximum pool size. Recorded on pool lifecycle events only. | +| `db.client.connection.idle.min` | LongGauge | `{connection}` | Configured minimum pool size (approximation — see Limitations). Recorded on pool lifecycle events only. | +| `db.client.connection.wait_time` | DoubleHistogram | `s` | Borrow wait time approximation (see Limitations). Recorded on `CONNECTION_BORROWED` only, when > 0. | -| Metric name | Unit | Description | -|---|---|---| -| `db.client.connection.borrowed_count` | `{connection}` | Snapshot of checked-out connections at event time | -| `db.client.connection.available_count` | `{connection}` | Snapshot of idle connections at event time | -| `db.client.connection.total_count` | `{connection}` | Snapshot of total connections (borrowed + available) at event time | -| `db.client.connection.created_count` | `{connection}` | Cumulative connections created since pool start at event time | -| `db.client.connection.closed_count` | `{connection}` | Cumulative connections closed since pool start at event time | -| `db.client.connection.wait_time` | `ms` | Average connection wait time in milliseconds at event time | -| `db.client.connection.max_pool_size` | `{connection}` | Configured maximum pool size at event time | -| `db.client.connection.min_pool_size` | `{connection}` | Configured minimum pool size at event time | +> **Note on spec deviations:** +> The spec defines `db.client.connection.count` as an `UpDownCounter`. We use the name +> `db.client.connection.usage` (to avoid the reserved `_count` Prometheus suffix) and +> `LongGauge` (because UCP provides absolute snapshots, not incremental deltas). +> The spec defines `db.client.connection.max` and `db.client.connection.idle.min` as +> `UpDownCounter`. We use `LongGauge` for the same reason — these are configuration +> constants, not incrementally updated values. -#### Event counters +#### UCP-specific metrics -One counter is incremented by 1 each time the corresponding UCP event fires. +These metrics have no equivalent in the OTel spec but expose data available from UCP's +event context that is useful for observability. -| Metric name | Incremented on | -|---|---| -| `db.client.connection.pool.created` | `POOL_CREATED` | -| `db.client.connection.pool.starting` | `POOL_STARTING` | -| `db.client.connection.pool.started` | `POOL_STARTED` | -| `db.client.connection.pool.stopped` | `POOL_STOPPED` | -| `db.client.connection.pool.destroyed` | `POOL_DESTROYED` | -| `db.client.connection.pool.refreshed` | `POOL_REFRESHED` | -| `db.client.connection.pool.recycled` | `POOL_RECYCLED` | -| `db.client.connection.pool.purged` | `POOL_PURGED` | -| `db.client.connection.created` | `CONNECTION_CREATED` | -| `db.client.connection.borrowed` | `CONNECTION_BORROWED` | -| `db.client.connection.returned` | `CONNECTION_RETURNED` | -| `db.client.connection.closed` | `CONNECTION_CLOSED` | +| Metric name | Instrument | Unit | Description | +|---|---|---|---| +| `db.client.connection.established` | LongGauge | `{connection}` | Cumulative physical connections opened since pool start. | +| `db.client.connection.closed` | LongGauge | `{connection}` | Cumulative physical connections closed since pool start. | #### Common attributes @@ -246,6 +243,34 @@ All metrics carry the following attribute: |---|---|---| | `db.client.connection.pool.name` | `"MyPool"` | Name of the UCP pool that emitted the event | +The `db.client.connection.usage` metric additionally carries: + +| Attribute key | Values | Description | +|---|---|---| +| `db.client.connection.state` | `"used"` / `"idle"` | Whether connections are checked out or available | + +#### Limitations + +- **`db.client.connection.wait_time`**: The spec intends per-borrow wait durations. + `UCPEventContext.getAverageConnectionWaitTime()` returns a cumulative pool-wide average + since pool start, not a per-borrow value. This is the closest approximation UCP's event + API allows. Recorded only when the average is greater than zero to avoid histogram + pollution at pool startup. + +- **`db.client.connection.idle.min`**: The spec intends the minimum number of idle + connections. UCP's `minPoolSize()` returns the minimum total pool size (borrowed + idle), + not a dedicated idle floor. These differ when connections are actively borrowed. + +- **`db.client.connection.established` / `db.client.connection.closed`**: These are + monotonically increasing lifetime totals. `LongGauge` is used instead of `LongCounter` + because UCP exposes them as absolute values — a counter with delta=0 would produce no + data point when UCP reuses cached connections and never opens new physical sockets. + +> **Note on Prometheus metadata:** `db_client_connection_usage` may appear as `gauge` +> with no issues. Some metrics may display `unknown` type in the Prometheus +> `/api/v1/metadata` UI due to a known limitation of the OTel Java Prometheus exporter. +> This does not affect queries or Grafana dashboards. + ### Prometheus / Grafana quick-start **1. Add the Prometheus exporter (test / application scope)** @@ -278,27 +303,29 @@ scrape_configs: **3. Useful Grafana PromQL queries** ```promql -# Average borrowed connections over the last minute -rate(db_client_connection_borrowed_count_sum[1m]) - / rate(db_client_connection_borrowed_count_count[1m]) +# Current used connections +db_client_connection_usage{db_client_connection_state="used"} + +# Current idle connections +db_client_connection_usage{db_client_connection_state="idle"} -# Average available connections over the last minute -rate(db_client_connection_available_count_sum[1m]) - / rate(db_client_connection_available_count_count[1m]) +# Total connections (used + idle) +sum by (db_client_connection_pool_name) (db_client_connection_usage) -# Average connection wait time over the last minute -rate(db_client_connection_wait_time_milliseconds_sum[1m]) - / rate(db_client_connection_wait_time_milliseconds_count[1m]) +# Average borrow wait time over the last minute +rate(db_client_connection_wait_time_seconds_sum[1m]) + / rate(db_client_connection_wait_time_seconds_count[1m]) -# 95th-percentile connection wait time +# 95th-percentile borrow wait time histogram_quantile(0.95, - rate(db_client_connection_wait_time_milliseconds_bucket[1m])) + rate(db_client_connection_wait_time_seconds_bucket[1m])) -# Total borrow events (cumulative counter) -db_client_connection_borrowed_total +# Physical connection churn rate (new sockets opened per minute) +rate(db_client_connection_established[1m]) -# Borrow rate per minute -rate(db_client_connection_borrowed_total[1m]) +# Pool capacity utilisation +db_client_connection_usage{db_client_connection_state="used"} + / db_client_connection_max ``` --- @@ -337,6 +364,7 @@ Both providers handle all twelve event types defined by `UCPEventListener.EventT The JFR provider has no runtime dependencies beyond the Oracle UCP jar and a JDK that supports JFR (JDK 11+). The OpenTelemetry provider requires `opentelemetry-api` on the classpath; the SDK and exporter are the application's responsibility. + --- ## See also From 629e4a25d8c88d7d2258470ea3d8254071d490da Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Sun, 29 Mar 2026 06:23:35 +0100 Subject: [PATCH 55/82] Added the Oracle UPL license header --- .../jfr/core/JFRUCPEventListenerProvider.java | 38 +++++++++++++++++++ .../observability/jfr/core/UCPBaseEvent.java | 38 +++++++++++++++++++ .../jfr/core/UCPEventFactory.java | 38 +++++++++++++++++++ .../connection/ConnectionBorrowedEvent.java | 38 +++++++++++++++++++ .../connection/ConnectionClosedEvent.java | 38 +++++++++++++++++++ .../connection/ConnectionCreatedEvent.java | 38 +++++++++++++++++++ .../connection/ConnectionReturnedEvent.java | 38 +++++++++++++++++++ .../events/lifecycle/PoolCreatedEvent.java | 38 +++++++++++++++++++ .../events/lifecycle/PoolDestroyedEvent.java | 38 +++++++++++++++++++ .../events/lifecycle/PoolStartedEvent.java | 38 +++++++++++++++++++ .../events/lifecycle/PoolStartingEvent.java | 38 +++++++++++++++++++ .../events/lifecycle/PoolStoppedEvent.java | 38 +++++++++++++++++++ .../events/maintenance/PoolPurgedEvent.java | 38 +++++++++++++++++++ .../events/maintenance/PoolRecycledEvent.java | 38 +++++++++++++++++++ .../maintenance/PoolRefreshedEvent.java | 38 +++++++++++++++++++ .../JFRUCPEventListenerProviderTest.java | 38 +++++++++++++++++++ 16 files changed, 608 insertions(+) diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java index 32276fec..03edbc70 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.core; import oracle.ucp.events.core.UCPEventContext; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java index fd5f5108..85762d1c 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.core; import jdk.jfr.*; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java index 6ba83de9..f2a19465 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.core; import jdk.jfr.Event; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java index b2fed277..8e344987 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.connection; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java index 27498e65..7165a06d 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.connection; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java index 10d60c10..123e7ef6 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.connection; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java index ace9574a..0b1ee12c 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.connection; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java index f787198f..e6d8835c 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.lifecycle; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java index 95b23d72..bd2383bc 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.lifecycle; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java index c76a8b8e..61eba6a6 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.lifecycle; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java index 34c97390..b63df0be 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.lifecycle; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java index aebd3a26..82dc1883 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.lifecycle; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java index 3870a4b9..5a3f1184 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.maintenance; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java index 174ccd81..024b8b78 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.maintenance; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java index b5d1e854..4bd78a96 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.jfr.events.maintenance; import jdk.jfr.Category; diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java index 33ad8d56..75853745 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability; import oracle.ucp.events.core.UCPEventContext; From 96ef1ffac0dac29ab37213783a28c0dd288aa228 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Sun, 29 Mar 2026 06:25:41 +0100 Subject: [PATCH 56/82] =?UTF-8?q?Edited=20the=20OTEL=20provider=20to=20add?= =?UTF-8?q?ress=20Mohsin's=20comments.=20I=20added=20detailed=20comments?= =?UTF-8?q?=20to=20explain=20my=20approach=E2=80=94we=20can=20remove=20the?= =?UTF-8?q?m=20later.=20Please=20take=20a=20look.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otel/OtelUCPEventListenerProvider.java | 474 +++++++++++++----- 1 file changed, 346 insertions(+), 128 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java index 715f8ad2..fae54d2a 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability.otel; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -8,18 +46,75 @@ import oracle.ucp.events.core.UCPEventListener; import oracle.ucp.events.core.UCPEventListenerProvider; +import java.io.NotSerializableException; +import java.io.ObjectOutputStream; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * OpenTelemetry provider for UCP connection pool metrics. * - *

    Each UCP event carries a snapshot of pool data at the moment it fired. - * This provider simply records that data directly into OTel instruments + *

    Metrics follow the OpenTelemetry semantic conventions for database client + * connection pools: https://opentelemetry.io/docs/specs/semconv/database/database-metrics/ + * Where the spec defines an instrument for data that UCP exposes, the spec name + * and instrument type are used exactly. *

    * + *

    Spec-aligned metrics

    + *
      + *
    • {@code db.client.connection.usage} LongGauge — current used/idle connections + * (state attribute: {@code used} | {@code idle}). + * Note: the spec defines this metric as {@code db.client.connection.count} with + * instrument type UpDownCounter. We deviate on both: we use {@code usage} as the + * name (to avoid the reserved Prometheus {@code _count} suffix which causes the + * OTel exporter to report an unknown type), and LongGauge as the instrument + * (because UCP provides absolute snapshots, not incremental deltas).
    • + *
    • {@code db.client.connection.max} LongGauge — configured maximum pool size. + * Recorded only on pool lifecycle events (POOL_CREATED, POOL_STARTING, + * POOL_STARTED, POOL_STOPPED, POOL_DESTROYED) since this is a configuration + * constant that rarely changes, avoiding unnecessary overhead on high-frequency + * connection events.
    • + *
    • {@code db.client.connection.idle.min} LongGauge — configured minimum pool size. + * Note: UCP's {@link UCPEventContext#minPoolSize()} returns the minimum total + * pool size, not a dedicated minimum-idle value. This is the closest approximation + * UCP's event API allows. Recorded only on pool lifecycle events for the same + * reason as {@code db.client.connection.max}.
    • + *
    • {@code db.client.connection.wait_time} Histogram (s) — approximated from UCP's + * cumulative average wait time; recorded on CONNECTION_BORROWED only, and only + * when the average is greater than zero to avoid polluting the histogram with + * zero-value observations at pool startup.
    • + *
    + * + *

    UCP-specific metrics (no spec equivalent)

    + *
      + *
    • {@code db.client.connection.established} LongGauge — cumulative physical connections + * ever opened since pool start
    • + *
    • {@code db.client.connection.closed} LongGauge — cumulative physical connections + * ever closed since pool start
    • + *
    + * + *

    Limitations

    + *
      + *
    • {@code db.client.connection.wait_time}: The spec intends per-borrow wait durations. + * {@link UCPEventContext#getAverageConnectionWaitTime()} returns a cumulative pool-wide + * average since pool start, not a per-borrow value. This is the closest approximation + * UCP's event API allows.
    • + *
    • {@code db.client.connection.idle.min}: The spec intends the minimum number of idle + * connections. UCP's {@link UCPEventContext#minPoolSize()} returns the minimum total + * pool size (borrowed + idle), not a dedicated idle floor. These differ when minimum + * pool size is non-zero and connections are actively borrowed.
    • + *
    • Serialization: {@code OtelUCPEventListener} implements {@link java.io.Serializable} + * as required by the {@link UCPEventListener} contract, but its OTel instrument fields + * are not serializable. Attempting to serialize this instance will throw + * {@link NotSerializableException}.
    • + *
    • Pool state memory: {@code OtelUCPEventListener} retains a {@code PoolState} entry + * per pool name and removes it on {@code POOL_DESTROYED}. If a pool is abandoned + * without {@code POOL_DESTROYED} firing (e.g. abrupt JVM shutdown), that entry will + * linger for the lifetime of the JVM.
    • + *
    */ public final class OtelUCPEventListenerProvider - implements UCPEventListenerProvider { + implements UCPEventListenerProvider { private volatile UCPEventListener listener; @@ -30,6 +125,10 @@ public String getName() { @Override public UCPEventListener getListener(Map config) { + // config is intentionally unused: OTel SDK configuration (exporters, + // export intervals, resource attributes, etc.) is managed externally + // via the OTel SDK setup (e.g. OpenTelemetrySdkAutoConfiguration or + // programmatic SDK builder), not through UCP's provider config map. if (listener == null) { synchronized (this) { if (listener == null) { @@ -44,112 +143,181 @@ private static final class OtelUCPEventListener implements UCPEventListener { private static final long serialVersionUID = 1L; - private final Meter meter = GlobalOpenTelemetry.getMeter("oracle.ucp"); + // ------------------------------------------------------------------------- + // Attribute keys — static: stateless, shared across all pools. + // ------------------------------------------------------------------------- private static final AttributeKey POOL_NAME = - AttributeKey.stringKey("db.client.connection.pool.name"); - - private final LongCounter poolCreatedCounter = - meter.counterBuilder("db.client.connection.pool.created") - .setDescription("Number of POOL_CREATED events.") - .setUnit("{event}").build(); - - private final LongCounter poolStartingCounter = - meter.counterBuilder("db.client.connection.pool.starting") - .setDescription("Number of POOL_STARTING events.") - .setUnit("{event}").build(); - - private final LongCounter poolStartedCounter = - meter.counterBuilder("db.client.connection.pool.started") - .setDescription("Number of POOL_STARTED events.") - .setUnit("{event}").build(); - - private final LongCounter poolStoppedCounter = - meter.counterBuilder("db.client.connection.pool.stopped") - .setDescription("Number of POOL_STOPPED events.") - .setUnit("{event}").build(); - - private final LongCounter poolDestroyedCounter = - meter.counterBuilder("db.client.connection.pool.destroyed") - .setDescription("Number of POOL_DESTROYED events.") - .setUnit("{event}").build(); - - private final LongCounter poolRefreshedCounter = - meter.counterBuilder("db.client.connection.pool.refreshed") - .setDescription("Number of POOL_REFRESHED events.") - .setUnit("{event}").build(); - - private final LongCounter poolRecycledCounter = - meter.counterBuilder("db.client.connection.pool.recycled") - .setDescription("Number of POOL_RECYCLED events.") - .setUnit("{event}").build(); - - private final LongCounter poolPurgedCounter = - meter.counterBuilder("db.client.connection.pool.purged") - .setDescription("Number of POOL_PURGED events.") - .setUnit("{event}").build(); - - private final LongCounter connectionCreatedCounter = - meter.counterBuilder("db.client.connection.created") - .setDescription("Number of CONNECTION_CREATED events.") - .setUnit("{event}").build(); - - private final LongCounter connectionBorrowedCounter = - meter.counterBuilder("db.client.connection.borrowed") - .setDescription("Number of CONNECTION_BORROWED events.") - .setUnit("{event}").build(); - - private final LongCounter connectionReturnedCounter = - meter.counterBuilder("db.client.connection.returned") - .setDescription("Number of CONNECTION_RETURNED events.") - .setUnit("{event}").build(); - - private final LongCounter connectionClosedCounter = - meter.counterBuilder("db.client.connection.closed") - .setDescription("Number of CONNECTION_CLOSED events.") - .setUnit("{event}").build(); - - - private final LongHistogram borrowedConnectionsHistogram = - meter.histogramBuilder("db.client.connection.borrowed_count") - .setDescription("Snapshot of borrowed connections count at the time of the event.") - .setUnit("{connection}").ofLongs().build(); - - private final LongHistogram availableConnectionsHistogram = - meter.histogramBuilder("db.client.connection.available_count") - .setDescription("Snapshot of available connections count at the time of the event.") - .setUnit("{connection}").ofLongs().build(); - - private final LongHistogram totalConnectionsHistogram = - meter.histogramBuilder("db.client.connection.total_count") - .setDescription("Snapshot of total connections (borrowed + available) at the time of the event.") - .setUnit("{connection}").ofLongs().build(); - - private final LongHistogram createdConnectionsHistogram = - meter.histogramBuilder("db.client.connection.created_count") - .setDescription("Snapshot of cumulative created connections at the time of the event.") - .setUnit("{connection}").ofLongs().build(); - - private final LongHistogram closedConnectionsHistogram = - meter.histogramBuilder("db.client.connection.closed_count") - .setDescription("Snapshot of cumulative closed connections at the time of the event.") - .setUnit("{connection}").ofLongs().build(); - - private final LongHistogram avgWaitTimeHistogram = - meter.histogramBuilder("db.client.connection.wait_time") - .setDescription("Average connection wait time in milliseconds at the time of the event.") - .setUnit("ms").ofLongs().build(); - - private final LongHistogram maxPoolSizeHistogram = - meter.histogramBuilder("db.client.connection.max_pool_size") - .setDescription("Snapshot of configured max pool size at the time of the event.") - .setUnit("{connection}").ofLongs().build(); - - private final LongHistogram minPoolSizeHistogram = - meter.histogramBuilder("db.client.connection.min_pool_size") - .setDescription("Snapshot of configured min pool size at the time of the event.") - .setUnit("{connection}").ofLongs().build(); + AttributeKey.stringKey("db.client.connection.pool.name"); + + private static final AttributeKey STATE = + AttributeKey.stringKey("db.client.connection.state"); + + // ------------------------------------------------------------------------- + // Meter and instruments — instance fields, intentionally NOT static. + // + // GlobalOpenTelemetry.getMeter() is called when OtelUCPEventListener is + // constructed, which happens inside getListener(), which UCP calls at pool + // activation time — after the application has registered its OTel SDK. + // If these were static, they would be initialized at class-load time, + // before the SDK is registered, and would silently become permanent no-ops. + // + // Inverse risk: if the OTel SDK is reinitialized after the listener is + // constructed (uncommon in production; typical in tests that reset the SDK + // between runs), these instruments remain bound to the original SDK instance + // and will export to the old exporter. Tests should obtain a fresh provider + // instance after each SDK reset rather than reusing the singleton. + // ------------------------------------------------------------------------- + + // Note: meter is retained as an instance field rather than a constructor-local + // variable for readability — the instrument declarations below read as a + // cohesive block. The Meter object itself is lightweight (no pooled resources), + // so the marginal GC cost of holding the reference is negligible. + private final Meter meter = + GlobalOpenTelemetry.getMeter("oracle.ucp"); + + // Spec-aligned: db.client.connection.usage (LongGauge, state=used|idle) + // Spec says UpDownCounter named db.client.connection.count. We deviate on + // both: name changed to avoid the reserved Prometheus _count suffix (which + // causes the OTel Prometheus exporter to report an unknown metric type), and + // LongGauge used because UCP provides absolute snapshots at each event — + // not incremental deltas. LongGauge.set() maps directly to that model without + // drift risk. A single instrument is registered; the state attribute + // differentiates used vs idle at the data level. + private final LongGauge connectionUsage = + meter.gaugeBuilder("db.client.connection.usage") + .setDescription("The number of connections that are currently in the state described by the state attribute.") + .setUnit("{connection}").ofLongs().build(); + + // Spec-aligned: db.client.connection.max (LongGauge) + // Spec says UpDownCounter, but maxPoolSize is a configuration value set at + // pool creation and rarely changes. LongGauge records the actual current + // value directly, which is semantically correct for a configured constant. + // Recorded only on pool lifecycle events to avoid per-connection overhead. + private final LongGauge connectionMax = + meter.gaugeBuilder("db.client.connection.max") + .setDescription("The maximum number of open connections allowed.") + .setUnit("{connection}").ofLongs().build(); + + // Spec-aligned: db.client.connection.idle.min (LongGauge) + // Same reasoning as db.client.connection.max above. + // Note: UCP's minPoolSize() is the minimum total pool size, not a dedicated + // idle floor. See class-level Javadoc for the full limitation note. + // Recorded only on pool lifecycle events to avoid per-connection overhead. + private final LongGauge connectionIdleMin = + meter.gaugeBuilder("db.client.connection.idle.min") + .setDescription( + "Approximation of the minimum number of idle open connections allowed. " + + "Sourced from UCP's minPoolSize (minimum total pool size), which is the " + + "closest value UCP's event API exposes. May differ from true idle minimum " + + "when connections are actively borrowed.") + .setUnit("{connection}").ofLongs().build(); + + // Spec-aligned: db.client.connection.wait_time (DoubleHistogram, unit: s) + // DoubleHistogram (not Long) because fractional seconds matter: + // e.g. 250 ms = 0.25 s would be truncated to 0 with a LongHistogram. + // Recorded on CONNECTION_BORROWED events only when wait time > 0 to avoid + // polluting the histogram with zero-value observations at pool startup. + // UCP value is in ms — divided by 1000.0 before recording. + // See class-level Javadoc for the full limitation note. + private final DoubleHistogram waitTime = + meter.histogramBuilder("db.client.connection.wait_time") + .setDescription( + "Approximation of borrow wait time based on UCP's pool-wide cumulative " + + "average. Recorded on CONNECTION_BORROWED events only when wait time > 0.") + .setUnit("s").build(); + + // UCP-specific: db.client.connection.established (LongGauge) + // Cumulative count of physical connections ever opened since pool start. + // Although monotonically increasing, we use LongGauge because UCP exposes + // this as an absolute lifetime total. When UCP reuses cached connections + // and never opens new physical sockets, the value stays at 0 — a LongCounter + // with delta=0 produces no data point at all, making the metric invisible. + // LongGauge.set() always records the current value, even when it is 0. + private final LongGauge connectionEstablished = + meter.gaugeBuilder("db.client.connection.established") + .setDescription("Cumulative number of physical connections opened since pool start.") + .setUnit("{connection}").ofLongs().build(); + + // UCP-specific: db.client.connection.closed (LongGauge) + // Same reasoning as db.client.connection.established above. + private final LongGauge connectionClosed = + meter.gaugeBuilder("db.client.connection.closed") + .setDescription("Cumulative number of physical connections closed since pool start.") + .setUnit("{connection}").ofLongs().build(); + + // ------------------------------------------------------------------------- + // Per-pool state — holds pre-built Attributes objects reused on every event + // to avoid allocating new objects on every onUCPEvent call under high load. + // PoolState is a plain data holder — attribute keys passed in explicitly + // to avoid hidden coupling to the enclosing class's static fields. + // + // Entries are removed on POOL_DESTROYED. If a pool is abandoned without + // POOL_DESTROYED firing, its entry will linger for the JVM lifetime. + // ------------------------------------------------------------------------- + + private static final class PoolState { + final Attributes attrs; + final Attributes attrsUsed; + final Attributes attrsIdle; + + PoolState(String poolName, + AttributeKey poolNameKey, + AttributeKey stateKey) { + this.attrs = Attributes.of(poolNameKey, poolName); + this.attrsUsed = Attributes.of(poolNameKey, poolName, stateKey, "used"); + this.attrsIdle = Attributes.of(poolNameKey, poolName, stateKey, "idle"); + } + } + + private final ConcurrentHashMap poolStates = + new ConcurrentHashMap<>(); + + // ------------------------------------------------------------------------- + // Serialization guard + // + // UCPEventListener extends Serializable, but this class holds OTel + // instrument fields that are not serializable. Explicitly block + // serialization rather than letting it fail with an opaque stack trace. + // ------------------------------------------------------------------------- + + private void writeObject(ObjectOutputStream ignored) throws java.io.IOException { + throw new NotSerializableException( + OtelUCPEventListener.class.getName() + + ": OTel instrument fields are not serializable."); + } + // ------------------------------------------------------------------------- + // Event filter — explicit opt-in to all events. + // + // All UCP event types are processed: + // - Pool lifecycle (POOL_CREATED, POOL_STARTING, POOL_STARTED, + // POOL_STOPPED) → connectionMax, connectionIdleMin, + // plus live snapshot + // - POOL_DESTROYED → connectionMax, connectionIdleMin, + // live snapshot + poolStates cleanup + // - CONNECTION_BORROWED → waitTime (when > 0), plus live snapshot + // - All other connection events → live snapshot only + // (CONNECTION_CREATED, CONNECTION_RETURNED, CONNECTION_CLOSED) + // - Maintenance events → live snapshot only + // (POOL_REFRESHED, POOL_RECYCLED, POOL_PURGED) + // + // Maintenance events have no dedicated metric handling but still contribute + // a live connectionUsage / connectionEstablished / connectionClosed snapshot, + // which is useful for observing pool state immediately after a refresh/recycle. + // + // Overriding isDesiredEvent() makes this opt-in explicit rather than relying + // on the default implementation. + // ------------------------------------------------------------------------- + + @Override + public boolean isDesiredEvent(EventType eventType) { + return true; + } + + // ------------------------------------------------------------------------- + // Event handler + // ------------------------------------------------------------------------- @Override public void onUCPEvent(EventType eventType, UCPEventContext ctx) { @@ -157,32 +325,82 @@ public void onUCPEvent(EventType eventType, UCPEventContext ctx) { return; } - Attributes attrs = Attributes.of(POOL_NAME, ctx.poolName()); + String poolName = ctx.poolName(); + // ConcurrentHashMap does not permit null keys — guard explicitly rather + // than relying on UCP to always provide a non-null pool name. + if (poolName == null) { + return; + } - borrowedConnectionsHistogram.record(ctx.borrowedConnectionsCount(), attrs); - availableConnectionsHistogram.record(ctx.availableConnectionsCount(), attrs); - totalConnectionsHistogram.record(ctx.totalConnections(), attrs); - createdConnectionsHistogram.record(ctx.createdConnections(), attrs); - closedConnectionsHistogram.record(ctx.closedConnections(), attrs); - avgWaitTimeHistogram.record(ctx.getAverageConnectionWaitTime(), attrs); - maxPoolSizeHistogram.record(ctx.maxPoolSize(), attrs); - minPoolSizeHistogram.record(ctx.minPoolSize(), attrs); + // POOL_DESTROYED is handled first: remove the PoolState entry atomically + // before recording the final snapshot. This avoids the alternative pattern + // of computeIfAbsent followed by an immediate remove, which would allocate + // a new PoolState only to discard it. The removed state is used to record + // the final metric snapshot; if it is null (pool was never seen), we skip. + if (eventType == EventType.POOL_DESTROYED) { + PoolState state = poolStates.remove(poolName); + if (state != null) { + recordSnapshot(eventType, ctx, state); + } + return; + } + + PoolState state = poolStates.computeIfAbsent( + poolName, k -> new PoolState(k, POOL_NAME, STATE)); + + recordSnapshot(eventType, ctx, state); + } + + /** + * Records all metric observations for a single event. Extracted from + * {@link #onUCPEvent} to keep the event-routing logic and the metric + * recording logic separately readable. + */ + private void recordSnapshot(EventType eventType, UCPEventContext ctx, PoolState state) { + + // --- db.client.connection.usage (LongGauge, direct snapshot) ------------ + + connectionUsage.set(ctx.borrowedConnectionsCount(), state.attrsUsed); + connectionUsage.set(ctx.availableConnectionsCount(), state.attrsIdle); + + // --- db.client.connection.max / idle.min -------------------------------- + // Configuration constants — only recorded on pool lifecycle events to + // avoid redundant work on high-frequency connection borrow/return events. + // Maintenance events (POOL_REFRESHED, POOL_RECYCLED, POOL_PURGED) are + // intentionally excluded — they only contribute a live connection snapshot. 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_DESTROYED: poolDestroyedCounter.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; - case CONNECTION_CREATED: connectionCreatedCounter.add(1, attrs); break; - case CONNECTION_BORROWED: connectionBorrowedCounter.add(1, attrs); break; - case CONNECTION_RETURNED: connectionReturnedCounter.add(1, attrs); break; - case CONNECTION_CLOSED: connectionClosedCounter.add(1, attrs); break; - default: break; + case POOL_CREATED: + case POOL_STARTING: + case POOL_STARTED: + case POOL_STOPPED: + case POOL_DESTROYED: + connectionMax.set(ctx.maxPoolSize(), state.attrs); + connectionIdleMin.set(ctx.minPoolSize(), state.attrs); + break; + default: + break; } + + // --- db.client.connection.wait_time (CONNECTION_BORROWED only) ----------- + // Cached in a local variable to avoid calling getAverageConnectionWaitTime() + // twice and to guard against any future context mutability. + // Only recorded when > 0 to avoid polluting the histogram with zero-value + // observations before any real waits occur. + // See limitation note in Javadoc — UCP exposes a cumulative average, + // not a per-borrow value. + + if (eventType == EventType.CONNECTION_BORROWED) { + double avgWaitMs = ctx.getAverageConnectionWaitTime(); + if (avgWaitMs > 0) { + waitTime.record(avgWaitMs / 1000.0, state.attrs); + } + } + + // --- UCP-specific metrics ----------------------------------------------- + + connectionEstablished.set(ctx.createdConnections(), state.attrs); + connectionClosed.set(ctx.closedConnections(), state.attrs); } } } \ No newline at end of file From 4bda949e582d0ecb7fe9049abbe7d2cdff4b6651 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Sun, 29 Mar 2026 06:26:35 +0100 Subject: [PATCH 57/82] Edited the OTEL unit test class to adapt to the new version of the OTEL provider. --- .../OtelUCPEventListenerProviderTest.java | 274 ++++++++++++------ 1 file changed, 189 insertions(+), 85 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java index 6c7bd797..abc7f3ee 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java @@ -1,3 +1,41 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + package oracle.ucp.provider.observability; import oracle.ucp.events.core.UCPEventContext; @@ -29,7 +67,6 @@ void setUpMockContext() { when(mockContext.minPoolSize()).thenReturn(2); when(mockContext.borrowedConnectionsCount()).thenReturn(3); when(mockContext.availableConnectionsCount()).thenReturn(5); - when(mockContext.totalConnections()).thenReturn(8); when(mockContext.closedConnections()).thenReturn(1); when(mockContext.createdConnections()).thenReturn(9); when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); @@ -56,23 +93,21 @@ void testGetName() { @Test @DisplayName("getListener() returns a non-null listener") void testGetListenerNotNull() { - UCPEventListener listener = provider.getListener(Collections.emptyMap()); - assertNotNull(listener); + assertNotNull(provider.getListener(Collections.emptyMap())); } @Test @DisplayName("getListener() returns a non-null listener when config is null") void testGetListenerWithNullConfig() { - UCPEventListener listener = provider.getListener(null); - assertNotNull(listener); + assertNotNull(provider.getListener(null)); } @Test @DisplayName("getListener() returns the same instance on repeated calls (singleton)") void testGetListenerReturnsSameInstance() { - UCPEventListener listener1 = provider.getListener(Collections.emptyMap()); - UCPEventListener listener2 = provider.getListener(Collections.emptyMap()); - assertSame(listener1, listener2); + UCPEventListener l1 = provider.getListener(Collections.emptyMap()); + UCPEventListener l2 = provider.getListener(Collections.emptyMap()); + assertSame(l1, l2); } @Test @@ -81,7 +116,7 @@ void testIsDesiredEventReturnsTrueForAll() { UCPEventListener listener = provider.getListener(Collections.emptyMap()); for (EventType type : EventType.values()) { assertTrue(listener.isDesiredEvent(type), - "Expected isDesiredEvent to return true for: " + type); + "Expected isDesiredEvent to return true for: " + type); } } } @@ -96,7 +131,7 @@ class ListenerNullSafetyTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @Test @@ -109,7 +144,7 @@ void testOnUCPEventNullEventType() { @DisplayName("onUCPEvent() does not throw when context is null") void testOnUCPEventNullContext() { assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, null)); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, null)); } @Test @@ -117,10 +152,18 @@ void testOnUCPEventNullContext() { void testOnUCPEventBothNull() { assertDoesNotThrow(() -> listener.onUCPEvent(null, null)); } + + @Test + @DisplayName("onUCPEvent() does not throw when poolName() returns null") + void testOnUCPEventNullPoolName() { + when(mockContext.poolName()).thenReturn(null); + assertDoesNotThrow(() -> + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); + } } @Nested - @DisplayName("UCPEventListener — onUCPEvent robustness") + @DisplayName("UCPEventListener — robustness") class ListenerRobustnessTests { private UCPEventListener listener; @@ -128,7 +171,7 @@ class ListenerRobustnessTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @ParameterizedTest(name = "onUCPEvent() does not throw for EventType.{0}") @@ -142,10 +185,9 @@ void testOnUCPEventDoesNotThrowForAllTypes(EventType type) { @DisplayName("onUCPEvent() does not throw when context has zero/default values") void testOnUCPEventWithZeroValues() { UCPEventContext zeroContext = mock(UCPEventContext.class); - when(zeroContext.poolName()).thenReturn(""); + when(zeroContext.poolName()).thenReturn("ZeroPool"); when(zeroContext.borrowedConnectionsCount()).thenReturn(0); when(zeroContext.availableConnectionsCount()).thenReturn(0); - when(zeroContext.totalConnections()).thenReturn(0); when(zeroContext.closedConnections()).thenReturn(0); when(zeroContext.createdConnections()).thenReturn(0); when(zeroContext.getAverageConnectionWaitTime()).thenReturn(0L); @@ -153,91 +195,155 @@ void testOnUCPEventWithZeroValues() { when(zeroContext.minPoolSize()).thenReturn(0); assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, zeroContext)); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, zeroContext)); } } - @Nested - @DisplayName("UCPEventListener — histogram fields read on every event") - class ListenerHistogramFieldTests { + @DisplayName("UCPEventListener — context field access") + class ListenerContextAccessTests { private UCPEventListener listener; @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @Test - @DisplayName("poolName() is always read — used as metric attribute on every event") + @DisplayName("poolName() is always read — used as metric attribute key") void testPoolNameAlwaysRead() { listener.onUCPEvent(EventType.POOL_CREATED, mockContext); verify(mockContext, atLeastOnce()).poolName(); } @Test - @DisplayName("All 8 histogram fields are read on CONNECTION_BORROWED") - void testAllHistogramFieldsReadOnBorrow() { - listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); + @DisplayName("borrowedConnectionsCount() and availableConnectionsCount() are read on every event") + void testUsageFieldsReadOnEveryEvent() { + listener.onUCPEvent(EventType.CONNECTION_RETURNED, mockContext); verify(mockContext).borrowedConnectionsCount(); verify(mockContext).availableConnectionsCount(); - verify(mockContext).totalConnections(); + } + + @Test + @DisplayName("createdConnections() and closedConnections() are read on every event") + void testLifetimeFieldsReadOnEveryEvent() { + listener.onUCPEvent(EventType.CONNECTION_RETURNED, mockContext); verify(mockContext).createdConnections(); verify(mockContext).closedConnections(); - verify(mockContext).getAverageConnectionWaitTime(); - verify(mockContext).maxPoolSize(); - verify(mockContext).minPoolSize(); } @Test - @DisplayName("All 8 histogram fields are read on POOL_CREATED") - void testAllHistogramFieldsReadOnPoolCreated() { + @DisplayName("maxPoolSize() and minPoolSize() are read on POOL_CREATED") + void testConfigFieldsReadOnPoolCreated() { listener.onUCPEvent(EventType.POOL_CREATED, mockContext); - verify(mockContext).borrowedConnectionsCount(); - verify(mockContext).availableConnectionsCount(); - verify(mockContext).totalConnections(); - verify(mockContext).createdConnections(); - verify(mockContext).closedConnections(); - verify(mockContext).getAverageConnectionWaitTime(); verify(mockContext).maxPoolSize(); verify(mockContext).minPoolSize(); } @Test - @DisplayName("All 8 histogram fields are read on POOL_DESTROYED") - void testAllHistogramFieldsReadOnPoolDestroyed() { + @DisplayName("maxPoolSize() and minPoolSize() are read on POOL_DESTROYED") + void testConfigFieldsReadOnPoolDestroyed() { + + listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + reset(mockContext); + when(mockContext.poolName()).thenReturn("TestPool"); + when(mockContext.borrowedConnectionsCount()).thenReturn(3); + when(mockContext.availableConnectionsCount()).thenReturn(5); + when(mockContext.createdConnections()).thenReturn(9); + when(mockContext.closedConnections()).thenReturn(1); + when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); + when(mockContext.maxPoolSize()).thenReturn(50); + when(mockContext.minPoolSize()).thenReturn(2); listener.onUCPEvent(EventType.POOL_DESTROYED, mockContext); - verify(mockContext).borrowedConnectionsCount(); - verify(mockContext).availableConnectionsCount(); - verify(mockContext).totalConnections(); - verify(mockContext).createdConnections(); - verify(mockContext).closedConnections(); - verify(mockContext).getAverageConnectionWaitTime(); verify(mockContext).maxPoolSize(); verify(mockContext).minPoolSize(); } - @ParameterizedTest(name = "All histogram fields are read for EventType.{0}") - @EnumSource(EventType.class) - @DisplayName("All 8 histogram fields are read for every EventType") - void testAllHistogramFieldsReadForAllEventTypes(EventType type) { - listener.onUCPEvent(type, mockContext); - verify(mockContext).borrowedConnectionsCount(); - verify(mockContext).availableConnectionsCount(); - verify(mockContext).totalConnections(); - verify(mockContext).createdConnections(); - verify(mockContext).closedConnections(); - verify(mockContext).getAverageConnectionWaitTime(); - verify(mockContext).maxPoolSize(); - verify(mockContext).minPoolSize(); + @Test + @DisplayName("maxPoolSize() and minPoolSize() are NOT read on CONNECTION_BORROWED") + void testConfigFieldsNotReadOnConnectionBorrowed() { + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); + verify(mockContext, never()).maxPoolSize(); + verify(mockContext, never()).minPoolSize(); + } + + @Test + @DisplayName("maxPoolSize() and minPoolSize() are NOT read on maintenance events") + void testConfigFieldsNotReadOnMaintenanceEvents() { + listener.onUCPEvent(EventType.POOL_REFRESHED, mockContext); + verify(mockContext, never()).maxPoolSize(); + verify(mockContext, never()).minPoolSize(); + + reset(mockContext); + when(mockContext.poolName()).thenReturn("TestPool"); + when(mockContext.borrowedConnectionsCount()).thenReturn(3); + when(mockContext.availableConnectionsCount()).thenReturn(5); + when(mockContext.createdConnections()).thenReturn(9); + when(mockContext.closedConnections()).thenReturn(1); + when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); + + listener.onUCPEvent(EventType.POOL_RECYCLED, mockContext); + verify(mockContext, never()).maxPoolSize(); + verify(mockContext, never()).minPoolSize(); + } + + @Test + @DisplayName("getAverageConnectionWaitTime() is read on CONNECTION_BORROWED when > 0") + void testWaitTimeReadOnBorrowWhenPositive() { + when(mockContext.getAverageConnectionWaitTime()).thenReturn(100L); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); + verify(mockContext, atLeastOnce()).getAverageConnectionWaitTime(); + } + + @Test + @DisplayName("getAverageConnectionWaitTime() is NOT recorded on CONNECTION_BORROWED when 0") + void testWaitTimeNotRecordedWhenZero() { + + when(mockContext.getAverageConnectionWaitTime()).thenReturn(0L); + assertDoesNotThrow(() -> + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); + } + + @Test + @DisplayName("getAverageConnectionWaitTime() is NOT read on non-borrow events") + void testWaitTimeNotReadOnNonBorrowEvents() { + listener.onUCPEvent(EventType.CONNECTION_RETURNED, mockContext); + verify(mockContext, never()).getAverageConnectionWaitTime(); + + reset(mockContext); + when(mockContext.poolName()).thenReturn("TestPool"); + when(mockContext.borrowedConnectionsCount()).thenReturn(3); + when(mockContext.availableConnectionsCount()).thenReturn(5); + when(mockContext.createdConnections()).thenReturn(9); + when(mockContext.closedConnections()).thenReturn(1); + + listener.onUCPEvent(EventType.POOL_REFRESHED, mockContext); + verify(mockContext, never()).getAverageConnectionWaitTime(); } - } + @Test + @DisplayName("totalConnections() is never read — removed from provider") + void testTotalConnectionsNeverRead() { + for (EventType type : EventType.values()) { + reset(mockContext); + when(mockContext.poolName()).thenReturn("TestPool"); + when(mockContext.borrowedConnectionsCount()).thenReturn(3); + when(mockContext.availableConnectionsCount()).thenReturn(5); + when(mockContext.createdConnections()).thenReturn(9); + when(mockContext.closedConnections()).thenReturn(1); + when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); + when(mockContext.maxPoolSize()).thenReturn(50); + when(mockContext.minPoolSize()).thenReturn(2); + listener.onUCPEvent(type, mockContext); + verify(mockContext, never()).totalConnections(); + } + } + } @Nested - @DisplayName("UCPEventListener — multiple pool independence") + @DisplayName("UCPEventListener — multi-pool independence") class ListenerMultiPoolTests { private UCPEventListener listener; @@ -249,35 +355,33 @@ void setUp() { } @Test - @DisplayName("Multiple pools can emit events independently without interference") + @DisplayName("Multiple pools emit events independently without interference") void testMultiplePoolsIndependent() { - UCPEventContext pool1Context = mock(UCPEventContext.class); - when(pool1Context.poolName()).thenReturn("Pool1"); - when(pool1Context.borrowedConnectionsCount()).thenReturn(2); - when(pool1Context.availableConnectionsCount()).thenReturn(3); - when(pool1Context.totalConnections()).thenReturn(5); - when(pool1Context.getAverageConnectionWaitTime()).thenReturn(10L); - when(pool1Context.maxPoolSize()).thenReturn(10); - when(pool1Context.minPoolSize()).thenReturn(1); - when(pool1Context.createdConnections()).thenReturn(5); - when(pool1Context.closedConnections()).thenReturn(0); - - UCPEventContext pool2Context = mock(UCPEventContext.class); - when(pool2Context.poolName()).thenReturn("Pool2"); - when(pool2Context.borrowedConnectionsCount()).thenReturn(7); - when(pool2Context.availableConnectionsCount()).thenReturn(1); - when(pool2Context.totalConnections()).thenReturn(8); - when(pool2Context.getAverageConnectionWaitTime()).thenReturn(50L); - when(pool2Context.maxPoolSize()).thenReturn(20); - when(pool2Context.minPoolSize()).thenReturn(2); - when(pool2Context.createdConnections()).thenReturn(8); - when(pool2Context.closedConnections()).thenReturn(1); + UCPEventContext pool1 = mock(UCPEventContext.class); + when(pool1.poolName()).thenReturn("Pool1"); + when(pool1.borrowedConnectionsCount()).thenReturn(2); + when(pool1.availableConnectionsCount()).thenReturn(3); + when(pool1.getAverageConnectionWaitTime()).thenReturn(10L); + when(pool1.maxPoolSize()).thenReturn(10); + when(pool1.minPoolSize()).thenReturn(1); + when(pool1.createdConnections()).thenReturn(5); + when(pool1.closedConnections()).thenReturn(0); + + UCPEventContext pool2 = mock(UCPEventContext.class); + when(pool2.poolName()).thenReturn("Pool2"); + when(pool2.borrowedConnectionsCount()).thenReturn(7); + when(pool2.availableConnectionsCount()).thenReturn(1); + when(pool2.getAverageConnectionWaitTime()).thenReturn(50L); + when(pool2.maxPoolSize()).thenReturn(20); + when(pool2.minPoolSize()).thenReturn(2); + when(pool2.createdConnections()).thenReturn(8); + when(pool2.closedConnections()).thenReturn(1); assertDoesNotThrow(() -> { - listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool1Context); - listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool2Context); - listener.onUCPEvent(EventType.CONNECTION_RETURNED, pool1Context); - listener.onUCPEvent(EventType.POOL_DESTROYED, pool2Context); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool1); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool2); + listener.onUCPEvent(EventType.CONNECTION_RETURNED, pool1); + listener.onUCPEvent(EventType.POOL_DESTROYED, pool2); }); } From c55a42a3f113ae4ba7564a4f89e86cfa76f351d0 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Mon, 30 Mar 2026 13:13:04 +0100 Subject: [PATCH 58/82] =?UTF-8?q?fix:=20harden=20JFR=20provider=20?= =?UTF-8?q?=E2=80=94=20private=20fields,=20serialVersionUID,=20direct=20co?= =?UTF-8?q?mmit,=20per-event=20@Description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jfr/core/JFRUCPEventListenerProvider.java | 16 +++++++------ .../observability/jfr/core/UCPBaseEvent.java | 23 ++++++++++--------- .../jfr/core/UCPEventFactory.java | 16 ++++++------- .../connection/ConnectionBorrowedEvent.java | 4 +++- .../connection/ConnectionClosedEvent.java | 4 +++- .../connection/ConnectionCreatedEvent.java | 4 +++- .../connection/ConnectionReturnedEvent.java | 4 +++- .../events/lifecycle/PoolCreatedEvent.java | 4 +++- .../events/lifecycle/PoolDestroyedEvent.java | 4 +++- .../events/lifecycle/PoolStartedEvent.java | 4 +++- .../events/lifecycle/PoolStartingEvent.java | 4 +++- .../events/lifecycle/PoolStoppedEvent.java | 4 +++- .../events/maintenance/PoolPurgedEvent.java | 4 +++- .../events/maintenance/PoolRecycledEvent.java | 4 +++- .../maintenance/PoolRefreshedEvent.java | 4 +++- 15 files changed, 65 insertions(+), 38 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java index 03edbc70..fe895dd5 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java @@ -49,7 +49,7 @@ * Integrates UCP events with Java Flight Recorder for low-overhead monitoring. */ public final class JFRUCPEventListenerProvider - implements UCPEventListenerProvider { + implements UCPEventListenerProvider { private final UCPEventListener listener; @@ -59,12 +59,14 @@ public final class JFRUCPEventListenerProvider * 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); - } - }; + new UCPEventListener() { + private static final long serialVersionUID = 1L; + + @Override + public void onUCPEvent(EventType eventType, UCPEventContext context) { + UCPEventFactory.recordEvent(eventType, context); + } + }; /** * Creates a new provider instance. diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java index 85762d1c..6598a2d9 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java @@ -54,7 +54,7 @@ public abstract class UCPBaseEvent extends Event { /** Name of the connection pool */ @Label("Pool Name") - protected String poolName; + private String poolName; /** * @@ -63,40 +63,40 @@ public abstract class UCPBaseEvent extends Event { * automatically — this field complements it rather than replacing it. */ @Label("UCP Timestamp (ms)") - protected long ucpTimestamp; + private long ucpTimestamp; /** Maximum configured pool size */ @Label("Max Pool Size") - protected int maxPoolSize; + private int maxPoolSize; /** Minimum configured pool size */ @Label("Min Pool Size") - protected int minPoolSize; + private int minPoolSize; /** Current count of borrowed connections */ @Label("Borrowed Connections") - protected int borrowedConnections; + private int borrowedConnections; /** Current count of available connections */ @Label("Available Connections") - protected int availableConnections; + private int availableConnections; /** Total active connections (borrowed + available) */ @Label("Total Connections") - protected int totalConnections; + private int totalConnections; /** Lifetime count of closed connections */ @Label("Closed Connections") - protected int closedConnections; + private int closedConnections; /** Lifetime count of created connections */ @Label("Created Connections") - protected int createdConnections; + private int createdConnections; /** Average connection wait time in milliseconds */ @Label("Average Wait Time (ms)") @Timespan(Timespan.MILLISECONDS) - protected long avgWaitTime; + private long avgWaitTime; /** * Initializes common fields from UCP event context. @@ -107,7 +107,8 @@ public abstract class UCPBaseEvent extends Event { protected void initCommonFields(UCPEventContext ctx) { Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); - this.poolName = ctx.poolName(); + String name = ctx.poolName(); + this.poolName = name != null ? name : ""; this.ucpTimestamp = ctx.timestamp(); this.maxPoolSize = ctx.maxPoolSize(); this.minPoolSize = ctx.minPoolSize(); diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java index f2a19465..0b5a9752 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java @@ -53,10 +53,12 @@ * Maps UCP event types to specific JFR event classes and handles * recording. */ -public class UCPEventFactory { +public final class UCPEventFactory { + + private UCPEventFactory() {} private static final Logger LOGGER = - Logger.getLogger(UCPEventFactory.class.getName()); + Logger.getLogger(UCPEventFactory.class.getName()); /** * Creates a JFR event instance for the specified UCP event type. @@ -67,7 +69,7 @@ public class UCPEventFactory { * @throws NullPointerException if parameters are null */ static Event createEvent( - UCPEventListener.EventType type, UCPEventContext ctx) { + UCPEventListener.EventType type, UCPEventContext ctx) { Objects.requireNonNull(type, "EventType cannot be null"); Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); @@ -92,7 +94,7 @@ static Event createEvent( default: LOGGER.fine(() -> - "Unrecognized UCP EventType ignored by JFR provider: " + type); + "Unrecognized UCP EventType ignored by JFR provider: " + type); return null; } } @@ -106,7 +108,7 @@ static Event createEvent( * @throws NullPointerException if parameters are null */ public static void recordEvent( - UCPEventListener.EventType type, UCPEventContext ctx) { + UCPEventListener.EventType type, UCPEventContext ctx) { Objects.requireNonNull(type, "EventType cannot be null"); Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); @@ -116,8 +118,6 @@ public static void recordEvent( return; } - if (event.shouldCommit()) { - event.commit(); - } + event.commit(); } } \ No newline at end of file diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java index 8e344987..93da79b0 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.ConnectionBorrowed") @Label("Connection Borrowed") +@Description("Emitted when a connection is borrowed from the pool") @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-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java index 7165a06d..027666ba 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.ConnectionClosed") @Label("Connection Closed") +@Description("Emitted when a connection is 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-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java index 123e7ef6..5e6bdc2e 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.ConnectionCreated") @Label("Connection Created") +@Description("Emitted when a new connection is created") @Category({"UCP Events", "Connection Lifecycle Events"}) public class ConnectionCreatedEvent extends UCPBaseEvent { public ConnectionCreatedEvent(UCPEventContext ctx) { initCommonFields(ctx); } -} +} \ No newline at end of file diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java index 0b1ee12c..e009f28b 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.ConnectionReturned") @Label("Connection Returned") +@Description("Emitted when a connection is returned to the pool") @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-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java index e6d8835c..2713510c 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolCreated") @Label("Pool Created") +@Description("Emitted when the connection pool is successfully 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-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java index bd2383bc..694b246c 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolDestroyed") @Label("Pool Destroyed") +@Description("Emitted when the connection pool is successfully destroyed") @Category({"UCP Events", "Pool Lifecycle Events"}) public class PoolDestroyedEvent extends UCPBaseEvent { public PoolDestroyedEvent(UCPEventContext ctx) { initCommonFields(ctx); } -} +} \ No newline at end of file diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java index 61eba6a6..9a5dbd6c 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolStarted") @Label("Pool Started") +@Description("Emitted when the connection pool is successfully 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-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java index b63df0be..89fd40d4 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolStarting") @Label("Pool Starting") +@Description("Emitted when pool startup begins") @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-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java index 82dc1883..c14b0470 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolStopped") @Label("Pool Stopped") +@Description("Emitted when the connection pool is successfully stopped") @Category({"UCP Events", "Pool Lifecycle Events"}) public class PoolStoppedEvent extends UCPBaseEvent { public PoolStoppedEvent(UCPEventContext ctx) { initCommonFields(ctx); } -} +} \ No newline at end of file diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java index 5a3f1184..ef768099 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolPurged") @Label("Pool Purged") +@Description("Emitted when a pool purge operation completes") @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-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java index 024b8b78..1151cdfc 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolRecycled") @Label("Pool Recycled") +@Description("Emitted when a pool recycle operation completes") @Category({"UCP Events", "Maintenance Operations Events"}) public class PoolRecycledEvent extends UCPBaseEvent { public PoolRecycledEvent(UCPEventContext ctx) { initCommonFields(ctx); } -} +} \ No newline at end of file diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java index 4bd78a96..bca837a5 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java @@ -40,16 +40,18 @@ import jdk.jfr.Category; import jdk.jfr.Label; +import jdk.jfr.Description; import jdk.jfr.Name; import oracle.ucp.events.core.UCPEventContext; import oracle.ucp.provider.observability.jfr.core.UCPBaseEvent; @Name("ucp.PoolRefreshed") @Label("Pool Refreshed") +@Description("Emitted when a pool refresh operation completes") @Category({"UCP Events", "Maintenance Operations Events"}) public class PoolRefreshedEvent extends UCPBaseEvent { public PoolRefreshedEvent(UCPEventContext ctx) { initCommonFields(ctx); } -} +} \ No newline at end of file From 216947c6fc1023540d95b9074789e82af1b7c64e Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Mon, 30 Mar 2026 13:13:51 +0100 Subject: [PATCH 59/82] Improved Unit tests classes --- .../JFRUCPEventListenerProviderTest.java | 37 +- .../OtelUCPEventListenerProviderTest.java | 350 +++++++++++++----- 2 files changed, 283 insertions(+), 104 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java index 75853745..6d273e75 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java @@ -99,13 +99,6 @@ void testGetListenerNotNull() { assertNotNull(listener); } - @Test - @DisplayName("getListener() returns a non-null listener when config is null") - void testGetListenerWithNullConfig() { - UCPEventListener listener = provider.getListener(null); - assertNotNull(listener); - } - @Test @DisplayName("isDesiredEvent() returns true for all event types by default") void testIsDesiredEventReturnsTrueForAll() { @@ -166,6 +159,25 @@ void testRecordEventWithZeroValues() { assertDoesNotThrow(() -> UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, zeroContext)); } + + @Test + @DisplayName("recordEvent() does not throw when poolName is null") + void testRecordEventWithNullPoolName() { + UCPEventContext nullPoolNameContext = mock(UCPEventContext.class); + when(nullPoolNameContext.poolName()).thenReturn(null); + when(nullPoolNameContext.timestamp()).thenReturn(0L); + when(nullPoolNameContext.maxPoolSize()).thenReturn(0); + when(nullPoolNameContext.minPoolSize()).thenReturn(0); + when(nullPoolNameContext.borrowedConnectionsCount()).thenReturn(0); + when(nullPoolNameContext.availableConnectionsCount()).thenReturn(0); + when(nullPoolNameContext.totalConnections()).thenReturn(0); + when(nullPoolNameContext.closedConnections()).thenReturn(0); + when(nullPoolNameContext.createdConnections()).thenReturn(0); + when(nullPoolNameContext.getAverageConnectionWaitTime()).thenReturn(0L); + + assertDoesNotThrow(() -> + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, nullPoolNameContext)); + } } @Nested @@ -181,13 +193,16 @@ void testOnUCPEventDoesNotThrowForAllTypes(EventType type) { assertDoesNotThrow(() -> listener.onUCPEvent(type, mockContext)); } - @Test - @DisplayName("onUCPEvent() reads all context fields during event creation") - void testOnUCPEventReadsAllContextFields() { + @ParameterizedTest(name = "onUCPEvent() reads all context fields for EventType.{0}") + @EnumSource(value = EventType.class, names = { + "POOL_CREATED", "CONNECTION_BORROWED", "POOL_REFRESHED" + }) + @DisplayName("onUCPEvent() reads all context fields for each event category") + void testOnUCPEventReadsAllContextFields(EventType type) { JFRUCPEventListenerProvider provider = new JFRUCPEventListenerProvider(); UCPEventListener listener = provider.getListener(Collections.emptyMap()); - listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); + listener.onUCPEvent(type, mockContext); verify(mockContext).poolName(); verify(mockContext).timestamp(); diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java index abc7f3ee..5a3f9c18 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java @@ -49,7 +49,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import java.io.ByteArrayOutputStream; +import java.io.NotSerializableException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -72,6 +81,46 @@ void setUpMockContext() { when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); } + /** + * Resets the shared mockContext and re-applies all standard stubs. + * + *

    Use this helper in tests that call reset() mid-test (e.g. after a + * POOL_DESTROYED call) and need to verify interaction counts on subsequent + * events without contamination from earlier invocations.

    + */ + private void resetAndRestubContext() { + reset(mockContext); + when(mockContext.poolName()).thenReturn("TestPool"); + when(mockContext.maxPoolSize()).thenReturn(50); + when(mockContext.minPoolSize()).thenReturn(2); + when(mockContext.borrowedConnectionsCount()).thenReturn(3); + when(mockContext.availableConnectionsCount()).thenReturn(5); + when(mockContext.closedConnections()).thenReturn(1); + when(mockContext.createdConnections()).thenReturn(9); + when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); + } + + /** + * Creates a fully-stubbed mock context for the given pool name. + * Used in multi-pool and concurrency tests to avoid shared state. + */ + private UCPEventContext newFullyStubbed(String poolName) { + UCPEventContext ctx = mock(UCPEventContext.class); + when(ctx.poolName()).thenReturn(poolName); + when(ctx.maxPoolSize()).thenReturn(10); + when(ctx.minPoolSize()).thenReturn(1); + when(ctx.borrowedConnectionsCount()).thenReturn(1); + when(ctx.availableConnectionsCount()).thenReturn(1); + when(ctx.closedConnections()).thenReturn(0); + when(ctx.createdConnections()).thenReturn(1); + when(ctx.getAverageConnectionWaitTime()).thenReturn(10L); + return ctx; + } + + + // --------------------------------------------------------------------------- + // Provider contract + // --------------------------------------------------------------------------- @Nested @DisplayName("OtelUCPEventListenerProvider") @@ -99,6 +148,7 @@ void testGetListenerNotNull() { @Test @DisplayName("getListener() returns a non-null listener when config is null") void testGetListenerWithNullConfig() { + // config is intentionally unused by the provider — null must be safe. assertNotNull(provider.getListener(null)); } @@ -116,12 +166,16 @@ void testIsDesiredEventReturnsTrueForAll() { UCPEventListener listener = provider.getListener(Collections.emptyMap()); for (EventType type : EventType.values()) { assertTrue(listener.isDesiredEvent(type), - "Expected isDesiredEvent to return true for: " + type); + "Expected isDesiredEvent to return true for: " + type); } } } + // --------------------------------------------------------------------------- + // Null safety + // --------------------------------------------------------------------------- + @Nested @DisplayName("UCPEventListener — null safety") class ListenerNullSafetyTests { @@ -131,7 +185,7 @@ class ListenerNullSafetyTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @Test @@ -144,7 +198,7 @@ void testOnUCPEventNullEventType() { @DisplayName("onUCPEvent() does not throw when context is null") void testOnUCPEventNullContext() { assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, null)); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, null)); } @Test @@ -154,14 +208,26 @@ void testOnUCPEventBothNull() { } @Test - @DisplayName("onUCPEvent() does not throw when poolName() returns null") + @DisplayName("onUCPEvent() does not throw and reads no fields when poolName() returns null") void testOnUCPEventNullPoolName() { + // The null-poolName guard returns immediately — no context fields beyond + // poolName() itself should be accessed after the early return. when(mockContext.poolName()).thenReturn(null); assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); + + verify(mockContext, never()).borrowedConnectionsCount(); + verify(mockContext, never()).availableConnectionsCount(); + verify(mockContext, never()).maxPoolSize(); + verify(mockContext, never()).createdConnections(); } } + + // --------------------------------------------------------------------------- + // Robustness — all event types + // --------------------------------------------------------------------------- + @Nested @DisplayName("UCPEventListener — robustness") class ListenerRobustnessTests { @@ -171,9 +237,16 @@ class ListenerRobustnessTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } + /** + * Verifies that onUCPEvent() does not throw for any EventType. + * + *

    The enum values are iterated in declaration order. POOL_DESTROYED + * removes the pool state entry — subsequent events simply re-create it + * via computeIfAbsent, so ordering has no correctness impact here.

    + */ @ParameterizedTest(name = "onUCPEvent() does not throw for EventType.{0}") @EnumSource(EventType.class) @DisplayName("onUCPEvent() does not throw for any known EventType") @@ -195,10 +268,15 @@ void testOnUCPEventWithZeroValues() { when(zeroContext.minPoolSize()).thenReturn(0); assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, zeroContext)); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, zeroContext)); } } + + // --------------------------------------------------------------------------- + // Context field access — what gets read, when + // --------------------------------------------------------------------------- + @Nested @DisplayName("UCPEventListener — context field access") class ListenerContextAccessTests { @@ -208,36 +286,60 @@ class ListenerContextAccessTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @Test - @DisplayName("poolName() is always read — used as metric attribute key") - void testPoolNameAlwaysRead() { + @DisplayName("poolName() is read exactly once per event — used as metric attribute key") + void testPoolNameReadExactlyOnce() { listener.onUCPEvent(EventType.POOL_CREATED, mockContext); - verify(mockContext, atLeastOnce()).poolName(); + verify(mockContext, times(1)).poolName(); } @Test - @DisplayName("borrowedConnectionsCount() and availableConnectionsCount() are read on every event") - void testUsageFieldsReadOnEveryEvent() { + @DisplayName("borrowedConnectionsCount() and availableConnectionsCount() are read on a non-lifecycle event") + void testUsageFieldsReadOnNonLifecycleEvent() { + // "Every non-POOL_DESTROYED event" — CONNECTION_RETURNED is representative. + // POOL_DESTROYED is excluded because its snapshot is conditional on pool + // state existing; this test focuses on the unconditional snapshot path. listener.onUCPEvent(EventType.CONNECTION_RETURNED, mockContext); verify(mockContext).borrowedConnectionsCount(); verify(mockContext).availableConnectionsCount(); } @Test - @DisplayName("createdConnections() and closedConnections() are read on every event") - void testLifetimeFieldsReadOnEveryEvent() { + @DisplayName("borrowedConnectionsCount() and availableConnectionsCount() are read on a maintenance event") + void testUsageFieldsReadOnMaintenanceEvent() { + listener.onUCPEvent(EventType.POOL_REFRESHED, mockContext); + verify(mockContext).borrowedConnectionsCount(); + verify(mockContext).availableConnectionsCount(); + } + + @Test + @DisplayName("createdConnections() and closedConnections() are read on a non-lifecycle event") + void testLifetimeFieldsReadOnNonLifecycleEvent() { listener.onUCPEvent(EventType.CONNECTION_RETURNED, mockContext); verify(mockContext).createdConnections(); verify(mockContext).closedConnections(); } @Test - @DisplayName("maxPoolSize() and minPoolSize() are read on POOL_CREATED") - void testConfigFieldsReadOnPoolCreated() { - listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + @DisplayName("createdConnections() and closedConnections() are read on a maintenance event") + void testLifetimeFieldsReadOnMaintenanceEvent() { + listener.onUCPEvent(EventType.POOL_REFRESHED, mockContext); + verify(mockContext).createdConnections(); + verify(mockContext).closedConnections(); + } + + // --- Config fields (maxPoolSize / minPoolSize) — lifecycle events only ---- + + @ParameterizedTest(name = "maxPoolSize() and minPoolSize() are read on {0}") + @EnumSource(value = EventType.class, names = { + "POOL_CREATED", "POOL_STARTING", "POOL_STARTED", "POOL_STOPPED" + }) + @DisplayName("maxPoolSize() and minPoolSize() are read on all non-destroy lifecycle events") + void testConfigFieldsReadOnLifecycleEvents(EventType type) { + listener.onUCPEvent(type, mockContext); verify(mockContext).maxPoolSize(); verify(mockContext).minPoolSize(); } @@ -245,17 +347,12 @@ void testConfigFieldsReadOnPoolCreated() { @Test @DisplayName("maxPoolSize() and minPoolSize() are read on POOL_DESTROYED") void testConfigFieldsReadOnPoolDestroyed() { - + // POOL_DESTROYED removes the poolStates entry before calling recordSnapshot. + // The entry must exist first — seed it via POOL_CREATED so that the remove + // finds a non-null PoolState and recordSnapshot is actually invoked. listener.onUCPEvent(EventType.POOL_CREATED, mockContext); - reset(mockContext); - when(mockContext.poolName()).thenReturn("TestPool"); - when(mockContext.borrowedConnectionsCount()).thenReturn(3); - when(mockContext.availableConnectionsCount()).thenReturn(5); - when(mockContext.createdConnections()).thenReturn(9); - when(mockContext.closedConnections()).thenReturn(1); - when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); - when(mockContext.maxPoolSize()).thenReturn(50); - when(mockContext.minPoolSize()).thenReturn(2); + resetAndRestubContext(); + listener.onUCPEvent(EventType.POOL_DESTROYED, mockContext); verify(mockContext).maxPoolSize(); verify(mockContext).minPoolSize(); @@ -269,79 +366,128 @@ void testConfigFieldsNotReadOnConnectionBorrowed() { verify(mockContext, never()).minPoolSize(); } - @Test - @DisplayName("maxPoolSize() and minPoolSize() are NOT read on maintenance events") - void testConfigFieldsNotReadOnMaintenanceEvents() { - listener.onUCPEvent(EventType.POOL_REFRESHED, mockContext); - verify(mockContext, never()).maxPoolSize(); - verify(mockContext, never()).minPoolSize(); - - reset(mockContext); - when(mockContext.poolName()).thenReturn("TestPool"); - when(mockContext.borrowedConnectionsCount()).thenReturn(3); - when(mockContext.availableConnectionsCount()).thenReturn(5); - when(mockContext.createdConnections()).thenReturn(9); - when(mockContext.closedConnections()).thenReturn(1); - when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); - - listener.onUCPEvent(EventType.POOL_RECYCLED, mockContext); + @ParameterizedTest(name = "maxPoolSize() and minPoolSize() are NOT read on {0}") + @EnumSource(value = EventType.class, names = { + "POOL_REFRESHED", "POOL_RECYCLED", "POOL_PURGED" + }) + @DisplayName("maxPoolSize() and minPoolSize() are NOT read on any maintenance event") + void testConfigFieldsNotReadOnMaintenanceEvents(EventType type) { + listener.onUCPEvent(type, mockContext); verify(mockContext, never()).maxPoolSize(); verify(mockContext, never()).minPoolSize(); } + // --- Wait time ------------------------------------------------------------ + @Test - @DisplayName("getAverageConnectionWaitTime() is read on CONNECTION_BORROWED when > 0") - void testWaitTimeReadOnBorrowWhenPositive() { + @DisplayName("getAverageConnectionWaitTime() is read exactly once on CONNECTION_BORROWED when > 0") + void testWaitTimeReadOnceOnBorrowWhenPositive() { when(mockContext.getAverageConnectionWaitTime()).thenReturn(100L); listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); - verify(mockContext, atLeastOnce()).getAverageConnectionWaitTime(); + // Verify exactly once: the provider caches the value in a local variable + // to avoid a redundant second call inside the zero-guard branch. + verify(mockContext, times(1)).getAverageConnectionWaitTime(); } @Test - @DisplayName("getAverageConnectionWaitTime() is NOT recorded on CONNECTION_BORROWED when 0") - void testWaitTimeNotRecordedWhenZero() { - + @DisplayName("getAverageConnectionWaitTime() is read but recording is suppressed when 0") + void testWaitTimeReadButNotRecordedWhenZero() { when(mockContext.getAverageConnectionWaitTime()).thenReturn(0L); assertDoesNotThrow(() -> listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); + // The branch was entered (CONNECTION_BORROWED) — the method must have been + // called once to evaluate the zero guard. No exception means zero was + // correctly suppressed rather than passed to histogram.record(). + verify(mockContext, times(1)).getAverageConnectionWaitTime(); } - @Test - @DisplayName("getAverageConnectionWaitTime() is NOT read on non-borrow events") - void testWaitTimeNotReadOnNonBorrowEvents() { - listener.onUCPEvent(EventType.CONNECTION_RETURNED, mockContext); - verify(mockContext, never()).getAverageConnectionWaitTime(); - - reset(mockContext); - when(mockContext.poolName()).thenReturn("TestPool"); - when(mockContext.borrowedConnectionsCount()).thenReturn(3); - when(mockContext.availableConnectionsCount()).thenReturn(5); - when(mockContext.createdConnections()).thenReturn(9); - when(mockContext.closedConnections()).thenReturn(1); - - listener.onUCPEvent(EventType.POOL_REFRESHED, mockContext); + @ParameterizedTest(name = "getAverageConnectionWaitTime() is NOT read on {0}") + @EnumSource(value = EventType.class, names = { + "CONNECTION_CREATED", "CONNECTION_RETURNED", "CONNECTION_CLOSED", + "POOL_CREATED", "POOL_STARTING", "POOL_STARTED", "POOL_STOPPED", + "POOL_REFRESHED", "POOL_RECYCLED", "POOL_PURGED" + }) + @DisplayName("getAverageConnectionWaitTime() is NOT read on any non-borrow event") + void testWaitTimeNotReadOnNonBorrowEvents(EventType type) { + listener.onUCPEvent(type, mockContext); verify(mockContext, never()).getAverageConnectionWaitTime(); } + // --- Removed fields ------------------------------------------------------- + @Test - @DisplayName("totalConnections() is never read — removed from provider") + @DisplayName("totalConnections() is never read — not used by the provider") void testTotalConnectionsNeverRead() { for (EventType type : EventType.values()) { - reset(mockContext); - when(mockContext.poolName()).thenReturn("TestPool"); - when(mockContext.borrowedConnectionsCount()).thenReturn(3); - when(mockContext.availableConnectionsCount()).thenReturn(5); - when(mockContext.createdConnections()).thenReturn(9); - when(mockContext.closedConnections()).thenReturn(1); - when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); - when(mockContext.maxPoolSize()).thenReturn(50); - when(mockContext.minPoolSize()).thenReturn(2); + resetAndRestubContext(); listener.onUCPEvent(type, mockContext); verify(mockContext, never()).totalConnections(); } } } + + // --------------------------------------------------------------------------- + // Pool state lifecycle + // --------------------------------------------------------------------------- + + @Nested + @DisplayName("UCPEventListener — pool state lifecycle") + class ListenerPoolStateTests { + + private UCPEventListener listener; + + @BeforeEach + void setUp() { + listener = new OtelUCPEventListenerProvider() + .getListener(Collections.emptyMap()); + } + + @Test + @DisplayName("POOL_DESTROYED with no prior registration skips recordSnapshot gracefully") + void testPoolDestroyedWithoutPriorRegistration() { + // Fire POOL_DESTROYED on a pool that was never registered — the null-state + // guard inside onUCPEvent should prevent recordSnapshot from being called. + UCPEventContext unknownPool = mock(UCPEventContext.class); + when(unknownPool.poolName()).thenReturn("NeverRegisteredPool"); + + assertDoesNotThrow(() -> + listener.onUCPEvent(EventType.POOL_DESTROYED, unknownPool)); + + // recordSnapshot was skipped — no context fields should have been read. + verify(unknownPool, never()).borrowedConnectionsCount(); + verify(unknownPool, never()).maxPoolSize(); + } + + @Test + @DisplayName("Second POOL_DESTROYED on same pool after first is already cleaned up skips silently") + void testDoublePoolDestroyed() { + // First destroy: pool state exists and is removed, recordSnapshot is called. + listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + listener.onUCPEvent(EventType.POOL_DESTROYED, mockContext); + + // Second destroy: pool state is gone — poolStates.remove() returns null + // and recordSnapshot must be skipped entirely. + UCPEventContext secondDestroy = mock(UCPEventContext.class); + when(secondDestroy.poolName()).thenReturn("TestPool"); + assertDoesNotThrow(() -> + listener.onUCPEvent(EventType.POOL_DESTROYED, secondDestroy)); + verify(secondDestroy, never()).borrowedConnectionsCount(); + } + + @Test + @DisplayName("Listener throws NotSerializableException on serialization attempt") + void testListenerIsNotSerializable() { + assertThrows(NotSerializableException.class, () -> + new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(listener)); + } + } + + + // --------------------------------------------------------------------------- + // Multi-pool independence and concurrency + // --------------------------------------------------------------------------- + @Nested @DisplayName("UCPEventListener — multi-pool independence") class ListenerMultiPoolTests { @@ -357,25 +503,8 @@ void setUp() { @Test @DisplayName("Multiple pools emit events independently without interference") void testMultiplePoolsIndependent() { - UCPEventContext pool1 = mock(UCPEventContext.class); - when(pool1.poolName()).thenReturn("Pool1"); - when(pool1.borrowedConnectionsCount()).thenReturn(2); - when(pool1.availableConnectionsCount()).thenReturn(3); - when(pool1.getAverageConnectionWaitTime()).thenReturn(10L); - when(pool1.maxPoolSize()).thenReturn(10); - when(pool1.minPoolSize()).thenReturn(1); - when(pool1.createdConnections()).thenReturn(5); - when(pool1.closedConnections()).thenReturn(0); - - UCPEventContext pool2 = mock(UCPEventContext.class); - when(pool2.poolName()).thenReturn("Pool2"); - when(pool2.borrowedConnectionsCount()).thenReturn(7); - when(pool2.availableConnectionsCount()).thenReturn(1); - when(pool2.getAverageConnectionWaitTime()).thenReturn(50L); - when(pool2.maxPoolSize()).thenReturn(20); - when(pool2.minPoolSize()).thenReturn(2); - when(pool2.createdConnections()).thenReturn(8); - when(pool2.closedConnections()).thenReturn(1); + UCPEventContext pool1 = newFullyStubbed("Pool1"); + UCPEventContext pool2 = newFullyStubbed("Pool2"); assertDoesNotThrow(() -> { listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool1); @@ -383,6 +512,12 @@ void testMultiplePoolsIndependent() { listener.onUCPEvent(EventType.CONNECTION_RETURNED, pool1); listener.onUCPEvent(EventType.POOL_DESTROYED, pool2); }); + + // Each pool's context was accessed independently — poolName() is called + // exactly once per onUCPEvent() invocation (at the top of the method), + // so two events fired against a pool means two poolName() reads. + verify(pool1, atLeast(2)).poolName(); // CONNECTION_BORROWED + CONNECTION_RETURNED = 2 calls + verify(pool2, atLeast(2)).poolName(); // CONNECTION_BORROWED + POOL_DESTROYED = 2 calls } @Test @@ -394,5 +529,34 @@ void testEventsAfterPoolDestroyed() { listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); }); } + + @Test + @DisplayName("Concurrent events from multiple pools do not cause errors") + void testConcurrentMultiPoolEvents() throws Exception { + // Validates that ConcurrentHashMap's thread-safety holds under concurrent + // access from multiple pools firing events simultaneously on the singleton + // listener. Each thread owns a distinct pool name to ensure independent + // map entries without artificial serialisation. + int threads = 8; + ExecutorService exec = Executors.newFixedThreadPool(threads); + List> futures = new ArrayList<>(); + + for (int i = 0; i < threads; i++) { + UCPEventContext ctx = newFullyStubbed("ConcurrentPool-" + i); + futures.add(exec.submit(() -> { + listener.onUCPEvent(EventType.POOL_CREATED, ctx); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, ctx); + listener.onUCPEvent(EventType.CONNECTION_RETURNED, ctx); + listener.onUCPEvent(EventType.POOL_DESTROYED, ctx); + })); + } + + exec.shutdown(); + for (Future f : futures) { + // get(timeout) re-throws any exception thrown on the worker thread and + // also fails fast if a thread deadlocks rather than hanging indefinitely. + assertDoesNotThrow(() -> f.get(5, TimeUnit.SECONDS)); + } + } } } \ No newline at end of file From 552197180e18a07f1eb08a2c4a67e1a47d353f5c Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Tue, 31 Mar 2026 02:52:52 +0100 Subject: [PATCH 60/82] Unit Tests clean up --- .../JFRUCPEventListenerProviderTest.java | 12 +- .../OtelUCPEventListenerProviderTest.java | 121 +++--------------- 2 files changed, 27 insertions(+), 106 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java index 6d273e75..9bc8e064 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java @@ -105,7 +105,7 @@ void testIsDesiredEventReturnsTrueForAll() { UCPEventListener listener = provider.getListener(Collections.emptyMap()); for (EventType type : EventType.values()) { assertTrue(listener.isDesiredEvent(type), - "Expected isDesiredEvent to return true for: " + type); + "Expected isDesiredEvent to return true for: " + type); } } } @@ -119,14 +119,14 @@ class EventFactoryNullSafetyTests { @DisplayName("recordEvent() throws NullPointerException when EventType is null") void testRecordEventNullType() { assertThrows(NullPointerException.class, () -> - UCPEventFactory.recordEvent(null, mockContext)); + UCPEventFactory.recordEvent(null, mockContext)); } @Test @DisplayName("recordEvent() throws NullPointerException when context is null") void testRecordEventNullContext() { assertThrows(NullPointerException.class, () -> - UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, null)); + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, null)); } } @@ -157,7 +157,7 @@ void testRecordEventWithZeroValues() { when(zeroContext.getAverageConnectionWaitTime()).thenReturn(0L); assertDoesNotThrow(() -> - UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, zeroContext)); + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, zeroContext)); } @Test @@ -176,7 +176,7 @@ void testRecordEventWithNullPoolName() { when(nullPoolNameContext.getAverageConnectionWaitTime()).thenReturn(0L); assertDoesNotThrow(() -> - UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, nullPoolNameContext)); + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, nullPoolNameContext)); } } @@ -195,7 +195,7 @@ void testOnUCPEventDoesNotThrowForAllTypes(EventType type) { @ParameterizedTest(name = "onUCPEvent() reads all context fields for EventType.{0}") @EnumSource(value = EventType.class, names = { - "POOL_CREATED", "CONNECTION_BORROWED", "POOL_REFRESHED" + "POOL_CREATED", "CONNECTION_BORROWED", "POOL_REFRESHED" }) @DisplayName("onUCPEvent() reads all context fields for each event category") void testOnUCPEventReadsAllContextFields(EventType type) { diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java index 5a3f9c18..43a5e7f4 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java @@ -81,13 +81,6 @@ void setUpMockContext() { when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); } - /** - * Resets the shared mockContext and re-applies all standard stubs. - * - *

    Use this helper in tests that call reset() mid-test (e.g. after a - * POOL_DESTROYED call) and need to verify interaction counts on subsequent - * events without contamination from earlier invocations.

    - */ private void resetAndRestubContext() { reset(mockContext); when(mockContext.poolName()).thenReturn("TestPool"); @@ -100,10 +93,6 @@ private void resetAndRestubContext() { when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); } - /** - * Creates a fully-stubbed mock context for the given pool name. - * Used in multi-pool and concurrency tests to avoid shared state. - */ private UCPEventContext newFullyStubbed(String poolName) { UCPEventContext ctx = mock(UCPEventContext.class); when(ctx.poolName()).thenReturn(poolName); @@ -117,11 +106,6 @@ private UCPEventContext newFullyStubbed(String poolName) { return ctx; } - - // --------------------------------------------------------------------------- - // Provider contract - // --------------------------------------------------------------------------- - @Nested @DisplayName("OtelUCPEventListenerProvider") class ProviderTests { @@ -148,7 +132,6 @@ void testGetListenerNotNull() { @Test @DisplayName("getListener() returns a non-null listener when config is null") void testGetListenerWithNullConfig() { - // config is intentionally unused by the provider — null must be safe. assertNotNull(provider.getListener(null)); } @@ -166,16 +149,11 @@ void testIsDesiredEventReturnsTrueForAll() { UCPEventListener listener = provider.getListener(Collections.emptyMap()); for (EventType type : EventType.values()) { assertTrue(listener.isDesiredEvent(type), - "Expected isDesiredEvent to return true for: " + type); + "Expected isDesiredEvent to return true for: " + type); } } } - - // --------------------------------------------------------------------------- - // Null safety - // --------------------------------------------------------------------------- - @Nested @DisplayName("UCPEventListener — null safety") class ListenerNullSafetyTests { @@ -185,7 +163,7 @@ class ListenerNullSafetyTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @Test @@ -198,7 +176,7 @@ void testOnUCPEventNullEventType() { @DisplayName("onUCPEvent() does not throw when context is null") void testOnUCPEventNullContext() { assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, null)); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, null)); } @Test @@ -210,11 +188,9 @@ void testOnUCPEventBothNull() { @Test @DisplayName("onUCPEvent() does not throw and reads no fields when poolName() returns null") void testOnUCPEventNullPoolName() { - // The null-poolName guard returns immediately — no context fields beyond - // poolName() itself should be accessed after the early return. when(mockContext.poolName()).thenReturn(null); assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); verify(mockContext, never()).borrowedConnectionsCount(); verify(mockContext, never()).availableConnectionsCount(); @@ -223,11 +199,6 @@ void testOnUCPEventNullPoolName() { } } - - // --------------------------------------------------------------------------- - // Robustness — all event types - // --------------------------------------------------------------------------- - @Nested @DisplayName("UCPEventListener — robustness") class ListenerRobustnessTests { @@ -237,16 +208,9 @@ class ListenerRobustnessTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } - /** - * Verifies that onUCPEvent() does not throw for any EventType. - * - *

    The enum values are iterated in declaration order. POOL_DESTROYED - * removes the pool state entry — subsequent events simply re-create it - * via computeIfAbsent, so ordering has no correctness impact here.

    - */ @ParameterizedTest(name = "onUCPEvent() does not throw for EventType.{0}") @EnumSource(EventType.class) @DisplayName("onUCPEvent() does not throw for any known EventType") @@ -268,15 +232,10 @@ void testOnUCPEventWithZeroValues() { when(zeroContext.minPoolSize()).thenReturn(0); assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, zeroContext)); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, zeroContext)); } } - - // --------------------------------------------------------------------------- - // Context field access — what gets read, when - // --------------------------------------------------------------------------- - @Nested @DisplayName("UCPEventListener — context field access") class ListenerContextAccessTests { @@ -286,7 +245,7 @@ class ListenerContextAccessTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @Test @@ -299,9 +258,6 @@ void testPoolNameReadExactlyOnce() { @Test @DisplayName("borrowedConnectionsCount() and availableConnectionsCount() are read on a non-lifecycle event") void testUsageFieldsReadOnNonLifecycleEvent() { - // "Every non-POOL_DESTROYED event" — CONNECTION_RETURNED is representative. - // POOL_DESTROYED is excluded because its snapshot is conditional on pool - // state existing; this test focuses on the unconditional snapshot path. listener.onUCPEvent(EventType.CONNECTION_RETURNED, mockContext); verify(mockContext).borrowedConnectionsCount(); verify(mockContext).availableConnectionsCount(); @@ -331,11 +287,9 @@ void testLifetimeFieldsReadOnMaintenanceEvent() { verify(mockContext).closedConnections(); } - // --- Config fields (maxPoolSize / minPoolSize) — lifecycle events only ---- - @ParameterizedTest(name = "maxPoolSize() and minPoolSize() are read on {0}") @EnumSource(value = EventType.class, names = { - "POOL_CREATED", "POOL_STARTING", "POOL_STARTED", "POOL_STOPPED" + "POOL_CREATED", "POOL_STARTING", "POOL_STARTED", "POOL_STOPPED" }) @DisplayName("maxPoolSize() and minPoolSize() are read on all non-destroy lifecycle events") void testConfigFieldsReadOnLifecycleEvents(EventType type) { @@ -347,9 +301,6 @@ void testConfigFieldsReadOnLifecycleEvents(EventType type) { @Test @DisplayName("maxPoolSize() and minPoolSize() are read on POOL_DESTROYED") void testConfigFieldsReadOnPoolDestroyed() { - // POOL_DESTROYED removes the poolStates entry before calling recordSnapshot. - // The entry must exist first — seed it via POOL_CREATED so that the remove - // finds a non-null PoolState and recordSnapshot is actually invoked. listener.onUCPEvent(EventType.POOL_CREATED, mockContext); resetAndRestubContext(); @@ -368,7 +319,7 @@ void testConfigFieldsNotReadOnConnectionBorrowed() { @ParameterizedTest(name = "maxPoolSize() and minPoolSize() are NOT read on {0}") @EnumSource(value = EventType.class, names = { - "POOL_REFRESHED", "POOL_RECYCLED", "POOL_PURGED" + "POOL_REFRESHED", "POOL_RECYCLED", "POOL_PURGED" }) @DisplayName("maxPoolSize() and minPoolSize() are NOT read on any maintenance event") void testConfigFieldsNotReadOnMaintenanceEvents(EventType type) { @@ -377,15 +328,11 @@ void testConfigFieldsNotReadOnMaintenanceEvents(EventType type) { verify(mockContext, never()).minPoolSize(); } - // --- Wait time ------------------------------------------------------------ - @Test @DisplayName("getAverageConnectionWaitTime() is read exactly once on CONNECTION_BORROWED when > 0") void testWaitTimeReadOnceOnBorrowWhenPositive() { when(mockContext.getAverageConnectionWaitTime()).thenReturn(100L); listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext); - // Verify exactly once: the provider caches the value in a local variable - // to avoid a redundant second call inside the zero-guard branch. verify(mockContext, times(1)).getAverageConnectionWaitTime(); } @@ -394,18 +341,16 @@ void testWaitTimeReadOnceOnBorrowWhenPositive() { void testWaitTimeReadButNotRecordedWhenZero() { when(mockContext.getAverageConnectionWaitTime()).thenReturn(0L); assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); - // The branch was entered (CONNECTION_BORROWED) — the method must have been - // called once to evaluate the zero guard. No exception means zero was - // correctly suppressed rather than passed to histogram.record(). + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); + verify(mockContext, times(1)).getAverageConnectionWaitTime(); } @ParameterizedTest(name = "getAverageConnectionWaitTime() is NOT read on {0}") @EnumSource(value = EventType.class, names = { - "CONNECTION_CREATED", "CONNECTION_RETURNED", "CONNECTION_CLOSED", - "POOL_CREATED", "POOL_STARTING", "POOL_STARTED", "POOL_STOPPED", - "POOL_REFRESHED", "POOL_RECYCLED", "POOL_PURGED" + "CONNECTION_CREATED", "CONNECTION_RETURNED", "CONNECTION_CLOSED", + "POOL_CREATED", "POOL_STARTING", "POOL_STARTED", "POOL_STOPPED", + "POOL_REFRESHED", "POOL_RECYCLED", "POOL_PURGED" }) @DisplayName("getAverageConnectionWaitTime() is NOT read on any non-borrow event") void testWaitTimeNotReadOnNonBorrowEvents(EventType type) { @@ -413,7 +358,6 @@ void testWaitTimeNotReadOnNonBorrowEvents(EventType type) { verify(mockContext, never()).getAverageConnectionWaitTime(); } - // --- Removed fields ------------------------------------------------------- @Test @DisplayName("totalConnections() is never read — not used by the provider") @@ -426,11 +370,6 @@ void testTotalConnectionsNeverRead() { } } - - // --------------------------------------------------------------------------- - // Pool state lifecycle - // --------------------------------------------------------------------------- - @Nested @DisplayName("UCPEventListener — pool state lifecycle") class ListenerPoolStateTests { @@ -440,21 +379,19 @@ class ListenerPoolStateTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @Test @DisplayName("POOL_DESTROYED with no prior registration skips recordSnapshot gracefully") void testPoolDestroyedWithoutPriorRegistration() { - // Fire POOL_DESTROYED on a pool that was never registered — the null-state - // guard inside onUCPEvent should prevent recordSnapshot from being called. + UCPEventContext unknownPool = mock(UCPEventContext.class); when(unknownPool.poolName()).thenReturn("NeverRegisteredPool"); assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.POOL_DESTROYED, unknownPool)); + listener.onUCPEvent(EventType.POOL_DESTROYED, unknownPool)); - // recordSnapshot was skipped — no context fields should have been read. verify(unknownPool, never()).borrowedConnectionsCount(); verify(unknownPool, never()).maxPoolSize(); } @@ -462,16 +399,13 @@ void testPoolDestroyedWithoutPriorRegistration() { @Test @DisplayName("Second POOL_DESTROYED on same pool after first is already cleaned up skips silently") void testDoublePoolDestroyed() { - // First destroy: pool state exists and is removed, recordSnapshot is called. listener.onUCPEvent(EventType.POOL_CREATED, mockContext); listener.onUCPEvent(EventType.POOL_DESTROYED, mockContext); - // Second destroy: pool state is gone — poolStates.remove() returns null - // and recordSnapshot must be skipped entirely. UCPEventContext secondDestroy = mock(UCPEventContext.class); when(secondDestroy.poolName()).thenReturn("TestPool"); assertDoesNotThrow(() -> - listener.onUCPEvent(EventType.POOL_DESTROYED, secondDestroy)); + listener.onUCPEvent(EventType.POOL_DESTROYED, secondDestroy)); verify(secondDestroy, never()).borrowedConnectionsCount(); } @@ -479,15 +413,10 @@ void testDoublePoolDestroyed() { @DisplayName("Listener throws NotSerializableException on serialization attempt") void testListenerIsNotSerializable() { assertThrows(NotSerializableException.class, () -> - new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(listener)); + new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(listener)); } } - - // --------------------------------------------------------------------------- - // Multi-pool independence and concurrency - // --------------------------------------------------------------------------- - @Nested @DisplayName("UCPEventListener — multi-pool independence") class ListenerMultiPoolTests { @@ -497,7 +426,7 @@ class ListenerMultiPoolTests { @BeforeEach void setUp() { listener = new OtelUCPEventListenerProvider() - .getListener(Collections.emptyMap()); + .getListener(Collections.emptyMap()); } @Test @@ -513,9 +442,6 @@ void testMultiplePoolsIndependent() { listener.onUCPEvent(EventType.POOL_DESTROYED, pool2); }); - // Each pool's context was accessed independently — poolName() is called - // exactly once per onUCPEvent() invocation (at the top of the method), - // so two events fired against a pool means two poolName() reads. verify(pool1, atLeast(2)).poolName(); // CONNECTION_BORROWED + CONNECTION_RETURNED = 2 calls verify(pool2, atLeast(2)).poolName(); // CONNECTION_BORROWED + POOL_DESTROYED = 2 calls } @@ -533,10 +459,7 @@ void testEventsAfterPoolDestroyed() { @Test @DisplayName("Concurrent events from multiple pools do not cause errors") void testConcurrentMultiPoolEvents() throws Exception { - // Validates that ConcurrentHashMap's thread-safety holds under concurrent - // access from multiple pools firing events simultaneously on the singleton - // listener. Each thread owns a distinct pool name to ensure independent - // map entries without artificial serialisation. + int threads = 8; ExecutorService exec = Executors.newFixedThreadPool(threads); List> futures = new ArrayList<>(); @@ -553,8 +476,6 @@ void testConcurrentMultiPoolEvents() throws Exception { exec.shutdown(); for (Future f : futures) { - // get(timeout) re-throws any exception thrown on the worker thread and - // also fails fast if a thread deadlocks rather than hanging indefinitely. assertDoesNotThrow(() -> f.get(5, TimeUnit.SECONDS)); } } From 3170566fb893cf7e913c4b8d1ea67c22fb38e506 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Wed, 1 Apr 2026 06:57:01 +0100 Subject: [PATCH 61/82] Clean up the code --- .../jfr/core/JFRUCPEventListenerProvider.java | 16 ++--- .../jfr/core/UCPEventFactory.java | 8 +-- .../otel/OtelUCPEventListenerProvider.java | 62 +++++++++---------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java index fe895dd5..738123f7 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java @@ -49,7 +49,7 @@ * Integrates UCP events with Java Flight Recorder for low-overhead monitoring. */ public final class JFRUCPEventListenerProvider - implements UCPEventListenerProvider { + implements UCPEventListenerProvider { private final UCPEventListener listener; @@ -59,14 +59,14 @@ public final class JFRUCPEventListenerProvider * Thread-safe and optimized for minimal overhead. */ public static final UCPEventListener TRACE_EVENT_LISTENER = - new UCPEventListener() { - private static final long serialVersionUID = 1L; + new UCPEventListener() { + private static final long serialVersionUID = 1L; - @Override - public void onUCPEvent(EventType eventType, UCPEventContext context) { - UCPEventFactory.recordEvent(eventType, context); - } - }; + @Override + public void onUCPEvent(EventType eventType, UCPEventContext context) { + UCPEventFactory.recordEvent(eventType, context); + } + }; /** * Creates a new provider instance. diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java index 0b5a9752..045b13af 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java @@ -58,7 +58,7 @@ public final class UCPEventFactory { private UCPEventFactory() {} private static final Logger LOGGER = - Logger.getLogger(UCPEventFactory.class.getName()); + Logger.getLogger(UCPEventFactory.class.getName()); /** * Creates a JFR event instance for the specified UCP event type. @@ -69,7 +69,7 @@ private UCPEventFactory() {} * @throws NullPointerException if parameters are null */ static Event createEvent( - UCPEventListener.EventType type, UCPEventContext ctx) { + UCPEventListener.EventType type, UCPEventContext ctx) { Objects.requireNonNull(type, "EventType cannot be null"); Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); @@ -94,7 +94,7 @@ static Event createEvent( default: LOGGER.fine(() -> - "Unrecognized UCP EventType ignored by JFR provider: " + type); + "Unrecognized UCP EventType ignored by JFR provider: " + type); return null; } } @@ -108,7 +108,7 @@ static Event createEvent( * @throws NullPointerException if parameters are null */ public static void recordEvent( - UCPEventListener.EventType type, UCPEventContext ctx) { + UCPEventListener.EventType type, UCPEventContext ctx) { Objects.requireNonNull(type, "EventType cannot be null"); Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java index fae54d2a..f9c2d979 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -174,7 +174,7 @@ private static final class OtelUCPEventListener implements UCPEventListener { // cohesive block. The Meter object itself is lightweight (no pooled resources), // so the marginal GC cost of holding the reference is negligible. private final Meter meter = - GlobalOpenTelemetry.getMeter("oracle.ucp"); + GlobalOpenTelemetry.getMeter("oracle.ucp"); // Spec-aligned: db.client.connection.usage (LongGauge, state=used|idle) // Spec says UpDownCounter named db.client.connection.count. We deviate on @@ -185,9 +185,9 @@ private static final class OtelUCPEventListener implements UCPEventListener { // drift risk. A single instrument is registered; the state attribute // differentiates used vs idle at the data level. private final LongGauge connectionUsage = - meter.gaugeBuilder("db.client.connection.usage") - .setDescription("The number of connections that are currently in the state described by the state attribute.") - .setUnit("{connection}").ofLongs().build(); + meter.gaugeBuilder("db.client.connection.usage") + .setDescription("The number of connections that are currently in the state described by the state attribute.") + .setUnit("{connection}").ofLongs().build(); // Spec-aligned: db.client.connection.max (LongGauge) // Spec says UpDownCounter, but maxPoolSize is a configuration value set at @@ -195,9 +195,9 @@ private static final class OtelUCPEventListener implements UCPEventListener { // value directly, which is semantically correct for a configured constant. // Recorded only on pool lifecycle events to avoid per-connection overhead. private final LongGauge connectionMax = - meter.gaugeBuilder("db.client.connection.max") - .setDescription("The maximum number of open connections allowed.") - .setUnit("{connection}").ofLongs().build(); + meter.gaugeBuilder("db.client.connection.max") + .setDescription("The maximum number of open connections allowed.") + .setUnit("{connection}").ofLongs().build(); // Spec-aligned: db.client.connection.idle.min (LongGauge) // Same reasoning as db.client.connection.max above. @@ -205,13 +205,13 @@ private static final class OtelUCPEventListener implements UCPEventListener { // idle floor. See class-level Javadoc for the full limitation note. // Recorded only on pool lifecycle events to avoid per-connection overhead. private final LongGauge connectionIdleMin = - meter.gaugeBuilder("db.client.connection.idle.min") - .setDescription( - "Approximation of the minimum number of idle open connections allowed. " + - "Sourced from UCP's minPoolSize (minimum total pool size), which is the " + - "closest value UCP's event API exposes. May differ from true idle minimum " + - "when connections are actively borrowed.") - .setUnit("{connection}").ofLongs().build(); + meter.gaugeBuilder("db.client.connection.idle.min") + .setDescription( + "Approximation of the minimum number of idle open connections allowed. " + + "Sourced from UCP's minPoolSize (minimum total pool size), which is the " + + "closest value UCP's event API exposes. May differ from true idle minimum " + + "when connections are actively borrowed.") + .setUnit("{connection}").ofLongs().build(); // Spec-aligned: db.client.connection.wait_time (DoubleHistogram, unit: s) // DoubleHistogram (not Long) because fractional seconds matter: @@ -221,11 +221,11 @@ private static final class OtelUCPEventListener implements UCPEventListener { // UCP value is in ms — divided by 1000.0 before recording. // See class-level Javadoc for the full limitation note. private final DoubleHistogram waitTime = - meter.histogramBuilder("db.client.connection.wait_time") - .setDescription( - "Approximation of borrow wait time based on UCP's pool-wide cumulative " + - "average. Recorded on CONNECTION_BORROWED events only when wait time > 0.") - .setUnit("s").build(); + meter.histogramBuilder("db.client.connection.wait_time") + .setDescription( + "Approximation of borrow wait time based on UCP's pool-wide cumulative " + + "average. Recorded on CONNECTION_BORROWED events only when wait time > 0.") + .setUnit("s").build(); // UCP-specific: db.client.connection.established (LongGauge) // Cumulative count of physical connections ever opened since pool start. @@ -235,16 +235,16 @@ private static final class OtelUCPEventListener implements UCPEventListener { // with delta=0 produces no data point at all, making the metric invisible. // LongGauge.set() always records the current value, even when it is 0. private final LongGauge connectionEstablished = - meter.gaugeBuilder("db.client.connection.established") - .setDescription("Cumulative number of physical connections opened since pool start.") - .setUnit("{connection}").ofLongs().build(); + meter.gaugeBuilder("db.client.connection.established") + .setDescription("Cumulative number of physical connections opened since pool start.") + .setUnit("{connection}").ofLongs().build(); // UCP-specific: db.client.connection.closed (LongGauge) // Same reasoning as db.client.connection.established above. private final LongGauge connectionClosed = - meter.gaugeBuilder("db.client.connection.closed") - .setDescription("Cumulative number of physical connections closed since pool start.") - .setUnit("{connection}").ofLongs().build(); + meter.gaugeBuilder("db.client.connection.closed") + .setDescription("Cumulative number of physical connections closed since pool start.") + .setUnit("{connection}").ofLongs().build(); // ------------------------------------------------------------------------- // Per-pool state — holds pre-built Attributes objects reused on every event @@ -262,8 +262,8 @@ private static final class PoolState { final Attributes attrsIdle; PoolState(String poolName, - AttributeKey poolNameKey, - AttributeKey stateKey) { + AttributeKey poolNameKey, + AttributeKey stateKey) { this.attrs = Attributes.of(poolNameKey, poolName); this.attrsUsed = Attributes.of(poolNameKey, poolName, stateKey, "used"); this.attrsIdle = Attributes.of(poolNameKey, poolName, stateKey, "idle"); @@ -271,7 +271,7 @@ private static final class PoolState { } private final ConcurrentHashMap poolStates = - new ConcurrentHashMap<>(); + new ConcurrentHashMap<>(); // ------------------------------------------------------------------------- // Serialization guard @@ -283,8 +283,8 @@ private static final class PoolState { private void writeObject(ObjectOutputStream ignored) throws java.io.IOException { throw new NotSerializableException( - OtelUCPEventListener.class.getName() + - ": OTel instrument fields are not serializable."); + OtelUCPEventListener.class.getName() + + ": OTel instrument fields are not serializable."); } // ------------------------------------------------------------------------- @@ -346,7 +346,7 @@ public void onUCPEvent(EventType eventType, UCPEventContext ctx) { } PoolState state = poolStates.computeIfAbsent( - poolName, k -> new PoolState(k, POOL_NAME, STATE)); + poolName, k -> new PoolState(k, POOL_NAME, STATE)); recordSnapshot(eventType, ctx, state); } From 72cc39fc048e74d2e04a2544bb01f5d82d2dbc9e Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Mon, 6 Apr 2026 12:40:35 +0100 Subject: [PATCH 62/82] update copyright year to 2026 and replace inline FQNs with imports --- .../observability/jfr/core/JFRUCPEventListenerProvider.java | 2 +- .../ucp/provider/observability/jfr/core/UCPBaseEvent.java | 2 +- .../ucp/provider/observability/jfr/core/UCPEventFactory.java | 2 +- .../jfr/events/connection/ConnectionBorrowedEvent.java | 2 +- .../jfr/events/connection/ConnectionClosedEvent.java | 2 +- .../jfr/events/connection/ConnectionCreatedEvent.java | 2 +- .../jfr/events/connection/ConnectionReturnedEvent.java | 2 +- .../observability/jfr/events/lifecycle/PoolCreatedEvent.java | 2 +- .../jfr/events/lifecycle/PoolDestroyedEvent.java | 2 +- .../observability/jfr/events/lifecycle/PoolStartedEvent.java | 2 +- .../jfr/events/lifecycle/PoolStartingEvent.java | 2 +- .../observability/jfr/events/lifecycle/PoolStoppedEvent.java | 2 +- .../jfr/events/maintenance/PoolPurgedEvent.java | 2 +- .../jfr/events/maintenance/PoolRecycledEvent.java | 2 +- .../jfr/events/maintenance/PoolRefreshedEvent.java | 2 +- .../observability/otel/OtelUCPEventListenerProvider.java | 5 +++-- .../observability/JFRUCPEventListenerProviderTest.java | 2 +- .../observability/OtelUCPEventListenerProviderTest.java | 2 +- 18 files changed, 20 insertions(+), 19 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java index 738123f7..a899df0c 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java index 6598a2d9..a994bb98 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java index 045b13af..af541fff 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java index 93da79b0..622e9fb1 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java index 027666ba..52059252 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java index 5e6bdc2e..089b6631 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java index e009f28b..f760f670 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java index 2713510c..9c9ff667 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java index 694b246c..2cbfe7d6 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java index 9a5dbd6c..a79c471c 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java index 89fd40d4..5cafebcf 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java index c14b0470..83b0bf18 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java index ef768099..21bb6f6b 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java index 1151cdfc..6e609c64 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java index bca837a5..f288759f 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java index f9c2d979..792d7552 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** @@ -46,6 +46,7 @@ import oracle.ucp.events.core.UCPEventListener; import oracle.ucp.events.core.UCPEventListenerProvider; +import java.io.IOException; import java.io.NotSerializableException; import java.io.ObjectOutputStream; import java.util.Map; @@ -281,7 +282,7 @@ private static final class PoolState { // serialization rather than letting it fail with an opaque stack trace. // ------------------------------------------------------------------------- - private void writeObject(ObjectOutputStream ignored) throws java.io.IOException { + private void writeObject(ObjectOutputStream ignored) throws IOException { throw new NotSerializableException( OtelUCPEventListener.class.getName() + ": OTel instrument fields are not serializable."); diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java index 9bc8e064..8df5d710 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** diff --git a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java index 43a5e7f4..65147bd3 100644 --- a/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java @@ -1,5 +1,5 @@ /* - ** Copyright (c) 2025 Oracle and/or its affiliates. + ** Copyright (c) 2026 Oracle and/or its affiliates. ** ** The Universal Permissive License (UPL), Version 1.0 ** From bb4f6b2f6dfc84505b62c03000ec7073633d2072 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Mon, 6 Apr 2026 12:56:09 +0100 Subject: [PATCH 63/82] clean up requirements table in README --- ojdbc-provider-ucp-observability/README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md index 7425b843..c52f1e3c 100644 --- a/ojdbc-provider-ucp-observability/README.md +++ b/ojdbc-provider-ucp-observability/README.md @@ -355,15 +355,13 @@ Both providers handle all twelve event types defined by `UCPEventListener.EventT | Requirement | Minimum version | |---|---| -| Oracle JDBC driver | 23.26.0.0.0 | | Oracle UCP | 23.26.1.0.0 | -| Java (compile) | 11 | -| Java (runtime) | 11+ | +| Java | 11+ | | OpenTelemetry API *(OTel provider only)* | 1.44.1 | -The JFR provider has no runtime dependencies beyond the Oracle UCP jar and a JDK that -supports JFR (JDK 11+). The OpenTelemetry provider requires `opentelemetry-api` on the -classpath; the SDK and exporter are the application's responsibility. +The JFR provider has no dependencies beyond the Oracle UCP jar and a JDK that supports JFR +(JDK 11+). The OpenTelemetry provider requires `opentelemetry-api` on the classpath; the +SDK and exporter are the application's responsibility. --- From 7d4d7e84004cab4f3ab939fec1d352459e47c9b2 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Mon, 6 Apr 2026 14:14:30 +0100 Subject: [PATCH 64/82] update README activation, event descriptions, and field definitions --- ojdbc-provider-ucp-observability/README.md | 329 +++++++-------------- 1 file changed, 105 insertions(+), 224 deletions(-) diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md index c52f1e3c..c508b127 100644 --- a/ojdbc-provider-ucp-observability/README.md +++ b/ojdbc-provider-ucp-observability/README.md @@ -1,15 +1,13 @@ # Oracle JDBC UCP Observability Providers -Implementations of the `UCPEventListenerProvider` SPI that expose Oracle Universal Connection -Pool (UCP) telemetry through two industry-standard observability backends: +Implementations of the `UCPEventListenerProvider` SPI that expose Oracle UCP telemetry through two observability backends: | Provider | Listener name | Backend | |---|---|---| | `JFRUCPEventListenerProvider` | `jfr-ucp-listener` | Java Flight Recorder (JFR) | | `OtelUCPEventListenerProvider` | `otel-ucp-listener` | OpenTelemetry (metrics) | -Both providers are registered automatically via `java.util.ServiceLoader` — no code changes -are required in the application. +Both providers are registered automatically via `java.util.ServiceLoader` — no code changes required. --- @@ -18,13 +16,7 @@ are required in the application. - [Installation](#installation) - [Activation](#activation) - [JFR Provider](#jfr-provider) - - [Recorded event types](#recorded-event-types) - - [Enabling a JFR recording](#enabling-a-jfr-recording) - - [Analysing events in JDK Mission Control](#analysing-events-in-jdk-mission-control) - [OpenTelemetry Provider](#opentelemetry-provider) - - [SDK initialisation requirement](#sdk-initialisation-requirement) - - [Exported metrics](#exported-metrics) - - [Prometheus / Grafana quick-start](#prometheus--grafana-quick-start) - [Supported UCP event types](#supported-ucp-event-types) - [Requirements](#requirements) @@ -32,8 +24,6 @@ are required in the application. ## Installation -Add the module to your Maven project: - ```xml com.oracle.database.jdbc @@ -46,308 +36,203 @@ Add the module to your Maven project: ## Activation -Each provider is activated by registering its listener name on the UCP data source **before** -the pool is started. +Two ways to activate a provider, in priority order: -### Programmatic activation +**1. Pool-level property** (highest priority) — set directly on the data source before the pool starts: ```java -public class MyApp { - public static void main(String[] args) throws Exception { - PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); - pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource"); - pds.setURL("jdbc:oracle:thin:@//host:1521/service"); - - // Activate your provider - pds.setUCPEventListenerProvider("jfr-ucp-listener"); - // pds.setUCPEventListenerProvider("otel-ucp-listener"); - } -} +pds.setUCPEventListenerProvider("jfr-ucp-listener"); +// pds.setUCPEventListenerProvider("otel-ucp-listener"); ``` +**2. JVM system property** — applies globally to all pools in the JVM: + +```bash +java -DUCPEventListenerProvider=jfr-ucp-listener -jar myapp.jar +``` + +Or programmatically: + +```java +System.setProperty("UCPEventListenerProvider", "jfr-ucp-listener"); +``` + +If no provider is configured, a no-op provider is used automatically with zero overhead. + --- ## JFR Provider -`JFRUCPEventListenerProvider` converts every UCP pool and connection event into a committed -JFR event. Events are emitted with zero overhead when no JFR recording is active, making the -provider safe to ship in production at all times. +Converts every UCP pool and connection event into a committed JFR event. Zero overhead when no recording is active. ### Recorded event types -All events live under the `ucp` JFR category. - #### Pool lifecycle | JFR event class | Fired when | |---|---| -| `PoolCreatedEvent` | A new UCP pool instance is created | -| `PoolStartingEvent` | Pool initialisation begins | -| `PoolStartedEvent` | Pool is fully started and ready | -| `PoolStoppedEvent` | Pool has been stopped | -| `PoolDestroyedEvent` | Pool instance is destroyed | +| `PoolCreatedEvent` | Pool is successfully created | +| `PoolStartingEvent` | Pool startup begins | +| `PoolStartedEvent` | Pool is successfully started | +| `PoolStoppedEvent` | Pool is successfully stopped | +| `PoolDestroyedEvent` | Pool is successfully destroyed | #### Connection lifecycle | JFR event class | Fired when | |---|---| -| `ConnectionCreatedEvent` | A physical connection is opened to the database | -| `ConnectionBorrowedEvent` | A connection is checked out by the application | -| `ConnectionReturnedEvent` | A connection is returned to the pool | -| `ConnectionClosedEvent` | A physical connection is closed | +| `ConnectionCreatedEvent` | New connection is created | +| `ConnectionBorrowedEvent` | Connection is borrowed from pool | +| `ConnectionReturnedEvent` | Connection is returned to pool | +| `ConnectionClosedEvent` | Connection is closed | -#### Maintenance operations +#### Maintenance | JFR event class | Fired when | |---|---| -| `PoolRefreshedEvent` | The pool replaces all connections with fresh ones | -| `PoolRecycledEvent` | The pool recycles stale connections | -| `PoolPurgedEvent` | All connections are forcibly removed from the pool | +| `PoolRefreshedEvent` | Refresh completes | +| `PoolRecycledEvent` | Recycle completes | +| `PoolPurgedEvent` | Purge completes | ### Fields recorded on every event | Field | Type | Description | |---|---|---| -| `ucpTimestamp` | `long` | Epoch millisecond timestamp from the UCP event context | -| `poolName` | `String` | Name of the UCP pool that fired the event | -| `maxPoolSize` | `int` | Maximum pool capacity at event time | -| `minPoolSize` | `int` | Minimum pool capacity at event time | -| `borrowedConnectionsCount` | `int` | Checked-out connections at event time | -| `availableConnectionsCount` | `int` | Idle connections at event time | -| `totalConnections` | `int` | Total physical connections at event time | -| `createdConnections` | `long` | Cumulative connections created since pool start | -| `closedConnections` | `long` | Cumulative connections closed since pool start | -| `averageConnectionWaitTime` | `long` | Average borrow wait time in milliseconds | +| `ucpTimestamp` | `long` | Event occurrence time in milliseconds since epoch | +| `poolName` | `String` | Unique identifier for the pool instance | +| `maxPoolSize` | `int` | Maximum allowed connections | +| `minPoolSize` | `int` | Minimum maintained connections | +| `borrowedConnectionsCount` | `int` | Currently checked-out connections | +| `availableConnectionsCount` | `int` | Ready-to-use connections | +| `totalConnections` | `int` | Current active connections (borrowed + available) | +| `createdConnections` | `long` | Total connections ever created | +| `closedConnections` | `long` | Total connections closed | +| `averageConnectionWaitTime` | `long` | Average milliseconds a thread waited to obtain a connection | ### Enabling a JFR recording -**At JVM start** — capture everything from pool creation: +**At JVM start:** ```bash -java \ - -XX:StartFlightRecording=filename=ucp.jfr,settings=profile \ - -jar myapp.jar +java -XX:StartFlightRecording=filename=ucp.jfr,settings=profile -jar myapp.jar ``` -**At runtime via `jcmd`** — attach to a running process: +**At runtime via `jcmd`:** ```bash -# Start recording jcmd JFR.start name=ucp settings=profile - -# Dump to file jcmd JFR.dump name=ucp filename=ucp.jfr - -# Stop jcmd JFR.stop name=ucp ``` -To limit recording to UCP events only and reduce file size, create a custom settings file -and enable only the `ucp.*` event namespace: - -```bash -jcmd JFR.start name=ucp settings=ucp-custom.jfc -``` - -Where `ucp-custom.jfc` enables `true`. - ### Analysing events in JDK Mission Control -Open the `.jfr` file in [JDK Mission Control (JMC)](https://www.oracle.com/java/technologies/jdk-mission-control.html). -UCP events appear under **Event Browser → ucp** and can be correlated with GC pauses, -thread activity, and I/O latency on the same timeline. +Open the `.jfr` file in [JDK Mission Control](https://www.oracle.com/java/technologies/jdk-mission-control.html). UCP events appear under **Event Browser → ucp**. --- ## OpenTelemetry Provider -`OtelUCPEventListenerProvider` publishes UCP pool and connection metrics through the -[OpenTelemetry API](https://opentelemetry.io/docs/languages/java/). The provider is -**event-driven**: every time a UCP event fires, the snapshot data it carries is recorded -directly into OTel instruments. There are no background threads, no polling, and no cached -state — each event is self-contained. - -The provider depends only on `opentelemetry-api` — it does **not** pull in the SDK or any -exporter. The application is responsible for initialising an OpenTelemetry SDK and -registering it with `GlobalOpenTelemetry` **before** the pool is started. +Publishes UCP metrics through the [OpenTelemetry API](https://opentelemetry.io/docs/languages/java/). Event-driven — no background threads or polling. Depends only on `opentelemetry-api`; the SDK and exporter are the application's responsibility. -### SDK initialisation requirement +### SDK initialisation -The provider obtains its `Meter` on first listener instantiation using -`GlobalOpenTelemetry.getMeter("oracle.ucp")`. The SDK must therefore be registered before -the pool is started. A minimal setup using the Prometheus exporter: +The SDK must be registered with `GlobalOpenTelemetry` **before** the pool is started. Example using the Prometheus exporter: ```java -public class MyApp { - public static void main(String[] args) throws Exception { - // 1. Register the SDK BEFORE pool creation - PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder() - .setPort(9464) - .build(); - - SdkMeterProvider meterProvider = SdkMeterProvider.builder() - .registerMetricReader(prometheusServer) - .build(); - - OpenTelemetrySdk.builder() - .setMeterProvider(meterProvider) - .buildAndRegisterGlobal(); - - // 2. Then create and start the pool - PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); - pds.setUCPEventListenerProvider("otel-ucp-listener"); - } -} -``` +PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder().setPort(9464).build(); -For applications that already configure an OpenTelemetry SDK (e.g. via the Java agent or -Spring Boot auto-configuration) no additional setup is required. +SdkMeterProvider meterProvider = SdkMeterProvider.builder() + .registerMetricReader(prometheusServer) + .build(); -### Exported metrics +OpenTelemetrySdk.builder() + .setMeterProvider(meterProvider) + .buildAndRegisterGlobal(); + +// Then create and start the pool +pds.setUCPEventListenerProvider("otel-ucp-listener"); +``` -All spec-aligned metrics use the `db.client.connection` prefix following -[OpenTelemetry semantic conventions for database clients](https://opentelemetry.io/docs/specs/semconv/database/database-metrics/). +### Exported metrics -#### Spec-aligned metrics +#### Spec-aligned (`db.client.connection` prefix) | Metric name | Instrument | Unit | Description | |---|---|---|---| -| `db.client.connection.usage` | LongGauge | `{connection}` | Current connections in each state (attribute: `db.client.connection.state` = `used` \| `idle`). Recorded on every event. | -| `db.client.connection.max` | LongGauge | `{connection}` | Configured maximum pool size. Recorded on pool lifecycle events only. | -| `db.client.connection.idle.min` | LongGauge | `{connection}` | Configured minimum pool size (approximation — see Limitations). Recorded on pool lifecycle events only. | -| `db.client.connection.wait_time` | DoubleHistogram | `s` | Borrow wait time approximation (see Limitations). Recorded on `CONNECTION_BORROWED` only, when > 0. | - -> **Note on spec deviations:** -> The spec defines `db.client.connection.count` as an `UpDownCounter`. We use the name -> `db.client.connection.usage` (to avoid the reserved `_count` Prometheus suffix) and -> `LongGauge` (because UCP provides absolute snapshots, not incremental deltas). -> The spec defines `db.client.connection.max` and `db.client.connection.idle.min` as -> `UpDownCounter`. We use `LongGauge` for the same reason — these are configuration -> constants, not incrementally updated values. - -#### UCP-specific metrics +| `db.client.connection.usage` | LongGauge | `{connection}` | Connections per state (`used` / `idle`). Recorded on every event. | +| `db.client.connection.max` | LongGauge | `{connection}` | Configured maximum pool size. | +| `db.client.connection.idle.min` | LongGauge | `{connection}` | Configured minimum pool size. | +| `db.client.connection.wait_time` | DoubleHistogram | `s` | Average borrow wait time. Recorded on `CONNECTION_BORROWED` only, when > 0. | -These metrics have no equivalent in the OTel spec but expose data available from UCP's -event context that is useful for observability. +#### UCP-specific | Metric name | Instrument | Unit | Description | |---|---|---|---| -| `db.client.connection.established` | LongGauge | `{connection}` | Cumulative physical connections opened since pool start. | -| `db.client.connection.closed` | LongGauge | `{connection}` | Cumulative physical connections closed since pool start. | +| `db.client.connection.established` | LongGauge | `{connection}` | Cumulative physical connections opened. | +| `db.client.connection.closed` | LongGauge | `{connection}` | Cumulative physical connections closed. | #### Common attributes -All metrics carry the following attribute: - -| Attribute key | Example value | Description | -|---|---|---| -| `db.client.connection.pool.name` | `"MyPool"` | Name of the UCP pool that emitted the event | - -The `db.client.connection.usage` metric additionally carries: - -| Attribute key | Values | Description | -|---|---|---| -| `db.client.connection.state` | `"used"` / `"idle"` | Whether connections are checked out or available | - -#### Limitations - -- **`db.client.connection.wait_time`**: The spec intends per-borrow wait durations. - `UCPEventContext.getAverageConnectionWaitTime()` returns a cumulative pool-wide average - since pool start, not a per-borrow value. This is the closest approximation UCP's event - API allows. Recorded only when the average is greater than zero to avoid histogram - pollution at pool startup. - -- **`db.client.connection.idle.min`**: The spec intends the minimum number of idle - connections. UCP's `minPoolSize()` returns the minimum total pool size (borrowed + idle), - not a dedicated idle floor. These differ when connections are actively borrowed. - -- **`db.client.connection.established` / `db.client.connection.closed`**: These are - monotonically increasing lifetime totals. `LongGauge` is used instead of `LongCounter` - because UCP exposes them as absolute values — a counter with delta=0 would produce no - data point when UCP reuses cached connections and never opens new physical sockets. - -> **Note on Prometheus metadata:** `db_client_connection_usage` may appear as `gauge` -> with no issues. Some metrics may display `unknown` type in the Prometheus -> `/api/v1/metadata` UI due to a known limitation of the OTel Java Prometheus exporter. -> This does not affect queries or Grafana dashboards. +| Attribute | Description | +|---|---| +| `db.client.connection.pool.name` | Pool name | +| `db.client.connection.state` | `used` / `idle` (on `usage` metric only) | ### Prometheus / Grafana quick-start -**1. Add the Prometheus exporter (test / application scope)** +**1. Add the Prometheus exporter:** ```xml - - - io.opentelemetry - opentelemetry-sdk - 1.44.1 - - - io.opentelemetry - opentelemetry-exporter-prometheus - 1.44.1-alpha - - + + io.opentelemetry + opentelemetry-sdk + 1.44.1 + + + io.opentelemetry + opentelemetry-exporter-prometheus + 1.44.1-alpha + ``` -**2. Configure Prometheus to scrape the application** +**2. Configure Prometheus:** ```yaml -# prometheus.yml scrape_configs: - job_name: ucp static_configs: - targets: ["localhost:9464"] ``` -**3. Useful Grafana PromQL queries** +**3. Useful PromQL queries:** ```promql -# Current used connections db_client_connection_usage{db_client_connection_state="used"} - -# Current idle connections db_client_connection_usage{db_client_connection_state="idle"} - -# Total connections (used + idle) sum by (db_client_connection_pool_name) (db_client_connection_usage) - -# Average borrow wait time over the last minute -rate(db_client_connection_wait_time_seconds_sum[1m]) - / rate(db_client_connection_wait_time_seconds_count[1m]) - -# 95th-percentile borrow wait time -histogram_quantile(0.95, - rate(db_client_connection_wait_time_seconds_bucket[1m])) - -# Physical connection churn rate (new sockets opened per minute) -rate(db_client_connection_established[1m]) - -# Pool capacity utilisation -db_client_connection_usage{db_client_connection_state="used"} - / db_client_connection_max +histogram_quantile(0.95, rate(db_client_connection_wait_time_seconds_bucket[1m])) +db_client_connection_usage{db_client_connection_state="used"} / db_client_connection_max ``` --- ## Supported UCP event types -Both providers handle all twelve event types defined by `UCPEventListener.EventType`: - -| Event type | Category | -|---|---| -| `POOL_CREATED` | Pool lifecycle | -| `POOL_STARTING` | Pool lifecycle | -| `POOL_STARTED` | Pool lifecycle | -| `POOL_STOPPED` | Pool lifecycle | -| `POOL_DESTROYED` | Pool lifecycle | -| `CONNECTION_CREATED` | Connection lifecycle | -| `CONNECTION_BORROWED` | Connection lifecycle | -| `CONNECTION_RETURNED` | Connection lifecycle | -| `CONNECTION_CLOSED` | Connection lifecycle | -| `POOL_REFRESHED` | Maintenance | -| `POOL_RECYCLED` | Maintenance | -| `POOL_PURGED` | Maintenance | +| Event type | Category | Description | +|---|---|---| +| `POOL_CREATED` | Pool lifecycle | Pool is successfully created | +| `POOL_STARTING` | Pool lifecycle | Pool startup begins | +| `POOL_STARTED` | Pool lifecycle | Pool is successfully started | +| `POOL_STOPPED` | Pool lifecycle | Pool is successfully stopped | +| `POOL_DESTROYED` | Pool lifecycle | Pool is successfully destroyed | +| `CONNECTION_CREATED` | Connection lifecycle | New connection is created | +| `CONNECTION_BORROWED` | Connection lifecycle | Connection is borrowed from pool | +| `CONNECTION_RETURNED` | Connection lifecycle | Connection is returned to pool | +| `CONNECTION_CLOSED` | Connection lifecycle | Connection is closed | +| `POOL_REFRESHED` | Maintenance | Refresh completes | +| `POOL_RECYCLED` | Maintenance | Recycle completes | +| `POOL_PURGED` | Maintenance | Purge completes | --- @@ -359,10 +244,6 @@ Both providers handle all twelve event types defined by `UCPEventListener.EventT | Java | 11+ | | OpenTelemetry API *(OTel provider only)* | 1.44.1 | -The JFR provider has no dependencies beyond the Oracle UCP jar and a JDK that supports JFR -(JDK 11+). The OpenTelemetry provider requires `opentelemetry-api` on the classpath; the -SDK and exporter are the application's responsibility. - --- ## See also @@ -370,4 +251,4 @@ SDK and exporter are the application's responsibility. - [Oracle UCP Developer's Guide](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjucp/) - [OpenTelemetry Java documentation](https://opentelemetry.io/docs/languages/java/) - [JDK Mission Control download](https://www.oracle.com/java/technologies/jdk-mission-control.html) -- [Oracle JDBC Driver Extensions — root README](../README.md) \ No newline at end of file +- [Oracle JDBC Driver Extensions — root README](../README.md) From 7ef42fb3e2695132e163ecc81c0a64ef71511724 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Mon, 6 Apr 2026 14:35:43 +0100 Subject: [PATCH 65/82] fix code snippets and XML dependency wrappers in README --- ojdbc-provider-ucp-observability/README.md | 80 ++++++++++++++-------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md index c508b127..5c56ad27 100644 --- a/ojdbc-provider-ucp-observability/README.md +++ b/ojdbc-provider-ucp-observability/README.md @@ -25,11 +25,13 @@ Both providers are registered automatically via `java.util.ServiceLoader` — no ## Installation ```xml - - com.oracle.database.jdbc - ojdbc-provider-ucp-observability - 1.0.6 - + + + com.oracle.database.jdbc + ojdbc-provider-ucp-observability + 1.0.6 + + ``` --- @@ -41,8 +43,13 @@ Two ways to activate a provider, in priority order: **1. Pool-level property** (highest priority) — set directly on the data source before the pool starts: ```java -pds.setUCPEventListenerProvider("jfr-ucp-listener"); -// pds.setUCPEventListenerProvider("otel-ucp-listener"); +public class MyApp { + public static void main(String[] args) throws Exception { + PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); + pds.setUCPEventListenerProvider("jfr-ucp-listener"); + // pds.setUCPEventListenerProvider("otel-ucp-listener"); + } +} ``` **2. JVM system property** — applies globally to all pools in the JVM: @@ -54,7 +61,12 @@ java -DUCPEventListenerProvider=jfr-ucp-listener -jar myapp.jar Or programmatically: ```java -System.setProperty("UCPEventListenerProvider", "jfr-ucp-listener"); +public class MyApp { + public static void main(String[] args) throws Exception { + System.setProperty("UCPEventListenerProvider", "jfr-ucp-listener"); + // System.setProperty("UCPEventListenerProvider", "otel-ucp-listener"); + } +} ``` If no provider is configured, a no-op provider is used automatically with zero overhead. @@ -140,18 +152,24 @@ Publishes UCP metrics through the [OpenTelemetry API](https://opentelemetry.io/d The SDK must be registered with `GlobalOpenTelemetry` **before** the pool is started. Example using the Prometheus exporter: ```java -PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder().setPort(9464).build(); - -SdkMeterProvider meterProvider = SdkMeterProvider.builder() - .registerMetricReader(prometheusServer) - .build(); - -OpenTelemetrySdk.builder() - .setMeterProvider(meterProvider) - .buildAndRegisterGlobal(); - -// Then create and start the pool -pds.setUCPEventListenerProvider("otel-ucp-listener"); +public class MyApp { + public static void main(String[] args) throws Exception { + // 1. Register the SDK before pool creation + PrometheusHttpServer prometheusServer = PrometheusHttpServer.builder().setPort(9464).build(); + + SdkMeterProvider meterProvider = SdkMeterProvider.builder() + .registerMetricReader(prometheusServer) + .build(); + + OpenTelemetrySdk.builder() + .setMeterProvider(meterProvider) + .buildAndRegisterGlobal(); + + // 2. Then create and start the pool + PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource(); + pds.setUCPEventListenerProvider("otel-ucp-listener"); + } +} ``` ### Exported metrics @@ -184,16 +202,18 @@ pds.setUCPEventListenerProvider("otel-ucp-listener"); **1. Add the Prometheus exporter:** ```xml - - io.opentelemetry - opentelemetry-sdk - 1.44.1 - - - io.opentelemetry - opentelemetry-exporter-prometheus - 1.44.1-alpha - + + + io.opentelemetry + opentelemetry-sdk + 1.44.1 + + + io.opentelemetry + opentelemetry-exporter-prometheus + 1.44.1-alpha + + ``` **2. Configure Prometheus:** From 1099cd4807793101c56474d7f2e10cc8d4646594 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Tue, 7 Apr 2026 10:22:12 +0100 Subject: [PATCH 66/82] remove @Description from base event, add isEnabled() guard before commit(), and improve Javadoc clarity --- .../observability/jfr/core/UCPBaseEvent.java | 6 ++---- .../jfr/core/UCPEventFactory.java | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java index a994bb98..25fd45ca 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java @@ -49,7 +49,6 @@ * standard pool metrics and metadata. */ @StackTrace(false) -@Description("Base UCP Event") public abstract class UCPBaseEvent extends Event { /** Name of the connection pool */ @@ -57,10 +56,9 @@ public abstract class UCPBaseEvent extends Event { private String poolName; /** + * UCP event timestamp in milliseconds since epoch. * - *

    Note: This is the timestamp captured by UCP at event creation time, - * preserved for correlation purposes. JFR also records its own startTime - * automatically — this field complements it rather than replacing it. + *

    Complements JFR's own {@code startTime} — preserved for correlation. */ @Label("UCP Timestamp (ms)") private long ucpTimestamp; diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java index af541fff..33d1b5f5 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java @@ -58,18 +58,18 @@ public final class UCPEventFactory { private UCPEventFactory() {} private static final Logger LOGGER = - Logger.getLogger(UCPEventFactory.class.getName()); + Logger.getLogger(UCPEventFactory.class.getName()); /** * 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 + * @return configured JFR event, or {@code null} for unrecognized types * @throws NullPointerException if parameters are null */ static Event createEvent( - UCPEventListener.EventType type, UCPEventContext ctx) { + UCPEventListener.EventType type, UCPEventContext ctx) { Objects.requireNonNull(type, "EventType cannot be null"); Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); @@ -94,21 +94,23 @@ static Event createEvent( default: LOGGER.fine(() -> - "Unrecognized UCP EventType ignored by JFR provider: " + type); + "Unrecognized UCP EventType ignored by JFR provider: " + type); return null; } } /** - * Creates and records a JFR event for the given UCP operation, - * only if JFR recording is currently active. + * Creates and records a JFR event for the given UCP operation. + * + *

    Skips {@link Event#commit()} when JFR has no active recording for + * this event type, avoiding serialisation overhead on the hot path. * * @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) { + UCPEventListener.EventType type, UCPEventContext ctx) { Objects.requireNonNull(type, "EventType cannot be null"); Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); @@ -118,6 +120,8 @@ public static void recordEvent( return; } - event.commit(); + if (event.isEnabled()) { + event.commit(); + } } } \ No newline at end of file From 2292e23ffa89e27c54b039d503da806dfdf2034a Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Tue, 7 Apr 2026 10:47:51 +0100 Subject: [PATCH 67/82] refactor(otel): condense Javadoc and inline comments for clarity and brevity --- .../otel/OtelUCPEventListenerProvider.java | 260 +++++------------- 1 file changed, 72 insertions(+), 188 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java index 792d7552..a557392b 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -55,67 +55,46 @@ /** * OpenTelemetry provider for UCP connection pool metrics. * - *

    Metrics follow the OpenTelemetry semantic conventions for database client - * connection pools: https://opentelemetry.io/docs/specs/semconv/database/database-metrics/ - * Where the spec defines an instrument for data that UCP exposes, the spec name - * and instrument type are used exactly. - *

    + *

    Metrics follow the + * + * OTel semantic conventions for database client connection pools. * *

    Spec-aligned metrics

    *
      - *
    • {@code db.client.connection.usage} LongGauge — current used/idle connections - * (state attribute: {@code used} | {@code idle}). - * Note: the spec defines this metric as {@code db.client.connection.count} with - * instrument type UpDownCounter. We deviate on both: we use {@code usage} as the - * name (to avoid the reserved Prometheus {@code _count} suffix which causes the - * OTel exporter to report an unknown type), and LongGauge as the instrument - * (because UCP provides absolute snapshots, not incremental deltas).
    • - *
    • {@code db.client.connection.max} LongGauge — configured maximum pool size. - * Recorded only on pool lifecycle events (POOL_CREATED, POOL_STARTING, - * POOL_STARTED, POOL_STOPPED, POOL_DESTROYED) since this is a configuration - * constant that rarely changes, avoiding unnecessary overhead on high-frequency - * connection events.
    • - *
    • {@code db.client.connection.idle.min} LongGauge — configured minimum pool size. - * Note: UCP's {@link UCPEventContext#minPoolSize()} returns the minimum total - * pool size, not a dedicated minimum-idle value. This is the closest approximation - * UCP's event API allows. Recorded only on pool lifecycle events for the same - * reason as {@code db.client.connection.max}.
    • - *
    • {@code db.client.connection.wait_time} Histogram (s) — approximated from UCP's - * cumulative average wait time; recorded on CONNECTION_BORROWED only, and only - * when the average is greater than zero to avoid polluting the histogram with - * zero-value observations at pool startup.
    • + *
    • {@code db.client.connection.usage} (LongGauge, state={@code used}|{@code idle}) — + * deviates from the spec name {@code db.client.connection.count} (UpDownCounter) to + * avoid the reserved Prometheus {@code _count} suffix, and uses LongGauge because + * UCP provides absolute snapshots rather than incremental deltas.
    • + *
    • {@code db.client.connection.max} (LongGauge) — recorded on pool lifecycle events + * only, since maxPoolSize is a configuration constant.
    • + *
    • {@code db.client.connection.idle.min} (LongGauge) — approximated from + * {@link UCPEventContext#minPoolSize()} (minimum total pool size, not idle-only). + * Recorded on pool lifecycle events only.
    • + *
    • {@code db.client.connection.wait_time} (DoubleHistogram, seconds) — approximated + * from UCP's cumulative pool-wide average; recorded on {@code CONNECTION_BORROWED} + * only when {@literal >} 0.
    • *
    * - *

    UCP-specific metrics (no spec equivalent)

    + *

    UCP-specific metrics

    *
      - *
    • {@code db.client.connection.established} LongGauge — cumulative physical connections - * ever opened since pool start
    • - *
    • {@code db.client.connection.closed} LongGauge — cumulative physical connections - * ever closed since pool start
    • + *
    • {@code db.client.connection.established} (LongGauge) — cumulative connections opened.
    • + *
    • {@code db.client.connection.closed} (LongGauge) — cumulative connections closed.
    • *
    * *

    Limitations

    *
      - *
    • {@code db.client.connection.wait_time}: The spec intends per-borrow wait durations. - * {@link UCPEventContext#getAverageConnectionWaitTime()} returns a cumulative pool-wide - * average since pool start, not a per-borrow value. This is the closest approximation - * UCP's event API allows.
    • - *
    • {@code db.client.connection.idle.min}: The spec intends the minimum number of idle - * connections. UCP's {@link UCPEventContext#minPoolSize()} returns the minimum total - * pool size (borrowed + idle), not a dedicated idle floor. These differ when minimum - * pool size is non-zero and connections are actively borrowed.
    • - *
    • Serialization: {@code OtelUCPEventListener} implements {@link java.io.Serializable} - * as required by the {@link UCPEventListener} contract, but its OTel instrument fields - * are not serializable. Attempting to serialize this instance will throw - * {@link NotSerializableException}.
    • - *
    • Pool state memory: {@code OtelUCPEventListener} retains a {@code PoolState} entry - * per pool name and removes it on {@code POOL_DESTROYED}. If a pool is abandoned - * without {@code POOL_DESTROYED} firing (e.g. abrupt JVM shutdown), that entry will - * linger for the lifetime of the JVM.
    • + *
    • {@code db.client.connection.wait_time}: UCP exposes a cumulative pool-wide average, + * not a per-borrow value.
    • + *
    • {@code db.client.connection.idle.min}: {@link UCPEventContext#minPoolSize()} is the + * minimum total pool size, not a dedicated idle floor.
    • + *
    • Serialization: OTel instrument fields are not serializable; attempting to serialize + * this listener throws {@link NotSerializableException}.
    • + *
    • Pool state memory: a {@code PoolState} entry per pool name is retained until + * {@code POOL_DESTROYED} fires; abandoned pools linger for the JVM lifetime.
    • *
    */ public final class OtelUCPEventListenerProvider - implements UCPEventListenerProvider { + implements UCPEventListenerProvider { private volatile UCPEventListener listener; @@ -126,10 +105,8 @@ public String getName() { @Override public UCPEventListener getListener(Map config) { - // config is intentionally unused: OTel SDK configuration (exporters, - // export intervals, resource attributes, etc.) is managed externally - // via the OTel SDK setup (e.g. OpenTelemetrySdkAutoConfiguration or - // programmatic SDK builder), not through UCP's provider config map. + // config is intentionally unused: OTel SDK configuration is managed + // externally via the SDK setup, not through UCP's provider config map. if (listener == null) { synchronized (this) { if (listener == null) { @@ -140,123 +117,81 @@ public UCPEventListener getListener(Map config) { return listener; } - private static final class OtelUCPEventListener implements UCPEventListener { + private static final class OtelUCPEventListener + implements UCPEventListener { private static final long serialVersionUID = 1L; - // ------------------------------------------------------------------------- // Attribute keys — static: stateless, shared across all pools. - // ------------------------------------------------------------------------- - private static final AttributeKey POOL_NAME = - AttributeKey.stringKey("db.client.connection.pool.name"); + AttributeKey.stringKey("db.client.connection.pool.name"); private static final AttributeKey STATE = - AttributeKey.stringKey("db.client.connection.state"); + AttributeKey.stringKey("db.client.connection.state"); - // ------------------------------------------------------------------------- // Meter and instruments — instance fields, intentionally NOT static. - // - // GlobalOpenTelemetry.getMeter() is called when OtelUCPEventListener is - // constructed, which happens inside getListener(), which UCP calls at pool - // activation time — after the application has registered its OTel SDK. - // If these were static, they would be initialized at class-load time, - // before the SDK is registered, and would silently become permanent no-ops. - // - // Inverse risk: if the OTel SDK is reinitialized after the listener is - // constructed (uncommon in production; typical in tests that reset the SDK - // between runs), these instruments remain bound to the original SDK instance - // and will export to the old exporter. Tests should obtain a fresh provider - // instance after each SDK reset rather than reusing the singleton. - // ------------------------------------------------------------------------- - - // Note: meter is retained as an instance field rather than a constructor-local - // variable for readability — the instrument declarations below read as a - // cohesive block. The Meter object itself is lightweight (no pooled resources), - // so the marginal GC cost of holding the reference is negligible. + // GlobalOpenTelemetry.getMeter() is called at listener construction time + // (inside getListener()), which happens after the application registers + // its OTel SDK. Static initialization would fire at class-load time — + // before the SDK is ready — and silently produce permanent no-ops. private final Meter meter = GlobalOpenTelemetry.getMeter("oracle.ucp"); - // Spec-aligned: db.client.connection.usage (LongGauge, state=used|idle) - // Spec says UpDownCounter named db.client.connection.count. We deviate on - // both: name changed to avoid the reserved Prometheus _count suffix (which - // causes the OTel Prometheus exporter to report an unknown metric type), and - // LongGauge used because UCP provides absolute snapshots at each event — - // not incremental deltas. LongGauge.set() maps directly to that model without - // drift risk. A single instrument is registered; the state attribute - // differentiates used vs idle at the data level. + // db.client.connection.usage (LongGauge, state=used|idle) + // See class-level Javadoc for deviation rationale. private final LongGauge connectionUsage = meter.gaugeBuilder("db.client.connection.usage") .setDescription("The number of connections that are currently in the state described by the state attribute.") .setUnit("{connection}").ofLongs().build(); - // Spec-aligned: db.client.connection.max (LongGauge) - // Spec says UpDownCounter, but maxPoolSize is a configuration value set at - // pool creation and rarely changes. LongGauge records the actual current - // value directly, which is semantically correct for a configured constant. - // Recorded only on pool lifecycle events to avoid per-connection overhead. + // db.client.connection.max (LongGauge) + // Recorded on pool lifecycle events only — maxPoolSize rarely changes. private final LongGauge connectionMax = meter.gaugeBuilder("db.client.connection.max") .setDescription("The maximum number of open connections allowed.") .setUnit("{connection}").ofLongs().build(); - // Spec-aligned: db.client.connection.idle.min (LongGauge) - // Same reasoning as db.client.connection.max above. - // Note: UCP's minPoolSize() is the minimum total pool size, not a dedicated - // idle floor. See class-level Javadoc for the full limitation note. - // Recorded only on pool lifecycle events to avoid per-connection overhead. + // db.client.connection.idle.min (LongGauge) + // Sourced from minPoolSize() (minimum total size, not idle-only). + // Recorded on pool lifecycle events only. private final LongGauge connectionIdleMin = meter.gaugeBuilder("db.client.connection.idle.min") .setDescription( - "Approximation of the minimum number of idle open connections allowed. " + - "Sourced from UCP's minPoolSize (minimum total pool size), which is the " + - "closest value UCP's event API exposes. May differ from true idle minimum " + - "when connections are actively borrowed.") + "Approximation of the minimum number of idle open connections allowed. " + + "Sourced from UCP's minPoolSize (minimum total pool size), which is the " + + "closest value UCP's event API exposes. May differ from true idle minimum " + + "when connections are actively borrowed.") .setUnit("{connection}").ofLongs().build(); - // Spec-aligned: db.client.connection.wait_time (DoubleHistogram, unit: s) - // DoubleHistogram (not Long) because fractional seconds matter: - // e.g. 250 ms = 0.25 s would be truncated to 0 with a LongHistogram. - // Recorded on CONNECTION_BORROWED events only when wait time > 0 to avoid - // polluting the histogram with zero-value observations at pool startup. + // db.client.connection.wait_time (DoubleHistogram, seconds) + // DoubleHistogram preserves fractional seconds (e.g. 250 ms = 0.25 s). + // Recorded on CONNECTION_BORROWED only when wait time > 0. // UCP value is in ms — divided by 1000.0 before recording. - // See class-level Javadoc for the full limitation note. private final DoubleHistogram waitTime = meter.histogramBuilder("db.client.connection.wait_time") .setDescription( - "Approximation of borrow wait time based on UCP's pool-wide cumulative " + - "average. Recorded on CONNECTION_BORROWED events only when wait time > 0.") + "Approximation of borrow wait time based on UCP's pool-wide cumulative " + + "average. Recorded on CONNECTION_BORROWED events only when wait time > 0.") .setUnit("s").build(); - // UCP-specific: db.client.connection.established (LongGauge) - // Cumulative count of physical connections ever opened since pool start. - // Although monotonically increasing, we use LongGauge because UCP exposes - // this as an absolute lifetime total. When UCP reuses cached connections - // and never opens new physical sockets, the value stays at 0 — a LongCounter - // with delta=0 produces no data point at all, making the metric invisible. - // LongGauge.set() always records the current value, even when it is 0. + // db.client.connection.established (LongGauge) + // LongGauge rather than LongCounter: UCP exposes an absolute lifetime total, + // and LongGauge.set() records the value even when it is 0 (delta=0 on a + // counter produces no data point, making the metric invisible). private final LongGauge connectionEstablished = meter.gaugeBuilder("db.client.connection.established") .setDescription("Cumulative number of physical connections opened since pool start.") .setUnit("{connection}").ofLongs().build(); - // UCP-specific: db.client.connection.closed (LongGauge) - // Same reasoning as db.client.connection.established above. + // db.client.connection.closed (LongGauge) — same reasoning as above. private final LongGauge connectionClosed = meter.gaugeBuilder("db.client.connection.closed") .setDescription("Cumulative number of physical connections closed since pool start.") .setUnit("{connection}").ofLongs().build(); - // ------------------------------------------------------------------------- - // Per-pool state — holds pre-built Attributes objects reused on every event - // to avoid allocating new objects on every onUCPEvent call under high load. - // PoolState is a plain data holder — attribute keys passed in explicitly - // to avoid hidden coupling to the enclosing class's static fields. - // - // Entries are removed on POOL_DESTROYED. If a pool is abandoned without - // POOL_DESTROYED firing, its entry will linger for the JVM lifetime. - // ------------------------------------------------------------------------- - + // Per-pool state — pre-built Attributes objects reused on every event to + // avoid per-call allocation under high load. Entries are removed on + // POOL_DESTROYED; abandoned pools linger for the JVM lifetime. private static final class PoolState { final Attributes attrs; final Attributes attrsUsed; @@ -274,52 +209,18 @@ private static final class PoolState { private final ConcurrentHashMap poolStates = new ConcurrentHashMap<>(); - // ------------------------------------------------------------------------- - // Serialization guard - // - // UCPEventListener extends Serializable, but this class holds OTel - // instrument fields that are not serializable. Explicitly block - // serialization rather than letting it fail with an opaque stack trace. - // ------------------------------------------------------------------------- - + // Serialization guard — OTel instrument fields are not serializable. private void writeObject(ObjectOutputStream ignored) throws IOException { throw new NotSerializableException( OtelUCPEventListener.class.getName() + ": OTel instrument fields are not serializable."); } - // ------------------------------------------------------------------------- - // Event filter — explicit opt-in to all events. - // - // All UCP event types are processed: - // - Pool lifecycle (POOL_CREATED, POOL_STARTING, POOL_STARTED, - // POOL_STOPPED) → connectionMax, connectionIdleMin, - // plus live snapshot - // - POOL_DESTROYED → connectionMax, connectionIdleMin, - // live snapshot + poolStates cleanup - // - CONNECTION_BORROWED → waitTime (when > 0), plus live snapshot - // - All other connection events → live snapshot only - // (CONNECTION_CREATED, CONNECTION_RETURNED, CONNECTION_CLOSED) - // - Maintenance events → live snapshot only - // (POOL_REFRESHED, POOL_RECYCLED, POOL_PURGED) - // - // Maintenance events have no dedicated metric handling but still contribute - // a live connectionUsage / connectionEstablished / connectionClosed snapshot, - // which is useful for observing pool state immediately after a refresh/recycle. - // - // Overriding isDesiredEvent() makes this opt-in explicit rather than relying - // on the default implementation. - // ------------------------------------------------------------------------- - @Override public boolean isDesiredEvent(EventType eventType) { return true; } - // ------------------------------------------------------------------------- - // Event handler - // ------------------------------------------------------------------------- - @Override public void onUCPEvent(EventType eventType, UCPEventContext ctx) { if (eventType == null || ctx == null) { @@ -327,17 +228,13 @@ public void onUCPEvent(EventType eventType, UCPEventContext ctx) { } String poolName = ctx.poolName(); - // ConcurrentHashMap does not permit null keys — guard explicitly rather - // than relying on UCP to always provide a non-null pool name. + // ConcurrentHashMap does not permit null keys. if (poolName == null) { return; } - // POOL_DESTROYED is handled first: remove the PoolState entry atomically - // before recording the final snapshot. This avoids the alternative pattern - // of computeIfAbsent followed by an immediate remove, which would allocate - // a new PoolState only to discard it. The removed state is used to record - // the final metric snapshot; if it is null (pool was never seen), we skip. + // POOL_DESTROYED: remove state atomically before recording the final + // snapshot to avoid allocating a new PoolState only to discard it. if (eventType == EventType.POOL_DESTROYED) { PoolState state = poolStates.remove(poolName); if (state != null) { @@ -353,23 +250,18 @@ public void onUCPEvent(EventType eventType, UCPEventContext ctx) { } /** - * Records all metric observations for a single event. Extracted from - * {@link #onUCPEvent} to keep the event-routing logic and the metric - * recording logic separately readable. + * Records all metric observations for a single event. + * Extracted from {@link #onUCPEvent} to keep routing and recording logic separate. */ private void recordSnapshot(EventType eventType, UCPEventContext ctx, PoolState state) { - // --- db.client.connection.usage (LongGauge, direct snapshot) ------------ - + // db.client.connection.usage — direct snapshot on every event. connectionUsage.set(ctx.borrowedConnectionsCount(), state.attrsUsed); connectionUsage.set(ctx.availableConnectionsCount(), state.attrsIdle); - // --- db.client.connection.max / idle.min -------------------------------- - // Configuration constants — only recorded on pool lifecycle events to - // avoid redundant work on high-frequency connection borrow/return events. + // db.client.connection.max / idle.min — pool lifecycle events only. // Maintenance events (POOL_REFRESHED, POOL_RECYCLED, POOL_PURGED) are - // intentionally excluded — they only contribute a live connection snapshot. - + // excluded; they contribute only a live connection snapshot. switch (eventType) { case POOL_CREATED: case POOL_STARTING: @@ -383,14 +275,7 @@ private void recordSnapshot(EventType eventType, UCPEventContext ctx, PoolState break; } - // --- db.client.connection.wait_time (CONNECTION_BORROWED only) ----------- - // Cached in a local variable to avoid calling getAverageConnectionWaitTime() - // twice and to guard against any future context mutability. - // Only recorded when > 0 to avoid polluting the histogram with zero-value - // observations before any real waits occur. - // See limitation note in Javadoc — UCP exposes a cumulative average, - // not a per-borrow value. - + // db.client.connection.wait_time — CONNECTION_BORROWED only, when > 0. if (eventType == EventType.CONNECTION_BORROWED) { double avgWaitMs = ctx.getAverageConnectionWaitTime(); if (avgWaitMs > 0) { @@ -398,8 +283,7 @@ private void recordSnapshot(EventType eventType, UCPEventContext ctx, PoolState } } - // --- UCP-specific metrics ----------------------------------------------- - + // UCP-specific metrics. connectionEstablished.set(ctx.createdConnections(), state.attrs); connectionClosed.set(ctx.closedConnections(), state.attrs); } From dd234dff6c3a204df11cb0670240d3df5e650feb Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 13 Apr 2026 16:05:44 +0200 Subject: [PATCH 68/82] Update dependency versions --- ojdbc-provider-gcp/pom.xml | 2 +- ojdbc-provider-oci/pom.xml | 2 +- ojdbc-provider-opentelemetry/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ojdbc-provider-gcp/pom.xml b/ojdbc-provider-gcp/pom.xml index 26729484..b1bfcc75 100644 --- a/ojdbc-provider-gcp/pom.xml +++ b/ojdbc-provider-gcp/pom.xml @@ -20,7 +20,7 @@ com.google.cloud libraries-bom - 26.54.0 + 26.79.0 pom import diff --git a/ojdbc-provider-oci/pom.xml b/ojdbc-provider-oci/pom.xml index a7931fb4..8347264f 100644 --- a/ojdbc-provider-oci/pom.xml +++ b/ojdbc-provider-oci/pom.xml @@ -18,7 +18,7 @@ com.oracle.oci.sdk oci-java-sdk-bom - 3.74.2 + 3.83.0 pom import diff --git a/ojdbc-provider-opentelemetry/pom.xml b/ojdbc-provider-opentelemetry/pom.xml index 2a2b8982..cbed028c 100644 --- a/ojdbc-provider-opentelemetry/pom.xml +++ b/ojdbc-provider-opentelemetry/pom.xml @@ -15,7 +15,7 @@ - 1.55.0 + 1.61.0 From d86d1dfe8e1339eda263577176e62b6355206eff Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 13 Apr 2026 17:12:46 +0200 Subject: [PATCH 69/82] Fixed version number --- ojdbc-provider-pkl/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ojdbc-provider-pkl/pom.xml b/ojdbc-provider-pkl/pom.xml index 5f93cea7..3ac301d0 100644 --- a/ojdbc-provider-pkl/pom.xml +++ b/ojdbc-provider-pkl/pom.xml @@ -10,7 +10,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 From 1227d5c3c2dc31274dac856bdcf76462cf14bec9 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Mon, 13 Apr 2026 22:39:01 +0200 Subject: [PATCH 70/82] Third party licenses for Oracle JDBC Providers Pkl Module --- ojdbc-provider-pkl/THIRD_PARTY_LICENSES.txt | 254 ++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 ojdbc-provider-pkl/THIRD_PARTY_LICENSES.txt diff --git a/ojdbc-provider-pkl/THIRD_PARTY_LICENSES.txt b/ojdbc-provider-pkl/THIRD_PARTY_LICENSES.txt new file mode 100644 index 00000000..2a8e7af2 --- /dev/null +++ b/ojdbc-provider-pkl/THIRD_PARTY_LICENSES.txt @@ -0,0 +1,254 @@ +---------------------- Third-Party Components --------------------------------- +Java config library based on the Pkl config language. + +-- License and Notices for Java config library based on the Pkl config language +SPDX:Apache-2.0 + +---------------------------------- Copyright ----------------------------------- +Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. +Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + +-------------------------- Fourth Party Dependencies --------------------------- + +----------------------------------- Licenses ----------------------------------- +SPDX:Apache-2.0 + +--------------------------------- (separator) ---------------------------------- + +== Dependency +Pkl-core (https://github.com/apple/pkl/tree/main/pkl-core) + +== License Type +SPDX:Apache-2.0 + +== Copyright +Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. + +--------------------------------- (separator) ---------------------------------- + +== Dependency +GeantyRef (https://github.com/leangen/geantyref) + +== License Type +SPDX:Apache-2.0 + +== Copyright +Copyright © 2017 Kaqqao + +--------------------------------- (separator) ---------------------------------- + +== Dependency +msgpack-core (https://msgpack.org/) + +== License Type +SPDX:Apache-2.0 + +== Copyright +Copyright © 2016 Sadayuki Furuhashi, Muga Nishizawa, Taro L. Saito, Mitsunori +Komatsu, Ozawa Tsuyoshi + +--------------------------------- (separator) ---------------------------------- +== SPDX:Apache-2.0 + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file From e0916eac0b57e791a5873e71780c5122dcb96df5 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Tue, 14 Apr 2026 10:16:02 +0200 Subject: [PATCH 71/82] Updated jackson version --- ojdbc-provider-jackson-oson/pom.xml | 2 +- .../provider/oson/deser/OsonOffsetDateTimeDeserializer.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index 2bca81f6..21ae1eed 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -13,7 +13,7 @@ ojdbc-provider-jackson-oson - 2.20.0 + 2.21.2 3.1.0 diff --git a/ojdbc-provider-jackson-oson/src/main/java/oracle/jdbc/provider/oson/deser/OsonOffsetDateTimeDeserializer.java b/ojdbc-provider-jackson-oson/src/main/java/oracle/jdbc/provider/oson/deser/OsonOffsetDateTimeDeserializer.java index 29e25ff8..3c9bb540 100644 --- a/ojdbc-provider-jackson-oson/src/main/java/oracle/jdbc/provider/oson/deser/OsonOffsetDateTimeDeserializer.java +++ b/ojdbc-provider-jackson-oson/src/main/java/oracle/jdbc/provider/oson/deser/OsonOffsetDateTimeDeserializer.java @@ -81,7 +81,7 @@ protected OsonOffsetDateTimeDeserializer() { a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId), a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId), (d, z) -> (d.isEqual(OffsetDateTime.MIN) || d.isEqual(OffsetDateTime.MAX) ? d : d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime()))), - true); + true, true, true); } protected OsonOffsetDateTimeDeserializer(OsonOffsetDateTimeDeserializer base, DateTimeFormatter f) @@ -102,7 +102,7 @@ protected OsonOffsetDateTimeDeserializer(OsonOffsetDateTimeDeserializer base, Da } @Override - protected OsonOffsetDateTimeDeserializer withDateFormat(DateTimeFormatter dtf) { + public OsonOffsetDateTimeDeserializer withDateFormat(DateTimeFormatter dtf) { if (dtf == _formatter) { return this; } From 5242fb64bcc5bd8f3e7f02472bdfce4245ef20d6 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Tue, 14 Apr 2026 18:16:57 +0200 Subject: [PATCH 72/82] Try changing driver version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 36c9d516..652833e2 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 1.8 1.8 - 23.26.0.0.0 + 23.26.1.0.0 5.9.0 4.11.0 UTF-8 From 1b5fa98235f839ead7f16959af5c7e354aecece4 Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 15 Apr 2026 10:19:38 +0200 Subject: [PATCH 73/82] Catch exception in test --- .../BackwardCompatibilityTest.java | 119 +++++++++--------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java index 81e4e7f9..416e2ade 100644 --- a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java @@ -38,6 +38,7 @@ package oracle.jdbc.provider.observability; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.lang.management.ManagementFactory; import java.util.HashMap; @@ -47,6 +48,7 @@ import javax.management.MBeanServer; import javax.management.ObjectName; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import oracle.jdbc.spi.OracleResourceProvider.Parameter; @@ -58,62 +60,67 @@ public class BackwardCompatibilityTest { @Test public void testConfiguration() throws Exception { - // System properties - System.setProperty("oracle.jdbc.provider.opentelemetry.enabled", "true"); - System.setProperty("oracle.jdbc.provider.opentelemetry.sensitive-enabled", "true"); - - TraceEventListenerProvider provider = new OpenTelemetryTraceEventListenerProvider(); - Map parameters = new HashMap<>(); - provider.getParameters().forEach(parameter -> { - parameters.put(parameter, (CharSequence)INSTANCE_NAME); - }); - ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); - ObservabilityConfiguration configuration = listener.getObservabilityConfiguration(); - - assertEquals(true, configuration.getEnabled()); - assertEquals("OTEL", configuration.getEnabledTracers()); - assertEquals(true, configuration.getSensitiveDataEnabled()); - - assertEquals(1, configuration.getEnabledTracersAsList().size()); - assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); - - // MBean - ObjectName objectName = listener.getMBeanObjectName(); - MBeanServer server = ManagementFactory.getPlatformMBeanServer(); - String enabled = server.getAttribute(objectName, "Enabled").toString(); - String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); - String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); - - assertEquals(enabled, "true"); - assertEquals(enabledTracers, "OTEL"); - assertEquals(sensitiveDataEnabled, "true"); - - server.setAttribute(objectName, new Attribute("Enabled", false)); - server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); - - assertEquals(false, configuration.getEnabled()); - assertEquals(false, configuration.getSensitiveDataEnabled()); - - assertEquals("OTEL", configuration.getEnabledTracers()); - assertEquals(false, configuration.getSensitiveDataEnabled()); - - assertEquals(1, configuration.getEnabledTracersAsList().size()); - assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); - - // Singleton - configuration.setEnabled(true); - configuration.setSensitiveDataEnabled(true); - - enabled = server.getAttribute(objectName, "Enabled").toString(); - enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); - sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); - - assertEquals("true", enabled); - assertEquals("OTEL", enabledTracers); - assertEquals("true", sensitiveDataEnabled); - - assertEquals(1, configuration.getEnabledTracersAsList().size()); - assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + try { + // System properties + System.setProperty("oracle.jdbc.provider.opentelemetry.enabled", "true"); + System.setProperty("oracle.jdbc.provider.opentelemetry.sensitive-enabled", "true"); + + TraceEventListenerProvider provider = new OpenTelemetryTraceEventListenerProvider(); + Map parameters = new HashMap<>(); + provider.getParameters().forEach(parameter -> { + parameters.put(parameter, (CharSequence)INSTANCE_NAME); + }); + ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); + ObservabilityConfiguration configuration = listener.getObservabilityConfiguration(); + + assertEquals(true, configuration.getEnabled()); + assertEquals("OTEL", configuration.getEnabledTracers()); + assertEquals(true, configuration.getSensitiveDataEnabled()); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + + // MBean + ObjectName objectName = listener.getMBeanObjectName(); + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + String enabled = server.getAttribute(objectName, "Enabled").toString(); + String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals(enabled, "true"); + assertEquals(enabledTracers, "OTEL"); + assertEquals(sensitiveDataEnabled, "true"); + + server.setAttribute(objectName, new Attribute("Enabled", false)); + server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); + + assertEquals(false, configuration.getEnabled()); + assertEquals(false, configuration.getSensitiveDataEnabled()); + + assertEquals("OTEL", configuration.getEnabledTracers()); + assertEquals(false, configuration.getSensitiveDataEnabled()); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + + // Singleton + configuration.setEnabled(true); + configuration.setSensitiveDataEnabled(true); + + enabled = server.getAttribute(objectName, "Enabled").toString(); + enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals("true", enabled); + assertEquals("OTEL", enabledTracers); + assertEquals("true", sensitiveDataEnabled); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + } catch (Throwable throwable) { + throwable.printStackTrace(); + Assertions.fail("Failed", throwable); + } } } From 1b034d5dc5983d40e48082315197b6b6c42c543e Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 15 Apr 2026 10:32:45 +0200 Subject: [PATCH 74/82] Not specifying distribution --- .github/workflows/run-tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index f966a281..50a2f450 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -19,7 +19,6 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: - distribution: 'oracle' java-version: '17' - name: Cache Maven packages uses: actions/cache@v4 From 2114f3c01a9ed651b57b8097264ded7935fdac4b Mon Sep 17 00:00:00 2001 From: Fernanda Meheust Date: Wed, 15 Apr 2026 10:34:04 +0200 Subject: [PATCH 75/82] updated jdk version --- .github/workflows/run-tests.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 50a2f450..620057aa 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -16,10 +16,11 @@ jobs: steps: - name: Checkout the repository uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v5 with: - java-version: '17' + distribution: 'oracle' + java-version: '21' - name: Cache Maven packages uses: actions/cache@v4 with: From 85dbfa6b9c2acdb7ef8c2cce5443042200bb749f Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Wed, 15 Apr 2026 15:16:48 +0100 Subject: [PATCH 76/82] align ojdbc-provider-ucp-observability parent from 1.0.6 to 1.0.7 --- ojdbc-provider-ucp-observability/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ojdbc-provider-ucp-observability/pom.xml b/ojdbc-provider-ucp-observability/pom.xml index 12a77297..b2924144 100644 --- a/ojdbc-provider-ucp-observability/pom.xml +++ b/ojdbc-provider-ucp-observability/pom.xml @@ -7,7 +7,7 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 Oracle UCP Observability Provider @@ -16,6 +16,7 @@ 1.44.1 + 11 11 11 From 9855be819d4bc1225a09cb0953db34502b3e8ec1 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Wed, 15 Apr 2026 16:33:43 +0100 Subject: [PATCH 77/82] Fix UCP observability README field types --- ojdbc-provider-ucp-observability/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md index 5c56ad27..317fa55c 100644 --- a/ojdbc-provider-ucp-observability/README.md +++ b/ojdbc-provider-ucp-observability/README.md @@ -117,8 +117,8 @@ Converts every UCP pool and connection event into a committed JFR event. Zero ov | `borrowedConnectionsCount` | `int` | Currently checked-out connections | | `availableConnectionsCount` | `int` | Ready-to-use connections | | `totalConnections` | `int` | Current active connections (borrowed + available) | -| `createdConnections` | `long` | Total connections ever created | -| `closedConnections` | `long` | Total connections closed | +| `createdConnections` | `int` | Total connections ever created | +| `closedConnections` | `int` | Total connections closed | | `averageConnectionWaitTime` | `long` | Average milliseconds a thread waited to obtain a connection | ### Enabling a JFR recording From 1b0ab2eb26fd9313f3fe04ed36d5a14c16bd0943 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Wed, 15 Apr 2026 17:50:59 +0100 Subject: [PATCH 78/82] Add README note for OTel usage metric naming --- ojdbc-provider-ucp-observability/README.md | 2 ++ .../otel/OtelUCPEventListenerProvider.java | 13 ++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md index 317fa55c..d99747e2 100644 --- a/ojdbc-provider-ucp-observability/README.md +++ b/ojdbc-provider-ucp-observability/README.md @@ -183,6 +183,8 @@ public class MyApp { | `db.client.connection.idle.min` | LongGauge | `{connection}` | Configured minimum pool size. | | `db.client.connection.wait_time` | DoubleHistogram | `s` | Average borrow wait time. Recorded on `CONNECTION_BORROWED` only, when > 0. | +Note: `db.client.connection.usage` intentionally deviates from the OpenTelemetry semantic convention name `db.client.connection.count` to avoid the reserved Prometheus `_count` suffix. + #### UCP-specific | Metric name | Instrument | Unit | Description | diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java index a557392b..bade4dff 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -157,10 +157,10 @@ private static final class OtelUCPEventListener private final LongGauge connectionIdleMin = meter.gaugeBuilder("db.client.connection.idle.min") .setDescription( - "Approximation of the minimum number of idle open connections allowed. " + - "Sourced from UCP's minPoolSize (minimum total pool size), which is the " + - "closest value UCP's event API exposes. May differ from true idle minimum " + - "when connections are actively borrowed.") + "Approximation of the minimum number of idle open connections allowed. " + + "Sourced from UCP's minPoolSize (minimum total pool size), which is the " + + "closest value UCP's event API exposes. May differ from true idle minimum " + + "when connections are actively borrowed.") .setUnit("{connection}").ofLongs().build(); // db.client.connection.wait_time (DoubleHistogram, seconds) @@ -170,8 +170,8 @@ private static final class OtelUCPEventListener private final DoubleHistogram waitTime = meter.histogramBuilder("db.client.connection.wait_time") .setDescription( - "Approximation of borrow wait time based on UCP's pool-wide cumulative " + - "average. Recorded on CONNECTION_BORROWED events only when wait time > 0.") + "Approximation of borrow wait time based on UCP's pool-wide cumulative " + + "average. Recorded on CONNECTION_BORROWED events only when wait time > 0.") .setUnit("s").build(); // db.client.connection.established (LongGauge) @@ -283,7 +283,6 @@ private void recordSnapshot(EventType eventType, UCPEventContext ctx, PoolState } } - // UCP-specific metrics. connectionEstablished.set(ctx.createdConnections(), state.attrs); connectionClosed.set(ctx.closedConnections(), state.attrs); } From 2597e4d30492d51735aa1d0bef67086140b2f9fc Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Wed, 15 Apr 2026 17:59:28 +0100 Subject: [PATCH 79/82] Fixed Javadoc. --- .../otel/OtelUCPEventListenerProvider.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java index bade4dff..76f572c5 100644 --- a/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -67,10 +67,10 @@ * UCP provides absolute snapshots rather than incremental deltas. *
  • {@code db.client.connection.max} (LongGauge) — recorded on pool lifecycle events * only, since maxPoolSize is a configuration constant.
  • - *
  • {@code db.client.connection.idle.min} (LongGauge) — approximated from + *
  • {@code db.client.connection.idle.min} (LongGauge) — sourced from * {@link UCPEventContext#minPoolSize()} (minimum total pool size, not idle-only). * Recorded on pool lifecycle events only.
  • - *
  • {@code db.client.connection.wait_time} (DoubleHistogram, seconds) — approximated + *
  • {@code db.client.connection.wait_time} (DoubleHistogram, seconds) — sourced * from UCP's cumulative pool-wide average; recorded on {@code CONNECTION_BORROWED} * only when {@literal >} 0.
  • * @@ -157,10 +157,8 @@ private static final class OtelUCPEventListener private final LongGauge connectionIdleMin = meter.gaugeBuilder("db.client.connection.idle.min") .setDescription( - "Approximation of the minimum number of idle open connections allowed. " + - "Sourced from UCP's minPoolSize (minimum total pool size), which is the " + - "closest value UCP's event API exposes. May differ from true idle minimum " + - "when connections are actively borrowed.") + "The minimum number of idle open connections allowed. " + + "Sourced from UCP's minPoolSize (minimum total pool size).") .setUnit("{connection}").ofLongs().build(); // db.client.connection.wait_time (DoubleHistogram, seconds) @@ -170,8 +168,8 @@ private static final class OtelUCPEventListener private final DoubleHistogram waitTime = meter.histogramBuilder("db.client.connection.wait_time") .setDescription( - "Approximation of borrow wait time based on UCP's pool-wide cumulative " + - "average. Recorded on CONNECTION_BORROWED events only when wait time > 0.") + "Borrow wait time based on UCP's pool-wide cumulative average. " + + "Recorded on CONNECTION_BORROWED events only when wait time > 0.") .setUnit("s").build(); // db.client.connection.established (LongGauge) From 3b4173472efa00870235a52caf8f56847dd22d72 Mon Sep 17 00:00:00 2001 From: Mouhsin Elmajdouby Date: Thu, 16 Apr 2026 15:32:13 +0100 Subject: [PATCH 80/82] restore javadoc build after Jackson upgrade --- ojdbc-provider-jackson-oson/pom.xml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index b64acc5a..f12ffb1c 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -15,7 +15,6 @@ 2.21.2 3.1.0 - true @@ -86,6 +85,20 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + + --add-exports=com.fasterxml.jackson.datatype.jsr310/com.fasterxml.jackson.datatype.jsr310.util=com.oracle.database.jdbc.provider.jackson.oson + + + + + From 76589921992fe39d4602e205d00b06cd67efe143 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Sat, 18 Apr 2026 09:43:05 +0100 Subject: [PATCH 81/82] fix: package UCP SPI service descriptors in Maven resources --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index d98f0592..7d9d665e 100644 --- a/pom.xml +++ b/pom.xml @@ -217,6 +217,7 @@ ${project.basedir}/src/main/resources/META-INF/services/ oracle.jdbc.spi.* + oracle.ucp.events.core.* META-INF/services/ From 90ffe96599f83a22cdbfaf82200995349f397929 Mon Sep 17 00:00:00 2001 From: abdessamadelaaissaoui Date: Tue, 21 Apr 2026 12:33:53 +0100 Subject: [PATCH 82/82] docs: add ojdbc-provider-ucp-observability to root README --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 07c7098f..d6a0d6ad 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,12 @@ Each module of this project contains a set of providers.
    Oracle JDBC AWS Providers
    Providers for integration with AWS
    -The next one contains a provider for Open Telemetry: +The next modules contain providers for observability:
    Oracle JDBC Observability Provider
    Provider for integration with Open Telemetry and Java Flight Recorder.
    +
    Oracle JDBC UCP Observability Provider
    +
    Providers for integration with Oracle UCP observability through Open Telemetry metrics and Java Flight Recorder events.
    And the last one contains a provider for Jackson-OSON:
    @@ -142,6 +144,10 @@ this project: [ojdbc-provider-gcp](ojdbc-provider-gcp/README.md#installation) +[ojdbc-provider-observability](ojdbc-provider-observability/README.md#installation) + +[ojdbc-provider-ucp-observability](ojdbc-provider-ucp-observability/README.md#installation) + [ojdbc-provider-opentelemetry](ojdbc-provider-opentelemetry/README.md#installation) [ojdbc-provider-jackson-oson](ojdbc-provider-jackson-oson/README.md#installation)