-
Notifications
You must be signed in to change notification settings - Fork 325
Prevent overhead from excessive span creation #3151
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
Changes from all commits
7d2b26d
22b4974
02052e6
7038f14
ac6e143
bb1a8cf
d281bc5
2d78927
1c50894
3677e7b
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 |
|---|---|---|
|
|
@@ -20,11 +20,14 @@ | |
|
|
||
| import co.elastic.apm.agent.MockTracer; | ||
| import co.elastic.apm.agent.TransactionUtils; | ||
| import co.elastic.apm.agent.configuration.CoreConfiguration; | ||
| import co.elastic.apm.agent.impl.ElasticApmTracer; | ||
| import co.elastic.apm.agent.impl.metadata.MetaDataMock; | ||
| import co.elastic.apm.agent.impl.sampling.ConstantSampler; | ||
| import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; | ||
| import co.elastic.apm.agent.report.ApmServerClient; | ||
| import co.elastic.apm.agent.report.serialize.DslJsonSerializer; | ||
| import co.elastic.apm.agent.report.serialize.SerializationConstants; | ||
| import co.elastic.apm.agent.tracer.Outcome; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
@@ -36,6 +39,7 @@ | |
| import java.util.stream.Stream; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.mockito.Mockito.doReturn; | ||
| import static org.mockito.Mockito.mock; | ||
|
|
||
| public class TransactionTest { | ||
|
|
@@ -44,6 +48,9 @@ public class TransactionTest { | |
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| CoreConfiguration coreConfig = MockTracer.createRealTracer().getConfig(CoreConfiguration.class); | ||
| SerializationConstants.init(coreConfig); | ||
|
Comment on lines
+51
to
+52
Member
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. [for reviewer] this was necessary as this test failed when run in isolation, most tests actually trigger this init through instrumentation init. |
||
|
|
||
| jsonSerializer = new DslJsonSerializer( | ||
| mock(StacktraceConfiguration.class), | ||
| mock(ApmServerClient.class), | ||
|
|
@@ -119,6 +126,50 @@ static Stream<Arguments> typeTestArguments() { | |
| ); | ||
| } | ||
|
|
||
| @Test | ||
| void skipChildSpanCreationWhenLimitReached() { | ||
| int limit = 3; | ||
|
|
||
| ElasticApmTracer tracer = MockTracer.createRealTracer(); | ||
| doReturn(limit).when(tracer.getConfig(CoreConfiguration.class)).getTransactionMaxSpans(); | ||
|
|
||
| Transaction transaction = tracer.startRootTransaction(TransactionTest.class.getClassLoader()); | ||
| assertThat(transaction).isNotNull(); | ||
| transaction.activate(); | ||
|
|
||
| int dropped = 7; | ||
| int total = limit + dropped; | ||
| for (int i = 1; i <= total; i++) { | ||
| // emulates an instrumentation that will bypass span creation | ||
| // each call to createSpan is expected to be guarded by a single call to shouldSkipChildSpanCreation | ||
| // for proper dropped span accounting. | ||
|
|
||
| boolean shouldSkip = transaction.shouldSkipChildSpanCreation(); | ||
| assertThat(shouldSkip) | ||
| .describedAs("span %d should be skipped, limit is %d", i, limit) | ||
| .isEqualTo(i > limit); | ||
|
|
||
| if (shouldSkip) { | ||
| assertThat(transaction.getSpanCount().getReported().get()).isEqualTo(limit); | ||
| assertThat(transaction.getSpanCount().getDropped().get()).isEqualTo(i - limit); | ||
| } else { | ||
| transaction.createSpan() | ||
| .withName("child span " + i) | ||
| .activate() | ||
| .deactivate() | ||
| .end(); | ||
|
|
||
| assertThat(transaction.getSpanCount().getReported().get()).isEqualTo(i); | ||
| } | ||
| } | ||
| assertThat(transaction.getSpanCount().getReported().get()).isEqualTo(limit); | ||
| assertThat(transaction.getSpanCount().getDropped().get()).isEqualTo(dropped); | ||
| assertThat(transaction.getSpanCount().getTotal().get()).isEqualTo(total); | ||
|
|
||
| transaction.deactivate().end(); | ||
|
|
||
| } | ||
|
|
||
| /** | ||
| * A utility to enable arbitrary tests to set an existing {@link Transaction} state without making this functionality globally accessible | ||
| * @param recorded should the provided trace context be recorded | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -184,4 +184,17 @@ public interface AbstractSpan<T extends AbstractSpan<T>> extends ElasticContext< | |
| void incrementReferences(); | ||
|
|
||
| void decrementReferences(); | ||
|
|
||
| /** | ||
| * @return {@literal true} when span limit is reached and the caller can optimize and not create a span. The caller | ||
| * is expected to call this method before every span creation operation for proper dropped spans accounting. If not | ||
| * called before attempting span creation, a span will be created and dropped before reporting. | ||
| * <br/> | ||
| * Expected caller behavior depends on the returned value: | ||
| * <ul> | ||
| * <li>{@literal true} returned means the caller is expected to NOT call {@link #createSpan()} or {@link #createExitSpan()}</li> | ||
| * <li>{@literal false} returned means the caller MAY call {@link #createSpan()} or {@link #createExitSpan()}</li> | ||
| * </ul> | ||
| */ | ||
| boolean shouldSkipChildSpanCreation(); | ||
|
Contributor
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 my opinion it would be cleaner to instead alter the contract of However, this would be a lot of work as we need to revisit all
Member
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. That's exactly why I took this simple approach here, and it would have been quite consistent with Another alternative would be to have a way to return a no-op span, which could avoid most of the refactoring but could still have some unexpected side-effects. I think it could be worth investing a bit of time for a refactoring/tech-debt PR. |
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.