Skip to content

Commit 7075bf5

Browse files
Add composite UCP observability listener provider
1 parent 564ed23 commit 7075bf5

4 files changed

Lines changed: 247 additions & 8 deletions

File tree

ojdbc-provider-ucp-observability/README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Oracle JDBC UCP Observability Providers
22

33
This module contains providers that add observability capabilities to Oracle
4-
Universal Connection Pool (UCP). Two providers are available:
4+
Universal Connection Pool (UCP). Three listener providers are available:
55

66
* JFR: exports UCP pool and connection lifecycle events to Java Flight Recorder.
77
* OTEL: publishes UCP connection pool metrics through OpenTelemetry.
8+
* Observability: exports the same UCP event stream to both JFR and OTEL.
89

910
These providers implement the `UCPEventListenerProvider` interface provided by
1011
UCP. They are notified when UCP emits pool, connection, and maintenance events,
@@ -20,10 +21,11 @@ The exported telemetry includes:
2021

2122
| Provider | Listener name | Backend |
2223
|---|---|---|
24+
| `ObservabilityUCPEventListenerProvider` | `ucp-observability-listener` | Java Flight Recorder (JFR) and OpenTelemetry (metrics) |
2325
| `JFRUCPEventListenerProvider` | `jfr-ucp-listener` | Java Flight Recorder (JFR) |
2426
| `OtelUCPEventListenerProvider` | `otel-ucp-listener` | OpenTelemetry (metrics) |
2527

26-
Both providers are discovered automatically via `java.util.ServiceLoader`;
28+
All providers are discovered automatically via `java.util.ServiceLoader`;
2729
activate one by setting the UCP listener provider name on the pool or through
2830
the JVM system property.
2931

@@ -56,25 +58,26 @@ activation modes are supported, in priority order:
5658

5759
```java
5860
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
59-
pds.setUCPEventListenerProvider("jfr-ucp-listener");
60-
// pds.setUCPEventListenerProvider("otel-ucp-listener");
61+
pds.setUCPEventListenerProvider("ucp-observability-listener");
6162
```
6263

6364
**2. JVM system property** — applies globally to all pools in the JVM:
6465

6566
```bash
66-
java -DUCPEventListenerProvider=jfr-ucp-listener -jar myapp.jar
67+
java -DUCPEventListenerProvider=ucp-observability-listener -jar myapp.jar
6768
```
6869

6970
Or programmatically:
7071

7172
```java
72-
System.setProperty("UCPEventListenerProvider", "jfr-ucp-listener");
73-
// System.setProperty("UCPEventListenerProvider", "otel-ucp-listener");
73+
System.setProperty("UCPEventListenerProvider", "ucp-observability-listener");
7474
```
7575

7676
If no provider is configured, UCP uses its default no-op behavior.
7777

78+
Use `ucp-observability-listener` to enable both JFR and OpenTelemetry. Use
79+
`jfr-ucp-listener` for JFR only, or `otel-ucp-listener` for OpenTelemetry only.
80+
7881
---
7982

