-
Notifications
You must be signed in to change notification settings - Fork 550
fix(tracer): flush telemetry when Stop is called #5345
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
base: main
Are you sure you want to change the base?
Changes from all commits
349e457
dd3b0a8
08fc3bf
0c477b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,7 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/DataDog/dd-trace-go/v2/internal/orchestrion" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/DataDog/dd-trace-go/v2/internal/telemetry" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/DataDog/dd-trace-go/v2/internal/telemetry/telemetrytest" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/DataDog/dd-trace-go/v2/internal/traceprof" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/DataDog/dd-trace-go/v2/profiler" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/stretchr/testify/assert" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -232,3 +233,64 @@ func TestRepeatStartRecordsEnvDiffOnActiveClient(t *testing.T) { | |||||||||||||||||||||||||||||||||||||||||||||||||
| require.True(t, ok, "expected config.repeat_start_env_diff to be recorded on the active telemetry client") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assert.Equal(t, float64(1), handle.Get()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestTracerStopFlushesTelemetry(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Start() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| defer globalconfig.SetServiceName("") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| require.NotNil(t, telemetry.GlobalClient()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| assert.Nil(t, telemetry.GlobalClient()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestTracerStopDoesNotStopForeignTelemetry(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| telemetryClient := new(telemetrytest.RecordClient) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| defer telemetry.MockClient(telemetryClient)() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Start() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| defer globalconfig.SetServiceName("") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Profiler or another product already owns the global client. Stop must | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // not call StopApp on it. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assert.False(t, telemetryClient.Stopped) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assert.Equal(t, telemetry.Client(telemetryClient), telemetry.GlobalClient()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // ProductStopped(tracers) must still propagate to the foreign client | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // (ProductStarted set it true during Start; Stop sets it back to false). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assert.False(t, telemetryClient.Products[telemetry.NamespaceTracers]) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestTracerStopKeepsTelemetryWhenProfilerStillRunning(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Start() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| defer globalconfig.SetServiceName("") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| require.NotNil(t, telemetry.GlobalClient()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| wasEnabled := traceprof.SetProfilerEnabled(true) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| traceprof.SetProfilerEnabled(wasEnabled) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| telemetry.StopApp() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Profiler started after the tracer and still shares the client, so Stop | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // must flush without emitting app-stopped / clearing the global client. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assert.NotNil(t, telemetry.GlobalClient()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+277
to
+280
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The three tests cover tracer-only, foreign-owner, and profiler-still-running, but skip the
Suggested change
Two other gaps I'd leave for a follow-up rather than this PR: the telemetry-disabled path
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added TestTracerStopStopsTelemetryAfterProfilerStopped |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestTracerStopStopsTelemetryAfterProfilerStopped(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Start() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| defer globalconfig.SetServiceName("") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| require.NotNil(t, telemetry.GlobalClient()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| wasEnabled := traceprof.SetProfilerEnabled(true) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| defer traceprof.SetProfilerEnabled(wasEnabled) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // The profiler started after the tracer, then stopped before tracer.Stop(). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| traceprof.SetProfilerEnabled(false) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Stop() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Nobody else needs the client anymore, so Stop must fully stop the app. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assert.Nil(t, telemetry.GlobalClient()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -132,6 +132,8 @@ type tracer struct { | |||||||||||||||||||
|
|
||||||||||||||||||||
| // stopOnce ensures the tracer is stopped exactly once. | ||||||||||||||||||||
| stopOnce sync.Once | ||||||||||||||||||||
| // telemetryStopOnce ensures telemetry shutdown runs once across concurrent Stop calls. | ||||||||||||||||||||
| telemetryStopOnce sync.Once | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // wg waits for all goroutines to exit when stopping. | ||||||||||||||||||||
| wg sync.WaitGroup | ||||||||||||||||||||
|
|
@@ -1271,13 +1273,39 @@ func (t *tracer) Stop() { | |||||||||||||||||||
| } | ||||||||||||||||||||
| appsec.Stop() | ||||||||||||||||||||
| remoteconfig.Stop() | ||||||||||||||||||||
| // Flush telemetry before closing the log file so StopApp diagnostics still land. | ||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This block is the tracer/profiler ownership handshake for the global telemetry client, but Until then, a pointer from this block to that plan would help the next refactor find it:
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment now points at the newTracer APMAPI-1771 TODO |
||||||||||||||||||||
| // Interim tracer/profiler ownership handshake for the global telemetry client; | ||||||||||||||||||||
| // to be superseded by shared client control (see TODO at newTracer, APMAPI-1771). | ||||||||||||||||||||
| t.telemetryStopOnce.Do(func() { | ||||||||||||||||||||
| client := t.telemetry | ||||||||||||||||||||
| t.telemetry = nil | ||||||||||||||||||||
| if client == nil { | ||||||||||||||||||||
| return | ||||||||||||||||||||
| } | ||||||||||||||||||||
| telemetry.ProductStopped(telemetry.NamespaceTracers) | ||||||||||||||||||||
| if telemetry.GlobalClient() != client { | ||||||||||||||||||||
| // StartApp ignored this client because another product already owns | ||||||||||||||||||||
| // the global one. Close the leftover so its ticker does not leak. | ||||||||||||||||||||
| client.Close() | ||||||||||||||||||||
| return | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if traceprof.ProfilerEnabled() { | ||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check has a small TOCTOU window against profiler.Start(). SetProfilerEnabled(true) This race is pre-existing (main closes the same shared client unconditionally in that
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. raised SetProfilerEnabled(true) before run() so the window is closed
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check also trusts a flag that can go stale. In profiler.Start() (profiler/profiler.go:87-89),
In both sequences no profiler is running, but this branch sees ProfilerEnabled() == true, The staleness is fixable in five lines, in profiler.Start's stop-old-profiler branch: That makes the flag strictly track "a profiler is assigned and running" on every early return,
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. clearing activeProfiler + SetProfilerEnabled(false) in the stop-old branch now, plus a restart-disabled test |
||||||||||||||||||||
| // Profiler started after us and still shares this client. Mark the | ||||||||||||||||||||
| // tracer product stopped and flush, but leave the app running. | ||||||||||||||||||||
|
Comment on lines
+1293
to
+1294
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this branch the app is deliberately left running because the profiler still
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added the TODO as suggested |
||||||||||||||||||||
| // TODO: profiler.Stop() never stops telemetry, so when the profiler is | ||||||||||||||||||||
| // the last product to stop, app-stopped is never sent and the client's | ||||||||||||||||||||
| // ticker goroutine lives until process exit. Follow-up: profiler.Stop() | ||||||||||||||||||||
| // should send ProductStopped(NamespaceProfilers) and StopApp() when it | ||||||||||||||||||||
| // owns the global client. | ||||||||||||||||||||
| client.Flush() | ||||||||||||||||||||
| return | ||||||||||||||||||||
| } | ||||||||||||||||||||
| telemetry.StopApp() | ||||||||||||||||||||
| }) | ||||||||||||||||||||
| // Close log file last to account for any logs from the above calls | ||||||||||||||||||||
| if t.logFile != nil { | ||||||||||||||||||||
| t.logFile.Close() | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if t.telemetry != nil { | ||||||||||||||||||||
| t.telemetry.Close() | ||||||||||||||||||||
| } | ||||||||||||||||||||
| t.config.httpClient.CloseIdleConnections() | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These assertions prove StopApp wasn't called on the foreign client — good — but none of the
three tests observe the behavior the PR is named after. assert.Nil(GlobalClient()) in
TestTracerStopFlushesTelemetry proves StopApp ran, not that the queue flushed; and here the
leftover-client Close() (the ticker-leak fix the code comment describes) is unverified,
because RecordClient.Flush() is a no-op (telemetrytest/record.go:216) and Close() records
nothing (record.go:44-46).
RecordClient.Products does record ProductStopped — worth asserting here to pin down that the
tracer's product-stopped still propagates to the foreign-owned client:
If you also want the flush and leftover-close directly observable, that needs a small
telemetrytest extension (e.g. Flushes int / Closed bool on RecordClient) — happy to review
that as a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the ProductStopped assert. leaving Flushes/Closed on RecordClient for a follow up