diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index f966a281..620057aa 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -16,11 +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: distribution: 'oracle' - java-version: '17' + java-version: '21' - name: Cache Maven packages uses: actions/cache@v4 with: 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) 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 d8614850..c61f18fc 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 ec8af5d7..2ca39d87 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 8978f355..fe6a0299 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 da4c8f30..3ce16c96 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-common/src/main/java/oracle/jdbc/provider/configuration/JsonSecretUtil.java b/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/configuration/JsonSecretUtil.java deleted file mode 100644 index e69de29b..00000000 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 124bf2fb..0da1eab1 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 @@ -20,7 +20,7 @@ com.google.cloud libraries-bom - 26.54.0 + 26.79.0 pom import 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 82f33db4..0d70290c 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 671f790b..f12ffb1c 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -8,12 +8,12 @@ com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 ojdbc-provider-jackson-oson - 2.18.1 + 2.21.2 3.1.0 @@ -21,6 +21,7 @@ com.oracle.database.jdbc ojdbc-provider-common + 1.0.7 com.oracle.database.jdbc @@ -84,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 + + + + + diff --git a/ojdbc-provider-jackson-oson/src/main/java/oracle/jdbc/provider/oson/deser/OsonDurationDeserializer.java b/ojdbc-provider-jackson-oson/src/main/java/oracle/jdbc/provider/oson/deser/OsonDurationDeserializer.java index 4dbf3093..fca9db32 100644 --- a/ojdbc-provider-jackson-oson/src/main/java/oracle/jdbc/provider/oson/deser/OsonDurationDeserializer.java +++ b/ojdbc-provider-jackson-oson/src/main/java/oracle/jdbc/provider/oson/deser/OsonDurationDeserializer.java @@ -38,7 +38,6 @@ package oracle.jdbc.provider.oson.deser; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonTokenId; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer; 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; } diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml index 8cc2aba4..e593b8cc 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.6 + 1.0.7 Oracle JDBC Observability Provider @@ -15,7 +15,7 @@ ojdbc-provider-observability - 1.44.1 + 1.61.0 11 11 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); + } } } diff --git a/ojdbc-provider-oci/README.md b/ojdbc-provider-oci/README.md index bcde18f0..abae35a5 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 4f232a5b..87c080d8 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.83.0 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 57749a47..d5c7e08b 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.61.0 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 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 diff --git a/ojdbc-provider-samples/pom.xml b/ojdbc-provider-samples/pom.xml index c4491926..0be55ed2 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 - 1.0.6 + 1.0.7 jar com.oracle.database.jdbc ojdbc-extensions - 1.0.6 + 1.0.7 diff --git a/ojdbc-provider-ucp-observability/README.md b/ojdbc-provider-ucp-observability/README.md new file mode 100644 index 00000000..d99747e2 --- /dev/null +++ b/ojdbc-provider-ucp-observability/README.md @@ -0,0 +1,276 @@ +# Oracle JDBC UCP Observability Providers + +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 required. + +--- + +## Contents + +- [Installation](#installation) +- [Activation](#activation) +- [JFR Provider](#jfr-provider) +- [OpenTelemetry Provider](#opentelemetry-provider) +- [Supported UCP event types](#supported-ucp-event-types) +- [Requirements](#requirements) + +--- + +## Installation + +```xml + + + com.oracle.database.jdbc + ojdbc-provider-ucp-observability + 1.0.6 + + +``` + +--- + +## Activation + +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 +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: + +```bash +java -DUCPEventListenerProvider=jfr-ucp-listener -jar myapp.jar +``` + +Or programmatically: + +```java +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. + +--- + +## JFR Provider + +Converts every UCP pool and connection event into a committed JFR event. Zero overhead when no recording is active. + +### Recorded event types + +#### Pool lifecycle + +| JFR event class | Fired when | +|---|---| +| `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` | New connection is created | +| `ConnectionBorrowedEvent` | Connection is borrowed from pool | +| `ConnectionReturnedEvent` | Connection is returned to pool | +| `ConnectionClosedEvent` | Connection is closed | + +#### Maintenance + +| JFR event class | Fired when | +|---|---| +| `PoolRefreshedEvent` | Refresh completes | +| `PoolRecycledEvent` | Recycle completes | +| `PoolPurgedEvent` | Purge completes | + +### Fields recorded on every event + +| Field | Type | Description | +|---|---|---| +| `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` | `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 + +**At JVM start:** + +```bash +java -XX:StartFlightRecording=filename=ucp.jfr,settings=profile -jar myapp.jar +``` + +**At runtime via `jcmd`:** + +```bash +jcmd JFR.start name=ucp settings=profile +jcmd JFR.dump name=ucp filename=ucp.jfr +jcmd JFR.stop name=ucp +``` + +### Analysing events in JDK Mission Control + +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 + +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 + +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"); + } +} +``` + +### Exported metrics + +#### Spec-aligned (`db.client.connection` prefix) + +| Metric name | Instrument | Unit | Description | +|---|---|---|---| +| `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. | + +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 | +|---|---|---|---| +| `db.client.connection.established` | LongGauge | `{connection}` | Cumulative physical connections opened. | +| `db.client.connection.closed` | LongGauge | `{connection}` | Cumulative physical connections closed. | + +#### Common attributes + +| 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:** + +```xml + + + io.opentelemetry + opentelemetry-sdk + 1.44.1 + + + io.opentelemetry + opentelemetry-exporter-prometheus + 1.44.1-alpha + + +``` + +**2. Configure Prometheus:** + +```yaml +scrape_configs: + - job_name: ucp + static_configs: + - targets: ["localhost:9464"] +``` + +**3. Useful PromQL queries:** + +```promql +db_client_connection_usage{db_client_connection_state="used"} +db_client_connection_usage{db_client_connection_state="idle"} +sum by (db_client_connection_pool_name) (db_client_connection_usage) +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 + +| 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 | + +--- + +## Requirements + +| Requirement | Minimum version | +|---|---| +| Oracle UCP | 23.26.1.0.0 | +| Java | 11+ | +| OpenTelemetry API *(OTel provider only)* | 1.44.1 | + +--- + +## 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) diff --git a/ojdbc-provider-ucp-observability/pom.xml b/ojdbc-provider-ucp-observability/pom.xml new file mode 100644 index 00000000..b2924144 --- /dev/null +++ b/ojdbc-provider-ucp-observability/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + + com.oracle.database.jdbc + ojdbc-extensions + 1.0.7 + + + Oracle UCP Observability Provider + com.oracle.database.jdbc + ojdbc-provider-ucp-observability + + + 1.44.1 + 11 + 11 + 11 + + + + + com.oracle.database.jdbc + ucp + 23.26.1.0.0 + + + 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-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 new file mode 100644 index 00000000..a899df0c --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/JFRUCPEventListenerProvider.java @@ -0,0 +1,98 @@ +/* + ** Copyright (c) 2026 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; +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() { + private static final long serialVersionUID = 1L; + + @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-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 new file mode 100644 index 00000000..25fd45ca --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPBaseEvent.java @@ -0,0 +1,120 @@ +/* + ** Copyright (c) 2026 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.*; +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. + */ +@StackTrace(false) +public abstract class UCPBaseEvent extends Event { + + /** Name of the connection pool */ + @Label("Pool Name") + private String poolName; + + /** + * UCP event timestamp in milliseconds since epoch. + * + *

