-
Notifications
You must be signed in to change notification settings - Fork 358
Expose OpenTelemetry metrics shutdown through MeterProvider #12317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
bm1549
wants to merge
21
commits into
master
Choose a base branch
from
brian.marks/otel-metrics-lifecycle
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
3fd40fc
Add OpenTelemetry metrics lifecycle controls
bm1549 f585a26
Test OpenTelemetry metrics lifecycle delegation
bm1549 31a8c31
Clarify OpenTelemetry metrics lifecycle behavior
mabdinur 7e11776
Merge remote-tracking branch 'origin/master' into HEAD
mabdinur cd8ab82
Expose OpenTelemetry metrics shutdown through MeterProvider
bm1549 6b62b8c
Isolate OpenTelemetry shutdown result callbacks
bm1549 01e1241
Suppress false singleton warning
bm1549 2fd3467
Improve OpenTelemetry shutdown test coverage
bm1549 dc3deb9
test(otel): remove force flush API assertion
mabdinur 0845632
Revert to the simpler AgentTaskScheduler API
mcculls bf1ab75
Move CompletableResultCode to datadog.trace.api
mcculls 0cc199f
Move joining on OTLP shutdown to end of tracer shutdown (avoids seque…
mcculls 1852036
No need to adjust async propagation now we're using the plain AgentTa…
mcculls 485ec18
Minor cleanup
mcculls 728833a
Avoid upfront locking in completed CompletableResultCodes
mcculls dd02a74
Document CompletableResultCode.newResultView
mcculls db3b467
Check scheduler availability for final export during shutdown
mcculls 1617ee6
If scheduler is not available, skip final export rather than start a …
mcculls 9317ab4
Rework PR so we only export then shutdown on request - the default sh…
mcculls 6f58358
Scheduler will shut itself down on JVM shutdown, no need to forcibly …
mcculls 0b47823
Cleanup and de-duplicate tests
mcculls File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
...-1.47/src/test/java/opentelemetry147/metrics/OpenTelemetryMetricsLifecycleForkedTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package opentelemetry147.metrics; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertInstanceOf; | ||
| import static org.junit.jupiter.api.Assertions.assertSame; | ||
|
|
||
| import datadog.trace.agent.test.AbstractInstrumentationTest; | ||
| import datadog.trace.api.CompletableResultCode; | ||
| import datadog.trace.api.metrics.DatadogMeterProvider; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentTracer; | ||
| import datadog.trace.test.junit.utils.config.WithConfig; | ||
| import io.opentelemetry.api.GlobalOpenTelemetry; | ||
| import java.lang.reflect.Proxy; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| @WithConfig(key = "metrics.otel.enabled", value = "true") | ||
| class OpenTelemetryMetricsLifecycleForkedTest extends AbstractInstrumentationTest { | ||
|
|
||
| @Test | ||
| void globalMeterProviderExposesDatadogShutdown() { | ||
| DatadogMeterProvider meterProvider = | ||
| assertInstanceOf(DatadogMeterProvider.class, GlobalOpenTelemetry.get().getMeterProvider()); | ||
| AgentTracer.TracerAPI originalAgentTracer = AgentTracer.get(); | ||
| Object expected = new CompletableResultCode(); | ||
| AgentTracer.TracerAPI replacementAgentTracer = | ||
| (AgentTracer.TracerAPI) | ||
| Proxy.newProxyInstance( | ||
| AgentTracer.TracerAPI.class.getClassLoader(), | ||
| new Class<?>[] {AgentTracer.TracerAPI.class}, | ||
| (proxy, method, arguments) -> | ||
| method.getName().equals("shutdownOtelMetrics") ? expected : null); | ||
|
|
||
| Object result; | ||
| try { | ||
| AgentTracer.forceRegister(replacementAgentTracer); | ||
| result = meterProvider.shutdown(); | ||
| } finally { | ||
| AgentTracer.forceRegister(originalAgentTracer); | ||
| } | ||
|
|
||
| assertSame(expected, result); | ||
| } | ||
| } |
205 changes: 205 additions & 0 deletions
205
dd-trace-api/src/main/java/datadog/trace/api/CompletableResultCode.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| package datadog.trace.api; | ||
|
|
||
| import static java.util.concurrent.TimeUnit.NANOSECONDS; | ||
|
|
||
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| /** Replacement for java.util.concurrent.CompletableFuture without the FJP side effects. */ | ||
| public final class CompletableResultCode { | ||
| private static final CompletableResultCode SUCCESS = new CompletableResultCode(true); | ||
| private static final CompletableResultCode FAILURE = new CompletableResultCode(false); | ||
|
|
||
| private final SharedState sharedState; | ||
| private final boolean resultView; | ||
|
|
||
| private volatile Boolean resultViewSuccess; | ||
| private List<Runnable> callbacks; | ||
|
|
||
| @SuppressFBWarnings( | ||
| value = "SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR", | ||
| justification = "Not a singleton") | ||
| public CompletableResultCode() { | ||
| this(new SharedState(), false); | ||
| } | ||
|
|
||
| private CompletableResultCode(SharedState sharedState, boolean resultView) { | ||
| this.sharedState = sharedState; | ||
| this.resultView = resultView; | ||
| } | ||
|
|
||
| private CompletableResultCode(boolean success) { | ||
| this(); | ||
| complete(success); | ||
| } | ||
|
|
||
| public static CompletableResultCode ofSuccess() { | ||
| return SUCCESS; | ||
| } | ||
|
|
||
| public static CompletableResultCode ofFailure() { | ||
| return FAILURE; | ||
| } | ||
|
|
||
| /** | ||
| * Creates an independent view onto this result's outcome, to hand to a separate caller. A view | ||
| * observes this result's completion, or may complete first on its own, without either side | ||
| * holding a reference to the other. | ||
| * | ||
| * @return a new view sharing this result's outcome | ||
| */ | ||
| public CompletableResultCode newResultView() { | ||
| return new CompletableResultCode(sharedState, true); | ||
| } | ||
|
|
||
| public CompletableResultCode succeed() { | ||
| return complete(true); | ||
| } | ||
|
|
||
| public CompletableResultCode fail() { | ||
| return complete(false); | ||
| } | ||
|
|
||
| public boolean isSuccess() { | ||
| return Boolean.TRUE.equals(outcome()); | ||
| } | ||
|
|
||
| public boolean isDone() { | ||
| return outcome() != null; | ||
| } | ||
|
|
||
| /** | ||
| * Registers an action to run on the completing thread. If this result is already complete, the | ||
| * action runs immediately on the calling thread. | ||
| * | ||
| * @param callback action to run after completion | ||
| * @return this result | ||
| * @throws NullPointerException if {@code callback} is {@code null} | ||
| */ | ||
| public CompletableResultCode whenComplete(Runnable callback) { | ||
| Objects.requireNonNull(callback, "callback"); | ||
| if (outcome() == null) { | ||
| synchronized (sharedState) { | ||
| if (outcome() == null) { | ||
| if (callbacks == null) { | ||
| callbacks = new ArrayList<>(); | ||
| if (sharedState.callbackResults == null) { | ||
| sharedState.callbackResults = new ArrayList<>(); | ||
| } | ||
| sharedState.callbackResults.add(this); | ||
| } | ||
| callbacks.add(callback); | ||
| return this; | ||
| } | ||
| } | ||
| } | ||
| callback.run(); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Waits up to the timeout for completion and returns this result. A timeout does not complete or | ||
| * cancel the operation; use {@link #isDone()} and {@link #isSuccess()} to inspect the outcome. | ||
| * | ||
| * @param timeout maximum time to wait | ||
| * @param unit unit of the timeout | ||
| * @return this result, which may still be incomplete after the timeout | ||
| */ | ||
| public CompletableResultCode join(long timeout, TimeUnit unit) { | ||
| if (outcome() == null) { | ||
| synchronized (sharedState) { | ||
| if (outcome() == null) { | ||
| long remainingNanos = Objects.requireNonNull(unit, "unit").toNanos(timeout); | ||
| while (outcome() == null && remainingNanos > 0) { | ||
| long start = System.nanoTime(); | ||
| try { | ||
| NANOSECONDS.timedWait(sharedState, remainingNanos); | ||
| } catch (InterruptedException ignored) { | ||
| Thread.currentThread().interrupt(); | ||
| break; | ||
| } | ||
| remainingNanos -= Math.max(1, System.nanoTime() - start); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return this; | ||
| } | ||
|
|
||
| private CompletableResultCode complete(boolean succeeded) { | ||
| List<Runnable> completionCallbacks; | ||
| if (outcome() != null) { | ||
| return this; | ||
| } | ||
| synchronized (sharedState) { | ||
| if (outcome() != null) { | ||
| return this; | ||
| } | ||
|
|
||
| if (resultView) { | ||
| resultViewSuccess = succeeded; | ||
| completionCallbacks = callbacks; | ||
| callbacks = null; | ||
| removeCallbackResult(); | ||
| } else { | ||
| sharedState.success = succeeded; | ||
| completionCallbacks = collectCallbacks(); | ||
| } | ||
| sharedState.notifyAll(); | ||
| } | ||
|
|
||
| Throwable firstFailure = null; | ||
| if (completionCallbacks != null) { | ||
| for (Runnable callback : completionCallbacks) { | ||
| try { | ||
| callback.run(); | ||
| } catch (RuntimeException | Error failure) { | ||
| if (firstFailure == null) { | ||
| firstFailure = failure; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (firstFailure instanceof RuntimeException) { | ||
| throw (RuntimeException) firstFailure; | ||
| } | ||
| if (firstFailure != null) { | ||
| throw (Error) firstFailure; | ||
| } | ||
| return this; | ||
| } | ||
|
|
||
| private Boolean outcome() { | ||
| return resultView && resultViewSuccess != null ? resultViewSuccess : sharedState.success; | ||
| } | ||
|
|
||
| private List<Runnable> collectCallbacks() { | ||
| if (sharedState.callbackResults == null) { | ||
| return null; | ||
| } | ||
| List<Runnable> completionCallbacks = new ArrayList<>(); | ||
| for (CompletableResultCode result : sharedState.callbackResults) { | ||
| completionCallbacks.addAll(result.callbacks); | ||
| result.callbacks = null; | ||
| } | ||
| sharedState.callbackResults = null; | ||
| return completionCallbacks; | ||
| } | ||
|
|
||
| private void removeCallbackResult() { | ||
| if (sharedState.callbackResults != null) { | ||
| sharedState.callbackResults.remove(this); | ||
| if (sharedState.callbackResults.isEmpty()) { | ||
| sharedState.callbackResults = null; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static final class SharedState { | ||
| private volatile Boolean success; | ||
| private List<CompletableResultCode> callbackResults; | ||
| } | ||
| } | ||
20 changes: 20 additions & 0 deletions
20
dd-trace-api/src/main/java/datadog/trace/api/metrics/DatadogMeterProvider.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package datadog.trace.api.metrics; | ||
|
|
||
| import datadog.trace.api.CompletableResultCode; | ||
|
|
||
| /** | ||
| * Datadog lifecycle controls implemented by the {@code MeterProvider} returned from {@code | ||
| * GlobalOpenTelemetry} when Datadog OpenTelemetry metrics support is enabled. | ||
| */ | ||
| public interface DatadogMeterProvider { | ||
|
|
||
| /** | ||
| * Performs a final export and stops Datadog's OpenTelemetry metrics pipeline. Repeated calls | ||
| * observe the first result. | ||
| * | ||
| * <p>A timed join bounds only the caller and does not cancel shutdown. | ||
| * | ||
| * @return the shutdown result; an unavailable or disabled pipeline succeeds as a no-op | ||
| */ | ||
| CompletableResultCode shutdown(); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.