8083
## JFR Provider
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
** Copyright (c) 2026 Oracle and/or its affiliates.
3+
**
4+
** The Universal Permissive License (UPL), Version 1.0
5+
**
6+
** Subject to the condition set forth below, permission is hereby granted to any
7+
** person obtaining a copy of this software, associated documentation and/or data
8+
** (collectively the "Software"), free of charge and under any and all copyright
9+
** rights in the Software, and any and all patent rights owned or freely
10+
** licensable by each licensor hereunder covering either (i) the unmodified
11+
** Software as contributed to or provided by such licensor, or (ii) the Larger
12+
** Works (as defined below), to deal in both
13+
**
14+
** (a) the Software, and
15+
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
** one is included with the Software (each a "Larger Work" to which the
17+
** Software is contributed by such licensors),
18+
**
19+
** without restriction, including without limitation the rights to copy, create
20+
** derivative works of, display, perform, and distribute the Software and make,
21+
** use, sell, offer for sale, import, export, have made, and have sold the
22+
** Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
** either these or other terms.
24+
**
25+
** This license is subject to the following condition:
26+
** The above copyright notice and either this complete permission notice or at
27+
** a minimum a reference to the UPL must be included in all copies or
28+
** substantial portions of the Software.
29+
**
30+
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
** SOFTWARE.
37+
*/
38+
39+
package oracle.ucp.provider.observability;
40+
41+
import oracle.ucp.events.core.UCPEventContext;
42+
import oracle.ucp.events.core.UCPEventListener;
43+
import oracle.ucp.events.core.UCPEventListenerProvider;
44+
import oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider;
45+
import oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider;
46+
47+
import java.util.Map;
48+
import java.util.Objects;
49+
50+
/**
51+
* Composite UCP observability provider that publishes events to both JFR and
52+
* OpenTelemetry.
53+
*
54+
* <p>Use {@code ucp-observability-listener} when both backends should receive
55+
* the same UCP event stream. Use {@code jfr-ucp-listener} or
56+
* {@code otel-ucp-listener} when only one backend is needed.
57+
*/
58+
public final class ObservabilityUCPEventListenerProvider
59+
implements UCPEventListenerProvider {
60+
61+
private static final String PROVIDER_NAME = "ucp-observability-listener";
62+
63+
private final UCPEventListenerProvider jfrProvider;
64+
private final UCPEventListenerProvider otelProvider;
65+
private volatile UCPEventListener listener;
66+
67+
/**
68+
* Creates a provider that delegates to the built-in JFR and OpenTelemetry
69+
* UCP listeners.
70+
*/
71+
public ObservabilityUCPEventListenerProvider() {
72+
this(
73+
new JFRUCPEventListenerProvider(),
74+
new OtelUCPEventListenerProvider());
75+
}
76+
77+
ObservabilityUCPEventListenerProvider(
78+
UCPEventListenerProvider jfrProvider,
79+
UCPEventListenerProvider otelProvider) {
80+
this.jfrProvider = Objects.requireNonNull(
81+
jfrProvider, "jfrProvider cannot be null");
82+
this.otelProvider = Objects.requireNonNull(
83+
otelProvider, "otelProvider cannot be null");
84+
}
85+
86+
@Override
87+
public String getName() {
88+
return PROVIDER_NAME;
89+
}
90+
91+
@Override
92+
public UCPEventListener getListener(Map<String, String> config) {
93+
if (listener == null) {
94+
synchronized (this) {
95+
if (listener == null) {
96+
listener = new CompositeUCPEventListener(
97+
jfrProvider.getListener(config),
98+
otelProvider.getListener(config));
99+
}
100+
}
101+
}
102+
return listener;
103+
}
104+
105+
private static final class CompositeUCPEventListener
106+
implements UCPEventListener {
107+
108+
private static final long serialVersionUID = 1L;
109+
110+
private final UCPEventListener jfrListener;
111+
private final UCPEventListener otelListener;
112+
113+
private CompositeUCPEventListener(
114+
UCPEventListener jfrListener,
115+
UCPEventListener otelListener) {
116+
this.jfrListener = Objects.requireNonNull(
117+
jfrListener, "jfrListener cannot be null");
118+
this.otelListener = Objects.requireNonNull(
119+
otelListener, "otelListener cannot be null");
120+
}
121+
122+
@Override
123+
public boolean isDesiredEvent(EventType eventType) {
124+
return jfrListener.isDesiredEvent(eventType)
125+
|| otelListener.isDesiredEvent(eventType);
126+
}
127+
128+
@Override
129+
public void onUCPEvent(EventType eventType, UCPEventContext ctx) {
130+
jfrListener.onUCPEvent(eventType, ctx);
131+
otelListener.onUCPEvent(eventType, ctx);
132+
}
133+
}
134+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
oracle.ucp.provider.observability.jfr.core.JFRUCPEventListenerProvider
2-
oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider
2+
oracle.ucp.provider.observability.otel.OtelUCPEventListenerProvider
3+
oracle.ucp.provider.observability.ObservabilityUCPEventListenerProvider
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
** Copyright (c) 2026 Oracle and/or its affiliates.
3+
**
4+
** The Universal Permissive License (UPL), Version 1.0
5+
**
6+
** Subject to the condition set forth below, permission is hereby granted to any
7+
** person obtaining a copy of this software, associated documentation and/or data
8+
** (collectively the "Software"), free of charge and under any and all copyright
9+
** rights in the Software, and any and all patent rights owned or freely
10+
** licensable by each licensor hereunder covering either (i) the unmodified
11+
** Software as contributed to or provided by such licensor, or (ii) the Larger
12+
** Works (as defined below), to deal in both
13+
**
14+
** (a) the Software, and
15+
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
** one is included with the Software (each a "Larger Work" to which the
17+
** Software is contributed by such licensors),
18+
**
19+
** without restriction, including without limitation the rights to copy, create
20+
** derivative works of, display, perform, and distribute the Software and make,
21+
** use, sell, offer for sale, import, export, have made, and have sold the
22+
** Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
** either these or other terms.
24+
**
25+
** This license is subject to the following condition:
26+
** The above copyright notice and either this complete permission notice or at
27+
** a minimum a reference to the UPL must be included in all copies or
28+
** substantial portions of the Software.
29+
**
30+
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
** SOFTWARE.
37+
*/
38+
39+
package oracle.ucp.provider.observability;
40+
41+
import oracle.ucp.events.core.UCPEventContext;
42+
import oracle.ucp.events.core.UCPEventListener;
43+
import oracle.ucp.events.core.UCPEventListener.EventType;
44+
import oracle.ucp.events.core.UCPEventListenerProvider;
45+
import org.junit.jupiter.api.DisplayName;
46+
import org.junit.jupiter.api.Test;
47+
48+
import java.util.Collections;
49+
50+
import static org.junit.jupiter.api.Assertions.*;
51+
import static org.mockito.Mockito.*;
52+
53+
@DisplayName("Composite UCP Observability Event Listener Provider Tests")
54+
class ObservabilityUCPEventListenerProviderTest {
55+
56+
@Test
57+
@DisplayName("getName() returns the composite provider identifier")
58+
void testGetName() {
59+
ObservabilityUCPEventListenerProvider provider =
60+
new ObservabilityUCPEventListenerProvider();
61+
62+
assertEquals("ucp-observability-listener", provider.getName());
63+
}
64+
65+
@Test
66+
@DisplayName("getListener() returns the same composite listener instance")
67+
void testGetListenerReturnsSameInstance() {
68+
ObservabilityUCPEventListenerProvider provider =
69+
new ObservabilityUCPEventListenerProvider();
70+
71+
UCPEventListener listener1 = provider.getListener(Collections.emptyMap());
72+
UCPEventListener listener2 = provider.getListener(null);
73+
74+
assertNotNull(listener1);
75+
assertSame(listener1, listener2);
76+
}
77+
78+
@Test
79+
@DisplayName("onUCPEvent() delegates the event to JFR and OTel listeners")
80+
void testOnUCPEventDelegatesToBothListeners() {
81+
UCPEventListener jfr = mock(UCPEventListener.class);
82+
UCPEventListener otel = mock(UCPEventListener.class);
83+
UCPEventContext ctx = mock(UCPEventContext.class);
84+
85+
ObservabilityUCPEventListenerProvider provider =
86+
new ObservabilityUCPEventListenerProvider(
87+
providerReturning(jfr), providerReturning(otel));
88+
89+
provider.getListener(null).onUCPEvent(EventType.POOL_REFRESHED, ctx);
90+
91+
verify(jfr).onUCPEvent(EventType.POOL_REFRESHED, ctx);
92+
verify(otel).onUCPEvent(EventType.POOL_REFRESHED, ctx);
93+
}
94+
95+
private static UCPEventListenerProvider providerReturning(
96+
UCPEventListener listener) {
97+
UCPEventListenerProvider provider = mock(UCPEventListenerProvider.class);
98+
when(provider.getListener(any())).thenReturn(listener);
99+
return provider;
100+
}
101+
}

0 commit comments

Comments
 (0)