Complements JFR's own {@code startTime} — preserved for correlation. + */ + @Label("UCP Timestamp (ms)") + private long ucpTimestamp; + + /** Maximum configured pool size */ + @Label("Max Pool Size") + private int maxPoolSize; + + /** Minimum configured pool size */ + @Label("Min Pool Size") + private int minPoolSize; + + /** Current count of borrowed connections */ + @Label("Borrowed Connections") + private int borrowedConnections; + + /** Current count of available connections */ + @Label("Available Connections") + private int availableConnections; + + /** Total active connections (borrowed + available) */ + @Label("Total Connections") + private int totalConnections; + + /** Lifetime count of closed connections */ + @Label("Closed Connections") + private int closedConnections; + + /** Lifetime count of created connections */ + @Label("Created Connections") + private int createdConnections; + + /** Average connection wait time in milliseconds */ + @Label("Average Wait Time (ms)") + @Timespan(Timespan.MILLISECONDS) + private 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"); + + String name = ctx.poolName(); + this.poolName = name != null ? name : ""; + 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.createdConnections = ctx.createdConnections(); + 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 new file mode 100644 index 00000000..33d1b5f5 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/core/UCPEventFactory.java @@ -0,0 +1,127 @@ +/* + ** Copyright (c) 2026 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; +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.*; + +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 + * recording. + */ +public final class UCPEventFactory { + + private 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 + * @return configured JFR event, or {@code null} for unrecognized types + * @throws NullPointerException if parameters are null + */ + 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); + + // 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: + LOGGER.fine(() -> + "Unrecognized UCP EventType ignored by JFR provider: " + type); + return null; + } + } + + /** + * 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) { + Objects.requireNonNull(type, "EventType cannot be null"); + Objects.requireNonNull(ctx, "UCPEventContext cannot be null"); + + Event event = createEvent(type, ctx); + + if (event == null) { + return; + } + + if (event.isEnabled()) { + 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 new file mode 100644 index 00000000..622e9fb1 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionBorrowedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..52059252 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionClosedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..089b6631 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionCreatedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..f760f670 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/connection/ConnectionReturnedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..9c9ff667 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolCreatedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..2cbfe7d6 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolDestroyedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..a79c471c --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..5cafebcf --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStartingEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..83b0bf18 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/lifecycle/PoolStoppedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..21bb6f6b --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolPurgedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..6e609c64 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRecycledEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 new file mode 100644 index 00000000..f288759f --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/jfr/events/maintenance/PoolRefreshedEvent.java @@ -0,0 +1,57 @@ +/* + ** Copyright (c) 2026 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; +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 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 new file mode 100644 index 00000000..76f572c5 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/java/oracle/ucp/provider/observability/otel/OtelUCPEventListenerProvider.java @@ -0,0 +1,288 @@ +/* + ** Copyright (c) 2026 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; +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.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectOutputStream; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * OpenTelemetry provider for UCP connection pool metrics. + * + *

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

Spec-aligned metrics

+ *
    + *
  • {@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) — 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) — sourced + * from UCP's cumulative pool-wide average; recorded on {@code CONNECTION_BORROWED} + * only when {@literal >} 0.
  • + *
+ * + *

UCP-specific metrics

+ *
    + *
  • {@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}: 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 { + + private volatile UCPEventListener listener; + + @Override + public String getName() { + return "otel-ucp-listener"; + } + + @Override + public UCPEventListener getListener(Map config) { + // 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) { + listener = new OtelUCPEventListener(); + } + } + } + return listener; + } + + 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"); + + private static final AttributeKey STATE = + AttributeKey.stringKey("db.client.connection.state"); + + // Meter and instruments — instance fields, intentionally NOT static. + // 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"); + + // 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(); + + // 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(); + + // 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( + "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) + // 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. + private final DoubleHistogram waitTime = + meter.histogramBuilder("db.client.connection.wait_time") + .setDescription( + "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) + // 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(); + + // 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 — 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; + 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 — 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."); + } + + @Override + public boolean isDesiredEvent(EventType eventType) { + return true; + } + + @Override + public void onUCPEvent(EventType eventType, UCPEventContext ctx) { + if (eventType == null || ctx == null) { + return; + } + + String poolName = ctx.poolName(); + // ConcurrentHashMap does not permit null keys. + if (poolName == null) { + return; + } + + // 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) { + 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 routing and recording logic separate. + */ + private void recordSnapshot(EventType eventType, UCPEventContext ctx, PoolState state) { + + // 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 — pool lifecycle events only. + // Maintenance events (POOL_REFRESHED, POOL_RECYCLED, POOL_PURGED) are + // excluded; they contribute only a live connection snapshot. + switch (eventType) { + 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, when > 0. + if (eventType == EventType.CONNECTION_BORROWED) { + double avgWaitMs = ctx.getAverageConnectionWaitTime(); + if (avgWaitMs > 0) { + waitTime.record(avgWaitMs / 1000.0, state.attrs); + } + } + + connectionEstablished.set(ctx.createdConnections(), state.attrs); + connectionClosed.set(ctx.closedConnections(), state.attrs); + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..27deb021 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/main/resources/META-INF/services/oracle.ucp.events.core.UCPEventListenerProvider @@ -0,0 +1,2 @@ +oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider +oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider \ No newline at end of file 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 new file mode 100644 index 00000000..8df5d710 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/JFRUCPEventListenerProviderTest.java @@ -0,0 +1,219 @@ +/* + ** Copyright (c) 2026 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; +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.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("JFR UCP Event Listener Provider Tests") +class JFRUCPEventListenerProviderTest { + + private UCPEventContext mockContext; + + @BeforeEach + 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 + @DisplayName("getName() returns the expected provider identifier") + void testGetName() { + assertEquals("jfr-ucp-listener", provider.getName()); + } + + @Test + @DisplayName("getListener() returns a non-null listener") + void testGetListenerNotNull() { + UCPEventListener listener = provider.getListener(Collections.emptyMap()); + assertNotNull(listener); + } + + @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); + } + } + } + + + @Nested + @DisplayName("UCPEventFactory — null safety") + class EventFactoryNullSafetyTests { + + @Test + @DisplayName("recordEvent() throws NullPointerException when EventType is null") + void testRecordEventNullType() { + assertThrows(NullPointerException.class, () -> + UCPEventFactory.recordEvent(null, mockContext)); + } + + @Test + @DisplayName("recordEvent() throws NullPointerException when context is null") + void testRecordEventNullContext() { + assertThrows(NullPointerException.class, () -> + UCPEventFactory.recordEvent(EventType.CONNECTION_BORROWED, null)); + } + } + + @Nested + @DisplayName("UCPEventFactory — event recording") + class EventFactoryRecordingTests { + + @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)); + } + + @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 + @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 + @DisplayName("UCPEventListener — onUCPEvent robustness") + class ListenerOnEventTests { + + @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)); + } + + @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(type, 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 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..65147bd3 --- /dev/null +++ b/ojdbc-provider-ucp-observability/src/test/java/oracle/ucp/provider/observability/OtelUCPEventListenerProviderTest.java @@ -0,0 +1,483 @@ +/* + ** Copyright (c) 2026 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; +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.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.*; + +@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.closedConnections()).thenReturn(1); + when(mockContext.createdConnections()).thenReturn(9); + when(mockContext.getAverageConnectionWaitTime()).thenReturn(42L); + } + + 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); + } + + 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; + } + + @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() { + assertNotNull(provider.getListener(Collections.emptyMap())); + } + + @Test + @DisplayName("getListener() returns a non-null listener when config is null") + void testGetListenerWithNullConfig() { + assertNotNull(provider.getListener(null)); + } + + @Test + @DisplayName("getListener() returns the same instance on repeated calls (singleton)") + void testGetListenerReturnsSameInstance() { + UCPEventListener l1 = provider.getListener(Collections.emptyMap()); + UCPEventListener l2 = provider.getListener(Collections.emptyMap()); + assertSame(l1, l2); + } + + @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)); + } + + @Test + @DisplayName("onUCPEvent() does not throw and reads no fields when poolName() returns null") + void testOnUCPEventNullPoolName() { + when(mockContext.poolName()).thenReturn(null); + assertDoesNotThrow(() -> + listener.onUCPEvent(EventType.CONNECTION_BORROWED, mockContext)); + + verify(mockContext, never()).borrowedConnectionsCount(); + verify(mockContext, never()).availableConnectionsCount(); + verify(mockContext, never()).maxPoolSize(); + verify(mockContext, never()).createdConnections(); + } + } + + @Nested + @DisplayName("UCPEventListener — 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("ZeroPool"); + when(zeroContext.borrowedConnectionsCount()).thenReturn(0); + when(zeroContext.availableConnectionsCount()).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 — context field access") + class ListenerContextAccessTests { + + private UCPEventListener listener; + + @BeforeEach + void setUp() { + listener = new OtelUCPEventListenerProvider() + .getListener(Collections.emptyMap()); + } + + @Test + @DisplayName("poolName() is read exactly once per event — used as metric attribute key") + void testPoolNameReadExactlyOnce() { + listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + verify(mockContext, times(1)).poolName(); + } + + @Test + @DisplayName("borrowedConnectionsCount() and availableConnectionsCount() are read on a non-lifecycle event") + void testUsageFieldsReadOnNonLifecycleEvent() { + listener.onUCPEvent(EventType.CONNECTION_RETURNED, mockContext); + verify(mockContext).borrowedConnectionsCount(); + verify(mockContext).availableConnectionsCount(); + } + + @Test + @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("createdConnections() and closedConnections() are read on a maintenance event") + void testLifetimeFieldsReadOnMaintenanceEvent() { + listener.onUCPEvent(EventType.POOL_REFRESHED, mockContext); + verify(mockContext).createdConnections(); + verify(mockContext).closedConnections(); + } + + @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(); + } + + @Test + @DisplayName("maxPoolSize() and minPoolSize() are read on POOL_DESTROYED") + void testConfigFieldsReadOnPoolDestroyed() { + listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + resetAndRestubContext(); + + listener.onUCPEvent(EventType.POOL_DESTROYED, mockContext); + 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(); + } + + @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(); + } + + @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(mockContext, times(1)).getAverageConnectionWaitTime(); + } + + @Test + @DisplayName("getAverageConnectionWaitTime() is read but recording is suppressed when 0") + void testWaitTimeReadButNotRecordedWhenZero() { + when(mockContext.getAverageConnectionWaitTime()).thenReturn(0L); + assertDoesNotThrow(() -> + 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" + }) + @DisplayName("getAverageConnectionWaitTime() is NOT read on any non-borrow event") + void testWaitTimeNotReadOnNonBorrowEvents(EventType type) { + listener.onUCPEvent(type, mockContext); + verify(mockContext, never()).getAverageConnectionWaitTime(); + } + + + @Test + @DisplayName("totalConnections() is never read — not used by the provider") + void testTotalConnectionsNeverRead() { + for (EventType type : EventType.values()) { + resetAndRestubContext(); + listener.onUCPEvent(type, mockContext); + verify(mockContext, never()).totalConnections(); + } + } + } + + @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() { + + UCPEventContext unknownPool = mock(UCPEventContext.class); + when(unknownPool.poolName()).thenReturn("NeverRegisteredPool"); + + assertDoesNotThrow(() -> + listener.onUCPEvent(EventType.POOL_DESTROYED, unknownPool)); + + 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() { + listener.onUCPEvent(EventType.POOL_CREATED, mockContext); + listener.onUCPEvent(EventType.POOL_DESTROYED, mockContext); + + 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)); + } + } + + @Nested + @DisplayName("UCPEventListener — multi-pool independence") + class ListenerMultiPoolTests { + + private UCPEventListener listener; + + @BeforeEach + void setUp() { + listener = new OtelUCPEventListenerProvider() + .getListener(Collections.emptyMap()); + } + + @Test + @DisplayName("Multiple pools emit events independently without interference") + void testMultiplePoolsIndependent() { + UCPEventContext pool1 = newFullyStubbed("Pool1"); + UCPEventContext pool2 = newFullyStubbed("Pool2"); + + assertDoesNotThrow(() -> { + listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool1); + listener.onUCPEvent(EventType.CONNECTION_BORROWED, pool2); + listener.onUCPEvent(EventType.CONNECTION_RETURNED, pool1); + listener.onUCPEvent(EventType.POOL_DESTROYED, pool2); + }); + + verify(pool1, atLeast(2)).poolName(); // CONNECTION_BORROWED + CONNECTION_RETURNED = 2 calls + verify(pool2, atLeast(2)).poolName(); // CONNECTION_BORROWED + POOL_DESTROYED = 2 calls + } + + @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); + }); + } + + @Test + @DisplayName("Concurrent events from multiple pools do not cause errors") + void testConcurrentMultiPoolEvents() throws Exception { + + 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) { + assertDoesNotThrow(() -> f.get(5, TimeUnit.SECONDS)); + } + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index f0518d1e..7d9d665e 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 @@ -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 @@ -74,6 +74,7 @@ ojdbc-provider-gcp ojdbc-provider-jackson-oson ojdbc-provider-observability + ojdbc-provider-ucp-observability ojdbc-provider-hashicorp ojdbc-provider-pkl @@ -83,7 +84,7 @@ com.oracle.database.jdbc ojdbc-provider-common - 1.0.6 + 1.0.7 com.oracle.database.jdbc @@ -117,7 +118,7 @@ com.oracle.database.jdbc ojdbc-provider-common - 1.0.6 + 1.0.7 tests test-jar test @@ -216,6 +217,7 @@ ${project.basedir}/src/main/resources/META-INF/services/ oracle.jdbc.spi.* + oracle.ucp.events.core.* META-INF/services/