diff --git a/.circleci/config.continue.yml.j2 b/.circleci/config.continue.yml.j2 index 026026ea390..5d1486aa3c6 100644 --- a/.circleci/config.continue.yml.j2 +++ b/.circleci/config.continue.yml.j2 @@ -36,7 +36,7 @@ instrumentation_modules: &instrumentation_modules "dd-java-agent/instrumentation debugger_modules: &debugger_modules "dd-java-agent/agent-debugger|dd-java-agent/agent-bootstrap|dd-java-agent/agent-builder|internal-api|communication|dd-trace-core" profiling_modules: &profiling_modules "dd-java-agent/agent-profiling" -default_system_tests_commit: &default_system_tests_commit 2487cea5160a398549743d2cfd927a863792e3bd +default_system_tests_commit: &default_system_tests_commit 6998b307ce0e8d206ca6eb99045651dcc8bc853f parameters: nightly: @@ -735,6 +735,7 @@ jobs: name: Generate muzzle dep report command: >- SKIP_BUILDSCAN="true" + GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx2G -Xms2G -XX:ErrorFile=/tmp/hs_err_pid%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp'" ./gradlew generateMuzzleReport muzzleInstrumentationReport - run: name: Collect Reports diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 658580cf016..b8e6662f7c7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,6 +19,7 @@ dd-java-agent/instrumentation/cucumber/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/jacoco/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/junit-4.10/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/junit-5.3/ @DataDog/ci-app-libraries-java +dd-java-agent/instrumentation/karate/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/testng/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/gradle/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/maven-3.2.1/ @DataDog/ci-app-libraries-java @@ -36,6 +37,9 @@ dd-smoke-tests/debugger-integration-tests/ @DataDog/debugger-java dd-java-agent/agent-iast/ @DataDog/asm-java dd-java-agent/instrumentation/*iast* @DataDog/asm-java dd-java-agent/instrumentation/*appsec* @DataDog/asm-java +dd-java-agent/instrumentation/json/ @DataDog/asm-java +dd-smoke-tests/iast-util/ @DataDog/asm-java +dd-java-agent/instrumentation/commons-fileupload/ @DataDog/asm-java **/appsec/ @DataDog/asm-java **/iast/ @DataDog/asm-java **/Iast*.java @DataDog/asm-java diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3be67d8c675..1ed6915a8ff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -82,6 +82,12 @@ package: script: - ../.gitlab/build_java_package.sh +package-arm: + extends: .package-arm + when: on_success # this can't use 'needs: [build]', since build is not available in the scheduled pipeline + script: + - ../.gitlab/build_java_package.sh + .release-package: stage: deploy variables: diff --git a/communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java b/communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java index e00b1260e04..5491f999da2 100644 --- a/communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java +++ b/communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java @@ -39,10 +39,15 @@ public void createRemaining(Config config) { String namedPipe = config.getAgentNamedPipe(); okHttpClient = OkHttpUtils.buildHttpClient( - agentUrl, - unixDomainSocket, - namedPipe, - TimeUnit.SECONDS.toMillis(config.getAgentTimeout())); + agentUrl, unixDomainSocket, namedPipe, getHttpClientTimeout(config)); + } + } + + private static long getHttpClientTimeout(Config config) { + if (!config.isCiVisibilityEnabled() || !config.isCiVisibilityAgentlessEnabled()) { + return TimeUnit.SECONDS.toMillis(config.getAgentTimeout()); + } else { + return config.getCiVisibilityBackendApiTimeoutMillis(); } } diff --git a/communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java b/communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java index 0455e2ec3ad..e4dbe099ca2 100644 --- a/communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java +++ b/communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java @@ -115,7 +115,6 @@ private void doConnect() { // when using UDS, set "entity-id" to "none" to avoid having the DogStatsD // server add origin tags (see https://github.com/DataDog/jmxfetch/pull/264) if (this.port == 0) { - clientBuilder.constantTags("dd.internal.card:none"); clientBuilder.entityID("none"); } else { clientBuilder.entityID(null); diff --git a/dd-java-agent/agent-ci-visibility/build.gradle b/dd-java-agent/agent-ci-visibility/build.gradle index 9917f17cbdb..906c1cca383 100644 --- a/dd-java-agent/agent-ci-visibility/build.gradle +++ b/dd-java-agent/agent-ci-visibility/build.gradle @@ -27,11 +27,12 @@ excludedClassesCoverage += [ "datadog.trace.civisibility.ci.CIInfo", "datadog.trace.civisibility.ci.CIInfo.Builder", "datadog.trace.civisibility.communication.*", - "datadog.trace.civisibility.config.JvmInfoFactory", + "datadog.trace.civisibility.config.CachingJvmInfoFactory", + "datadog.trace.civisibility.config.JvmInfoFactoryImpl", + "datadog.trace.civisibility.config.JvmInfoFactoryImpl.JvmVersionOutputParser", "datadog.trace.civisibility.config.ConfigurationApi", "datadog.trace.civisibility.config.ModuleExecutionSettingsFactory", "datadog.trace.civisibility.config.JvmInfo", - "datadog.trace.civisibility.config.JvmInfoFactory.JvmVersionOutputParser", "datadog.trace.civisibility.config.CachingModuleExecutionSettingsFactory", "datadog.trace.civisibility.config.CachingModuleExecutionSettingsFactory.Key", "datadog.trace.civisibility.config.CiVisibilitySettings", @@ -73,6 +74,7 @@ excludedClassesCoverage += [ "datadog.trace.civisibility.source.index.RepoIndexBuilder.RepoIndexingFileVisitor", "datadog.trace.civisibility.source.index.RepoIndexFetcher", "datadog.trace.civisibility.source.index.RepoIndexSourcePathResolver", + "datadog.trace.civisibility.source.Utils", "datadog.trace.civisibility.utils.ShellCommandExecutor", "datadog.trace.civisibility.utils.ShellCommandExecutor.OutputParser", "datadog.trace.civisibility.utils.SpanUtils" diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java index 7f0c82c9304..af0727d51cb 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java @@ -1,6 +1,7 @@ package datadog.trace.civisibility; import datadog.communication.ddagent.SharedCommunicationObjects; +import datadog.communication.ddagent.TracerVersion; import datadog.trace.api.Config; import datadog.trace.api.civisibility.CIVisibility; import datadog.trace.api.civisibility.InstrumentationBridge; @@ -17,10 +18,12 @@ import datadog.trace.civisibility.codeowners.CodeownersProvider; import datadog.trace.civisibility.communication.BackendApi; import datadog.trace.civisibility.communication.BackendApiFactory; +import datadog.trace.civisibility.config.CachingJvmInfoFactory; import datadog.trace.civisibility.config.CachingModuleExecutionSettingsFactory; import datadog.trace.civisibility.config.ConfigurationApi; import datadog.trace.civisibility.config.ConfigurationApiImpl; import datadog.trace.civisibility.config.JvmInfoFactory; +import datadog.trace.civisibility.config.JvmInfoFactoryImpl; import datadog.trace.civisibility.config.ModuleExecutionSettingsFactory; import datadog.trace.civisibility.config.ModuleExecutionSettingsFactoryImpl; import datadog.trace.civisibility.coverage.CoverageProbeStoreFactory; @@ -52,6 +55,7 @@ import datadog.trace.civisibility.source.index.RepoIndexProvider; import datadog.trace.civisibility.source.index.RepoIndexSourcePathResolver; import datadog.trace.util.Strings; +import datadog.trace.util.throwable.FatalAgentMisconfigurationError; import java.net.InetSocketAddress; import java.nio.file.FileSystems; import java.nio.file.Path; @@ -74,6 +78,19 @@ public static void start(SharedCommunicationObjects sco) { return; } + String injectedTracerVersion = config.getCiVisibilityInjectedTracerVersion(); + if (injectedTracerVersion != null + && !injectedTracerVersion.equals(TracerVersion.TRACER_VERSION)) { + throw new FatalAgentMisconfigurationError( + "Running JVM with tracer version " + + TracerVersion.TRACER_VERSION + + " however parent process attempted to inject " + + injectedTracerVersion + + ". Do not inject the tracer into the forked JVMs manually, or ensure the manually injected version is the same as the one injected automatically"); + } + + sco.createRemaining(config); + GitClient.Factory gitClientFactory = buildGitClientFactory(config); CoverageProbeStoreFactory coverageProbeStoreFactory = buildTestProbesFactory(config); @@ -117,10 +134,11 @@ private static BuildEventsHandler.Factory buildEventsHandlerFactory( DDBuildSystemSession.Factory sessionFactory = buildSystemSessionFactory( config, sco, gitInfoProvider, coverageProbeStoreFactory, gitClientFactory); + JvmInfoFactory jvmInfoFactory = new CachingJvmInfoFactory(config, new JvmInfoFactoryImpl()); return new BuildEventsHandler.Factory() { @Override public BuildEventsHandler create() { - return new BuildEventsHandlerImpl<>(sessionFactory, new JvmInfoFactory()); + return new BuildEventsHandlerImpl<>(sessionFactory, jvmInfoFactory); } }; } @@ -132,8 +150,6 @@ private static DDBuildSystemSession.Factory buildSystemSessionFactory( CoverageProbeStoreFactory coverageProbeStoreFactory, GitClient.Factory gitClientFactory) { BackendApiFactory backendApiFactory = new BackendApiFactory(config, sco); - BackendApi backendApi = backendApiFactory.createBackendApi(); - return (String projectName, Path projectRoot, String startCommand, @@ -163,6 +179,7 @@ private static DDBuildSystemSession.Factory buildSystemSessionFactory( TestDecorator testDecorator = new TestDecoratorImpl(buildSystemName, ciTags); TestModuleRegistry testModuleRegistry = new TestModuleRegistry(); + BackendApi backendApi = backendApiFactory.createBackendApi(); GitDataUploader gitDataUploader = buildGitDataUploader(config, gitInfoProvider, gitClientFactory, backendApi, repoRoot); ModuleExecutionSettingsFactory moduleExecutionSettingsFactory = @@ -210,7 +227,9 @@ private static TestEventsHandler.Factory testEventsHandlerFactory( CIInfo ciInfo = ciProviderInfo.buildCIInfo(); String repoRoot = ciInfo.getCiWorkspace(); String moduleName = - (repoRoot != null) ? Paths.get(repoRoot).relativize(path).toString() : path.toString(); + repoRoot != null && path.startsWith(repoRoot) + ? Paths.get(repoRoot).relativize(path).toString() + : config.getServiceName(); DDTestFrameworkSession testSession = sessionFactory.startSession(moduleName, path, component, null); @@ -226,8 +245,6 @@ private static DDTestFrameworkSession.Factory testFrameworkSessionFactory( CoverageProbeStoreFactory coverageProbeStoreFactory, GitClient.Factory gitClientFactory) { BackendApiFactory backendApiFactory = new BackendApiFactory(config, sco); - BackendApi backendApi = backendApiFactory.createBackendApi(); - return (String projectName, Path projectRoot, String component, Long startTime) -> { CIProviderInfoFactory ciProviderInfoFactory = new CIProviderInfoFactory(config); CIProviderInfo ciProviderInfo = ciProviderInfoFactory.createCIProviderInfo(projectRoot); @@ -273,6 +290,7 @@ private static DDTestFrameworkSession.Factory testFrameworkSessionFactory( // either we are in the build system // or we are in the tests JVM and the build system is not instrumented if (parentProcessSessionId == null || parentProcessModuleId == null) { + BackendApi backendApi = backendApiFactory.createBackendApi(); GitDataUploader gitDataUploader = buildGitDataUploader(config, gitInfoProvider, gitClientFactory, backendApi, repoRoot); RepoIndexProvider indexProvider = new RepoIndexBuilder(repoRoot, FileSystems.getDefault()); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/BackendApiFactory.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/BackendApiFactory.java index c79fb8d65ab..934e68868ee 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/BackendApiFactory.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/BackendApiFactory.java @@ -23,7 +23,6 @@ public BackendApiFactory(Config config, SharedCommunicationObjects sharedCommuni } public @Nullable BackendApi createBackendApi() { - long timeoutMillis = config.getCiVisibilityBackendApiTimeoutMillis(); HttpRetryPolicy.Factory retryPolicyFactory = new HttpRetryPolicy.Factory(5, 100, 2.0); if (config.isCiVisibilityAgentlessEnabled()) { @@ -33,15 +32,20 @@ public BackendApiFactory(Config config, SharedCommunicationObjects sharedCommuni throw new FatalAgentMisconfigurationError( "Agentless mode is enabled and api key is not set. Please set application key"); } - return new IntakeApi(site, apiKey, timeoutMillis, retryPolicyFactory); + long timeoutMillis = config.getCiVisibilityBackendApiTimeoutMillis(); + String traceId = config.getIdGenerationStrategy().generateTraceId().toString(); + return new IntakeApi(site, apiKey, traceId, timeoutMillis, retryPolicyFactory); } DDAgentFeaturesDiscovery featuresDiscovery = sharedCommunicationObjects.featuresDiscovery(config); + featuresDiscovery.discoverIfOutdated(); if (featuresDiscovery.supportsEvpProxy()) { + String traceId = config.getIdGenerationStrategy().generateTraceId().toString(); String evpProxyEndpoint = featuresDiscovery.getEvpProxyEndpoint(); HttpUrl evpProxyUrl = sharedCommunicationObjects.agentUrl.resolve(evpProxyEndpoint); - return new EvpProxyApi(evpProxyUrl, timeoutMillis, retryPolicyFactory); + return new EvpProxyApi( + traceId, evpProxyUrl, retryPolicyFactory, sharedCommunicationObjects.okHttpClient); } log.warn( diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/EvpProxyApi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/EvpProxyApi.java index 589c0811f22..2329b86b061 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/EvpProxyApi.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/EvpProxyApi.java @@ -19,17 +19,24 @@ public class EvpProxyApi implements BackendApi { private static final String API_VERSION = "v2"; private static final String X_DATADOG_EVP_SUBDOMAIN_HEADER = "X-Datadog-EVP-Subdomain"; + private static final String X_DATADOG_TRACE_ID_HEADER = "x-datadog-trace-id"; + private static final String X_DATADOG_PARENT_ID_HEADER = "x-datadog-parent-id"; private static final String API_SUBDOMAIN = "api"; + private final String traceId; private final HttpRetryPolicy.Factory retryPolicyFactory; private final HttpUrl evpProxyUrl; private final OkHttpClient httpClient; public EvpProxyApi( - HttpUrl evpProxyUrl, long timeoutMillis, HttpRetryPolicy.Factory retryPolicyFactory) { + String traceId, + HttpUrl evpProxyUrl, + HttpRetryPolicy.Factory retryPolicyFactory, + OkHttpClient httpClient) { + this.traceId = traceId; this.evpProxyUrl = evpProxyUrl.resolve(String.format("api/%s/", API_VERSION)); this.retryPolicyFactory = retryPolicyFactory; - httpClient = OkHttpUtils.buildHttpClient(evpProxyUrl, timeoutMillis); + this.httpClient = httpClient; } @Override @@ -41,6 +48,8 @@ public T post( new Request.Builder() .url(url) .addHeader(X_DATADOG_EVP_SUBDOMAIN_HEADER, API_SUBDOMAIN) + .addHeader(X_DATADOG_TRACE_ID_HEADER, traceId) + .addHeader(X_DATADOG_PARENT_ID_HEADER, traceId) .post(requestBody) .build(); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/IntakeApi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/IntakeApi.java index d8617376b9c..55728a277d2 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/IntakeApi.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/IntakeApi.java @@ -20,15 +20,23 @@ public class IntakeApi implements BackendApi { private static final String API_VERSION = "v2"; private static final String DD_API_KEY_HEADER = "dd-api-key"; + private static final String X_DATADOG_TRACE_ID_HEADER = "x-datadog-trace-id"; + private static final String X_DATADOG_PARENT_ID_HEADER = "x-datadog-parent-id"; private final String apiKey; + private final String traceId; private final HttpRetryPolicy.Factory retryPolicyFactory; private final HttpUrl hostUrl; private final OkHttpClient httpClient; public IntakeApi( - String site, String apiKey, long timeoutMillis, HttpRetryPolicy.Factory retryPolicyFactory) { + String site, + String apiKey, + String traceId, + long timeoutMillis, + HttpRetryPolicy.Factory retryPolicyFactory) { this.apiKey = apiKey; + this.traceId = traceId; this.retryPolicyFactory = retryPolicyFactory; final String ciVisibilityAgentlessUrlStr = Config.get().getCiVisibilityAgentlessUrl(); @@ -47,7 +55,12 @@ public T post( throws IOException { HttpUrl url = hostUrl.resolve(uri); Request.Builder requestBuilder = - new Request.Builder().url(url).post(requestBody).addHeader(DD_API_KEY_HEADER, apiKey); + new Request.Builder() + .url(url) + .post(requestBody) + .addHeader(DD_API_KEY_HEADER, apiKey) + .addHeader(X_DATADOG_TRACE_ID_HEADER, traceId) + .addHeader(X_DATADOG_PARENT_ID_HEADER, traceId); Request request = requestBuilder.build(); HttpRetryPolicy retryPolicy = retryPolicyFactory.create(); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CachingJvmInfoFactory.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CachingJvmInfoFactory.java new file mode 100644 index 00000000000..62204a42aec --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CachingJvmInfoFactory.java @@ -0,0 +1,23 @@ +package datadog.trace.civisibility.config; + +import datadog.trace.api.Config; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import java.nio.file.Path; + +public class CachingJvmInfoFactory implements JvmInfoFactory { + + private final DDCache cache; + private final JvmInfoFactoryImpl delegate; + + public CachingJvmInfoFactory(Config config, JvmInfoFactoryImpl delegate) { + this.delegate = delegate; + this.cache = + DDCaches.newFixedSizeCache(config.getCiVisibilityModuleExecutionSettingsCacheSize()); + } + + @Override + public JvmInfo getJvmInfo(Path jvmExecutablePath) { + return cache.computeIfAbsent(jvmExecutablePath, delegate::getJvmInfo); + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationsJson.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationsJson.java index b1cfd999ef4..49c861a466a 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationsJson.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationsJson.java @@ -4,6 +4,7 @@ import com.squareup.moshi.Json; import com.squareup.moshi.ToJson; import datadog.trace.api.civisibility.config.Configurations; +import java.util.Map; public final class ConfigurationsJson { @Json(name = "os.platform") @@ -33,6 +34,9 @@ public final class ConfigurationsJson { @Json(name = "test.bundle") private final String testBundle; + @Json(name = "custom") + private final Map custom; + public ConfigurationsJson( String osPlatform, String osArchitecture, @@ -41,7 +45,8 @@ public ConfigurationsJson( String runtimeVersion, String runtimeVendor, String runtimeArchitecture, - String testBundle) { + String testBundle, + Map custom) { this.osPlatform = osPlatform; osArch = osArchitecture; this.osArchitecture = osArchitecture; @@ -51,6 +56,7 @@ public ConfigurationsJson( this.runtimeVendor = runtimeVendor; this.runtimeArchitecture = runtimeArchitecture; this.testBundle = testBundle; + this.custom = custom; } public static final class ConfigurationsJsonAdapter { @@ -64,7 +70,8 @@ public Configurations fromJson(ConfigurationsJson configurationsJson) { configurationsJson.runtimeVersion, configurationsJson.runtimeVendor, configurationsJson.runtimeArchitecture, - configurationsJson.testBundle); + configurationsJson.testBundle, + configurationsJson.custom); } @ToJson @@ -77,7 +84,8 @@ public ConfigurationsJson toJson(Configurations configurations) { configurations.getRuntimeVersion(), configurations.getRuntimeVendor(), configurations.getRuntimeArchitecture(), - configurations.getTestBundle()); + configurations.getTestBundle(), + configurations.getCustom()); } } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactory.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactory.java index 6a62ce3927d..99a26b3a885 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactory.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactory.java @@ -1,89 +1,7 @@ package datadog.trace.civisibility.config; -import datadog.trace.civisibility.utils.ShellCommandExecutor; -import datadog.trace.util.ProcessUtils; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; import java.nio.file.Path; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class JvmInfoFactory { - - private static final Logger LOGGER = LoggerFactory.getLogger(JvmInfoFactory.class); - - private static final int JVM_VERSION_LAUNCH_TIMEOUT = 5_000; - - // provide some confidence - public JvmInfo getJvmInfo(Path jvmExecutablePath) { - String currentJvm = - ProcessUtils.getCurrentJvmPath(); // might be home dir or full executable path - // if we cannot determine forked JVM, - // we assume it is the same as current one, - // which is the most common case - if (jvmExecutablePath == null - || currentJvm != null && jvmExecutablePath.startsWith(currentJvm)) { - return JvmInfo.CURRENT_JVM; - } else { - return doGetJvmInfo(jvmExecutablePath); - } - } - - static JvmInfo doGetJvmInfo(Path jvmExecutablePath) { - Path jvmExecutableFolder = jvmExecutablePath.getParent(); - ShellCommandExecutor commandExecutor = - new ShellCommandExecutor(jvmExecutableFolder.toFile(), JVM_VERSION_LAUNCH_TIMEOUT); - try { - return commandExecutor.executeCommandReadingError( - new JvmVersionOutputParser(), "./java", "-XshowSettings:properties", "-version"); - - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOGGER.warn( - "Interrupted while waiting for JVM runtime info for {}, assuming {}", - jvmExecutablePath, - JvmInfo.CURRENT_JVM); - return JvmInfo.CURRENT_JVM; - - } catch (Exception e) { - LOGGER.warn( - "Could not determine JVM runtime info for {}, assuming {}", - jvmExecutablePath, - JvmInfo.CURRENT_JVM, - e); - return JvmInfo.CURRENT_JVM; - } - } - - private static final class JvmVersionOutputParser - implements ShellCommandExecutor.OutputParser { - @Override - public JvmInfo parse(InputStream inputStream) throws IOException { - String name = null; - String version = null; - String vendor = null; - - BufferedReader bis = - new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); - String line; - while ((line = bis.readLine()) != null) { - if (line.contains("java.runtime.name ")) { - name = getPropertyValue(line); - } else if (line.contains("java.version ")) { - version = getPropertyValue(line); - } else if (line.contains("java.vendor ")) { - vendor = getPropertyValue(line); - } - } - return new JvmInfo(name, version, vendor); - } - - private String getPropertyValue(String line) { - // format of the input is: " property.name = propertyValue" - return line.substring(line.indexOf('=') + 2); - } - } +public interface JvmInfoFactory { + JvmInfo getJvmInfo(Path jvmExecutablePath); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactoryImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactoryImpl.java new file mode 100644 index 00000000000..0a3a4a28519 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactoryImpl.java @@ -0,0 +1,89 @@ +package datadog.trace.civisibility.config; + +import datadog.trace.civisibility.utils.ShellCommandExecutor; +import datadog.trace.util.ProcessUtils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JvmInfoFactoryImpl implements JvmInfoFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(JvmInfoFactoryImpl.class); + + private static final int JVM_VERSION_LAUNCH_TIMEOUT = 5_000; + + @Override + public JvmInfo getJvmInfo(Path jvmExecutablePath) { + String currentJvm = + ProcessUtils.getCurrentJvmPath(); // might be home dir or full executable path + // if we cannot determine forked JVM, + // we assume it is the same as current one, + // which is the most common case + if (jvmExecutablePath == null + || currentJvm != null && jvmExecutablePath.startsWith(currentJvm)) { + return JvmInfo.CURRENT_JVM; + } else { + return doGetJvmInfo(jvmExecutablePath); + } + } + + static JvmInfo doGetJvmInfo(Path jvmExecutablePath) { + Path jvmExecutableFolder = jvmExecutablePath.getParent(); + ShellCommandExecutor commandExecutor = + new ShellCommandExecutor(jvmExecutableFolder.toFile(), JVM_VERSION_LAUNCH_TIMEOUT); + try { + return commandExecutor.executeCommandReadingError( + new JvmVersionOutputParser(), "./java", "-XshowSettings:properties", "-version"); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.warn( + "Interrupted while waiting for JVM runtime info for {}, assuming {}", + jvmExecutablePath, + JvmInfo.CURRENT_JVM); + return JvmInfo.CURRENT_JVM; + + } catch (Exception e) { + LOGGER.warn( + "Could not determine JVM runtime info for {}, assuming {}", + jvmExecutablePath, + JvmInfo.CURRENT_JVM, + e); + return JvmInfo.CURRENT_JVM; + } + } + + private static final class JvmVersionOutputParser + implements ShellCommandExecutor.OutputParser { + @Override + public JvmInfo parse(InputStream inputStream) throws IOException { + String name = null; + String version = null; + String vendor = null; + + BufferedReader bis = + new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); + String line; + while ((line = bis.readLine()) != null) { + if (line.contains("java.runtime.name ")) { + name = getPropertyValue(line); + } else if (line.contains("java.version ")) { + version = getPropertyValue(line); + } else if (line.contains("java.vendor ")) { + vendor = getPropertyValue(line); + } + } + return new JvmInfo(name, version, vendor); + } + + private String getPropertyValue(String line) { + // format of the input is: " property.name = propertyValue" + return line.substring(line.indexOf('=') + 2); + } + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ModuleExecutionSettingsFactoryImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ModuleExecutionSettingsFactoryImpl.java index 6122ac1aabe..12f8fe937b0 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ModuleExecutionSettingsFactoryImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ModuleExecutionSettingsFactoryImpl.java @@ -1,5 +1,6 @@ package datadog.trace.civisibility.config; +import datadog.communication.ddagent.TracerVersion; import datadog.trace.api.Config; import datadog.trace.api.civisibility.config.Configurations; import datadog.trace.api.civisibility.config.ModuleExecutionSettings; @@ -31,6 +32,7 @@ public class ModuleExecutionSettingsFactoryImpl implements ModuleExecutionSettin private static final Logger LOGGER = LoggerFactory.getLogger(ModuleExecutionSettingsFactoryImpl.class); + private static final String TEST_CONFIGURATION_TAG_PREFIX = "test.configuration."; private final Config config; private final ConfigurationApi configurationApi; @@ -87,11 +89,21 @@ private TracerEnvironment buildTracerEnvironment( String repositoryRoot, JvmInfo jvmInfo, @Nullable String moduleName) { GitInfo gitInfo = GitInfoProvider.INSTANCE.getGitInfo(repositoryRoot); + TracerEnvironment.Builder builder = TracerEnvironment.builder(); + for (Map.Entry e : config.getGlobalTags().entrySet()) { + String key = e.getKey(); + if (key.startsWith(TEST_CONFIGURATION_TAG_PREFIX)) { + String configurationKey = key.substring(TEST_CONFIGURATION_TAG_PREFIX.length()); + String configurationValue = e.getValue(); + builder.customTag(configurationKey, configurationValue); + } + } + /* * IMPORTANT: JVM and OS properties should match tags * set in datadog.trace.civisibility.decorator.TestDecorator */ - return TracerEnvironment.builder() + return builder .service(config.getServiceName()) .env(config.getEnv()) .repositoryUrl(gitInfo.getRepositoryURL()) @@ -156,6 +168,11 @@ private Map getPropertiesPropagatedToChildProcess( CiVisibilityConfig.CIVISIBILITY_BUILD_INSTRUMENTATION_ENABLED), Boolean.toString(false)); + propagatedSystemProperties.put( + Strings.propertyNameToSystemPropertyName( + CiVisibilityConfig.CIVISIBILITY_INJECTED_TRACER_VERSION), + TracerVersion.TRACER_VERSION); + return propagatedSystemProperties; } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializer.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializer.java index 3250f3a6389..e01e56785c9 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializer.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializer.java @@ -26,10 +26,10 @@ public static ByteBuffer serialize(Collection skippableTests) { String name = test.getName(); String parameters = test.getParameters(); - length += suite.length(); - length += name.length(); + length += suite.getBytes(CHARSET).length; + length += name.getBytes(CHARSET).length; if (parameters != null) { - length += parameters.length(); + length += parameters.getBytes(CHARSET).length; } } @@ -41,14 +41,18 @@ public static ByteBuffer serialize(Collection skippableTests) { String name = test.getName(); String parameters = test.getParameters(); - buffer.putInt(suite.length()); - buffer.put(suite.getBytes(CHARSET)); - buffer.putInt(name.length()); - buffer.put(name.getBytes(CHARSET)); + byte[] suiteBytes = suite.getBytes(CHARSET); + buffer.putInt(suiteBytes.length); + buffer.put(suiteBytes); + + byte[] nameBytes = name.getBytes(CHARSET); + buffer.putInt(nameBytes.length); + buffer.put(nameBytes); if (parameters != null) { - buffer.putInt(parameters.length()); - buffer.put(parameters.getBytes(CHARSET)); + byte[] parametersBytes = parameters.getBytes(CHARSET); + buffer.putInt(parametersBytes.length); + buffer.put(parametersBytes); } else { buffer.putInt(-1); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java index 6240aea6264..86dce897bcb 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java @@ -2,6 +2,8 @@ import com.squareup.moshi.Json; import datadog.trace.api.civisibility.config.Configurations; +import java.util.HashMap; +import java.util.Map; public class TracerEnvironment { @@ -52,6 +54,7 @@ public static final class Builder { private String runtimeVendor; private String runtimeArchitecture; private String testBundle; + private final Map customTags = new HashMap<>(); public Builder service(String service) { this.service = service; @@ -118,6 +121,11 @@ public Builder testBundle(String testBundle) { return this; } + public Builder customTag(String key, String value) { + this.customTags.put(key, value); + return this; + } + public TracerEnvironment build() { return new TracerEnvironment( service, @@ -133,7 +141,8 @@ public TracerEnvironment build() { runtimeVersion, runtimeVendor, runtimeArchitecture, - testBundle)); + testBundle, + customTags)); } } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/events/BuildEventsHandlerImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/events/BuildEventsHandlerImpl.java index 8cd2afcd20d..2220d831d6c 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/events/BuildEventsHandlerImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/events/BuildEventsHandlerImpl.java @@ -40,9 +40,19 @@ public void onTestSessionStart( final Path projectRoot, final String startCommand, final String buildSystemName, - final String buildSystemVersion) { + final String buildSystemVersion, + Map additionalTags) { DDBuildSystemSession testSession = sessionFactory.startSession(projectName, projectRoot, startCommand, buildSystemName, null); + + if (additionalTags != null) { + for (Map.Entry e : additionalTags.entrySet()) { + String tag = e.getKey(); + Object value = e.getValue(); + testSession.setTag(tag, value); + } + } + testSession.setTag(Tags.TEST_TOOLCHAIN, buildSystemName + ":" + buildSystemVersion); inProgressTestSessions.put(sessionKey, testSession); } @@ -139,4 +149,9 @@ public ModuleExecutionSettings getModuleExecutionSettings(T sessionKey, Path jvm JvmInfo jvmInfo = jvmInfoFactory.getJvmInfo(jvmExecutablePath); return testSession.getModuleExecutionSettings(jvmInfo); } + + @Override + public ModuleInfo getModuleInfo(T sessionKey, String moduleName) { + return getTestModule(sessionKey, moduleName).getModuleInfo(); + } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/Utils.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/Utils.java index f9023ab5b60..8c826776656 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/Utils.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/Utils.java @@ -7,7 +7,26 @@ public abstract class Utils { public static InputStream getClassStream(Class clazz) throws IOException { String className = clazz.getName(); - String classPath = "/" + className.replace('.', '/') + ".class"; - return clazz.getResourceAsStream(classPath); + InputStream classStream = clazz.getResourceAsStream(toResourceName(className)); + if (classStream != null) { + return classStream; + } else { + // might be auto-generated inner class (e.g. Mockito mock) + String topLevelClassName = stripNestedClassNames(clazz.getName()); + return clazz.getResourceAsStream(toResourceName(topLevelClassName)); + } + } + + private static String toResourceName(String className) { + return "/" + className.replace('.', '/') + ".class"; + } + + public static String stripNestedClassNames(String className) { + int innerClassNameIdx = className.indexOf('$'); + if (innerClassNameIdx >= 0) { + return className.substring(0, innerClassNameIdx); + } else { + return className; + } } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndex.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndex.java index d8010aeb072..54537b18810 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndex.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndex.java @@ -50,7 +50,7 @@ public List getRootPackages() { @Nullable public String getSourcePath(@Nonnull Class c) { - String topLevelClassName = stripNestedClassNames(c.getName()); + String topLevelClassName = Utils.stripNestedClassNames(c.getName()); SourceType sourceType = detectSourceType(c); String extension = sourceType.getExtension(); String classNameWithExtension = topLevelClassName + extension; @@ -107,15 +107,6 @@ private SourceType detectSourceType(Class c) { return SourceType.JAVA; } - private String stripNestedClassNames(String className) { - int innerClassNameIdx = className.indexOf('$'); - if (innerClassNameIdx >= 0) { - return className.substring(0, innerClassNameIdx); - } else { - return className; - } - } - /** * Names of package-private classes do not have to correspond to the names of their source code * files. For such classes filename is extracted from SourceFile attribute that is available in @@ -126,6 +117,10 @@ private String getSourcePathForPackagePrivateOrNonJavaClass(Class c) { SourceFileAttributeVisitor sourceFileAttributeVisitor = new SourceFileAttributeVisitor(); try (InputStream classStream = Utils.getClassStream(c)) { + if (classStream == null) { + log.debug("Could not get input stream for class {}", c.getName()); + return null; + } ClassReader classReader = new ClassReader(classStream); classReader.accept( sourceFileAttributeVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy index 72539841faa..2637740aeb0 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy @@ -2,12 +2,14 @@ package datadog.trace.civisibility.config import com.squareup.moshi.Moshi import datadog.communication.http.HttpRetryPolicy +import datadog.communication.http.OkHttpUtils import datadog.trace.agent.test.server.http.TestHttpServer import datadog.trace.api.civisibility.config.Configurations import datadog.trace.api.civisibility.config.SkippableTest import datadog.trace.civisibility.communication.BackendApi import datadog.trace.civisibility.communication.EvpProxyApi import okhttp3.HttpUrl +import okhttp3.OkHttpClient import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification @@ -45,7 +47,10 @@ class ConfigurationApiImplTest extends Specification { "runtime.name" : "runtimeName", "runtime.version" : "runtimeVersion", "runtime.vendor" : "vendor", - "runtime.architecture": "amd64" + "runtime.architecture": "amd64", + "custom": [ + "customTag": "customValue" + ] ] ] ] @@ -78,7 +83,10 @@ class ConfigurationApiImplTest extends Specification { "runtime.name" : "runtimeName", "runtime.version" : "runtimeVersion", "runtime.vendor" : "vendor", - "runtime.architecture": "amd64" + "runtime.architecture": "amd64", + "custom": [ + "customTag": "customValue" + ] ] ] ] @@ -87,9 +95,9 @@ class ConfigurationApiImplTest extends Specification { if (expectedRequest) { response.status(200).send('{ "data": [' + '{ "id": "49968354e2091cdb", "type": "test", "attributes": ' + - '{ "configurations": { "test.bundle": "testBundle-a" }, "suite": "suite-a", "name": "name-a", "parameters": "parameters-a" } },' + + '{ "configurations": { "test.bundle": "testBundle-a", "custom": { "customTag": "customValue" } }, "suite": "suite-a", "name": "name-a", "parameters": "parameters-a" } },' + '{ "id": "49968354e2091cdc", "type": "test", "attributes": ' + - ' { "configurations": { "test.bundle": "testBundle-b" }, "suite": "suite-b", "name": "name-b", "parameters": "parameters-b" } }' + + ' { "configurations": { "test.bundle": "testBundle-b", "custom": { "customTag": "customValue" } }, "suite": "suite-b", "name": "name-b", "parameters": "parameters-b" } }' + '] }') } else { response.status(400).send() @@ -125,17 +133,19 @@ class ConfigurationApiImplTest extends Specification { skippableTests == [ new SkippableTest("suite-a", "name-a", "parameters-a", new Configurations(null, null, null, null, null, - null, null, "testBundle-a")), + null, null, "testBundle-a", Collections.singletonMap("customTag", "customValue"))), new SkippableTest("suite-b", "name-b", "parameters-b", new Configurations(null, null, null, null, null, - null, null, "testBundle-b")) + null, null, "testBundle-b", Collections.singletonMap("customTag", "customValue"))) ] } private BackendApi givenEvpProxy() { + String traceId = "a-trace-id" HttpUrl proxyUrl = HttpUrl.get(intakeServer.address) HttpRetryPolicy.Factory retryPolicyFactory = new HttpRetryPolicy.Factory(5, 100, 2.0) - return new EvpProxyApi(proxyUrl, REQUEST_TIMEOUT_MILLIS, retryPolicyFactory) + OkHttpClient client = OkHttpUtils.buildHttpClient(proxyUrl, REQUEST_TIMEOUT_MILLIS) + return new EvpProxyApi(traceId, proxyUrl, retryPolicyFactory, client) } private static TracerEnvironment givenTracerEnvironment() { @@ -152,6 +162,7 @@ class ConfigurationApiImplTest extends Specification { .runtimeVersion("runtimeVersion") .runtimeVendor("vendor") .runtimeArchitecture("amd64") + .customTag("customTag", "customValue") .build() } } diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/JvmInfoFactoryTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/JvmInfoFactoryTest.groovy index db6a991d2df..1d438fc2780 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/JvmInfoFactoryTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/JvmInfoFactoryTest.groovy @@ -15,7 +15,7 @@ class JvmInfoFactoryTest extends Specification { def currentJvmExecutable = getCurrentJvmExecutable() when: - def jvmInfo = JvmInfoFactory.doGetJvmInfo(currentJvmExecutable) + def jvmInfo = JvmInfoFactoryImpl.doGetJvmInfo(currentJvmExecutable) then: jvmInfo == JvmInfo.CURRENT_JVM diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy similarity index 90% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy rename to dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy index db382fc92a1..76494f2c43b 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy @@ -22,6 +22,8 @@ class SkippableTestsSerializerTest extends Specification { tests << [ // single test [["suite", "name", null]], + [["suite", "𝕄 add user properties 𝕎 addUserProperties()", null]], + // non-ASCII characters [["suite", "name", "parameters"]], [["suite", "name", "{\"metadata\":{\"test_name\":\"test display name with #a #b #c\"}}"]], // multiple tests diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitDataApiTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitDataApiTest.groovy index aba6ce092fa..c280fe51f76 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitDataApiTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitDataApiTest.groovy @@ -2,11 +2,13 @@ package datadog.trace.civisibility.git.tree import com.squareup.moshi.Moshi import datadog.communication.http.HttpRetryPolicy +import datadog.communication.http.OkHttpUtils import datadog.trace.agent.test.server.http.TestHttpServer import datadog.trace.civisibility.communication.BackendApi import datadog.trace.civisibility.communication.EvpProxyApi import datadog.trace.test.util.MultipartRequestParser import okhttp3.HttpUrl +import okhttp3.OkHttpClient import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification @@ -112,9 +114,11 @@ class GitDataApiTest extends Specification { } private BackendApi givenEvpProxy() { + String traceId = "a-trace-id" HttpUrl proxyUrl = HttpUrl.get(intakeServer.address) HttpRetryPolicy.Factory retryPolicyFactory = new HttpRetryPolicy.Factory(5, 100, 2.0) - return new EvpProxyApi(proxyUrl, REQUEST_TIMEOUT_MILLIS, retryPolicyFactory) + OkHttpClient client = OkHttpUtils.buildHttpClient(proxyUrl, REQUEST_TIMEOUT_MILLIS) + return new EvpProxyApi(traceId, proxyUrl, retryPolicyFactory, client) } private Path givenPackFile() { diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolverTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolverTest.groovy index 59a29662254..57b6b0ad229 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolverTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolverTest.groovy @@ -72,6 +72,17 @@ class RepoIndexSourcePathResolverTest extends Specification { sourcePathResolver.getSourcePath(PackagePrivateClass) == expectedSourcePath } + def "test source path resolution for class nested into package-private class"() { + setup: + def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") + + when: + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + + then: + sourcePathResolver.getSourcePath(PackagePrivateClass.NestedIntoPackagePrivateClass) == expectedSourcePath + } + def "test source path resolution for non-java class whose file name is different from class name"() { setup: def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") @@ -173,6 +184,7 @@ class RepoIndexSourcePathResolverTest extends Specification { @PackageScope class PackagePrivateClass { + class NestedIntoPackagePrivateClass {} } class PublicClassWhoseNameDoesNotCorrespondToFileName { diff --git a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTest.groovy b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTest.groovy index cd818861261..9c09587944c 100644 --- a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTest.groovy @@ -12,7 +12,7 @@ import datadog.trace.api.config.CiVisibilityConfig import datadog.trace.api.config.GeneralConfig import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.civisibility.codeowners.Codeowners -import datadog.trace.civisibility.config.JvmInfoFactory +import datadog.trace.civisibility.config.JvmInfoFactoryImpl import datadog.trace.civisibility.config.ModuleExecutionSettingsFactory import datadog.trace.civisibility.coverage.NoopCoverageProbeStore import datadog.trace.civisibility.decorator.TestDecorator @@ -122,7 +122,7 @@ abstract class CiVisibilityTest extends AgentTestRunner { } InstrumentationBridge.registerBuildEventsHandlerFactory { - decorator -> new BuildEventsHandlerImpl<>(buildSystemSessionFactory, new JvmInfoFactory()) + decorator -> new BuildEventsHandlerImpl<>(buildSystemSessionFactory, new JvmInfoFactoryImpl()) } InstrumentationBridge.registerCoverageProbeStoreRegistry(coverageProbeStoreFactory) diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java index d0b062f281c..7c01cd8ee56 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java @@ -206,7 +206,7 @@ public void addReturn(CapturedValue retValue) { if (locals == null) { locals = new HashMap<>(); } - locals.put("@return", retValue); // special local name for the return value + locals.put(ValueReferences.RETURN_REF, retValue); // special local name for the return value extensions.put(ValueReferences.RETURN_EXTENSION_NAME, retValue); } @@ -327,22 +327,14 @@ public Status evaluate( ValueReferences.DURATION_EXTENSION_NAME, duration / 1_000_000.0); // convert to ms } this.thisClassName = thisClassName; - boolean shouldEvaluate = resolveEvaluateAt(probeImplementation, methodLocation); + boolean shouldEvaluate = + MethodLocation.isSame(methodLocation, probeImplementation.getEvaluateAt()); if (shouldEvaluate) { probeImplementation.evaluate(this, status, methodLocation); } return status; } - private static boolean resolveEvaluateAt( - ProbeImplementation probeImplementation, MethodLocation methodLocation) { - if (methodLocation == MethodLocation.DEFAULT) { - // line probe, no evaluation of probe's evaluateAt - return true; - } - return MethodLocation.isSame(methodLocation, probeImplementation.getEvaluateAt()); - } - public Status getStatus(String probeId) { Status result = statusByProbeId.get(probeId); if (result == null) { diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CorrelationAccess.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CorrelationAccess.java index ed56d4ca89f..085f814ed31 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CorrelationAccess.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CorrelationAccess.java @@ -30,7 +30,7 @@ private CorrelationAccess() { MethodHandle traceIdHandle = null; MethodHandle spanIdHandle = null; // ignore correlations if tracer is not enabled - if (ConfigProvider.getInstance().getBoolean(TraceInstrumentationConfig.TRACE_ENABLED, false)) { + if (ConfigProvider.getInstance().getBoolean(TraceInstrumentationConfig.TRACE_ENABLED, true)) { try { Class clz = ClassLoader.getSystemClassLoader().loadClass(CORRELATION_IDENTIFIER_CLASSNAME); diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/MethodLocation.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/MethodLocation.java index 73c5fe212c0..7bceadf702d 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/MethodLocation.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/MethodLocation.java @@ -6,6 +6,11 @@ public enum MethodLocation { EXIT; public static boolean isSame(MethodLocation methodLocation, MethodLocation evaluateAt) { + if (methodLocation == MethodLocation.DEFAULT) { + // line probe, no evaluation of probe's evaluateAt + // MethodLocation.DEFAULT is used for line probe when evaluating the context + return true; + } if (methodLocation == MethodLocation.ENTRY) { return evaluateAt == MethodLocation.DEFAULT || evaluateAt == MethodLocation.ENTRY; } diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/Redaction.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/Redaction.java index 41383d3c50a..69b720c60a9 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/Redaction.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/Redaction.java @@ -18,30 +18,87 @@ public class Redaction { private static final Pattern COMMA_PATTERN = Pattern.compile(","); private static final List PREDEFINED_KEYWORDS = Arrays.asList( - "password", - "passwd", - "secret", + "2fa", + "accesstoken", + "aiohttpsession", + "apisecret", + "apisignature", "apikey", "auth", + "authtoken", + "authorization", + "ccnumber", + "certificatepin", + "cipher", + "clientid", + "clientsecret", + "config", + "connect.sid", + "cookie", "credentials", + "creditcard", + "csrf", + "csrftoken", + "cvv", + "databaseurl", + "dburl", + "encryptionkey", + "encryptionkeyid", + "env", + "gpgkey", + "jti", + "jwt", + "licensekey", + "masterkey", "mysqlpwd", + "nonce", + "oauth", + "oauth_token", + "otp", + "passhash", + "passwd", + "password", + "passwordb", + "pemfile", + "pgpkey", + "phpsessid", + "pin", + "pincode", + "pkcs8", "privatekey", - "token", - "ipaddress", + "publickey", + "recaptchakey", + "refreshtoken", + "routingnumber", + "salt", + "secret", + "secrettoken", + "secretKey", + "securityanswer", + "securitycode", + "securityquestion", + "serviceaccountcredentials", "session", - // django - "csrftoken", + "sessionkey", "sessionid", - // wsgi - "remoteaddr", - "xcsrftoken", - "xforwardedfor", "setcookie", - "cookie", - "authorization", + "signature", + "signaturekey", + "sshkey", + "ssn", + "symfony", + "token", + "transactionid", + "twiliotoken", + "usersession", + "voterid", "xapikey", + "xcsrftoken", "xforwardedfor", - "xrealip"); + "xrealip", + "xauthtoken", + "xsrftoken", + "pwd"); private static final Set KEYWORDS = ConcurrentHashMap.newKeySet(); private static ClassNameTrie typeTrie = ClassNameTrie.Builder.EMPTY_TRIE; private static List redactedClasses; diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/LogMessageTemplateBuilder.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/StringTemplateBuilder.java similarity index 82% rename from dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/LogMessageTemplateBuilder.java rename to dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/StringTemplateBuilder.java index 6b314725870..3f19bf2d2b2 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/LogMessageTemplateBuilder.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/StringTemplateBuilder.java @@ -9,13 +9,14 @@ import com.datadog.debugger.probe.LogProbe; import datadog.trace.bootstrap.debugger.CapturedContext; import datadog.trace.bootstrap.debugger.EvaluationError; +import datadog.trace.bootstrap.debugger.Limits; import datadog.trace.bootstrap.debugger.util.Redaction; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class LogMessageTemplateBuilder { - private static final Logger LOGGER = LoggerFactory.getLogger(LogMessageTemplateBuilder.class); +public class StringTemplateBuilder { + private static final Logger LOGGER = LoggerFactory.getLogger(StringTemplateBuilder.class); /** * Serialization limits for log messages. Most values are lower than snapshot because you can * directly reference values that are in your interest with Expression Language: @@ -23,8 +24,11 @@ public class LogMessageTemplateBuilder { */ private final List segments; - public LogMessageTemplateBuilder(List segments) { + private final Limits limits; + + public StringTemplateBuilder(List segments, Limits limits) { this.segments = segments; + this.limits = limits; } public String evaluate(CapturedContext context, LogProbe.LogStatus status) { @@ -45,7 +49,8 @@ public String evaluate(CapturedContext context, LogProbe.LogStatus status) { } else if (result.isNull()) { sb.append("null"); } else { - serializeValue(sb, segment.getParsedExpr().getDsl(), result.getValue(), status); + serializeValue( + sb, segment.getParsedExpr().getDsl(), result.getValue(), status, limits); } } catch (EvaluationException ex) { LOGGER.debug("Evaluation error: ", ex); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java index eb32212e0fc..5c4da11bd66 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java @@ -166,7 +166,9 @@ public static void emitReflectiveCall( String methodName = getReflectiveMethodName(sort); // stack: [target_object, string] org.objectweb.asm.Type returnType = - sort == org.objectweb.asm.Type.OBJECT ? Types.OBJECT_TYPE : fieldType.getMainType(); + sort == org.objectweb.asm.Type.OBJECT || sort == org.objectweb.asm.Type.ARRAY + ? Types.OBJECT_TYPE + : fieldType.getMainType(); invokeStatic( insnList, REFLECTIVE_FIELD_VALUE_RESOLVER_TYPE, @@ -174,7 +176,7 @@ public static void emitReflectiveCall( returnType, targetType, Types.STRING_TYPE); - if (sort == org.objectweb.asm.Type.OBJECT) { + if (sort == org.objectweb.asm.Type.OBJECT || sort == org.objectweb.asm.Type.ARRAY) { insnList.add(new TypeInsnNode(Opcodes.CHECKCAST, fieldType.getMainType().getInternalName())); } // stack: [field_value] @@ -199,6 +201,7 @@ private static String getReflectiveMethodName(int sort) { case org.objectweb.asm.Type.BOOLEAN: return "getFieldValueAsBoolean"; case org.objectweb.asm.Type.OBJECT: + case org.objectweb.asm.Type.ARRAY: return "getFieldValue"; default: throw new IllegalArgumentException("Unsupported type sort:" + sort); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/MetricInstrumentor.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/MetricInstrumentor.java index 38642365cc4..6d361e5c1a9 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/MetricInstrumentor.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/MetricInstrumentor.java @@ -11,6 +11,8 @@ import static com.datadog.debugger.instrumentation.ASMHelper.ldc; import static com.datadog.debugger.instrumentation.Types.*; import static datadog.trace.util.Strings.getClassName; +import static org.objectweb.asm.Type.DOUBLE_TYPE; +import static org.objectweb.asm.Type.LONG_TYPE; import com.datadog.debugger.el.InvalidValueException; import com.datadog.debugger.el.Visitor; @@ -46,6 +48,7 @@ import com.datadog.debugger.el.values.StringValue; import com.datadog.debugger.probe.MetricProbe; import com.datadog.debugger.probe.Where; +import datadog.trace.bootstrap.debugger.MethodLocation; import datadog.trace.bootstrap.debugger.el.ValueReferences; import java.lang.reflect.Field; import java.util.ArrayList; @@ -141,6 +144,7 @@ protected InsnList getBeforeReturnInsnList(AbstractInsnNode node) { int size = 1; int storeOpCode = 0; int loadOpCode = 0; + Type returnType = null; switch (node.getOpcode()) { case Opcodes.RET: case Opcodes.RETURN: @@ -149,29 +153,35 @@ protected InsnList getBeforeReturnInsnList(AbstractInsnNode node) { storeOpCode = Opcodes.LSTORE; loadOpCode = Opcodes.LLOAD; size = 2; + returnType = Type.LONG_TYPE; break; case Opcodes.DRETURN: storeOpCode = Opcodes.DSTORE; loadOpCode = Opcodes.DLOAD; size = 2; + returnType = Type.DOUBLE_TYPE; break; case Opcodes.IRETURN: storeOpCode = Opcodes.ISTORE; loadOpCode = Opcodes.ILOAD; + returnType = Type.INT_TYPE; break; case Opcodes.FRETURN: storeOpCode = Opcodes.FSTORE; loadOpCode = Opcodes.FLOAD; + returnType = Type.FLOAT_TYPE; break; case Opcodes.ARETURN: storeOpCode = Opcodes.ASTORE; loadOpCode = Opcodes.ALOAD; + returnType = OBJECT_TYPE; break; default: throw new UnsupportedOperationException("Unsupported opcode: " + node.getOpcode()); } - InsnList insnList = wrapTryCatch(callMetric(metricProbe)); int tmpIdx = newVar(size); + InsnList insnList = + wrapTryCatch(callMetric(metricProbe, new ReturnContext(tmpIdx, loadOpCode, returnType))); // store return value from the stack to local before wrapped call insnList.insert(new VarInsnNode(storeOpCode, tmpIdx)); // restore return value to the stack after wrapped call @@ -179,7 +189,7 @@ protected InsnList getBeforeReturnInsnList(AbstractInsnNode node) { return insnList; } - private InsnList callCount(MetricProbe metricProbe) { + private InsnList callCount(MetricProbe metricProbe, ReturnContext returnContext) { if (metricProbe.getValue() == null) { InsnList insnList = new InsnList(); // consider the metric as an increment counter one @@ -203,16 +213,20 @@ private InsnList callCount(MetricProbe metricProbe) { // stack [] return insnList; } - return internalCallMetric(metricProbe); + return internalCallMetric(metricProbe, returnContext); } - private InsnList internalCallMetric(MetricProbe metricProbe) { + private InsnList internalCallMetric(MetricProbe metricProbe, ReturnContext returnContext) { InsnList insnList = new InsnList(); InsnList nullBranch = new InsnList(); VisitorResult result; Type resultType; try { - result = metricProbe.getValue().getExpr().accept(new MetricValueVisitor(this, nullBranch)); + result = + metricProbe + .getValue() + .getExpr() + .accept(new MetricValueVisitor(this, nullBranch, returnContext)); } catch (InvalidValueException | UnsupportedOperationException ex) { reportError(ex.getMessage()); return EMPTY_INSN_LIST; @@ -271,16 +285,20 @@ private Type convertIfRequired(Type currentType, InsnList insnList) { } private InsnList callMetric(MetricProbe metricProbe) { + return callMetric(metricProbe, null); + } + + private InsnList callMetric(MetricProbe metricProbe, ReturnContext returnContext) { switch (metricProbe.getKind()) { case COUNT: - return callCount(metricProbe); + return callCount(metricProbe, returnContext); case GAUGE: case HISTOGRAM: case DISTRIBUTION: if (metricProbe.getValue() == null) { return EMPTY_INSN_LIST; } - return internalCallMetric(metricProbe); + return internalCallMetric(metricProbe, returnContext); default: reportError(String.format("Unknown metric kind: %s", metricProbe.getKind())); } @@ -332,10 +350,13 @@ public VisitorResult(ASMHelper.Type type, InsnList insnList) { private static class MetricValueVisitor implements Visitor { private final MetricInstrumentor instrumentor; private final InsnList nullBranch; + private final ReturnContext returnContext; - public MetricValueVisitor(MetricInstrumentor instrumentor, InsnList nullBranch) { + public MetricValueVisitor( + MetricInstrumentor instrumentor, InsnList nullBranch, ReturnContext returnContext) { this.instrumentor = instrumentor; this.nullBranch = nullBranch; + this.returnContext = returnContext; } @Override @@ -463,7 +484,11 @@ public VisitorResult visit(ValueRefExpression valueRefExpression) { insnList.add(new VarInsnNode(Opcodes.ALOAD, 0)); // stack [this] } else { - currentType = tryRetrieve(name, insnList); + if (name.startsWith(ValueReferences.SYNTHETIC_PREFIX)) { + currentType = tryRetrieveSynthetic(name, insnList); + } else { + currentType = tryRetrieve(name, insnList); + } if (currentType == null) { throw new InvalidValueException("Cannot resolve symbol " + name); } @@ -727,5 +752,58 @@ private ASMHelper.Type tryRetrieveField( } return returnType; } + + private ASMHelper.Type tryRetrieveSynthetic(String name, InsnList insnList) { + if (name.equals(ValueReferences.RETURN_REF)) { + if (instrumentor.metricProbe.getEvaluateAt() != MethodLocation.EXIT) { + return null; + } + if (returnContext == null) { + return null; + } + VarInsnNode varInsnNode = + new VarInsnNode(returnContext.opLoad, returnContext.returnLocalVarIdx); + insnList.add(varInsnNode); + // stack [return_value] + return new ASMHelper.Type(returnContext.type); + } + if (name.equals(ValueReferences.DURATION_REF)) { + if (instrumentor.metricProbe.getEvaluateAt() != MethodLocation.EXIT) { + return null; + } + // call System.nanoTime at the beginning of the method + int var = instrumentor.newVar(LONG_TYPE); + InsnList nanoTimeList = new InsnList(); + invokeStatic(nanoTimeList, Type.getType(System.class), "nanoTime", LONG_TYPE); + nanoTimeList.add(new VarInsnNode(Opcodes.LSTORE, var)); + instrumentor.methodNode.instructions.insert(instrumentor.methodEnterLabel, nanoTimeList); + // diff nanoTime before calling metric + invokeStatic(insnList, Type.getType(System.class), "nanoTime", LONG_TYPE); + // stack [long] + insnList.add(new VarInsnNode(Opcodes.LLOAD, var)); + // stack [long, long] + insnList.add(new InsnNode(Opcodes.LSUB)); + // stack [long] + insnList.add(new InsnNode(Opcodes.L2D)); + insnList.add(new LdcInsnNode(1_000_000D)); + // stack [long, double] + insnList.add(new InsnNode(Opcodes.DDIV)); + // stack [double] + return new ASMHelper.Type(DOUBLE_TYPE); + } + return null; + } + } + + private static class ReturnContext { + private final int returnLocalVarIdx; + private final int opLoad; + private final Type type; + + public ReturnContext(int returnLocalVarIdx, int opLoad, Type type) { + this.returnLocalVarIdx = returnLocalVarIdx; + this.opLoad = opLoad; + this.type = type; + } } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java index acb71fa7f50..9c5093ce9ad 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java @@ -4,7 +4,7 @@ import com.datadog.debugger.agent.DebuggerAgent; import com.datadog.debugger.agent.Generated; -import com.datadog.debugger.agent.LogMessageTemplateBuilder; +import com.datadog.debugger.agent.StringTemplateBuilder; import com.datadog.debugger.el.EvaluationException; import com.datadog.debugger.el.ProbeCondition; import com.datadog.debugger.el.ValueScript; @@ -41,6 +41,8 @@ /** Stores definition of a log probe */ public class LogProbe extends ProbeDefinition { private static final Logger LOGGER = LoggerFactory.getLogger(LogProbe.class); + private static final Limits LIMITS = new Limits(1, 3, 8192, 5); + private static final int LOG_MSG_LIMIT = 8192; /** Stores part of a templated message either a str or an expression */ public static class Segment { @@ -381,13 +383,20 @@ public void evaluate( new EvaluationError( "uncaught exception", throwable.getType() + ": " + throwable.getMessage())); } - if (hasCondition() && logStatus.getCondition()) { - // sample if probe has condition and condition is true + if (hasCondition() && (logStatus.getCondition() || logStatus.hasConditionErrors())) { + // sample if probe has condition and condition is true or has error sample(logStatus, methodLocation); } if (logStatus.isSampled() && logStatus.getCondition()) { - LogMessageTemplateBuilder logMessageBuilder = new LogMessageTemplateBuilder(segments); - logStatus.setMessage(logMessageBuilder.evaluate(context, logStatus)); + StringTemplateBuilder logMessageBuilder = new StringTemplateBuilder(segments, LIMITS); + String msg = logMessageBuilder.evaluate(context, logStatus); + if (msg != null && msg.length() > LOG_MSG_LIMIT) { + StringBuilder sb = new StringBuilder(LOG_MSG_LIMIT + 3); + sb.append(msg, 0, LOG_MSG_LIMIT); + sb.append("..."); + msg = sb.toString(); + } + logStatus.setMessage(msg); } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/SpanDecorationProbe.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/SpanDecorationProbe.java index 5de07fc1a7b..47b170f57b1 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/SpanDecorationProbe.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/SpanDecorationProbe.java @@ -2,7 +2,7 @@ import com.datadog.debugger.agent.DebuggerAgent; import com.datadog.debugger.agent.Generated; -import com.datadog.debugger.agent.LogMessageTemplateBuilder; +import com.datadog.debugger.agent.StringTemplateBuilder; import com.datadog.debugger.el.EvaluationException; import com.datadog.debugger.el.ProbeCondition; import com.datadog.debugger.instrumentation.CapturedContextInstrumentor; @@ -33,6 +33,7 @@ public class SpanDecorationProbe extends ProbeDefinition { private static final Logger LOGGER = LoggerFactory.getLogger(SpanDecorationProbe.class); private static final String PROBEID_DD_TAGS_FORMAT = "_dd.di.%s.probe_id"; private static final String EVALERROR_DD_TAGS_FORMAT = "_dd.di.%s.evaluation_error"; + private static final Limits LIMITS = new Limits(1, 3, 255, 5); public enum TargetSpan { ACTIVE, @@ -166,7 +167,7 @@ public void evaluate( SpanDecorationStatus spanStatus = (SpanDecorationStatus) status; for (Tag tag : decoration.tags) { String tagName = sanitize(tag.name); - LogMessageTemplateBuilder builder = new LogMessageTemplateBuilder(tag.value.getSegments()); + StringTemplateBuilder builder = new StringTemplateBuilder(tag.value.getSegments(), LIMITS); LogProbe.LogStatus logStatus = new LogProbe.LogStatus(this); String tagValue = builder.evaluate(context, logStatus); if (logStatus.hasLogTemplateErrors()) { diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SnapshotSink.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SnapshotSink.java index 559bd259424..4ceca7b547e 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SnapshotSink.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SnapshotSink.java @@ -2,7 +2,7 @@ import com.datadog.debugger.agent.DebuggerAgent; import com.datadog.debugger.util.ExceptionHelper; -import com.datadog.debugger.util.SnapshotSlicer; +import com.datadog.debugger.util.SnapshotPruner; import datadog.trace.api.Config; import datadog.trace.relocate.api.RatelimitedLogger; import datadog.trace.util.TagsHelper; @@ -64,20 +64,13 @@ public boolean offer(Snapshot snapshot) { String serializeSnapshot(String serviceName, Snapshot snapshot) { String str = DebuggerAgent.getSnapshotSerializer().serializeSnapshot(serviceName, snapshot); - int currentMaxDepth = snapshot.getMaxDepth(); - while (str.length() > MAX_SNAPSHOT_SIZE && currentMaxDepth >= 0) { + String prunedStr = SnapshotPruner.prune(str, MAX_SNAPSHOT_SIZE, 4); + if (prunedStr.length() != str.length()) { LOGGER.debug( - "serializing snapshot breached 1MB limit: {}, reducing depth level {} -> {}", + "serializing snapshot breached 1MB limit, reducing size from {} -> {}", str.length(), - currentMaxDepth, - currentMaxDepth - 1); - currentMaxDepth -= 1; - str = SnapshotSlicer.slice(currentMaxDepth, str); + prunedStr.length()); } - if (str.length() > MAX_SNAPSHOT_SIZE) { - ratelimitedLogger.warn( - "Snapshot is too large even after reducing depth to 0: {}", str.length()); - } - return str; + return prunedStr; } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/LanguageSpecifics.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/LanguageSpecifics.java new file mode 100644 index 00000000000..78599d993d1 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/LanguageSpecifics.java @@ -0,0 +1,144 @@ +package com.datadog.debugger.symbol; + +import com.datadog.debugger.agent.Generated; +import com.squareup.moshi.Json; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +public class LanguageSpecifics { + @Json(name = "access_modifiers") + private final List accessModifiers; + + private final List annotations; + + @Json(name = "super_class") + private final String superClass; + + private final List interfaces; + + @Json(name = "return_type") + private final String returnType; + + public LanguageSpecifics( + List accessModifiers, + List annotations, + String superClass, + List interfaces, + String returnType) { + this.accessModifiers = accessModifiers; + this.annotations = annotations; + this.superClass = superClass; + this.interfaces = interfaces; + this.returnType = returnType; + } + + public List getAccessModifiers() { + return accessModifiers; + } + + public List getAnnotations() { + return annotations; + } + + public String getSuperClass() { + return superClass; + } + + public List getInterfaces() { + return interfaces; + } + + public String getReturnType() { + return returnType; + } + + @Generated + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LanguageSpecifics that = (LanguageSpecifics) o; + return Objects.equals(accessModifiers, that.accessModifiers) + && Objects.equals(annotations, that.annotations) + && Objects.equals(superClass, that.superClass) + && Objects.equals(interfaces, that.interfaces) + && Objects.equals(returnType, that.returnType); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(accessModifiers, annotations, superClass, interfaces, returnType); + } + + @Generated + @Override + public String toString() { + return "LanguageSpecifics{" + + "accessModifiers=" + + accessModifiers + + ", annotations=" + + annotations + + ", superClass='" + + superClass + + '\'' + + ", interfaces=" + + interfaces + + ", returnType='" + + returnType + + '\'' + + '}'; + } + + public static class Builder { + private List accessModifiers; + private List annotations; + private String superClass; + private List interfaces; + private String returnType; + + public Builder addModifiers(Collection modifiers) { + if (modifiers == null || modifiers.isEmpty()) { + this.accessModifiers = null; + return this; + } + accessModifiers = new ArrayList<>(modifiers); + return this; + } + + public Builder addAnnotations(Collection annotations) { + if (annotations == null || annotations.isEmpty()) { + this.annotations = null; + return this; + } + this.annotations = new ArrayList<>(annotations); + return this; + } + + public Builder superClass(String superClass) { + this.superClass = superClass; + return this; + } + + public Builder addInterfaces(Collection interfaces) { + if (interfaces == null || interfaces.isEmpty()) { + this.interfaces = null; + return this; + } + this.interfaces = new ArrayList<>(interfaces); + return this; + } + + public Builder returnType(String returnType) { + this.returnType = returnType; + return this; + } + + public LanguageSpecifics build() { + return new LanguageSpecifics( + accessModifiers, annotations, superClass, interfaces, returnType); + } + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Scope.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Scope.java index 4963e687959..bd0012bfb03 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Scope.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Scope.java @@ -19,7 +19,7 @@ public class Scope { private final String name; @Json(name = "language_specifics") - private final List languageSpecifics; + private final LanguageSpecifics languageSpecifics; private final List symbols; private final List scopes; @@ -30,7 +30,7 @@ public Scope( int startLine, int endLine, String name, - List languageSpecifics, + LanguageSpecifics languageSpecifics, List symbols, List scopes) { this.scopeType = scopeType; @@ -63,7 +63,7 @@ public String getName() { return name; } - public List getLanguageSpecifics() { + public LanguageSpecifics getLanguageSpecifics() { return languageSpecifics; } @@ -110,7 +110,7 @@ public static class Builder { private final int startLine; private final int endLine; private String name; - private List languageSpecifics; + private LanguageSpecifics languageSpecifics; private List symbols; private List scopes; @@ -126,7 +126,7 @@ public Builder name(String name) { return this; } - public Builder languageSpecifics(List languageSpecifics) { + public Builder languageSpecifics(LanguageSpecifics languageSpecifics) { this.languageSpecifics = languageSpecifics; return this; } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Symbol.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Symbol.java index 55380f6c916..66deef6ee9f 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Symbol.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Symbol.java @@ -10,11 +10,20 @@ public class Symbol { private final int line; private final String type; - public Symbol(SymbolType symbolType, String name, int line, String type) { + @Json(name = "language_specifics") + private final LanguageSpecifics languageSpecifics; + + public Symbol( + SymbolType symbolType, + String name, + int line, + String type, + LanguageSpecifics languageSpecifics) { this.symbolType = symbolType; this.name = name; this.line = line; this.type = type; + this.languageSpecifics = languageSpecifics; } public SymbolType getSymbolType() { @@ -33,6 +42,10 @@ public String getType() { return type; } + public LanguageSpecifics getLanguageSpecifics() { + return languageSpecifics; + } + @Override public String toString() { return "Symbol{" @@ -46,6 +59,8 @@ public String toString() { + ", type='" + type + '\'' + + ", languageSpecifics=" + + languageSpecifics + '}'; } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractor.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractor.java index 3498234820e..f5fadff6ce0 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractor.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractor.java @@ -7,16 +7,19 @@ import com.datadog.debugger.instrumentation.ASMHelper; import datadog.trace.util.Strings; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.LabelNode; @@ -35,44 +38,27 @@ public static Scope extract(byte[] classFileBuffer, String jarName) { private static Scope extractScopes(ClassNode classNode, String jarName) { try { String sourceFile = extractSourceFile(classNode); - List methodScopes = new ArrayList<>(); - for (MethodNode method : classNode.methods) { - MethodLineInfo methodLineInfo = extractMethodLineInfo(method); - List varScopes = new ArrayList<>(); - List methodSymbols = new ArrayList<>(); - int localVarBaseSlot = extractArgs(method, methodSymbols, methodLineInfo.start); - extractScopesFromVariables( - sourceFile, method, methodLineInfo.lineMap, varScopes, localVarBaseSlot); - ScopeType methodScopeType = ScopeType.METHOD; - if (method.name.startsWith("lambda$")) { - methodScopeType = ScopeType.CLOSURE; - } - Scope methodScope = - Scope.builder(methodScopeType, sourceFile, methodLineInfo.start, methodLineInfo.end) - .name(method.name) - .scopes(varScopes) - .symbols(methodSymbols) - .build(); - methodScopes.add(methodScope); - } + List methodScopes = extractMethods(classNode, sourceFile); int classStartLine = Integer.MAX_VALUE; int classEndLine = 0; for (Scope scope : methodScopes) { classStartLine = Math.min(classStartLine, scope.getStartLine()); classEndLine = Math.max(classEndLine, scope.getEndLine()); } - List fields = new ArrayList<>(); - for (FieldNode fieldNode : classNode.fields) { - SymbolType symbolType = - ASMHelper.isStaticField(fieldNode) ? SymbolType.STATIC_FIELD : SymbolType.FIELD; - fields.add( - new Symbol(symbolType, fieldNode.name, 0, Type.getType(fieldNode.desc).getClassName())); - } + List fields = extractFields(classNode); + LanguageSpecifics classSpecifics = + new LanguageSpecifics.Builder() + .addModifiers(extractClassModifiers(classNode.access)) + .addInterfaces(extractInterfaces(classNode)) + .addAnnotations(extractAnnotations(classNode.visibleAnnotations)) + .superClass(Strings.getClassName(classNode.superName)) + .build(); Scope classScope = Scope.builder(ScopeType.CLASS, sourceFile, classStartLine, classEndLine) .name(Strings.getClassName(classNode.name)) .scopes(methodScopes) .symbols(fields) + .languageSpecifics(classSpecifics) .build(); return Scope.builder(ScopeType.JAR, jarName, 0, 0) .name(jarName) @@ -84,6 +70,214 @@ private static Scope extractScopes(ClassNode classNode, String jarName) { } } + private static Collection extractInterfaces(ClassNode classNode) { + if (classNode.interfaces.isEmpty()) { + return Collections.emptyList(); + } + return classNode.interfaces.stream().map(Strings::getClassName).collect(Collectors.toList()); + } + + private static List extractFields(ClassNode classNode) { + List fields = new ArrayList<>(); + for (FieldNode fieldNode : classNode.fields) { + SymbolType symbolType = + ASMHelper.isStaticField(fieldNode) ? SymbolType.STATIC_FIELD : SymbolType.FIELD; + LanguageSpecifics fieldSpecifics = + new LanguageSpecifics.Builder() + .addModifiers(extractFieldModifiers(fieldNode.access)) + .addAnnotations(extractAnnotations(fieldNode.visibleAnnotations)) + .build(); + fields.add( + new Symbol( + symbolType, + fieldNode.name, + 0, + Type.getType(fieldNode.desc).getClassName(), + fieldSpecifics)); + } + return fields; + } + + private static List extractMethods(ClassNode classNode, String sourceFile) { + List methodScopes = new ArrayList<>(); + for (MethodNode method : classNode.methods) { + MethodLineInfo methodLineInfo = extractMethodLineInfo(method); + List varScopes = new ArrayList<>(); + List methodSymbols = new ArrayList<>(); + int localVarBaseSlot = extractArgs(method, methodSymbols, methodLineInfo.start); + extractScopesFromVariables( + sourceFile, method, methodLineInfo.lineMap, varScopes, localVarBaseSlot); + ScopeType methodScopeType = ScopeType.METHOD; + if (method.name.startsWith("lambda$")) { + methodScopeType = ScopeType.CLOSURE; + } + LanguageSpecifics methodSpecifics = + new LanguageSpecifics.Builder() + .addModifiers(extractMethodModifiers(classNode, method, method.access)) + .addAnnotations(extractAnnotations(method.visibleAnnotations)) + .returnType(Type.getType(method.desc).getReturnType().getClassName()) + .build(); + Scope methodScope = + Scope.builder(methodScopeType, sourceFile, methodLineInfo.start, methodLineInfo.end) + .name(method.name) + .scopes(varScopes) + .symbols(methodSymbols) + .languageSpecifics(methodSpecifics) + .build(); + methodScopes.add(methodScope); + } + return methodScopes; + } + + private static Collection extractClassModifiers(int access) { + List results = new ArrayList<>(); + for (int remaining = access, bit; remaining != 0; remaining -= bit) { + bit = Integer.lowestOneBit(remaining); + switch (bit) { + case Opcodes.ACC_PUBLIC: + results.add("public"); + break; + case Opcodes.ACC_PRIVATE: + results.add("private"); + break; + case Opcodes.ACC_PROTECTED: + results.add("protected"); + break; + case Opcodes.ACC_STATIC: + results.add("static"); + break; + case Opcodes.ACC_FINAL: + results.add("final"); + break; + case Opcodes.ACC_SUPER: + break; // not interesting + case Opcodes.ACC_INTERFACE: + results.add("interface"); + break; + case Opcodes.ACC_ABSTRACT: + results.add("abstract"); + break; + case Opcodes.ACC_SYNTHETIC: + results.add("synthetic"); + break; + case Opcodes.ACC_ANNOTATION: + results.add("annotation"); + break; + case Opcodes.ACC_ENUM: + results.add("enum"); + break; + default: + throw new IllegalArgumentException("Invalid access modifiers: " + bit); + } + } + return results; + } + + private static Collection extractMethodModifiers( + ClassNode classNode, MethodNode methodNode, int access) { + List results = new ArrayList<>(); + for (int remaining = access, bit; remaining != 0; remaining -= bit) { + bit = Integer.lowestOneBit(remaining); + switch (bit) { + case Opcodes.ACC_PUBLIC: + results.add("public"); + break; + case Opcodes.ACC_PRIVATE: + results.add("private"); + break; + case Opcodes.ACC_PROTECTED: + results.add("protected"); + break; + case Opcodes.ACC_STATIC: + results.add("static"); + break; + case Opcodes.ACC_FINAL: + results.add("final"); + break; + case Opcodes.ACC_SYNCHRONIZED: + results.add("synchronized"); + break; + case Opcodes.ACC_BRIDGE: + results.add("(bridge)"); + break; + case Opcodes.ACC_VARARGS: + results.add("(varargs)"); + break; + case Opcodes.ACC_NATIVE: + results.add("native"); + break; + case Opcodes.ACC_ABSTRACT: + results.add("abstract"); + break; + case Opcodes.ACC_STRICT: + results.add("strictfp"); + break; + case Opcodes.ACC_SYNTHETIC: + results.add("synthetic"); + break; + default: + throw new IllegalArgumentException("Invalid access modifiers: " + bit); + } + } + // if class is an interface && method as code this is a default method + if ((classNode.access & Opcodes.ACC_INTERFACE) > 0 && methodNode.instructions.size() > 0) { + results.add("default"); + } + return results; + } + + private static Collection extractFieldModifiers(int access) { + List results = new ArrayList<>(); + for (int remaining = access, bit; remaining != 0; remaining -= bit) { + bit = Integer.lowestOneBit(remaining); + switch (bit) { + case Opcodes.ACC_PUBLIC: + results.add("public"); + break; + case Opcodes.ACC_PRIVATE: + results.add("private"); + break; + case Opcodes.ACC_PROTECTED: + results.add("protected"); + break; + case Opcodes.ACC_STATIC: + results.add("static"); + break; + case Opcodes.ACC_FINAL: + results.add("final"); + break; + case Opcodes.ACC_VOLATILE: + results.add("volatile"); + break; + case Opcodes.ACC_TRANSIENT: + results.add("transient"); + break; + case Opcodes.ACC_SYNTHETIC: + results.add("synthetic"); + break; + case Opcodes.ACC_ENUM: + results.add("enum"); + break; + default: + throw new IllegalArgumentException("Invalid access modifiers: " + bit); + } + } + return results; + } + + private static Collection extractAnnotations(List annotationNodes) { + if (annotationNodes == null || annotationNodes.isEmpty()) { + return Collections.emptyList(); + } + List results = new ArrayList<>(); + for (AnnotationNode annotationNode : annotationNodes) { + StringBuilder sb = new StringBuilder("@"); + sb.append(Type.getType(annotationNode.desc).getClassName()); + results.add(sb.toString()); + } + return results; + } + private static String extractSourceFile(ClassNode classNode) { String packageName = classNode.name; int idx = packageName.lastIndexOf('/'); @@ -111,7 +305,7 @@ private static int extractArgs( } String argName = localVarsBySlot[slot] != null ? localVarsBySlot[slot].name : "p" + slot; methodSymbols.add( - new Symbol(SymbolType.ARG, argName, methodStartLine, argType.getClassName())); + new Symbol(SymbolType.ARG, argName, methodStartLine, argType.getClassName(), null)); slot += argType.getSize(); } return slot; @@ -149,7 +343,8 @@ private static void extractScopesFromVariables( int line = monotonicLineMap.get(var.start.getLabel()); minLine = Math.min(line, minLine); varSymbols.add( - new Symbol(SymbolType.LOCAL, var.name, line, Type.getType(var.desc).getClassName())); + new Symbol( + SymbolType.LOCAL, var.name, line, Type.getType(var.desc).getClassName(), null)); } int endLine = monotonicLineMap.get(entry.getKey().getLabel()); Scope varScope = diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotPruner.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotPruner.java new file mode 100644 index 00000000000..216441237b0 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotPruner.java @@ -0,0 +1,240 @@ +package com.datadog.debugger.util; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.function.Supplier; + +public class SnapshotPruner { + private static final String NOT_CAPTURED_REASON = "notCapturedReason"; + private static final String DEPTH = "depth"; + private static final String PRUNED = "{\"pruned\":true}"; + + private State state = State.OBJECT; + private final Deque stack = new ArrayDeque<>(32); + private int currentLevel; + private int strMatchIdx; + private Supplier onStringMatches; + private String matchingString; + private Node root; + + public static String prune(String snapshot, int maxTargetedSize, int minLevel) { + int delta = snapshot.length() - maxTargetedSize; + if (delta <= 0) { + return snapshot; + } + SnapshotPruner snapshotPruner = new SnapshotPruner(snapshot); + Collection leaves = snapshotPruner.getLeaves(minLevel); + PriorityQueue sortedLeaves = + new PriorityQueue<>( + Comparator.comparing((Node n) -> n.notCapturedDepth) + .thenComparingInt((Node n) -> n.level) + .thenComparing((Node n) -> n.notCaptured) + .thenComparingInt(Node::size) + .reversed()); + sortedLeaves.addAll(leaves); + int total = 0; + Map nodes = new HashMap<>(); + while (!sortedLeaves.isEmpty()) { + Node leaf = sortedLeaves.poll(); + nodes.put(leaf.start, leaf); + total += leaf.size() - PRUNED.length(); + if (total > delta) break; + Node parent = leaf.parent; + if (parent == null) { + break; + } + parent.pruned++; + if (parent.pruned >= parent.children.size() && parent.level >= minLevel) { + // We have pruned all the children of this parent node, so we can + // treat it as a leaf now. + parent.notCaptured = true; + parent.notCapturedDepth = true; + sortedLeaves.offer(parent); + for (Node child : parent.children) { + nodes.remove(child.start); + total -= child.size() - PRUNED.length(); + } + } + } + List prunedNodes = new ArrayList<>(nodes.values()); + prunedNodes.sort(Comparator.comparing((Node n) -> n.start)); + StringBuilder sb = new StringBuilder(); + sb.append(snapshot, 0, prunedNodes.get(0).start); + for (int i = 1; i < prunedNodes.size(); i++) { + sb.append(PRUNED); + sb.append(snapshot, prunedNodes.get(i - 1).end + 1, prunedNodes.get(i).start); + } + sb.append(PRUNED); + sb.append(snapshot, prunedNodes.get(prunedNodes.size() - 1).end + 1, snapshot.length()); + return sb.toString(); + } + + private Collection getLeaves(int minLevel) { + if (root == null) { + return Collections.emptyList(); + } + return root.getLeaves(minLevel); + } + + private SnapshotPruner(String snapshot) { + for (int i = 0; i < snapshot.length(); i++) { + state = state.parse(this, snapshot.charAt(i), i); + if (state == null) { + break; + } + } + } + + enum State { + OBJECT { + @Override + public State parse(SnapshotPruner pruner, char c, int index) { + switch (c) { + case '{': + { + Node n = new Node(index, pruner.currentLevel); + pruner.currentLevel++; + if (!pruner.stack.isEmpty()) { + n.parent = pruner.stack.peekLast(); + n.parent.children.add(n); + } + pruner.stack.addLast(n); + return this; + } + case '}': + { + Node n = pruner.stack.removeLast(); + n.end = index; + pruner.currentLevel--; + if (pruner.stack.isEmpty()) { + pruner.root = n; + return null; + } + return this; + } + case '"': + { + pruner.strMatchIdx = 0; + pruner.matchingString = NOT_CAPTURED_REASON; + pruner.onStringMatches = + () -> { + Node n = pruner.stack.peekLast(); + if (n == null) { + throw new IllegalStateException("empty stack"); + } + n.notCaptured = true; + return NOT_CAPTURED; + }; + return STRING; + } + default: + return this; + } + } + }, + STRING { + @Override + public State parse(SnapshotPruner pruner, char c, int index) { + switch (c) { + case '"': + { + if (pruner.strMatchIdx == pruner.matchingString.length()) { + return pruner.onStringMatches.get(); + } + return OBJECT; + } + case '\\': + { + pruner.strMatchIdx = -1; + return ESCAPE; + } + default: + if (pruner.strMatchIdx > -1) { + char current = pruner.matchingString.charAt(pruner.strMatchIdx++); + if (c != current) { + pruner.strMatchIdx = -1; + } + } + return this; + } + } + }, + NOT_CAPTURED { + @Override + public State parse(SnapshotPruner pruner, char c, int index) { + switch (c) { + case '"': + { + pruner.strMatchIdx = 0; + pruner.matchingString = DEPTH; + pruner.onStringMatches = + () -> { + Node n = pruner.stack.peekLast(); + if (n == null) { + throw new IllegalStateException("empty stack"); + } + n.notCapturedDepth = true; + return OBJECT; + }; + return STRING; + } + case ' ': + case ':': + case '\n': + case '\t': + case '\r': + return this; + default: + return OBJECT; + } + } + }, + ESCAPE { + @Override + public State parse(SnapshotPruner pruner, char c, int index) { + return STRING; + } + }; + + public abstract State parse(SnapshotPruner pruner, char c, int index); + } + + private static class Node { + int pruned; + Node parent; + List children = new ArrayList<>(); + final int start; + int end; + final int level; + boolean notCaptured; + boolean notCapturedDepth; + + public Node(int start, int level) { + this.start = start; + this.level = level; + } + + public Collection getLeaves(int minLevel) { + if (children.isEmpty() && level >= minLevel) { + return Collections.singleton(this); + } + Collection results = new ArrayList<>(); + for (int i = children.size() - 1; i >= 0; i--) { + results.addAll(children.get(i).getLeaves(minLevel)); + } + return results; + } + + public int size() { + return end - start + 1; + } + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotSlicer.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotSlicer.java deleted file mode 100644 index 43232c0647d..00000000000 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotSlicer.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.datadog.debugger.util; - -import static com.datadog.debugger.util.MoshiSnapshotHelper.DEPTH_REASON; -import static com.datadog.debugger.util.MoshiSnapshotHelper.ELEMENTS; -import static com.datadog.debugger.util.MoshiSnapshotHelper.ENTRIES; -import static com.datadog.debugger.util.MoshiSnapshotHelper.FIELDS; -import static com.datadog.debugger.util.MoshiSnapshotHelper.NOT_CAPTURED_REASON; - -import com.squareup.moshi.JsonReader; -import com.squareup.moshi.JsonWriter; -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.Deque; -import okio.Buffer; - -/** Reduces the serialized snapshot by removing last depth level (fields or collections) */ -public class SnapshotSlicer { - - private final Deque tokenLevels = new ArrayDeque<>(32); - private final Deque fieldsLevels = new ArrayDeque<>(32); - private final int maxDepth; - private boolean copy = true; - private int skipTokenLevel = -1; - private JsonReader jsonReader; - private JsonWriter jsonWriter; - - public static String slice(int maxDepth, String snapshot) { - return new SnapshotSlicer(maxDepth).internalSlice(snapshot); - } - - private SnapshotSlicer(int maxDepth) { - this.maxDepth = maxDepth; - } - - private String internalSlice(String snapshot) { - try { - Buffer writeBuffer = new Buffer(); - jsonWriter = JsonWriter.of(writeBuffer); - jsonReader = JsonReader.of(new Buffer().writeUtf8(snapshot)); - while (jsonReader.hasNext()) { - JsonReader.Token token = jsonReader.peek(); - processToken(token); - handleLevelEnd(); - } - return writeBuffer.readUtf8(); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - private void processToken(JsonReader.Token token) throws IOException { - switch (token) { - case BEGIN_ARRAY: - tokenLevels.addLast(token); - jsonReader.beginArray(); - if (copy) { - jsonWriter.beginArray(); - } - break; - case BEGIN_OBJECT: - tokenLevels.addLast(token); - jsonReader.beginObject(); - if (copy) { - jsonWriter.beginObject(); - } - break; - case NAME: - String name = jsonReader.nextName(); - if (FIELDS.equals(name)) { - fieldsLevels.addLast(tokenLevels.size()); - if (copy && fieldsLevels.size() > maxDepth) { - jsonWriter.name(NOT_CAPTURED_REASON); - jsonWriter.value(DEPTH_REASON); - copy = false; - skipTokenLevel = tokenLevels.size(); - } - } - if (ELEMENTS.equals(name) || ENTRIES.equals(name)) { - fieldsLevels.addLast(tokenLevels.size()); - } - if (copy) { - jsonWriter.name(name); - } - break; - case BOOLEAN: - boolean b = jsonReader.nextBoolean(); - if (copy) { - jsonWriter.value(b); - } - break; - case NULL: - jsonReader.nextNull(); - if (copy) { - jsonWriter.nullValue(); - } - break; - case NUMBER: - // Moshi always consider numbers as decimal. - // need to parse it as string and detect if dot is present - // or not to determine ints/longs vs doubles - String numberStrValue = jsonReader.nextString(); - if (copy) { - if (numberStrValue.indexOf('.') > 0) { - jsonWriter.value(Double.parseDouble(numberStrValue)); - } else { - jsonWriter.value(Long.parseLong(numberStrValue)); - } - } - break; - case STRING: - String s = jsonReader.nextString(); - if (copy) { - jsonWriter.value(s); - } - break; - default: - throw new UnsupportedOperationException("Unsupported token: " + token); - } - } - - private void handleLevelEnd() throws IOException { - while (!jsonReader.hasNext() && !tokenLevels.isEmpty()) { - JsonReader.Token lastToken = tokenLevels.removeLast(); - switch (lastToken) { - case BEGIN_ARRAY: - jsonReader.endArray(); - if (copy) { - jsonWriter.endArray(); - } - break; - case BEGIN_OBJECT: - jsonReader.endObject(); - if (copy) { - jsonWriter.endObject(); - } - if (!fieldsLevels.isEmpty() && tokenLevels.size() == fieldsLevels.peekLast().intValue()) { - fieldsLevels.removeLast(); - } - if (!copy && skipTokenLevel == tokenLevels.size()) { - copy = true; - skipTokenLevel = -1; - } - break; - default: - throw new UnsupportedOperationException("Unsupported last token: " + lastToken); - } - } - } -} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ValueScriptHelper.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ValueScriptHelper.java index fb4ee1cfccf..60e25f8254c 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ValueScriptHelper.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ValueScriptHelper.java @@ -9,10 +9,8 @@ import java.time.temporal.ChronoUnit; public class ValueScriptHelper { - private static final Limits LIMITS = new Limits(1, 3, 255, 5); - public static void serializeValue( - StringBuilder sb, String expr, Object value, CapturedContext.Status status) { + StringBuilder sb, String expr, Object value, CapturedContext.Status status, Limits limits) { Duration timeout = Duration.of(Config.get().getDebuggerCaptureTimeout(), ChronoUnit.MILLIS); TimeoutChecker timeoutChecker = new TimeoutChecker(timeout); SerializerWithLimits serializer = @@ -21,7 +19,7 @@ public static void serializeValue( serializer.serialize( value, value != null ? value.getClass().getTypeName() : Object.class.getTypeName(), - LIMITS); + limits); } catch (Exception ex) { status.addError(new EvaluationError(expr, ex.getMessage())); } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index 1b8ec443bce..d96f3587754 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -148,7 +148,7 @@ public void methodProbe() throws IOException, URISyntaxException { public void singleLineProbe() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot01"; DebuggerTransformerTest.TestSnapshotListener listener = - installSingleProbe(CLASS_NAME, "main", "int (java.lang.String)", "8"); + installSingleProbeAtExit(CLASS_NAME, "main", "int (java.lang.String)", "8"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); assertEquals(3, result); @@ -871,6 +871,40 @@ public void simpleConditionTest() throws IOException, URISyntaxException { listener.snapshots.get(0).getCaptures().getReturn(), "arg", "java.lang.String", "5"); } + @Test + public void lineProbeCondition() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot08"; + LogProbe logProbe = + createProbeBuilder(PROBE_ID, CLASS_NAME, "doit", "int (java.lang.String)", "34") + .when( + new ProbeCondition( + DSL.when( + DSL.and( + // this is always true + DSL.and( + // this reference is resolved directly from the snapshot + DSL.eq(DSL.ref("fld"), DSL.value(11)), + // this reference chain needs to use reflection + DSL.eq( + DSL.getMember( + DSL.getMember( + DSL.getMember(DSL.ref("typed"), "fld"), "fld"), + "msg"), + DSL.value("hello"))), + DSL.eq(DSL.ref("arg"), DSL.value("5")))), + "(fld == 11 && typed.fld.fld.msg == \"hello\") && arg == '5'")) + .build(); + DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, logProbe); + Class testClass = compileAndLoadClass(CLASS_NAME); + for (int i = 0; i < 100; i++) { + int result = Reflect.on(testClass).call("main", String.valueOf(i)).get(); + assertTrue((i == 2 && result == 2) || result == 3); + } + assertEquals(1, listener.snapshots.size()); + assertCaptureArgs( + listener.snapshots.get(0).getCaptures().getLines().get(34), "arg", "java.lang.String", "5"); + } + @Test public void staticFieldCondition() throws IOException, URISyntaxException { final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot19"; @@ -1112,7 +1146,7 @@ public void fields() throws IOException, URISyntaxException { int result = Reflect.on(testClass).call("main", "f").get(); assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); - assertCaptureFieldCount(snapshot.getCaptures().getEntry(), 5); + assertCaptureFieldCount(snapshot.getCaptures().getEntry(), 7); // +2 for correlation ids assertCaptureFields(snapshot.getCaptures().getEntry(), "intValue", "int", "24"); assertCaptureFields(snapshot.getCaptures().getEntry(), "doubleValue", "double", "3.14"); assertCaptureFields( @@ -1124,7 +1158,7 @@ public void fields() throws IOException, URISyntaxException { Arrays.asList("foo", "bar")); assertCaptureFields( snapshot.getCaptures().getEntry(), "strMap", "java.util.HashMap", Collections.emptyMap()); - assertCaptureFieldCount(snapshot.getCaptures().getReturn(), 5); + assertCaptureFieldCount(snapshot.getCaptures().getReturn(), 7); // +2 for correlation ids assertCaptureFields(snapshot.getCaptures().getReturn(), "intValue", "int", "48"); assertCaptureFields(snapshot.getCaptures().getReturn(), "doubleValue", "double", "3.14"); assertCaptureFields(snapshot.getCaptures().getReturn(), "strValue", "java.lang.String", "done"); @@ -1154,11 +1188,11 @@ public void inheritedFields() throws IOException, URISyntaxException { assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); // Only Declared fields in the current class are captured, not inherited fields - assertCaptureFieldCount(snapshot.getCaptures().getEntry(), 5); + assertCaptureFieldCount(snapshot.getCaptures().getEntry(), 7); // +2 for correlation ids assertCaptureFields( snapshot.getCaptures().getEntry(), "strValue", "java.lang.String", "foobar"); assertCaptureFields(snapshot.getCaptures().getEntry(), "intValue", "int", "24"); - assertCaptureFieldCount(snapshot.getCaptures().getReturn(), 5); + assertCaptureFieldCount(snapshot.getCaptures().getReturn(), 7); // +2 for correlation ids assertCaptureFields( snapshot.getCaptures().getReturn(), "strValue", "java.lang.String", "barfoo"); assertCaptureFields(snapshot.getCaptures().getEntry(), "intValue", "int", "24"); @@ -1200,10 +1234,12 @@ public void staticInheritedFields() throws IOException, URISyntaxException { Snapshot snapshot = assertOneSnapshot(listener); Map staticFields = snapshot.getCaptures().getReturn().getStaticFields(); - assertEquals(5, staticFields.size()); + assertEquals(7, staticFields.size()); assertEquals("barfoo", getValue(staticFields.get("strValue"))); assertEquals("48", getValue(staticFields.get("intValue"))); assertEquals("6.28", getValue(staticFields.get("doubleValue"))); + assertEquals("[1, 2, 3, 4]", getValue(staticFields.get("longValues"))); + assertEquals("[foo, bar]", getValue(staticFields.get("strValues"))); } @Test @@ -1582,7 +1618,7 @@ private Snapshot doUnknownCount(String CLASS_NAME) throws IOException, URISyntax public void beforeForLoopLineProbe() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot02"; DebuggerTransformerTest.TestSnapshotListener listener = - installSingleProbe(CLASS_NAME, null, null, "46"); + installSingleProbeAtExit(CLASS_NAME, null, null, "46"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "synchronizedBlock").get(); assertEquals(76, result); @@ -1597,10 +1633,12 @@ public void dupLineProbeSameTemplate() throws IOException, URISyntaxException { LogProbe probe1 = createProbeBuilder(PROBE_ID1, CLASS_NAME, null, null, "39") .template(LOG_TEMPLATE, parseTemplate(LOG_TEMPLATE)) + .evaluateAt(MethodLocation.EXIT) .build(); LogProbe probe2 = createProbeBuilder(PROBE_ID2, CLASS_NAME, null, null, "39") .template(LOG_TEMPLATE, parseTemplate(LOG_TEMPLATE)) + .evaluateAt(MethodLocation.EXIT) .build(); DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, probe1, probe2); @@ -1824,25 +1862,36 @@ public void typeRedactionCondition() throws IOException, URISyntaxException { } @Test - public void samplingMethodProbe() throws IOException, URISyntaxException { + public void ensureCallingSamplingMethodProbe() throws IOException, URISyntaxException { doSamplingTest(this::methodProbe, 1, 1); } @Test - public void samplingProbeCondition() throws IOException, URISyntaxException { + public void ensureCallingSamplingProbeCondition() throws IOException, URISyntaxException { doSamplingTest(this::simpleConditionTest, 1, 1); } @Test - public void samplingDupMethodProbeCondition() throws IOException, URISyntaxException { + public void ensureCallingSamplingProbeConditionError() throws IOException, URISyntaxException { + doSamplingTest(this::nullCondition, 1, 1); + } + + @Test + public void ensureCallingSamplingDupMethodProbeCondition() + throws IOException, URISyntaxException { doSamplingTest(this::mergedProbesWithAdditionalProbeConditionTest, 2, 2); } @Test - public void samplingLineProbe() throws IOException, URISyntaxException { + public void ensureCallingSamplingLineProbe() throws IOException, URISyntaxException { doSamplingTest(this::singleLineProbe, 1, 1); } + @Test + public void ensureCallingSamplingLineProbeCondition() throws IOException, URISyntaxException { + doSamplingTest(this::lineProbeCondition, 1, 1); + } + interface TestMethod { void run() throws IOException, URISyntaxException; } @@ -1910,6 +1959,12 @@ private DebuggerTransformerTest.TestSnapshotListener installSingleProbe( return installProbes(typeName, logProbes); } + private DebuggerTransformerTest.TestSnapshotListener installSingleProbeAtExit( + String typeName, String methodName, String signature, String... lines) { + LogProbe logProbes = createProbeAtExit(PROBE_ID, typeName, methodName, signature, lines); + return installProbes(typeName, logProbes); + } + private DebuggerTransformerTest.TestSnapshotListener installProbes( String expectedClassName, Configuration configuration) { Config config = mock(Config.class); @@ -2087,6 +2142,12 @@ private static String getValue(CapturedContext.CapturedValue capturedValue) { Assertions.fail("NotCapturedReason: " + valued.getNotCapturedReason()); } Object obj = valued.getValue(); + if (obj != null && obj.getClass().isArray()) { + if (obj.getClass().getComponentType().isPrimitive()) { + return primitiveArrayToString(obj); + } + return Arrays.toString((Object[]) obj); + } return obj != null ? String.valueOf(obj) : null; } catch (IOException e) { e.printStackTrace(); @@ -2094,6 +2155,35 @@ private static String getValue(CapturedContext.CapturedValue capturedValue) { } } + private static String primitiveArrayToString(Object obj) { + Class componentType = obj.getClass().getComponentType(); + if (componentType == long.class) { + return Arrays.toString((long[]) obj); + } + if (componentType == int.class) { + return Arrays.toString((int[]) obj); + } + if (componentType == short.class) { + return Arrays.toString((short[]) obj); + } + if (componentType == char.class) { + return Arrays.toString((char[]) obj); + } + if (componentType == byte.class) { + return Arrays.toString((byte[]) obj); + } + if (componentType == boolean.class) { + return Arrays.toString((boolean[]) obj); + } + if (componentType == float.class) { + return Arrays.toString((float[]) obj); + } + if (componentType == double.class) { + return Arrays.toString((double[]) obj); + } + return null; + } + public static Map getFields( CapturedContext.CapturedValue capturedValue) { try { @@ -2169,6 +2259,13 @@ private static LogProbe createProbe( return createProbeBuilder(id, typeName, methodName, signature, lines).build(); } + private static LogProbe createProbeAtExit( + ProbeId id, String typeName, String methodName, String signature, String... lines) { + return createProbeBuilder(id, typeName, methodName, signature, lines) + .evaluateAt(MethodLocation.EXIT) + .build(); + } + private static LogProbe.Builder createProbeBuilder( ProbeId id, String typeName, String methodName, String signature, String... lines) { return LogProbe.builder() @@ -2186,6 +2283,7 @@ private static LogProbe createSourceFileProbe(ProbeId id, String sourceFile, int .probeId(id) .captureSnapshot(true) .where(null, null, null, line, sourceFile) + .evaluateAt(MethodLocation.EXIT) .build(); } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogProbesInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogProbesInstrumentationTest.java index 47f80e0c94d..e112833fb1d 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogProbesInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogProbesInstrumentationTest.java @@ -37,6 +37,8 @@ public class LogProbesInstrumentationTest { private static final ProbeId LOG_ID1 = new ProbeId("beae1807-f3b0-4ea8-a74f-826790c5e6f8", 0); private static final ProbeId LOG_ID2 = new ProbeId("beae1807-f3b0-4ea8-a74f-826790c5e6f9", 0); private static final String SERVICE_NAME = "service-name"; + private static final String STR_8K = + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; private Instrumentation instr = ByteBuddyAgent.install(); private ClassFileTransformer currentTransformer; @@ -62,6 +64,19 @@ public void methodPlainLog() throws IOException, URISyntaxException { assertEquals("this is log line", snapshot.getMessage()); } + @Test + public void methodLargePlainLog() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + DebuggerTransformerTest.TestSnapshotListener listener = + installSingleProbe(STR_8K + "123", CLASS_NAME, "main", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "1").get(); + Assertions.assertEquals(3, result); + Snapshot snapshot = assertOneSnapshot(listener); + assertCapturesNull(snapshot); + assertEquals(STR_8K + "...", snapshot.getMessage()); + } + @Test public void methodTemplateArgLog() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot01"; @@ -76,6 +91,49 @@ public void methodTemplateArgLog() throws IOException, URISyntaxException { assertEquals("this is log line with arg=1", snapshot.getMessage()); } + @Test + public void methodTemplateArgLogLarge() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + DebuggerTransformerTest.TestSnapshotListener listener = + installSingleProbe(STR_8K + "{arg}", CLASS_NAME, "main", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "1").get(); + Assertions.assertEquals(3, result); + Snapshot snapshot = assertOneSnapshot(listener); + assertCapturesNull(snapshot); + assertEquals(STR_8K + "...", snapshot.getMessage()); + } + + @Test + public void methodTemplateLargeArgLog() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + DebuggerTransformerTest.TestSnapshotListener listener = + installSingleProbe( + "this is log line with arg={arg}", CLASS_NAME, "main", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", STR_8K).get(); + Assertions.assertEquals(3, result); + Snapshot snapshot = assertOneSnapshot(listener); + assertCapturesNull(snapshot); + final String expectedStr = "this is log line with arg="; + assertEquals( + expectedStr + STR_8K.substring(0, STR_8K.length() - expectedStr.length()) + "...", + snapshot.getMessage()); + } + + @Test + public void methodTemplateTooLargeArgLog() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + DebuggerTransformerTest.TestSnapshotListener listener = + installSingleProbe("{arg}", CLASS_NAME, "main", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", STR_8K + "123").get(); + Assertions.assertEquals(3, result); + Snapshot snapshot = assertOneSnapshot(listener); + assertCapturesNull(snapshot); + assertEquals(STR_8K + "...", snapshot.getMessage()); + } + @Test public void methodTemplateArgLogEvaluateAtExit() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot01"; diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java index d898d7d904a..0fbb89f2a2e 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java @@ -366,6 +366,63 @@ public void methodFieldRefValueDistributionDoubleMetric() throws IOException, UR Assertions.assertArrayEquals(new String[] {METRIC_PROBEID_TAG}, listener.lastTags); } + @Test + public void methodSyntheticReturnGaugeMetric() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot06"; + String METRIC_NAME = "syn_gauge"; + MetricProbe metricProbe = + createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) + .where(CLASS_NAME, "f", "()") + .valueScript(new ValueScript(DSL.ref("@return"), "@return")) + .evaluateAt(MethodLocation.EXIT) + .build(); + MetricForwarderListener listener = installMetricProbes(metricProbe); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "f").get(); + Assertions.assertEquals(42, result); + Assertions.assertTrue(listener.gauges.containsKey(METRIC_NAME)); + Assertions.assertEquals(42, listener.gauges.get(METRIC_NAME).longValue()); + Assertions.assertArrayEquals(new String[] {METRIC_PROBEID_TAG}, listener.lastTags); + } + + @Test + public void methodSyntheticReturnInvalidType() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot06"; + final String INHERITED_CLASS_NAME = CLASS_NAME + "$Inherited"; + String METRIC_NAME = "syn_gauge"; + MetricProbe metricProbe = + createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) + .where(INHERITED_CLASS_NAME, "", "()") + .valueScript(new ValueScript(DSL.ref("@return"), "@return")) + .evaluateAt(MethodLocation.EXIT) + .build(); + MetricForwarderListener listener = installMetricProbes(metricProbe); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "").get(); + Assertions.assertEquals(42, result); + Assertions.assertFalse(listener.gauges.containsKey(METRIC_NAME)); + verify(probeStatusSink).addError(eq(METRIC_ID), eq("Cannot resolve symbol @return")); + } + + @Test + public void methodSyntheticDurationGaugeMetric() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot06"; + String METRIC_NAME = "syn_gauge"; + MetricProbe metricProbe = + createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) + .where(CLASS_NAME, "f", "()") + .valueScript(new ValueScript(DSL.ref("@duration"), "@duration")) + .evaluateAt(MethodLocation.EXIT) + .build(); + MetricForwarderListener listener = installMetricProbes(metricProbe); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "f").get(); + Assertions.assertEquals(42, result); + Assertions.assertTrue(listener.doubleGauges.containsKey(METRIC_NAME)); + Assertions.assertTrue(listener.doubleGauges.get(METRIC_NAME).doubleValue() > 0); + Assertions.assertArrayEquals(new String[] {METRIC_PROBEID_TAG}, listener.lastTags); + } + @Test public void lineArgumentRefValueCountMetric() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot03"; diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanDecorationProbeInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanDecorationProbeInstrumentationTest.java index b19401ac7a2..2da4d093479 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanDecorationProbeInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanDecorationProbeInstrumentationTest.java @@ -1,6 +1,7 @@ package com.datadog.debugger.agent; import static com.datadog.debugger.el.DSL.eq; +import static com.datadog.debugger.el.DSL.gt; import static com.datadog.debugger.el.DSL.ref; import static com.datadog.debugger.el.DSL.value; import static com.datadog.debugger.probe.SpanDecorationProbe.TargetSpan.ACTIVE; @@ -11,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static utils.InstrumentationTestHelper.compileAndLoadClass; @@ -180,6 +182,34 @@ public void methodActiveSpanInvalidCondition() throws IOException, URISyntaxExce assertEquals("Cannot find symbol: noarg", snapshot.getEvaluationErrors().get(0).getMessage()); } + @Test + public void methodActiveSpanSynthReturn() throws IOException, URISyntaxException { + final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; + SpanDecorationProbe.Decoration decoration = + createDecoration(gt(ref("@return"), value(0)), "@return > '0", "tag1", "{@return}"); + installSingleSpanDecoration( + CLASS_NAME, ACTIVE, decoration, "process", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "5").get(); + assertEquals(84, result); + MutableSpan span = traceInterceptor.getFirstSpan(); + assertEquals("84", span.getTags().get("tag1")); + } + + @Test + public void methodActiveSpanSynthDuration() throws IOException, URISyntaxException { + final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; + SpanDecorationProbe.Decoration decoration = + createDecoration(gt(ref("@duration"), value(0)), "@return > '0", "tag1", "{@duration}"); + installSingleSpanDecoration( + CLASS_NAME, ACTIVE, decoration, "process", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "5").get(); + assertEquals(84, result); + MutableSpan span = traceInterceptor.getFirstSpan(); + assertTrue(Double.parseDouble((String) span.getTags().get("tag1")) > 0); + } + @Test public void lineActiveSpanSimpleTag() throws IOException, URISyntaxException { final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogMessageTemplateBuilderTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/StringTemplateBuilderTest.java similarity index 85% rename from dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogMessageTemplateBuilderTest.java rename to dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/StringTemplateBuilderTest.java index 0089376522b..83df1db244a 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogMessageTemplateBuilderTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/StringTemplateBuilderTest.java @@ -11,6 +11,7 @@ import com.datadog.debugger.probe.LogProbe; import datadog.trace.bootstrap.debugger.CapturedContext; import datadog.trace.bootstrap.debugger.EvaluationError; +import datadog.trace.bootstrap.debugger.Limits; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Arrays; @@ -23,12 +24,13 @@ import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.JRE; -class LogMessageTemplateBuilderTest { +class StringTemplateBuilderTest { + private static final Limits LIMITS = new Limits(1, 3, 255, 5); @Test public void emptyProbe() { LogProbe probe = LogProbe.builder().build(); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); String message = summaryBuilder.evaluate(new CapturedContext(), new LogProbe.LogStatus(probe)); assertNull(message); } @@ -36,7 +38,7 @@ public void emptyProbe() { @Test public void emptyTemplate() { LogProbe probe = createLogProbe(""); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); String message = summaryBuilder.evaluate(new CapturedContext(), new LogProbe.LogStatus(probe)); assertEquals("", message); } @@ -44,7 +46,7 @@ public void emptyTemplate() { @Test public void onlyStringTemplate() { LogProbe probe = createLogProbe("this is a simple string"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); String message = summaryBuilder.evaluate(new CapturedContext(), new LogProbe.LogStatus(probe)); assertEquals("this is a simple string", message); } @@ -52,7 +54,7 @@ public void onlyStringTemplate() { @Test public void undefinedArgTemplate() { LogProbe probe = createLogProbe("{arg}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); String message = summaryBuilder.evaluate(new CapturedContext(), new LogProbe.LogStatus(probe)); assertEquals("{Cannot find symbol: arg}", message); } @@ -60,7 +62,7 @@ public void undefinedArgTemplate() { @Test public void argTemplate() { LogProbe probe = createLogProbe("{arg}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -78,7 +80,7 @@ public void booleanArgTemplate() { new ValueScript( DSL.bool(DSL.contains(DSL.ref("arg"), new StringValue("o"))), "{arg}"))); LogProbe probe = LogProbe.builder().template("{contains(arg, 'o')}", segments).build(); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -91,14 +93,14 @@ public void booleanArgTemplate() { @Test public void argMultipleInFlightTemplate() { LogProbe probe = createLogProbe("{arg}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { CapturedContext.CapturedValue.of("arg", String.class.getTypeName(), "foo") }); String message = summaryBuilder.evaluate(capturedContext, new LogProbe.LogStatus(probe)); - LogMessageTemplateBuilder summaryBuilder2 = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder2 = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext2 = new CapturedContext(); capturedContext2.addArguments( new CapturedContext.CapturedValue[] { @@ -112,7 +114,7 @@ public void argMultipleInFlightTemplate() { @Test public void argNullTemplate() { LogProbe probe = createLogProbe("{nullObject}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -125,7 +127,7 @@ public void argNullTemplate() { @Test public void argArrayTemplate() { LogProbe probe = createLogProbe("{primArray} {strArray}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -145,7 +147,7 @@ public void argArrayTemplate() { @Test public void argCollectionTemplate() { LogProbe probe = createLogProbe("{strList} {strSet}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -171,7 +173,7 @@ public void argCollectionTemplate() { @Test public void argMapTemplate() { LogProbe probe = createLogProbe("{strMap}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); Map map = new LinkedHashMap<>(); for (int i = 0; i < 10; i++) { @@ -199,7 +201,7 @@ static class Level1 { @Test public void argComplexObjectTemplate() { LogProbe probe = createLogProbe("{obj}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -212,7 +214,7 @@ public void argComplexObjectTemplate() { @Test public void argComplexObjectArrayTemplate() { LogProbe probe = createLogProbe("{array}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -228,7 +230,7 @@ public void argComplexObjectArrayTemplate() { @DisabledIf("datadog.trace.api.Platform#isJ9") public void argInaccessibleFieldTemplate() { LogProbe probe = createLogProbe("{obj}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java index 990efce48e6..01f4cfed5d8 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java @@ -1,7 +1,9 @@ package com.datadog.debugger.symbol; +import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.when; import static utils.InstrumentationTestHelper.compileAndLoadClass; @@ -17,6 +19,7 @@ import org.joor.Reflect; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import org.mockito.Mockito; class SymbolExtractionTransformerTest { @@ -52,6 +55,7 @@ public void noIncludesFilterOutDatadogClass() throws IOException, URISyntaxExcep } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction01() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction01"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction01.java"; @@ -117,6 +121,7 @@ public void symbolExtraction01() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction02() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction02"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction02.java"; @@ -144,6 +149,7 @@ public void symbolExtraction02() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction03() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction03"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction03.java"; @@ -211,6 +217,7 @@ public void symbolExtraction03() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction04() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction04"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction04.java"; @@ -282,6 +289,7 @@ public void symbolExtraction04() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction05() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction05"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction05.java"; @@ -327,6 +335,7 @@ public void symbolExtraction05() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction06() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction06"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction06.java"; @@ -372,6 +381,7 @@ public void symbolExtraction06() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction07() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction07"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction07.java"; @@ -403,6 +413,7 @@ public void symbolExtraction07() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction08() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction08"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction08.java"; @@ -436,6 +447,7 @@ public void symbolExtraction08() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction09() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction09"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction09.java"; @@ -527,6 +539,7 @@ public void symbolExtraction09() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction10() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction10"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction10.java"; @@ -578,6 +591,7 @@ public void symbolExtraction10() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction11() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction11"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction11.java"; @@ -611,6 +625,7 @@ public void symbolExtraction11() throws IOException, URISyntaxException { } @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") public void symbolExtraction12() throws IOException, URISyntaxException { final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction12"; final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction12.java"; @@ -683,6 +698,180 @@ public void symbolExtraction12() throws IOException, URISyntaxException { 11); } + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction13() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction13"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = + new SymbolExtractionTransformer(symbolSinkMock, config); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertLangSpecifics( + classScope.getLanguageSpecifics(), + asList("public"), + asList( + "@com.datadog.debugger.symbol.MyAnnotation", "@com.datadog.debugger.symbol.MyMarker"), + Object.class.getTypeName(), + null, + null); + Scope mainMethodScope = classScope.getScopes().get(1); + assertLangSpecifics( + mainMethodScope.getLanguageSpecifics(), + asList("public", "static"), + asList("@com.datadog.debugger.symbol.MyAnnotation"), + null, + null, + Integer.TYPE.getTypeName()); + assertEquals(3, classScope.getSymbols().size()); + Symbol intField = classScope.getSymbols().get(0); + assertLangSpecifics( + intField.getLanguageSpecifics(), + asList("private"), + asList("@com.datadog.debugger.symbol.MyAnnotation"), + null, + null, + null); + Scope myAnnotationClassScope = symbolSinkMock.jarScopes.get(1).getScopes().get(0); + assertLangSpecifics( + myAnnotationClassScope.getLanguageSpecifics(), + asList("interface", "abstract", "annotation"), + asList("@java.lang.annotation.Target", "@java.lang.annotation.Retention"), + Object.class.getTypeName(), + asList("java.lang.annotation.Annotation"), + null); + Symbol strField = classScope.getSymbols().get(1); + assertLangSpecifics( + strField.getLanguageSpecifics(), + asList("public", "static", "volatile"), + null, + null, + null, + null); + Symbol doubleField = classScope.getSymbols().get(2); + assertLangSpecifics( + doubleField.getLanguageSpecifics(), + asList("protected", "final", "transient"), + null, + null, + null, + null); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction14() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction14"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = + new SymbolExtractionTransformer(symbolSinkMock, config); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertLangSpecifics( + classScope.getLanguageSpecifics(), + asList("public", "abstract"), + null, + Object.class.getTypeName(), + asList("com.datadog.debugger.symbol.I1", "com.datadog.debugger.symbol.I2"), + null); + assertEquals(4, classScope.getScopes().size()); + Scope m1MethodScope = classScope.getScopes().get(2); + assertLangSpecifics( + m1MethodScope.getLanguageSpecifics(), + asList("protected", "abstract"), + null, + null, + null, + Void.TYPE.getTypeName()); + Scope m2MethodScope = classScope.getScopes().get(3); + assertLangSpecifics( + m2MethodScope.getLanguageSpecifics(), + asList("private", "final", "synchronized", "(varargs)", "strictfp"), + null, + null, + null, + String.class.getTypeName()); + Scope i1ClassScope = symbolSinkMock.jarScopes.get(1).getScopes().get(0); + assertLangSpecifics( + i1ClassScope.getLanguageSpecifics(), + asList("interface", "abstract"), + null, + Object.class.getTypeName(), + null, + null); + Scope m3MethodScope = i1ClassScope.getScopes().get(0); + assertLangSpecifics( + m3MethodScope.getLanguageSpecifics(), + asList("public", "default"), + null, + null, + null, + Void.TYPE.getTypeName()); + Scope myEnumClassScope = symbolSinkMock.jarScopes.get(3).getScopes().get(0); + assertLangSpecifics( + myEnumClassScope.getLanguageSpecifics(), + asList("final", "enum"), + null, + Enum.class.getTypeName(), + null, + null); + assertEquals(4, myEnumClassScope.getSymbols().size()); + Symbol oneField = myEnumClassScope.getSymbols().get(0); + assertLangSpecifics( + oneField.getLanguageSpecifics(), + asList("public", "static", "final", "enum"), + null, + null, + null, + null); + Symbol valuesField = myEnumClassScope.getSymbols().get(3); + assertLangSpecifics( + valuesField.getLanguageSpecifics(), + asList("private", "static", "final", "synthetic"), + null, + null, + null, + null); + } + + private void assertLangSpecifics( + LanguageSpecifics languageSpecifics, + List expectedModifiers, + List expectedAnnotations, + String expectedSuperClass, + List expectedInterfaces, + String expectedReturnType) { + if (expectedModifiers == null) { + assertNull(languageSpecifics.getAccessModifiers()); + } else { + assertEquals(expectedModifiers, languageSpecifics.getAccessModifiers()); + } + if (expectedAnnotations == null) { + assertNull(languageSpecifics.getAnnotations()); + } else { + assertEquals(expectedAnnotations, languageSpecifics.getAnnotations()); + } + if (expectedSuperClass == null) { + assertNull(languageSpecifics.getSuperClass()); + } else { + assertEquals(expectedSuperClass, languageSpecifics.getSuperClass()); + } + if (expectedInterfaces == null) { + assertNull(languageSpecifics.getInterfaces()); + } else { + assertEquals(expectedInterfaces, languageSpecifics.getInterfaces()); + } + if (expectedReturnType == null) { + assertNull(languageSpecifics.getReturnType()); + } else { + assertEquals(expectedReturnType, languageSpecifics.getReturnType()); + } + } + private static void assertScope( Scope scope, ScopeType scopeType, diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotPrunerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotPrunerTest.java new file mode 100644 index 00000000000..038053dd524 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotPrunerTest.java @@ -0,0 +1,232 @@ +package com.datadog.debugger.util; + +import static com.datadog.debugger.sink.SnapshotSink.MAX_SNAPSHOT_SIZE; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class SnapshotPrunerTest { + @Test + public void noPruning() { + assertEquals("[]", SnapshotPruner.prune("[]", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("{}", SnapshotPruner.prune("{}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("[{},{},{}]", SnapshotPruner.prune("[{},{},{}]", MAX_SNAPSHOT_SIZE, 0)); + assertEquals( + "{\"foo\":[[],[],[]]}", SnapshotPruner.prune("{\"foo\":[[],[],[]]}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals( + "{\"foo\":\"bar\"}", SnapshotPruner.prune("{\"foo\":\"bar\"}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("{\"foo\":1001}", SnapshotPruner.prune("{\"foo\":1001}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("{\"foo\":3.14}", SnapshotPruner.prune("{\"foo\":3.14}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("{\"foo\":true}", SnapshotPruner.prune("{\"foo\":true}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals( + "{\"foo\":{\"name\":\"value\"}}", + SnapshotPruner.prune("{\"foo\":{\"name\":\"value\"}}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals( + "{\"foo1\":{\"foo2\":{\"foo3\":{\"foo4\":{\"foo5\":{}}}}}}", + SnapshotPruner.prune( + "{\"foo1\":{\"foo2\":{\"foo3\":{\"foo4\":{\"foo5\":{}}}}}}", MAX_SNAPSHOT_SIZE, 0)); + } + + @Test + public void basic() { + assertEquals("aaaa{\"pruned\":true}bbbb", SnapshotPruner.prune("aaaa{}bbbb", 8, 0)); + assertEquals( + "aa{\"pruned\":true}bb", + SnapshotPruner.prune("aa{\"notCapturedReason\": \"depth\"}bb", 8, 0)); + assertEquals( + "aa{\"pruned\":true}bb", + SnapshotPruner.prune("aa{\"notCapturedReason\": \"collectionSize\"}bb", 8, 0)); + } + + @Test + public void priorityPruning() { + final String INPUT = + "{\n" + + " \"elements\":[\n" + + " {\n" + + " \"type\": \"list\",\n" + + " \"notCapturedReason\": \"collectionSize\"\n" + + " },\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"notCapturedReason\": \"depth\"\n" + + " },\n" + + " {\n" + + " \"type\": \"deep\",\n" + + " \"subobject\": {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"subobject\"\n" + + " }\n" + + " }\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"sfsfsdfklsdfslkfjsdfkjsdklfjsdflksdjfsdlfjsdklfsjdfklsjfksfjslkdfjskdlf\"\n" + + " }\n" + + " ]\n" + + "}"; + assertEquals( + "{\n" + + " \"elements\":[\n" + + " {\n" + + " \"type\": \"list\",\n" + + " \"notCapturedReason\": \"collectionSize\"\n" + + " },\n" + + " {\"pruned\":true},\n" + + " {\n" + + " \"type\": \"deep\",\n" + + " \"subobject\": {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"subobject\"\n" + + " }\n" + + " }\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"sfsfsdfklsdfslkfjsdfkjsdklfjsdflksdjfsdlfjsdklfsjdfklsjfksfjslkdfjskdlf\"\n" + + " }\n" + + " ]\n" + + "}", + SnapshotPruner.prune(INPUT, 400, 0)); + assertEquals( + "{\n" + + " \"elements\":[\n" + + " {\n" + + " \"type\": \"list\",\n" + + " \"notCapturedReason\": \"collectionSize\"\n" + + " },\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"sfsfsdfklsdfslkfjsdfkjsdklfjsdflksdjfsdlfjsdklfsjdfklsjfksfjslkdfjskdlf\"\n" + + " }\n" + + " ]\n" + + "}", + SnapshotPruner.prune(INPUT, 300, 0)); + assertEquals( + "{\n" + + " \"elements\":[\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"sfsfsdfklsdfslkfjsdfkjsdklfjsdflksdjfsdlfjsdklfsjdfklsjfksfjslkdfjskdlf\"\n" + + " }\n" + + " ]\n" + + "}", + SnapshotPruner.prune(INPUT, 250, 0)); + assertEquals( + "{\n" + + " \"elements\":[\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " {\"pruned\":true}\n" + + " ]\n" + + "}", + SnapshotPruner.prune(INPUT, 200, 0)); + } + + @Test + public void sizeReduction() { + final String INPUT = + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}\n" + + " ]},\n" + + " \"prune\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"Custom\", \"notCapturedReason\": \"depth\"},\n" + + " {\"type\": \"Custom\", \"notCapturedReason\": \"depth\"}\n" + + " ]}\n" + + " }"; + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}\n" + + " ]},\n" + + " \"prune\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"Custom\", \"notCapturedReason\": \"depth\"},\n" + + " {\"type\": \"Custom\", \"notCapturedReason\": \"depth\"}\n" + + " ]}\n" + + " }", + SnapshotPruner.prune(INPUT, 800, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}\n" + + " ]},\n" + + " \"prune\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " ]}\n" + + " }", + SnapshotPruner.prune(INPUT, 440, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}\n" + + " ]},\n" + + " \"prune\": {\"pruned\":true}\n" + + " }", + SnapshotPruner.prune(INPUT, 350, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"pruned\":true}\n" + + " ]},\n" + + " \"prune\": {\"pruned\":true}\n" + + " }", + SnapshotPruner.prune(INPUT, 270, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " ]},\n" + + " \"prune\": {\"pruned\":true}\n" + + " }", + SnapshotPruner.prune(INPUT, 240, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"pruned\":true},\n" + + " \"prune\": {\"pruned\":true}\n" + + " }", + SnapshotPruner.prune(INPUT, 120, 0)); + assertEquals("{\"pruned\":true}", SnapshotPruner.prune(INPUT, 20, 0)); + } + + @Test + public void sliceSmallSnapshot() throws Exception { + final int MIN_LEVEL = 6; + String inputSmallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot.json").trim(); + assertEquals( + inputSmallSnapshot, + SnapshotPruner.prune(inputSmallSnapshot, inputSmallSnapshot.length(), MIN_LEVEL)); + String smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned0.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 1500, MIN_LEVEL)); + smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned1.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 1250, MIN_LEVEL)); + smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned2.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 1100, MIN_LEVEL)); + smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned3.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 1000, MIN_LEVEL)); + smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned4.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 500, MIN_LEVEL)); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotSlicerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotSlicerTest.java deleted file mode 100644 index f6148407097..00000000000 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotSlicerTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.datadog.debugger.util; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import utils.TestHelper; - -class SnapshotSlicerTest { - - @Test - public void noSlice() throws Exception { - Assertions.assertEquals("[]", SnapshotSlicer.slice(16, "[]")); - Assertions.assertEquals("{}", SnapshotSlicer.slice(16, "{}")); - Assertions.assertEquals("[{},{},{}]", SnapshotSlicer.slice(16, "[{},{},{}]")); - Assertions.assertEquals( - "{\"foo\":[[],[],[]]}", SnapshotSlicer.slice(16, "{\"foo\":[[],[],[]]}")); - Assertions.assertEquals("{\"foo\":\"bar\"}", SnapshotSlicer.slice(16, "{\"foo\":\"bar\"}")); - Assertions.assertEquals("{\"foo\":1001}", SnapshotSlicer.slice(16, "{\"foo\":1001}")); - Assertions.assertEquals("{\"foo\":3.14}", SnapshotSlicer.slice(16, "{\"foo\":3.14}")); - Assertions.assertEquals("{\"foo\":true}", SnapshotSlicer.slice(16, "{\"foo\":true}")); - Assertions.assertEquals( - "{\"foo\":{\"name\":\"value\"}}", - SnapshotSlicer.slice(16, "{\"foo\":{\"name\":\"value\"}}")); - Assertions.assertEquals( - "{\"foo1\":{\"foo2\":{\"foo3\":{\"foo4\":{\"foo5\":{}}}}}}", - SnapshotSlicer.slice(16, "{\"foo1\":{\"foo2\":{\"foo3\":{\"foo4\":{\"foo5\":{}}}}}}")); - } - - @Test - public void sliceSmallSnapshot() throws Exception { - String inputSmallSnapshot = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot.json").trim(); - Assertions.assertEquals(inputSmallSnapshot, SnapshotSlicer.slice(5, inputSmallSnapshot)); - String smallSnapshot4 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_4.json").trim(); - Assertions.assertEquals(smallSnapshot4, SnapshotSlicer.slice(4, inputSmallSnapshot)); - String smallSnapshot3 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_3.json").trim(); - Assertions.assertEquals(smallSnapshot3, SnapshotSlicer.slice(3, inputSmallSnapshot)); - String smallSnapshot2 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_2.json").trim(); - Assertions.assertEquals(smallSnapshot2, SnapshotSlicer.slice(2, inputSmallSnapshot)); - String smallSnapshot1 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_1.json").trim(); - Assertions.assertEquals(smallSnapshot1, SnapshotSlicer.slice(1, inputSmallSnapshot)); - String smallSnapshot0 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_0.json").trim(); - Assertions.assertEquals(smallSnapshot0, SnapshotSlicer.slice(0, inputSmallSnapshot)); - } - - @Test - public void sliceLargeSnapshot() throws Exception { - String inputLargeSnapshot = - TestHelper.getFixtureContent("/com/datadog/debugger/util/largeSnapshot.json").trim(); - String largeSnapshot2 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/largeSnapshot_2.json").trim(); - Assertions.assertEquals(largeSnapshot2, SnapshotSlicer.slice(2, inputLargeSnapshot)); - String largeSnapshot1 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/largeSnapshot_1.json").trim(); - Assertions.assertEquals(largeSnapshot1, SnapshotSlicer.slice(1, inputLargeSnapshot)); - String largeSnapshot0 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/largeSnapshot_0.json").trim(); - Assertions.assertEquals(largeSnapshot0, SnapshotSlicer.slice(0, inputLargeSnapshot)); - } -} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ValueScriptHelperTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ValueScriptHelperTest.java index dd6ebad74c7..739e66b5cff 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ValueScriptHelperTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ValueScriptHelperTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import datadog.trace.bootstrap.debugger.CapturedContext; +import datadog.trace.bootstrap.debugger.Limits; import datadog.trace.bootstrap.debugger.ProbeId; import datadog.trace.bootstrap.debugger.ProbeImplementation; import datadog.trace.bootstrap.debugger.ProbeLocation; @@ -17,12 +18,13 @@ class ValueScriptHelperTest { new ProbeLocation("java.lang.String", "indexOf", "String.java", Arrays.asList("12-15", "23")); private static final ProbeImplementation DUMMY_PROBE = new ProbeImplementation.NoopProbeImplementation(PROBE_ID, PROBE_LOCATION); + private static final Limits LIMITS = new Limits(1, 3, 255, 5); @Test public void nullValue() { CapturedContext.Status status = new CapturedContext.Status(DUMMY_PROBE); StringBuilder sb = new StringBuilder(); - ValueScriptHelper.serializeValue(sb, "", null, status); + ValueScriptHelper.serializeValue(sb, "", null, status, LIMITS); assertEquals("null", sb.toString()); } @@ -30,7 +32,7 @@ public void nullValue() { public void basicString() { CapturedContext.Status status = new CapturedContext.Status(DUMMY_PROBE); StringBuilder sb = new StringBuilder(); - ValueScriptHelper.serializeValue(sb, "", "foo", status); + ValueScriptHelper.serializeValue(sb, "", "foo", status, LIMITS); assertEquals("foo", sb.toString()); } } diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot19.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot19.java index 8b04dcffe2e..5909e5644f2 100644 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot19.java +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot19.java @@ -17,6 +17,8 @@ static class Base { private static int intValue = 24; protected static double doubleValue = 3.14; private static Object obj1; + private static long[] longValues = new long[] {1, 2, 3, 4}; + private static String[] strValues = new String[] {"foo", "bar"}; public Base(Object obj1) { this.obj1 = obj1; diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction13.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction13.java new file mode 100644 index 00000000000..6cd268066ef --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction13.java @@ -0,0 +1,39 @@ +package com.datadog.debugger.symbol; + +import org.junit.jupiter.api.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@MyAnnotation("class") +@MyMarker +public class SymbolExtraction13 { + + @MyAnnotation("field") + private int intField; + public static volatile String strField; + protected final transient double doubleField = 3.14; + + @MyAnnotation("method") + public static int main(String arg) { + System.out.println(MyAnnotation.class); + return 42; + } + + private static class InnerClass { + + } +} + +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@interface MyAnnotation { + String value() default ""; +} + +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@interface MyMarker { +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction14.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction14.java new file mode 100644 index 00000000000..417867c3659 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction14.java @@ -0,0 +1,36 @@ +package com.datadog.debugger.symbol; + +import org.junit.jupiter.api.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public abstract class SymbolExtraction14 extends Object implements I1, I2{ + + public static int main(String arg) { + System.out.println(MyEnum.ONE); + return 42; + } + + protected abstract void m1(); + private strictfp synchronized final String m2(String... strVarArgs) { + return null; + } + +} + +interface I1 { + default void m3(){} +} + +interface I2 { + +} + +enum MyEnum { + ONE, + TWO, + THREE +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot.json deleted file mode 100644 index f15769e4a1c..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke","lineNumber":62},{"fileName":"DelegatingMethodAccessorImpl.java","function":"sun.reflect.DelegatingMethodAccessorImpl.invoke","lineNumber":43},{"fileName":"Method.java","function":"java.lang.reflect.Method.invoke","lineNumber":498},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke","lineNumber":197},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest","lineNumber":141},{"fileName":"ServletInvocableHandlerMethod.java","function":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle","lineNumber":106},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod","lineNumber":894},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal","lineNumber":808},{"fileName":"AbstractHandlerMethodAdapter.java","function":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle","lineNumber":87},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doDispatch","lineNumber":1060},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doService","lineNumber":962},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.processRequest","lineNumber":1006},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.doGet","lineNumber":898},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":626},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.service","lineNumber":883},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":733},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":227},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WsFilter.java","function":"org.apache.tomcat.websocket.server.WsFilter.doFilter","lineNumber":53},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ResourceUrlEncodingFilter.java","function":"org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter","lineNumber":67},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"RequestContextFilter.java","function":"org.springframework.web.filter.RequestContextFilter.doFilterInternal","lineNumber":100},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"FormContentFilter.java","function":"org.springframework.web.filter.FormContentFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"HandlerMappingResourceNameFilter.java","function":"datadog.trace.instrumentation.springweb.HandlerMappingResourceNameFilter.doFilterInternal","lineNumber":50},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WebMvcMetricsFilter.java","function":"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"CharacterEncodingFilter.java","function":"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal","lineNumber":201},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ServletRequestPathFilter.java","function":"org.springframework.web.filter.ServletRequestPathFilter.doFilter","lineNumber":55},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate","lineNumber":358},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.doFilter","lineNumber":271},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"StandardWrapperValve.java","function":"org.apache.catalina.core.StandardWrapperValve.invoke","lineNumber":202},{"fileName":"StandardContextValve.java","function":"org.apache.catalina.core.StandardContextValve.invoke","lineNumber":97},{"fileName":"AuthenticatorBase.java","function":"org.apache.catalina.authenticator.AuthenticatorBase.invoke","lineNumber":542},{"fileName":"StandardHostValve.java","function":"org.apache.catalina.core.StandardHostValve.invoke","lineNumber":143},{"fileName":"ErrorReportValve.java","function":"org.apache.catalina.valves.ErrorReportValve.invoke","lineNumber":92},{"fileName":"StandardEngineValve.java","function":"org.apache.catalina.core.StandardEngineValve.invoke","lineNumber":78},{"fileName":"CoyoteAdapter.java","function":"org.apache.catalina.connector.CoyoteAdapter.service","lineNumber":357},{"fileName":"Http11Processor.java","function":"org.apache.coyote.http11.Http11Processor.service","lineNumber":374},{"fileName":"AbstractProcessorLight.java","function":"org.apache.coyote.AbstractProcessorLight.process","lineNumber":65},{"fileName":"AbstractProtocol.java","function":"org.apache.coyote.AbstractProtocol$ConnectionHandler.process","lineNumber":893},{"fileName":"NioEndpoint.java","function":"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun","lineNumber":1707},{"fileName":"SocketProcessorBase.java","function":"org.apache.tomcat.util.net.SocketProcessorBase.run","lineNumber":49},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor.runWorker","lineNumber":1149},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor$Worker.run","lineNumber":624},{"fileName":"TaskThread.java","function":"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run","lineNumber":61},{"fileName":"Thread.java","function":"java.lang.Thread.run","lineNumber":750}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"type":"java.util.concurrent.ThreadPoolExecutor","fields":{"termination":{"notCapturedReason":"depth","type":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"},"acc":{"isNull":true,"type":"java.security.AccessControlContext"},"handler":{"notCapturedReason":"depth","type":"java.util.concurrent.ThreadPoolExecutor$AbortPolicy"},"threadFactory":{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$$Lambda$1112/1706225206"},"keepAliveTime":{"type":"long","value":"0"},"largestPoolSize":{"type":"int","value":"1"},"__datadogContext$3":{"type":"java.lang.Boolean","value":"true"},"allowCoreThreadTimeOut":{"type":"boolean","value":"false"},"corePoolSize":{"type":"int","value":"1"},"ctl":{"type":"java.util.concurrent.atomic.AtomicInteger","value":"-536870911"},"completedTaskCount":{"type":"long","value":"0"},"workQueue":{"notCapturedReason":"depth","type":"java.util.concurrent.LinkedBlockingQueue"},"maximumPoolSize":{"type":"int","value":"1"},"workers":{"notCapturedReason":"depth","type":"java.util.HashSet"},"mainLock":{"notCapturedReason":"depth","type":"java.util.concurrent.locks.ReentrantLock"}}}}},"logger":{"type":"ch.qos.logback.classic.Logger","fields":{"parent":{"type":"ch.qos.logback.classic.Logger","fields":{"parent":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.Logger"},"level":{"isNull":true,"type":"ch.qos.logback.classic.Level"},"name":{"type":"java.lang.String","value":"org.springframework.samples.petclinic.vet"},"aai":{"isNull":true,"type":"ch.qos.logback.core.spi.AppenderAttachableImpl"},"childrenList":{"notCapturedReason":"depth","type":"java.util.concurrent.CopyOnWriteArrayList"},"loggerContext":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.LoggerContext"},"effectiveLevelInt":{"type":"int","value":"20000"},"additive":{"type":"boolean","value":"true"}}},"level":{"isNull":true,"type":"ch.qos.logback.classic.Level"},"name":{"type":"java.lang.String","value":"org.springframework.samples.petclinic.vet.VetController"},"aai":{"isNull":true,"type":"ch.qos.logback.core.spi.AppenderAttachableImpl"},"childrenList":{"isNull":true,"type":"java.util.List"},"loggerContext":{"notCapturedReason":"fieldCount","type":"ch.qos.logback.classic.LoggerContext","fields":{"loggerCache":{"notCapturedReason":"depth","type":"java.util.concurrent.ConcurrentHashMap"},"frameworkPackages":{"notCapturedReason":"depth","type":"java.util.ArrayList"},"resetCount":{"type":"int","value":"2"},"noAppenderWarning":{"type":"int","value":"0"},"configurationLock":{"notCapturedReason":"depth","type":"ch.qos.logback.core.spi.LogbackLock"},"birthTime":{"type":"long","value":"1686734324162"},"objectMap":{"notCapturedReason":"depth","type":"java.util.HashMap"},"propertyMap":{"notCapturedReason":"depth","type":"java.util.HashMap"},"lifeCycleManager":{"notCapturedReason":"depth","type":"ch.qos.logback.core.LifeCycleManager"},"loggerContextListenerList":{"notCapturedReason":"depth","type":"java.util.ArrayList"},"size":{"type":"int","value":"995"},"maxCallerDataDepth":{"type":"int","value":"8"},"packagingDataEnabled":{"type":"boolean","value":"true"},"root":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.Logger"},"name":{"type":"java.lang.String","value":"default"},"sm":{"notCapturedReason":"depth","type":"ch.qos.logback.core.BasicStatusManager"},"scheduledExecutorService":{"isNull":true,"type":"java.util.concurrent.ScheduledExecutorService"},"loggerContextRemoteView":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.spi.LoggerContextVO"},"turboFilterList":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.spi.TurboFilterList"},"scheduledFutures":{"notCapturedReason":"depth","type":"java.util.ArrayList"}}},"effectiveLevelInt":{"type":"int","value":"20000"},"additive":{"type":"boolean","value":"true"}}},"vets":{"notCapturedReason":"fieldCount","type":"com.sun.proxy.$Proxy165","fields":{"m0":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"8"},"modifiers":{"type":"int","value":"257"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"hashCode"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"class java.lang.Object"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"int"}}},"m1":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"6"},"modifiers":{"type":"int","value":"1"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"equals"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"class java.lang.Object"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m2":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"7"},"modifiers":{"type":"int","value":"1"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"toString"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"class java.lang.Object"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"class java.lang.String"}}},"m3":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"notCapturedReason":"depth","type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"notCapturedReason":"depth","type":"java.util.LinkedHashMap"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"type":"java.lang.String","value":"()Ljava/util/Collection;"},"annotations":{"notCapturedReason":"depth","type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"0"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"findAll"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.samples.petclinic.vet.VetRepository"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"interface java.util.Collection"}}},"m4":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"0"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"indexOf"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"int"}}},"m7":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"3"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"getTargetSource"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"interface org.springframework.aop.TargetSource"}}},"m8":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"type":"java.lang.String","value":"()[Ljava/lang/Class<*>;"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"5"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"getProxiedInterfaces"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"class [Ljava.lang.Class;"}}},"m9":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"type":"java.lang.String","value":"(Ljava/lang/Class<*>;)Z"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"6"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"isInterfaceProxied"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m21":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"18"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"removeAdvisor"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m10":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"7"},"modifiers":{"type":"int","value":"1"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"getAdvisorCount"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"int"}}},"m24":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"21"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"addAdvice"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m13":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"10"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"setTargetSource"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m12":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"9"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"isProxyTargetClass"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m23":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"20"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"addAdvice"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m15":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"12"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"isExposeProxy"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m25":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"22"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"removeAdvice"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m14":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"11"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"setExposeProxy"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m27":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"type":"java.lang.String","value":"()Ljava/lang/Class<*>;"},"annotations":{"notCapturedReason":"depth","type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"0"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"notCapturedReason":"depth","type":"sun.reflect.DelegatingMethodAccessorImpl"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"getTargetClass"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.TargetClassAware"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"class java.lang.Class"}}},"m19":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"16"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"addAdvisor"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m18":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"15"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"notCapturedReason":"depth","type":"sun.reflect.DelegatingMethodAccessorImpl"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"addAdvisor"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}}}},"garbage":{"size":"10","elements":[{"size":"7515","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"4412","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"3665","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"635","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6794","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"7280","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6563","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"85","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"type":"java.lang.Object[]"},{"size":"4819","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"2521","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"}],"type":"java.util.ArrayList"},"counter":{"type":"int","value":"0"},"timeout":{"type":"java.time.Duration","value":"PT30S"},"syntheticLiveSet":{"type":"java.util.concurrent.atomic.AtomicReference","fields":{"value":{"isNull":true,"type":"java.lang.Object"}}}}},"model":{"entries":[[{"type":"java.lang.String","value":"vets"},{"type":"org.springframework.samples.petclinic.vet.Vets","fields":{"vets":{"size":"12000","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"}],"notCapturedReason":"collectionSize","type":"java.util.ArrayList"}}}]],"size":"1","type":"org.springframework.validation.support.BindingAwareModelMap"},"uuid":{"isNull":true,"type":"java.lang.String"}},"locals":{"@return":{"type":"java.lang.String","value":"vets/vetList"}}}},"language":"java","id":"75332018-9ffa-41f2-9aa1-5a3804fabe9c","probe":{"location":{"method":"showVetList","type":"org.springframework.samples.petclinic.vet.VetController"},"id":"2147bca7-2880-4ce5-a856-6b5f161b5f79","version":4},"timestamp":1686734349534}},"logger":{"thread_id":376,"method":"showVetList","thread_name":"http-nio-8080-exec-1","name":"org.springframework.samples.petclinic.vet.VetController","version":2},"timestamp":1686734349534}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_0.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_0.json deleted file mode 100644 index 46b82a86e8f..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_0.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke","lineNumber":62},{"fileName":"DelegatingMethodAccessorImpl.java","function":"sun.reflect.DelegatingMethodAccessorImpl.invoke","lineNumber":43},{"fileName":"Method.java","function":"java.lang.reflect.Method.invoke","lineNumber":498},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke","lineNumber":197},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest","lineNumber":141},{"fileName":"ServletInvocableHandlerMethod.java","function":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle","lineNumber":106},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod","lineNumber":894},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal","lineNumber":808},{"fileName":"AbstractHandlerMethodAdapter.java","function":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle","lineNumber":87},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doDispatch","lineNumber":1060},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doService","lineNumber":962},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.processRequest","lineNumber":1006},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.doGet","lineNumber":898},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":626},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.service","lineNumber":883},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":733},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":227},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WsFilter.java","function":"org.apache.tomcat.websocket.server.WsFilter.doFilter","lineNumber":53},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ResourceUrlEncodingFilter.java","function":"org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter","lineNumber":67},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"RequestContextFilter.java","function":"org.springframework.web.filter.RequestContextFilter.doFilterInternal","lineNumber":100},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"FormContentFilter.java","function":"org.springframework.web.filter.FormContentFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"HandlerMappingResourceNameFilter.java","function":"datadog.trace.instrumentation.springweb.HandlerMappingResourceNameFilter.doFilterInternal","lineNumber":50},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WebMvcMetricsFilter.java","function":"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"CharacterEncodingFilter.java","function":"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal","lineNumber":201},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ServletRequestPathFilter.java","function":"org.springframework.web.filter.ServletRequestPathFilter.doFilter","lineNumber":55},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate","lineNumber":358},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.doFilter","lineNumber":271},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"StandardWrapperValve.java","function":"org.apache.catalina.core.StandardWrapperValve.invoke","lineNumber":202},{"fileName":"StandardContextValve.java","function":"org.apache.catalina.core.StandardContextValve.invoke","lineNumber":97},{"fileName":"AuthenticatorBase.java","function":"org.apache.catalina.authenticator.AuthenticatorBase.invoke","lineNumber":542},{"fileName":"StandardHostValve.java","function":"org.apache.catalina.core.StandardHostValve.invoke","lineNumber":143},{"fileName":"ErrorReportValve.java","function":"org.apache.catalina.valves.ErrorReportValve.invoke","lineNumber":92},{"fileName":"StandardEngineValve.java","function":"org.apache.catalina.core.StandardEngineValve.invoke","lineNumber":78},{"fileName":"CoyoteAdapter.java","function":"org.apache.catalina.connector.CoyoteAdapter.service","lineNumber":357},{"fileName":"Http11Processor.java","function":"org.apache.coyote.http11.Http11Processor.service","lineNumber":374},{"fileName":"AbstractProcessorLight.java","function":"org.apache.coyote.AbstractProcessorLight.process","lineNumber":65},{"fileName":"AbstractProtocol.java","function":"org.apache.coyote.AbstractProtocol$ConnectionHandler.process","lineNumber":893},{"fileName":"NioEndpoint.java","function":"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun","lineNumber":1707},{"fileName":"SocketProcessorBase.java","function":"org.apache.tomcat.util.net.SocketProcessorBase.run","lineNumber":49},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor.runWorker","lineNumber":1149},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor$Worker.run","lineNumber":624},{"fileName":"TaskThread.java","function":"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run","lineNumber":61},{"fileName":"Thread.java","function":"java.lang.Thread.run","lineNumber":750}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","notCapturedReason":"depth"},"model":{"entries":[[{"type":"java.lang.String","value":"vets"},{"type":"org.springframework.samples.petclinic.vet.Vets","notCapturedReason":"depth"}]],"size":"1","type":"org.springframework.validation.support.BindingAwareModelMap"},"uuid":{"isNull":true,"type":"java.lang.String"}},"locals":{"@return":{"type":"java.lang.String","value":"vets/vetList"}}}},"language":"java","id":"75332018-9ffa-41f2-9aa1-5a3804fabe9c","probe":{"location":{"method":"showVetList","type":"org.springframework.samples.petclinic.vet.VetController"},"id":"2147bca7-2880-4ce5-a856-6b5f161b5f79","version":4},"timestamp":1686734349534}},"logger":{"thread_id":376,"method":"showVetList","thread_name":"http-nio-8080-exec-1","name":"org.springframework.samples.petclinic.vet.VetController","version":2},"timestamp":1686734349534}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_1.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_1.json deleted file mode 100644 index fb652cd0dc6..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_1.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke","lineNumber":62},{"fileName":"DelegatingMethodAccessorImpl.java","function":"sun.reflect.DelegatingMethodAccessorImpl.invoke","lineNumber":43},{"fileName":"Method.java","function":"java.lang.reflect.Method.invoke","lineNumber":498},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke","lineNumber":197},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest","lineNumber":141},{"fileName":"ServletInvocableHandlerMethod.java","function":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle","lineNumber":106},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod","lineNumber":894},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal","lineNumber":808},{"fileName":"AbstractHandlerMethodAdapter.java","function":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle","lineNumber":87},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doDispatch","lineNumber":1060},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doService","lineNumber":962},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.processRequest","lineNumber":1006},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.doGet","lineNumber":898},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":626},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.service","lineNumber":883},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":733},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":227},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WsFilter.java","function":"org.apache.tomcat.websocket.server.WsFilter.doFilter","lineNumber":53},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ResourceUrlEncodingFilter.java","function":"org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter","lineNumber":67},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"RequestContextFilter.java","function":"org.springframework.web.filter.RequestContextFilter.doFilterInternal","lineNumber":100},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"FormContentFilter.java","function":"org.springframework.web.filter.FormContentFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"HandlerMappingResourceNameFilter.java","function":"datadog.trace.instrumentation.springweb.HandlerMappingResourceNameFilter.doFilterInternal","lineNumber":50},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WebMvcMetricsFilter.java","function":"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"CharacterEncodingFilter.java","function":"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal","lineNumber":201},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ServletRequestPathFilter.java","function":"org.springframework.web.filter.ServletRequestPathFilter.doFilter","lineNumber":55},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate","lineNumber":358},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.doFilter","lineNumber":271},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"StandardWrapperValve.java","function":"org.apache.catalina.core.StandardWrapperValve.invoke","lineNumber":202},{"fileName":"StandardContextValve.java","function":"org.apache.catalina.core.StandardContextValve.invoke","lineNumber":97},{"fileName":"AuthenticatorBase.java","function":"org.apache.catalina.authenticator.AuthenticatorBase.invoke","lineNumber":542},{"fileName":"StandardHostValve.java","function":"org.apache.catalina.core.StandardHostValve.invoke","lineNumber":143},{"fileName":"ErrorReportValve.java","function":"org.apache.catalina.valves.ErrorReportValve.invoke","lineNumber":92},{"fileName":"StandardEngineValve.java","function":"org.apache.catalina.core.StandardEngineValve.invoke","lineNumber":78},{"fileName":"CoyoteAdapter.java","function":"org.apache.catalina.connector.CoyoteAdapter.service","lineNumber":357},{"fileName":"Http11Processor.java","function":"org.apache.coyote.http11.Http11Processor.service","lineNumber":374},{"fileName":"AbstractProcessorLight.java","function":"org.apache.coyote.AbstractProcessorLight.process","lineNumber":65},{"fileName":"AbstractProtocol.java","function":"org.apache.coyote.AbstractProtocol$ConnectionHandler.process","lineNumber":893},{"fileName":"NioEndpoint.java","function":"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun","lineNumber":1707},{"fileName":"SocketProcessorBase.java","function":"org.apache.tomcat.util.net.SocketProcessorBase.run","lineNumber":49},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor.runWorker","lineNumber":1149},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor$Worker.run","lineNumber":624},{"fileName":"TaskThread.java","function":"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run","lineNumber":61},{"fileName":"Thread.java","function":"java.lang.Thread.run","lineNumber":750}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","notCapturedReason":"depth"},"logger":{"type":"ch.qos.logback.classic.Logger","notCapturedReason":"depth"},"vets":{"notCapturedReason":"fieldCount","type":"com.sun.proxy.$Proxy165","notCapturedReason":"depth"},"garbage":{"size":"10","elements":[{"size":"7515","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"4412","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"3665","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"635","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6794","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"7280","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6563","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"85","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"type":"java.lang.Object[]"},{"size":"4819","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"2521","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"}],"type":"java.util.ArrayList"},"counter":{"type":"int","value":"0"},"timeout":{"type":"java.time.Duration","value":"PT30S"},"syntheticLiveSet":{"type":"java.util.concurrent.atomic.AtomicReference","notCapturedReason":"depth"}}},"model":{"entries":[[{"type":"java.lang.String","value":"vets"},{"type":"org.springframework.samples.petclinic.vet.Vets","notCapturedReason":"depth"}]],"size":"1","type":"org.springframework.validation.support.BindingAwareModelMap"},"uuid":{"isNull":true,"type":"java.lang.String"}},"locals":{"@return":{"type":"java.lang.String","value":"vets/vetList"}}}},"language":"java","id":"75332018-9ffa-41f2-9aa1-5a3804fabe9c","probe":{"location":{"method":"showVetList","type":"org.springframework.samples.petclinic.vet.VetController"},"id":"2147bca7-2880-4ce5-a856-6b5f161b5f79","version":4},"timestamp":1686734349534}},"logger":{"thread_id":376,"method":"showVetList","thread_name":"http-nio-8080-exec-1","name":"org.springframework.samples.petclinic.vet.VetController","version":2},"timestamp":1686734349534}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_2.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_2.json deleted file mode 100644 index f18a6613a91..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_2.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke","lineNumber":62},{"fileName":"DelegatingMethodAccessorImpl.java","function":"sun.reflect.DelegatingMethodAccessorImpl.invoke","lineNumber":43},{"fileName":"Method.java","function":"java.lang.reflect.Method.invoke","lineNumber":498},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke","lineNumber":197},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest","lineNumber":141},{"fileName":"ServletInvocableHandlerMethod.java","function":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle","lineNumber":106},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod","lineNumber":894},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal","lineNumber":808},{"fileName":"AbstractHandlerMethodAdapter.java","function":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle","lineNumber":87},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doDispatch","lineNumber":1060},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doService","lineNumber":962},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.processRequest","lineNumber":1006},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.doGet","lineNumber":898},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":626},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.service","lineNumber":883},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":733},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":227},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WsFilter.java","function":"org.apache.tomcat.websocket.server.WsFilter.doFilter","lineNumber":53},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ResourceUrlEncodingFilter.java","function":"org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter","lineNumber":67},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"RequestContextFilter.java","function":"org.springframework.web.filter.RequestContextFilter.doFilterInternal","lineNumber":100},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"FormContentFilter.java","function":"org.springframework.web.filter.FormContentFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"HandlerMappingResourceNameFilter.java","function":"datadog.trace.instrumentation.springweb.HandlerMappingResourceNameFilter.doFilterInternal","lineNumber":50},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WebMvcMetricsFilter.java","function":"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"CharacterEncodingFilter.java","function":"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal","lineNumber":201},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ServletRequestPathFilter.java","function":"org.springframework.web.filter.ServletRequestPathFilter.doFilter","lineNumber":55},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate","lineNumber":358},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.doFilter","lineNumber":271},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"StandardWrapperValve.java","function":"org.apache.catalina.core.StandardWrapperValve.invoke","lineNumber":202},{"fileName":"StandardContextValve.java","function":"org.apache.catalina.core.StandardContextValve.invoke","lineNumber":97},{"fileName":"AuthenticatorBase.java","function":"org.apache.catalina.authenticator.AuthenticatorBase.invoke","lineNumber":542},{"fileName":"StandardHostValve.java","function":"org.apache.catalina.core.StandardHostValve.invoke","lineNumber":143},{"fileName":"ErrorReportValve.java","function":"org.apache.catalina.valves.ErrorReportValve.invoke","lineNumber":92},{"fileName":"StandardEngineValve.java","function":"org.apache.catalina.core.StandardEngineValve.invoke","lineNumber":78},{"fileName":"CoyoteAdapter.java","function":"org.apache.catalina.connector.CoyoteAdapter.service","lineNumber":357},{"fileName":"Http11Processor.java","function":"org.apache.coyote.http11.Http11Processor.service","lineNumber":374},{"fileName":"AbstractProcessorLight.java","function":"org.apache.coyote.AbstractProcessorLight.process","lineNumber":65},{"fileName":"AbstractProtocol.java","function":"org.apache.coyote.AbstractProtocol$ConnectionHandler.process","lineNumber":893},{"fileName":"NioEndpoint.java","function":"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun","lineNumber":1707},{"fileName":"SocketProcessorBase.java","function":"org.apache.tomcat.util.net.SocketProcessorBase.run","lineNumber":49},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor.runWorker","lineNumber":1149},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor$Worker.run","lineNumber":624},{"fileName":"TaskThread.java","function":"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run","lineNumber":61},{"fileName":"Thread.java","function":"java.lang.Thread.run","lineNumber":750}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"type":"java.util.concurrent.ThreadPoolExecutor","notCapturedReason":"depth"}}},"logger":{"type":"ch.qos.logback.classic.Logger","fields":{"parent":{"type":"ch.qos.logback.classic.Logger","notCapturedReason":"depth"},"level":{"isNull":true,"type":"ch.qos.logback.classic.Level"},"name":{"type":"java.lang.String","value":"org.springframework.samples.petclinic.vet.VetController"},"aai":{"isNull":true,"type":"ch.qos.logback.core.spi.AppenderAttachableImpl"},"childrenList":{"isNull":true,"type":"java.util.List"},"loggerContext":{"notCapturedReason":"fieldCount","type":"ch.qos.logback.classic.LoggerContext","notCapturedReason":"depth"},"effectiveLevelInt":{"type":"int","value":"20000"},"additive":{"type":"boolean","value":"true"}}},"vets":{"notCapturedReason":"fieldCount","type":"com.sun.proxy.$Proxy165","fields":{"m0":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m1":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m2":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m3":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m4":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m7":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m8":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m9":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m21":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m10":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m24":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m13":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m12":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m23":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m15":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m25":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m14":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m27":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m19":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m18":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"}}},"garbage":{"size":"10","elements":[{"size":"7515","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"4412","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"3665","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"635","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6794","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"7280","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6563","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"85","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"type":"java.lang.Object[]"},{"size":"4819","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"2521","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"}],"type":"java.util.ArrayList"},"counter":{"type":"int","value":"0"},"timeout":{"type":"java.time.Duration","value":"PT30S"},"syntheticLiveSet":{"type":"java.util.concurrent.atomic.AtomicReference","notCapturedReason":"depth"}}},"model":{"entries":[[{"type":"java.lang.String","value":"vets"},{"type":"org.springframework.samples.petclinic.vet.Vets","notCapturedReason":"depth"}]],"size":"1","type":"org.springframework.validation.support.BindingAwareModelMap"},"uuid":{"isNull":true,"type":"java.lang.String"}},"locals":{"@return":{"type":"java.lang.String","value":"vets/vetList"}}}},"language":"java","id":"75332018-9ffa-41f2-9aa1-5a3804fabe9c","probe":{"location":{"method":"showVetList","type":"org.springframework.samples.petclinic.vet.VetController"},"id":"2147bca7-2880-4ce5-a856-6b5f161b5f79","version":4},"timestamp":1686734349534}},"logger":{"thread_id":376,"method":"showVetList","thread_name":"http-nio-8080-exec-1","name":"org.springframework.samples.petclinic.vet.VetController","version":2},"timestamp":1686734349534}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot.json index 560f0b48f7a..4c7303c2131 100644 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot.json +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot.json @@ -1 +1 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"fields":{"level":2,"field21":{"fields":{"level":3,"field31":{"fields":{"level":4,"field41":{"fields":{"level":5,"field51":"foo51","field52":"foo52"}},"field42":"foo42"}},"field32":"foo32"}},"field22":"foo22"}},"field12":"foo12"}},"field02":"foo02"} +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"type":"java.util.concurrent.ThreadPoolExecutor","fields":{"termination":{"notCapturedReason":"depth","type":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"},"acc":{"isNull":true,"type":"java.security.AccessControlContext"},"handler":{"notCapturedReason":"depth","type":"java.util.concurrent.ThreadPoolExecutor$AbortPolicy"},"threadFactory":{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$$Lambda$1112/1706225206"}}}}}}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_0.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_0.json deleted file mode 100644 index b14e715d2ed..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_0.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"notCapturedReason":"depth"},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_1.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_1.json deleted file mode 100644 index 20c31bf7885..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_1.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"notCapturedReason":"depth"},"field12":"foo12"}},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_2.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_2.json deleted file mode 100644 index 023aca34040..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_2.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"fields":{"level":2,"field21":{"notCapturedReason":"depth"},"field22":"foo22"}},"field12":"foo12"}},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_3.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_3.json deleted file mode 100644 index 716489fdef1..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_3.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"fields":{"level":2,"field21":{"fields":{"level":3,"field31":{"notCapturedReason":"depth"},"field32":"foo32"}},"field22":"foo22"}},"field12":"foo12"}},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_4.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_4.json deleted file mode 100644 index f6f29a64bf5..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_4.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"fields":{"level":2,"field21":{"fields":{"level":3,"field31":{"fields":{"level":4,"field41":{"notCapturedReason":"depth"},"field42":"foo42"}},"field32":"foo32"}},"field22":"foo22"}},"field12":"foo12"}},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned0.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned0.json new file mode 100644 index 00000000000..f47e198fc02 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned0.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"type":"java.util.concurrent.ThreadPoolExecutor","fields":{"termination":{"pruned":true},"acc":{"isNull":true,"type":"java.security.AccessControlContext"},"handler":{"notCapturedReason":"depth","type":"java.util.concurrent.ThreadPoolExecutor$AbortPolicy"},"threadFactory":{"pruned":true}}}}}}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned1.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned1.json new file mode 100644 index 00000000000..d4def329595 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned1.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"pruned":true}}}}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned2.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned2.json new file mode 100644 index 00000000000..5198d062c7d --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned2.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"pruned":true},"executor":{"pruned":true}}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned3.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned3.json new file mode 100644 index 00000000000..d8268f4591f --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned3.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"pruned":true}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned4.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned4.json new file mode 100644 index 00000000000..495103ba4a6 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned4.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"pruned":true}}}}}}} diff --git a/dd-java-agent/agent-iast/build.gradle b/dd-java-agent/agent-iast/build.gradle index 068bbf5b61d..a6474456aa6 100644 --- a/dd-java-agent/agent-iast/build.gradle +++ b/dd-java-agent/agent-iast/build.gradle @@ -21,7 +21,7 @@ java { tasks.withType(AbstractCompile).configureEach { // ensure no APIs beyond JDK8 are used - options.compilerArgs.addAll(['--release', '8']) + options.release = 8 } // First version with Mac M1 support diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java index 8c167fbed7b..21df4f14862 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java @@ -6,6 +6,8 @@ import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.SourceTypes; import datadog.trace.api.iast.propagation.PropagationModule; +import datadog.trace.api.iast.telemetry.IastMetric; +import datadog.trace.api.iast.telemetry.IastMetricCollector; import java.util.function.BiFunction; import javax.annotation.Nonnull; @@ -30,8 +32,12 @@ public Flow apply(final RequestContext ctx, final Object o) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null && o != null) { final IastContext iastCtx = IastContext.Provider.get(ctx); - module.taintDeeply( - iastCtx, o, SourceTypes.GRPC_BODY, GrpcRequestMessageHandler::isProtobufArtifact); + final byte source = SourceTypes.GRPC_BODY; + final int tainted = + module.taintDeeply(iastCtx, o, source, GrpcRequestMessageHandler::isProtobufArtifact); + if (tainted > 0) { + IastMetricCollector.add(IastMetric.EXECUTED_SOURCE, source, tainted, iastCtx); + } } return Flow.ResultFlow.empty(); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java index 589b2dfc80e..5da60ff4d30 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java @@ -14,6 +14,7 @@ import com.datadog.iast.sink.PathTraversalModuleImpl; import com.datadog.iast.sink.SqlInjectionModuleImpl; import com.datadog.iast.sink.SsrfModuleImpl; +import com.datadog.iast.sink.StacktraceLeakModuleImpl; import com.datadog.iast.sink.TrustBoundaryViolationModuleImpl; import com.datadog.iast.sink.UnvalidatedRedirectModuleImpl; import com.datadog.iast.sink.WeakCipherModuleImpl; @@ -100,7 +101,8 @@ private static Stream iastModules(final Dependencies dependencies) { new WeakRandomnessModuleImpl(dependencies), new XPathInjectionModuleImpl(dependencies), new TrustBoundaryViolationModuleImpl(dependencies), - new XssModuleImpl(dependencies)); + new XssModuleImpl(dependencies), + new StacktraceLeakModuleImpl(dependencies)); } private static void registerRequestStartedCallback( diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java index ae1d25ddfb3..62fdc6db56e 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java @@ -65,6 +65,9 @@ public interface VulnerabilityType { InjectionType XSS = new InjectionTypeImpl(VulnerabilityTypes.XSS_STRING, VulnerabilityMarks.XSS_MARK, ' '); + VulnerabilityType STACKTRACE_LEAK = + new VulnerabilityTypeImpl(VulnerabilityTypes.STACKTRACE_LEAK_STRING, NOT_MARKED); + String name(); /** A bit flag to ignore tainted ranges for this vulnerability. Set to 0 if none. */ diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java index ea0abe982b3..dd5ec368a06 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java @@ -74,7 +74,7 @@ public void taint( if (!canBeTainted(target)) { return; } - internalTaint(ctx, target, new Source(origin, name, sourceValue(target, value)), NOT_MARKED); + internalTaint(ctx, target, newSource(target, origin, name, value), NOT_MARKED); } @Override @@ -175,7 +175,7 @@ public void taintIfTainted( return; } if (isTainted(ctx, input)) { - internalTaint(ctx, target, new Source(origin, name, sourceValue(target, value)), NOT_MARKED); + internalTaint(ctx, target, newSource(target, origin, name, value), NOT_MARKED); } } @@ -228,31 +228,34 @@ public void taintIfAnyTainted( } @Override - public void taintDeeply( + public int taintDeeply( @Nullable final Object target, final byte origin, final Predicate> classFilter) { if (!canBeTainted(target)) { - return; + return 0; } - taintDeeply(LazyContext.build(), target, origin, classFilter); + return taintDeeply(LazyContext.build(), target, origin, classFilter); } @Override - public void taintDeeply( + public int taintDeeply( @Nullable final IastContext ctx, @Nullable final Object target, final byte origin, final Predicate> classFilter) { if (!canBeTainted(target)) { - return; + return 0; } final TaintedObjects to = getTaintedObjects(ctx); if (to == null) { - return; + return 0; } if (target instanceof CharSequence) { - internalTaint(ctx, target, new Source(origin, null, sourceValue(target)), NOT_MARKED); + internalTaint(ctx, target, newSource(target, origin, null, sourceValue(target)), NOT_MARKED); + return 1; } else { - ObjectVisitor.visit(target, new TaintingVisitor(to, origin), classFilter); + final TaintingVisitor visitor = new TaintingVisitor(to, origin); + ObjectVisitor.visit(target, visitor, classFilter); + return visitor.getCount(); } } @@ -282,19 +285,16 @@ public boolean isTainted(@Nullable final IastContext ctx, @Nullable final Object return target != null && findSource(ctx, target) != null; } - /** - * Compares origin and value to check if they are the same reference in order to prevent retaining - * references - * - * @see #sourceValue(Object) - */ - @Nullable - private static CharSequence sourceValue( - @Nullable final Object origin, @Nullable final CharSequence value) { - if (value != null && origin == value) { - return sourceValue(value); - } - return value; + /** Ensures that the reference is not kept strongly via the name or value properties */ + private static Source newSource( + @Nonnull final Object reference, + final byte origin, + @Nullable final CharSequence name, + @Nullable final CharSequence value) { + return new Source( + origin, + reference == name ? sourceValue(name) : name, + reference == value ? sourceValue(value) : value); } /** @@ -485,6 +485,7 @@ private static class TaintingVisitor implements ObjectVisitor.Visitor { private final TaintedObjects taintedObjects; private final byte origin; + private int count; private TaintingVisitor(@Nonnull final TaintedObjects taintedObjects, final byte origin) { this.taintedObjects = taintedObjects; @@ -497,12 +498,17 @@ public ObjectVisitor.State visit(@Nonnull final String path, @Nonnull final Obje if (value instanceof CharSequence) { final CharSequence charSequence = (CharSequence) value; if (canBeTainted(charSequence)) { - final Source source = new Source(origin, path, sourceValue(value)); + final Source source = newSource(value, origin, path, charSequence); + count++; taintedObjects.taint( charSequence, Ranges.forCharSequence(charSequence, source, NOT_MARKED)); } } return CONTINUE; } + + public int getCount() { + return count; + } } } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/StacktraceLeakModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/StacktraceLeakModuleImpl.java new file mode 100644 index 00000000000..2cfb07e5f49 --- /dev/null +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/StacktraceLeakModuleImpl.java @@ -0,0 +1,37 @@ +package com.datadog.iast.sink; + +import com.datadog.iast.Dependencies; +import com.datadog.iast.model.Evidence; +import com.datadog.iast.model.Location; +import com.datadog.iast.model.Vulnerability; +import com.datadog.iast.model.VulnerabilityType; +import datadog.trace.api.iast.sink.StacktraceLeakModule; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import org.jetbrains.annotations.NotNull; + +public class StacktraceLeakModuleImpl extends SinkModuleBase implements StacktraceLeakModule { + + public StacktraceLeakModuleImpl(@NotNull Dependencies dependencies) { + super(dependencies); + } + + @Override + public void onStacktraceLeak( + Throwable throwable, String moduleName, String className, String methodName) { + if (throwable != null) { + final AgentSpan span = AgentTracer.activeSpan(); + + Evidence evidence = + new Evidence( + "ExceptionHandler in " + + moduleName + + " \r\nthrown " + + throwable.getClass().getName()); + Location location = Location.forSpanAndClassAndMethod(span, className, methodName); + + reporter.report( + span, new Vulnerability(VulnerabilityType.STACKTRACE_LEAK, location, evidence)); + } + } +} diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy index e4081f96d7d..2d9597e728b 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy @@ -1,5 +1,6 @@ package com.datadog.iast +import com.datadog.iast.propagation.PropagationModuleImpl import com.datadog.iast.protobuf.Test2 import com.datadog.iast.protobuf.Test3 import com.datadog.iast.util.ObjectVisitor @@ -8,6 +9,8 @@ import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.iast.SourceTypes import datadog.trace.api.iast.propagation.PropagationModule +import datadog.trace.api.iast.telemetry.IastMetric +import datadog.trace.api.iast.telemetry.IastMetricCollector import datadog.trace.test.util.DDSpecification import foo.bar.VisitableClass @@ -22,9 +25,9 @@ class GrpcRequestMessageHandlerTest extends DDSpecification { private RequestContext ctx void setup() { - propagation = Mock(PropagationModule) + propagation = Spy(new PropagationModuleImpl()) InstrumentationBridge.registerIastModule(propagation) - iastCtx = Mock(IastRequestContext) + iastCtx = Spy(new IastRequestContext()) ctx = Mock(RequestContext) { getData(RequestContextSlot.IAST) >> iastCtx } @@ -73,7 +76,6 @@ class GrpcRequestMessageHandlerTest extends DDSpecification { return CONTINUE } } - final url = 'https://dd.datad0g.com/' final nonProtobufMessage = new VisitableClass(name: 'test') final filter = GrpcRequestMessageHandler::isProtobufArtifact @@ -102,6 +104,24 @@ class GrpcRequestMessageHandlerTest extends DDSpecification { protobufMessage << [buildProto2Message(), buildProto3Message()] } + void 'test that metrics are properly generated'() { + given: + final collector = Spy(new IastMetricCollector()) + iastCtx.getMetricCollector() >> collector + final handler = new GrpcRequestMessageHandler() + + when: + handler.apply(ctx, message) + + then: + 1 * collector.addMetric(IastMetric.EXECUTED_SOURCE, SourceTypes.GRPC_BODY, 6) + + where: + message | _ + buildProto2Message() | _ + buildProto3Message() | _ + } + private static def buildProto2Message() { final child = Test2.Proto2Child.newBuilder() .setOptional("optional") diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/propagation/PropagationModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/propagation/PropagationModuleTest.groovy index b0dceae6377..e573d5dfb2c 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/propagation/PropagationModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/propagation/PropagationModuleTest.groovy @@ -434,6 +434,19 @@ class PropagationModuleTest extends IastModuleImplTestBase { stringBuilder((0..Config.get().getIastTruncationMaxValueLength() * 2).join('')) | _ } + void 'test that source names should not make a strong reference over the value'() { + given: + final name = 'name' + + when: + module.taint(name, SourceTypes.REQUEST_PARAMETER_NAME, name) + + then: + final tainted = ctx.getTaintedObjects().get(name) + final taintedName = tainted.ranges[0].source.name + assert !taintedName.is(name) : 'Weak value should not be retained by the source name' + } + private List> taintIfSuite() { return [ Tuple.tuple(string('string'), string('string')), @@ -531,7 +544,8 @@ class PropagationModuleTest extends IastModuleImplTestBase { assert (range.marks & mark) > 0 } final source = range.source - assert !source.value.is(originalValue): 'Weak value should not be retained by the source' + assert !source.name.is(originalValue): 'Weak value should not be retained by the source name' + assert !source.value.is(originalValue): 'Weak value should not be retained by the source value' final expectedSource = expected.source assert source.origin == expectedSource.origin diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/StacktraceLeakModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/StacktraceLeakModuleTest.groovy new file mode 100644 index 00000000000..ecf8aaa3bd0 --- /dev/null +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/StacktraceLeakModuleTest.groovy @@ -0,0 +1,51 @@ +package com.datadog.iast.sink + +import com.datadog.iast.IastModuleImplTestBase +import com.datadog.iast.model.Evidence +import com.datadog.iast.model.Vulnerability +import com.datadog.iast.model.VulnerabilityType +import datadog.trace.api.iast.sink.StacktraceLeakModule +import datadog.trace.bootstrap.instrumentation.api.AgentSpan + +class StacktraceLeakModuleTest extends IastModuleImplTestBase { + private StacktraceLeakModule module + + def setup() { + module = new StacktraceLeakModuleImpl(dependencies) + } + + void 'iast stacktrace leak module'() { + given: + final spanId = 123456 + final span = Mock(AgentSpan) + + def throwable = new Exception('some exception') + def moduleName = 'moduleName' + def className = 'className' + def methodName = 'methodName' + + when: + module.onStacktraceLeak(throwable, moduleName, className, methodName) + + then: + 1 * tracer.activeSpan() >> span + 1 * span.getSpanId() >> spanId + 1 * span.getServiceName() + 1 * reporter.report(_, _) >> { args -> + Vulnerability vuln = args[1] as Vulnerability + assert vuln != null + assert vuln.getType() == VulnerabilityType.STACKTRACE_LEAK + assert vuln.getEvidence() == new Evidence('ExceptionHandler in moduleName \r\nthrown java.lang.Exception') + assert vuln.getLocation() != null + } + 0 * _ + } + + void 'iast stacktrace leak no exception'() { + when: + module.onStacktraceLeak(null, null, null, null) + + then: + 0 * _ + } +} diff --git a/dd-java-agent/agent-jmxfetch/integrations-core b/dd-java-agent/agent-jmxfetch/integrations-core index 03aed80d105..d211ef6ec4c 160000 --- a/dd-java-agent/agent-jmxfetch/integrations-core +++ b/dd-java-agent/agent-jmxfetch/integrations-core @@ -1 +1 @@ -Subproject commit 03aed80d105aa81b047e74c6da086165cac5ff6f +Subproject commit d211ef6ec4c9584df4b9f430dd10382f169d4727 diff --git a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLogger.java b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLogger.java index 958dfe6f14d..99e9d2fbd97 100644 --- a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLogger.java +++ b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLogger.java @@ -329,7 +329,7 @@ public void formatLog(LogLevel level, Marker marker, String format, Object arg) } FormattingTuple tuple = MessageFormatter.format(format, arg); - alwaysLog(level, marker, tuple.getMessage(), tuple.getThrowable()); + alwaysLog(level, marker, format, tuple.getMessage(), tuple.getThrowable()); } public void formatLog(LogLevel level, Marker marker, String format, Object arg1, Object arg2) { @@ -338,7 +338,7 @@ public void formatLog(LogLevel level, Marker marker, String format, Object arg1, } FormattingTuple tuple = MessageFormatter.format(format, arg1, arg2); - alwaysLog(level, marker, tuple.getMessage(), tuple.getThrowable()); + alwaysLog(level, marker, format, tuple.getMessage(), tuple.getThrowable()); } public void formatLog(LogLevel level, Marker marker, String format, Object... arguments) { @@ -347,17 +347,17 @@ public void formatLog(LogLevel level, Marker marker, String format, Object... ar } FormattingTuple tuple = MessageFormatter.arrayFormat(format, arguments); - alwaysLog(level, marker, tuple.getMessage(), tuple.getThrowable()); + alwaysLog(level, marker, format, tuple.getMessage(), tuple.getThrowable()); } private void log(LogLevel level, Marker marker, String msg, Throwable t) { if (!helper.enabled(level, marker)) { return; } - alwaysLog(level, marker, msg, t); + alwaysLog(level, marker, null, msg, t); } - private void alwaysLog(LogLevel level, Marker marker, String msg, Throwable t) { + protected void alwaysLog(LogLevel level, Marker marker, String format, String msg, Throwable t) { helper.log(level, marker, msg, t); } } diff --git a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLoggerFactory.java b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLoggerFactory.java index 1958c015f65..efd8ef2293f 100644 --- a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLoggerFactory.java +++ b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLoggerFactory.java @@ -1,5 +1,6 @@ package datadog.trace.logging.ddlogger; +import datadog.trace.api.Platform; import datadog.trace.logging.LogLevel; import datadog.trace.logging.LogLevelSwitcher; import datadog.trace.logging.LoggerHelper; @@ -12,8 +13,9 @@ public class DDLoggerFactory implements ILoggerFactory, LogLevelSwitcher { - private volatile LoggerHelperFactory helperFactory = null; + private final boolean telemetryLogCollectionEnabled = getLogCollectionEnabled(false); + private volatile LoggerHelperFactory helperFactory = null; private volatile LogLevel override = null; public DDLoggerFactory() {} @@ -59,7 +61,12 @@ public void log(LogLevel level, Marker marker, String message, Throwable t) { @Override public Logger getLogger(String name) { LoggerHelper helper = getHelperFactory().loggerHelperForName(name); - return new DDLogger(new HelperWrapper(helper), name); + HelperWrapper helperWrapper = new HelperWrapper(helper); + if (!telemetryLogCollectionEnabled || Platform.isNativeImageBuilder()) { + return new DDLogger(helperWrapper, name); + } else { + return new DDTelemetryLogger(helperWrapper, name); + } } private LoggerHelperFactory getHelperFactory() { @@ -80,4 +87,24 @@ private LoggerHelperFactory getHelperFactory() { public void reinitialize() { helperFactory = null; } + + // DDLoggerFactory can be called at very early stage, before Config loaded + // So to get property/env we use this custom function + private static boolean getLogCollectionEnabled(boolean defaultValue) { + String value = System.getProperty("dd.telemetry.log-collection.enabled"); + if ("true".equalsIgnoreCase(value)) { + return true; + } + if ("false".equalsIgnoreCase(value)) { + return false; + } + value = System.getenv("DD_TELEMETRY_LOG_COLLECTION_ENABLED"); + if ("true".equalsIgnoreCase(value)) { + return true; + } + if ("false".equalsIgnoreCase(value)) { + return false; + } + return defaultValue; + } } diff --git a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDTelemetryLogger.java b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDTelemetryLogger.java new file mode 100644 index 00000000000..9e7b7301cbb --- /dev/null +++ b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDTelemetryLogger.java @@ -0,0 +1,31 @@ +package datadog.trace.logging.ddlogger; + +import datadog.trace.api.LogCollector; +import datadog.trace.api.Platform; +import datadog.trace.logging.LogLevel; +import datadog.trace.logging.LoggerHelper; +import org.slf4j.Marker; + +public class DDTelemetryLogger extends DDLogger { + + public DDTelemetryLogger(LoggerHelper helper, String name) { + super(helper, name); + } + + @Override + protected void alwaysLog(LogLevel level, Marker marker, String format, String msg, Throwable t) { + super.alwaysLog(level, marker, format, msg, t); + if (!Platform.isNativeImageBuilder()) { + // We report only messages with Throwable or explicitly marked with SEND_TELEMETRY + if (t != null || marker == LogCollector.SEND_TELEMETRY) { + // We are scrubbing all data from messages, and only send static (compile time) + // log messages, plus redacted stack traces. + if (format != null) { + LogCollector.get().addLogMessage(level.name(), format, t); + } else if (msg != null) { + LogCollector.get().addLogMessage(level.name(), msg, t); + } + } + } + } +} diff --git a/dd-java-agent/agent-logging/src/test/groovy/datadog/trace/logging/ddlogger/DDTelemetryLoggerTest.groovy b/dd-java-agent/agent-logging/src/test/groovy/datadog/trace/logging/ddlogger/DDTelemetryLoggerTest.groovy new file mode 100644 index 00000000000..16ad041ec35 --- /dev/null +++ b/dd-java-agent/agent-logging/src/test/groovy/datadog/trace/logging/ddlogger/DDTelemetryLoggerTest.groovy @@ -0,0 +1,52 @@ +package datadog.trace.logging.ddlogger + +import datadog.trace.api.LogCollector +import datadog.trace.logging.LogValidatingSpecification +import datadog.trace.logging.simplelogger.SLCompatFactory +import datadog.trace.logging.simplelogger.SLCompatSettings + +class DDTelemetryLoggerTest extends LogValidatingSpecification { + def "test logging with log collector"() { + setup: + def name = "foo.bar" + Properties props = new Properties() + props.setProperty(SLCompatSettings.Keys.DEFAULT_LOG_LEVEL, "debug") + props.setProperty(SLCompatSettings.Keys.SHOW_THREAD_NAME, "false") + def settings = new SLCompatSettings(props) + def factory = new SLCompatFactory(props, settings) + def logger = new DDTelemetryLogger(factory.loggerHelperForName(name), name) + + when: + logger.debug("debug message {}", 42, new Exception()) + def collection = LogCollector.get().drain() + + then: + collection.size() == 1 + collection[0].message() == 'debug message {}' + + when: + logger.warn(LogCollector.SEND_TELEMETRY, "warning message {}", 42) + collection = LogCollector.get().drain() + + then: + collection.size() == 1 + collection[0].message() == 'warning message {}' + + when: + logger.error(LogCollector.SEND_TELEMETRY, "plain error message") + collection = LogCollector.get().drain() + + then: + collection.size() == 1 + collection[0].message() == 'plain error message' + + when: + logger.debug(LogCollector.SEND_TELEMETRY, null) + logger.error(null, new Exception()) + logger.warn("warn message", null) + collection = LogCollector.get().drain() + + then: + collection.size() == 0 + } +} diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary-ddprof/src/main/java/com/datadog/profiling/auxiliary/ddprof/AuxiliaryDatadogProfiler.java b/dd-java-agent/agent-profiling/profiling-auxiliary-ddprof/src/main/java/com/datadog/profiling/auxiliary/ddprof/AuxiliaryDatadogProfiler.java index bf3dc01fdc7..43cc0bdf0e5 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary-ddprof/src/main/java/com/datadog/profiling/auxiliary/ddprof/AuxiliaryDatadogProfiler.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary-ddprof/src/main/java/com/datadog/profiling/auxiliary/ddprof/AuxiliaryDatadogProfiler.java @@ -2,10 +2,10 @@ import com.datadog.profiling.auxiliary.AuxiliaryImplementation; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.ddprof.DatadogProfiler; import com.datadog.profiling.utils.ProfilingMode; import com.google.auto.service.AutoService; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.EnumSet; import java.util.Set; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryImplementation.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryImplementation.java index b3d955167d1..0f28f087c37 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryImplementation.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryImplementation.java @@ -1,8 +1,8 @@ package com.datadog.profiling.auxiliary; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.utils.ProfilingMode; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.Collections; import java.util.Set; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingData.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingData.java index 155775e054d..4de89e7c428 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingData.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingData.java @@ -1,7 +1,7 @@ package com.datadog.profiling.auxiliary; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.IOException; import java.time.Instant; import javax.annotation.Nonnull; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingStreams.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingStreams.java index 3109a5b7e35..e7b9f56fa5f 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingStreams.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingStreams.java @@ -1,7 +1,7 @@ package com.datadog.profiling.auxiliary; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingDataTest.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingDataTest.java index 319591cd749..fec7322970f 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingDataTest.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingDataTest.java @@ -2,9 +2,9 @@ import static org.junit.jupiter.api.Assertions.*; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/TestAuxiliaryProfilerImplementation.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/TestAuxiliaryProfilerImplementation.java index 764a2199940..aeb47f30b03 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/TestAuxiliaryProfilerImplementation.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/TestAuxiliaryProfilerImplementation.java @@ -1,9 +1,9 @@ package com.datadog.profiling.auxiliary; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.utils.ProfilingMode; import com.google.auto.service.AutoService; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.EnumSet; import java.util.Set; diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecording.java b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecording.java index 00b44761bc6..1d61e984768 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecording.java +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecording.java @@ -17,11 +17,11 @@ import com.datadog.profiling.controller.OngoingRecording; import com.datadog.profiling.controller.ProfilerSettingsSupport; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.ddprof.DatadogProfiler; import datadog.trace.api.Platform; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; import java.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerControllerTest.java b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerControllerTest.java index 8f344543279..ca07b0d3229 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerControllerTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerControllerTest.java @@ -1,9 +1,9 @@ package com.datadog.profiling.controller.ddprof; import static datadog.trace.api.config.ProfilingConfig.PROFILING_AUXILIARY_TYPE; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; -import com.datadog.profiling.controller.RecordingData; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.Properties; import org.junit.jupiter.api.Test; diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecordingTest.java b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecordingTest.java index a40d67a9a8a..6ebe1f63029 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecordingTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecordingTest.java @@ -2,9 +2,9 @@ import static org.junit.jupiter.api.Assertions.*; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.ddprof.DatadogProfiler; +import datadog.trace.api.profiling.RecordingData; import java.time.Instant; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java index 541ad63d14e..7712afeb869 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java @@ -20,6 +20,8 @@ import static datadog.trace.api.Platform.isJavaVersionAtLeast; import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_ENABLED_DEFAULT; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_MODE; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_MODE_DEFAULT; import static datadog.trace.api.config.ProfilingConfig.PROFILING_ULTRA_MINIMAL; import com.datadog.profiling.controller.ConfigurationException; @@ -110,7 +112,15 @@ public OpenJdkController(final ConfigProvider configProvider) "enabling Datadog heap histogram on JVM without an efficient implementation of the jdk.ObjectCount event. " + "This may increase p99 latency. Consider upgrading to JDK 17.0.9+ or 21+ to reduce latency impact."); } - enableEvent(recordingSettings, "jdk.ObjectCount", "user enabled histogram heap collection"); + String mode = + configProvider.getString( + PROFILING_HEAP_HISTOGRAM_MODE, PROFILING_HEAP_HISTOGRAM_MODE_DEFAULT); + if ("periodic".equalsIgnoreCase(mode)) { + enableEvent(recordingSettings, "jdk.ObjectCount", "user enabled histogram heap collection"); + } else { + enableEvent( + recordingSettings, "jdk.ObjectCountAfterGC", "user enabled histogram heap collection"); + } } // Toggle settings from override file diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecording.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecording.java index 9c0f4401b5a..e66f45556aa 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecording.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecording.java @@ -3,10 +3,10 @@ import com.datadog.profiling.auxiliary.AuxiliaryProfiler; import com.datadog.profiling.auxiliary.AuxiliaryRecordingData; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.utils.ProfilingMode; import datadog.trace.api.profiling.ProfilingListenersRegistry; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; import java.time.Duration; import java.time.Instant; import java.util.Map; diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java index 3c0b3a0d13b..6cedf96691b 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java @@ -15,8 +15,8 @@ */ package com.datadog.profiling.controller.openjdk; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.IOException; import java.time.Instant; import javax.annotation.Nonnull; diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkControllerTest.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkControllerTest.java index d1a4ee5d9c2..f1d76dac7b0 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkControllerTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkControllerTest.java @@ -6,8 +6,8 @@ import static datadog.trace.api.config.ProfilingConfig.PROFILING_TEMPLATE_OVERRIDE_FILE; import static org.junit.jupiter.api.Assertions.*; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.jfr.JfpUtilsTest; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.Properties; import jdk.jfr.Recording; diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecordingTest.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecordingTest.java index 226305b6469..7ce507aeef7 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecordingTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecordingTest.java @@ -5,7 +5,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.datadog.profiling.controller.RecordingData; +import datadog.trace.api.profiling.RecordingData; import java.time.Instant; import jdk.jfr.Recording; import jdk.jfr.RecordingState; diff --git a/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java b/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java index e66d32dccb3..226e1b0f24d 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java +++ b/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java @@ -15,8 +15,8 @@ */ package com.datadog.profiling.controller.oracle; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.IOException; import java.io.InputStream; import java.time.Instant; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/OngoingRecording.java b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/OngoingRecording.java index f2b0630ae6c..767957adf3a 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/OngoingRecording.java +++ b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/OngoingRecording.java @@ -1,6 +1,7 @@ package com.datadog.profiling.controller; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; import java.io.Closeable; import java.time.Instant; import javax.annotation.Nonnull; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ProfilingSystem.java b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ProfilingSystem.java index d5ae0359da5..f1bf7757dae 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ProfilingSystem.java +++ b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ProfilingSystem.java @@ -18,6 +18,9 @@ import static datadog.trace.util.AgentThreadFactory.AgentThread.PROFILER_RECORDING_SCHEDULER; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingDataListener; +import datadog.trace.api.profiling.RecordingType; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.util.AgentTaskScheduler; import java.time.Duration; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ProfilingSystemTest.java b/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ProfilingSystemTest.java index f3cb16ff88d..286790b315c 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ProfilingSystemTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ProfilingSystemTest.java @@ -15,7 +15,7 @@ */ package com.datadog.profiling.controller; -import static com.datadog.profiling.controller.RecordingType.CONTINUOUS; +import static datadog.trace.api.profiling.RecordingType.CONTINUOUS; import static datadog.trace.util.AgentThreadFactory.AgentThread.PROFILER_RECORDING_SCHEDULER; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; @@ -36,6 +36,8 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingDataListener; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.util.AgentTaskScheduler; import java.time.Duration; diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java index bd8d4a37b24..8f29aba8365 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java @@ -28,12 +28,12 @@ import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_THRESHOLD_MILLIS_DEFAULT; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.utils.ProfilingMode; import com.datadoghq.profiler.ContextSetter; import com.datadoghq.profiler.JavaProfiler; import datadog.trace.api.config.ProfilingConfig; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.io.IOException; import java.nio.file.Files; @@ -151,7 +151,7 @@ private DatadogProfiler(ConfigProvider configProvider) throws UnsupportedEnviron ProfilingConfig.PROFILING_DATADOG_PROFILER_SCRATCH, ProfilingConfig.PROFILING_DATADOG_PROFILER_SCRATCH_DEFAULT)); } catch (IOException e) { - throw new UnsupportedOperationException("Unable to instantiate datadog profiler"); + throw new UnsupportedOperationException("Unable to instantiate datadog profiler", e); } if (profiler == null) { throw new UnsupportedEnvironmentException("Unable to instantiate datadog profiler"); diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java index f45be14f8ac..3746ca29936 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java @@ -291,7 +291,7 @@ public static boolean isSpanNameContextAttributeEnabled(ConfigProvider configPro } public static boolean isResourceNameContextAttributeEnabled(ConfigProvider configProvider) { - return configProvider.getBoolean(PROFILING_CONTEXT_ATTRIBUTES_RESOURCE_NAME_ENABLED, true); + return configProvider.getBoolean(PROFILING_CONTEXT_ATTRIBUTES_RESOURCE_NAME_ENABLED, false); } public static String getString(ConfigProvider configProvider, String key, String defaultValue) { diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecording.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecording.java index d1e0781b964..8154a8a444e 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecording.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecording.java @@ -1,8 +1,8 @@ package com.datadog.profiling.ddprof; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java index e9cb4f03393..954e79834d2 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java @@ -1,7 +1,7 @@ package com.datadog.profiling.ddprof; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java index 6810a590419..5f8e202c006 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java @@ -17,9 +17,6 @@ public class DatadogProfilingIntegration implements ProfilingContextIntegration private static final boolean WALLCLOCK_ENABLED = DatadogProfilerConfig.isWallClockProfilerEnabled(); - private static final boolean QUEUEING_TIME_ENABLED = - WALLCLOCK_ENABLED && DatadogProfilerConfig.isQueueingTimeEnabled(); - @Override public void onAttach() { if (WALLCLOCK_ENABLED) { @@ -39,6 +36,27 @@ public int encode(CharSequence constant) { return DDPROF.encode(constant); } + @Override + public int encodeOperationName(CharSequence constant) { + if (SPAN_NAME_INDEX >= 0) { + return DDPROF.encode(constant); + } + return 0; + } + + @Override + public int encodeResourceName(CharSequence constant) { + if (RESOURCE_NAME_INDEX >= 0) { + return DDPROF.encode(constant); + } + return 0; + } + + @Override + public String name() { + return "ddprof"; + } + @Override public void setContext(ProfilerContext profilerContext) { DDPROF.setSpanContext(profilerContext.getSpanId(), profilerContext.getRootSpanId()); @@ -67,12 +85,4 @@ public ProfilingContextAttribute createContextAttribute(String attribute) { public ProfilingScope newScope() { return new DatadogProfilingScope(DDPROF); } - - @Override - public boolean isQueuingTimeEnabled() { - return QUEUEING_TIME_ENABLED; - } - - @Override - public void recordQueueingTime(long duration) {} } diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingTest.java index 6d9ab83f3e2..65bfac0e226 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.*; -import com.datadog.profiling.controller.RecordingData; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.io.InputStream; import java.nio.file.Files; diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java index 67531996499..50e9051f3ab 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java @@ -6,11 +6,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.utils.ProfilingMode; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.api.profiling.ProfilingScope; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.nio.file.Path; import java.nio.file.Paths; @@ -143,7 +143,7 @@ private static ConfigProvider configProvider( props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_ENABLED, Boolean.toString(wall)); props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_ALLOC_ENABLED, Boolean.toString(alloc)); props.put( - ProfilingConfig.PROFILING_DATADOG_PROFILER_MEMLEAK_ENABLED, Boolean.toString(memleak)); + ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED, Boolean.toString(memleak)); return ConfigProvider.withPropertiesOverride(props); } } diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/CompressingRequestBody.java b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/CompressingRequestBody.java index eadf3c998ec..7d77956557c 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/CompressingRequestBody.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/CompressingRequestBody.java @@ -1,6 +1,6 @@ package com.datadog.profiling.uploader; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/ProfileUploader.java b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/ProfileUploader.java index 19c4ae3d21f..159c16ecb9a 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/ProfileUploader.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/ProfileUploader.java @@ -17,8 +17,6 @@ import static datadog.trace.util.AgentThreadFactory.AgentThread.PROFILER_HTTP_DISPATCHER; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingType; import com.datadog.profiling.uploader.util.JfrCliHelper; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonReader; @@ -29,6 +27,8 @@ import datadog.trace.api.DDTags; import datadog.trace.api.git.GitInfo; import datadog.trace.api.git.GitInfoProvider; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingType; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.relocate.api.IOLogger; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/util/JfrCliHelper.java b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/util/JfrCliHelper.java index 68d8a0bab2d..9668dc61d72 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/util/JfrCliHelper.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/util/JfrCliHelper.java @@ -2,7 +2,7 @@ import static datadog.trace.util.AgentThreadFactory.AgentThread.PROFILER_HTTP_DISPATCHER; -import com.datadog.profiling.controller.RecordingData; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.relocate.api.IOLogger; import datadog.trace.util.AgentThreadFactory; import java.io.ByteArrayOutputStream; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/CompressingRequestBodyTest.java b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/CompressingRequestBodyTest.java index 5b2cf5bec79..9dd3a6e23ac 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/CompressingRequestBodyTest.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/CompressingRequestBodyTest.java @@ -5,7 +5,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/ProfileUploaderTest.java b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/ProfileUploaderTest.java index cb6a2f284e3..fce3459ddec 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/ProfileUploaderTest.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/ProfileUploaderTest.java @@ -33,9 +33,6 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; -import com.datadog.profiling.controller.RecordingType; import com.datadog.profiling.testing.ProfilingTestUtils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,6 +42,9 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTags; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; +import datadog.trace.api.profiling.RecordingType; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.relocate.api.IOLogger; import datadog.trace.util.PidHelper; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/util/JfrCliHelperTest.java b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/util/JfrCliHelperTest.java index 892d9ea96c0..a8d2c1fd7a1 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/util/JfrCliHelperTest.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/util/JfrCliHelperTest.java @@ -9,8 +9,8 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import datadog.trace.relocate.api.IOLogger; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; diff --git a/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProfilingAgent.java b/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProfilingAgent.java index 3a0832f5c8e..6bcddf20174 100644 --- a/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProfilingAgent.java +++ b/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProfilingAgent.java @@ -8,14 +8,14 @@ import com.datadog.profiling.controller.Controller; import com.datadog.profiling.controller.ControllerFactory; import com.datadog.profiling.controller.ProfilingSystem; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingDataListener; -import com.datadog.profiling.controller.RecordingType; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.uploader.ProfileUploader; import datadog.trace.api.Config; import datadog.trace.api.Platform; import datadog.trace.api.config.ProfilingConfig; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingDataListener; +import datadog.trace.api.profiling.RecordingType; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.io.IOException; import java.lang.ref.WeakReference; diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/log/UnionMap.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/log/UnionMap.java index 219dcda7e11..802393601d7 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/log/UnionMap.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/log/UnionMap.java @@ -1,7 +1,9 @@ package datadog.trace.agent.tooling.log; +import java.io.Serializable; import java.util.AbstractMap; import java.util.AbstractSet; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -11,7 +13,7 @@ * New entries are put in the primary map while old entries are deleted from both, as appropriate. * Lazy deduplication occurs once: before iterating over entries/values, or when combining sizes. */ -public final class UnionMap extends AbstractMap { +public final class UnionMap extends AbstractMap implements Serializable { private final Map primaryMap; private final Map secondaryMap; private transient Set> entrySet; @@ -145,4 +147,8 @@ public void remove() { } return entrySet; } + + public Object writeReplace() { + return new HashMap<>(this); // serialize de-duplicated copy + } } diff --git a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie index d244f5a623b..2c012f531b0 100644 --- a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie +++ b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie @@ -188,6 +188,9 @@ 2 com.github.mustachejava.* 2 com.google.api.* 0 com.google.api.client.http.HttpRequest +0 com.google.api.gax.grpc.* +0 com.google.api.gax.retrying.* +0 com.google.cloud.pubsub.v1.* 2 com.google.cloud.* 2 com.google.common.* 0 com.google.common.base.internal.Finalizer @@ -288,6 +291,9 @@ 2 org.springframework.expression.* 2 org.springframework.format.* 2 org.springframework.http.* +# Need for IAST: Spring gson support +0 org.springframework.http.converter.json.AbstractJsonHttpMessageConverter +0 org.springframework.http.converter.json.GsonHttpMessageConverter # Need for IAST: we instrument these classes 0 org.springframework.http.HttpHeaders 0 org.springframework.http.ReadOnlyHttpHeaders @@ -330,6 +336,8 @@ 0 org.springframework.web.servlet.* # Need for IAST so propagation of tainted objects is complete in spring 2.7.5 0 org.springframework.util.StreamUtils$NonClosingInputStream +# Need for IAST gson propagation +0 org.springframework.util.StreamUtils # Included for IAST Spring mvc unvalidated redirect and xss vulnerability 0 org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite 2 org.xml.* diff --git a/dd-java-agent/appsec/src/main/resources/default_config.json b/dd-java-agent/appsec/src/main/resources/default_config.json index a6e01468548..172558f06e3 100644 --- a/dd-java-agent/appsec/src/main/resources/default_config.json +++ b/dd-java-agent/appsec/src/main/resources/default_config.json @@ -1,7 +1,7 @@ { "version": "2.2", "metadata": { - "rules_version": "1.8.0" + "rules_version": "1.9.0" }, "rules": [ { @@ -3004,6 +3004,7 @@ ], "regex": "]*>[\\s\\S]*?", "options": { + "case_sensitive": false, "min_length": 8 } }, @@ -4207,7 +4208,6 @@ "name": "Remote Command Execution: Java process spawn (CVE-2017-9805)", "tags": { "type": "java_code_injection", - "crs_id": "944110", "category": "attack_attempt", "cwe": "94", "capec": "1000/152/242" @@ -4235,48 +4235,16 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "(?:runtime|processbuilder)", + "regex": "(?:unmarshaller|base64data|java\\.).*(?:runtime|processbuilder)", "options": { - "case_sensitive": true, - "min_length": 7 - } - }, - "operator": "match_regex" - }, - { - "parameters": { - "inputs": [ - { - "address": "server.request.query" - }, - { - "address": "server.request.body" - }, - { - "address": "server.request.path_params" - }, - { - "address": "server.request.headers.no_cookies" - }, - { - "address": "grpc.server.request.message" - }, - { - "address": "graphql.server.all_resolvers" - } - ], - "regex": "(?:unmarshaller|base64data|java\\.)", - "options": { - "case_sensitive": true, - "min_length": 5 + "case_sensitive": false, + "min_length": 13 } }, "operator": "match_regex" } ], - "transformers": [ - "lowercase" - ] + "transformers": [] }, { "id": "crs-944-130", @@ -4479,6 +4447,9 @@ }, { "address": "graphql.server.all_resolvers" + }, + { + "address": "server.request.headers.no_cookies" } ], "regex": "[#%$]{(?:[^}]+[^\\w\\s}\\-_][^}]+|\\d+-\\d+)}", @@ -4752,7 +4723,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "\\bqualysperiscope\\.com\\b" + "regex": "\\bqualysperiscope\\.com\\b|\\.oscomm\\." }, "operator": "match_regex" } @@ -4833,7 +4804,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "\\b(?:webhook\\.site|\\.canarytokens\\.com|vii\\.one|act1on3\\.ru|gdsburp\\.com)\\b" + "regex": "\\b(?:webhook\\.site|\\.canarytokens\\.com|vii\\.one|act1on3\\.ru|gdsburp\\.com|arcticwolf\\.net|oob\\.li|htbiw\\.com|h4\\.vc|mochan\\.cloud|imshopping\\.com|bootstrapnodejs\\.com|mooo-ng\\.com|securitytrails\\.com|canyouhackit\\.io|7bae\\.xyz)\\b" }, "operator": "match_regex" } @@ -4955,7 +4926,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "\\b(?:interact\\.sh|oast\\.(?:pro|live|site|online|fun|me))\\b" + "regex": "\\b(?:interact\\.sh|oast\\.(?:pro|live|site|online|fun|me)|indusfacefinder\\.in|where\\.land|syhunt\\.net|tssrt\\.de|boardofcyber\\.io|assetnote-callback\\.com|praetorianlabs\\.dev|netspi\\.sh)\\b" }, "operator": "match_regex" } @@ -4996,7 +4967,187 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "\\b(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)r87(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)(?:me|com)\\b", + "regex": "\\b(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)?r87(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)(?:me|com)\\b", + "options": { + "case_sensitive": false, + "min_length": 7 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-009", + "name": "WhiteHat Security OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "WhiteHatSecurity", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "\\bwhsec(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)us\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-010", + "name": "Nessus OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Nessus", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "\\b\\.nessus\\.org\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-011", + "name": "Watchtowr OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Watchtowr", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "\\bwatchtowr\\.com\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-012", + "name": "AppCheck NG OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "AppCheckNG", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "\\bptst\\.io\\b", "options": { "case_sensitive": false, "min_length": 7 @@ -5048,6 +5199,50 @@ ], "transformers": [] }, + { + "id": "dog-932-100", + "name": "Shell spawn executing network command", + "tags": { + "type": "command_injection", + "category": "attack_attempt", + "cwe": "77", + "capec": "1000/152/248/88", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "(?:(?:['\"\\x60({|;&]|(?:^|['\"\\x60({|;&])(?:cmd(?:\\.exe)?\\s+(?:/\\w(?::\\w+)?\\s+)*))(?:ping|curl|wget|telnet)|\\bnslookup)[\\s,]", + "options": { + "case_sensitive": true, + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, { "id": "dog-934-001", "name": "XXE - XML file loads external entity", @@ -5056,7 +5251,7 @@ "category": "attack_attempt", "cwe": "91", "capec": "1000/152/248/250", - "confidence": "0" + "confidence": "1" }, "conditions": [ { @@ -5091,7 +5286,7 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "0" + "confidence": "1" }, "conditions": [ { @@ -5125,7 +5320,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "<(?:iframe|esi:include)(?:(?:\\s|/)*\\w+=[\"'\\w]+)*(?:\\s|/)*src(?:doc)?=[\"']?(?:data:|javascript:|http:|//)[^\\s'\"]+['\"]?", + "regex": "<(?:iframe|esi:include)(?:(?:\\s|/)*\\w+=[\"'\\w]+)*(?:\\s|/)*src(?:doc)?=[\"']?(?:data:|javascript:|http:|dns:|//)[^\\s'\"]+['\"]?", "options": { "min_length": 14 } @@ -5171,7 +5366,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "https?:\\/\\/(?:.*\\.)?(?:bxss\\.in|xss\\.ht|js\\.rip)", + "regex": "https?:\\/\\/(?:.*\\.)?(?:bxss\\.(?:in|me)|xss\\.ht|js\\.rip)", "options": { "case_sensitive": false } @@ -6110,7 +6305,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "(http|https):\\/\\/(?:.*\\.)?(?:burpcollaborator\\.net|localtest\\.me|mail\\.ebc\\.apple\\.com|bugbounty\\.dod\\.network|.*\\.[nx]ip\\.io|oastify\\.com|oast\\.(?:pro|live|site|online|fun|me)|sslip\\.io|requestbin\\.com|requestbin\\.net|hookbin\\.com|webhook\\.site|canarytokens\\.com|interact\\.sh|ngrok\\.io|bugbounty\\.click|prbly\\.win|qualysperiscope\\.com|vii.one|act1on3.ru)" + "regex": "(http|https):\\/\\/(?:.*\\.)?(?:burpcollaborator\\.net|localtest\\.me|mail\\.ebc\\.apple\\.com|bugbounty\\.dod\\.network|.*\\.[nx]ip\\.io|oastify\\.com|oast\\.(?:pro|live|site|online|fun|me)|sslip\\.io|requestbin\\.com|requestbin\\.net|hookbin\\.com|webhook\\.site|canarytokens\\.com|interact\\.sh|ngrok\\.io|bugbounty\\.click|prbly\\.win|qualysperiscope\\.com|vii\\.one|act1on3\\.ru)" }, "operator": "match_regex" } @@ -7610,6 +7805,35 @@ ], "transformers": [] }, + { + "id": "ua0-600-63x", + "name": "FeroxBuster", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "feroxbuster", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^feroxbuster/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, { "id": "ua0-600-6xx", "name": "Stealthy scanner", @@ -7631,7 +7855,7 @@ ] } ], - "regex": "mozilla/4\\.0 \\(compatible(; msie (?:6\\.0; win32|4\\.0; Windows NT))?\\)", + "regex": "mozilla/4\\.0 \\(compatible(; msie (?:6\\.0; (?:win32|Windows NT 5\\.0)|4\\.0; Windows NT))?\\)", "options": { "case_sensitive": false } diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/build.gradle b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/build.gradle index 2e036a050e1..6625bc50fb0 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/build.gradle +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/build.gradle @@ -49,6 +49,8 @@ dependencies { // needed for kinesis: testImplementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-cbor', version: versions.jackson + testImplementation group: 'org.json', name: 'json', version: '20231013' + test_before_1_11_106Implementation(group: 'com.amazonaws', name: 'aws-java-sdk-s3') { version { diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy index 9bf73151744..9874e1a5510 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy @@ -39,6 +39,7 @@ import datadog.trace.api.Config import datadog.trace.api.DDSpanTypes import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.test.util.Flaky +import org.json.XML import spock.lang.AutoCleanup import spock.lang.Shared @@ -61,12 +62,23 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { def credentialsProvider = new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()) @Shared def responseBody = new AtomicReference() + @Shared + def jsonPointer = new AtomicReference() + @AutoCleanup @Shared def server = httpServer { handlers { all { - response.status(200).send(responseBody.get()) + def body = responseBody.get() + if (request.headers.get("Content-Type")?.contains("json")) { + def json = XML.toJSONObject(body) + if (jsonPointer.get() != null) { + json = json.query(jsonPointer.get()) + } + body = json.toString() + } + response.status(200).send(body) } } } @@ -131,6 +143,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { def "send #operation request with mocked response"() { setup: responseBody.set(body) + jsonPointer.set(jsonPointerStr) when: def response = call.call(client) @@ -181,19 +194,22 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { server.lastRequest.headers.get("x-datadog-trace-id") == null server.lastRequest.headers.get("x-datadog-parent-id") == null + cleanup: + jsonPointer.set(null) + where: - service | operation | method | path | client | call | additionalTags | body | peerService - "S3" | "CreateBucket" | "PUT" | "/testbucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket", "bucketname": "testbucket"] | "" | "aws.bucket.name" - "S3" | "GetObject" | "GET" | "/someBucket/someKey" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket", "bucketname": "someBucket"] | "" | "aws.bucket.name" - "DynamoDBv2" | "CreateTable" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" | "aws.table.name" - "Kinesis" | "DeleteStream" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" | "aws.stream.name" + service | operation | method | path | client | call | additionalTags | body | peerService | jsonPointerStr + "S3" | "CreateBucket" | "PUT" | "/testbucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket", "bucketname": "testbucket"] | "" | "aws.bucket.name" | null + "S3" | "GetObject" | "GET" | "/someBucket/someKey" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket", "bucketname": "someBucket"] | "" | "aws.bucket.name" | null + "DynamoDBv2" | "CreateTable" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" | "aws.table.name" | null + "Kinesis" | "DeleteStream" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" | "aws.stream.name" | null "SQS" | "CreateQueue" | "POST" | "/" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createQueue(new CreateQueueRequest("somequeue")) } | ["aws.queue.name": "somequeue", "queuename": "somequeue"] | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 - """ | "aws.queue.name" + """ | "aws.queue.name" | "/CreateQueueResponse/CreateQueueResult" "SQS" | "SendMessage" | "POST" | "/someurl" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.sendMessage(new SendMessageRequest("someurl", "")) } | ["aws.queue.url": "someurl"] | """ @@ -203,7 +219,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { 27daac76-34dd-47df-bd01-1f6e873584a0 - """ | "aws.queue.url" + """ | "aws.queue.url" | "/SendMessageResponse/SendMessageResult" "SNS" | "Publish" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:some-topic", "")) } | ["aws.topic.name": "some-topic", "topicname": "some-topic"] | """ @@ -211,22 +227,21 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { d74b8436-ae13-5ab4-a9ff-ce54dfea72a0 - """ | "aws.topic.name" + """ | "aws.topic.name" | "/PublishResponse/PublishResult" "EC2" | "AllocateAddress" | "POST" | "/" | AmazonEC2ClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.allocateAddress() } | [:] | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard - """ | null - + """ | null | "/AllocateAddressResponse" "RDS" | "DeleteOptionGroup" | "POST" | "/" | AmazonRDSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteOptionGroup(new DeleteOptionGroupRequest()) } | [:] | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 - """ | null + """ | null | null } def "send #operation request to closed port"() { diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/LegacyAWS1ClientForkedTest.groovy b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/LegacyAWS1ClientForkedTest.groovy index e59540b4ab5..a03133a45c8 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/LegacyAWS1ClientForkedTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/LegacyAWS1ClientForkedTest.groovy @@ -36,6 +36,7 @@ import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.test.util.Flaky import org.apache.http.conn.HttpHostConnectException import org.apache.http.impl.execchain.RequestAbortedException +import org.json.XML import spock.lang.AutoCleanup import spock.lang.Shared @@ -68,12 +69,23 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { def credentialsProvider = new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()) @Shared def responseBody = new AtomicReference() + @Shared + def jsonPointer = new AtomicReference() + @AutoCleanup @Shared def server = httpServer { handlers { all { - response.status(200).send(responseBody.get()) + def body = responseBody.get() + if (request.headers.get("Content-Type")?.contains("json")) { + def json = XML.toJSONObject(body) + if (jsonPointer.get() != null) { + json = json.query(jsonPointer.get()) + } + body = json.toString() + } + response.status(200).send(body) } } } @@ -124,6 +136,7 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { def "send #operation request with mocked response"() { setup: responseBody.set(body) + jsonPointer.set(jsonPointerStr) when: def response = call.call(client) @@ -187,18 +200,21 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { server.lastRequest.headers.get("x-datadog-trace-id") == null server.lastRequest.headers.get("x-datadog-parent-id") == null + cleanup: + jsonPointer.set(null) + where: - service | operation | ddService | method | path | client | call | additionalTags | body - "S3" | "CreateBucket" | "java-aws-sdk" | "PUT" | "/testbucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket", "bucketname": "testbucket"] | "" - "S3" | "GetObject" | "java-aws-sdk" | "GET" | "/someBucket/someKey" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket", "bucketname": "someBucket"] | "" - "DynamoDBv2" | "CreateTable" | "java-aws-sdk" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" - "Kinesis" | "DeleteStream" | "java-aws-sdk" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" + service | operation | ddService | method | path | client | call | additionalTags | body | jsonPointerStr + "S3" | "CreateBucket" | "java-aws-sdk" | "PUT" | "/testbucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket", "bucketname": "testbucket"] | "" | null + "S3" | "GetObject" | "java-aws-sdk" | "GET" | "/someBucket/someKey" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket", "bucketname": "someBucket"] | "" | null + "DynamoDBv2" | "CreateTable" | "java-aws-sdk" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" | null + "Kinesis" | "DeleteStream" | "java-aws-sdk" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" | null "SQS" | "CreateQueue" | "java-aws-sdk" | "POST" | "/" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createQueue(new CreateQueueRequest("somequeue")) } | ["aws.queue.name": "somequeue", "queuename": "somequeue"] | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 - """ + """ | "/CreateQueueResponse/CreateQueueResult" "SQS" | "SendMessage" | "sqs" | "POST" | "/someurl" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.sendMessage(new SendMessageRequest("someurl", "")) } | ["aws.queue.url": "someurl"] | """ @@ -208,7 +224,7 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { 27daac76-34dd-47df-bd01-1f6e873584a0 - """ + """ | "/SendMessageResponse/SendMessageResult" "SNS" | "Publish" | "sns" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:some-topic", "")) } | ["aws.topic.name": "some-topic", "topicname": "some-topic"] | """ @@ -216,21 +232,21 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { d74b8436-ae13-5ab4-a9ff-ce54dfea72a0 - """ + """ | "/PublishResponse/PublishResult" "EC2" | "AllocateAddress" | "java-aws-sdk" | "POST" | "/" | AmazonEC2ClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.allocateAddress() } | [:] | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard - """ + """ | null "RDS" | "DeleteOptionGroup" | "java-aws-sdk" | "POST" | "/" | AmazonRDSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteOptionGroup(new DeleteOptionGroupRequest()) } | [:] | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 - """ + """ | null } def "send #operation request to closed port"() { diff --git a/dd-java-agent/instrumentation/aws-java-sqs-1.0/build.gradle b/dd-java-agent/instrumentation/aws-java-sqs-1.0/build.gradle index 3f9a4eca2cf..e979e34e7fd 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-1.0/build.gradle +++ b/dd-java-agent/instrumentation/aws-java-sqs-1.0/build.gradle @@ -12,6 +12,7 @@ muzzle { apply from: "$rootDir/gradle/java.gradle" addTestSuiteForDir('latestDepTest', 'test') +addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') dependencies { compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.0' @@ -23,7 +24,7 @@ dependencies { testImplementation project(':dd-java-agent:instrumentation:jms') // SQS<->JMS testing: - testImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.13', version: '1.2.3' + testImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.13', version: '1.4.7' testImplementation group: 'com.amazonaws', name: 'amazon-sqs-java-messaging-lib', version: '1.0.8' latestDepTestImplementation group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '+' diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/build.gradle b/dd-java-agent/instrumentation/aws-java-sqs-2.0/build.gradle index a292ea34a70..adff4418500 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/build.gradle +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/build.gradle @@ -13,6 +13,8 @@ muzzle { apply from: "$rootDir/gradle/java.gradle" addTestSuiteForDir('latestDepTest', 'test') +addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') + dependencies { compileOnly group: 'software.amazon.awssdk', name: 'sqs', version: '2.2.0' @@ -24,7 +26,7 @@ dependencies { testImplementation project(':dd-java-agent:instrumentation:jms') // SQS<->JMS testing: - testImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.13', version: '1.2.3' + testImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.13', version: '1.4.7' testImplementation group: 'com.amazonaws', name: 'amazon-sqs-java-messaging-lib', version: '2.0.0' latestDepTestImplementation group: 'software.amazon.awssdk', name: 'sqs', version: '2.20.33' diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/LegacySqsClientForkedTest.groovy b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/LegacySqsClientForkedTest.groovy index d2692a61d0c..84ad8f22268 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/LegacySqsClientForkedTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/LegacySqsClientForkedTest.groovy @@ -94,7 +94,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "SendMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -140,7 +140,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "ReceiveMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -222,7 +222,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "SendMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -285,7 +285,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "DeleteMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -330,7 +330,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "ReceiveMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/SqsClientTest.groovy b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/SqsClientTest.groovy index 05b5b5a0971..ad1219478f5 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/SqsClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/SqsClientTest.groovy @@ -394,7 +394,7 @@ abstract class SqsClientTest extends VersionedNamingTestBase { "aws.operation" "DeleteMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/TimeInQueueForkedTest.groovy b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/TimeInQueueForkedTest.groovy index 7c864453979..b19b99c0c82 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/TimeInQueueForkedTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/TimeInQueueForkedTest.groovy @@ -302,7 +302,7 @@ class TimeInQueueForkedTest extends AgentTestRunner { "aws.operation" "SendMessageBatch" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -325,7 +325,7 @@ class TimeInQueueForkedTest extends AgentTestRunner { "aws.operation" "ReceiveMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags(parent.resourceName as String == "Sqs.SendMessageBatch") } } @@ -344,7 +344,7 @@ class TimeInQueueForkedTest extends AgentTestRunner { "$Tags.COMPONENT" "java-aws-sdk" "$Tags.SPAN_KIND" Tags.SPAN_KIND_BROKER "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags(true) } } diff --git a/dd-java-agent/instrumentation/build.gradle b/dd-java-agent/instrumentation/build.gradle index 3847c8b9dd0..1d22aba7ebe 100644 --- a/dd-java-agent/instrumentation/build.gradle +++ b/dd-java-agent/instrumentation/build.gradle @@ -99,7 +99,7 @@ subprojects { Project subProj -> onlyIf { !project.rootProject.hasProperty("skipInstTests") } - if (subTask.name == 'latestDepTest') { + if (subTask.name in ['latestDepTest', 'latestDepForkedTest']) { subTask.jvmArgs '-Dtest.dd.latestDepTest=true' } } diff --git a/dd-java-agent/instrumentation/commons-fileupload/build.gradle b/dd-java-agent/instrumentation/commons-fileupload/build.gradle new file mode 100644 index 00000000000..eadd96edbea --- /dev/null +++ b/dd-java-agent/instrumentation/commons-fileupload/build.gradle @@ -0,0 +1,13 @@ + +apply from: "$rootDir/gradle/java.gradle" +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly group: 'org.apache.commons', name: 'commons-fileupload2', version: '2.0.0-M1' + testImplementation group: 'org.apache.commons', name: 'commons-fileupload2', version: '2.0.0-M1' + testImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '7.0.0' + + + testRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter') + latestDepTestImplementation group: 'org.apache.commons', name: 'commons-fileupload2', version: '+' +} diff --git a/dd-java-agent/instrumentation/commons-fileupload/src/main/java/datadog/trace/instrumentation/commons/fileupload/CommonsFileuploadInstrumenter.java b/dd-java-agent/instrumentation/commons-fileupload/src/main/java/datadog/trace/instrumentation/commons/fileupload/CommonsFileuploadInstrumenter.java new file mode 100644 index 00000000000..8f67ae94cb0 --- /dev/null +++ b/dd-java-agent/instrumentation/commons-fileupload/src/main/java/datadog/trace/instrumentation/commons/fileupload/CommonsFileuploadInstrumenter.java @@ -0,0 +1,65 @@ +package datadog.trace.instrumentation.commons.fileupload; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.iast.IastContext; +import datadog.trace.api.iast.InstrumentationBridge; +import datadog.trace.api.iast.Source; +import datadog.trace.api.iast.SourceTypes; +import datadog.trace.api.iast.propagation.PropagationModule; +import java.util.Map; +import net.bytebuddy.asm.Advice; + +@AutoService(Instrumenter.class) +public class CommonsFileuploadInstrumenter extends Instrumenter.Iast + implements Instrumenter.ForKnownTypes { + + public CommonsFileuploadInstrumenter() { + super("commons-fileupload"); + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice( + isMethod() + .and(named("parse")) + .and(isPublic()) + .and(returns(Map.class)) + .and(takesArguments(char[].class, int.class, int.class, char.class)), + getClass().getName() + "$ParseAdvice"); + } + + @Override + public String[] knownMatchingTypes() { + return new String[] { + "org.apache.commons.fileupload.ParameterParser", + "org.apache.tomcat.util.http.fileupload.ParameterParser" + }; + } + + public static class ParseAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + @Source(SourceTypes.REQUEST_MULTIPART_PARAMETER) + public static Map onExit(@Advice.Return final Map map) { + if (!map.isEmpty()) { + final PropagationModule module = InstrumentationBridge.PROPAGATION; + if (module != null) { + final IastContext ctx = IastContext.Provider.get(); + for (final Map.Entry entry : map.entrySet()) { + if (entry.getValue() != null) { + module.taint( + ctx, entry.getValue(), SourceTypes.REQUEST_MULTIPART_PARAMETER, entry.getKey()); + } + } + } + } + return map; + } + } +} diff --git a/dd-java-agent/instrumentation/commons-fileupload/src/test/groovy/MultipartInstrumentationTest.groovy b/dd-java-agent/instrumentation/commons-fileupload/src/test/groovy/MultipartInstrumentationTest.groovy new file mode 100644 index 00000000000..4e780a02d1a --- /dev/null +++ b/dd-java-agent/instrumentation/commons-fileupload/src/test/groovy/MultipartInstrumentationTest.groovy @@ -0,0 +1,40 @@ +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.SourceTypes +import datadog.trace.api.iast.propagation.PropagationModule + + +class MultipartInstrumentationTest extends AgentTestRunner { + @Override + protected void configurePreAgent() { + injectSysConfig('dd.iast.enabled', 'true') + } + + @Override + void cleanup() { + InstrumentationBridge.clearIastModules() + } + + void 'test commons fileupload2 ParameterParser.parse'() { + given: + final module = Mock(PropagationModule) + InstrumentationBridge.registerIastModule(module) + final content = "Content-Disposition: form-data; name=\"file\"; filename=\"=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=\"\r\n" + final parser = clazz.newInstance() + + when: + parser.parse(content, new char[]{ + ',', ';' + }) + + then: + 1 * module.taint(null, 'file', SourceTypes.REQUEST_MULTIPART_PARAMETER, 'name') + 1 * module.taint(null, _, SourceTypes.REQUEST_MULTIPART_PARAMETER, 'filename') + 0 * _ + + where: + clazz | _ + org.apache.commons.fileupload.ParameterParser | _ + org.apache.tomcat.util.http.fileupload.ParameterParser | _ + } +} diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/CouchbaseClientDecorator.java b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/CouchbaseClientDecorator.java index a1e62890976..c50eceebd82 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/CouchbaseClientDecorator.java +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/CouchbaseClientDecorator.java @@ -2,10 +2,15 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_TYPE; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.normalize.SQLNormalizer; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator; +import java.util.function.Function; +import java.util.function.ToIntFunction; class CouchbaseClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String DB_TYPE = "couchbase"; @@ -16,6 +21,13 @@ class CouchbaseClientDecorator extends DBTypeProcessingDatabaseClientDecorator { public static final CharSequence COUCHBASE_CLIENT = UTF8BytesString.create("couchbase-client"); public static final CouchbaseClientDecorator DECORATE = new CouchbaseClientDecorator(); + private static final Function NORMALIZE = SQLNormalizer::normalize; + private static final int COMBINED_STATEMENT_LIMIT = 2 * 1024 * 1024; // characters + + private static final ToIntFunction STATEMENT_WEIGHER = UTF8BytesString::length; + private static final DDCache CACHED_STATEMENTS = + DDCaches.newFixedSizeWeightedCache(512, STATEMENT_WEIGHER, COMBINED_STATEMENT_LIMIT); + @Override protected String[] instrumentationNames() { return new String[] {"couchbase"}; @@ -55,4 +67,8 @@ protected String dbInstance(final Object o) { protected String dbHostname(Object o) { return null; } + + protected static UTF8BytesString normalizedQuery(String sql) { + return CACHED_STATEMENTS.computeIfAbsent(sql, NORMALIZE); + } } diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/DatadogRequestSpan.java b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/DatadogRequestSpan.java index bf36d0b4ee5..0e4af4d1373 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/DatadogRequestSpan.java +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/DatadogRequestSpan.java @@ -64,7 +64,11 @@ public void setAttribute(String key, String value) { // TODO when `db.statement` is set here it will be intercepted by the TagInterceptor, so any // sort of obfuscation should go in there, preferably as a lazy sort of Utf8String that does // the actual work at the end - span.setTag(key, value); + if ("db.statement".equals(key)) { + span.setTag(key, CouchbaseClientDecorator.normalizedQuery(value)); + } else { + span.setTag(key, value); + } } // This method shows up in later versions diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/test/groovy/CouchbaseClient31Test.groovy b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/test/groovy/CouchbaseClient31Test.groovy index afdc418332a..20ce3f32219 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/test/groovy/CouchbaseClient31Test.groovy +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/test/groovy/CouchbaseClient31Test.groovy @@ -139,7 +139,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], 'select * from `test-bucket` limit 1') + ], 'select * from `test-bucket` limit ?') assertCouchbaseDispatchCall(it, span(0)) } } @@ -148,6 +148,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check query spans with parent"() { setup: def query = 'select * from `test-bucket` limit 1' + def normalizedQuery = 'select * from `test-bucket` limit ?' when: runUnderTrace('query.parent') { @@ -163,7 +164,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false) + ], normalizedQuery, span(0), false) assertCouchbaseDispatchCall(it, span(1)) } } @@ -171,6 +172,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check query spans with parent and adhoc #adhoc"() { def query = 'select count(1) from `test-bucket` where (`something` = "else") limit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "else") limit ?' int count = 0 when: @@ -192,12 +194,12 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false) + ], normalizedQuery, span(0), false) if (!adhoc) { assertCouchbaseCall(it, "prepare", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], "PREPARE $query", span(1), true) + ], "PREPARE $normalizedQuery", span(1), true) } assertCouchbaseDispatchCall(it, span(adhoc ? 1 : 2)) } @@ -209,6 +211,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check multiple query spans with parent and adhoc false"() { def query = 'select count(1) from `test-bucket` where (`something` = "wonderful") limit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "wonderful") limit ?' int count1 = 0 int count2 = 0 @@ -237,20 +240,20 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false) + ], normalizedQuery, span(0), false) assertCouchbaseCall(it, "prepare", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], "PREPARE $query", span(1), true) + ], "PREPARE $normalizedQuery", span(1), true) assertCouchbaseDispatchCall(it, span(2)) assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false) + ], normalizedQuery, span(0), false) assertCouchbaseCall(it, "execute", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(4), true) + ], normalizedQuery, span(4), true) assertCouchbaseDispatchCall(it, span(5)) } } @@ -259,6 +262,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check error query spans with parent"() { setup: def query = 'select * from `test-bucket` limeit 1' + def normalizedQuery = 'select * from `test-bucket` limeit ?' Throwable ex = null when: @@ -280,7 +284,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false, ex) + ], normalizedQuery, span(0), false, ex) assertCouchbaseDispatchCall(it, span(1)) } } @@ -288,6 +292,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check multiple error query spans with parent and adhoc false"() { def query = 'select count(1) from `test-bucket` where (`something` = "wonderful") limeit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "wonderful") limeit ?' int count1 = 0 int count2 = 0 Throwable ex1 = null @@ -328,20 +333,20 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false, ex1) + ], normalizedQuery, span(0), false, ex1) assertCouchbaseCall(it, "prepare", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], "PREPARE $query", span(1), true, ex1) + ], "PREPARE $normalizedQuery", span(1), true, ex1) assertCouchbaseDispatchCall(it, span(2)) assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false, ex2) + ], normalizedQuery, span(0), false, ex2) assertCouchbaseCall(it, "prepare", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], "PREPARE $query", span(4), true, ex2) + ], "PREPARE $normalizedQuery", span(4), true, ex2) assertCouchbaseDispatchCall(it, span(5)) } } diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/CouchbaseClientDecorator.java b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/CouchbaseClientDecorator.java index 2b84ad23c07..cad336cc2de 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/CouchbaseClientDecorator.java +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/CouchbaseClientDecorator.java @@ -2,10 +2,15 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_TYPE; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.normalize.SQLNormalizer; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator; +import java.util.function.Function; +import java.util.function.ToIntFunction; class CouchbaseClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String DB_TYPE = "couchbase"; @@ -16,6 +21,13 @@ class CouchbaseClientDecorator extends DBTypeProcessingDatabaseClientDecorator { public static final CharSequence COUCHBASE_CLIENT = UTF8BytesString.create("couchbase-client"); public static final CouchbaseClientDecorator DECORATE = new CouchbaseClientDecorator(); + private static final Function NORMALIZE = SQLNormalizer::normalize; + private static final int COMBINED_STATEMENT_LIMIT = 2 * 1024 * 1024; // characters + + private static final ToIntFunction STATEMENT_WEIGHER = UTF8BytesString::length; + private static final DDCache CACHED_STATEMENTS = + DDCaches.newFixedSizeWeightedCache(512, STATEMENT_WEIGHER, COMBINED_STATEMENT_LIMIT); + @Override protected String[] instrumentationNames() { return new String[] {"couchbase"}; @@ -55,4 +67,8 @@ protected String dbInstance(final Object o) { protected String dbHostname(Object o) { return null; } + + protected static UTF8BytesString normalizedQuery(String sql) { + return CACHED_STATEMENTS.computeIfAbsent(sql, NORMALIZE); + } } diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/DatadogRequestSpan.java b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/DatadogRequestSpan.java index ac3b0e20b40..11d452bf45d 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/DatadogRequestSpan.java +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/DatadogRequestSpan.java @@ -77,7 +77,11 @@ public void attribute(String key, String value) { // TODO when `db.statement` is set here it will be intercepted by the TagInterceptor, so any // sort of obfuscation should go in there, preferably as a lazy sort of Utf8String that does // the actual work at the end - span.setTag(key, value); + if ("db.statement".equals(key)) { + span.setTag(key, CouchbaseClientDecorator.normalizedQuery(value)); + } else { + span.setTag(key, value); + } } @Override diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/test/groovy/CouchbaseClient32Test.groovy b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/test/groovy/CouchbaseClient32Test.groovy index 7fb5feba1fd..d692cbe2077 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/test/groovy/CouchbaseClient32Test.groovy +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/test/groovy/CouchbaseClient32Test.groovy @@ -149,7 +149,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { assertTraces(1) { sortSpansByStart() trace(2) { - assertCouchbaseCall(it, 'select * from `test-bucket` limit 1', [ + assertCouchbaseCall(it, 'select * from `test-bucket` limit ?', [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ]) @@ -161,7 +161,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check query spans with parent"() { setup: def query = 'select * from `test-bucket` limit 1' - + def normalizedQuery = 'select * from `test-bucket` limit ?' when: runUnderTrace('query.parent') { cluster.query(query) @@ -172,7 +172,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(3) { basicSpan(it, 'query.parent') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0)) @@ -184,6 +184,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check async query spans with parent and adhoc #adhoc"() { setup: def query = 'select count(1) from `test-bucket` where (`something` = "else") limit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "else") limit ?' int count = 0 when: @@ -202,12 +203,12 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(adhoc ? 3 : 4) { basicSpan(it, 'async.parent') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0)) if (!adhoc) { - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(1), true) @@ -223,6 +224,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check multiple async query spans with parent and adhoc false"() { setup: def query = 'select count(1) from `test-bucket` where (`something` = "wonderful") limit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "wonderful") limit ?' int count1 = 0 int count2 = 0 def extraPrepare = isLatestDepTest @@ -249,26 +251,26 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(extraPrepare ? 8 : 7) { basicSpan(it, 'async.multiple') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0)) - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(1), true) assertCouchbaseDispatchCall(it, span(2)) - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0)) if (extraPrepare) { - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(4), true) } - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(4), true) @@ -280,12 +282,13 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check error query spans with parent"() { setup: def query = 'select * from `test-bucket` limeit 1' + def normalizedQuery = "select * from `test-bucket` limeit ?" Throwable ex = null when: runUnderTrace('query.failure') { try { - cluster.query('select * from `test-bucket` limeit 1') + cluster.query(query) } catch (ParsingFailureException expected) { ex = expected } @@ -297,7 +300,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(3) { basicSpan(it, 'query.failure') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', 'db.system' : 'couchbase', @@ -310,6 +313,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check multiple async error query spans with parent and adhoc false"() { setup: def query = 'select count(1) from `test-bucket` where (`something` = "wonderful") limeit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "wonderful") limeit ?' int count1 = 0 int count2 = 0 Throwable ex1 = null @@ -347,20 +351,20 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(7) { basicSpan(it, 'async.failure') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0), false, ex1) - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(1), true, ex1) assertCouchbaseDispatchCall(it, span(2)) - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0), false, ex2) - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(4), true, ex2) diff --git a/dd-java-agent/instrumentation/datastax-cassandra-3.8/src/test/groovy/CassandraClientTest.groovy b/dd-java-agent/instrumentation/datastax-cassandra-3.8/src/test/groovy/CassandraClientTest.groovy index 8775d27baec..946e1b762a5 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-3.8/src/test/groovy/CassandraClientTest.groovy +++ b/dd-java-agent/instrumentation/datastax-cassandra-3.8/src/test/groovy/CassandraClientTest.groovy @@ -134,11 +134,15 @@ abstract class CassandraClientTest extends VersionedNamingTestBase { "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "async_test" | true } + String normalize(String statement){ + return statement.replaceAll("'alice'", "?") + } + def cassandraSpan(TraceAssert trace, String statement, String keyspace, boolean renameService, Object parentSpan = null, Throwable exception = null) { trace.span { serviceName renameService && keyspace ? keyspace : service() operationName operation() - resourceName statement + resourceName normalize(statement) spanType DDSpanTypes.CASSANDRA if (parentSpan == null) { parent() diff --git a/dd-java-agent/instrumentation/datastax-cassandra-3/src/main/java/datadog/trace/instrumentation/datastax/cassandra/CassandraClientDecorator.java b/dd-java-agent/instrumentation/datastax-cassandra-3/src/main/java/datadog/trace/instrumentation/datastax/cassandra/CassandraClientDecorator.java index 28d5b32eddf..ffe7c81fb25 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-3/src/main/java/datadog/trace/instrumentation/datastax/cassandra/CassandraClientDecorator.java +++ b/dd-java-agent/instrumentation/datastax-cassandra-3/src/main/java/datadog/trace/instrumentation/datastax/cassandra/CassandraClientDecorator.java @@ -3,11 +3,15 @@ import com.datastax.driver.core.Host; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Session; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.normalize.SQLNormalizer; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator; +import java.util.function.ToIntFunction; public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String DB_TYPE = "cassandra"; @@ -19,6 +23,15 @@ public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDeco public static final CassandraClientDecorator DECORATE = new CassandraClientDecorator(); + private static final int COMBINED_STATEMENT_LIMIT = 2 * 1024 * 1024; // chars + private static final ToIntFunction STATEMENT_WEIGHER = UTF8BytesString::length; + private static final DDCache CACHED_STATEMENTS = + DDCaches.newFixedSizeWeightedCache(512, STATEMENT_WEIGHER, COMBINED_STATEMENT_LIMIT); + + protected static UTF8BytesString normalizedQuery(CharSequence sql) { + return CACHED_STATEMENTS.computeIfAbsent(sql, SQLNormalizer::normalizeCharSequence); + } + @Override protected String[] instrumentationNames() { return new String[] {"cassandra"}; @@ -60,6 +73,11 @@ protected String dbHostname(Session session) { return null; } + public AgentSpan onStatement(final AgentSpan span, final CharSequence statement) { + span.setResourceName(normalizedQuery(statement)); + return span; + } + public AgentSpan onResponse(final AgentSpan span, final ResultSet result) { if (result != null) { final Host host = result.getExecutionInfo().getQueriedHost(); diff --git a/dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy b/dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy index 8775d27baec..946e1b762a5 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy +++ b/dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy @@ -134,11 +134,15 @@ abstract class CassandraClientTest extends VersionedNamingTestBase { "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "async_test" | true } + String normalize(String statement){ + return statement.replaceAll("'alice'", "?") + } + def cassandraSpan(TraceAssert trace, String statement, String keyspace, boolean renameService, Object parentSpan = null, Throwable exception = null) { trace.span { serviceName renameService && keyspace ? keyspace : service() operationName operation() - resourceName statement + resourceName normalize(statement) spanType DDSpanTypes.CASSANDRA if (parentSpan == null) { parent() diff --git a/dd-java-agent/instrumentation/datastax-cassandra-4/src/main/java/datadog/trace/instrumentation/datastax/cassandra4/CassandraClientDecorator.java b/dd-java-agent/instrumentation/datastax-cassandra-4/src/main/java/datadog/trace/instrumentation/datastax/cassandra4/CassandraClientDecorator.java index 98817ca2b5d..dc1cb7d5b31 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-4/src/main/java/datadog/trace/instrumentation/datastax/cassandra4/CassandraClientDecorator.java +++ b/dd-java-agent/instrumentation/datastax-cassandra-4/src/main/java/datadog/trace/instrumentation/datastax/cassandra4/CassandraClientDecorator.java @@ -5,7 +5,10 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.session.Session; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.normalize.SQLNormalizer; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; @@ -13,6 +16,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Objects; +import java.util.function.ToIntFunction; public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String DB_TYPE = "cassandra"; @@ -24,6 +28,15 @@ public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDeco public static final CassandraClientDecorator DECORATE = new CassandraClientDecorator(); + private static final int COMBINED_STATEMENT_LIMIT = 2 * 1024 * 1024; // chars + private static final ToIntFunction STATEMENT_WEIGHER = UTF8BytesString::length; + private static final DDCache CACHED_STATEMENTS = + DDCaches.newFixedSizeWeightedCache(512, STATEMENT_WEIGHER, COMBINED_STATEMENT_LIMIT); + + protected static UTF8BytesString normalizedQuery(CharSequence sql) { + return CACHED_STATEMENTS.computeIfAbsent(sql, SQLNormalizer::normalizeCharSequence); + } + @Override protected String[] instrumentationNames() { return new String[] {"cassandra"}; @@ -64,6 +77,11 @@ protected String dbHostname(Session session) { return null; } + public AgentSpan onStatement(final AgentSpan span, final CharSequence statement) { + span.setResourceName(normalizedQuery(statement)); + return span; + } + public AgentSpan onResponse(final AgentSpan span, final ResultSet result) { if (result != null) { return onResponse(span, result.getExecutionInfo().getCoordinator()); diff --git a/dd-java-agent/instrumentation/datastax-cassandra-4/src/test/groovy/CassandraClientTest.groovy b/dd-java-agent/instrumentation/datastax-cassandra-4/src/test/groovy/CassandraClientTest.groovy index bdafd0f4a1f..21bb4bf3ff3 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-4/src/test/groovy/CassandraClientTest.groovy +++ b/dd-java-agent/instrumentation/datastax-cassandra-4/src/test/groovy/CassandraClientTest.groovy @@ -210,11 +210,15 @@ abstract class CassandraClientTest extends VersionedNamingTestBase { .withConfigLoader(configLoader) } + String normalize(String statement){ + return statement.replaceAll("'alice'", "?") + } + def cassandraSpan(TraceAssert trace, String statement, String keyspace, boolean renameService, Object parentSpan = null, Throwable throwable = null) { trace.span { serviceName renameService && keyspace ? keyspace : service() operationName operation() - resourceName statement + resourceName normalize(statement) spanType DDSpanTypes.CASSANDRA if (parentSpan == null) { parent() diff --git a/dd-java-agent/instrumentation/google-pubsub/build.gradle b/dd-java-agent/instrumentation/google-pubsub/build.gradle new file mode 100644 index 00000000000..386a943bff8 --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/build.gradle @@ -0,0 +1,26 @@ +muzzle { + pass { + group = "com.google.cloud" + module = "google-cloud-pubsub" + versions = "[1.116.0,)" + excludeDependency "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava" + } +} +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') +addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') + + +dependencies { + compileOnly group: 'com.google.cloud', name: 'google-cloud-pubsub', version: '1.116.0' + testImplementation "org.testcontainers:gcloud:${versions.testcontainers}" + testImplementation group: 'com.google.cloud', name: 'google-cloud-pubsub', version: '1.116.0' + testImplementation project(":dd-java-agent:instrumentation:grpc-1.5") + testImplementation project(":dd-java-agent:instrumentation:guava-10") + latestDepTestImplementation group: 'com.google.cloud', name: 'google-cloud-pubsub', version: '+' +} + +tasks.withType(Test).configureEach { + usesService(testcontainersLimit) +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/MessageReceiverWithAckResponseWrapper.java b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/MessageReceiverWithAckResponseWrapper.java new file mode 100644 index 00000000000..aa6ba981dd4 --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/MessageReceiverWithAckResponseWrapper.java @@ -0,0 +1,32 @@ +package datadog.trace.instrumentation.googlepubsub; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.instrumentation.googlepubsub.PubSubDecorator.CONSUMER_DECORATE; + +import com.google.cloud.pubsub.v1.AckReplyConsumerWithResponse; +import com.google.cloud.pubsub.v1.MessageReceiverWithAckResponse; +import com.google.pubsub.v1.PubsubMessage; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; + +public class MessageReceiverWithAckResponseWrapper implements MessageReceiverWithAckResponse { + private final String subscription; + private final MessageReceiverWithAckResponse delegate; + + public MessageReceiverWithAckResponseWrapper( + String subscription, MessageReceiverWithAckResponse delegate) { + this.subscription = subscription; + this.delegate = delegate; + } + + @Override + public void receiveMessage(PubsubMessage message, AckReplyConsumerWithResponse consumer) { + final AgentSpan span = CONSUMER_DECORATE.onConsume(message, subscription); + try (final AgentScope scope = activateSpan(span)) { + this.delegate.receiveMessage(message, consumer); + } finally { + CONSUMER_DECORATE.beforeFinish(span); + span.finish(); + } + } +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/MessageReceiverWrapper.java b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/MessageReceiverWrapper.java new file mode 100644 index 00000000000..fc87a410a08 --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/MessageReceiverWrapper.java @@ -0,0 +1,31 @@ +package datadog.trace.instrumentation.googlepubsub; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.instrumentation.googlepubsub.PubSubDecorator.CONSUMER_DECORATE; + +import com.google.cloud.pubsub.v1.AckReplyConsumer; +import com.google.cloud.pubsub.v1.MessageReceiver; +import com.google.pubsub.v1.PubsubMessage; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; + +public final class MessageReceiverWrapper implements MessageReceiver { + private final String subscription; + private final MessageReceiver delegate; + + public MessageReceiverWrapper(String subscription, MessageReceiver delegate) { + this.subscription = subscription; + this.delegate = delegate; + } + + @Override + public void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) { + final AgentSpan span = CONSUMER_DECORATE.onConsume(message, subscription); + try (final AgentScope scope = activateSpan(span)) { + this.delegate.receiveMessage(message, consumer); + } finally { + CONSUMER_DECORATE.beforeFinish(span); + span.finish(); + } + } +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/PubSubDecorator.java b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/PubSubDecorator.java new file mode 100644 index 00000000000..b4165c68838 --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/PubSubDecorator.java @@ -0,0 +1,156 @@ +package datadog.trace.instrumentation.googlepubsub; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.core.datastreams.TagsProcessor.DIRECTION_IN; +import static datadog.trace.core.datastreams.TagsProcessor.DIRECTION_TAG; +import static datadog.trace.core.datastreams.TagsProcessor.SUBSCRIPTION_TAG; +import static datadog.trace.core.datastreams.TagsProcessor.TYPE_TAG; + +import com.google.protobuf.Timestamp; +import com.google.pubsub.v1.PubsubMessage; +import datadog.trace.api.Functions; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.MessagingClientDecorator; +import java.util.LinkedHashMap; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PubSubDecorator extends MessagingClientDecorator { + private static class RegexExtractor implements Function { + private final Pattern pattern; + private final int group; + + public RegexExtractor(final String regex, final int group) { + pattern = Pattern.compile(regex); + this.group = group; + } + + @Override + public CharSequence apply(CharSequence input) { + final Matcher matcher = pattern.matcher(input); + if (matcher.matches() && group <= matcher.groupCount()) { + return matcher.group(group); + } + return input; + } + } + + private static final String PUBSUB = "google-pubsub"; + public static final CharSequence JAVA_PUBSUB = UTF8BytesString.create("java-google-pubsub"); + public static final CharSequence PUBSUB_CONSUME = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().messaging().inboundOperation(PUBSUB)); + public static final CharSequence PUBSUB_PRODUCE = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().messaging().outboundOperation(PUBSUB)); + + private static final DDCache TOPIC_NAME_CACHE = + DDCaches.newFixedSizeCache(32); + private static final DDCache SUBSCRIPTION_NAME_CACHE = + DDCaches.newFixedSizeCache(32); + + private static final DDCache PRODUCER_RESOURCE_NAME_CACHE = + DDCaches.newFixedSizeCache(32); + private static final DDCache CONSUMER_RESOURCE_NAME_CACHE = + DDCaches.newFixedSizeCache(32); + private static final Functions.Prefix PRODUCER_PREFIX = new Functions.Prefix("Produce Topic "); + private static final Functions.Prefix CONSUMER_PREFIX = + new Functions.Prefix("Consume Subscription "); + + private static final Function TOPIC_EXTRACTION_FUNCTION = + new RegexExtractor("^projects/(.+)/topics/(.+)$", 2).andThen(UTF8BytesString::create); + private static final Function SUBSCRIPTION_EXTRACTION_FUNCTION = + new RegexExtractor("^projects/(.+)/subscriptions/(.+)$", 2).andThen(UTF8BytesString::create); + + public static final PubSubDecorator PRODUCER_DECORATE = + new PubSubDecorator( + Tags.SPAN_KIND_PRODUCER, + InternalSpanTypes.MESSAGE_PRODUCER, + SpanNaming.instance().namingSchema().messaging().outboundService(PUBSUB, true)); + + public static final PubSubDecorator CONSUMER_DECORATE = + new PubSubDecorator( + Tags.SPAN_KIND_CONSUMER, + InternalSpanTypes.MESSAGE_CONSUMER, + SpanNaming.instance().namingSchema().messaging().inboundService(PUBSUB, true)); + private final String spanKind; + private final CharSequence spanType; + private final String serviceName; + + protected PubSubDecorator(String spanKind, CharSequence spanType, String serviceName) { + this.spanKind = spanKind; + this.spanType = spanType; + this.serviceName = serviceName; + } + + @Override + protected CharSequence spanType() { + return spanType; + } + + @Override + protected String[] instrumentationNames() { + return new String[] {"google-pubsub"}; + } + + @Override + protected String service() { + return serviceName; + } + + @Override + protected CharSequence component() { + return JAVA_PUBSUB; + } + + @Override + protected String spanKind() { + return spanKind; + } + + public AgentSpan onConsume(final PubsubMessage message, final String subscription) { + final AgentSpan.Context spanContext = + propagate().extract(message, TextMapExtractAdapter.GETTER); + final AgentSpan span = startSpan(PUBSUB_CONSUME, spanContext); + final CharSequence parsedSubscription = extractSubscription(subscription); + final LinkedHashMap sortedTags = new LinkedHashMap<>(3); + sortedTags.put(DIRECTION_TAG, DIRECTION_IN); + sortedTags.put(SUBSCRIPTION_TAG, parsedSubscription.toString()); + sortedTags.put(TYPE_TAG, "google-pubsub"); + final Timestamp publishTime = message.getPublishTime(); + // FIXME: use full nanosecond resolution when this method will accept nanos + AgentTracer.get() + .getDataStreamsMonitoring() + .setCheckpoint( + span, + sortedTags, + publishTime.getSeconds() * 1_000 + publishTime.getNanos() / (int) 1e6, + message.getSerializedSize()); + afterStart(span); + span.setResourceName( + CONSUMER_RESOURCE_NAME_CACHE.computeIfAbsent(parsedSubscription, CONSUMER_PREFIX)); + return span; + } + + public void onProduce(final AgentSpan span, final CharSequence topic) { + span.setResourceName(PRODUCER_RESOURCE_NAME_CACHE.computeIfAbsent(topic, PRODUCER_PREFIX)); + } + + public CharSequence extractTopic(String fullTopic) { + return TOPIC_NAME_CACHE.computeIfAbsent(fullTopic, TOPIC_EXTRACTION_FUNCTION); + } + + public CharSequence extractSubscription(String fullSubscription) { + return SUBSCRIPTION_NAME_CACHE.computeIfAbsent( + fullSubscription, SUBSCRIPTION_EXTRACTION_FUNCTION); + } +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/PublisherInstrumentation.java b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/PublisherInstrumentation.java new file mode 100644 index 00000000000..27caaa2244d --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/PublisherInstrumentation.java @@ -0,0 +1,102 @@ +package datadog.trace.instrumentation.googlepubsub; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter.ExcludeType.RUNNABLE; +import static datadog.trace.core.datastreams.TagsProcessor.DIRECTION_OUT; +import static datadog.trace.core.datastreams.TagsProcessor.DIRECTION_TAG; +import static datadog.trace.core.datastreams.TagsProcessor.TOPIC_TAG; +import static datadog.trace.core.datastreams.TagsProcessor.TYPE_TAG; +import static datadog.trace.instrumentation.googlepubsub.PubSubDecorator.PRODUCER_DECORATE; +import static datadog.trace.instrumentation.googlepubsub.PubSubDecorator.PUBSUB_PRODUCE; +import static datadog.trace.instrumentation.googlepubsub.TextMapInjectAdapter.SETTER; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; + +import com.google.auto.service.AutoService; +import com.google.cloud.pubsub.v1.Publisher; +import com.google.pubsub.v1.PubsubMessage; +import datadog.trace.agent.tooling.ExcludeFilterProvider; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import net.bytebuddy.asm.Advice; + +@AutoService(Instrumenter.class) +public final class PublisherInstrumentation extends Instrumenter.Tracing + implements Instrumenter.ForSingleType, ExcludeFilterProvider { + + public PublisherInstrumentation() { + super("google-pubsub", "google-pubsub-publisher"); + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".PubSubDecorator", + packageName + ".PubSubDecorator$RegexExtractor", + packageName + ".TextMapInjectAdapter", + packageName + ".TextMapExtractAdapter", + }; + } + + @Override + public Map> excludedClasses() { + return singletonMap(RUNNABLE, singletonList("com.google.api.gax.rpc.Watchdog")); + } + + @Override + public String instrumentedType() { + return "com.google.cloud.pubsub.v1.Publisher"; + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice(isMethod().and(named("publish")), getClass().getName() + "$Wrap"); + } + + public static final class Wrap { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope before( + @Advice.Argument(value = 0, readOnly = false) PubsubMessage msg, + @Advice.This Publisher publisher) { + final AgentSpan span = startSpan(PUBSUB_PRODUCE); + + final CharSequence topicName = PRODUCER_DECORATE.extractTopic(publisher.getTopicNameString()); + PRODUCER_DECORATE.afterStart(span); + PRODUCER_DECORATE.onProduce(span, topicName); + + LinkedHashMap sortedTags = new LinkedHashMap<>(3); + sortedTags.put(DIRECTION_TAG, DIRECTION_OUT); + sortedTags.put(TOPIC_TAG, topicName.toString()); + sortedTags.put(TYPE_TAG, "google-pubsub"); + + PubsubMessage.Builder builder = msg.toBuilder(); + propagate().inject(span, builder, SETTER); + propagate().injectPathwayContext(span, builder, SETTER, sortedTags); + msg = builder.build(); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.span().finish(); + scope.close(); + } + } +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/ReceiverInstrumentation.java b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/ReceiverInstrumentation.java new file mode 100644 index 00000000000..5f5d2f720cc --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/ReceiverInstrumentation.java @@ -0,0 +1,59 @@ +package datadog.trace.instrumentation.googlepubsub; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import com.google.cloud.pubsub.v1.MessageReceiver; +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.asm.Advice; + +@AutoService(Instrumenter.class) +public class ReceiverInstrumentation extends Instrumenter.Tracing + implements Instrumenter.ForSingleType { + + public ReceiverInstrumentation() { + super("google-pubsub", "google-pubsub-receiver"); + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".PubSubDecorator", + packageName + ".PubSubDecorator$RegexExtractor", + packageName + ".TextMapInjectAdapter", + packageName + ".TextMapExtractAdapter", + packageName + ".MessageReceiverWrapper", + }; + } + + @Override + public String instrumentedType() { + return "com.google.cloud.pubsub.v1.Subscriber"; + } + + @Override + public void adviceTransformations(Instrumenter.AdviceTransformation transformation) { + transformation.applyAdvice( + isMethod() + .and(named("newBuilder")) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("com.google.cloud.pubsub.v1.MessageReceiver"))), + getClass().getName() + "$Wrap"); + } + + public static final class Wrap { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void before( + @Advice.Argument(value = 0) final String subscription, + @Advice.Argument(value = 1, readOnly = false) MessageReceiver receiver) { + receiver = new MessageReceiverWrapper(subscription, receiver); + } + } +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/ReceiverWithAckInstrumentation.java b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/ReceiverWithAckInstrumentation.java new file mode 100644 index 00000000000..4c448d43f30 --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/ReceiverWithAckInstrumentation.java @@ -0,0 +1,60 @@ +package datadog.trace.instrumentation.googlepubsub; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameEndsWith; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import com.google.cloud.pubsub.v1.MessageReceiverWithAckResponse; +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.asm.Advice; + +@AutoService(Instrumenter.class) +public final class ReceiverWithAckInstrumentation extends Instrumenter.Tracing + implements Instrumenter.ForSingleType { + + public ReceiverWithAckInstrumentation() { + super("google-pubsub", "google-pubsub-receiver"); + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".PubSubDecorator", + packageName + ".PubSubDecorator$RegexExtractor", + packageName + ".TextMapInjectAdapter", + packageName + ".TextMapExtractAdapter", + packageName + ".MessageReceiverWithAckResponseWrapper", + }; + } + + @Override + public String instrumentedType() { + return "com.google.cloud.pubsub.v1.Subscriber"; + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice( + isMethod() + .and(named("newBuilder")) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, nameEndsWith("MessageReceiverWithAckResponse"))), + getClass().getName() + "$Wrap"); + } + + public static final class Wrap { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void before( + @Advice.Argument(value = 0) final String subscription, + @Advice.Argument(value = 1, readOnly = false) MessageReceiverWithAckResponse receiver) { + receiver = new MessageReceiverWithAckResponseWrapper(subscription, receiver); + } + } +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/TextMapExtractAdapter.java b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/TextMapExtractAdapter.java new file mode 100644 index 00000000000..d84d0176c80 --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/TextMapExtractAdapter.java @@ -0,0 +1,36 @@ +package datadog.trace.instrumentation.googlepubsub; + +import com.google.pubsub.v1.PubsubMessage; +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import java.util.Map; + +public class TextMapExtractAdapter + implements AgentPropagation.ContextVisitor, + AgentPropagation.BinaryContextVisitor { + + public static final TextMapExtractAdapter GETTER = new TextMapExtractAdapter(); + + @Override + public void forEachKey(PubsubMessage carrier, AgentPropagation.KeyClassifier classifier) { + for (Map.Entry kv : carrier.getAttributesMap().entrySet()) { + String value = kv.getValue(); + if (null != value) { + if (!classifier.accept(kv.getKey(), value)) { + return; + } + } + } + } + + @Override + public void forEachKey(PubsubMessage carrier, AgentPropagation.BinaryKeyClassifier classifier) { + for (Map.Entry kv : carrier.getAttributesMap().entrySet()) { + String value = kv.getValue(); + if (null != value) { + if (!classifier.accept(kv.getKey(), value.getBytes())) { + return; + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/TextMapInjectAdapter.java b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/TextMapInjectAdapter.java new file mode 100644 index 00000000000..defb1513bbe --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/main/java/datadog/trace/instrumentation/googlepubsub/TextMapInjectAdapter.java @@ -0,0 +1,22 @@ +package datadog.trace.instrumentation.googlepubsub; + +import com.google.pubsub.v1.PubsubMessage; +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import java.nio.charset.StandardCharsets; + +public class TextMapInjectAdapter + implements AgentPropagation.Setter, + AgentPropagation.BinarySetter { + + public static final TextMapInjectAdapter SETTER = new TextMapInjectAdapter(); + + @Override + public void set(final PubsubMessage.Builder msg, final String key, final String value) { + msg.putAttributes(key, value); + } + + @Override + public void set(PubsubMessage.Builder msg, String key, byte[] value) { + msg.putAttributes(key, new String(value, StandardCharsets.UTF_8)); + } +} diff --git a/dd-java-agent/instrumentation/google-pubsub/src/test/groovy/PubSubTest.groovy b/dd-java-agent/instrumentation/google-pubsub/src/test/groovy/PubSubTest.groovy new file mode 100644 index 00000000000..44fec8874fd --- /dev/null +++ b/dd-java-agent/instrumentation/google-pubsub/src/test/groovy/PubSubTest.groovy @@ -0,0 +1,379 @@ +import static datadog.trace.agent.test.utils.TraceUtils.basicSpan + +import com.google.api.gax.core.NoCredentialsProvider +import com.google.api.gax.grpc.GrpcTransportChannel +import com.google.api.gax.rpc.FixedTransportChannelProvider +import com.google.api.gax.rpc.TransportChannelProvider +import com.google.cloud.pubsub.v1.AckReplyConsumer +import com.google.cloud.pubsub.v1.AckReplyConsumerWithResponse +import com.google.cloud.pubsub.v1.MessageReceiver +import com.google.cloud.pubsub.v1.MessageReceiverWithAckResponse +import com.google.cloud.pubsub.v1.Publisher +import com.google.cloud.pubsub.v1.Subscriber +import com.google.cloud.pubsub.v1.SubscriptionAdminClient +import com.google.cloud.pubsub.v1.SubscriptionAdminSettings +import com.google.cloud.pubsub.v1.TopicAdminClient +import com.google.cloud.pubsub.v1.TopicAdminSettings +import com.google.protobuf.ByteString +import com.google.pubsub.v1.PubsubMessage +import com.google.pubsub.v1.PushConfig +import com.google.pubsub.v1.SubscriptionName +import com.google.pubsub.v1.TopicName +import datadog.trace.agent.test.asserts.TraceAssert +import datadog.trace.agent.test.naming.VersionedNamingTestBase +import datadog.trace.agent.test.utils.TraceUtils +import datadog.trace.api.DDSpanTypes +import datadog.trace.api.DDTags +import datadog.trace.api.config.GeneralConfig +import datadog.trace.api.config.TraceInstrumentationConfig +import datadog.trace.bootstrap.instrumentation.api.Tags +import datadog.trace.core.DDSpan +import datadog.trace.core.datastreams.StatsGroup +import datadog.trace.instrumentation.grpc.client.GrpcClientDecorator +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder +import org.testcontainers.containers.PubSubEmulatorContainer +import org.testcontainers.utility.DockerImageName +import spock.lang.Shared + +import java.nio.charset.StandardCharsets +import java.util.concurrent.CountDownLatch +import java.util.function.Function +import java.util.function.ToDoubleFunction +import java.util.function.ToIntFunction +import java.util.function.ToLongFunction + +abstract class PubSubTest extends VersionedNamingTestBase { + private static final String PROJECT_ID = "dd-trace-java" + + private static final String TOPIC_ID = "test-topic" + + private static final String SUBSCRIPTION_ID = "my-subscription" + + @Shared + PubSubEmulatorContainer emulator + + @Shared + ManagedChannel channel + + @Shared + TransportChannelProvider transportChannelProvider + + @Shared + def noCredentialsProvider = NoCredentialsProvider.create() + + @Shared + String subscriptionName + + @Override + String operation() { + //specialized methods below + null + } + + @Override + boolean useStrictTraceWrites() { + false + } + + boolean shadowGrpcSpans() { + true + } + + abstract String operationForConsumer() + + abstract String operationForProducer() + + Object createMessageReceiver(final CountDownLatch latch) { + return new MessageReceiver() { + @Override + void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) { + consumer.ack() + latch.countDown() + } + } + } + + def setupSpec() { + emulator = new PubSubEmulatorContainer(DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:emulators")) + emulator.start() + channel = ManagedChannelBuilder.forTarget(emulator.getEmulatorEndpoint()).usePlaintext().build() + transportChannelProvider = FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel)) + createTopic(TOPIC_ID) + subscriptionName = createSubscription(SUBSCRIPTION_ID, TOPIC_ID).getName() + } + + def cleanupSpec() { + channel.shutdown() + emulator.stop() + } + + def createTopic(String topicId) throws IOException { + TopicAdminSettings topicAdminSettings = TopicAdminSettings + .newBuilder() + .setTransportChannelProvider(transportChannelProvider) + .setCredentialsProvider(noCredentialsProvider) + .build() + try (TopicAdminClient topicAdminClient = TopicAdminClient.create(topicAdminSettings)) { + TopicName topicName = TopicName.of(PROJECT_ID, topicId) + topicAdminClient.createTopic(topicName) + } + } + + def createSubscription(String subscriptionId, String topicId) throws IOException { + SubscriptionAdminSettings subscriptionAdminSettings = SubscriptionAdminSettings + .newBuilder() + .setTransportChannelProvider(transportChannelProvider) + .setCredentialsProvider(noCredentialsProvider) + .build() + try (final SubscriptionAdminClient subscriptionAdminClient = SubscriptionAdminClient.create(subscriptionAdminSettings)) { + SubscriptionName subscriptionName = SubscriptionName.of(PROJECT_ID, subscriptionId) + subscriptionAdminClient.createSubscription(subscriptionName, TopicName.of(PROJECT_ID, topicId), PushConfig.getDefaultInstance(), 100) + } + } + + @Override + protected void configurePreAgent() { + super.configurePreAgent() + injectSysConfig("integration.google-pubsub.enabled", "true") + injectSysConfig(GeneralConfig.SERVICE_NAME, "A-service") + injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, isDataStreamsEnabled().toString()) + if (!shadowGrpcSpans()) { + injectSysConfig(TraceInstrumentationConfig.GOOGLE_PUBSUB_IGNORED_GRPC_METHODS, "") + } + } + + def "trace is propagated between producer and consumer"() { + setup: + def publisher = Publisher + .newBuilder(TopicName.of(PROJECT_ID, TOPIC_ID)) + .setChannelProvider(transportChannelProvider) + .setCredentialsProvider(noCredentialsProvider) + .build() + def latch = new CountDownLatch(1) + + + def subscriber = Subscriber.newBuilder(subscriptionName, createMessageReceiver(latch)) + .setChannelProvider(transportChannelProvider) + .setCredentialsProvider(noCredentialsProvider) + .build() + subscriber.startAsync().awaitRunning() + TEST_WRITER.clear() + TEST_DATA_STREAMS_WRITER.clear() + + when: + TraceUtils.runUnderTrace('parent', { + publisher.publish(PubsubMessage.newBuilder().setData(ByteString.copyFrom('sometext', StandardCharsets.UTF_8)).build()) + }) + // wait for messages to be consumed + latch.await() + + then: + def sendSpan + assertTraces(shadowGrpcSpans() ? 2 : 4, [ + compare : { List o1, List o2 -> + // trace will never be empty + o1[0].localRootSpan.getTag(Tags.SPAN_KIND) <=> o2[0].localRootSpan.getTag(Tags.SPAN_KIND) + }, + ] as Comparator) { + trace(shadowGrpcSpans() ? 2 : 4) { + sortSpansByStart() + basicSpan(it, "parent") + span { + serviceName service() + operationName operationForProducer() + resourceName "Produce Topic test-topic" + spanType DDSpanTypes.MESSAGE_PRODUCER + errored false + measured true + childOfPrevious() + tags { + "$Tags.COMPONENT" "java-google-pubsub" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTagsNoPeerService() + } + } + // Publish + if (!shadowGrpcSpans()) { + grpcSpans(it) + } + sendSpan = span(1) + } + if (!shadowGrpcSpans()) { + // Acknowledge + trace(2) { + grpcSpans(it, "A-service", true) + } + // ModifyAckDeadline + trace(2) { + grpcSpans(it, "A-service", true) + } + } + trace(1) { + span { + serviceName service() + operationName operationForConsumer() + resourceName "Consume Subscription my-subscription" + spanType DDSpanTypes.MESSAGE_CONSUMER + topLevel true + errored false + measured true + tags { + "$Tags.COMPONENT" "java-google-pubsub" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + and: + if (isDataStreamsEnabled()) { + TEST_DATA_STREAMS_WRITER.waitForGroups(2) + + StatsGroup sendStat = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0} + verifyAll (sendStat) { + edgeTags.containsAll(["direction:out" , "topic:test-topic", "type:google-pubsub"]) + edgeTags.size() == 3 + } + StatsGroup receiveStat = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == sendStat.hash} + verifyAll(receiveStat) { + edgeTags.containsAll(["direction:in" , "subscription:my-subscription", "type:google-pubsub"]) + edgeTags.size() == 3 + pathwayLatency.count == 1 + pathwayLatency.minValue > 0.0 + edgeLatency.count == 1 + edgeLatency.minValue > 0.0 + payloadSize.minValue > 0.0 + } + } + cleanup: + publisher.shutdown() + subscriber.stopAsync().awaitTerminated() + } + + + def grpcSpans(TraceAssert traceAssert, String service = service(), boolean traceRoot = false) { + traceAssert.span { + serviceName service + operationName GrpcClientDecorator.OPERATION_NAME.toString() + resourceName { true } + spanType DDSpanTypes.RPC + errored false + measured true + if (traceRoot) { + topLevel true + } else { + childOfPrevious() + } + tags { + "$Tags.COMPONENT" "grpc-client" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT + "request.type" { String } + "response.type" { String } + "$Tags.RPC_SERVICE" { String } + "status.code" { String } + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + "$Tags.PEER_HOSTNAME" "localhost" + "$Tags.PEER_HOST_IPV4" "127.0.0.1" + "$Tags.PEER_PORT" { Integer } + peerServiceFrom(Tags.RPC_SERVICE) + defaultTags() + } + } + traceAssert.span { + serviceName service + operationName "grpc.message" + resourceName "grpc.message" + spanType DDSpanTypes.RPC + errored false + measured true + childOfPrevious() + tags { + "$Tags.COMPONENT" "grpc-client" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT + "message.type" { String } + defaultTagsNoPeerService() + } + } + } +} + +class PubSubNamingV0Test extends PubSubTest { + @Override + String operationForConsumer() { + "google-pubsub.consume" + } + + @Override + String operationForProducer() { + "google-pubsub.produce" + } + + @Override + int version() { + return 0 + } + + @Override + String service() { + "google-pubsub" + } +} + + +class PubSubNamingV1ForkedTest extends PubSubTest { + @Override + String operationForConsumer() { + "gcp.pubsub.process" + } + + @Override + String operationForProducer() { + "gcp.pubsub.send" + } + + @Override + int version() { + return 1 + } + + @Override + String service() { + "A-service" + } +} + +class PubSubLogGrpcSpansForkedTest extends PubSubNamingV0Test { + @Override + boolean shadowGrpcSpans() { + false + } +} + +class PubSubMessageReceiverWithAckResponseTest extends PubSubNamingV0Test { + @Override + Object createMessageReceiver(CountDownLatch latch) { + new MessageReceiverWithAckResponse() { + @Override + void receiveMessage(PubsubMessage message, AckReplyConsumerWithResponse consumer) { + consumer.ack().get() + latch.countDown() + } + } + } +} + +class PubSubDataStreamEnabledForkedTest extends PubSubNamingV0Test { + @Override + protected boolean isDataStreamsEnabled() { + return true + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityGradleListener.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityGradleListener.java new file mode 100644 index 00000000000..04fb13ba7b6 --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityGradleListener.java @@ -0,0 +1,217 @@ +package datadog.trace.instrumentation.gradle; + +import datadog.trace.api.Config; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.gradle.BuildAdapter; +import org.gradle.BuildResult; +import org.gradle.StartParameter; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.internal.BuildDefinition; +import org.gradle.api.internal.StartParameterInternal; +import org.gradle.api.internal.project.taskfactory.TaskIdentity; +import org.gradle.api.invocation.Gradle; +import org.gradle.api.plugins.PluginManager; +import org.gradle.api.provider.Provider; +import org.gradle.api.services.BuildServiceRegistry; +import org.gradle.api.tasks.TaskState; +import org.gradle.api.tasks.testing.Test; +import org.gradle.build.event.BuildEventsListenerRegistry; +import org.gradle.execution.taskgraph.TaskListenerInternal; +import org.gradle.internal.InternalBuildListener; +import org.gradle.internal.build.BuildState; +import org.gradle.internal.build.NestedBuildState; +import org.gradle.internal.build.RootBuildState; +import org.gradle.internal.service.scopes.ListenerService; + +@ListenerService +public class CiVisibilityGradleListener extends BuildAdapter + implements InternalBuildListener, TaskListenerInternal { + + private static final String TRACER_VERSION; + + static { + try (BufferedReader reader = + new BufferedReader( + new InputStreamReader( + ClassLoader.getSystemResourceAsStream("dd-java-agent.version")))) { + TRACER_VERSION = reader.lines().collect(Collectors.joining()); + } catch (IOException e) { + throw new RuntimeException("Could not read tracer version from dd-java-agent.version", e); + } + } + + { + /* + * Gradle's configuration cache has a set of "inputs" that determine whether an existing entry + * can be reused, or a new one should be created. + * Presence or absence of a javaagent and the variables that are used for the tracer configuration are + * not among those inputs. + * That means that a cache entry created without the tracer can be reused when the tracer is present: + * as the result test tasks will not be instrumented. + * Or the other way around, a cache entry created with the tracer can be reused when the tracer is absent: + * as the result a test task may try to access CI Visibility services and fail. + * Likewise, a cache entry created with an older version of the tracer can be reused with the newer version + * of the tracer, which is undesirable if there are some changes in the tasks configuration logic. + * To prevent this we have to ensure that the presence or absence of tracer, as well as the version of the tracer, + * is a part of the configuration cache inputs. + * We do this by settings the system property below, since all properties with the prefix "org.gradle.project." + * are included into configuration cache inputs by default. + * + * Please note that this has to be done for every build, so this block has to be dynamic and not static. + */ + System.setProperty("org.gradle.project.datadog.tracer.version", TRACER_VERSION); + } + + private final Config config = Config.get(); + private final Gradle gradle; + private final CiVisibilityService ciVisibilityService; + + public CiVisibilityGradleListener( + Gradle gradle, + BuildState buildState, + BuildEventsListenerRegistry buildEventsListenerRegistry) { + this.gradle = gradle; + + BuildServiceRegistry sharedServices = gradle.getSharedServices(); + Provider ciVisibilityServiceProvider = + sharedServices.registerIfAbsent( + "ciVisibilityService", CiVisibilityService.class, spec -> {}); + // registration is needed to keep the service alive until the end of the build + buildEventsListenerRegistry.onTaskCompletion(ciVisibilityServiceProvider); + ciVisibilityService = ciVisibilityServiceProvider.get(); + + String buildPath = buildState.getBuildIdentifier().getBuildPath(); + Path projectRoot = buildState.getBuildRootDir().toPath(); + + boolean nestedBuild; + String nestedBuildPath; + if (buildState instanceof NestedBuildState) { + nestedBuild = true; + nestedBuildPath = buildPath; + } else { + nestedBuild = false; + nestedBuildPath = null; + } + + StartParameterInternal startParameter = getStartParameter(buildState); + String startCommand = recreateStartCommand(startParameter, nestedBuildPath); + String gradleVersion = gradle.getGradleVersion(); + ciVisibilityService.onBuildStart( + buildPath, projectRoot, startCommand, gradleVersion, nestedBuild); + } + + private static StartParameterInternal getStartParameter(BuildState buildState) { + if (buildState instanceof RootBuildState) { + RootBuildState rootBuildState = (RootBuildState) buildState; + return rootBuildState.getStartParameter(); + } else if (buildState instanceof NestedBuildState) { + NestedBuildState nestedBuildState = (NestedBuildState) buildState; + BuildDefinition buildDefinition = nestedBuildState.getBuildDefinition(); + return buildDefinition.getStartParameter(); + } else { + throw new IllegalArgumentException("Unexpected build state: " + buildState); + } + } + + /** + * Returns command line used to start the build. We instrument Gradle daemon process, not the + * client process that is launched from the command line, so the result of this method is an + * approximation of what the actual command could look like + */ + private static String recreateStartCommand(StartParameter startParameter, String buildPath) { + StringBuilder command = new StringBuilder("gradle"); + + if (buildPath != null && !buildPath.isEmpty()) { + command.append(' ').append(buildPath); + } + + for (String taskName : startParameter.getTaskNames()) { + command.append(' ').append(taskName); + } + + for (String excludedTaskName : startParameter.getExcludedTaskNames()) { + command.append(" -x").append(excludedTaskName); + } + + for (Map.Entry e : startParameter.getProjectProperties().entrySet()) { + String propertyKey = e.getKey(); + String propertyValue = e.getValue(); + command.append(" -P").append(propertyKey); + if (propertyValue != null && !propertyValue.isEmpty()) { + command.append('=').append(propertyValue); + } + } + + for (Map.Entry e : startParameter.getSystemPropertiesArgs().entrySet()) { + String propertyKey = e.getKey(); + String propertyValue = e.getValue(); + command.append(" -D").append(propertyKey); + if (propertyValue != null && !propertyValue.isEmpty()) { + command.append('=').append(propertyValue); + } + } + + return command.toString(); + } + + @Override + public void projectsEvaluated(Gradle gradle) { + if (!config.isCiVisibilityAutoConfigurationEnabled()) { + return; + } + + Project rootProject = gradle.getRootProject(); + Set projects = rootProject.getAllprojects(); + for (Project project : projects) { + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(CiVisibilityPlugin.class); + } + } + + @SuppressWarnings("unchecked") + @Override + public void beforeExecute(TaskIdentity taskIdentity) { + if (!Test.class.isAssignableFrom(taskIdentity.getTaskType())) { + return; + } + + String projectPath = taskIdentity.getProjectPath(); + String taskPath = taskIdentity.getTaskPath(); + + Project project = gradle.getRootProject().project(projectPath); + Task task = project.getTasks().getByName(taskIdentity.name); + + Collection compiledClassFolders = + (Collection) + task.getInputs() + .getProperties() + .get(CiVisibilityPluginExtension.COMPILED_CLASS_FOLDERS_PROPERTY); + ciVisibilityService.onModuleStart(taskPath, compiledClassFolders); + } + + @Override + public void afterExecute(TaskIdentity taskIdentity, TaskState state) { + if (!Test.class.isAssignableFrom(taskIdentity.getTaskType())) { + return; + } + + String taskPath = taskIdentity.getTaskPath(); + Throwable failure = state.getFailure(); + String reason = state.getSkipped() || !state.getDidWork() ? state.getSkipMessage() : null; + ciVisibilityService.onModuleFinish(taskPath, failure, reason); + } + + @Override + public void buildFinished(BuildResult result) { + ciVisibilityService.onBuildFinish(result.getFailure()); + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityGradleListenerProvider.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityGradleListenerProvider.java new file mode 100644 index 00000000000..a0d561fff70 --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityGradleListenerProvider.java @@ -0,0 +1,36 @@ +package datadog.trace.instrumentation.gradle; + +import org.gradle.initialization.ClassLoaderRegistry; +import org.gradle.internal.service.ServiceRegistration; + +public class CiVisibilityGradleListenerProvider { + private final ClassLoaderRegistry classLoaderRegistry; + + public CiVisibilityGradleListenerProvider(ClassLoaderRegistry classLoaderRegistry) { + this.classLoaderRegistry = classLoaderRegistry; + } + + public void configure(ServiceRegistration serviceRegistration) { + Class ciVisibilityGradleListener = loadCiVisibilityGradleListener(); + serviceRegistration.add(ciVisibilityGradleListener); + } + + /** + * There are several class loaders in Gradle that load various parts of the system: core classes, + * plugins, etc. This provider class is injected into the core classloader since it is invoked by + * the build service registry, which is a part of the Gradle core. The CI Visibility listener + * (that is being registered as a service here) is injected into the plugins classloader because + * it needs to work with domain objects from Gradle Testing JVM (e.g. {@link + * org.gradle.api.tasks.testing.Test} task), which is a plugin. Therefore, we cannot reference its + * {@code Class} instance directly, and instead have to load it explicitly. + */ + private Class loadCiVisibilityGradleListener() { + try { + return classLoaderRegistry + .getPluginsClassLoader() + .loadClass("datadog.trace.instrumentation.gradle.CiVisibilityGradleListener"); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not load CI Visibility Gradle Listener", e); + } + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPlugin.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPlugin.java new file mode 100644 index 00000000000..bacb7914fa6 --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPlugin.java @@ -0,0 +1,154 @@ +package datadog.trace.instrumentation.gradle; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.FileCollection; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.SourceSetOutput; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.testing.Test; +import org.gradle.testing.jacoco.plugins.JacocoPluginExtension; + +public abstract class CiVisibilityPlugin implements Plugin { + + private static final Logger LOGGER = Logging.getLogger(CiVisibilityPlugin.class); + + private static final String PLUGIN_EXTENSION_NAME = "dd-ci-visibility"; + private static final String JACOCO_PLUGIN_ID = "jacoco"; + + private Project project; + + @Override + public void apply(Project project) { + this.project = project; + + CiVisibilityPluginExtension extension = + project + .getExtensions() + .create(PLUGIN_EXTENSION_NAME, CiVisibilityPluginExtension.class, project.getObjects()); + calculateCompiledClassesFolders(extension); + applyCompilerPlugin(extension); + applyJacocoPlugin(extension); + applyToTestTasks(extension); + } + + private void calculateCompiledClassesFolders(CiVisibilityPluginExtension extension) { + FileCollection compiledClassFolders = project.files(); + SourceSetContainer sourceSets = project.getExtensions().findByType(SourceSetContainer.class); + if (sourceSets == null) { + return; + } + List sourceSetNames = extension.getCoverageEnabledSourceSets(); + for (String sourceSetName : sourceSetNames) { + SourceSet sourceSet = sourceSets.findByName(sourceSetName); + if (sourceSet != null) { + SourceSetOutput output = sourceSet.getOutput(); + FileCollection classesDirs = output.getClassesDirs(); + compiledClassFolders = compiledClassFolders.plus(classesDirs); + } + } + extension.setCompiledClassesFolders(compiledClassFolders); + } + + private void applyCompilerPlugin(CiVisibilityPluginExtension extension) { + if (extension.isCompilerPluginEnabled()) { + addCompilerPluginConfigurations(extension); + addModuleName(extension); + applyToCompileTasks(extension); + } + } + + public void addCompilerPluginConfigurations(CiVisibilityPluginExtension extension) { + Configuration configuration = + project + .getConfigurations() + .detachedConfiguration( + project + .getDependencies() + .create( + String.format( + "com.datadoghq:dd-javac-plugin:%s", + extension.getCompilerPluginVersion())), + project + .getDependencies() + .create( + String.format( + "com.datadoghq:dd-javac-plugin-client:%s", + extension.getCompilerPluginVersion()))); + + // if instrumented project does dependency verification, + // we need to exclude the detached configurations that we're adding + // as corresponding entries are not in the project's verification-metadata.xml + configuration.getResolutionStrategy().disableDependencyVerification(); + + extension.setCompilerPluginClasspath(configuration); + } + + private void addModuleName(CiVisibilityPluginExtension extension) { + extension.setModuleName(getModuleName()); + } + + private static final Pattern MODULE_NAME_PATTERN = + Pattern.compile("\\s*module\\s*((\\w|\\.)+)\\s*\\{"); + + private String getModuleName() { + Path projectDir = project.getProjectDir().toPath(); + Path moduleInfo = projectDir.resolve(Paths.get("src", "main", "java", "module-info.java")); + try { + if (Files.exists(moduleInfo)) { + List lines = Files.readAllLines(moduleInfo); + for (String line : lines) { + Matcher matcher = MODULE_NAME_PATTERN.matcher(line); + if (matcher.matches()) { + return matcher.group(1); + } + } + } + } catch (Exception e) { + LOGGER.error("Could not read module name from {}", moduleInfo, e); + } + return null; + } + + private void applyToCompileTasks(CiVisibilityPluginExtension extension) { + project.getTasks().withType(JavaCompile.class).configureEach(extension::applyTo); + } + + private void applyJacocoPlugin(CiVisibilityPluginExtension extension) { + if (!extension.isCodeCoverageEnabled(project) + || project.getPluginManager().hasPlugin(JACOCO_PLUGIN_ID)) { + return; + } + + project.getPluginManager().apply(JACOCO_PLUGIN_ID); + JacocoPluginExtension jacocoExtension = + project.getExtensions().getByType(JacocoPluginExtension.class); + jacocoExtension.setToolVersion(extension.getJacocoVersion()); + + List jacocoConfigurations = + project.getConfigurations().stream() + .filter(c -> c.getName().startsWith("jacoco")) + .collect(Collectors.toList()); + for (Configuration jacocoConfiguration : jacocoConfigurations) { + // if instrumented project does dependency verification, + // we need to exclude configurations added by Jacoco + // as corresponding entries are not in the project's verification-metadata.xml + jacocoConfiguration.getResolutionStrategy().disableDependencyVerification(); + } + } + + private void applyToTestTasks(CiVisibilityPluginExtension extension) { + project.getTasks().withType(Test.class).configureEach(extension::applyTo); + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPluginExtension.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPluginExtension.java new file mode 100644 index 00000000000..dbd42508f6d --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPluginExtension.java @@ -0,0 +1,183 @@ +package datadog.trace.instrumentation.gradle; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Property; +import org.gradle.api.services.ServiceReference; +import org.gradle.api.tasks.compile.CompileOptions; +import org.gradle.api.tasks.compile.ForkOptions; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.testing.Test; +import org.gradle.internal.jvm.Jvm; +import org.gradle.jvm.toolchain.JavaLauncher; +import org.gradle.process.CommandLineArgumentProvider; +import org.gradle.testing.jacoco.plugins.JacocoTaskExtension; + +public abstract class CiVisibilityPluginExtension { + + private static final Logger LOGGER = Logging.getLogger(CiVisibilityPluginExtension.class); + + public static final String COMPILED_CLASS_FOLDERS_PROPERTY = "compiledClassFolders"; + + private final ObjectFactory objectFactory; + private FileCollection compilerPluginClasspath; + private String moduleName; + private FileCollection compiledClassFolders; + + @Inject + public CiVisibilityPluginExtension(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + public void setCompilerPluginClasspath(FileCollection compilerPluginClasspath) { + this.compilerPluginClasspath = compilerPluginClasspath; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public void setCompiledClassesFolders(FileCollection compiledClassFolders) { + this.compiledClassFolders = compiledClassFolders; + } + + @ServiceReference + abstract Property getCiVisibilityService(); + + public boolean isCompilerPluginEnabled() { + return getCiVisibilityService().get().isCompilerPluginEnabled(); + } + + public String getCompilerPluginVersion() { + return getCiVisibilityService().get().getCompilerPluginVersion(); + } + + public boolean isCodeCoverageEnabled(Project project) { + CiVisibilityService ciVisibilityService = getCiVisibilityService().get(); + return project.getTasks().withType(Test.class).stream() + .map(this::getEffectiveExecutable) + .anyMatch(ciVisibilityService::isCodeCoverageEnabled); + } + + public List getCoverageEnabledSourceSets() { + return getCiVisibilityService().get().getCoverageEnabledSourceSets(); + } + + public String getJacocoVersion() { + return getCiVisibilityService().get().getJacocoVersion(); + } + + public void applyTo(JavaCompile javaCompile) { + CompileOptions options = javaCompile.getOptions(); + ForkOptions forkOptions = options.getForkOptions(); + String compilerExecutable = forkOptions.getExecutable(); + if (compilerExecutable != null && !compilerExecutable.isEmpty()) { + // assuming a non-standard compiler is used + return; + } + + FileCollection classpath = javaCompile.getClasspath(); + FileCollection updatedClasspath = + classpath != null ? classpath.plus(compilerPluginClasspath) : compilerPluginClasspath; + javaCompile.setClasspath(updatedClasspath); + + FileCollection annotationProcessorPath = options.getAnnotationProcessorPath(); + FileCollection updatedAnnotationProcessorPath = + annotationProcessorPath != null + ? annotationProcessorPath.plus(compilerPluginClasspath) + : compilerPluginClasspath; + options.setAnnotationProcessorPath(updatedAnnotationProcessorPath); + + CommandLineArgumentProvider argumentProvider = + new JavaCompilerPluginArgumentsProvider(moduleName); + options.getCompilerArgumentProviders().add(argumentProvider); + } + + public void applyTo(Test task) { + task.getInputs().property(COMPILED_CLASS_FOLDERS_PROPERTY, compiledClassFolders.getFiles()); + + Path jvmExecutable = getEffectiveExecutable(task); + applyTracerSettings( + jvmExecutable, task.getPath(), getProjectProperties(task), task.getJvmArgumentProviders()); + + JacocoTaskExtension jacocoTaskExtension = + task.getExtensions().findByType(JacocoTaskExtension.class); + if (jacocoTaskExtension != null) { + applyJacocoSettings(jvmExecutable, jacocoTaskExtension); + } + } + + private Path getEffectiveExecutable(Test task) { + Property javaLauncher = task.getJavaLauncher(); + if (javaLauncher.isPresent()) { + try { + return javaLauncher.get().getExecutablePath().getAsFile().toPath(); + } catch (Exception e) { + LOGGER.error("Could not get Java launcher for test task", e); + } + } + String executable = task.getExecutable(); + if (executable != null && !executable.isEmpty()) { + return Paths.get(executable); + } else { + return Jvm.current().getJavaExecutable().toPath(); + } + } + + private static Map getProjectProperties(Test task) { + Map projectProperties = new HashMap<>(); + for (Map.Entry e : task.getProject().getProperties().entrySet()) { + String propertyName = e.getKey(); + Object propertyValue = e.getValue(); + if (propertyValue instanceof String) { + projectProperties.put(propertyName, (String) propertyValue); + } + } + return projectProperties; + } + + private void applyJacocoSettings(Path jvmExecutable, JacocoTaskExtension jacocoTaskExtension) { + CiVisibilityService ciVisibilityService = getCiVisibilityService().get(); + + List taskExcludeClassLoader = jacocoTaskExtension.getExcludeClassLoaders(); + if (taskExcludeClassLoader != null) { + taskExcludeClassLoader.addAll(ciVisibilityService.getExcludeClassLoaders()); + } else { + jacocoTaskExtension.setExcludeClassLoaders( + new ArrayList<>(ciVisibilityService.getExcludeClassLoaders())); + } + + List taskIncludePackages = jacocoTaskExtension.getIncludes(); + if (taskIncludePackages == null) { + taskIncludePackages = new ArrayList<>(); + jacocoTaskExtension.setIncludes(taskIncludePackages); + } + for (String coverageEnabledPackage : + ciVisibilityService.getCoverageEnabledPackages(jvmExecutable)) { + if (coverageEnabledPackage != null && !coverageEnabledPackage.isEmpty()) { + taskIncludePackages.add(coverageEnabledPackage); + } + } + } + + private void applyTracerSettings( + Path jvmExecutable, + String taskPath, + Map projectProperties, + List jvmArgumentProviders) { + CommandLineArgumentProvider tracerArgumentsProvider = + objectFactory.newInstance( + TracerArgumentsProvider.class, taskPath, jvmExecutable, projectProperties); + jvmArgumentProviders.add(tracerArgumentsProvider); + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityService.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityService.java new file mode 100644 index 00000000000..4c283f1df9c --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityService.java @@ -0,0 +1,147 @@ +package datadog.trace.instrumentation.gradle; + +import datadog.trace.api.Config; +import datadog.trace.api.civisibility.InstrumentationBridge; +import datadog.trace.api.civisibility.config.ModuleExecutionSettings; +import datadog.trace.api.civisibility.events.BuildEventsHandler; +import datadog.trace.api.config.CiVisibilityConfig; +import datadog.trace.bootstrap.DatadogClassLoader; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.util.Strings; +import de.thetaphi.forbiddenapis.SuppressForbidden; +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.gradle.api.services.BuildService; +import org.gradle.api.services.BuildServiceParameters; +import org.gradle.tooling.events.FinishEvent; +import org.gradle.tooling.events.OperationCompletionListener; + +public abstract class CiVisibilityService + implements BuildService, OperationCompletionListener { + + // using constant session key, since the service is already build-scoped + private static final Object SESSION_KEY = new Object(); + private final Config config = Config.get(); + private final BuildEventsHandler buildEventsHandler = + InstrumentationBridge.createBuildEventsHandler(); + + public boolean isCompilerPluginEnabled() { + return config.isCiVisibilityCompilerPluginAutoConfigurationEnabled(); + } + + public String getCompilerPluginVersion() { + return config.getCiVisibilityCompilerPluginVersion(); + } + + public boolean isCodeCoverageEnabled(Path jvmExecutable) { + ModuleExecutionSettings moduleExecutionSettings = + buildEventsHandler.getModuleExecutionSettings(SESSION_KEY, jvmExecutable); + return moduleExecutionSettings.isCodeCoverageEnabled(); + } + + public String getJacocoVersion() { + return config.getCiVisibilityJacocoPluginVersion(); + } + + public List getExcludeClassLoaders() { + return Collections.singletonList(DatadogClassLoader.class.getName()); + } + + public List getCoverageEnabledSourceSets() { + return config.getCiVisibilityJacocoGradleSourceSets(); + } + + public List getCoverageEnabledPackages(Path jvmExecutable) { + ModuleExecutionSettings moduleExecutionSettings = + buildEventsHandler.getModuleExecutionSettings(SESSION_KEY, jvmExecutable); + return moduleExecutionSettings.getCoverageEnabledPackages(); + } + + @SuppressForbidden + public Collection getTracerJvmArgs(String taskPath, Path jvmExecutable) { + List jvmArgs = new ArrayList<>(); + + ModuleExecutionSettings moduleExecutionSettings = + buildEventsHandler.getModuleExecutionSettings(SESSION_KEY, jvmExecutable); + Map propagatedSystemProperties = moduleExecutionSettings.getSystemProperties(); + // propagate to child process all "dd." system properties available in current process + for (Map.Entry e : propagatedSystemProperties.entrySet()) { + jvmArgs.add("-D" + e.getKey() + '=' + e.getValue()); + } + + Integer ciVisibilityDebugPort = config.getCiVisibilityDebugPort(); + if (ciVisibilityDebugPort != null) { + jvmArgs.add( + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + ciVisibilityDebugPort); + } + + String additionalArgs = config.getCiVisibilityAdditionalChildProcessJvmArgs(); + if (additionalArgs != null) { + List splitArgs = Arrays.asList(additionalArgs.split(" ")); + jvmArgs.addAll(splitArgs); + } + + jvmArgs.add("-javaagent:" + config.getCiVisibilityAgentJarFile().toPath()); + + BuildEventsHandler.ModuleInfo moduleInfo = + buildEventsHandler.getModuleInfo(SESSION_KEY, taskPath); + jvmArgs.add(arg(CiVisibilityConfig.CIVISIBILITY_SESSION_ID, moduleInfo.sessionId)); + jvmArgs.add(arg(CiVisibilityConfig.CIVISIBILITY_MODULE_ID, moduleInfo.moduleId)); + jvmArgs.add( + arg(CiVisibilityConfig.CIVISIBILITY_SIGNAL_SERVER_HOST, moduleInfo.signalServerHost)); + jvmArgs.add( + arg(CiVisibilityConfig.CIVISIBILITY_SIGNAL_SERVER_PORT, moduleInfo.signalServerPort)); + return jvmArgs; + } + + private String arg(String propertyName, Object value) { + return "-D" + Strings.propertyNameToSystemPropertyName(propertyName) + "=" + value; + } + + public void onBuildStart( + String buildPath, + Path projectRoot, + String startCommand, + String gradleVersion, + boolean nestedBuild) { + Map additionalTags = + nestedBuild + ? Collections.singletonMap(Tags.TEST_GRADLE_NESTED_BUILD, true) + : Collections.emptyMap(); + buildEventsHandler.onTestSessionStart( + SESSION_KEY, buildPath, projectRoot, startCommand, "gradle", gradleVersion, additionalTags); + } + + public void onModuleStart(String taskPath, Collection compiledClassFolders) { + buildEventsHandler.onTestModuleStart(SESSION_KEY, taskPath, compiledClassFolders, null); + } + + public void onModuleFinish( + String taskPath, @Nullable Throwable failure, @Nullable String skipReason) { + if (failure != null) { + buildEventsHandler.onTestModuleFail(SESSION_KEY, taskPath, failure); + } else if (skipReason != null) { + buildEventsHandler.onTestModuleSkip(SESSION_KEY, taskPath, skipReason); + } + buildEventsHandler.onTestModuleFinish(SESSION_KEY, taskPath); + } + + public void onBuildFinish(@Nullable Throwable failure) { + if (failure != null) { + buildEventsHandler.onTestSessionFail(SESSION_KEY, failure); + } + buildEventsHandler.onTestSessionFinish(SESSION_KEY); + } + + @Override + public void onFinish(FinishEvent event) { + // do nothing + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildScopeServicesInstrumentation.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildScopeServicesInstrumentation.java new file mode 100644 index 00000000000..453451aabed --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildScopeServicesInstrumentation.java @@ -0,0 +1,62 @@ +package datadog.trace.instrumentation.gradle; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.Config; +import java.util.Set; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatcher; +import org.gradle.initialization.ClassLoaderRegistry; +import org.gradle.internal.service.ServiceRegistry; +import org.gradle.internal.service.scopes.BuildScopeServices; + +@AutoService(Instrumenter.class) +public class GradleBuildScopeServicesInstrumentation extends Instrumenter.CiVisibility + implements Instrumenter.ForSingleType { + + public GradleBuildScopeServicesInstrumentation() { + super("gradle", "gradle-build-scope-services"); + } + + @Override + public ElementMatcher classLoaderMatcher() { + // Only instrument Gradle 8.3+ + return hasClassNamed("org.gradle.api.file.ConfigurableFilePermissions"); + } + + @Override + public String instrumentedType() { + return "org.gradle.internal.service.scopes.BuildScopeServices"; + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".CiVisibilityGradleListenerProvider", + }; + } + + @Override + public boolean isApplicable(Set enabledSystems) { + return super.isApplicable(enabledSystems) + && Config.get().isCiVisibilityBuildInstrumentationEnabled(); + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice(isConstructor(), getClass().getName() + "$Construct"); + } + + public static class Construct { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void afterConstructor( + @Advice.This final BuildScopeServices buildScopeServices, + @Advice.Argument(0) final ServiceRegistry parent) { + ClassLoaderRegistry classLoaderRegistry = parent.get(ClassLoaderRegistry.class); + buildScopeServices.addProvider(new CiVisibilityGradleListenerProvider(classLoaderRegistry)); + } + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradlePluginInjectorInstrumentation.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradlePluginInjectorInstrumentation.java new file mode 100644 index 00000000000..5b3e8ce5db9 --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradlePluginInjectorInstrumentation.java @@ -0,0 +1,63 @@ +package datadog.trace.instrumentation.gradle; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.HelperInjector; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.Config; +import java.util.Set; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public class GradlePluginInjectorInstrumentation extends Instrumenter.CiVisibility + implements Instrumenter.ForSingleType { + + public GradlePluginInjectorInstrumentation() { + super("gradle", "gradle-plugin-injector"); + } + + @Override + public ElementMatcher classLoaderMatcher() { + // Only instrument Gradle 8.3+ + return hasClassNamed("org.gradle.api.file.ConfigurableFilePermissions"); + } + + /** + * There are several class loaders in Gradle that load various parts of the system: core classes, + * plugins, etc. CI Visibility listener and plugin (as well as the rest of the helper classes) + * need to be injected into the plugins classloader because they refer to classes from Gradle + * Testing JVM (e.g. {@link org.gradle.api.tasks.testing.Test}), which are loaded by the plugins + * CL. The purpose of this instrumentation is only to inject the classes into the classloader, so + * that they could be used from elsewhere. + * + *

{@link org.gradle.platform.base.Platform} was chosen as the instrumented type since it is + * loaded early in the build lifecycle, therefore the injected classes can be safely used by the + * services that are initialized later on. + */ + @Override + public String instrumentedType() { + return "org.gradle.platform.base.Platform"; + } + + @Override + public boolean isApplicable(Set enabledSystems) { + return super.isApplicable(enabledSystems) + && Config.get().isCiVisibilityBuildInstrumentationEnabled(); + } + + @Override + public AdviceTransformer transformer() { + return new HelperInjector( + "gradle-plugin-injector", + packageName + ".CiVisibilityService", + packageName + ".JavaCompilerPluginArgumentsProvider", + packageName + ".TracerArgumentsProvider", + packageName + ".CiVisibilityGradleListener", + packageName + ".CiVisibilityPluginExtension", + packageName + ".CiVisibilityPlugin"); + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) {} +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/JavaCompilerPluginArgumentsProvider.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/JavaCompilerPluginArgumentsProvider.java new file mode 100644 index 00000000000..2860506252e --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/JavaCompilerPluginArgumentsProvider.java @@ -0,0 +1,31 @@ +package datadog.trace.instrumentation.gradle; + +import java.util.ArrayList; +import java.util.List; +import org.gradle.process.CommandLineArgumentProvider; + +public class JavaCompilerPluginArgumentsProvider implements CommandLineArgumentProvider { + private final String moduleName; + + public JavaCompilerPluginArgumentsProvider(String moduleName) { + this.moduleName = moduleName; + } + + @Override + public Iterable asArguments() { + List arguments = new ArrayList<>(); + arguments.add("-Xplugin:DatadogCompilerPlugin"); + + // disable compiler warnings related to annotation processing, + // since "fail-on-warning" linters might complain about the annotation that the compiler plugin + // injects + arguments.add("-Xlint:-processing"); + + if (moduleName != null) { + arguments.add("--add-reads"); + arguments.add(moduleName + "=ALL-UNNAMED"); + } + + return arguments; + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/TracerArgumentsProvider.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/TracerArgumentsProvider.java new file mode 100644 index 00000000000..51e6e98fcee --- /dev/null +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/TracerArgumentsProvider.java @@ -0,0 +1,50 @@ +package datadog.trace.instrumentation.gradle; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.inject.Inject; +import org.gradle.api.provider.Property; +import org.gradle.api.services.ServiceReference; +import org.gradle.process.CommandLineArgumentProvider; + +public abstract class TracerArgumentsProvider implements CommandLineArgumentProvider { + private static final Pattern PROJECT_PROPERTY_REFERENCE = Pattern.compile("\\$\\{([^}]+)\\}"); + + private final String taskPath; + private final Path jvmExecutable; + private final Map projectProperties; + + @Inject + public TracerArgumentsProvider( + String taskPath, Path jvmExecutable, Map projectProperties) { + this.taskPath = taskPath; + this.jvmExecutable = jvmExecutable; + this.projectProperties = projectProperties; + } + + @ServiceReference + abstract Property getCiVisibilityService(); + + @Override + public Iterable asArguments() { + Collection tracerJvmArgs = + getCiVisibilityService().get().getTracerJvmArgs(taskPath, jvmExecutable); + return tracerJvmArgs.stream().map(this::replaceProjectProperties).collect(Collectors.toList()); + } + + private String replaceProjectProperties(String s) { + StringBuffer output = new StringBuffer(); + Matcher matcher = PROJECT_PROPERTY_REFERENCE.matcher(s); + while (matcher.find()) { + String propertyName = matcher.group(1); + Object propertyValue = projectProperties.get(propertyName); + matcher.appendReplacement(output, Matcher.quoteReplacement(String.valueOf(propertyValue))); + } + matcher.appendTail(output); + return output.toString(); + } +} diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildListener.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleBuildListener.java similarity index 98% rename from dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildListener.java rename to dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleBuildListener.java index 6a6019b2d91..8b51e987ebc 100644 --- a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildListener.java +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleBuildListener.java @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.gradle; +package datadog.trace.instrumentation.gradle.legacy; import datadog.trace.api.Config; import datadog.trace.api.civisibility.InstrumentationBridge; @@ -46,7 +46,7 @@ public void settingsEvaluated(Settings settings) { String startCommand = GradleUtils.recreateStartCommand(settings.getStartParameter()); String gradleVersion = gradle.getGradleVersion(); buildEventsHandler.onTestSessionStart( - gradle, projectName, projectRoot, startCommand, "gradle", gradleVersion); + gradle, projectName, projectRoot, startCommand, "gradle", gradleVersion, null); } @Override diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildListenerInstrumentation.java b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleBuildListenerInstrumentation.java similarity index 78% rename from dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildListenerInstrumentation.java rename to dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleBuildListenerInstrumentation.java index 8abfeeb7e05..28cab2d074a 100644 --- a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildListenerInstrumentation.java +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleBuildListenerInstrumentation.java @@ -1,12 +1,15 @@ -package datadog.trace.instrumentation.gradle; +package datadog.trace.instrumentation.gradle.legacy; +import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed; import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.not; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.api.Config; import java.util.Set; import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatcher; import org.gradle.invocation.DefaultGradle; @AutoService(Instrumenter.class) @@ -17,6 +20,12 @@ public GradleBuildListenerInstrumentation() { super("gradle", "gradle-build-listener"); } + @Override + public ElementMatcher classLoaderMatcher() { + // Only instrument Gradle older than 8.3 + return not(hasClassNamed("org.gradle.api.file.ConfigurableFilePermissions")); + } + @Override public String instrumentedType() { return "org.gradle.invocation.DefaultGradle"; @@ -54,4 +63,9 @@ public static void afterConstructor(@Advice.This final DefaultGradle gradle) { gradle.addBuildListener(new GradleBuildListener()); } } + + @Override + public String muzzleDirective() { + return "skipMuzzle"; + } } diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleProjectConfigurator.groovy b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy similarity index 97% rename from dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleProjectConfigurator.groovy rename to dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy index 650f578e893..9c55bda87c7 100644 --- a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleProjectConfigurator.groovy +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.gradle +package datadog.trace.instrumentation.gradle.legacy import datadog.trace.api.Config import datadog.trace.api.civisibility.config.ModuleExecutionSettings @@ -39,9 +39,9 @@ class GradleProjectConfigurator { private static final Logger log = LoggerFactory.getLogger(GradleProjectConfigurator.class) - /** + /* * Each Groovy Closure in here is a separate class. - * When adding or removing a closure, be sure to update {@link datadog.trace.instrumentation.gradle.GradleBuildListenerInstrumentation#helperClassNames()} + * When adding or removing a closure, be sure to update {@link GradleBuildListenerInstrumentation#helperClassNames()} */ public static final GradleProjectConfigurator INSTANCE = new GradleProjectConfigurator() @@ -75,7 +75,9 @@ class GradleProjectConfigurator { jvmArgs.add("-javaagent:" + config.ciVisibilityAgentJarFile.toPath()) - task.jvmArgs(jvmArgs) + // be sure to use setJvmArgs() and not jvmArgs() + // as the latter will add the arguments rather than replacing them + task.setJvmArgs(jvmArgs) } private static final Pattern PROJECT_PROPERTY_REFERENCE = Pattern.compile("\\\$\\{([^}]+)\\}") diff --git a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleUtils.groovy b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleUtils.groovy similarity index 93% rename from dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleUtils.groovy rename to dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleUtils.groovy index b5336aeb6f1..99d0e7f265f 100644 --- a/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleUtils.groovy +++ b/dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleUtils.groovy @@ -1,4 +1,4 @@ -package datadog.trace.instrumentation.gradle +package datadog.trace.instrumentation.gradle.legacy import org.gradle.StartParameter import org.gradle.api.Project @@ -68,9 +68,12 @@ abstract class GradleUtils { def sourceSets = project.sourceSets Collection allOutputClassesDirs = new HashSet<>() for (String sourceSetName : sourceSetNames) { - def sourceSet = sourceSets.getByName(sourceSetName) - def sourceSetOutput = sourceSet.output + def sourceSet = sourceSets.findByName(sourceSetName) + if (sourceSet == null) { + continue + } + def sourceSetOutput = sourceSet.output if (sourceSetOutput.hasProperty('classesDirs')) { def outputClassesDirs = sourceSetOutput.classesDirs allOutputClassesDirs.addAll(outputClassesDirs.files) diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java index ea52721257d..414c8d23df9 100644 --- a/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java +++ b/dd-java-agent/instrumentation/grpc-1.5/src/main/java/datadog/trace/instrumentation/grpc/client/ClientCallImplInstrumentation.java @@ -16,10 +16,13 @@ import datadog.trace.bootstrap.instrumentation.api.AgentScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import io.grpc.ClientCall; +import io.grpc.Grpc; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; import io.grpc.StatusException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Collections; import java.util.Map; import net.bytebuddy.asm.Advice; @@ -156,6 +159,11 @@ public static void before( @Advice.This ClientCall call, @Advice.Argument(1) Throwable cause) { AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).remove(call); if (null != span) { + final SocketAddress socketAddress = + call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); + if (socketAddress instanceof InetSocketAddress) { + DECORATE.onPeerConnection(span, (InetSocketAddress) socketAddress); + } if (cause instanceof StatusException) { DECORATE.onClose(span, ((StatusException) cause).getStatus()); } @@ -170,6 +178,11 @@ public static void closeObserver( @Advice.This ClientCall call, @Advice.Argument(1) Status status) { AgentSpan span = InstrumentationContext.get(ClientCall.class, AgentSpan.class).remove(call); if (null != span) { + final SocketAddress socketAddress = + call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); + if (socketAddress instanceof InetSocketAddress) { + DECORATE.onPeerConnection(span, (InetSocketAddress) socketAddress); + } DECORATE.onClose(span, status); span.finish(); } diff --git a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy b/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy index ce7d28b785a..fe4261ed10c 100644 --- a/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy +++ b/dd-java-agent/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy @@ -22,6 +22,8 @@ import io.grpc.Status import io.grpc.StatusRuntimeException import io.grpc.inprocess.InProcessChannelBuilder import io.grpc.inprocess.InProcessServerBuilder +import io.grpc.netty.NettyChannelBuilder +import io.grpc.netty.NettyServerBuilder import io.grpc.stub.StreamObserver import spock.lang.Shared @@ -112,11 +114,12 @@ abstract class GrpcTest extends VersionedNamingTestBase { } } } - def builder = InProcessServerBuilder.forName(getClass().name).addService(greeter).executor(executor) + def builder = NettyServerBuilder.forPort(0).addService(greeter).executor(executor) (0..extraBuildCalls).each { builder.build() } Server server = builder.build().start() - ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build() + + ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build() GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel) when: @@ -146,6 +149,9 @@ abstract class GrpcTest extends VersionedNamingTestBase { "$Tags.COMPONENT" "grpc-client" "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT "$Tags.RPC_SERVICE" "example.Greeter" + "$Tags.PEER_HOSTNAME" "localhost" + "$Tags.PEER_HOST_IPV4" "127.0.0.1" + "$Tags.PEER_PORT" server.port "status.code" "OK" "request.type" "example.Helloworld\$Request" "response.type" "example.Helloworld\$Response" @@ -362,9 +368,9 @@ abstract class GrpcTest extends VersionedNamingTestBase { throw error } } - Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter).directExecutor().build().start() + Server server = NettyServerBuilder.forPort(0).addService(greeter).directExecutor().build().start() - ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build() + ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build() GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel) when: @@ -388,6 +394,9 @@ abstract class GrpcTest extends VersionedNamingTestBase { "$Tags.COMPONENT" "grpc-client" "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT "$Tags.RPC_SERVICE" "example.Greeter" + "$Tags.PEER_HOSTNAME" "localhost" + "$Tags.PEER_HOST_IPV4" "127.0.0.1" + "$Tags.PEER_PORT" server.port "status.code" "UNKNOWN" "request.type" "example.Helloworld\$Request" "response.type" "example.Helloworld\$Response" diff --git a/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie b/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie index 05e3234670b..45f3b31dbaf 100644 --- a/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie +++ b/dd-java-agent/instrumentation/iast-instrumenter/src/main/resources/datadog/trace/instrumentation/iastinstrumenter/iast_exclusion.trie @@ -163,6 +163,8 @@ 1 nano.xml.* 1 net.bytebuddy.* 1 net.jcip.* +# Weak randomness false positive in net.jodah.failsafe.RetryPolicyExecutor +1 net.jodah.failsafe.* # Amusing weak randomness false positive in XXHashFactory 1 net.jpountz.xxhash.* 1 net.logstash.* @@ -274,6 +276,9 @@ 2 org.springframework.security.web.authentication.* 2 org.springframework.web.context.request.* 2 org.springframework.web.util.WebUtils +# Need for Spring gson support +2 org.springframework.http.converter.json.AbstractJsonHttpMessageConverter +2 org.springframework.http.converter.json.GsonHttpMessageConverter # Need for ServletRequestCallSite.getInputStream() 2 org.springframework.http.server.* 1 org.sqlite.* diff --git a/dd-java-agent/instrumentation/java-io/src/main/java/datadog/trace/instrumentation/java/lang/InputStreamReaderCallSite.java b/dd-java-agent/instrumentation/java-io/src/main/java/datadog/trace/instrumentation/java/lang/InputStreamReaderCallSite.java new file mode 100644 index 00000000000..a73115ffb3b --- /dev/null +++ b/dd-java-agent/instrumentation/java-io/src/main/java/datadog/trace/instrumentation/java/lang/InputStreamReaderCallSite.java @@ -0,0 +1,30 @@ +package datadog.trace.instrumentation.java.lang; + +import datadog.trace.agent.tooling.csi.CallSite; +import datadog.trace.api.iast.IastCallSites; +import datadog.trace.api.iast.InstrumentationBridge; +import datadog.trace.api.iast.Propagation; +import datadog.trace.api.iast.propagation.PropagationModule; +import java.io.InputStreamReader; +import javax.annotation.Nonnull; + +@Propagation +@CallSite(spi = IastCallSites.class) +public class InputStreamReaderCallSite { + + @CallSite.After( + "void java.io.InputStreamReader.(java.io.InputStream, java.nio.charset.Charset)") + public static InputStreamReader afterInit( + @CallSite.AllArguments @Nonnull final Object[] params, + @CallSite.Return @Nonnull final InputStreamReader result) { + final PropagationModule module = InstrumentationBridge.PROPAGATION; + if (module != null) { + try { + module.taintIfTainted(result, params[0]); + } catch (final Throwable e) { + module.onUnexpectedException("afterInit threw", e); + } + } + return result; + } +} diff --git a/dd-java-agent/instrumentation/java-io/src/test/groovy/datadog/trace/instrumentation/java/io/InputStreamReaderCallSiteTest.groovy b/dd-java-agent/instrumentation/java-io/src/test/groovy/datadog/trace/instrumentation/java/io/InputStreamReaderCallSiteTest.groovy new file mode 100644 index 00000000000..f8b522e3e24 --- /dev/null +++ b/dd-java-agent/instrumentation/java-io/src/test/groovy/datadog/trace/instrumentation/java/io/InputStreamReaderCallSiteTest.groovy @@ -0,0 +1,23 @@ +package datadog.trace.instrumentation.java.io + +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.propagation.PropagationModule +import foo.bar.TestInputStreamReaderSuite + +import java.nio.charset.Charset + +class InputStreamReaderCallSiteTest extends BaseIoCallSiteTest{ + + void 'test InputStreamReader.'(){ + given: + PropagationModule iastModule = Mock(PropagationModule) + InstrumentationBridge.registerIastModule(iastModule) + + when: + TestInputStreamReaderSuite.init(new ByteArrayInputStream("test".getBytes()), Charset.defaultCharset()) + + then: + 1 * iastModule.taintIfTainted(_ as InputStreamReader, _ as InputStream) + 0 * _ + } +} diff --git a/dd-java-agent/instrumentation/java-io/src/test/java/foo/bar/TestInputStreamReaderSuite.java b/dd-java-agent/instrumentation/java-io/src/test/java/foo/bar/TestInputStreamReaderSuite.java new file mode 100644 index 00000000000..57c572575a5 --- /dev/null +++ b/dd-java-agent/instrumentation/java-io/src/test/java/foo/bar/TestInputStreamReaderSuite.java @@ -0,0 +1,12 @@ +package foo.bar; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; + +public class TestInputStreamReaderSuite { + + public static InputStreamReader init(final InputStream in, Charset charset) { + return new InputStreamReader(in, charset); + } +} diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java index cdec1cbf95a..04a9e793256 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java @@ -11,7 +11,6 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.api.InstrumenterConfig; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; @@ -26,7 +25,7 @@ @AutoService(Instrumenter.class) public class DBMCompatibleConnectionInstrumentation extends AbstractConnectionInstrumentation - implements Instrumenter.ForKnownTypes, Instrumenter.ForConfiguredType { + implements Instrumenter.ForKnownTypes { /** Instrumentation class for connections for Database Monitoring supported DBs * */ public DBMCompatibleConnectionInstrumentation() { @@ -36,7 +35,6 @@ public DBMCompatibleConnectionInstrumentation() { // Classes to cover all currently supported // db types for the Database Monitoring product static final String[] CONCRETE_TYPES = { - "com.microsoft.sqlserver.jdbc.SQLServerConnection", // should cover mysql "com.mysql.jdbc.Connection", "com.mysql.jdbc.jdbc1.Connection", @@ -64,13 +62,8 @@ public DBMCompatibleConnectionInstrumentation() { "postgresql.Connection", // EDB version of postgresql "com.edb.jdbc.PgConnection", - // jtds (for SQL Server and Sybase) - "net.sourceforge.jtds.jdbc.ConnectionJDBC2", // 1.2 - "net.sourceforge.jtds.jdbc.JtdsConnection", // 1.3 // aws-mysql-jdbc "software.aws.rds.jdbc.mysql.shading.com.mysql.cj.jdbc.ConnectionImpl", - // IBM Informix - "com.informix.jdbc.IfmxConnection", }; @Override @@ -85,12 +78,6 @@ public String[] knownMatchingTypes() { return CONCRETE_TYPES; } - @Override - public String configuredMatchingType() { - // this won't match any class unless the property is set - return InstrumenterConfig.get().getJdbcConnectionClassName(); - } - @Override public void adviceTransformations(AdviceTransformation transformation) { transformation.applyAdvice( @@ -124,7 +111,7 @@ public static String onEnter( final DBInfo dbInfo = JDBCDecorator.parseDBInfo( connection, InstrumentationContext.get(Connection.class, DBInfo.class)); - sql = SQLCommenter.inject(sql, DECORATE.getDbService(dbInfo)); + sql = SQLCommenter.prepend(sql, DECORATE.getDbService(dbInfo)); return inputSql; } return sql; diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DefaultConnectionInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DefaultConnectionInstrumentation.java index e32eabc4c50..98c65e69863 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DefaultConnectionInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DefaultConnectionInstrumentation.java @@ -38,6 +38,8 @@ public class DefaultConnectionInstrumentation extends AbstractConnectionInstrume "oadd.org.apache.calcite.avatica.AvaticaConnection", // SAP HANA in-memory DB "com.sap.db.jdbc.ConnectionSapDB", + // IBM Informix + "com.informix.jdbc.IfmxConnection", // for testing purposes "test.TestConnection" }; diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java index 58a58a09c6f..02eabb8e5fa 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java @@ -19,23 +19,29 @@ public class SQLCommenter { private static final char EQUALS = '='; private static final char COMMA = ','; private static final char QUOTE = '\''; - private static final String OPEN_COMMENT = " /*"; + private static final char SPACE = ' '; + private static final String OPEN_COMMENT = "/*"; private static final String CLOSE_COMMENT = "*/"; private static final int INITIAL_CAPACITY = computeInitialCapacity(); - public static String inject(final String sql, final String dbService) { - return inject(sql, dbService, null, false); + public static String append(final String sql, final String dbService) { + return inject(sql, dbService, null, false, true); + } + + public static String prepend(final String sql, final String dbService) { + return inject(sql, dbService, null, false, false); } public static String inject( final String sql, final String dbService, final String traceParent, - final boolean injectTrace) { + final boolean injectTrace, + final boolean appendComment) { if (sql == null || sql.isEmpty()) { return sql; } - if (hasDDComment(sql)) { + if (hasDDComment(sql, appendComment)) { return sql; } final Config config = Config.get(); @@ -44,34 +50,51 @@ public static String inject( final String version = config.getVersion(); final int commentSize = capacity(traceParent, parentService, dbService, env, version); StringBuilder sb = new StringBuilder(sql.length() + commentSize); - sb.append(sql); - sb.append(OPEN_COMMENT); - toComment(sb, injectTrace, parentService, dbService, env, version, traceParent); - if (sb.length() == OPEN_COMMENT.length() + sql.length()) { + boolean commentAdded = false; + if (appendComment) { + sb.append(sql); + sb.append(SPACE); + sb.append(OPEN_COMMENT); + commentAdded = + toComment(sb, injectTrace, parentService, dbService, env, version, traceParent); + sb.append(CLOSE_COMMENT); + } else { + sb.append(OPEN_COMMENT); + commentAdded = + toComment(sb, injectTrace, parentService, dbService, env, version, traceParent); + sb.append(CLOSE_COMMENT); + sb.append(SPACE); + sb.append(sql); + } + if (!commentAdded) { return sql; } - sb.append(CLOSE_COMMENT); return sb.toString(); } - private static boolean hasDDComment(String sql) { + private static boolean hasDDComment(String sql, final boolean appendComment) { // first check to see if sql ends with a comment - if (!(sql.endsWith("*/"))) { + if ((!(sql.endsWith(CLOSE_COMMENT)) && appendComment) + || ((!(sql.startsWith(OPEN_COMMENT))) && !appendComment)) { return false; } // else check to see if it's a DBM trace sql comment + int startIdx = 2; + if (appendComment) { + startIdx = sql.lastIndexOf(OPEN_COMMENT) + 2; + } + int startComment = appendComment ? startIdx : sql.length(); boolean found = false; - int i = sql.lastIndexOf("/*") + 2; - if (i >= 2) { - if (hasMatchingSubstring(sql, i, PARENT_SERVICE)) { + if (startComment > 2) { + if (hasMatchingSubstring(sql, startIdx, PARENT_SERVICE)) { found = true; - } else if (hasMatchingSubstring(sql, i, DATABASE_SERVICE)) { + } else if (hasMatchingSubstring(sql, startIdx, DATABASE_SERVICE)) { found = true; - } else if (hasMatchingSubstring(sql, i, DD_ENV)) { + } else if (hasMatchingSubstring(sql, startIdx, DD_ENV)) { found = true; - } else if (hasMatchingSubstring(sql, i, DD_VERSION)) { + } else if (hasMatchingSubstring(sql, startIdx, DD_VERSION)) { found = true; - } else if (hasMatchingSubstring(sql, i, TRACEPARENT)) { + } else if (hasMatchingSubstring(sql, startIdx, TRACEPARENT)) { found = true; } } @@ -97,7 +120,7 @@ private static String encode(final String val) { return val; } - protected static void toComment( + protected static boolean toComment( StringBuilder sb, final boolean injectTrace, final String parentService, @@ -113,6 +136,7 @@ protected static void toComment( if (injectTrace) { append(sb, TRACEPARENT, traceparent, sb.length() > emptySize); } + return sb.length() > emptySize; } private static void append(StringBuilder sb, String key, String value, boolean prependComma) { diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLServerConnectionInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLServerConnectionInstrumentation.java new file mode 100644 index 00000000000..edefa428c21 --- /dev/null +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLServerConnectionInstrumentation.java @@ -0,0 +1,116 @@ +package datadog.trace.instrumentation.jdbc; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameStartsWith; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DECORATE; +import static datadog.trace.instrumentation.jdbc.JDBCDecorator.INJECT_COMMENT; +import static datadog.trace.instrumentation.jdbc.JDBCDecorator.logQueryInfoInjection; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo; +import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import net.bytebuddy.asm.Advice; + +@AutoService(Instrumenter.class) +public class SQLServerConnectionInstrumentation extends AbstractConnectionInstrumentation + implements Instrumenter.ForKnownTypes { + + /** + * Instrumentation class for connections for SQL Server, which is a Database Monitoring supported + * DB * + */ + public SQLServerConnectionInstrumentation() { + super("jdbc", "dbm-sqlserver"); + } + + // Classes to cover all currently supported + // db types for the Database Monitoring product + static final String[] CONCRETE_TYPES = { + "com.microsoft.sqlserver.jdbc.SQLServerConnection", + // jtds (for SQL Server and Sybase) + "net.sourceforge.jtds.jdbc.ConnectionJDBC2", // 1.2 + "net.sourceforge.jtds.jdbc.JtdsConnection", // 1.3 + }; + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".JDBCDecorator", packageName + ".SQLCommenter", + }; + } + + @Override + public String[] knownMatchingTypes() { + return CONCRETE_TYPES; + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice( + nameStartsWith("prepare") + .and(takesArgument(0, String.class)) + // Also include CallableStatement, which is a subtype of PreparedStatement + .and(returns(hasInterface(named("java.sql.PreparedStatement")))), + SQLServerConnectionInstrumentation.class.getName() + "$ConnectionAdvice"); + } + + @Override + public Map contextStore() { + Map contextStore = new HashMap<>(4); + contextStore.put("java.sql.Statement", DBQueryInfo.class.getName()); + contextStore.put("java.sql.Connection", DBInfo.class.getName()); + return contextStore; + } + + public static class ConnectionAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static String onEnter( + @Advice.This Connection connection, + @Advice.Argument(value = 0, readOnly = false) String sql) { + if (INJECT_COMMENT) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Connection.class); + if (callDepth > 0) { + return null; + } + final String inputSql = sql; + final DBInfo dbInfo = + JDBCDecorator.parseDBInfo( + connection, InstrumentationContext.get(Connection.class, DBInfo.class)); + sql = SQLCommenter.append(sql, DECORATE.getDbService(dbInfo)); + return inputSql; + } + return sql; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void addDBInfo( + @Advice.This Connection connection, + @Advice.Enter final String inputSql, + @Advice.Return final PreparedStatement statement) { + if (null == inputSql) { + return; + } + ContextStore contextStore = + InstrumentationContext.get(Statement.class, DBQueryInfo.class); + if (null == contextStore.get(statement)) { + DBQueryInfo info = DBQueryInfo.ofPreparedStatement(inputSql); + contextStore.put(statement, info); + logQueryInfoInjection(connection, statement, info); + } + CallDepthThreadLocalMap.reset(Connection.class); + } + } +} diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java index d645601ee75..e09ee423149 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java @@ -60,6 +60,9 @@ public String[] helperClassNames() { }; } + // prepend mode will prepend the SQL comment to the raw sql query + private static final boolean appendComment = false; + @Override public void adviceTransformations(AdviceTransformation transformation) { transformation.applyAdvice( @@ -94,7 +97,9 @@ public static AgentScope onEnter( span.setTag(DBM_TRACE_INJECTED, true); } } - sql = SQLCommenter.inject(sql, span.getServiceName(), traceParent, INJECT_TRACE_CONTEXT); + sql = + SQLCommenter.inject( + sql, span.getServiceName(), traceParent, INJECT_TRACE_CONTEXT, appendComment); } DECORATE.onStatement(span, DBQueryInfo.ofStatement(copy)); return activateSpan(span); diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/SQLCommenterTest.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/SQLCommenterTest.groovy index d3c3e370b95..8e9eb9dab56 100644 --- a/dd-java-agent/instrumentation/jdbc/src/test/groovy/SQLCommenterTest.groovy +++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/SQLCommenterTest.groovy @@ -12,9 +12,13 @@ class SQLCommenterTest extends AgentTestRunner { when: String sqlWithComment = "" if (injectTrace) { - sqlWithComment = SQLCommenter.inject(query, dbService, traceParent, true) + sqlWithComment = SQLCommenter.inject(query, dbService, traceParent, true, appendComment) } else { - sqlWithComment = SQLCommenter.inject(query, dbService) + if (appendComment) { + sqlWithComment = SQLCommenter.append(query, dbService) + } else { + sqlWithComment = SQLCommenter.prepend(query, dbService) + } } sqlWithComment == expected @@ -23,27 +27,47 @@ class SQLCommenterTest extends AgentTestRunner { sqlWithComment == expected where: - query | ddService | ddEnv | dbService | ddVersion | injectTrace | traceParent | expected - "SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" - "{call dogshelterProc(?, ?)}" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "{call dogshelterProc(?, ?)} /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" - "SELECT * FROM foo" | "" | "Test" | "" | "TestVersion" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" - "SELECT * FROM foo" | "" | "Test" | "my-service" | "TestVersion" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" - "SELECT * FROM foo" | "" | "Test" | "" | "" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*dde='Test',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" - "SELECT * FROM foo" | "" | "" | "" | "" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" - "SELECT * from FOO -- test query" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | "SELECT * from FOO -- test query /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" - "SELECT /* customer-comment */ * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | "SELECT /* customer-comment */ * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" - "SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "SELECT * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" - "SELECT /* customer-comment */ * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "SELECT /* customer-comment */ * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" - "SELECT * from FOO -- test query" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "SELECT * from FOO -- test query /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" - "" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "" - " " | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | " /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" - "SELECT * FROM foo /*dddbs='my-service',dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "SELECT * FROM foo /*dddbs='my-service',dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/" - "SELECT * FROM foo /*dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "SELECT * FROM foo /*dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/" - "SELECT * FROM foo /*ddps='SqlCommenter',ddpv='TestVersion'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "SELECT * FROM foo /*ddps='SqlCommenter',ddpv='TestVersion'*/" - "SELECT * FROM foo /*ddpv='TestVersion'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "SELECT * FROM foo /*ddpv='TestVersion'*/" - "/*ddjk its a customer */ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "/*ddjk its a customer */ SELECT * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" - "SELECT * FROM foo /*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "SELECT * FROM foo /*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" - "/*customer-comment*/ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "/*customer-comment*/ SELECT * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" - "/*traceparent" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | null | "/*traceparent /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" + query | ddService | ddEnv | dbService | ddVersion | injectTrace | appendComment | traceParent | expected + "SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" + "{call dogshelterProc(?, ?)}" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "{call dogshelterProc(?, ?)} /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" + "SELECT * FROM foo" | "" | "Test" | "" | "TestVersion" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" + "SELECT * FROM foo" | "" | "Test" | "my-service" | "TestVersion" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" + "SELECT * FROM foo" | "" | "Test" | "" | "" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*dde='Test',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" + "SELECT * FROM foo" | "" | "" | "" | "" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "SELECT * FROM foo /*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/" + "SELECT * from FOO -- test query" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | "SELECT * from FOO -- test query /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" + "SELECT /* customer-comment */ * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | "SELECT /* customer-comment */ * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" + "SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "SELECT * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" + "SELECT /* customer-comment */ * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "SELECT /* customer-comment */ * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" + "SELECT * from FOO -- test query" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "SELECT * from FOO -- test query /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" + "" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "" + " " | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | true | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | " /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" + "SELECT * FROM foo /*dddbs='my-service',dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "SELECT * FROM foo /*dddbs='my-service',dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/" + "SELECT * FROM foo /*dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "SELECT * FROM foo /*dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/" + "SELECT * FROM foo /*ddps='SqlCommenter',ddpv='TestVersion'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "SELECT * FROM foo /*ddps='SqlCommenter',ddpv='TestVersion'*/" + "SELECT * FROM foo /*ddpv='TestVersion'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "SELECT * FROM foo /*ddpv='TestVersion'*/" + "/*ddjk its a customer */ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "/*ddjk its a customer */ SELECT * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" + "SELECT * FROM foo /*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "SELECT * FROM foo /*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/" + "/*customer-comment*/ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "/*customer-comment*/ SELECT * FROM foo /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" + "/*traceparent" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | true | null | "/*traceparent /*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/" + "SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/ SELECT * FROM foo" + "SELECT * FROM foo" | "" | "Test" | "" | "TestVersion" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "/*dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/ SELECT * FROM foo" + "SELECT * FROM foo" | "" | "Test" | "my-service" | "TestVersion" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "/*dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/ SELECT * FROM foo" + "SELECT * FROM foo" | "" | "Test" | "" | "" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "/*dde='Test',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/ SELECT * FROM foo" + "SELECT * FROM foo" | "" | "" | "" | "" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "/*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/ SELECT * FROM foo" + "SELECT * from FOO -- test query" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/ SELECT * from FOO -- test query" + "SELECT /* customer-comment */ * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/ SELECT /* customer-comment */ * FROM foo" + "SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/ SELECT * FROM foo" + "SELECT /* customer-comment */ * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/ SELECT /* customer-comment */ * FROM foo" + "SELECT * from FOO -- test query" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/ SELECT * from FOO -- test query" + "" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-00" | "" + " " | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | true | false | "00-00000000000000007fffffffffffffff-000000024cb016ea-01" | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/ " + "/*dddbs='my-service',dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*dddbs='my-service',dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/ SELECT * FROM foo" + "/*dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*dde='Test',ddps='SqlCommenter',ddpv='TestVersion'*/ SELECT * FROM foo" + "/*ddps='SqlCommenter',ddpv='TestVersion'*/ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*ddps='SqlCommenter',ddpv='TestVersion'*/ SELECT * FROM foo" + "/*ddpv='TestVersion'*/ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*ddpv='TestVersion'*/ SELECT * FROM foo" + "/*ddjk its a customer */ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/ /*ddjk its a customer */ SELECT * FROM foo" + "/*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/ SELECT * FROM foo" + "/*customer-comment*/ SELECT * FROM foo" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/ /*customer-comment*/ SELECT * FROM foo" + "/*traceparent" | "SqlCommenter" | "Test" | "my-service" | "TestVersion" | false | false | null | "/*ddps='SqlCommenter',dddbs='my-service',dde='Test',ddpv='TestVersion'*/ /*traceparent" } } diff --git a/dd-java-agent/instrumentation/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Utils.java b/dd-java-agent/instrumentation/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Utils.java index 23dec8dba88..445068b2f0d 100644 --- a/dd-java-agent/instrumentation/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Utils.java +++ b/dd-java-agent/instrumentation/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Utils.java @@ -293,6 +293,9 @@ public static boolean isTestSuiteDescription(final Description description) { public static boolean isSuiteContainingChildren(final Description description) { Class testClass = description.getTestClass(); + if (testClass == null) { + return false; + } for (Method method : testClass.getMethods()) { if (method.getAnnotation(Test.class) != null) { return true; diff --git a/dd-java-agent/instrumentation/junit-4.10/src/test/groovy/JUnit4Test.groovy b/dd-java-agent/instrumentation/junit-4.10/src/test/groovy/JUnit4Test.groovy index 951987863c9..4ce257ef018 100644 --- a/dd-java-agent/instrumentation/junit-4.10/src/test/groovy/JUnit4Test.groovy +++ b/dd-java-agent/instrumentation/junit-4.10/src/test/groovy/JUnit4Test.groovy @@ -456,8 +456,8 @@ class JUnit4Test extends CiVisibilityTest { }) where: - testTags_0 = [(Tags.TEST_PARAMETERS): '{"metadata":{"test_name":"parameterized_test_succeed[0]"}}'] - testTags_1 = [(Tags.TEST_PARAMETERS): '{"metadata":{"test_name":"parameterized_test_succeed[1]"}}'] + testTags_0 = [(Tags.TEST_PARAMETERS): '{"metadata":{"test_name":"parameterized_test_succeed[str1]"}}'] + testTags_1 = [(Tags.TEST_PARAMETERS): '{"metadata":{"test_name":"parameterized_test_succeed[\\"str2\\"]"}}'] } def "test ITR skipping"() { @@ -529,11 +529,11 @@ class JUnit4Test extends CiVisibilityTest { where: testTags_0 = [ - (Tags.TEST_PARAMETERS): '{"metadata":{"test_name":"parameterized_test_succeed[0]"}}', + (Tags.TEST_PARAMETERS): '{"metadata":{"test_name":"parameterized_test_succeed[str1]"}}', (Tags.TEST_SKIP_REASON): "Skipped by Datadog Intelligent Test Runner", (Tags.TEST_SKIPPED_BY_ITR): true ] - testTags_1 = [(Tags.TEST_PARAMETERS): '{"metadata":{"test_name":"parameterized_test_succeed[1]"}}'] + testTags_1 = [(Tags.TEST_PARAMETERS): '{"metadata":{"test_name":"parameterized_test_succeed[\\"str2\\"]"}}'] } def "test ITR unskippable"() { diff --git a/dd-java-agent/instrumentation/junit-4.10/src/test/java/org/example/TestParameterized.java b/dd-java-agent/instrumentation/junit-4.10/src/test/java/org/example/TestParameterized.java index 8548bed51b7..4ddfbab870e 100644 --- a/dd-java-agent/instrumentation/junit-4.10/src/test/java/org/example/TestParameterized.java +++ b/dd-java-agent/instrumentation/junit-4.10/src/test/java/org/example/TestParameterized.java @@ -11,7 +11,7 @@ @RunWith(Parameterized.class) public class TestParameterized { - @Parameterized.Parameters + @Parameterized.Parameters(name = "{1}") public static Collection data() { return Arrays.asList( new Object[][] {{new ParamObject(), "str1", 0}, {new ParamObject(), "\"str2\"", 1}}); diff --git a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingIterator.java b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingIterator.java index 7e14f23d69d..63caed72adc 100644 --- a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingIterator.java +++ b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/TracingIterator.java @@ -90,10 +90,11 @@ protected void startNewRecordSpan(ConsumerRecord val) { sortedTags.put(GROUP_TAG, group); sortedTags.put(TOPIC_TAG, val.topic()); sortedTags.put(TYPE_TAG, "kafka"); - + final long payloadSize = + span.traceConfig().isDataStreamsEnabled() ? computePayloadSizeBytes(val) : 0; AgentTracer.get() .getDataStreamsMonitoring() - .setCheckpoint(span, sortedTags, val.timestamp(), computePayloadSizeBytes(val)); + .setCheckpoint(span, sortedTags, val.timestamp(), payloadSize); } else { span = startSpan(operationName, null); } diff --git a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/Utils.java b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/Utils.java index 5a1fdc86e50..09f273f415a 100644 --- a/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/Utils.java +++ b/dd-java-agent/instrumentation/kafka-clients-0.11/src/main/java/datadog/trace/instrumentation/kafka_clients/Utils.java @@ -6,7 +6,7 @@ import org.apache.kafka.common.header.Headers; public final class Utils { - private Utils() {} // prevent instanciation + private Utils() {} // prevent instantiation // this method is used in kafka-clients and kafka-streams instrumentations public static long computePayloadSizeBytes(ConsumerRecord val) { diff --git a/dd-java-agent/instrumentation/kafka-streams-0.11/src/main/java/datadog/trace/instrumentation/kafka_streams/KafkaStreamTaskInstrumentation.java b/dd-java-agent/instrumentation/kafka-streams-0.11/src/main/java/datadog/trace/instrumentation/kafka_streams/KafkaStreamTaskInstrumentation.java index 871b3fdc240..2eabe708bfc 100644 --- a/dd-java-agent/instrumentation/kafka-streams-0.11/src/main/java/datadog/trace/instrumentation/kafka_streams/KafkaStreamTaskInstrumentation.java +++ b/dd-java-agent/instrumentation/kafka-streams-0.11/src/main/java/datadog/trace/instrumentation/kafka_streams/KafkaStreamTaskInstrumentation.java @@ -245,10 +245,11 @@ public static void start( } sortedTags.put(TOPIC_TAG, record.topic()); sortedTags.put(TYPE_TAG, "kafka"); + final long payloadSize = + span.traceConfig().isDataStreamsEnabled() ? computePayloadSizeBytes(record.value) : 0; AgentTracer.get() .getDataStreamsMonitoring() - .setCheckpoint( - span, sortedTags, record.timestamp, computePayloadSizeBytes(record.value)); + .setCheckpoint(span, sortedTags, record.timestamp, payloadSize); } else { span = startSpan(KAFKA_CONSUME, null); } diff --git a/dd-java-agent/instrumentation/karate/src/main/java/datadog/trace/instrumentation/karate/KarateTracingHook.java b/dd-java-agent/instrumentation/karate/src/main/java/datadog/trace/instrumentation/karate/KarateTracingHook.java index d5ceb88c976..396988a170c 100644 --- a/dd-java-agent/instrumentation/karate/src/main/java/datadog/trace/instrumentation/karate/KarateTracingHook.java +++ b/dd-java-agent/instrumentation/karate/src/main/java/datadog/trace/instrumentation/karate/KarateTracingHook.java @@ -20,14 +20,17 @@ import datadog.trace.bootstrap.instrumentation.api.Tags; import java.util.Collection; -// FIXME nikita: do not trace Karate tests in JUnit 4 / JUnit 5 instrumentations public class KarateTracingHook implements RuntimeHook { private static final String FRAMEWORK_NAME = "karate"; private static final String FRAMEWORK_VERSION = FileUtils.KARATE_VERSION; + private static final String KARATE_STEP_SPAN_NAME = "karate.step"; @Override public boolean beforeFeature(FeatureRuntime fr) { + if (skipTracking(fr)) { + return true; + } Feature feature = KarateUtils.getFeature(fr); Suite suite = fr.suite; TestEventsHandlerHolder.TEST_EVENTS_HANDLER.onTestSuiteStart( @@ -42,6 +45,9 @@ public boolean beforeFeature(FeatureRuntime fr) { @Override public void afterFeature(FeatureRuntime fr) { + if (skipTracking(fr)) { + return; + } String featureName = KarateUtils.getFeature(fr).getNameForReport(); FeatureResult result = fr.result; if (result.isFailed()) { @@ -55,6 +61,9 @@ public void afterFeature(FeatureRuntime fr) { @Override public boolean beforeScenario(ScenarioRuntime sr) { + if (skipTracking(sr)) { + return true; + } Scenario scenario = sr.scenario; Feature feature = scenario.getFeature(); @@ -70,7 +79,7 @@ public boolean beforeScenario(ScenarioRuntime sr) { TestEventsHandlerHolder.TEST_EVENTS_HANDLER.onTestIgnore( featureName, scenarioName, - scenario.getRefId(), + sr, FRAMEWORK_NAME, FRAMEWORK_VERSION, parameters, @@ -86,7 +95,7 @@ public boolean beforeScenario(ScenarioRuntime sr) { TestEventsHandlerHolder.TEST_EVENTS_HANDLER.onTestStart( featureName, scenarioName, - scenario.getRefId(), + sr, FRAMEWORK_NAME, FRAMEWORK_VERSION, parameters, @@ -99,6 +108,9 @@ public boolean beforeScenario(ScenarioRuntime sr) { @Override public void afterScenario(ScenarioRuntime sr) { + if (skipTracking(sr)) { + return; + } Scenario scenario = sr.scenario; Feature feature = scenario.getFeature(); @@ -111,25 +123,23 @@ public void afterScenario(ScenarioRuntime sr) { featureName, null, scenarioName, - scenario.getRefId(), + sr, KarateUtils.getParameters(scenario), result.getError()); } else if (result.getStepResults().isEmpty()) { TestEventsHandlerHolder.TEST_EVENTS_HANDLER.onTestSkip( - featureName, - null, - scenarioName, - scenario.getRefId(), - KarateUtils.getParameters(scenario), - null); + featureName, null, scenarioName, sr, KarateUtils.getParameters(scenario), null); } TestEventsHandlerHolder.TEST_EVENTS_HANDLER.onTestFinish( - featureName, null, scenarioName, scenario.getRefId(), KarateUtils.getParameters(scenario)); + featureName, null, scenarioName, sr, KarateUtils.getParameters(scenario)); } @Override public boolean beforeStep(Step step, ScenarioRuntime sr) { - AgentSpan span = AgentTracer.startSpan("karate", "karate.step"); + if (skipTracking(step)) { + return true; + } + AgentSpan span = AgentTracer.startSpan("karate", KARATE_STEP_SPAN_NAME); AgentScope scope = AgentTracer.activateSpan(span); String stepName = step.getPrefix() + " " + step.getText(); span.setResourceName(stepName); @@ -143,6 +153,10 @@ public boolean beforeStep(Step step, ScenarioRuntime sr) { @Override public void afterStep(StepResult result, ScenarioRuntime sr) { + if (skipTracking(result.getStep())) { + return; + } + AgentSpan span = AgentTracer.activeSpan(); if (span == null) { return; @@ -155,4 +169,22 @@ public void afterStep(StepResult result, ScenarioRuntime sr) { span.finish(); } + + private static boolean skipTracking(FeatureRuntime fr) { + // do not track nested feature calls + return !fr.caller.isNone(); + } + + private static boolean skipTracking(ScenarioRuntime sr) { + // do not track nested scenario calls and setup scenarios + return !sr.caller.isNone() || sr.tags.getTagKeys().contains("setup"); + } + + private static boolean skipTracking(Step step) { + // do not track steps that are not children of a tracked scenario or another tracked step + AgentSpan activeSpan = AgentTracer.activeSpan(); + return activeSpan == null + || (!KARATE_STEP_SPAN_NAME.contentEquals(activeSpan.getSpanName()) + && !Tags.SPAN_KIND_TEST.contentEquals(activeSpan.getSpanType())); + } } diff --git a/dd-java-agent/instrumentation/karate/src/test/groovy/KarateTest.groovy b/dd-java-agent/instrumentation/karate/src/test/groovy/KarateTest.groovy index 9b50d3a501d..4eeccb3c29b 100644 --- a/dd-java-agent/instrumentation/karate/src/test/groovy/KarateTest.groovy +++ b/dd-java-agent/instrumentation/karate/src/test/groovy/KarateTest.groovy @@ -13,6 +13,7 @@ import org.example.TestFailedKarate import org.example.TestParameterizedKarate import org.example.TestSucceedKarate import org.example.TestUnskippableKarate +import org.example.TestWithSetupKarate import org.junit.jupiter.api.Assumptions import org.junit.jupiter.engine.JupiterTestEngine import org.junit.platform.engine.DiscoverySelector @@ -50,6 +51,34 @@ class KarateTest extends CiVisibilityTest { }) } + def "test scenarios with setup generate spans"() { + given: + Assumptions.assumeTrue(isSetupTagSupported(FileUtils.KARATE_VERSION),"Current Karate version is ${FileUtils.KARATE_VERSION}, while @setup tags are supported starting with 1.3.0") + + when: + runTests(TestWithSetupKarate) + + then: + ListWriterAssert.assertTraces(TEST_WRITER, 3, false, SORT_TRACES_BY_DESC_SIZE_THEN_BY_NAMES, { + long testSessionId + long testModuleId + long testSuiteId + trace(3, true) { + testSessionId = testSessionSpan(it, 1, CIConstants.TEST_PASS) + testModuleId = testModuleSpan(it, 0, testSessionId, CIConstants.TEST_PASS) + testSuiteId = testSuiteSpan(it, 2, testSessionId, testModuleId, "[org/example/test_with_setup] test with setup", CIConstants.TEST_PASS, null, null, false, [], false) + } + trace(2, true) { + long testId = testSpan(it, 1, testSessionId, testModuleId, testSuiteId, "[org/example/test_with_setup] test with setup", "first scenario", null, CIConstants.TEST_PASS, [(Tags.TEST_PARAMETERS): '{"foo":"bar"}'], null, false, ['withSetup'], false, false) + karateStepSpan(it, 0, testId, "* print foo", 15, 15) + } + trace(2, true) { + long testId = testSpan(it, 1, testSessionId, testModuleId, testSuiteId, "[org/example/test_with_setup] test with setup", "second scenario", null, CIConstants.TEST_PASS, [(Tags.TEST_PARAMETERS): '{"foo":"bar"}'], null, false, ['withSetupOnce'], false, false) + karateStepSpan(it, 0, testId, "* print foo", 21, 21) + } + }) + } + def "test parameterized generates spans"() { setup: runTests(TestParameterizedKarate) @@ -305,4 +334,8 @@ class KarateTest extends CiVisibilityTest { // earlier Karate version contain a bug that does not allow skipping scenarios frameworkVersion >= "1.2.0" } + + boolean isSetupTagSupported(String frameworkVersion) { + frameworkVersion >= "1.3.0" + } } diff --git a/dd-java-agent/instrumentation/karate/src/test/java/org/example/TestWithSetupKarate.java b/dd-java-agent/instrumentation/karate/src/test/java/org/example/TestWithSetupKarate.java new file mode 100644 index 00000000000..4bea15c32f8 --- /dev/null +++ b/dd-java-agent/instrumentation/karate/src/test/java/org/example/TestWithSetupKarate.java @@ -0,0 +1,11 @@ +package org.example; + +import com.intuit.karate.junit5.Karate; + +public class TestWithSetupKarate { + + @Karate.Test + public Karate testSucceed() { + return Karate.run("classpath:org/example/test_with_setup.feature"); + } +} diff --git a/dd-java-agent/instrumentation/karate/src/test/java/org/example/karate-config.js b/dd-java-agent/instrumentation/karate/src/test/java/org/example/karate-config.js new file mode 100644 index 00000000000..7e70e8ac51b --- /dev/null +++ b/dd-java-agent/instrumentation/karate/src/test/java/org/example/karate-config.js @@ -0,0 +1,2 @@ +function fn() { +} diff --git a/dd-java-agent/instrumentation/karate/src/test/java/org/example/test_with_setup.feature b/dd-java-agent/instrumentation/karate/src/test/java/org/example/test_with_setup.feature new file mode 100644 index 00000000000..abccf348ebd --- /dev/null +++ b/dd-java-agent/instrumentation/karate/src/test/java/org/example/test_with_setup.feature @@ -0,0 +1,27 @@ +Feature: test with setup + + @setup + Scenario: setup scenario + * call read('@setupStep') + * def data = + """ + [ + {foo: "bar"} + ] + """ + + @withSetup + Scenario Outline: first scenario + * print foo + Examples: + | karate.setup().data | + + @withSetupOnce + Scenario Outline: second scenario + * print foo + Examples: + | karate.setupOnce().data | + + @ignore @setupStep + Scenario: setup step + * print 'test' diff --git a/dd-java-agent/instrumentation/maven-3.2.1/build.gradle b/dd-java-agent/instrumentation/maven-3.2.1/build.gradle index 488c0c64e49..69a0df3b0f3 100644 --- a/dd-java-agent/instrumentation/maven-3.2.1/build.gradle +++ b/dd-java-agent/instrumentation/maven-3.2.1/build.gradle @@ -23,7 +23,7 @@ dependencies { testImplementation group: 'org.apache.maven.resolver', name: 'maven-resolver-transport-http', version: '1.0.3' latestDepTestImplementation group: 'org.apache.maven', name: 'maven-embedder', version: '+' - latestDepTestImplementation group: 'org.apache.maven.resolver', name: 'maven-resolver-connector-basic', version: '+' - latestDepTestImplementation group: 'org.apache.maven.resolver', name: 'maven-resolver-transport-http', version: '+' + latestDepTestImplementation group: 'org.apache.maven.resolver', name: 'maven-resolver-connector-basic', version: '1.+' + latestDepTestImplementation group: 'org.apache.maven.resolver', name: 'maven-resolver-transport-http', version: '1.+' latestDepTestImplementation group: 'org.fusesource.jansi', name: 'jansi', version: '+' } diff --git a/dd-java-agent/instrumentation/maven-3.2.1/src/main/java/datadog/trace/instrumentation/maven3/MavenLifecycleParticipant.java b/dd-java-agent/instrumentation/maven-3.2.1/src/main/java/datadog/trace/instrumentation/maven3/MavenLifecycleParticipant.java index 657ed2cd706..4e46b90746e 100644 --- a/dd-java-agent/instrumentation/maven-3.2.1/src/main/java/datadog/trace/instrumentation/maven3/MavenLifecycleParticipant.java +++ b/dd-java-agent/instrumentation/maven-3.2.1/src/main/java/datadog/trace/instrumentation/maven3/MavenLifecycleParticipant.java @@ -94,7 +94,7 @@ public void afterProjectsRead(MavenSession session) { String startCommand = MavenUtils.getCommandLine(session); String mavenVersion = MavenUtils.getMavenVersion(session); buildEventsHandler.onTestSessionStart( - request, projectName, projectRoot, startCommand, "maven", mavenVersion); + request, projectName, projectRoot, startCommand, "maven", mavenVersion, null); if (!config.isCiVisibilityAutoConfigurationEnabled()) { return; diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-0.3/src/test/groovy/OpenTelemetryTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-0.3/src/test/groovy/OpenTelemetryTest.groovy index 74d25cb2475..fa10dd5bd6f 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-0.3/src/test/groovy/OpenTelemetryTest.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-0.3/src/test/groovy/OpenTelemetryTest.groovy @@ -1,8 +1,11 @@ import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.DDSpanId import datadog.trace.api.DDTags import datadog.trace.api.DDTraceId import datadog.trace.api.interceptor.MutableSpan import datadog.trace.core.propagation.PropagationTags + +import static datadog.trace.api.TracePropagationStyle.NONE import static datadog.trace.api.sampling.PrioritySampling.* import static datadog.trace.api.sampling.SamplingMechanism.* import datadog.trace.context.TraceScope @@ -135,11 +138,11 @@ class OpenTelemetryTest extends AgentTestRunner { setup: def builder = tracer.spanBuilder("some name") if (parentId) { - def ctx = new ExtractedContext(DDTraceId.ONE, parentId, SAMPLER_DROP, null, PropagationTags.factory().empty()) + def ctx = new ExtractedContext(DDTraceId.ONE, parentId, SAMPLER_DROP, null, PropagationTags.factory().empty(), NONE) builder.setParent(tracer.converter.toSpanContext(ctx)) } if (linkId) { - def ctx = new ExtractedContext(DDTraceId.ONE, linkId, SAMPLER_DROP, null, PropagationTags.factory().empty()) + def ctx = new ExtractedContext(DDTraceId.ONE, linkId, SAMPLER_DROP, null, PropagationTags.factory().empty(), NONE) builder.addLink(tracer.converter.toSpanContext(ctx)) } def result = builder.startSpan() @@ -270,13 +273,24 @@ class OpenTelemetryTest extends AgentTestRunner { httpPropagator.inject(context, textMap, new TextMapSetter()) then: + def expectedTraceparent = "00-${span.delegate.traceId.toHexStringPadded(32)}" + + "-${DDSpanId.toHexStringPadded(span.delegate.spanId)}" + + "-" + (propagatedPriority > 0 ? "01" : "00") + def expectedTracestate = "dd=s:${propagatedPriority}" + def expectedDatadogTags = null + if (propagatedMechanism != UNKNOWN) { + expectedDatadogTags = "_dd.p.dm=-" + propagatedMechanism + expectedTracestate+= ";t.dm:-" + propagatedMechanism + } def expectedTextMap = [ "x-datadog-trace-id" : "$span.delegate.traceId", "x-datadog-parent-id" : "$span.delegate.spanId", "x-datadog-sampling-priority": propagatedPriority.toString(), + "traceparent" : expectedTraceparent, + "tracestate" : expectedTracestate, ] - if (propagatedMechanism != UNKNOWN) { - expectedTextMap.put("x-datadog-tags", "_dd.p.dm=-" + propagatedMechanism) + if (expectedDatadogTags != null) { + expectedTextMap.put("x-datadog-tags", expectedDatadogTags) } textMap == expectedTextMap diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/build.gradle b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/build.gradle index 79da23c2d19..3c689b66333 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/build.gradle +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/build.gradle @@ -17,5 +17,6 @@ dependencies { compileOnly group: 'com.google.auto.value', name: 'auto-value-annotations', version: '1.6.6' testImplementation group: 'io.opentelemetry', name: 'opentelemetry-api', version: openTelemetryVersion + testImplementation group: 'org.skyscreamer', name: 'jsonassert', version: '1.5.1' latestDepTestImplementation group: 'io.opentelemetry', name: 'opentelemetry-api', version: '1+' } diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java index 35f0425cb90..09cb8bc26b6 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/OpenTelemetryInstrumentation.java @@ -64,6 +64,8 @@ public String[] helperClassNames() { packageName + ".context.propagation.AgentTextMapPropagator", packageName + ".context.propagation.OtelContextPropagators", packageName + ".trace.OtelExtractedContext", + packageName + ".trace.OtelConventions", + packageName + ".trace.OtelConventions$1", packageName + ".trace.OtelSpan", packageName + ".trace.OtelSpan$1", packageName + ".trace.OtelSpan$NoopSpan", @@ -71,6 +73,8 @@ public String[] helperClassNames() { packageName + ".trace.OtelSpanBuilder", packageName + ".trace.OtelSpanBuilder$1", packageName + ".trace.OtelSpanContext", + packageName + ".trace.OtelSpanLink", + packageName + ".trace.OtelSpanLink$1", packageName + ".trace.OtelTracer", packageName + ".trace.OtelTracerBuilder", packageName + ".trace.OtelTracerProvider", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java index df0676a5d6d..9e420e855ff 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextInstrumentation.java @@ -56,6 +56,8 @@ public String[] helperClassNames() { packageName + ".OtelContext", packageName + ".OtelScope", ROOT_PACKAGE_NAME + ".trace.OtelExtractedContext", + ROOT_PACKAGE_NAME + ".trace.OtelConventions", + ROOT_PACKAGE_NAME + ".trace.OtelConventions$1", ROOT_PACKAGE_NAME + ".trace.OtelSpan", ROOT_PACKAGE_NAME + ".trace.OtelSpan$1", ROOT_PACKAGE_NAME + ".trace.OtelSpan$NoopSpan", @@ -63,6 +65,8 @@ public String[] helperClassNames() { ROOT_PACKAGE_NAME + ".trace.OtelSpanBuilder", ROOT_PACKAGE_NAME + ".trace.OtelSpanBuilder$1", ROOT_PACKAGE_NAME + ".trace.OtelSpanContext", + ROOT_PACKAGE_NAME + ".trace.OtelSpanLink", + ROOT_PACKAGE_NAME + ".trace.OtelSpanLink$1", ROOT_PACKAGE_NAME + ".trace.OtelTracer", ROOT_PACKAGE_NAME + ".trace.OtelTracerBuilder", ROOT_PACKAGE_NAME + ".trace.OtelTracerProvider", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java index b7130cd55a3..0f7e6db5b0a 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/context/OpenTelemetryContextStorageInstrumentation.java @@ -62,6 +62,8 @@ public String[] helperClassNames() { packageName + ".OtelContext", packageName + ".OtelScope", ROOT_PACKAGE_NAME + ".trace.OtelExtractedContext", + ROOT_PACKAGE_NAME + ".trace.OtelConventions", + ROOT_PACKAGE_NAME + ".trace.OtelConventions$1", ROOT_PACKAGE_NAME + ".trace.OtelSpan", ROOT_PACKAGE_NAME + ".trace.OtelSpan$1", ROOT_PACKAGE_NAME + ".trace.OtelSpan$NoopSpan", @@ -69,6 +71,8 @@ public String[] helperClassNames() { ROOT_PACKAGE_NAME + ".trace.OtelSpanBuilder", ROOT_PACKAGE_NAME + ".trace.OtelSpanBuilder$1", ROOT_PACKAGE_NAME + ".trace.OtelSpanContext", + ROOT_PACKAGE_NAME + ".trace.OtelSpanLink", + ROOT_PACKAGE_NAME + ".trace.OtelSpanLink$1", ROOT_PACKAGE_NAME + ".trace.OtelTracer", ROOT_PACKAGE_NAME + ".trace.OtelTracerBuilder", ROOT_PACKAGE_NAME + ".trace.OtelTracerProvider", diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelConventions.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelConventions.java new file mode 100644 index 00000000000..a29b14712e1 --- /dev/null +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelConventions.java @@ -0,0 +1,223 @@ +package datadog.trace.instrumentation.opentelemetry14.trace; + +import static datadog.trace.api.DDTags.ANALYTICS_SAMPLE_RATE; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CONSUMER; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_PRODUCER; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER; +import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.api.trace.SpanKind.CONSUMER; +import static io.opentelemetry.api.trace.SpanKind.INTERNAL; +import static io.opentelemetry.api.trace.SpanKind.PRODUCER; +import static io.opentelemetry.api.trace.SpanKind.SERVER; +import static java.util.Locale.ROOT; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import io.opentelemetry.api.trace.SpanKind; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class OtelConventions { + static final String SPAN_KIND_INTERNAL = "internal"; + private static final Logger LOGGER = LoggerFactory.getLogger(OtelConventions.class); + private static final String OPERATION_NAME_SPECIFIC_ATTRIBUTE = "operation.name"; + private static final String SERVICE_NAME_SPECIFIC_ATTRIBUTE = "service.name"; + private static final String RESOURCE_NAME_SPECIFIC_ATTRIBUTE = "resource.name"; + private static final String SPAN_TYPE_SPECIFIC_ATTRIBUTES = "span.type"; + private static final String ANALYTICS_EVENT_SPECIFIC_ATTRIBUTES = "analytics.event"; + + private OtelConventions() {} + + /** + * Convert OpenTelemetry {@link SpanKind} to Datadog span type. + * + * @param spanKind The OpenTelemetry span kind to convert. + * @return The related Datadog span type. + */ + public static String toSpanType(SpanKind spanKind) { + switch (spanKind) { + case CLIENT: + return SPAN_KIND_CLIENT; + case SERVER: + return SPAN_KIND_SERVER; + case PRODUCER: + return SPAN_KIND_PRODUCER; + case CONSUMER: + return SPAN_KIND_CONSUMER; + case INTERNAL: + return SPAN_KIND_INTERNAL; + default: + return spanKind.toString().toLowerCase(ROOT); + } + } + + /** + * Convert Datadog span type to OpenTelemetry {@link SpanKind}. + * + * @param spanType The span type to convert. + * @return The related OpenTelemetry {@link SpanKind}. + */ + public static SpanKind toSpanKind(String spanType) { + if (spanType == null) { + return INTERNAL; + } + switch (spanType) { + case SPAN_KIND_CLIENT: + return CLIENT; + case SPAN_KIND_SERVER: + return SERVER; + case SPAN_KIND_PRODUCER: + return PRODUCER; + case SPAN_KIND_CONSUMER: + return CONSUMER; + default: + return INTERNAL; + } + } + + public static void applyConventions(AgentSpan span) { + String serviceName = getStringAttribute(span, SERVICE_NAME_SPECIFIC_ATTRIBUTE); + if (serviceName != null) { + span.setServiceName(serviceName); + } + String resourceName = getStringAttribute(span, RESOURCE_NAME_SPECIFIC_ATTRIBUTE); + if (resourceName != null) { + span.setResourceName(resourceName); + } + String spanType = getStringAttribute(span, SPAN_TYPE_SPECIFIC_ATTRIBUTES); + if (spanType != null) { + span.setSpanType(spanType); + } + Boolean analyticsEvent = getBooleanAttribute(span, ANALYTICS_EVENT_SPECIFIC_ATTRIBUTES); + if (analyticsEvent != null) { + span.setMetric(ANALYTICS_SAMPLE_RATE, analyticsEvent ? 1 : 0); + } + applyOperationName(span); + } + + private static void applyOperationName(AgentSpan span) { + String operationName = getStringAttribute(span, OPERATION_NAME_SPECIFIC_ATTRIBUTE); + if (operationName == null) { + operationName = computeOperationName(span); + } + span.setOperationName(operationName.toLowerCase(ROOT)); + } + + private static String computeOperationName(AgentSpan span) { + SpanKind spanKind = toSpanKind(span.getSpanType()); + /* + * HTTP convention: https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/http/ + */ + String httpRequestMethod = getStringAttribute(span, "http.request.method"); + if (spanKind == SERVER && httpRequestMethod != null) { + return "http.server.request"; + } + if (spanKind == CLIENT && httpRequestMethod != null) { + return "http.client.request"; + } + /* + * Database convention: https://opentelemetry.io/docs/specs/semconv/database/database-spans/ + */ + String dbSystem = getStringAttribute(span, "db.system"); + if (spanKind == CLIENT && dbSystem != null) { + return dbSystem + ".query"; + } + /* + * Messaging: https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/ + */ + String messagingSystem = getStringAttribute(span, "messaging.system"); + String messagingOperation = getStringAttribute(span, "messaging.operation"); + if ((spanKind == CONSUMER || spanKind == PRODUCER || spanKind == CLIENT || spanKind == SERVER) + && messagingSystem != null + && messagingOperation != null) { + return messagingSystem + "." + messagingOperation; + } + /* + * AWS: https://opentelemetry.io/docs/specs/semconv/cloud-providers/aws-sdk/ + */ + String rpcSystem = getStringAttribute(span, "rpc.system"); + if (spanKind == CLIENT && "aws-api".equals(rpcSystem)) { + String service = getStringAttribute(span, "rpc.service"); + if (service == null) { + return "aws.client.request"; + } else { + return "aws." + service + ".request"; + } + } + /* + * RPC: https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/ + */ + if (spanKind == CLIENT && rpcSystem != null) { + return rpcSystem + ".client.request"; + } + if (spanKind == SERVER && rpcSystem != null) { + return rpcSystem + ".server.request"; + } + /* + * FaaS: + * https://opentelemetry.io/docs/specs/semconv/faas/faas-spans/#incoming-faas-span-attributes + * https://opentelemetry.io/docs/specs/semconv/faas/faas-spans/#outgoing-invocations + */ + String faasInvokedProvider = getStringAttribute(span, "faas.invoked_provider"); + String faasInvokedName = getStringAttribute(span, "faas.invoked_name"); + if (spanKind == CLIENT && faasInvokedProvider != null && faasInvokedName != null) { + return faasInvokedProvider + "." + faasInvokedName + ".invoke"; + } + String faasTrigger = getStringAttribute(span, "faas.trigger"); + if (spanKind == SERVER && faasTrigger != null) { + return faasTrigger + ".invoke"; + } + /* + * GraphQL: https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/instrumentation/graphql/ + */ + String graphqlOperationType = getStringAttribute(span, "graphql.operation.type"); + if (graphqlOperationType != null) { + return "graphql.server.request"; + } + /* + * Generic server / client: https://opentelemetry.io/docs/specs/semconv/http/http-spans/ + */ + String networkProtocolName = getStringAttribute(span, "network.protocol.name"); + if (spanKind == SERVER) { + return networkProtocolName == null + ? "server.request" + : networkProtocolName + ".server.request"; + } + if (spanKind == CLIENT) { + return networkProtocolName == null + ? "client.request" + : networkProtocolName + ".client.request"; + } + // Fallback if no convention match + return spanKind.name(); + } + + @Nullable + private static String getStringAttribute(AgentSpan span, String key) { + Object tag = span.getTag(key); + if (tag == null) { + return null; + } else if (!(tag instanceof String)) { + LOGGER.debug("Span attributes {} is not a string", key); + return key; + } + return (String) tag; + } + + @Nullable + private static Boolean getBooleanAttribute(AgentSpan span, String key) { + Object tag = span.getTag(key); + if (tag == null) { + return null; + } + if (tag instanceof Boolean) { + return (Boolean) tag; + } else if (tag instanceof String) { + return Boolean.parseBoolean((String) tag); + } else { + LOGGER.debug("Span attributes {} is not a boolean", key); + return null; + } + } +} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpan.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpan.java index 1d4cb72aa16..9845e531558 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpan.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpan.java @@ -1,6 +1,7 @@ package datadog.trace.instrumentation.opentelemetry14.trace; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.applyConventions; import static io.opentelemetry.api.trace.StatusCode.ERROR; import static io.opentelemetry.api.trace.StatusCode.OK; import static io.opentelemetry.api.trace.StatusCode.UNSET; @@ -106,7 +107,7 @@ public Span recordException(Throwable exception, Attributes additionalAttributes @Override public Span updateName(String name) { if (this.recording) { - this.delegate.setOperationName(name); + this.delegate.setResourceName(name); } return this; } @@ -114,12 +115,14 @@ public Span updateName(String name) { @Override public void end() { this.recording = false; + applyConventions(this.delegate); this.delegate.finish(); } @Override public void end(long timestamp, TimeUnit unit) { this.recording = false; + applyConventions(this.delegate); this.delegate.finish(unit.toMicros(timestamp)); } diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanBuilder.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanBuilder.java index b39118edcc9..36f8d59e504 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanBuilder.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanBuilder.java @@ -1,10 +1,11 @@ package datadog.trace.instrumentation.opentelemetry14.trace; +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.toSpanType; import static datadog.trace.instrumentation.opentelemetry14.trace.OtelExtractedContext.extract; +import static io.opentelemetry.api.trace.SpanKind.INTERNAL; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import datadog.trace.bootstrap.instrumentation.api.Tags; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; @@ -13,16 +14,17 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import java.util.List; -import java.util.Locale; import java.util.concurrent.TimeUnit; import javax.annotation.ParametersAreNonnullByDefault; @ParametersAreNonnullByDefault public class OtelSpanBuilder implements SpanBuilder { private final AgentTracer.SpanBuilder delegate; + private boolean spanKindSet; public OtelSpanBuilder(AgentTracer.SpanBuilder delegate) { this.delegate = delegate; + this.spanKindSet = false; } @Override @@ -43,13 +45,17 @@ public SpanBuilder setNoParent() { @Override public SpanBuilder addLink(SpanContext spanContext) { - // Not supported + if (spanContext.isValid()) { + this.delegate.withLink(new OtelSpanLink(spanContext)); + } return this; } @Override public SpanBuilder addLink(SpanContext spanContext, Attributes attributes) { - // Not supported + if (spanContext.isValid()) { + this.delegate.withLink(new OtelSpanLink(spanContext, attributes)); + } return this; } @@ -106,24 +112,11 @@ public SpanBuilder setAttribute(AttributeKey key, T value) { @Override public SpanBuilder setSpanKind(SpanKind spanKind) { - this.delegate.withSpanType(toSpanType(spanKind)); - return this; - } - - private static String toSpanType(SpanKind spanKind) { - switch (spanKind) { - case CLIENT: - return Tags.SPAN_KIND_CLIENT; - case SERVER: - return Tags.SPAN_KIND_SERVER; - case PRODUCER: - return Tags.SPAN_KIND_PRODUCER; - case CONSUMER: - return Tags.SPAN_KIND_CONSUMER; - default: - case INTERNAL: - return spanKind.toString().toLowerCase(Locale.ROOT); + if (spanKind != null) { + this.delegate.withSpanType(toSpanType(spanKind)); + this.spanKindSet = true; } + return this; } @Override @@ -134,6 +127,10 @@ public SpanBuilder setStartTimestamp(long startTimestamp, TimeUnit unit) { @Override public Span startSpan() { + // Ensure the span kind is set + if (!this.spanKindSet) { + setSpanKind(INTERNAL); + } AgentSpan delegate = this.delegate.start(); return new OtelSpan(delegate); } diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanLink.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanLink.java new file mode 100644 index 00000000000..6e46938be13 --- /dev/null +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelSpanLink.java @@ -0,0 +1,86 @@ +package datadog.trace.instrumentation.opentelemetry14.trace; + +import datadog.trace.api.DDSpanId; +import datadog.trace.api.DDTraceId; +import datadog.trace.bootstrap.instrumentation.api.SpanLink; +import datadog.trace.bootstrap.instrumentation.api.SpanLinkAttributes; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceState; +import java.util.List; + +public class OtelSpanLink extends SpanLink { + private static final int TRACESTATE_MAX_SIZE = 512; + private static final char TRACESTATE_ENTRY_DELIMITER = ','; + private static final char TRACESTATE_KEY_VALUE_DELIMITER = '='; + + public OtelSpanLink(SpanContext spanContext) { + this(spanContext, io.opentelemetry.api.common.Attributes.empty()); + } + + public OtelSpanLink(SpanContext spanContext, io.opentelemetry.api.common.Attributes attributes) { + super( + DDTraceId.fromHex(spanContext.getTraceId()), + DDSpanId.fromHex(spanContext.getSpanId()), + spanContext.isSampled() ? SAMPLED_FLAG : DEFAULT_FLAGS, + encodeTraceState(spanContext.getTraceState()), + convertAttributes(attributes)); + } + + // Inspired from W3CTraceContextEncoding.encodeTraceState only available in API later versions. + private static String encodeTraceState(TraceState traceState) { + if (traceState.isEmpty()) { + return ""; + } + StringBuilder builder = new StringBuilder(TRACESTATE_MAX_SIZE); + traceState.forEach( + (key, value) -> { + if (builder.length() != 0) { + builder.append(TRACESTATE_ENTRY_DELIMITER); + } + builder.append(key).append(TRACESTATE_KEY_VALUE_DELIMITER).append(value); + }); + return builder.toString(); + } + + private static Attributes convertAttributes(io.opentelemetry.api.common.Attributes attributes) { + if (attributes.isEmpty()) { + return SpanLinkAttributes.EMPTY; + } + SpanLinkAttributes.Builder builder = SpanLinkAttributes.builder(); + attributes.forEach( + (attributeKey, value) -> { + String key = attributeKey.getKey(); + switch (attributeKey.getType()) { + case STRING: + builder.put(key, (String) value); + break; + case BOOLEAN: + builder.put(key, (boolean) value); + break; + case LONG: + builder.put(key, (long) value); + break; + case DOUBLE: + builder.put(key, (double) value); + break; + case STRING_ARRAY: + //noinspection unchecked + builder.putStringArray(key, (List) value); + break; + case BOOLEAN_ARRAY: + //noinspection unchecked + builder.putBooleanArray(key, (List) value); + break; + case LONG_ARRAY: + //noinspection unchecked + builder.putLongArray(key, (List) value); + break; + case DOUBLE_ARRAY: + //noinspection unchecked + builder.putDoubleArray(key, (List) value); + break; + } + }); + return builder.build(); + } +} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelTracer.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelTracer.java index 0ce1f69bbd9..65980b38a9d 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelTracer.java +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/main/java/datadog/trace/instrumentation/opentelemetry14/trace/OtelTracer.java @@ -1,5 +1,7 @@ package datadog.trace.instrumentation.opentelemetry14.trace; +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.SPAN_KIND_INTERNAL; + import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.Tracer; @@ -19,7 +21,7 @@ public OtelTracer(String instrumentationScopeName) { @Override public SpanBuilder spanBuilder(String spanName) { AgentTracer.SpanBuilder delegate = - this.tracer.buildSpan(INSTRUMENTATION_NAME, spanName).withResourceName(spanName); + this.tracer.buildSpan(INSTRUMENTATION_NAME, SPAN_KIND_INTERNAL).withResourceName(spanName); return new OtelSpanBuilder(delegate); } } diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14ConventionsTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14ConventionsTest.groovy new file mode 100644 index 00000000000..39ccac0a843 --- /dev/null +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14ConventionsTest.groovy @@ -0,0 +1,165 @@ +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.DDTags +import io.opentelemetry.api.GlobalOpenTelemetry +import io.opentelemetry.context.Context +import io.opentelemetry.context.ThreadLocalContextStorage +import spock.lang.Subject + +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.toSpanType +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static io.opentelemetry.api.trace.SpanKind.CONSUMER +import static io.opentelemetry.api.trace.SpanKind.INTERNAL +import static io.opentelemetry.api.trace.SpanKind.PRODUCER +import static io.opentelemetry.api.trace.SpanKind.SERVER + +class OpenTelemetry14ConventionsTest extends AgentTestRunner { + @Subject + def tracer = GlobalOpenTelemetry.get().tracerProvider.get("conventions") + + @Override + void configurePreAgent() { + super.configurePreAgent() + + injectSysConfig("dd.integration.opentelemetry.experimental.enabled", "true") + } + + def "test span name conventions"() { + when: + def builder = tracer.spanBuilder("some-name") + .setSpanKind(kind) + attributes.forEach { key, value -> builder.setAttribute(key, value) } + builder.startSpan() + .end() + + then: + assertTraces(1) { + trace(1) { + span { + parent() + spanType toSpanType(kind == null ? INTERNAL : kind) + operationName "$expectedOperationName" + resourceName "some-name" + } + } + } + + where: + kind | attributes | expectedOperationName + // Fallback behavior + null | [:] | "internal" + // Internal spans + INTERNAL | [:] | "internal" + // Server spans + SERVER | [:] | "server.request" + SERVER | ["http.request.method": "GET"] | "http.server.request" + SERVER | ["http.request.method": "GET"] | "http.server.request" + SERVER | ["network.protocol.name": "amqp"] | "amqp.server.request" + // Client spans + CLIENT | [:] | "client.request" + CLIENT | ["http.request.method": "GET"] | "http.client.request" + CLIENT | ["db.system": "mysql"] | "mysql.query" + CLIENT | ["network.protocol.name": "amqp"] | "amqp.client.request" + CLIENT | ["network.protocol.name": "AMQP"] | "amqp.client.request" + // Messaging spans + PRODUCER | [:] | "producer" + CONSUMER | [:] | "consumer" + CONSUMER | ["messaging.system": "rabbitmq", "messaging.operation": "publish"] | "rabbitmq.publish" + PRODUCER | ["messaging.system": "rabbitmq", "messaging.operation": "publish"] | "rabbitmq.publish" + CLIENT | ["messaging.system": "rabbitmq", "messaging.operation": "publish"] | "rabbitmq.publish" + SERVER | ["messaging.system": "rabbitmq", "messaging.operation": "publish"] | "rabbitmq.publish" + // RPC spans + CLIENT | ["rpc.system": "grpc"] | "grpc.client.request" + SERVER | ["rpc.system": "grpc"] | "grpc.server.request" + CLIENT | ["rpc.system": "aws-api"] | "aws.client.request" + CLIENT | ["rpc.system": "aws-api", "rpc.service": "helloworld"] | "aws.helloworld.request" + SERVER | ["rpc.system": "aws-api"] | "aws-api.server.request" + // FAAS spans + CLIENT | ["faas.invoked_provider": "alibaba_cloud", "faas.invoked_name": "my-function"] | "alibaba_cloud.my-function.invoke" + SERVER | ["faas.trigger": "datasource"] | "datasource.invoke" + // GraphQL spans + INTERNAL | ["graphql.operation.type": "query"] | "graphql.server.request" + null | ["graphql.operation.type": "query"] | "graphql.server.request" + // User override + CLIENT | ["db.system": "mysql", "operation.name": "db.query"] | "db.query" + CLIENT | ["db.system": "mysql", "operation.name": "DB.query"] | "db.query" + } + + def "test span specific tags"() { + when: + tracer.spanBuilder("some-name") + .setAttribute("service.name", "my-service") + .setAttribute("resource.name", "/my-resource") + .setAttribute("span.type", "$type") + .startSpan() + .end() + + + then: + assertTraces(1) { + trace(1) { + span { + parent() + spanType "$type" + operationName "$expectedOperationName" + resourceName "/my-resource" + serviceName "my-service" + } + } + } + + where: + type | expectedOperationName + SPAN_KIND_SERVER | "server.request" + SPAN_KIND_CLIENT | "client.request" + } + + def "test span analytics.event specific tag"() { + when: + tracer.spanBuilder("some-name") + .setAttribute("analytics.event", value) + .startSpan() + .end() + + + then: + assertTraces(1) { + trace(1) { + span { + parent() + operationName "internal" + spanType "internal" + tags { + defaultTags() + if (value != null) { + "analytics.event" value + "$DDTags.ANALYTICS_SAMPLE_RATE" expectedMetric + } + } + } + } + } + + where: + value | expectedMetric + true | 1 + Boolean.TRUE | 1 + false | 0 + Boolean.FALSE | 0 + null | 0 // Not used + "true" | 1 + "false" | 0 + "TRUE" | 1 + "something-else" | 0 + "" | 0 + } + + @Override + void cleanup() { + // Test for context leak + assert Context.current() == Context.root() + // Safely reset OTel context storage + ThreadLocalContextStorage.THREAD_LOCAL_STORAGE.remove() + } +} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy index ca0952048e5..3f4478f1887 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/OpenTelemetry14Test.groovy @@ -3,13 +3,21 @@ import datadog.trace.api.DDTags import datadog.trace.bootstrap.instrumentation.api.Tags import io.opentelemetry.api.GlobalOpenTelemetry import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.SpanContext import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.api.trace.TraceFlags +import io.opentelemetry.api.trace.TraceState import io.opentelemetry.context.Context import io.opentelemetry.context.ThreadLocalContextStorage +import org.skyscreamer.jsonassert.JSONAssert import spock.lang.Subject import java.security.InvalidParameterException +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.SPAN_KIND_INTERNAL +import static io.opentelemetry.api.trace.SpanKind.SERVER import static io.opentelemetry.api.trace.StatusCode.ERROR import static io.opentelemetry.api.trace.StatusCode.OK import static io.opentelemetry.api.trace.StatusCode.UNSET @@ -41,13 +49,15 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(2) { span { parent() - operationName "some-name" + operationName "internal" resourceName "some-name" + spanType "internal" } span { childOfPrevious() - operationName "other-name" + operationName "internal" resourceName "other-name" + spanType "internal" } } } @@ -69,11 +79,15 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(2) { span { parent() - operationName "some-name" + operationName "internal" + resourceName "some-name" + spanType "internal" } span { childOfPrevious() - operationName "other-name" + operationName "internal" + resourceName "other-name" + spanType "internal" } } } @@ -90,7 +104,6 @@ class OpenTelemetry14Test extends AgentTestRunner { TEST_WRITER.waitForTraces(1) def trace = TEST_WRITER.firstTrace() - then: trace.size() == 1 trace[0].spanId != 0 @@ -114,13 +127,17 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName "internal" + resourceName"some-name" + spanType "internal" } } trace(1) { span { parent() - operationName "other-name" + operationName "internal" + resourceName"other-name" + spanType "internal" } } } @@ -133,8 +150,6 @@ class OpenTelemetry14Test extends AgentTestRunner { anotherSpan.end() when: - // Adding link is not supported - builder.addLink(anotherSpan.getSpanContext()) // Adding event is not supported def result = builder.startSpan() result.addEvent("some-event") @@ -143,12 +158,167 @@ class OpenTelemetry14Test extends AgentTestRunner { then: assertTraces(2) { trace(1) { - span {} + span { + spanType "internal" + } + } + trace(1) { + span { + spanType "internal" + } + } + } + } + + def "test simple span links"() { + setup: + def traceId = "1234567890abcdef1234567890abcdef" as String + def spanId = "fedcba0987654321" as String + def traceState = TraceState.builder().put("string-key", "string-value").build() + + def expectedLinksTag = """ + [ + { traceId: "${traceId}", + spanId: "${spanId}", + traceFlags: 1, + traceState: "string-key=string-value"} + ]""" + + when: + def span1 =tracer.spanBuilder("some-name") + .addLink(SpanContext.getInvalid()) // Should not be added + .addLink(SpanContext.create(traceId, spanId, TraceFlags.getSampled(), traceState)) + .startSpan() + span1.end() + + then: + assertTraces(1) { + trace(1) { + span { + spanType "internal" + tags { + defaultTags() + tag("_dd.span_links", { JSONAssert.assertEquals(expectedLinksTag, it as String, true); return true }) + } + } + } + } + } + + def "test multiple span links"() { + setup: + def spanBuilder = tracer.spanBuilder("some-name") + + when: + def links = [] + 0..9.each { + def traceId = "1234567890abcdef1234567890abcde$it" as String + def spanId = "fedcba098765432$it" as String + def traceState = TraceState.builder().put('string-key', 'string-value'+it).build() + links << """{ traceId: "${traceId}", + spanId: "${spanId}", + traceFlags: 1, + traceState: "string-key=string-value$it"}""" + spanBuilder.addLink(SpanContext.create(traceId, spanId, TraceFlags.getSampled(), traceState)) + } + def expectedLinksTag = "[${links.join(',')}]" as String + + spanBuilder.startSpan().end() + + then: + assertTraces(1) { + trace(1) { + span { + spanType "internal" + tags { + defaultTags() + tag("_dd.span_links", { JSONAssert.assertEquals(expectedLinksTag, it as String, true); return true }) + } + } + } + } + } + + def "test span link attributes"() { + setup: + def traceId = "1234567890abcdef1234567890abcdef" as String + def spanId = "fedcba0987654321" as String + def traceState = TraceState.builder().put("string-key", "string-value").build() + + def expectedLinksTag = """ + [ + { traceId: "${traceId}", + spanId: "${spanId}", + traceFlags: 1, + traceState: "string-key=string-value" + ${ expectedAttributes == null ? "" : ", attributes: " + expectedAttributes }} + ]""" + + when: + def span1 =tracer.spanBuilder("some-name") + .addLink(SpanContext.create(traceId, spanId, TraceFlags.getSampled(), traceState), attributes) + .startSpan() + span1.end() + + then: + assertTraces(1) { + trace(1) { + span { + spanType "internal" + tags { + defaultTags() + tag("_dd.span_links", { JSONAssert.assertEquals(expectedLinksTag, it as String, true); return true }) + } + } + } + } + + where: + attributes | expectedAttributes + Attributes.empty() | null + Attributes.builder().put("string-key", "string-value").put("long-key", 123456789L).put("double-key", 1234.5678D).put("boolean-key-true", true).put("boolean-key-false", false).build() | '{ string-key: "string-value", long-key: "123456789", double-key: "1234.5678", boolean-key-true: "true", boolean-key-false: "false" }' + Attributes.builder().put("string-key-array", "string-value1", "string-value2", "string-value3").put("long-key-array", 123456L, 1234567L, 12345678L).put("double-key-array", 1234.5D, 1234.56D, 1234.567D).put("boolean-key-array", true, false, true).build() | '{ string-key-array.0: "string-value1", string-key-array.1: "string-value2", string-key-array.2: "string-value3", long-key-array.0: "123456", long-key-array.1: "1234567", long-key-array.2: "12345678", double-key-array.0: "1234.5", double-key-array.1: "1234.56", double-key-array.2: "1234.567", boolean-key-array.0: "true", boolean-key-array.1: "false", boolean-key-array.2: "true" }' + } + + def "test span links trace state"() { + setup: + def traceId = "1234567890abcdef1234567890abcdef" as String + def spanId = "fedcba0987654321" as String + + def expectedTraceStateJson = expectedTraceState == null ? '' : ", traceState: \"$expectedTraceState\"" + def expectedLinksTag = """ + [ + { traceId: "${traceId}", + spanId: "${spanId}", + traceFlags: 1 + $expectedTraceStateJson } + ]""" + + when: + def span1 =tracer.spanBuilder("some-name") + .addLink(SpanContext.create(traceId, spanId, TraceFlags.getSampled(), traceState)) + .startSpan() + span1.end() + + then: + assertTraces(1) { trace(1) { - span {} + span { + spanType "internal" + tags { + defaultTags() + tag("_dd.span_links", { JSONAssert.assertEquals(expectedLinksTag, it as String, true); return true }) + } + } } } + + where: + traceState | expectedTraceState + TraceState.getDefault() | null + TraceState.builder().put("key", "value").build() | 'key=value' + TraceState.builder().put("key1", "value1").put("key2", "value2").put("key3", "value3").put("key4", "value4").put("key5", "value5").build() | 'key5=value5,key4=value4,key3=value3,key2=value2,key1=value1' } def "test span attributes"() { @@ -195,7 +365,8 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName "internal" + spanType "internal" if (tagSpan) { resourceName "other-resource" } else if (tagBuilder) { @@ -280,7 +451,7 @@ class OpenTelemetry14Test extends AgentTestRunner { SpanKind.CONSUMER | Tags.SPAN_KIND_CONSUMER SpanKind.INTERNAL | "internal" SpanKind.PRODUCER | Tags.SPAN_KIND_PRODUCER - SpanKind.SERVER | Tags.SPAN_KIND_SERVER + SERVER | Tags.SPAN_KIND_SERVER } def "test span error status"() { @@ -297,8 +468,9 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName "internal" resourceName "some-name" + spanType "internal" errored true tags { @@ -349,8 +521,9 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName "internal" resourceName "some-name" + spanType "internal" errored false tags { defaultTags() @@ -390,8 +563,9 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName "internal" resourceName "some-name" + spanType "internal" errored false tags { defaultTags() @@ -405,16 +579,18 @@ class OpenTelemetry14Test extends AgentTestRunner { def "test span name update"() { setup: def builder = tracer.spanBuilder("some-name") - def result = builder.startSpan() + def result = builder.setSpanKind(SERVER).startSpan() expect: - result.delegate.operationName == "some-name" + result.delegate.operationName == SPAN_KIND_INTERNAL + result.delegate.resourceName == "some-name" when: result.updateName("other-name") then: - result.delegate.operationName == "other-name" + result.delegate.operationName == SPAN_KIND_INTERNAL + result.delegate.resourceName == "other-name" when: result.end() @@ -424,8 +600,9 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "other-name" - resourceName "some-name" + spanType SPAN_KIND_SERVER + operationName "server.request" + resourceName "other-name" } } } @@ -449,7 +626,9 @@ class OpenTelemetry14Test extends AgentTestRunner { trace(1) { span { parent() - operationName "some-name" + operationName "internal" + resourceName"some-name" + spanType "internal" errored true tags { defaultTags() diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/ContextTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/ContextTest.groovy index 3c1805133a8..470084b6406 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/ContextTest.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/ContextTest.groovy @@ -12,6 +12,7 @@ import spock.lang.Subject import static datadog.trace.bootstrap.instrumentation.api.ScopeSource.MANUAL import static datadog.trace.instrumentation.opentelemetry14.context.OtelContext.DATADOG_CONTEXT_ROOT_SPAN_KEY import static datadog.trace.instrumentation.opentelemetry14.context.OtelContext.OTEL_CONTEXT_SPAN_KEY +import static datadog.trace.instrumentation.opentelemetry14.trace.OtelConventions.SPAN_KIND_INTERNAL class ContextTest extends AgentTestRunner { @Subject @@ -188,7 +189,8 @@ class ContextTest extends AgentTestRunner { def activeSpan = TEST_TRACER.activeSpan() then: - activeSpan.operationName == "some-name" + activeSpan.operationName == SPAN_KIND_INTERNAL + activeSpan.resourceName == "some-name" DDSpanId.toHexStringPadded(activeSpan.spanId) == otelParentSpan.getSpanContext().spanId when: @@ -205,7 +207,8 @@ class ContextTest extends AgentTestRunner { activeSpan = TEST_TRACER.activeSpan() then: - activeSpan.operationName == "another-name" + activeSpan.operationName == SPAN_KIND_INTERNAL + activeSpan.resourceName == "another-name" DDSpanId.toHexStringPadded(activeSpan.spanId) == otelGrandChildSpan.getSpanContext().spanId when: @@ -221,7 +224,9 @@ class ContextTest extends AgentTestRunner { trace(3) { span { parent() - operationName "some-name" + operationName "internal" + resourceName "some-name" + spanType "internal" } span { childOfPrevious() @@ -229,7 +234,9 @@ class ContextTest extends AgentTestRunner { } span { childOfPrevious() - operationName "another-name" + operationName "internal" + resourceName "another-name" + spanType "internal" } } } diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/propagation/AbstractPropagatorTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/propagation/AbstractPropagatorTest.groovy index 351ea547b24..80c988de26d 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/propagation/AbstractPropagatorTest.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.4/src/test/groovy/opentelemetry14/context/propagation/AbstractPropagatorTest.groovy @@ -86,7 +86,9 @@ abstract class AbstractPropagatorTest extends AgentTestRunner { assertTraces(1) { trace(1) { span { - operationName "some-name" + operationName "internal" + resourceName "some-name" + spanType "internal" traceDDId(DD128bTraceId.fromHex(traceId)) parentSpanId(DDSpanId.fromHex(spanId).toLong() as BigInteger) } diff --git a/dd-java-agent/instrumentation/opentracing/api-0.31/src/test/groovy/OpenTracing31Test.groovy b/dd-java-agent/instrumentation/opentracing/api-0.31/src/test/groovy/OpenTracing31Test.groovy index 47089453eef..b1bc139d3fd 100644 --- a/dd-java-agent/instrumentation/opentracing/api-0.31/src/test/groovy/OpenTracing31Test.groovy +++ b/dd-java-agent/instrumentation/opentracing/api-0.31/src/test/groovy/OpenTracing31Test.groovy @@ -10,6 +10,7 @@ import datadog.trace.instrumentation.opentracing31.OTTracer import datadog.trace.instrumentation.opentracing31.TypeConverter import spock.lang.Shared +import static datadog.trace.api.TracePropagationStyle.NONE import static datadog.trace.api.sampling.PrioritySampling.* import static datadog.trace.api.sampling.SamplingMechanism.* import datadog.trace.context.TraceScope @@ -45,7 +46,7 @@ class OpenTracing31Test extends AgentTestRunner { .withTag("boolean", true) } if (addReference) { - def ctx = new ExtractedContext(DDTraceId.ONE, 2, SAMPLER_DROP, null, PropagationTags.factory().empty()) + def ctx = new ExtractedContext(DDTraceId.ONE, 2, SAMPLER_DROP, null, PropagationTags.factory().empty(), NONE) builder.addReference(addReference, tracer.tracer.converter.toSpanContext(ctx)) } def result = builder.start() @@ -278,14 +279,26 @@ class OpenTracing31Test extends AgentTestRunner { tracer.inject(context, Format.Builtin.TEXT_MAP, adapter) then: + def expectedTraceparent = "00-${context.delegate.traceId.toHexStringPadded(32)}" + + "-${DDSpanId.toHexStringPadded(context.delegate.spanId)}" + + "-" + (propagatedPriority > 0 ? "01" : "00") + def expectedTracestate = "dd=s:${propagatedPriority}" + def expectedDatadogTags = null + if (propagatedPriority > 0) { + def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism + expectedDatadogTags = "_dd.p.dm=-" + effectiveSamplingMechanism + expectedTracestate+= ";t.dm:-" + effectiveSamplingMechanism + } + def expectedTextMap = [ "x-datadog-trace-id" : "$context.delegate.traceId", "x-datadog-parent-id" : "$context.delegate.spanId", "x-datadog-sampling-priority": propagatedPriority.toString(), + "traceparent" : expectedTraceparent, + "tracestate" : expectedTracestate, ] - if (propagatedPriority > 0) { - def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism - expectedTextMap.put("x-datadog-tags", "_dd.p.dm=-" + effectiveSamplingMechanism) + if (expectedDatadogTags != null) { + expectedTextMap.put("x-datadog-tags", expectedDatadogTags) } textMap == expectedTextMap diff --git a/dd-java-agent/instrumentation/opentracing/api-0.32/src/test/groovy/OpenTracing32Test.groovy b/dd-java-agent/instrumentation/opentracing/api-0.32/src/test/groovy/OpenTracing32Test.groovy index d233a6b3225..5d0df28bf83 100644 --- a/dd-java-agent/instrumentation/opentracing/api-0.32/src/test/groovy/OpenTracing32Test.groovy +++ b/dd-java-agent/instrumentation/opentracing/api-0.32/src/test/groovy/OpenTracing32Test.groovy @@ -23,6 +23,7 @@ import spock.lang.Shared import spock.lang.Subject import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace +import static datadog.trace.api.TracePropagationStyle.NONE import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP import static datadog.trace.api.sampling.PrioritySampling.UNSET @@ -50,7 +51,7 @@ class OpenTracing32Test extends AgentTestRunner { .withTag("boolean", true) } if (addReference) { - def ctx = new ExtractedContext(DDTraceId.ONE, 2, SAMPLER_DROP, null, PropagationTags.factory().empty()) + def ctx = new ExtractedContext(DDTraceId.ONE, 2, SAMPLER_DROP, null, PropagationTags.factory().empty(), NONE) builder.addReference(addReference, tracer.tracer.converter.toSpanContext(ctx)) } def result = builder.start() @@ -293,14 +294,25 @@ class OpenTracing32Test extends AgentTestRunner { tracer.inject(context, Format.Builtin.TEXT_MAP, adapter) then: + def expectedTraceparent = "00-${context.delegate.traceId.toHexStringPadded(32)}" + + "-${DDSpanId.toHexStringPadded(context.delegate.spanId)}" + + "-" + (propagatedPriority > 0 ? "01" : "00") + def expectedTracestate = "dd=s:${propagatedPriority}" + def expectedDatadogTags = null + if (propagatedPriority > 0) { + def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism + expectedDatadogTags = "_dd.p.dm=-" + effectiveSamplingMechanism + expectedTracestate+= ";t.dm:-" + effectiveSamplingMechanism + } def expectedTextMap = [ "x-datadog-trace-id" : "$context.delegate.traceId", "x-datadog-parent-id" : "$context.delegate.spanId", "x-datadog-sampling-priority": propagatedPriority.toString(), + "traceparent" : expectedTraceparent, + "tracestate" : expectedTracestate ] - if (propagatedPriority > 0) { - def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism - expectedTextMap.put("x-datadog-tags", "_dd.p.dm=-" + effectiveSamplingMechanism) + if (expectedDatadogTags != null) { + expectedTextMap.put("x-datadog-tags", expectedDatadogTags) } textMap == expectedTextMap diff --git a/dd-java-agent/instrumentation/spring-core/build.gradle b/dd-java-agent/instrumentation/spring-core/build.gradle new file mode 100644 index 00000000000..70258ccb85b --- /dev/null +++ b/dd-java-agent/instrumentation/spring-core/build.gradle @@ -0,0 +1,19 @@ +muzzle { + pass { + group = 'org.springframework' + module = 'spring-core' + versions = '[3.2.2.RELEASE,]' + assertInverse = true + } +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly group: 'org.springframework', name: 'spring-beans', version: '3.2.2.RELEASE' + testImplementation group: 'org.springframework', name: 'spring-beans', version: '3.2.2.RELEASE' + testRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter') + latestDepTestImplementation group: 'org.unbescape', name: 'unbescape', version: '+' +} diff --git a/dd-java-agent/instrumentation/spring-core/src/main/java/datadog/trace/instrumentation/springcore/StreamUtilsInstrumentation.java b/dd-java-agent/instrumentation/spring-core/src/main/java/datadog/trace/instrumentation/springcore/StreamUtilsInstrumentation.java new file mode 100644 index 00000000000..84300071eb5 --- /dev/null +++ b/dd-java-agent/instrumentation/spring-core/src/main/java/datadog/trace/instrumentation/springcore/StreamUtilsInstrumentation.java @@ -0,0 +1,61 @@ +package datadog.trace.instrumentation.springcore; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.iast.InstrumentationBridge; +import datadog.trace.api.iast.Propagation; +import datadog.trace.api.iast.propagation.PropagationModule; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import net.bytebuddy.asm.Advice; +import org.springframework.util.StreamUtils; + +@AutoService(Instrumenter.class) +public final class StreamUtilsInstrumentation extends Instrumenter.Iast + implements Instrumenter.ForSingleType { + + public StreamUtilsInstrumentation() { + super("spring-core"); + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice( + isMethod() + .and(named("copyToString")) + .and(takesArguments(2)) + .and(takesArgument(0, InputStream.class)) + .and(takesArgument(1, Charset.class)), + StreamUtilsInstrumentation.class.getName() + "$SpringAdvice"); + } + + @Override + public String instrumentedType() { + return "org.springframework.util.StreamUtils"; + } + + public static class SpringAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + @Propagation + public static void checkReturnedObject( + @Advice.Return String string, @Advice.Argument(0) final InputStream in) { + final PropagationModule module = InstrumentationBridge.PROPAGATION; + if (in != null && string != null && !string.isEmpty()) { + module.taintIfTainted(string, in); + } + } + + private static void muzzleCheck() throws IOException { + StreamUtils.copyToString( + new ByteArrayInputStream("test".getBytes()), Charset.defaultCharset()); + } + } +} diff --git a/dd-java-agent/instrumentation/spring-core/src/test/groovy/datadog/trace/instrumentation/springcore/StreamUtilsInstrumentationTest.groovy b/dd-java-agent/instrumentation/spring-core/src/test/groovy/datadog/trace/instrumentation/springcore/StreamUtilsInstrumentationTest.groovy new file mode 100644 index 00000000000..fe58485ce0b --- /dev/null +++ b/dd-java-agent/instrumentation/spring-core/src/test/groovy/datadog/trace/instrumentation/springcore/StreamUtilsInstrumentationTest.groovy @@ -0,0 +1,30 @@ +package datadog.trace.instrumentation.springcore + +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.propagation.PropagationModule +import org.springframework.util.StreamUtils + +import java.nio.charset.StandardCharsets + +class StreamUtilsInstrumentationTest extends AgentTestRunner { + + @Override + protected void configurePreAgent() { + injectSysConfig("dd.iast.enabled", "true") + } + + void 'test'(){ + setup: + InstrumentationBridge.clearIastModules() + final module= Mock(PropagationModule) + InstrumentationBridge.registerIastModule(module) + + when: + StreamUtils.copyToString(new ByteArrayInputStream("test".getBytes()), StandardCharsets.ISO_8859_1) + + then: + 1 * module.taintIfTainted(_ as String, _ as InputStream) + 0 * _ + } +} diff --git a/dd-java-agent/instrumentation/tomcat-appsec-7/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueAdvice.java b/dd-java-agent/instrumentation/tomcat-appsec-7/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueAdvice.java new file mode 100644 index 00000000000..6d5cf633435 --- /dev/null +++ b/dd-java-agent/instrumentation/tomcat-appsec-7/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueAdvice.java @@ -0,0 +1,78 @@ +package datadog.trace.instrumentation.tomcat7; + +import static datadog.trace.bootstrap.blocking.BlockingActionHelper.TemplateType.HTML; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; + +import datadog.trace.api.Config; +import datadog.trace.api.iast.InstrumentationBridge; +import datadog.trace.api.iast.sink.StacktraceLeakModule; +import datadog.trace.bootstrap.blocking.BlockingActionHelper; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import net.bytebuddy.asm.Advice; +import org.apache.catalina.connector.Response; + +public class ErrorReportValueAdvice { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean onEnter( + @Advice.Argument(value = 1) Response response, + @Advice.Argument(value = 2) Throwable throwable, + @Advice.Origin("#t") String className, + @Advice.Origin("#m") String methodName) { + int statusCode = response.getStatus(); + + // Do nothing on a 1xx, 2xx, 3xx and 404 status + // Do nothing if the response hasn't been explicitly marked as in error + // and that error has not been reported. + if (statusCode < 400 || statusCode == 404 || !response.isError()) { + return true; // skip original method + } + + final AgentSpan span = activeSpan(); + if (span != null && throwable != null) { + // Report IAST + final StacktraceLeakModule module = InstrumentationBridge.STACKTRACE_LEAK_MODULE; + if (module != null) { + try { + module.onStacktraceLeak(throwable, "Tomcat 7+", className, methodName); + } catch (final Throwable e) { + module.onUnexpectedException("onResponseException threw", e); + } + } + } + + // If we don't need to suppress stacktrace leak + if (!Config.get().isIastStacktraceLeakSuppress()) { + return false; + } + + byte[] template = BlockingActionHelper.getTemplate(HTML); + if (template == null) { + return false; + } + + try { + try { + String contentType = BlockingActionHelper.getContentType(HTML); + response.setContentType(contentType); + } catch (Throwable t) { + // Ignore + } + Writer writer = response.getReporter(); + if (writer != null) { + // If writer is null, it's an indication that the response has + // been hard committed already, which should never happen + String html = new String(template, StandardCharsets.UTF_8); + writer.write(html); + response.finishResponse(); + } + } catch (IOException | IllegalStateException e) { + // Ignore + } + + return false; + } +} diff --git a/dd-java-agent/instrumentation/tomcat-appsec-7/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueInstrumentation.java b/dd-java-agent/instrumentation/tomcat-appsec-7/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueInstrumentation.java new file mode 100644 index 00000000000..117ce32f082 --- /dev/null +++ b/dd-java-agent/instrumentation/tomcat-appsec-7/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueInstrumentation.java @@ -0,0 +1,40 @@ +package datadog.trace.instrumentation.tomcat7; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isProtected; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; + +@AutoService(Instrumenter.class) +public class ErrorReportValueInstrumentation extends Instrumenter.Iast + implements Instrumenter.ForSingleType { + + public ErrorReportValueInstrumentation() { + super("tomcat"); + } + + @Override + public String muzzleDirective() { + return "from7"; + } + + @Override + public String instrumentedType() { + return "org.apache.catalina.valves.ErrorReportValve"; + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice( + isMethod() + .and(named("report")) + .and(takesArgument(0, named("org.apache.catalina.connector.Request"))) + .and(takesArgument(1, named("org.apache.catalina.connector.Response"))) + .and(takesArgument(2, Throwable.class)) + .and(isProtected()), + packageName + ".ErrorReportValueAdvice"); + } +} diff --git a/dd-java-agent/instrumentation/vertx-mysql-client-4.4.2/build.gradle b/dd-java-agent/instrumentation/vertx-mysql-client-4.4.2/build.gradle index 2d2a33be79c..1499cbc2913 100644 --- a/dd-java-agent/instrumentation/vertx-mysql-client-4.4.2/build.gradle +++ b/dd-java-agent/instrumentation/vertx-mysql-client-4.4.2/build.gradle @@ -5,7 +5,7 @@ muzzle { pass { group = 'io.vertx' module = 'vertx-mysql-client' - versions = '[4.4.2,4.5.0)' + versions = '[4.4.2,4.6.0)' assertInverse = true } } diff --git a/dd-java-agent/instrumentation/vertx-web-4.0/build.gradle b/dd-java-agent/instrumentation/vertx-web-4.0/build.gradle index bc887e6929a..ff0f792de7d 100644 --- a/dd-java-agent/instrumentation/vertx-web-4.0/build.gradle +++ b/dd-java-agent/instrumentation/vertx-web-4.0/build.gradle @@ -44,6 +44,6 @@ dependencies { testRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core') testRuntimeOnly project(':dd-java-agent:instrumentation:netty-buffer-4') - latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '4.+' - latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '4.+' + latestDepTestImplementation group: 'io.vertx', name: 'vertx-web', version: '4.4.+' + latestDepTestImplementation group: 'io.vertx', name: 'vertx-web-client', version: '4.4.+' } diff --git a/dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java b/dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java index 332f41f469b..107ec5f4b56 100644 --- a/dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java +++ b/dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java @@ -10,6 +10,7 @@ import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -203,26 +204,48 @@ public static void main(final String[] args) { private static synchronized URL installAgentJar(final Instrumentation inst) throws IOException, URISyntaxException { - URL ddJavaAgentJarURL = null; - // First try Code Source final CodeSource codeSource = thisClass.getProtectionDomain().getCodeSource(); - if (codeSource != null) { - ddJavaAgentJarURL = codeSource.getLocation(); + URL ddJavaAgentJarURL = codeSource.getLocation(); if (ddJavaAgentJarURL != null) { final File ddJavaAgentJarPath = new File(ddJavaAgentJarURL.toURI()); if (!ddJavaAgentJarPath.isDirectory()) { - checkJarManifestMainClassIsThis(ddJavaAgentJarURL); - inst.appendToBootstrapClassLoaderSearch(new JarFile(ddJavaAgentJarPath)); - return ddJavaAgentJarURL; + return appendAgentToBootstrapClassLoaderSearch( + inst, ddJavaAgentJarURL, ddJavaAgentJarPath); } } } System.out.println("Could not get bootstrap jar from code source, using -javaagent arg"); + File javaagentFile = getAgentFileFromJavaagentArg(inst); + if (javaagentFile != null) { + URL ddJavaAgentJarURL = javaagentFile.toURI().toURL(); + return appendAgentToBootstrapClassLoaderSearch(inst, ddJavaAgentJarURL, javaagentFile); + } + + System.out.println( + "Could not get agent jar from -javaagent arg, using ClassLoader#getResource"); + javaagentFile = getAgentFileUsingClassLoaderLookup(); + if (!javaagentFile.isDirectory()) { + URL ddJavaAgentJarURL = javaagentFile.toURI().toURL(); + return appendAgentToBootstrapClassLoaderSearch(inst, ddJavaAgentJarURL, javaagentFile); + } + + throw new IllegalStateException( + "Could not determine agent jar location, not installing tracing agent"); + } + + private static URL appendAgentToBootstrapClassLoaderSearch( + Instrumentation inst, URL ddJavaAgentJarURL, File javaagentFile) throws IOException { + checkJarManifestMainClassIsThis(ddJavaAgentJarURL); + inst.appendToBootstrapClassLoaderSearch(new JarFile(javaagentFile)); + return ddJavaAgentJarURL; + } + private static File getAgentFileFromJavaagentArg(Instrumentation inst) throws IOException { + URL ddJavaAgentJarURL; // ManagementFactory indirectly references java.util.logging.LogManager // - On Oracle-based JDKs after 1.8 // - On IBM-based JDKs since at least 1.7 @@ -236,33 +259,57 @@ private static synchronized URL installAgentJar(final Instrumentation inst) if (agentArgument == null) { agentArgument = arg; } else { - throw new IllegalStateException( - "Multiple javaagents specified and code source unavailable, not installing tracing agent"); + System.out.println( + "Could not get bootstrap jar from -javaagent arg: multiple javaagents specified"); + return null; } } } if (agentArgument == null) { - throw new IllegalStateException( - "Could not find javaagent parameter and code source unavailable, not installing tracing agent"); + System.out.println("Could not get bootstrap jar from -javaagent arg: no argument specified"); + return null; } // argument is of the form -javaagent:/path/to/dd-java-agent.jar=optionalargumentstring final Matcher matcher = Pattern.compile("-javaagent:([^=]+).*").matcher(agentArgument); if (!matcher.matches()) { - throw new IllegalStateException("Unable to parse javaagent parameter: " + agentArgument); + System.out.println( + "Could not get bootstrap jar from -javaagent arg: unable to parse javaagent parameter: " + + agentArgument); + return null; } final File javaagentFile = new File(matcher.group(1)); if (!(javaagentFile.exists() || javaagentFile.isFile())) { - throw new IllegalStateException("Unable to find javaagent file: " + javaagentFile); + System.out.println( + "Could not get bootstrap jar from -javaagent arg: unable to find javaagent file: " + + javaagentFile); + return null; } - ddJavaAgentJarURL = javaagentFile.toURI().toURL(); - checkJarManifestMainClassIsThis(ddJavaAgentJarURL); - inst.appendToBootstrapClassLoaderSearch(new JarFile(javaagentFile)); + return javaagentFile; + } - return ddJavaAgentJarURL; + @SuppressForbidden + private static File getAgentFileUsingClassLoaderLookup() throws URISyntaxException { + File javaagentFile; + URL thisClassUrl; + String thisClassResourceName = thisClass.getName().replace('.', '/') + ".class"; + ClassLoader classLoader = thisClass.getClassLoader(); + if (classLoader == null) { + thisClassUrl = ClassLoader.getSystemResource(thisClassResourceName); + } else { + thisClassUrl = classLoader.getResource(thisClassResourceName); + } + + if (thisClassUrl == null) { + throw new IllegalStateException( + "Could not locate agent bootstrap class resource, not installing tracing agent"); + } + + javaagentFile = new File(new URI(thisClassUrl.getFile().split("!")[0])); + return javaagentFile; } @SuppressForbidden diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/TestProfilingContextIntegration.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/TestProfilingContextIntegration.groovy index a03b1a1824f..bbc2a7a9a05 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/TestProfilingContextIntegration.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/TestProfilingContextIntegration.groovy @@ -33,12 +33,8 @@ class TestProfilingContextIntegration implements ProfilingContextIntegration { } @Override - boolean isQueuingTimeEnabled() { - return true - } - - @Override - void recordQueueingTime(long duration) { + String name() { + return "test" } @Override diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy index c1e9defdaa2..d5279043898 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/TagsAssert.groovy @@ -60,6 +60,7 @@ class TagsAssert { assertedTags.add(DDTags.PID_TAG) assertedTags.add(DDTags.SCHEMA_VERSION_TAG_KEY) assertedTags.add(DDTags.PROFILING_ENABLED) + assertedTags.add(DDTags.PROFILING_CONTEXT_ENGINE) assertedTags.add(DDTags.BASE_SERVICE) assert tags["thread.name"] != null diff --git a/dd-smoke-tests/appsec/spring-tomcat7/build.gradle b/dd-smoke-tests/appsec/spring-tomcat7/build.gradle new file mode 100644 index 00000000000..bb3b9ddf5e9 --- /dev/null +++ b/dd-smoke-tests/appsec/spring-tomcat7/build.gradle @@ -0,0 +1,33 @@ +plugins { + id "com.github.johnrengelman.shadow" +} + +apply from: "$rootDir/gradle/java.gradle" +description = 'Spring Tomcat7 Smoke Tests.' + +jar { + manifest { + attributes('Main-Class': 'datadog.smoketest.appsec.springtomcat7.Main') + } +} + +dependencies { + implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '7.0.47' + implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '7.0.47' + implementation group: 'org.apache.tomcat', name: 'tomcat-juli', version: '7.0.47' + implementation group: 'org.springframework', name: 'spring-webmvc', version: '4.0.0.RELEASE' + + testImplementation project(':dd-smoke-tests:appsec') +} + +tasks.withType(Test).configureEach { + dependsOn "shadowJar" + + jvmArgs "-Ddatadog.smoketest.appsec.springtomcat7.shadowJar.path=${tasks.shadowJar.archiveFile.get()}" +} + +task testRuntimeActivation(type: Test) { + jvmArgs '-Dsmoke_test.appsec.enabled=inactive', + "-Ddatadog.smoketest.appsec.springtomcat7.shadowJar.path=${tasks.shadowJar.archiveFile.get()}" +} +tasks['check'].dependsOn(testRuntimeActivation) diff --git a/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/AppConfigurer.java b/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/AppConfigurer.java new file mode 100644 index 00000000000..f32a9d67b82 --- /dev/null +++ b/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/AppConfigurer.java @@ -0,0 +1,11 @@ +package datadog.smoketest.appsec.springtomcat7; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@EnableWebMvc +@ComponentScan(basePackages = {"datadog.smoketest.appsec.springtomcat7"}) +public class AppConfigurer extends WebMvcConfigurerAdapter {} diff --git a/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/Controller.java b/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/Controller.java new file mode 100644 index 00000000000..54e64878cf6 --- /dev/null +++ b/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/Controller.java @@ -0,0 +1,18 @@ +package datadog.smoketest.appsec.springtomcat7; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class Controller { + + @RequestMapping("/") + public String htmlString() { + return "Hello world!"; + } + + @RequestMapping("/exception") + public void exceptionMethod() throws Throwable { + throw new Throwable("hello"); + } +} diff --git a/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/Main.java b/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/Main.java new file mode 100644 index 00000000000..629bd4e8ef5 --- /dev/null +++ b/dd-smoke-tests/appsec/spring-tomcat7/src/main/java/datadog/smoketest/appsec/springtomcat7/Main.java @@ -0,0 +1,56 @@ +package datadog.smoketest.appsec.springtomcat7; + +import de.thetaphi.forbiddenapis.SuppressForbidden; +import java.io.File; +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +public class Main { + + private static final String ROOT = "/"; + private static final String SERVLET = "dispatcherServlet"; + + @SuppressForbidden + public static void main(String[] args) throws Exception { + int port = 8080; + for (String arg : args) { + if (arg.contains("=")) { + String[] kv = arg.split("="); + if (kv.length == 2) { + if ("--server.port".equalsIgnoreCase(kv[0])) { + try { + port = Integer.parseInt(kv[1]); + } catch (NumberFormatException e) { + System.out.println( + "--server.port '" + + kv[1] + + "' is not valid port. Will be used default port " + + port); + } + } + } + } + } + + Tomcat tomcat = new Tomcat(); + tomcat.setPort(port); + + Context context = tomcat.addContext(ROOT, new File(".").getAbsolutePath()); + + Tomcat.addServlet( + context, + SERVLET, + new DispatcherServlet( + new AnnotationConfigWebApplicationContext() { + { + register(AppConfigurer.class); + } + })); + context.addServletMapping(ROOT, SERVLET); + + tomcat.start(); + tomcat.getServer().await(); + } +} diff --git a/dd-smoke-tests/appsec/spring-tomcat7/src/test/groovy/datadog/smoketest/appsec/SpringTomcatSmokeTest.groovy b/dd-smoke-tests/appsec/spring-tomcat7/src/test/groovy/datadog/smoketest/appsec/SpringTomcatSmokeTest.groovy new file mode 100644 index 00000000000..75e7168cd3f --- /dev/null +++ b/dd-smoke-tests/appsec/spring-tomcat7/src/test/groovy/datadog/smoketest/appsec/SpringTomcatSmokeTest.groovy @@ -0,0 +1,36 @@ +package datadog.smoketest.appsec + +import okhttp3.Request + +class SpringTomcatSmokeTest extends AbstractAppSecServerSmokeTest { + + @Override + ProcessBuilder createProcessBuilder() { + String springBootShadowJar = System.getProperty("datadog.smoketest.appsec.springtomcat7.shadowJar.path") + + List command = new ArrayList<>() + command.add(javaPath()) + command.addAll(defaultJavaProperties) + command.add("-Ddd.iast.enabled=true") + command.add("-Ddd.iast.stacktrace-leak.suppress=true") + command.addAll((String[]) ["-jar", springBootShadowJar, "--server.port=${httpPort}"]) + + ProcessBuilder processBuilder = new ProcessBuilder(command) + processBuilder.directory(new File(buildDirectory)) + } + + def "suppress exception stacktrace"() { + when: + String url = "http://localhost:${httpPort}/exception" + def request = new Request.Builder() + .url(url) + .build() + def response = client.newCall(request).execute() + def responseBodyStr = response.body().string() + waitForTraceCount 1 + + then: + responseBodyStr.contains('Sorry, you cannot access this page. Please contact the customer service team.') + response.code() == 500 + } +} \ No newline at end of file diff --git a/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/DebuggerTestApplication.java b/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/DebuggerTestApplication.java index b9205e16a4f..2c1ffc19559 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/DebuggerTestApplication.java +++ b/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/DebuggerTestApplication.java @@ -35,10 +35,13 @@ static void run(String[] args) throws Exception { System.out.println("Waiting for instrumentation..."); waitForInstrumentation(LOG_FILENAME, Main.class.getName()); System.out.println("Executing method: " + methodName); - method.accept(args.length > 2 ? args[2] : null); - System.out.println("Executed"); - waitForUpload(LOG_FILENAME, expectedUploads); - System.out.println("Exiting..."); + try { + method.accept(args.length > 2 ? args[2] : null); + System.out.println("Executed"); + } finally { + waitForUpload(LOG_FILENAME, expectedUploads); + System.out.println("Exiting..."); + } } private static void registerMethods() { @@ -47,6 +50,7 @@ private static void registerMethods() { methodsByName.put("fullMethod", Main::runFullMethod); methodsByName.put("multiProbesFullMethod", Main::runFullMethod); methodsByName.put("loopingFullMethod", Main::runLoopingFullMethod); + methodsByName.put("exceptionMethod", Main::runExceptionMethod); } private static void emptyMethod(String arg) {} @@ -74,6 +78,10 @@ private static void runLoopingFullMethod(String arg) { } } + private static void runExceptionMethod(String s) { + exceptionMethod(s); + } + private static String fullMethod( int argInt, String argStr, double argDouble, Map argMap, String... argVar) { try { @@ -92,4 +100,17 @@ private static String fullMethod( return null; } } + + private static void exceptionMethod(String arg) { + if (arg.equals("uncaught")) { + throw new RuntimeException("oops uncaught!"); + } + if (arg.equals("caught")) { + try { + throw new IllegalArgumentException("oops caught!"); + } catch (IllegalArgumentException ex) { + ex.printStackTrace(); + } + } + } } diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/LogProbesIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/LogProbesIntegrationTest.java index 225c5a0899b..02df8902dad 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/LogProbesIntegrationTest.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/LogProbesIntegrationTest.java @@ -1,7 +1,12 @@ package datadog.smoketest; +import static com.datadog.debugger.el.DSL.and; import static com.datadog.debugger.el.DSL.eq; +import static com.datadog.debugger.el.DSL.gt; +import static com.datadog.debugger.el.DSL.len; +import static com.datadog.debugger.el.DSL.nullValue; import static com.datadog.debugger.el.DSL.ref; +import static com.datadog.debugger.el.DSL.value; import static com.datadog.debugger.el.DSL.when; import static com.datadog.debugger.util.LogProbeTestHelper.parseTemplate; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -27,6 +32,7 @@ import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; public class LogProbesIntegrationTest extends SimpleAppDebuggerIntegrationTest { @Test @@ -63,7 +69,7 @@ void testFullMethod() throws Exception { JsonAdapter> adapter = createAdapterForSnapshot(); System.out.println(bodyStr); Snapshot snapshot = adapter.fromJson(bodyStr).get(0).getDebugger().getSnapshot(); - assertEquals("123356536", snapshot.getProbe().getId()); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); assertFullMethodCaptureArgs(snapshot.getCaptures().getEntry()); assertEquals(0, snapshot.getCaptures().getEntry().getLocals().size()); assertNull(snapshot.getCaptures().getEntry().getThrowable()); @@ -90,7 +96,7 @@ void testFullMethodWithCondition() throws Exception { .where(MAIN_CLASS_NAME, METHOD_NAME) .when( new ProbeCondition( - when(eq(ref("argStr"), DSL.value("foobar"))), "argStr == \"foobar\"")) + when(eq(ref("argStr"), value("foobar"))), "argStr == \"foobar\"")) .captureSnapshot(true) .build(); setCurrentConfiguration(createConfig(probe)); @@ -102,10 +108,66 @@ void testFullMethodWithCondition() throws Exception { JsonAdapter> adapter = createAdapterForSnapshot(); System.out.println(bodyStr); Snapshot snapshot = adapter.fromJson(bodyStr).get(0).getDebugger().getSnapshot(); - assertEquals("123356536", snapshot.getProbe().getId()); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); assertFullMethodCaptureArgs(snapshot.getCaptures().getEntry()); } + @Test + @DisplayName("testFullMethodWithConditionAtExit") + void testFullMethodWithConditionAtExit() throws Exception { + final String METHOD_NAME = "fullMethod"; + final String EXPECTED_UPLOADS = "3"; + LogProbe probe = + LogProbe.builder() + .probeId(PROBE_ID) + .where(MAIN_CLASS_NAME, METHOD_NAME) + .when( + new ProbeCondition( + when(and(gt(len(ref("@return")), value(0)), gt(ref("@duration"), value(0)))), + "len(@return) > 0 && @duration > 0")) + .captureSnapshot(true) + .evaluateAt(MethodLocation.EXIT) + .build(); + setCurrentConfiguration(createConfig(probe)); + targetProcess = createProcessBuilder(logFilePath, METHOD_NAME, EXPECTED_UPLOADS).start(); + RecordedRequest request = retrieveSnapshotRequest(); + assertNotNull(request); + assertFalse(logHasErrors(logFilePath, it -> false)); + String bodyStr = request.getBody().readUtf8(); + JsonAdapter> adapter = createAdapterForSnapshot(); + System.out.println(bodyStr); + Snapshot snapshot = adapter.fromJson(bodyStr).get(0).getDebugger().getSnapshot(); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); + assertFullMethodCaptureArgs(snapshot.getCaptures().getReturn()); + } + + @Test + @DisplayName("testFullMethodWithConditionFailed") + void testFullMethodWithConditionFailed() throws Exception { + final String METHOD_NAME = "fullMethod"; + final String EXPECTED_UPLOADS = "3"; + LogProbe probe = + LogProbe.builder() + .probeId(PROBE_ID) + .where(MAIN_CLASS_NAME, METHOD_NAME) + .when(new ProbeCondition(when(eq(ref("noarg"), nullValue())), "noarg == null")) + .captureSnapshot(true) + .evaluateAt(MethodLocation.EXIT) + .build(); + setCurrentConfiguration(createConfig(probe)); + targetProcess = createProcessBuilder(logFilePath, METHOD_NAME, EXPECTED_UPLOADS).start(); + RecordedRequest request = retrieveSnapshotRequest(); + assertNotNull(request); + assertFalse(logHasErrors(logFilePath, it -> false)); + String bodyStr = request.getBody().readUtf8(); + JsonAdapter> adapter = createAdapterForSnapshot(); + System.out.println(bodyStr); + Snapshot snapshot = adapter.fromJson(bodyStr).get(0).getDebugger().getSnapshot(); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); + assertEquals(1, snapshot.getEvaluationErrors().size()); + assertEquals("Cannot find symbol: noarg", snapshot.getEvaluationErrors().get(0).getMessage()); + } + @Test @DisplayName("testFullMethodWithLogTemplate") void testFullMethodWithLogTemplate() throws Exception { @@ -127,7 +189,7 @@ void testFullMethodWithLogTemplate() throws Exception { JsonAdapter> adapter = createAdapterForSnapshot(); System.out.println(bodyStr); JsonSnapshotSerializer.IntakeRequest intakeRequest = adapter.fromJson(bodyStr).get(0); - assertEquals("123356536", intakeRequest.getDebugger().getSnapshot().getProbe().getId()); + assertEquals(PROBE_ID.getId(), intakeRequest.getDebugger().getSnapshot().getProbe().getId()); assertEquals( "log line 42 foobar 3.42 {[key1=val1], [key2=val2], [key3=val3]} [var1, var2, var3]", intakeRequest.getMessage()); @@ -176,24 +238,25 @@ void testMultiProbes() throws Exception { @Test @DisplayName("testSamplingSnapshotDefault") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testSamplingSnapshotDefault() throws Exception { doSamplingSnapshot(null, MethodLocation.EXIT); } @Test @DisplayName("testSamplingSnapshotDefaultWithConditionAtEntry") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testSamplingSnapshotDefaultWithConditionAtEntry() throws Exception { doSamplingSnapshot( - new ProbeCondition(DSL.when(DSL.eq(DSL.value(1), DSL.value(1))), "1 == 1"), - MethodLocation.ENTRY); + new ProbeCondition(DSL.when(DSL.eq(value(1), value(1))), "1 == 1"), MethodLocation.ENTRY); } @Test @DisplayName("testSamplingSnapshotDefaultWithConditionAtExit") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testSamplingSnapshotDefaultWithConditionAtExit() throws Exception { doSamplingSnapshot( - new ProbeCondition(DSL.when(DSL.eq(DSL.value(1), DSL.value(1))), "1 == 1"), - MethodLocation.EXIT); + new ProbeCondition(DSL.when(DSL.eq(value(1), value(1))), "1 == 1"), MethodLocation.EXIT); } private void doSamplingSnapshot(ProbeCondition probeCondition, MethodLocation evaluateAt) @@ -219,6 +282,7 @@ private void doSamplingSnapshot(ProbeCondition probeCondition, MethodLocation ev @Test @DisplayName("testSamplingLogDefault") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testSamplingLogDefault() throws Exception { batchSize = 100; final int LOOP_COUNT = 1000; @@ -242,6 +306,7 @@ void testSamplingLogDefault() throws Exception { @Test @DisplayName("testSamplingLogCustom") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testSamplingLogCustom() throws Exception { final int LOOP_COUNT = 1000; final String LOG_TEMPLATE = "log line {argInt} {argStr} {argDouble} {argMap} {argVar}"; @@ -262,6 +327,71 @@ void testSamplingLogCustom() throws Exception { assertTrue(countSnapshots() < 120); } + @Test + @DisplayName("testUncaughtException") + void testUncaughtException() throws Exception { + final String EXPECTED_UPLOADS = "3"; + final String METHOD_NAME = "exceptionMethod"; + LogProbe probe = + LogProbe.builder() + .probeId(PROBE_ID) + .where(MAIN_CLASS_NAME, METHOD_NAME) + .evaluateAt(MethodLocation.EXIT) + .captureSnapshot(true) + .build(); + setCurrentConfiguration(createConfig(probe)); + targetProcess = + createProcessBuilder(logFilePath, METHOD_NAME, EXPECTED_UPLOADS, "uncaught").start(); + RecordedRequest request = retrieveSnapshotRequest(); + assertNotNull(request); + assertFalse(logHasErrors(logFilePath, it -> false)); + String bodyStr = request.getBody().readUtf8(); + JsonAdapter> adapter = createAdapterForSnapshot(); + System.out.println(bodyStr); + JsonSnapshotSerializer.IntakeRequest intakeRequest = adapter.fromJson(bodyStr).get(0); + Snapshot snapshot = intakeRequest.getDebugger().getSnapshot(); + assertEquals("123356536", snapshot.getProbe().getId()); + CapturedContext.CapturedThrowable throwable = snapshot.getCaptures().getReturn().getThrowable(); + assertEquals("oops uncaught!", throwable.getMessage()); + assertTrue(throwable.getStacktrace().size() > 0); + assertEquals( + "datadog.smoketest.debugger.Main.exceptionMethod", + throwable.getStacktrace().get(0).getFunction()); + } + + @Test + @DisplayName("testCaughtException") + void testCaughtException() throws Exception { + final String EXPECTED_UPLOADS = "3"; + final String METHOD_NAME = "exceptionMethod"; + LogProbe probe = + LogProbe.builder() + .probeId(PROBE_ID) + .where(MAIN_CLASS_NAME, METHOD_NAME) + .evaluateAt(MethodLocation.EXIT) + .captureSnapshot(true) + .build(); + setCurrentConfiguration(createConfig(probe)); + targetProcess = + createProcessBuilder(logFilePath, METHOD_NAME, EXPECTED_UPLOADS, "caught").start(); + RecordedRequest request = retrieveSnapshotRequest(); + assertNotNull(request); + assertFalse(logHasErrors(logFilePath, it -> false)); + String bodyStr = request.getBody().readUtf8(); + JsonAdapter> adapter = createAdapterForSnapshot(); + System.out.println(bodyStr); + JsonSnapshotSerializer.IntakeRequest intakeRequest = adapter.fromJson(bodyStr).get(0); + Snapshot snapshot = intakeRequest.getDebugger().getSnapshot(); + assertEquals("123356536", snapshot.getProbe().getId()); + assertEquals(1, snapshot.getCaptures().getCaughtExceptions().size()); + CapturedContext.CapturedThrowable throwable = + snapshot.getCaptures().getCaughtExceptions().get(0); + assertEquals("oops caught!", throwable.getMessage()); + assertEquals( + "datadog.smoketest.debugger.Main.exceptionMethod", + throwable.getStacktrace().get(0).getFunction()); + } + private int countSnapshots() throws Exception { int snapshotCount = 0; RecordedRequest request; diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/MetricProbesIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/MetricProbesIntegrationTest.java index f4fbb0217a7..7bc4f8b8f90 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/MetricProbesIntegrationTest.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/MetricProbesIntegrationTest.java @@ -1,11 +1,14 @@ package datadog.smoketest; import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import com.datadog.debugger.agent.ProbeStatus; import com.datadog.debugger.el.DSL; import com.datadog.debugger.el.ValueScript; import com.datadog.debugger.probe.MetricProbe; import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -37,6 +40,26 @@ void testMethodMetricCount() throws Exception { "dynamic.instrumentation.metric.probe.%s:42|c|#debugger.probeid:%s"); } + @Test + @DisplayName("testMethodMetricCountInvalidSymbol") + void testMethodMetricCountInvalidSymbol() throws Exception { + doMethodInvalidMetric( + "fullMethod_count", + MetricProbe.MetricKind.COUNT, + new ValueScript(DSL.ref("noarg"), "noarg"), + "Cannot resolve symbol noarg"); + } + + @Test + @DisplayName("testMethodMetricCountInvalidType") + void testMethodMetricCountInvalidType() throws Exception { + doMethodInvalidMetric( + "fullMethod_count", + MetricProbe.MetricKind.COUNT, + new ValueScript(DSL.ref("argStr"), "argStr"), + "Incompatible type for expression: java.lang.String with expected types: [long]"); + } + @Test @DisplayName("testMethodMetricGauge") void testMethodMetricGauge() throws Exception { @@ -87,6 +110,39 @@ private void doMethodMetric( assertNotNull(retrieveStatsdMessage(msgExpected)); } + private void doMethodInvalidMetric( + String metricName, MetricProbe.MetricKind kind, ValueScript script, String expectedMsg) + throws Exception { + final String METHOD_NAME = "fullMethod"; + final String EXPECTED_UPLOADS = + "-1"; // wait for TIMEOUT_S for letting the Probe Status to be sent (async) + MetricProbe metricProbe = + MetricProbe.builder() + .probeId(PROBE_ID) + .where(MAIN_CLASS_NAME, METHOD_NAME) + .kind(kind) + .metricName(metricName) + .valueScript(script) + .build(); + setCurrentConfiguration(createMetricConfig(metricProbe)); + targetProcess = createProcessBuilder(logFilePath, METHOD_NAME, EXPECTED_UPLOADS).start(); + AtomicBoolean received = new AtomicBoolean(); + AtomicBoolean error = new AtomicBoolean(); + registerProbeStatusListener( + probeStatus -> { + if (probeStatus.getDiagnostics().getStatus() == ProbeStatus.Status.RECEIVED) { + received.set(true); + } + if (probeStatus.getDiagnostics().getStatus() == ProbeStatus.Status.ERROR) { + assertEquals(expectedMsg, probeStatus.getDiagnostics().getException().getMessage()); + error.set(true); + } + return received.get() && error.get(); + }); + processRequests(); + clearProbeStatusListener(); + } + @Test @DisplayName("testLineMetricInc") void testLineMetricInc() throws Exception { @@ -146,7 +202,8 @@ private void doLineMetric( MetricProbe metricProbe = MetricProbe.builder() .probeId(PROBE_ID) - .where("DebuggerTestApplication.java", 80) + // on line: System.out.println("fullMethod"); + .where("DebuggerTestApplication.java", 88) .kind(kind) .metricName(metricName) .valueScript(script) diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/MoshiConfigTestHelper.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/MoshiConfigTestHelper.java index 0729f654bd9..e6dc676d594 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/MoshiConfigTestHelper.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/MoshiConfigTestHelper.java @@ -85,7 +85,18 @@ public JsonConditionVisitor(JsonWriter jsonWriter) { @Override public Void visit(BinaryExpression binaryExpression) { - throw new UnsupportedOperationException("binary expression"); + try { + jsonWriter.beginObject(); + binaryExpression.getOperator().accept(this); + jsonWriter.beginArray(); + binaryExpression.getLeft().accept(this); + binaryExpression.getRight().accept(this); + jsonWriter.endArray(); + jsonWriter.endObject(); + } catch (IOException ex) { + LOGGER.debug("Cannot serialize: ", ex); + } + return null; } @Override @@ -100,12 +111,14 @@ public Void visit(BinaryOperator operator) { @Override public Void visit(ComparisonExpression comparisonExpression) { - comparisonExpression.getOperator().accept(this); try { + jsonWriter.beginObject(); + comparisonExpression.getOperator().accept(this); jsonWriter.beginArray(); comparisonExpression.getLeft().accept(this); comparisonExpression.getRight().accept(this); jsonWriter.endArray(); + jsonWriter.endObject(); } catch (IOException ex) { LOGGER.debug("Cannot serialize: ", ex); } @@ -169,7 +182,15 @@ public Void visit(IsUndefinedExpression isUndefinedExpression) { @Override public Void visit(LenExpression lenExpression) { - throw new UnsupportedOperationException("len expression"); + try { + jsonWriter.beginObject(); + jsonWriter.name("len"); + lenExpression.getSource().accept(this); + jsonWriter.endObject(); + } catch (IOException ex) { + LOGGER.debug("Cannot serialize: ", ex); + } + return null; } @Override @@ -180,8 +201,8 @@ public Void visit(MatchesExpression matchesExpression) { @Override public Void visit(NotExpression notExpression) { try { - jsonWriter.name("not"); jsonWriter.beginObject(); + jsonWriter.name("not"); notExpression.getPredicate().accept(this); jsonWriter.endObject(); } catch (IOException ex) { @@ -247,13 +268,7 @@ public Void visit(IndexExpression indexExpression) { @Override public Void visit(WhenExpression whenExpression) { - try { - jsonWriter.beginObject(); - whenExpression.getExpression().accept(this); - jsonWriter.endObject(); - } catch (IOException ex) { - LOGGER.debug("Cannot serialize: ", ex); - } + whenExpression.getExpression().accept(this); return null; } diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/ProbeStateIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/ProbeStateIntegrationTest.java index bc0a9c3c4dd..9876f013021 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/ProbeStateIntegrationTest.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/ProbeStateIntegrationTest.java @@ -12,10 +12,12 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; public class ProbeStateIntegrationTest extends ServerAppDebuggerIntegrationTest { @Test @DisplayName("testAddRemoveProbes") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testAddRemoveProbes() throws Exception { LogProbe logProbe = LogProbe.builder().probeId(PROBE_ID).where(TEST_APP_CLASS_NAME, FULL_METHOD_NAME).build(); @@ -37,6 +39,7 @@ void testAddRemoveProbes() throws Exception { @Test @DisplayName("testDisableEnableProbes") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testDisableEnableProbes() throws Exception { LogProbe logProbe = LogProbe.builder().probeId(PROBE_ID).where(TEST_APP_CLASS_NAME, FULL_METHOD_NAME).build(); @@ -58,6 +61,7 @@ void testDisableEnableProbes() throws Exception { @Test @DisplayName("testDisableEnableProbesUsingDenyList") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testDisableEnableProbesUsingDenyList() throws Exception { LogProbe logProbe = LogProbe.builder().probeId(PROBE_ID).where(TEST_APP_CLASS_NAME, FULL_METHOD_NAME).build(); @@ -88,6 +92,7 @@ void testDisableEnableProbesUsingDenyList() throws Exception { @Test @DisplayName("testDisableEnableProbesUsingAllowList") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testDisableEnableProbesUsingAllowList() throws Exception { LogProbe logProbe = LogProbe.builder().probeId(PROBE_ID).where(TEST_APP_CLASS_NAME, FULL_METHOD_NAME).build(); @@ -140,5 +145,6 @@ public void testProbeStatusError() throws Exception { } return received.get() && error.get(); }); + processRequests(); } } diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/ServerAppDebuggerIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/ServerAppDebuggerIntegrationTest.java index 34ee1de31fa..251accd83bc 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/ServerAppDebuggerIntegrationTest.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/ServerAppDebuggerIntegrationTest.java @@ -48,6 +48,7 @@ void setup(TestInfo testInfo) throws Exception { controlServer = new MockWebServer(); // controlServer.setDispatcher(new ControlDispatcher()); controlServer.start(); + LOG.info("ControlServer on {}", controlServer.getPort()); controlUrl = controlServer.url(CONTROL_URL); startApp(); appUrl = waitForAppStartedAndGetUrl(); diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/SpanDecorationProbesIntegrationTests.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/SpanDecorationProbesIntegrationTests.java index 634ca3e4129..7aed4e56b16 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/SpanDecorationProbesIntegrationTests.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/SpanDecorationProbesIntegrationTests.java @@ -16,7 +16,6 @@ import com.datadog.debugger.el.ProbeCondition; import com.datadog.debugger.el.expressions.BooleanExpression; import com.datadog.debugger.probe.SpanDecorationProbe; -import datadog.trace.api.Platform; import datadog.trace.bootstrap.debugger.EvaluationError; import datadog.trace.test.agent.decoder.DecodedSpan; import java.nio.file.Path; @@ -26,6 +25,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; public class SpanDecorationProbesIntegrationTests extends ServerAppDebuggerIntegrationTest { @@ -39,11 +39,10 @@ protected ProcessBuilder createProcessBuilder(Path logFilePath, String... params @Test @DisplayName("testMethodSimpleTagNoCondition") + @DisabledIf( + value = "datadog.trace.api.Platform#isJ9", + disabledReason = "we cannot get local variable debug info") void testMethodSimpleTagNoCondition() throws Exception { - if (Platform.isJ9()) { - // skip for J9/OpenJ9 as we cannot get local variable debug info. - return; - } SpanDecorationProbe spanDecorationProbe = SpanDecorationProbe.builder() .probeId(PROBE_ID) @@ -70,11 +69,10 @@ void testMethodSimpleTagNoCondition() throws Exception { @Test @DisplayName("testMethodMultiTagsMultiConditions") + @DisabledIf( + value = "datadog.trace.api.Platform#isJ9", + disabledReason = "we cannot get local variable debug info") void testMethodMultiTagsMultiConditions() throws Exception { - if (Platform.isJ9()) { - // skip for J9/OpenJ9 as we cannot get local variable debug info. - return; - } List decorations = Arrays.asList( createDecoration( @@ -242,6 +240,7 @@ void testMethodMultiTagValueError() throws Exception { @Test @DisplayName("testSamplingSpanDecoration") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testSamplingSpanDecoration() throws Exception { SpanDecorationProbe spanDecorationProbe = SpanDecorationProbe.builder() diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/SpanProbesIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/SpanProbesIntegrationTest.java index a97bb73d929..b7598f121a6 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/SpanProbesIntegrationTest.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/SpanProbesIntegrationTest.java @@ -11,6 +11,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; public class SpanProbesIntegrationTest extends SimpleAppDebuggerIntegrationTest { @@ -41,19 +42,30 @@ void testLineRangeSpan() throws Exception { final String METHOD_NAME = "fullMethod"; final String EXPECTED_UPLOADS = "3"; // 2 + 1 for letting the trace being sent (async) SpanProbe spanProbe = - SpanProbe.builder().probeId(PROBE_ID).where(MAIN_CLASS_NAME, 80, 89).build(); + SpanProbe.builder() + .probeId(PROBE_ID) + // from line: System.out.println("fullMethod"); + // to line: + String.join(",", argVar); + .where(MAIN_CLASS_NAME, 88, 97) + .build(); setCurrentConfiguration(createSpanConfig(spanProbe)); targetProcess = createProcessBuilder(logFilePath, METHOD_NAME, EXPECTED_UPLOADS).start(); DecodedSpan decodedSpan = retrieveSpanRequest(DebuggerTracer.OPERATION_NAME); - assertEquals("Main.fullMethod:L80-89", decodedSpan.getResource()); + assertEquals("Main.fullMethod:L88-97", decodedSpan.getResource()); } @Test @DisplayName("testSingleLineSpan") + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") void testSingleLineSpan() throws Exception { final String METHOD_NAME = "fullMethod"; final String EXPECTED_UPLOADS = "2"; // 2 probe statuses: RECEIVED + ERROR - SpanProbe spanProbe = SpanProbe.builder().probeId(PROBE_ID).where(MAIN_CLASS_NAME, 80).build(); + SpanProbe spanProbe = + SpanProbe.builder() + .probeId(PROBE_ID) + // on line: System.out.println("fullMethod"); + .where(MAIN_CLASS_NAME, 88) + .build(); setCurrentConfiguration(createSpanConfig(spanProbe)); targetProcess = createProcessBuilder(logFilePath, METHOD_NAME, EXPECTED_UPLOADS).start(); AtomicBoolean received = new AtomicBoolean(false); @@ -71,5 +83,6 @@ void testSingleLineSpan() throws Exception { } return received.get() && error.get(); }); + processRequests(); } } diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/TracerDebuggerIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/TracerDebuggerIntegrationTest.java index 2d6f6817901..4b1884dd4b9 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/TracerDebuggerIntegrationTest.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/TracerDebuggerIntegrationTest.java @@ -1,8 +1,9 @@ package datadog.smoketest; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; +import static com.datadog.debugger.util.LogProbeTestHelper.parseTemplate; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import com.datadog.debugger.agent.JsonSnapshotSerializer; @@ -56,7 +57,7 @@ void testTracer() throws Exception { .build(); JsonSnapshotSerializer.IntakeRequest request = doTestTracer(logProbe); Snapshot snapshot = request.getDebugger().getSnapshot(); - assertEquals("123356536", snapshot.getProbe().getId()); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); assertTrue(Pattern.matches("[0-9a-f]+", request.getTraceId())); assertTrue(Pattern.matches("\\d+", request.getSpanId())); assertFalse( @@ -78,7 +79,7 @@ void testTracerDynamicLog() throws Exception { .build(); JsonSnapshotSerializer.IntakeRequest request = doTestTracer(logProbe); Snapshot snapshot = request.getDebugger().getSnapshot(); - assertEquals("123356536", snapshot.getProbe().getId()); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); assertTrue(Pattern.matches("[0-9a-f]+", request.getTraceId())); assertTrue(Pattern.matches("\\d+", request.getSpanId())); assertFalse( @@ -96,12 +97,51 @@ void testTracerSameMethod() throws Exception { .build(); JsonSnapshotSerializer.IntakeRequest request = doTestTracer(logProbe); Snapshot snapshot = request.getDebugger().getSnapshot(); - assertEquals("123356536", snapshot.getProbe().getId()); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); assertEquals(42, snapshot.getCaptures().getEntry().getArguments().get("argInt").getValue()); assertTrue(Pattern.matches("[0-9a-f]+", request.getTraceId())); assertTrue(Pattern.matches("\\d+", request.getSpanId())); } + @Test + @DisplayName("testTracerLineSnapshotProbe") + void testTracerLineSnapshotProbe() throws Exception { + LogProbe logProbe = + LogProbe.builder() + .probeId(PROBE_ID) + // on line: System.out.println(argInt); + .where("WebController.java", 15) + .captureSnapshot(true) + .build(); + JsonSnapshotSerializer.IntakeRequest request = doTestTracer(logProbe); + Snapshot snapshot = request.getDebugger().getSnapshot(); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); + assertEquals( + 42, snapshot.getCaptures().getLines().get(15).getArguments().get("argInt").getValue()); + assertTrue(Pattern.matches("[0-9a-f]+", request.getTraceId())); + assertTrue(Pattern.matches("\\d+", request.getSpanId())); + } + + @Test + @DisplayName("testTracerLineDynamicLogProbe") + void testTracerLineDynamicLogProbe() throws Exception { + final String LOG_TEMPLATE = "processWithArg {argInt}"; + LogProbe logProbe = + LogProbe.builder() + .probeId(PROBE_ID) + .template(LOG_TEMPLATE, parseTemplate(LOG_TEMPLATE)) + // on line: System.out.println(argInt); + .where("WebController.java", 15) + .captureSnapshot(false) + .build(); + JsonSnapshotSerializer.IntakeRequest request = doTestTracer(logProbe); + Snapshot snapshot = request.getDebugger().getSnapshot(); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); + assertEquals("processWithArg 42", request.getMessage()); + assertTrue(Pattern.matches("[0-9a-f]+", request.getTraceId())); + assertTrue(Pattern.matches("\\d+", request.getSpanId())); + } + private JsonSnapshotSerializer.IntakeRequest doTestTracer(LogProbe logProbe) throws Exception { setCurrentConfiguration(createConfig(logProbe)); String httpPort = String.valueOf(PortUtils.randomOpenPort()); @@ -138,7 +178,6 @@ private JsonSnapshotSerializer.IntakeRequest doTestTracer(LogProbe logProbe) thr @Override protected ProcessBuilder createProcessBuilder(Path logFilePath, String... params) { List commandParams = getDebuggerCommandParams(); - commandParams.add("-Ddd.trace.enabled=true"); commandParams.add( "-Ddd.trace.methods=datadog.smoketest.debugger.controller.WebController[processWithArg]"); return ProcessBuilderHelper.createProcessBuilder( diff --git a/dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy b/dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy index 76f869f6f50..8ca0ebd71cf 100644 --- a/dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy +++ b/dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy @@ -1,11 +1,15 @@ package datadog.smoketest +import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import datadog.trace.agent.test.server.http.TestHttpServer import datadog.trace.api.config.CiVisibilityConfig import datadog.trace.api.config.GeneralConfig import datadog.trace.test.util.MultipartRequestParser import datadog.trace.util.Strings +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response import org.gradle.internal.impldep.org.apache.commons.io.FileUtils import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner @@ -41,6 +45,22 @@ import static org.hamcrest.Matchers.not @Unroll class GradleDaemonSmokeTest extends Specification { + private static final String LATEST_GRADLE_VERSION = getLatestGradleVersion() + + private static String getLatestGradleVersion() { + OkHttpClient client = new OkHttpClient() + Request request = new Request.Builder().url("https://services.gradle.org/versions/current").build() + try (Response response = client.newCall(request).execute()) { + if (!response.successful) { + return GradleVersion.current().version + } + def responseBody = response.body().string() + ObjectMapper mapper = new ObjectMapper() + JsonNode root = mapper.readTree(responseBody) + return root.get("version").asText() + } + } + private static final String TEST_SERVICE_NAME = "test-gradle-service" private static final String TEST_ENVIRONMENT_NAME = "integration-test" @@ -98,14 +118,14 @@ class GradleDaemonSmokeTest extends Specification { prefix("/api/v2/ci/tests/skippable") { response.status(200).send('{ "data": [{' + - ' "id": "d230520a0561ee2f",' + - ' "type": "test",' + - ' "attributes": {' + - ' "configurations": {},' + - ' "name": "test_to_skip_with_itr",' + - ' "suite": "datadog.smoke.TestSucceed"' + - ' }' + - '}] }') + ' "id": "d230520a0561ee2f",' + + ' "type": "test",' + + ' "attributes": {' + + ' "configurations": {},' + + ' "name": "test_to_skip_with_itr",' + + ' "suite": "datadog.smoke.TestSucceed"' + + ' }' + + '}] }') } } } @@ -124,18 +144,53 @@ class GradleDaemonSmokeTest extends Specification { receivedCoverages.clear() } - def "Successful build emits session and module spans: Gradle v#gradleVersion #gradleProject"() { + def "Successful build emits session and module spans: Gradle v#gradleVersion (project: #gradleProject, configCache: #configurationCache)"() { given: givenGradleVersionIsCompatibleWithCurrentJvm(gradleVersion) givenGradleProjectFiles("datadog/smoketest/$gradleProject/") ensureDependenciesDownloaded(gradleVersion) when: - BuildResult buildResult = runGradleTests(gradleVersion) + BuildResult buildResult = runGradleTests(gradleVersion, true, configurationCache) then: assertBuildSuccessful(buildResult) + assertSuccessfulBuildEventsAndCoverages(sessionResourceName, gradleVersion) + + if (configurationCache) { + // if configuration cache is enabled, run the build one more time + // to verify that building with existing configuration cache entry works + when: + BuildResult buildResultWithConfigCacheEntry = runGradleTests(gradleVersion, true, configurationCache) + + then: + assertBuildSuccessful(buildResultWithConfigCacheEntry) + assertSuccessfulBuildEventsAndCoverages(sessionResourceName, gradleVersion) + } + + where: + gradleVersion | gradleProject | sessionResourceName | configurationCache + "4.0" | "success" | "gradle-instrumentation-test-project" | false + "5.0" | "success" | "gradle-instrumentation-test-project" | false + "6.0" | "success" | "gradle-instrumentation-test-project" | false + "7.6.3" | "success" | "gradle-instrumentation-test-project" | false + "8.2.1" | "success" | "gradle-instrumentation-test-project" | false + // in Gradle 8.3+ instrumentations we populate the resource name of the session spans differently: + // in legacy instrumentation session span is created after session evaluation + // while in the current one it is created when the build starts. + // At this point project name is not available yet, so we use build path instead. + // This has a few advantages: + // - session duration is more accurate (since settings evaluation time is included); + // - it is easier to distinguish buildSrc or plugin builds from the main project builds; + // - configuration cache is supported ("settings evaluated" event is not fired if it is enabled). + "8.3" | "success" | ":" | false + "8.3" | "success" | ":" | true + LATEST_GRADLE_VERSION | "success" | ":" | false + LATEST_GRADLE_VERSION | "successJunit5" | ":" | false + LATEST_GRADLE_VERSION | "success" | ":" | true + } + private assertSuccessfulBuildEventsAndCoverages(String sessionResourceName, String gradleVersion) { def events = waitForEvents(5) assert events.size() == 5 @@ -145,7 +200,7 @@ class GradleDaemonSmokeTest extends Specification { verifyAll(sessionEndEvent) { verifyAll(content) { name == "gradle.test_session" - resource == "gradle-instrumentation-test-project" // project name + resource == sessionResourceName verifyAll(metrics) { process_id > 0 // only applied to root spans it["test.itr.tests_skipping.count"] == 1 @@ -275,18 +330,6 @@ class GradleDaemonSmokeTest extends Specification { ] ] ] - - where: - gradleVersion | gradleProject - "4.0" | "success" - "5.0" | "success" - "6.0" | "success" - "7.0" | "success" - "7.6.1" | "success" - "8.0.2" | "success" - "8.1.1" | "success" - "8.3" | "success" - "8.3" | "successJunit5" } // this is a separate test case since older Gradle versions need to declare dependencies differently @@ -463,7 +506,7 @@ class GradleDaemonSmokeTest extends Specification { verifyAll(sessionEndEvent) { verifyAll(content) { name == "gradle.test_session" - resource == "gradle-instrumentation-test-project" // project name + resource == sessionResourceName verifyAll(metrics) { process_id > 0 // only applied to root spans } @@ -594,8 +637,22 @@ class GradleDaemonSmokeTest extends Specification { } where: - // - gradleVersion << ["4.0", "5.0", "6.0", "7.0", "7.6.1", "8.0.2", "8.1.1", "8.3"] + gradleVersion | sessionResourceName + "4.0" | "gradle-instrumentation-test-project" + "5.0" | "gradle-instrumentation-test-project" + "6.0" | "gradle-instrumentation-test-project" + "7.6.3" | "gradle-instrumentation-test-project" + "8.2.1" | "gradle-instrumentation-test-project" + // in Gradle 8.3+ instrumentations we populate the resource name of the session spans differently: + // in legacy instrumentation session span is created after session evaluation + // while in the current one it is created when the build starts. + // At this point project name is not available yet, so we use build path instead. + // This has a few advantages: + // - session duration is more accurate (since settings evaluation time is included); + // - it is easier to distinguish buildSrc or plugin builds from the main project builds; + // - configuration cache is supported (if it is enabled, "settings evaluated" event is not fired). + "8.3" | ":" + LATEST_GRADLE_VERSION | ":" } def "Failed build emits session and module spans: Gradle v#gradleVersion"() { @@ -627,7 +684,7 @@ class GradleDaemonSmokeTest extends Specification { verifyAll(sessionEndEvent) { verifyAll(content) { name == "gradle.test_session" - resource == "gradle-instrumentation-test-project" // project name + resource == sessionResourceName verifyAll(metrics) { process_id > 0 // only applied to root spans } @@ -699,7 +756,22 @@ class GradleDaemonSmokeTest extends Specification { } where: - gradleVersion << ["4.0", "5.0", "6.0", "7.0", "7.6.1", "8.0.2", "8.1.1", "8.3"] + gradleVersion | sessionResourceName + "4.0" | "gradle-instrumentation-test-project" + "5.0" | "gradle-instrumentation-test-project" + "6.0" | "gradle-instrumentation-test-project" + "7.6.3" | "gradle-instrumentation-test-project" + "8.2.1" | "gradle-instrumentation-test-project" + // in Gradle 8.3+ instrumentations we populate the resource name of the session spans differently: + // in legacy instrumentation session span is created after session evaluation + // while in the current one it is created when the build starts. + // At this point project name is not available yet, so we use build path instead. + // This has a few advantages: + // - session duration is more accurate (since settings evaluation time is included); + // - it is easier to distinguish buildSrc or plugin builds from the main project builds; + // - configuration cache is supported (if it is enabled, "settings evaluated" event is not fired). + "8.3" | ":" + LATEST_GRADLE_VERSION | ":" } def "Build without tests emits session and module spans: Gradle v#gradleVersion"() { @@ -723,7 +795,7 @@ class GradleDaemonSmokeTest extends Specification { verifyAll(sessionEndEvent) { verifyAll(content) { name == "gradle.test_session" - resource == "gradle-instrumentation-test-project" // project name + resource == sessionResourceName verifyAll(metrics) { process_id > 0 // only applied to root spans } @@ -752,7 +824,22 @@ class GradleDaemonSmokeTest extends Specification { } where: - gradleVersion << ["4.0", "5.0", "6.0", "7.0", "7.6.1", "8.0.2", "8.1.1", "8.3"] + gradleVersion | sessionResourceName + "4.0" | "gradle-instrumentation-test-project" + "5.0" | "gradle-instrumentation-test-project" + "6.0" | "gradle-instrumentation-test-project" + "7.6.3" | "gradle-instrumentation-test-project" + "8.2.1" | "gradle-instrumentation-test-project" + // in Gradle 8.3+ instrumentations we populate the resource name of the session spans differently: + // in legacy instrumentation session span is created after session evaluation + // while in the current one it is created when the build starts. + // At this point project name is not available yet, so we use build path instead. + // This has a few advantages: + // - session duration is more accurate (since settings evaluation time is included); + // - it is easier to distinguish buildSrc or plugin builds from the main project builds; + // - configuration cache is supported (if it is enabled, "settings evaluated" event is not fired). + "8.3" | ":" + LATEST_GRADLE_VERSION | ":" } def "Corrupted build emits session span: Gradle v#gradleVersion"() { @@ -776,7 +863,7 @@ class GradleDaemonSmokeTest extends Specification { verifyAll(sessionEndEvent) { verifyAll(content) { name == "gradle.test_session" - resource == "gradle-instrumentation-test-project" // project name + resource == sessionResourceName verifyAll(metrics) { process_id > 0 // only applied to root spans } @@ -789,7 +876,22 @@ class GradleDaemonSmokeTest extends Specification { } where: - gradleVersion << ["4.0", "5.0", "6.0", "7.0", "7.6.1", "8.0.2", "8.1.1", "8.3"] + gradleVersion | sessionResourceName + "4.0" | "gradle-instrumentation-test-project" + "5.0" | "gradle-instrumentation-test-project" + "6.0" | "gradle-instrumentation-test-project" + "7.6.3" | "gradle-instrumentation-test-project" + "8.2.1" | "gradle-instrumentation-test-project" + // in Gradle 8.3+ instrumentations we populate the resource name of the session spans differently: + // in legacy instrumentation session span is created after session evaluation + // while in the current one it is created when the build starts. + // At this point project name is not available yet, so we use build path instead. + // This has a few advantages: + // - session duration is more accurate (since settings evaluation time is included); + // - it is easier to distinguish buildSrc or plugin builds from the main project builds; + // - configuration cache is supported (if it is enabled, "settings evaluated" event is not fired). + "8.3" | ":" + LATEST_GRADLE_VERSION | ":" } def "Successful build with module that has multiple forks: Gradle v#gradleVersion"() { @@ -813,7 +915,7 @@ class GradleDaemonSmokeTest extends Specification { verifyAll(sessionEndEvent) { verifyAll(content) { name == "gradle.test_session" - resource == "gradle-instrumentation-test-project" // project name + resource == ":" // project name verifyAll(metrics) { process_id > 0 // only applied to root spans it["test.itr.tests_skipping.count"] == 0 @@ -987,7 +1089,7 @@ class GradleDaemonSmokeTest extends Specification { ]) where: - gradleVersion << ["8.3"] + gradleVersion << [LATEST_GRADLE_VERSION] } private static boolean verifyTestFrameworks(it) { @@ -1005,17 +1107,17 @@ class GradleDaemonSmokeTest extends Specification { Files.write(ddApiKeyPath, "dummy".getBytes()) def gradleProperties = - "org.gradle.jvmargs=" + - "-javaagent:${agentShadowJar}=" + - "${Strings.propertyNameToSystemPropertyName(GeneralConfig.ENV)}=${TEST_ENVIRONMENT_NAME}," + - "${Strings.propertyNameToSystemPropertyName(GeneralConfig.SERVICE_NAME)}=${TEST_SERVICE_NAME}," + - "${Strings.propertyNameToSystemPropertyName(GeneralConfig.API_KEY_FILE)}=${ddApiKeyPath.toAbsolutePath().toString()}," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_ENABLED)}=true," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_ENABLED)}=true," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_ENABLED)}=false," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_CIPROVIDER_INTEGRATION_ENABLED)}=false," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_COVERAGE_SEGMENTS_ENABLED)}=true," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_URL)}=${intakeServer.address.toString()}" + "org.gradle.jvmargs=" + + "-javaagent:${agentShadowJar}=" + + "${Strings.propertyNameToSystemPropertyName(GeneralConfig.ENV)}=${TEST_ENVIRONMENT_NAME}," + + "${Strings.propertyNameToSystemPropertyName(GeneralConfig.SERVICE_NAME)}=${TEST_SERVICE_NAME}," + + "${Strings.propertyNameToSystemPropertyName(GeneralConfig.API_KEY_FILE)}=${ddApiKeyPath.toAbsolutePath().toString()}," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_ENABLED)}=true," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_ENABLED)}=true," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_ENABLED)}=false," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_CIPROVIDER_INTEGRATION_ENABLED)}=false," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_COVERAGE_SEGMENTS_ENABLED)}=true," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_URL)}=${intakeServer.address.toString()}" Files.write(testKitFolder.resolve("gradle.properties"), gradleProperties.getBytes()) } @@ -1026,21 +1128,21 @@ class GradleDaemonSmokeTest extends Specification { FileUtils.copyDirectory(projectResourcesPath.toFile(), projectFolder.toFile()) Files.walkFileTree(projectFolder, new SimpleFileVisitor() { - @Override - FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (file.toString().endsWith(GRADLE_TEST_RESOURCE_EXTENSION)) { - def fileWithFixedExtension = Paths.get(file.toString().replace(GRADLE_TEST_RESOURCE_EXTENSION, GRADLE_REGULAR_EXTENSION)) - Files.move(file, fileWithFixedExtension) - } - return FileVisitResult.CONTINUE + @Override + FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(GRADLE_TEST_RESOURCE_EXTENSION)) { + def fileWithFixedExtension = Paths.get(file.toString().replace(GRADLE_TEST_RESOURCE_EXTENSION, GRADLE_REGULAR_EXTENSION)) + Files.move(file, fileWithFixedExtension) } - }) + return FileVisitResult.CONTINUE + } + }) // creating empty .git directory so that the tracer could detect projectFolder as repo root Files.createDirectory(projectFolder.resolve(".git")) } - private BuildResult runGradleTests(String gradleVersion, boolean successExpected = true) { + private BuildResult runGradleTests(String gradleVersion, boolean successExpected = true, boolean configurationCache = false) { def arguments = ["test", "--stacktrace"] if (gradleVersion > "5.6") { // fail on warnings is available starting from Gradle 5.6 @@ -1049,6 +1151,9 @@ class GradleDaemonSmokeTest extends Specification { // warning mode available starting from Gradle 4.5 arguments += ["--warning-mode", "all"] } + if (configurationCache) { + arguments += ["--configuration-cache", "--rerun-tasks"] + } BuildResult buildResult = runGradle(gradleVersion, arguments, successExpected) buildResult } @@ -1082,17 +1187,17 @@ class GradleDaemonSmokeTest extends Specification { } catch (Exception e) { println "${new Date()}: $specificationContext.currentIteration.displayName " + - "- Failed to install Gradle distribution, will proceed to run test kit hoping for the best: $e" + "- Failed to install Gradle distribution, will proceed to run test kit hoping for the best: $e" } } private runGradle(String gradleVersion, List arguments, boolean successExpected) { GradleRunner gradleRunner = GradleRunner.create() - .withTestKitDir(testKitFolder.toFile()) - .withProjectDir(projectFolder.toFile()) - .withGradleVersion(gradleVersion) - .withArguments(arguments) - .forwardOutput() + .withTestKitDir(testKitFolder.toFile()) + .withProjectDir(projectFolder.toFile()) + .withGradleVersion(gradleVersion) + .withArguments(arguments) + .forwardOutput() println "${new Date()}: $specificationContext.currentIteration.displayName - Starting Gradle run" def buildResult = successExpected ? gradleRunner.build() : gradleRunner.buildAndFail() @@ -1197,12 +1302,14 @@ class GradleDaemonSmokeTest extends Specification { void givenGradleVersionIsCompatibleWithCurrentJvm(String gradleVersion) { Assumptions.assumeTrue(isSupported(gradleVersion), - "Current JVM " + Jvm.current.javaVersion + " does not support Gradle version " + gradleVersion) + "Current JVM " + Jvm.current.javaVersion + " does not support Gradle version " + gradleVersion) } private static boolean isSupported(String gradleVersion) { // https://docs.gradle.org/current/userguide/compatibility.html - if (Jvm.current.java20Compatible) { + if (Jvm.current.java21Compatible) { + return gradleVersion >= "8.4" + } else if (Jvm.current.java20) { return gradleVersion >= "8.1" } else if (Jvm.current.java19) { return gradleVersion >= "7.6" diff --git a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/IastWebController.java b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/IastWebController.java index 4a6087f2ff5..6370a7fa7f8 100644 --- a/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/IastWebController.java +++ b/dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/IastWebController.java @@ -1,11 +1,13 @@ package datadog.smoketest.springboot.controller; import com.google.gson.Gson; +import com.google.gson.JsonParser; import datadog.smoketest.springboot.TestBean; import ddtest.client.sources.Hasher; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; +import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.HttpCookie; import java.net.HttpURLConnection; @@ -32,6 +34,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.MatrixVariable; import org.springframework.web.bind.annotation.PathVariable; @@ -357,6 +360,12 @@ String gson(@RequestParam("json") String json) { return "Test bean -> name: " + testBean.getName() + ", value: " + testBean.getValue(); } + @PostMapping(value = "/gson_json_parser_deserialization", consumes = MediaType.TEXT_PLAIN_VALUE) + String gsonJsonParser(@RequestBody String json) { + JsonParser.parseReader(new StringReader(json)); + return "Ok"; + } + private void withProcess(final Operation op) { Process process = null; try { diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractGsonIastSpringBootSmokeTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractGsonIastSpringBootSmokeTest.groovy new file mode 100644 index 00000000000..4888ddaaf4f --- /dev/null +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractGsonIastSpringBootSmokeTest.groovy @@ -0,0 +1,89 @@ +package datadog.smoketest + +import okhttp3.FormBody +import okhttp3.MediaType +import okhttp3.Request +import okhttp3.RequestBody + +import static datadog.trace.api.config.IastConfig.IAST_DEBUG_ENABLED +import static datadog.trace.api.config.IastConfig.IAST_DETECTION_MODE +import static datadog.trace.api.config.IastConfig.IAST_ENABLED +import static datadog.trace.api.config.IastConfig.IAST_REDACTION_ENABLED + +abstract class AbstractGsonIastSpringBootSmokeTest extends AbstractIastServerSmokeTest{ + + + @Override + ProcessBuilder createProcessBuilder() { + String springBootShadowJar = System.getProperty('datadog.smoketest.springboot.shadowJar.path') + + List command = [] + command.add(javaPath()) + command.addAll(defaultJavaProperties) + command.addAll(getCustomSpringProperties()) + command.addAll([ + withSystemProperty(IAST_ENABLED, true), + withSystemProperty(IAST_DETECTION_MODE, 'FULL'), + withSystemProperty(IAST_DEBUG_ENABLED, true), + withSystemProperty(IAST_REDACTION_ENABLED, false) + ]) + command.addAll((String[]) ['-jar', springBootShadowJar, "--server.port=${httpPort}"]) + ProcessBuilder processBuilder = new ProcessBuilder(command) + processBuilder.directory(new File(buildDirectory)) + // Spring will print all environment variables to the log, which may pollute it and affect log assertions. + processBuilder.environment().clear() + return processBuilder + } + + abstract String[] getCustomSpringProperties() + + void 'request body taint json'() { + setup: + String url = "http://localhost:${httpPort}/request_body/test" + def request = new Request.Builder().url(url).post(RequestBody.create(MediaType.parse('application/json; charset=utf-8'), '{"name": "nameTest", "value" : "valueTest"}')).build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'nameTest' && + tainted.ranges[0].source.origin == 'http.request.body' + } + } + + void 'gson deserialization'() { + + given: + final url = "http://localhost:${httpPort}/gson_deserialization" + final body = new FormBody.Builder().add('json', '{"name": "gsonTest", "value" : "valueTest"}').build() + final request = new Request.Builder().url(url).post(body).build() + + when: + client.newCall(request).execute() + + then: + hasTainted { tainted -> + tainted.value == 'gsonTest' && + tainted.ranges[0].source.name == 'json' && + tainted.ranges[0].source.origin == 'http.request.parameter' + } + } + + void 'gson json parser deserialization'() { + + given: + final url = "http://localhost:${httpPort}/gson_json_parser_deserialization" + final request = new Request.Builder().url(url).post(RequestBody.create(MediaType.parse('text/plain'), '{"name": "gsonReaderTest", "value" : "valueTest"}')).build() + + when: + def response = client.newCall(request).execute() + + then: + response.successful + hasTainted { tainted -> + tainted.value == 'gsonReaderTest' && + tainted.ranges[0].source.origin == 'http.request.body' + } + } +} diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy index 4217258e570..ee0cc033ecc 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy @@ -111,6 +111,34 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest { } + void 'Multipart Request original file name'(){ + given: + String url = "http://localhost:${httpPort}/multipart" + + RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("theFile", "theFileName", + RequestBody.create(MediaType.parse("text/plain"), "FILE_CONTENT")) + .addFormDataPart("param1", "param1Value") + .build() + + Request request = new Request.Builder() + .url(url) + .post(requestBody) + .build() + when: + final retValue = client.newCall(request).execute().body().string() + + then: + retValue == "fileName: theFile" + hasTainted { tainted -> + tainted.value == 'theFileName' && + tainted.ranges[0].source.name == 'filename' && + tainted.ranges[0].source.origin == 'http.request.multipart.parameter' + } + + } + + void 'iast.enabled tag is present'() { setup: String url = "http://localhost:${httpPort}/greeting" diff --git a/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy b/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy index 29413d42f67..3dd68d7831a 100644 --- a/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy +++ b/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy @@ -1,5 +1,6 @@ package datadog.smoketest + import com.fasterxml.jackson.databind.ObjectMapper import datadog.trace.agent.test.server.http.TestHttpServer import datadog.trace.api.Config @@ -7,14 +8,23 @@ import datadog.trace.api.config.CiVisibilityConfig import datadog.trace.api.config.GeneralConfig import datadog.trace.test.util.MultipartRequestParser import datadog.trace.util.Strings +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response import org.apache.maven.wrapper.MavenWrapperMain import org.msgpack.jackson.dataformat.MessagePackFactory +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.w3c.dom.Document +import org.w3c.dom.NodeList import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification import spock.lang.TempDir import spock.util.concurrent.PollingConditions +import javax.xml.parsers.DocumentBuilder +import javax.xml.parsers.DocumentBuilderFactory import java.nio.file.FileVisitResult import java.nio.file.Files import java.nio.file.Path @@ -31,6 +41,35 @@ import static org.hamcrest.Matchers.not class MavenSmokeTest extends Specification { + private static final Logger LOGGER = LoggerFactory.getLogger(MavenSmokeTest.class) + + private static final String LATEST_MAVEN_VERSION = getLatestMavenVersion() + + private static String getLatestMavenVersion() { + OkHttpClient client = new OkHttpClient() + Request request = new Request.Builder().url("https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/maven-metadata.xml").build() + try (Response response = client.newCall(request).execute()) { + if (response.successful) { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance() + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder() + Document doc = dBuilder.parse(response.body().byteStream()) + doc.getDocumentElement().normalize() + + NodeList versionList = doc.getElementsByTagName("latest") + if (versionList.getLength() > 0) { + return versionList.item(0).getTextContent() + } + } else { + LOGGER.warn("Could not get latest maven version, response from repo.maven.apache.org is ${response.code()}: ${response.body().string()}") + } + } catch (Exception e) { + LOGGER.warn("Could not get latest maven version", e) + } + def hardcodedLatestVersion = "4.0.0-alpha-8" + LOGGER.warn("Will run the 'latest' tests with hard-coded version ${hardcodedLatestVersion}") + return hardcodedLatestVersion + } + private static final String TEST_SERVICE_NAME = "test-maven-service" private static final String TEST_ENVIRONMENT_NAME = "integration-test" private static final String JAVAC_PLUGIN_VERSION = Config.get().ciVisibilityCompilerPluginVersion @@ -80,14 +119,14 @@ class MavenSmokeTest extends Specification { prefix("/api/v2/ci/tests/skippable") { response.status(200).send('{ "data": [{' + - ' "id": "d230520a0561ee2f",' + - ' "type": "test",' + - ' "attributes": {' + - ' "configurations": {},' + - ' "name": "test_to_skip_with_itr",' + - ' "suite": "datadog.smoke.TestSucceed"' + - ' }' + - '}] }') + ' "id": "d230520a0561ee2f",' + + ' "type": "test",' + + ' "attributes": {' + + ' "configurations": {},' + + ' "name": "test_to_skip_with_itr",' + + ' "suite": "datadog.smoke.TestSucceed"' + + ' }' + + '}] }') } } } @@ -112,7 +151,7 @@ class MavenSmokeTest extends Specification { verifyEventsAndCoverages(mavenVersion) where: - mavenVersion << ["3.2.1", "3.2.5", "3.3.9", "3.5.4", "3.6.3", "3.8.8", "3.9.4", "4.0.0-alpha-7"] + mavenVersion << ["3.2.1", "3.2.5", "3.3.9", "3.5.4", "3.6.3", "3.8.8", "3.9.5", LATEST_MAVEN_VERSION] } def "test maven run with jacoco and argLine, v#mavenVersion"() { @@ -130,7 +169,7 @@ class MavenSmokeTest extends Specification { verifyEventsAndCoverages(mavenVersion) where: - mavenVersion << ["3.9.4"] + mavenVersion << ["3.9.5"] } private verifyEventsAndCoverages(String mavenVersion) { @@ -302,20 +341,20 @@ class MavenSmokeTest extends Specification { private void copyFolder(Path src, Path dest) throws IOException { Files.walkFileTree(src, new SimpleFileVisitor() { - @Override - FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) - throws IOException { - Files.createDirectories(dest.resolve(src.relativize(dir))) - return FileVisitResult.CONTINUE - } + @Override + FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + Files.createDirectories(dest.resolve(src.relativize(dir))) + return FileVisitResult.CONTINUE + } - @Override - FileVisitResult visitFile(Path file, BasicFileAttributes attrs) - throws IOException { - Files.copy(file, dest.resolve(src.relativize(file))) - return FileVisitResult.CONTINUE - } - }) + @Override + FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.copy(file, dest.resolve(src.relativize(file))) + return FileVisitResult.CONTINUE + } + }) // creating empty .git directory so that the tracer could detect projectFolder as repo root Files.createDirectory(projectHome.resolve(".git")) @@ -401,16 +440,16 @@ class MavenSmokeTest extends Specification { if (runWithAgent) { def agentShadowJar = System.getProperty("datadog.smoketest.agent.shadowJar.path") def agentArgument = "-javaagent:${agentShadowJar}=" + - "${Strings.propertyNameToSystemPropertyName(GeneralConfig.ENV)}=${TEST_ENVIRONMENT_NAME}," + - "${Strings.propertyNameToSystemPropertyName(GeneralConfig.SERVICE_NAME)}=${TEST_SERVICE_NAME}," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_ENABLED)}=true," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_ENABLED)}=true," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_CIPROVIDER_INTEGRATION_ENABLED)}=false," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_SOURCE_DATA_ROOT_CHECK_ENABLED)}=false," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_ENABLED)}=false," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_COVERAGE_SEGMENTS_ENABLED)}=true," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_COMPILER_PLUGIN_VERSION)}=${JAVAC_PLUGIN_VERSION}," + - "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_URL)}=${intakeServer.address.toString()}" + "${Strings.propertyNameToSystemPropertyName(GeneralConfig.ENV)}=${TEST_ENVIRONMENT_NAME}," + + "${Strings.propertyNameToSystemPropertyName(GeneralConfig.SERVICE_NAME)}=${TEST_SERVICE_NAME}," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_ENABLED)}=true," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_ENABLED)}=true," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_CIPROVIDER_INTEGRATION_ENABLED)}=false," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_SOURCE_DATA_ROOT_CHECK_ENABLED)}=false," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_ENABLED)}=false," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_COVERAGE_SEGMENTS_ENABLED)}=true," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_COMPILER_PLUGIN_VERSION)}=${JAVAC_PLUGIN_VERSION}," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_URL)}=${intakeServer.address.toString()}" arguments += agentArgument.toString() } return arguments diff --git a/dd-smoke-tests/play-2.8-otel/app/actions/AbstractAction.java b/dd-smoke-tests/play-2.8-otel/app/actions/AbstractAction.java index 3d56e52cadd..9e27a2421f4 100644 --- a/dd-smoke-tests/play-2.8-otel/app/actions/AbstractAction.java +++ b/dd-smoke-tests/play-2.8-otel/app/actions/AbstractAction.java @@ -9,16 +9,16 @@ public abstract class AbstractAction extends Action.Simple { - private final String operationName; + private final String spanName; - protected AbstractAction(String operationName) { - this.operationName = operationName; + protected AbstractAction(String spanName) { + this.spanName = spanName; } @Override public CompletionStage call(Http.Request req) { Tracer tracer = GlobalOpenTelemetry.getTracer("play-test"); - Span span = tracer.spanBuilder(operationName).startSpan(); + Span span = tracer.spanBuilder(spanName).startSpan(); Scope scope = span.makeCurrent(); try { return delegate.call(req); diff --git a/dd-smoke-tests/play-2.8-otel/app/filters/AbstractFilter.java b/dd-smoke-tests/play-2.8-otel/app/filters/AbstractFilter.java index f9e28b8f6b8..b52223036aa 100644 --- a/dd-smoke-tests/play-2.8-otel/app/filters/AbstractFilter.java +++ b/dd-smoke-tests/play-2.8-otel/app/filters/AbstractFilter.java @@ -12,17 +12,16 @@ public abstract class AbstractFilter extends Filter { private final HttpExecutionContext ec; - private final String operationName; + private final String spanName; private final boolean wrap; - public AbstractFilter(String operationName, Materializer mat, HttpExecutionContext ec) { - this(operationName, false, mat, ec); + public AbstractFilter(String spanName, Materializer mat, HttpExecutionContext ec) { + this(spanName, false, mat, ec); } - public AbstractFilter( - String operationName, boolean wrap, Materializer mat, HttpExecutionContext ec) { + public AbstractFilter(String spanName, boolean wrap, Materializer mat, HttpExecutionContext ec) { super(mat); - this.operationName = operationName; + this.spanName = spanName; this.wrap = wrap; this.ec = ec; } @@ -32,14 +31,14 @@ public CompletionStage apply( Function> nextFilter, Http.RequestHeader requestHeader) { final Tracer tracer = GlobalOpenTelemetry.getTracer("play-test"); - final Span startedSpan = wrap ? tracer.spanBuilder(operationName).startSpan() : null; + final Span startedSpan = wrap ? tracer.spanBuilder(spanName).startSpan() : null; Scope outerScope = wrap ? startedSpan.makeCurrent() : null; try { return nextFilter .apply(requestHeader) .thenApplyAsync( result -> { - Span span = wrap ? startedSpan : tracer.spanBuilder(operationName).startSpan(); + Span span = wrap ? startedSpan : tracer.spanBuilder(spanName).startSpan(); try (Scope innerScope = span.makeCurrent()) { // Yes this does no real work return result; diff --git a/dd-smoke-tests/play-2.8-otel/src/test/groovy/datadog/smoketest/Play28OTelSmokeTest.groovy b/dd-smoke-tests/play-2.8-otel/src/test/groovy/datadog/smoketest/Play28OTelSmokeTest.groovy index b2a09396abe..2f8655ac7c4 100644 --- a/dd-smoke-tests/play-2.8-otel/src/test/groovy/datadog/smoketest/Play28OTelSmokeTest.groovy +++ b/dd-smoke-tests/play-2.8-otel/src/test/groovy/datadog/smoketest/Play28OTelSmokeTest.groovy @@ -52,8 +52,8 @@ abstract class Play28OTelSmokeTest extends AbstractServerSmokeTest { + " -Dhttp.port=${httpPort}" + " -Dhttp.address=127.0.0.1" + " -Dplay.server.provider=${serverProvider()}" - + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()},DDAgentWriter" - + " -Ddd.integration.opentelemetry.experimental.enabled=true" + + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()}:includeresource,DDAgentWriter" + + " -Ddd.integration.opentelemetry-1.enabled=true" + " -Dclient.request.base=${clientServer.address}/hello/") return processBuilder } @@ -77,14 +77,14 @@ abstract class Play28OTelSmokeTest extends AbstractServerSmokeTest { // that is completed is completed before the span is finished, the order of those filters and the request processing // is undefined. boolean isOk = true - def allowed = /|^\[${serverProviderName()}.request - |(\[filter1(\[filter\d])(\[filter\d])(\[filter\d])])? - |\[play.request\[action1\[action2\[do-get\[play-ws.request]]]]] - |(\[filter1(\[filter\d])(\[filter\d])(\[filter\d])])? + def allowed = /|^\[${serverProviderName()}.request:GET \/welcome[sj] + |(\[internal:filter1(\[internal:filter\d])(\[internal:filter\d])(\[internal:filter\d])])? + |\[play.request:GET \/welcome[sj]\[internal:action1\[internal:action2\[internal:do-get\[play-ws.request:GET \/hello\/\?]]]]] + |(\[internal:filter1(\[internal:filter\d])(\[internal:filter\d])(\[internal:filter\d])])? |]$/.stripMargin().replaceAll("[\n\r]", "") // Ignore [command_execution], which can be generated by hostname calls from Config/Telemetry on some systems. - traceCounts = traceCounts.findAll { it.key != "[command_execution]" } + traceCounts = traceCounts.findAll { it.key != "[command_execution:hostname]" } traceCounts.entrySet().each { def matcher = (it.key =~ allowed).findAll() @@ -94,9 +94,9 @@ abstract class Play28OTelSmokeTest extends AbstractServerSmokeTest { |traceCounts=${traceCounts}""".stripMargin() def matches = matcher.head().findAll{ it != null } isOk &= matches.size() == 5 - isOk &= matches.contains("[filter2]") - isOk &= matches.contains("[filter3]") - isOk &= matches.contains("[filter4]") + isOk &= matches.contains("[internal:filter2]") + isOk &= matches.contains("[internal:filter3]") + isOk &= matches.contains("[internal:filter4]") assert isOk : """\ |Trace ${it.key} does not match allowed pattern: |pattern=${allowed} diff --git a/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle b/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle index abb4140aa8f..f1889962b51 100644 --- a/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle +++ b/dd-smoke-tests/spring-boot-2.6-webmvc/build.gradle @@ -35,6 +35,7 @@ dependencies { implementation group: 'com.h2database', name: 'h2', version: '2.1.214' implementation group: 'jakarta.xml.bind', name: 'jakarta.xml.bind-api', version: '2.3.3' implementation group: 'com.sun.xml.bind', name: 'jaxb-ri', version: '2.3.3' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.10' testImplementation project(':dd-smoke-tests') diff --git a/dd-smoke-tests/spring-boot-2.6-webmvc/src/test/groovy/GsonIastSpringBootSmokeTest.groovy b/dd-smoke-tests/spring-boot-2.6-webmvc/src/test/groovy/GsonIastSpringBootSmokeTest.groovy new file mode 100644 index 00000000000..4671156ada2 --- /dev/null +++ b/dd-smoke-tests/spring-boot-2.6-webmvc/src/test/groovy/GsonIastSpringBootSmokeTest.groovy @@ -0,0 +1,9 @@ +import datadog.smoketest.AbstractGsonIastSpringBootSmokeTest + +class GsonIastSpringBootSmokeTest extends AbstractGsonIastSpringBootSmokeTest{ + + @Override + String[] getCustomSpringProperties(){ + return ['-Dspring.mvc.converters.preferred-json-mapper=gson'] + } +} diff --git a/dd-smoke-tests/springboot/src/test/groovy/datadog/smoketest/GsonIastSpringBootSmokeTest.groovy b/dd-smoke-tests/springboot/src/test/groovy/datadog/smoketest/GsonIastSpringBootSmokeTest.groovy new file mode 100644 index 00000000000..3ffe512bf27 --- /dev/null +++ b/dd-smoke-tests/springboot/src/test/groovy/datadog/smoketest/GsonIastSpringBootSmokeTest.groovy @@ -0,0 +1,9 @@ +package datadog.smoketest + +class GsonIastSpringBootSmokeTest extends AbstractGsonIastSpringBootSmokeTest{ + + @Override + String[] getCustomSpringProperties(){ + return ['-Dspring.http.converters.preferred-json-mapper=gson'] + } +} diff --git a/dd-smoke-tests/springboot/src/test/groovy/datadog/smoketest/IastSpringBootSmokeTest.groovy b/dd-smoke-tests/springboot/src/test/groovy/datadog/smoketest/IastSpringBootSmokeTest.groovy index 949556d3139..628ed6901e3 100644 --- a/dd-smoke-tests/springboot/src/test/groovy/datadog/smoketest/IastSpringBootSmokeTest.groovy +++ b/dd-smoke-tests/springboot/src/test/groovy/datadog/smoketest/IastSpringBootSmokeTest.groovy @@ -1,7 +1,6 @@ package datadog.smoketest import groovy.transform.CompileDynamic -import okhttp3.FormBody import okhttp3.Request import okhttp3.Response @@ -26,23 +25,4 @@ class IastSpringBootSmokeTest extends AbstractIastSpringBootTest { it.ranges[0].source.origin == 'http.request.header' } } - - - void 'gson deserialization'() { - - given: - final url = "http://localhost:${httpPort}/gson_deserialization" - final body = new FormBody.Builder().add('json', '{"name": "gsonTest", "value" : "valueTest"}').build() - final request = new Request.Builder().url(url).post(body).build() - - when: - client.newCall(request).execute() - - then: - hasTainted { tainted -> - tainted.value == 'gsonTest' && - tainted.ranges[0].source.name == 'json' && - tainted.ranges[0].source.origin == 'http.request.parameter' - } - } } diff --git a/dd-trace-api/build.gradle b/dd-trace-api/build.gradle index 0341c91c2e7..228298c5cf6 100644 --- a/dd-trace-api/build.gradle +++ b/dd-trace-api/build.gradle @@ -19,6 +19,10 @@ excludedClassesCoverage += [ 'datadog.trace.api.internal.TraceSegment.NoOp', 'datadog.trace.api.civisibility.CIVisibility', 'datadog.trace.api.civisibility.DDTestModule', + 'datadog.trace.api.civisibility.noop.NoOpDDTest', + 'datadog.trace.api.civisibility.noop.NoOpDDTestModule', + 'datadog.trace.api.civisibility.noop.NoOpDDTestSession', + 'datadog.trace.api.civisibility.noop.NoOpDDTestSuite', 'datadog.trace.api.config.ProfilingConfig', 'datadog.trace.api.interceptor.MutableSpan', 'datadog.trace.api.profiling.Profiling', diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index 24cd05cacdf..26514fbb7a9 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -1,8 +1,12 @@ package datadog.trace.api; -import java.util.Arrays; +import static datadog.trace.api.TracePropagationStyle.DATADOG; +import static datadog.trace.api.TracePropagationStyle.TRACECONTEXT; +import static java.util.Arrays.asList; + import java.util.BitSet; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; public final class ConfigDefaults { @@ -64,6 +68,10 @@ public final class ConfigDefaults { static final int DEFAULT_SCOPE_ITERATION_KEEP_ALIVE = 30; // in seconds static final int DEFAULT_PARTIAL_FLUSH_MIN_SPANS = 1000; static final boolean DEFAULT_PROPAGATION_EXTRACT_LOG_HEADER_NAMES_ENABLED = false; + static final Set DEFAULT_TRACE_PROPAGATION_STYLE = + new LinkedHashSet<>(asList(DATADOG, TRACECONTEXT)); + static final Set DEFAULT_PROPAGATION_STYLE = + new LinkedHashSet<>(asList(PropagationStyle.DATADOG)); static final boolean DEFAULT_JMX_FETCH_ENABLED = true; static final boolean DEFAULT_TRACE_AGENT_V05_ENABLED = false; @@ -71,6 +79,8 @@ public final class ConfigDefaults { static final int DEFAULT_CLOCK_SYNC_PERIOD = 30; // seconds + static final boolean DEFAULT_TRACE_PROPAGATION_EXTRACT_FIRST = false; + static final boolean DEFAULT_JMX_FETCH_MULTIPLE_RUNTIME_SERVICES_ENABLED = false; static final int DEFAULT_JMX_FETCH_MULTIPLE_RUNTIME_SERVICES_LIMIT = 10; @@ -96,7 +106,7 @@ public final class ConfigDefaults { public static final int DEFAULT_IAST_VULNERABILITIES_PER_REQUEST = 2; public static final int DEFAULT_IAST_REQUEST_SAMPLING = 33; static final Set DEFAULT_IAST_WEAK_HASH_ALGORITHMS = - new HashSet<>(Arrays.asList("SHA1", "SHA-1", "MD2", "MD5", "RIPEMD128", "MD4")); + new HashSet<>(asList("SHA1", "SHA-1", "MD2", "MD5", "RIPEMD128", "MD4")); static final String DEFAULT_IAST_WEAK_CIPHER_ALGORITHMS = "^(?:PBEWITH(?:HMACSHA(?:2(?:24ANDAES_(?:128|256)|56ANDAES_(?:128|256))|384ANDAES_(?:128|256)|512ANDAES_(?:128|256)|1ANDAES_(?:128|256))|SHA1AND(?:RC(?:2_(?:128|40)|4_(?:128|40))|DESEDE)|MD5AND(?:TRIPLEDES|DES))|DES(?:EDE(?:WRAP)?)?|BLOWFISH|ARCFOUR|RC2).*$"; static final boolean DEFAULT_IAST_REDACTION_ENABLED = true; @@ -105,6 +115,7 @@ public final class ConfigDefaults { static final String DEFAULT_IAST_REDACTION_VALUE_PATTERN = "(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))"; public static final int DEFAULT_IAST_MAX_RANGE_COUNT = 10; + static final boolean DEFAULT_IAST_STACKTRACE_LEAK_SUPPRESS = false; static final int DEFAULT_IAST_TRUNCATION_MAX_VALUE_LENGTH = 250; public static final boolean DEFAULT_IAST_DEDUPLICATION_ENABLED = true; @@ -118,8 +129,8 @@ public final class ConfigDefaults { static final boolean DEFAULT_CIVISIBILITY_BUILD_INSTRUMENTATION_ENABLED = true; static final boolean DEFAULT_CIVISIBILITY_AUTO_CONFIGURATION_ENABLED = true; static final boolean DEFAULT_CIVISIBILITY_COMPILER_PLUGIN_AUTO_CONFIGURATION_ENABLED = true; - static final String DEFAULT_CIVISIBILITY_COMPILER_PLUGIN_VERSION = "0.1.7"; - static final String DEFAULT_CIVISIBILITY_JACOCO_PLUGIN_VERSION = "0.8.10"; + static final String DEFAULT_CIVISIBILITY_COMPILER_PLUGIN_VERSION = "0.1.8"; + static final String DEFAULT_CIVISIBILITY_JACOCO_PLUGIN_VERSION = "0.8.11"; static final String DEFAULT_CIVISIBILITY_JACOCO_PLUGIN_EXCLUDES = "datadog.trace.*:org.apache.commons.*:org.mockito.*"; static final boolean DEFAULT_CIVISIBILITY_GIT_UPLOAD_ENABLED = true; @@ -181,8 +192,9 @@ public final class ConfigDefaults { 24 * 60 * 60; // 24 hours in seconds static final int DEFAULT_TELEMETRY_METRICS_INTERVAL = 10; // in seconds static final boolean DEFAULT_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED = true; + static final boolean DEFAULT_TELEMETRY_LOG_COLLECTION_ENABLED = false; - static final boolean DEFAULT_TRACE_128_BIT_TRACEID_GENERATION_ENABLED = false; + static final boolean DEFAULT_TRACE_128_BIT_TRACEID_GENERATION_ENABLED = true; static final boolean DEFAULT_TRACE_128_BIT_TRACEID_LOGGING_ENABLED = false; static final boolean DEFAULT_SECURE_RANDOM = false; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java b/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java index c2a7397b4fb..4a3e7e1cc18 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/DDTags.java @@ -52,5 +52,7 @@ public class DDTags { public static final String INTERNAL_GIT_COMMIT_SHA = "_dd.git.commit.sha"; public static final String PROFILING_ENABLED = "_dd.profiling.enabled"; + + public static final String PROFILING_CONTEXT_ENGINE = "_dd.profiling.ctx"; public static final String BASE_SERVICE = "_dd.base_service"; } diff --git a/dd-trace-api/src/main/java/datadog/trace/api/civisibility/CIVisibility.java b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/CIVisibility.java index 7ef83fdc289..688036b4566 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/civisibility/CIVisibility.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/CIVisibility.java @@ -1,5 +1,6 @@ package datadog.trace.api.civisibility; +import datadog.trace.api.civisibility.noop.NoOpDDTestSession; import java.nio.file.Path; import java.nio.file.Paths; import javax.annotation.Nullable; @@ -7,10 +8,7 @@ public class CIVisibility { private static volatile SessionFactory SESSION_FACTORY = - (projectName, projectRoot, component, startTime) -> { - throw new UnsupportedOperationException( - "session factory not registered, please ensure CI Visibility feature is enabled"); - }; + (projectName, projectRoot, component, startTime) -> NoOpDDTestSession.INSTANCE; /** * This a hook for injecting SessionFactory implementation. It should only be used internally by diff --git a/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTest.java b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTest.java new file mode 100644 index 00000000000..00162a78860 --- /dev/null +++ b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTest.java @@ -0,0 +1,20 @@ +package datadog.trace.api.civisibility.noop; + +import datadog.trace.api.civisibility.DDTest; +import javax.annotation.Nullable; + +public class NoOpDDTest implements DDTest { + static final DDTest INSTANCE = new NoOpDDTest(); + + @Override + public void setTag(String key, Object value) {} + + @Override + public void setErrorInfo(@Nullable Throwable error) {} + + @Override + public void setSkipReason(@Nullable String skipReason) {} + + @Override + public void end(@Nullable Long endTime) {} +} diff --git a/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestModule.java b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestModule.java new file mode 100644 index 00000000000..eb67b09514b --- /dev/null +++ b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestModule.java @@ -0,0 +1,30 @@ +package datadog.trace.api.civisibility.noop; + +import datadog.trace.api.civisibility.DDTestModule; +import datadog.trace.api.civisibility.DDTestSuite; +import javax.annotation.Nullable; + +public class NoOpDDTestModule implements DDTestModule { + static final DDTestModule INSTANCE = new NoOpDDTestModule(); + + @Override + public void setTag(String key, Object value) {} + + @Override + public void setErrorInfo(Throwable error) {} + + @Override + public void setSkipReason(String skipReason) {} + + @Override + public void end(@Nullable Long endTime) {} + + @Override + public DDTestSuite testSuiteStart( + String testSuiteName, + @Nullable Class testClass, + @Nullable Long startTime, + boolean parallelized) { + return NoOpDDTestSuite.INSTANCE; + } +} diff --git a/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestSession.java b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestSession.java new file mode 100644 index 00000000000..48f768cee93 --- /dev/null +++ b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestSession.java @@ -0,0 +1,26 @@ +package datadog.trace.api.civisibility.noop; + +import datadog.trace.api.civisibility.DDTestModule; +import datadog.trace.api.civisibility.DDTestSession; +import javax.annotation.Nullable; + +public class NoOpDDTestSession implements DDTestSession { + public static final DDTestSession INSTANCE = new NoOpDDTestSession(); + + @Override + public void setTag(String key, Object value) {} + + @Override + public void setErrorInfo(Throwable error) {} + + @Override + public void setSkipReason(String skipReason) {} + + @Override + public void end(@Nullable Long endTime) {} + + @Override + public DDTestModule testModuleStart(String moduleName, @Nullable Long startTime) { + return NoOpDDTestModule.INSTANCE; + } +} diff --git a/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestSuite.java b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestSuite.java new file mode 100644 index 00000000000..4fb420de390 --- /dev/null +++ b/dd-trace-api/src/main/java/datadog/trace/api/civisibility/noop/NoOpDDTestSuite.java @@ -0,0 +1,27 @@ +package datadog.trace.api.civisibility.noop; + +import datadog.trace.api.civisibility.DDTest; +import datadog.trace.api.civisibility.DDTestSuite; +import java.lang.reflect.Method; +import javax.annotation.Nullable; + +public class NoOpDDTestSuite implements DDTestSuite { + static final DDTestSuite INSTANCE = new NoOpDDTestSuite(); + + @Override + public void setTag(String key, Object value) {} + + @Override + public void setErrorInfo(Throwable error) {} + + @Override + public void setSkipReason(String skipReason) {} + + @Override + public void end(@Nullable Long endTime) {} + + @Override + public DDTest testStart(String testName, @Nullable Method testMethod, @Nullable Long startTime) { + return NoOpDDTest.INSTANCE; + } +} diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java index 1a3d861c3ac..59442a18588 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java @@ -58,8 +58,11 @@ public final class CiVisibilityConfig { "civisibility.repo.index.sharing.enabled"; public static final String CIVISIBILITY_MODULE_EXECUTION_SETTINGS_CACHE_SIZE = "civisibility.module.execution.settings.cache.size"; + public static final String CIVISIBILITY_JVM_INFO_CACHE_SIZE = "civisibility.jvm.info.cache.size"; public static final String CIVISIBILITY_COVERAGE_SEGMENTS_ENABLED = "civisibility.coverage.segments.enabled"; + public static final String CIVISIBILITY_INJECTED_TRACER_VERSION = + "civisibility.injected.tracer.version"; private CiVisibilityConfig() {} } diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java index b7448701013..05389a0d726 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java @@ -72,6 +72,7 @@ public final class GeneralConfig { public static final String TELEMETRY_METRICS_ENABLED = "telemetry.metrics.enabled"; public static final String TELEMETRY_DEPENDENCY_COLLECTION_ENABLED = "telemetry.dependency-collection.enabled"; + public static final String TELEMETRY_LOG_COLLECTION_ENABLED = "telemetry.log-collection.enabled"; public static final String TELEMETRY_DEBUG_REQUESTS_ENABLED = "telemetry.debug.requests.enabled"; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/IastConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/IastConfig.java index 3b6aa5c284c..286533c8230 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/IastConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/IastConfig.java @@ -16,6 +16,7 @@ public final class IastConfig { public static final String IAST_REDACTION_ENABLED = "iast.redaction.enabled"; public static final String IAST_REDACTION_NAME_PATTERN = "iast.redaction.name.pattern"; public static final String IAST_REDACTION_VALUE_PATTERN = "iast.redaction.value.pattern"; + public static final String IAST_STACKTRACE_LEAK_SUPPRESS = "iast.stacktrace-leak.suppress"; public static final String IAST_MAX_RANGE_COUNT = "iast.max-range-count"; public static final String IAST_TRUNCATION_MAX_VALUE_LENGTH = "iast.trunctation.max.value.length"; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java index 0f166d804f1..dcef2f2e1a2 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java @@ -114,8 +114,10 @@ public final class ProfilingConfig { public static final String PROFILING_DATADOG_PROFILER_CSTACK = "profiling.ddprof.cstack"; public static final String PROFILING_DATADOG_PROFILER_CSTACK_DEFAULT = "fp"; public static final String PROFILING_DATADOG_PROFILER_SAFEMODE = "profiling.ddprof.safemode"; - public static final int PROFILING_DATADOG_PROFILER_SAFEMODE_DEFAULT = - 12; // POP_METHOD|UNWIND_NATIVE + + private static final int POP_METHOD = 4; + private static final int LAST_JAVA_PC = 16; + public static final int PROFILING_DATADOG_PROFILER_SAFEMODE_DEFAULT = POP_METHOD | LAST_JAVA_PC; public static final String PROFILING_DATADOG_PROFILER_LINE_NUMBERS = "profiling.ddprof.linenumbers"; @@ -188,5 +190,8 @@ public final class ProfilingConfig { public static final String PROFILING_HEAP_HISTOGRAM_ENABLED = "profiling.heap.histogram.enabled"; public static final boolean PROFILING_HEAP_HISTOGRAM_ENABLED_DEFAULT = false; + public static final String PROFILING_HEAP_HISTOGRAM_MODE = "profiling.heap.histogram.mode"; + public static final String PROFILING_HEAP_HISTOGRAM_MODE_DEFAULT = "aftergc"; + private ProfilingConfig() {} } diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java index a0b82c7dcb2..3833509a78a 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java @@ -92,6 +92,9 @@ public final class TraceInstrumentationConfig { public static final String GRPC_IGNORED_INBOUND_METHODS = "trace.grpc.ignored.inbound.methods"; public static final String GRPC_IGNORED_OUTBOUND_METHODS = "trace.grpc.ignored.outbound.methods"; + + public static final String GOOGLE_PUBSUB_IGNORED_GRPC_METHODS = + "trace.google-pubsub.ignored.grpc.methods"; public static final String GRPC_SERVER_TRIM_PACKAGE_RESOURCE = "trace.grpc.server.trim-package-resource"; public static final String GRPC_SERVER_ERROR_STATUSES = "grpc.server.error.statuses"; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java index 3971d64eaa4..a4600c314a1 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java @@ -84,6 +84,7 @@ public final class TracerConfig { public static final String TRACE_PROPAGATION_STYLE = "trace.propagation.style"; public static final String TRACE_PROPAGATION_STYLE_EXTRACT = "trace.propagation.style.extract"; public static final String TRACE_PROPAGATION_STYLE_INJECT = "trace.propagation.style.inject"; + public static final String TRACE_PROPAGATION_EXTRACT_FIRST = "trace.propagation.extract.first"; public static final String ENABLE_TRACE_AGENT_V05 = "trace.agent.v0.5.enabled"; diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/WriterFactory.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/WriterFactory.java index d7395c1f936..a563695ee6e 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/WriterFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/WriterFactory.java @@ -164,6 +164,7 @@ private static RemoteApi createDDIntakeRemoteApi( TrackType trackType) { if (featuresDiscovery.supportsEvpProxy() && !config.isCiVisibilityAgentlessEnabled()) { return DDEvpProxyApi.builder() + .httpClient(commObjects.okHttpClient) .agentUrl(commObjects.agentUrl) .evpProxyEndpoint(featuresDiscovery.getEvpProxyEndpoint()) .trackType(trackType) diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddintake/DDEvpProxyApi.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddintake/DDEvpProxyApi.java index e0c0d8f69e8..6823a7b55a2 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddintake/DDEvpProxyApi.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddintake/DDEvpProxyApi.java @@ -64,7 +64,7 @@ public DDEvpProxyApiBuilder agentUrl(final HttpUrl agentUrl) { return this; } - DDEvpProxyApiBuilder httpClient(final OkHttpClient httpClient) { + public DDEvpProxyApiBuilder httpClient(final OkHttpClient httpClient) { this.httpClient = httpClient; return this; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index 9caf0eebbd1..33dd6beb80e 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -2,6 +2,7 @@ import static datadog.communication.monitor.DDAgentStatsDClientManager.statsDClientManager; import static datadog.trace.api.ConfigDefaults.DEFAULT_ASYNC_PROPAGATING; +import static datadog.trace.api.DDTags.PROFILING_CONTEXT_ENGINE; import static datadog.trace.common.metrics.MetricsAggregatorFactory.createMetricsAggregator; import static datadog.trace.util.AgentThreadFactory.AGENT_THREAD_GROUP; import static datadog.trace.util.CollectionUtils.tryMakeImmutableMap; @@ -517,7 +518,6 @@ private CoreTracer( .apply(); this.logs128bTraceIdEnabled = InstrumenterConfig.get().isLogs128bTraceIdEnabled(); - this.localRootSpanTags = localRootSpanTags; this.defaultSpanTags = defaultSpanTags; this.partialFlushMinSpans = partialFlushMinSpans; this.idGenerationStrategy = @@ -668,6 +668,13 @@ private CoreTracer( this.profilingContextIntegration = profilingContextIntegration; this.injectBaggageAsTags = injectBaggageAsTags; this.allowInferredServices = SpanNaming.instance().namingSchema().allowInferredServices(); + if (profilingContextIntegration != ProfilingContextIntegration.NoOp.INSTANCE) { + Map tmp = new HashMap<>(localRootSpanTags); + tmp.put(PROFILING_CONTEXT_ENGINE, profilingContextIntegration.name()); + this.localRootSpanTags = tryMakeImmutableMap(tmp); + } else { + this.localRootSpanTags = localRootSpanTags; + } } /** Used by AgentTestRunner to inject configuration into the test tracer. */ @@ -1191,6 +1198,7 @@ public CoreSpanBuilder ignoreActiveSpan() { } private DDSpan buildSpan() { + addTerminatedContextAsLinks(); DDSpan span = DDSpan.create(instrumentationName, timestampMicro, buildSpanContext(), links); if (span.isLocalRootSpan()) { EndpointTracker tracker = tracer.onRootSpanStarted(span); @@ -1199,6 +1207,19 @@ private DDSpan buildSpan() { return span; } + private void addTerminatedContextAsLinks() { + if (this.parent instanceof TagContext) { + List terminatedContextLinks = + ((TagContext) this.parent).getTerminatedContextLinks(); + if (!terminatedContextLinks.isEmpty()) { + if (this.links == null) { + this.links = new ArrayList<>(); + } + this.links.addAll(terminatedContextLinks); + } + } + } + @Override public AgentSpan start() { return buildSpan(); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java index 92ac2a970f3..c7763468e2c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java @@ -333,7 +333,7 @@ public DDSpanContext( // as fast as we can try to make this operation, we still might need to activate/deactivate // contexts at alarming rates in unpredictable async applications, so we'll try // to get away with doing this just once per span - this.encodedOperationName = profilingContextIntegration.encode(operationName); + this.encodedOperationName = profilingContextIntegration.encodeOperationName(operationName); setServiceName(serviceName); this.operationName = operationName; @@ -419,7 +419,7 @@ public void setResourceName(final CharSequence resourceName, byte priority) { if (priority >= this.resourceNamePriority) { this.resourceNamePriority = priority; this.resourceName = resourceName; - this.encodedResourceName = profilingContextIntegration.encode(resourceName); + this.encodedResourceName = profilingContextIntegration.encodeResourceName(resourceName); } } @@ -433,7 +433,7 @@ public CharSequence getOperationName() { public void setOperationName(final CharSequence operationName) { this.operationName = operationName; - this.encodedOperationName = profilingContextIntegration.encode(operationName); + this.encodedOperationName = profilingContextIntegration.encodeOperationName(operationName); } public boolean getErrorFlag() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/StatusLogger.java b/dd-trace-core/src/main/java/datadog/trace/core/StatusLogger.java index 4f822f43e44..cf9609cb1c2 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/StatusLogger.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/StatusLogger.java @@ -122,6 +122,10 @@ public void toJson(JsonWriter writer, Config config) throws IOException { writer.value(config.getAppSecRulesFile()); writer.name("telemetry_enabled"); writer.value(config.isTelemetryEnabled()); + writer.name("telemetry_dependency_collection_enabled"); + writer.value(config.isTelemetryDependencyServiceEnabled()); + writer.name("telemetry_log_collection_enabled"); + writer.value(config.isTelemetryLogCollectionEnabled()); writer.name("dd_version"); writer.value(config.getVersion()); writer.name("health_checks_enabled"); @@ -146,6 +150,8 @@ public void toJson(JsonWriter writer, Config config) throws IOException { writer.name("iast_enabled"); writer.value(config.getIastActivation().toString()); } + writer.name("data_streams_enabled"); + writer.value(config.isDataStreamsEnabled()); writer.endObject(); } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/datastreams/TagsProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/datastreams/TagsProcessor.java index 945f30dfe18..57a87ce2248 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/datastreams/TagsProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/datastreams/TagsProcessor.java @@ -48,7 +48,11 @@ public String apply(String key) { private static final Function GROUP_TAG_PREFIX = new StringPrefix("group:"); private static final Function CONSUMER_GROUP_TAG_PREFIX = new StringPrefix("consumer_group:"); - + public static final String SUBSCRIPTION_TAG = "subscription"; + private static final DDCache SUBSCRIPTION_TAG_CACHE = + DDCaches.newFixedSizeCache(32); + private static final Function SUBSCRIPTION_TAG_PREFIX = + new StringPrefix("subscription:"); public static final String EXCHANGE_TAG = "exchange"; private static final DDCache EXCHANGE_TAG_CACHE = DDCaches.newFixedSizeCache(32); private static final Function EXCHANGE_TAG_PREFIX = new StringPrefix("exchange:"); @@ -70,6 +74,7 @@ private static Map> createTagToCacheMap() { result.put(PARTITION_TAG, PARTITION_TAG_CACHE); result.put(GROUP_TAG, GROUP_TAG_CACHE); result.put(CONSUMER_GROUP_TAG, CONSUMER_GROUP_TAG_CACHE); + result.put(SUBSCRIPTION_TAG, SUBSCRIPTION_TAG_CACHE); result.put(EXCHANGE_TAG, EXCHANGE_TAG_CACHE); result.put(HAS_ROUTING_KEY_TAG, HAS_ROUTING_KEY_TAG_CACHE); return result; @@ -83,6 +88,7 @@ private static Map> createTagToPrefixMap() { result.put(PARTITION_TAG, PARTITION_TAG_PREFIX); result.put(GROUP_TAG, GROUP_TAG_PREFIX); result.put(CONSUMER_GROUP_TAG, CONSUMER_GROUP_TAG_PREFIX); + result.put(SUBSCRIPTION_TAG, SUBSCRIPTION_TAG_PREFIX); result.put(EXCHANGE_TAG, EXCHANGE_TAG_PREFIX); result.put(HAS_ROUTING_KEY_TAG, HAS_ROUTING_KEY_TAG_PREFIX); return result; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java index 5748c6a72d7..e39f0745851 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java @@ -1,5 +1,7 @@ package datadog.trace.core.propagation; +import static datadog.trace.api.TracePropagationStyle.B3MULTI; +import static datadog.trace.api.TracePropagationStyle.B3SINGLE; import static datadog.trace.core.propagation.HttpCodec.firstHeaderValue; import datadog.trace.api.Config; @@ -7,6 +9,7 @@ import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.sampling.PrioritySampling; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.core.DDSpanContext; @@ -161,7 +164,7 @@ static HttpCodec.Extractor newExtractor( final List extractors = new ArrayList<>(2); extractors.add(newSingleExtractor(config, traceConfigSupplier)); extractors.add(newMultiExtractor(config, traceConfigSupplier)); - return new HttpCodec.CompoundExtractor(extractors); + return new HttpCodec.CompoundExtractor(extractors, config.isTracePropagationExtractFirst()); } public static HttpCodec.Extractor newMultiExtractor( @@ -212,6 +215,11 @@ private B3MultiContextInterpreter(Config config) { super(config); } + @Override + public TracePropagationStyle style() { + return B3MULTI; + } + @Override public boolean accept(final String key, final String value) { if (null == key || key.isEmpty() || null == value || value.isEmpty()) { @@ -267,6 +275,11 @@ public B3SingleContextInterpreter(Config config) { super(config); } + @Override + public TracePropagationStyle style() { + return B3SINGLE; + } + @Override public boolean accept(String key, String value) { try { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java index 8897339d9f6..7e2368bbc76 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java @@ -21,6 +21,7 @@ import datadog.trace.api.DDTraceId; import datadog.trace.api.Functions; import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.cache.DDCache; import datadog.trace.api.cache.DDCaches; import datadog.trace.api.sampling.PrioritySampling; @@ -47,6 +48,7 @@ public abstract class ContextInterpreter implements AgentPropagation.KeyClassifi protected long endToEndStartTime; protected boolean valid; protected boolean fullContext; + protected final PropagationTags.Factory propagationTagsFactory; protected PropagationTags propagationTags; private TagContext.HttpHeaders httpHeaders; @@ -58,7 +60,7 @@ public abstract class ContextInterpreter implements AgentPropagation.KeyClassifi protected static final boolean LOG_EXTRACT_HEADER_NAMES = Config.get().isLogExtractHeaderNames(); private static final DDCache CACHE = DDCaches.newFixedSizeCache(64); - protected String toLowerCase(String key) { + protected static String toLowerCase(String key) { return CACHE.computeIfAbsent(key, Functions.LowerCase.INSTANCE); } @@ -66,11 +68,15 @@ protected ContextInterpreter(Config config) { this.customIpHeaderName = config.getTraceClientIpHeader(); this.clientIpResolutionEnabled = config.isTraceClientIpResolverEnabled(); this.clientIpWithoutAppSec = config.isClientIpEnabled(); + this.propagationTagsFactory = PropagationTags.factory(config); } - public interface Factory { - ContextInterpreter create(); - } + /** + * Gets the propagation style handled by the context interpreter. + * + * @return The propagation style handled. + */ + public abstract TracePropagationStyle style(); protected final boolean handledForwarding(String key, String value) { if (value == null || !collectIpHeaders) { @@ -229,20 +235,21 @@ public ContextInterpreter reset(TraceConfig traceConfig) { protected TagContext build() { if (valid) { if (fullContext && !DDTraceId.ZERO.equals(traceId)) { - final ExtractedContext context; - context = - new ExtractedContext( - traceId, - spanId, - samplingPriorityOrDefault(traceId, samplingPriority), - origin, - endToEndStartTime, - baggage, - tags, - httpHeaders, - propagationTags, - traceConfig); - return context; + if (propagationTags == null) { + propagationTags = propagationTagsFactory.empty(); + } + return new ExtractedContext( + traceId, + spanId, + samplingPriorityOrDefault(traceId, samplingPriority), + origin, + endToEndStartTime, + baggage, + tags, + httpHeaders, + propagationTags, + traceConfig, + style()); } else if (origin != null || !tags.isEmpty() || httpHeaders != null @@ -254,7 +261,8 @@ protected TagContext build() { httpHeaders, baggage, samplingPriorityOrDefault(traceId, samplingPriority), - traceConfig); + traceConfig, + style()); } } return null; @@ -284,4 +292,8 @@ private int samplingPriorityOrDefault(DDTraceId traceId, int samplingPriority) { ? defaultSamplingPriority() : samplingPriority; } + + public interface Factory { + ContextInterpreter create(); + } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/DatadogHttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/DatadogHttpCodec.java index a2162875fd6..9624a54a50a 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/DatadogHttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/DatadogHttpCodec.java @@ -1,5 +1,6 @@ package datadog.trace.core.propagation; +import static datadog.trace.api.TracePropagationStyle.DATADOG; import static datadog.trace.core.propagation.HttpCodec.firstHeaderValue; import static datadog.trace.core.propagation.XRayHttpCodec.XRayContextInterpreter.handleXRayTraceHeader; import static datadog.trace.core.propagation.XRayHttpCodec.X_AMZN_TRACE_ID; @@ -12,9 +13,11 @@ import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.TagContext; import datadog.trace.core.DDSpanContext; +import datadog.trace.core.propagation.PropagationTags.HeaderType; import java.util.Map; import java.util.TreeMap; import java.util.function.Supplier; @@ -75,8 +78,7 @@ public void inject( } // inject x-datadog-tags - String datadogTags = - context.getPropagationTags().headerValue(PropagationTags.HeaderType.DATADOG); + String datadogTags = context.getPropagationTags().headerValue(HeaderType.DATADOG); if (datadogTags != null) { setter.set(carrier, DATADOG_TAGS_KEY, datadogTags); } @@ -101,12 +103,15 @@ private static class DatadogContextInterpreter extends ContextInterpreter { private static final int IGNORE = -1; private final boolean isAwsPropagationEnabled; - private final PropagationTags.Factory datadogTagsFactory; private DatadogContextInterpreter(Config config) { super(config); isAwsPropagationEnabled = config.isAwsPropagationEnabled(); - datadogTagsFactory = PropagationTags.factory(config); + } + + @Override + public TracePropagationStyle style() { + return DATADOG; } @Override @@ -180,8 +185,7 @@ public boolean accept(String key, String value) { endToEndStartTime = extractEndToEndStartTime(firstHeaderValue(value)); break; case DD_TAGS: - propagationTags = - datadogTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, value); + propagationTags = propagationTagsFactory.fromHeaderValue(HeaderType.DATADOG, value); break; case OT_BAGGAGE: { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java index 0cfe87e6911..d164eec30b9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java @@ -2,6 +2,7 @@ import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.sampling.PrioritySampling; import datadog.trace.bootstrap.instrumentation.api.TagContext; import java.util.Map; @@ -20,8 +21,20 @@ public ExtractedContext( final long spanId, final int samplingPriority, final CharSequence origin, - final PropagationTags propagationTags) { - this(traceId, spanId, samplingPriority, origin, 0, null, null, null, propagationTags, null); + final PropagationTags propagationTags, + final TracePropagationStyle propagationStyle) { + this( + traceId, + spanId, + samplingPriority, + origin, + 0, + null, + null, + null, + propagationTags, + null, + propagationStyle); } public ExtractedContext( @@ -34,8 +47,9 @@ public ExtractedContext( final Map tags, final HttpHeaders httpHeaders, final PropagationTags propagationTags, - final TraceConfig traceConfig) { - super(origin, tags, httpHeaders, baggage, samplingPriority, traceConfig); + final TraceConfig traceConfig, + final TracePropagationStyle propagationStyle) { + super(origin, tags, httpHeaders, baggage, samplingPriority, traceConfig, propagationStyle); this.traceId = traceId; this.spanId = spanId; this.endToEndStartTime = endToEndStartTime; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/HaystackHttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/HaystackHttpCodec.java index af293ec42c3..0d349a4a858 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/HaystackHttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/HaystackHttpCodec.java @@ -1,5 +1,6 @@ package datadog.trace.core.propagation; +import static datadog.trace.api.TracePropagationStyle.HAYSTACK; import static datadog.trace.core.propagation.HttpCodec.firstHeaderValue; import datadog.trace.api.Config; @@ -7,6 +8,7 @@ import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.sampling.PrioritySampling; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.core.DDSpanContext; @@ -139,6 +141,11 @@ private HaystackContextInterpreter(Config config) { super(config); } + @Override + public TracePropagationStyle style() { + return HAYSTACK; + } + @Override public boolean accept(String key, String value) { if (null == key || key.isEmpty()) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java index 2e2172c9665..9d8c99530af 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java @@ -1,11 +1,17 @@ package datadog.trace.core.propagation; +import static datadog.trace.api.TracePropagationStyle.TRACECONTEXT; + import datadog.trace.api.Config; +import datadog.trace.api.DD128bTraceId; +import datadog.trace.api.DD64bTraceId; +import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.TagContext; import datadog.trace.core.DDSpanContext; +import datadog.trace.core.DDSpanLink; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; @@ -46,7 +52,17 @@ void inject( final DDSpanContext context, final C carrier, final AgentPropagation.Setter setter); } + /** This interface defines propagated context extractor. */ public interface Extractor { + /** + * Extracts a propagated context from the given carrier using the provided getter. + * + * @param carrier The carrier containing the propagated context. + * @param getter The getter used to extract data from the carrier. + * @param The type of the carrier. + * @return {@code null} for failed context extraction, a {@link TagContext} instance for partial + * context extraction or an {@link ExtractedContext} for complete context extraction. + */ TagContext extract(final C carrier, final AgentPropagation.ContextVisitor getter); } @@ -136,7 +152,14 @@ public static Extractor createExtractor( break; } } - return new CompoundExtractor(extractors); + switch (extractors.size()) { + case 0: + return StubExtractor.INSTANCE; + case 1: + return extractors.get(0); + default: + return new CompoundExtractor(extractors, config.isTracePropagationExtractFirst()); + } } public static class CompoundInjector implements Injector { @@ -157,29 +180,122 @@ public void inject( } } + private static class StubExtractor implements Extractor { + private static final StubExtractor INSTANCE = new StubExtractor(); + + @Override + public TagContext extract(C carrier, AgentPropagation.ContextVisitor getter) { + return null; + } + } + public static class CompoundExtractor implements Extractor { private final List extractors; + private final boolean extractFirst; - public CompoundExtractor(final List extractors) { + public CompoundExtractor(final List extractors, boolean extractFirst) { this.extractors = extractors; + this.extractFirst = extractFirst; } @Override public TagContext extract( final C carrier, final AgentPropagation.ContextVisitor getter) { - TagContext context = null; - - for (final Extractor extractor : extractors) { - context = extractor.extract(carrier, getter); - // Use incomplete TagContext only as last resort - if (context instanceof ExtractedContext) { - log.debug("Extract complete context {}", context); - return context; + ExtractedContext context = null; + TagContext partialContext = null; + // Extract and cache all headers in advance + ExtractionCache extractionCache = new ExtractionCache<>(carrier, getter); + + for (final Extractor extractor : this.extractors) { + TagContext extracted = extractor.extract(extractionCache, extractionCache); + // Check if context is valid + if (extracted instanceof ExtractedContext) { + ExtractedContext extractedContext = (ExtractedContext) extracted; + // If no prior valid context, store it as first valid context + if (context == null) { + context = extractedContext; + // Stop extraction if only extracting first valid context and drop everything else + if (this.extractFirst) { + break; + } + } + // If another valid context is extracted + else { + if (traceIdMatch(context.getTraceId(), extractedContext.getTraceId())) { + boolean comingFromTraceContext = extracted.getPropagationStyle() == TRACECONTEXT; + if (comingFromTraceContext) { + // Propagate newly extracted W3C tracestate to first valid context + String extractedTracestate = + extractedContext.getPropagationTags().getW3CTracestate(); + context.getPropagationTags().updateW3CTracestate(extractedTracestate); + } + } else { + // Terminate extracted context and add it as span link + context.addTerminatedContextLink(DDSpanLink.from((ExtractedContext) extracted)); + // TODO Note: Other vendor tracestate will be lost here + } + } } + // Check if context is at least partial to keep it as first valid partial context found + else if (extracted != null && partialContext == null) { + partialContext = extracted; + } + } + + if (context != null) { + log.debug("Extract complete context {}", context); + return context; + } else if (partialContext != null) { + log.debug("Extract incomplete context {}", partialContext); + return partialContext; + } else { + log.debug("Extract no context"); + return null; + } + } + } + + private static class ExtractionCache + implements AgentPropagation.KeyClassifier, + AgentPropagation.ContextVisitor> { + /** Cached context key-values (even indexes are header names, odd indexes are header values). */ + private final List keysAndValues; + + public ExtractionCache(C carrier, AgentPropagation.ContextVisitor getter) { + this.keysAndValues = new ArrayList<>(32); + getter.forEachKey(carrier, this); + } + + @Override + public boolean accept(String key, String value) { + this.keysAndValues.add(key); + this.keysAndValues.add(value); + return true; + } + + @Override + public void forEachKey(ExtractionCache carrier, AgentPropagation.KeyClassifier classifier) { + List keysAndValues = carrier.keysAndValues; + for (int i = 0; i < keysAndValues.size(); i += 2) { + classifier.accept(keysAndValues.get(i), keysAndValues.get(i + 1)); } + } + } - log.debug("Extract incomplete context {}", context); - return context; + /** + * Checks if trace identifier matches, even if they are not encoded using the same size (64-bit vs + * 128-bit). + * + * @param a A trace identifier to check. + * @param b Another trace identifier to check. + * @return {@code true} if the trace identifiers matches, {@code false} otherwise. + */ + private static boolean traceIdMatch(DDTraceId a, DDTraceId b) { + if (a instanceof DD128bTraceId && b instanceof DD128bTraceId + || a instanceof DD64bTraceId && b instanceof DD64bTraceId) { + return a.equals(b); + } else { + return a.toLong() == b.toLong(); } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java index 79bbd3eb342..00ed3d0f53d 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java @@ -66,6 +66,22 @@ public interface Factory { public abstract void updateTraceIdHighOrderBits(long highOrderBits); + /** + * Gets the original W3C + * tracestate header value. + * + * @return The original W3C tracestate header value. + */ + public abstract String getW3CTracestate(); + + /** + * Stores the original W3C + * tracestate header value. + * + * @param tracestate The original W3C tracestate header value. + */ + public abstract void updateW3CTracestate(String tracestate); + /** * Constructs a header value that includes valid propagated _dd.p.* tags and possibly a new * sampling decision tag _dd.p.dm based on the current state. Returns null if the value length diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CHttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CHttpCodec.java index 13258b270f9..76ca949e228 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CHttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CHttpCodec.java @@ -1,8 +1,10 @@ package datadog.trace.core.propagation; +import static datadog.trace.api.TracePropagationStyle.TRACECONTEXT; import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP; import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP; import static datadog.trace.core.propagation.HttpCodec.firstHeaderValue; +import static datadog.trace.core.propagation.PropagationTags.HeaderType.W3C; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -12,6 +14,7 @@ import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.internal.util.LongStringUtils; import datadog.trace.api.sampling.PrioritySampling; import datadog.trace.api.sampling.SamplingMechanism; @@ -61,6 +64,13 @@ public Injector(Map invertedBaggageMapping) { @Override public void inject( final DDSpanContext context, final C carrier, final AgentPropagation.Setter setter) { + injectTraceParent(context, carrier, setter); + injectTraceState(context, carrier, setter); + injectBaggage(context, carrier, setter); + } + + private void injectTraceParent( + DDSpanContext context, C carrier, AgentPropagation.Setter setter) { StringBuilder sb = new StringBuilder(TRACE_PARENT_LENGTH); sb.append("00-"); sb.append(context.getTraceId().toHexString()); @@ -68,11 +78,19 @@ public void inject( sb.append(DDSpanId.toHexStringPadded(context.getSpanId())); sb.append(context.getSamplingPriority() > 0 ? "-01" : "-00"); setter.set(carrier, TRACE_PARENT_KEY, sb.toString()); - String tracestate = context.getPropagationTags().headerValue(PropagationTags.HeaderType.W3C); + } + + private void injectTraceState( + DDSpanContext context, C carrier, AgentPropagation.Setter setter) { + PropagationTags propagationTags = context.getPropagationTags(); + String tracestate = propagationTags.headerValue(W3C); if (tracestate != null && !tracestate.isEmpty()) { setter.set(carrier, TRACE_STATE_KEY, tracestate); } + } + private void injectBaggage( + DDSpanContext context, C carrier, AgentPropagation.Setter setter) { long e2eStart = context.getEndToEndStartTime(); if (e2eStart > 0) { setter.set(carrier, E2E_START_KEY, Long.toString(NANOSECONDS.toMillis(e2eStart))); @@ -99,15 +117,17 @@ private static class W3CContextInterpreter extends ContextInterpreter { private static final int E2E_START = 3; private static final int IGNORE = -1; - private final PropagationTags.Factory datadogTagsFactory; - // We need to delay handling of the tracestate header until after traceparent private String tracestateHeader = null; private String traceparentHeader = null; private W3CContextInterpreter(Config config) { super(config); - datadogTagsFactory = PropagationTags.factory(config); + } + + @Override + public TracePropagationStyle style() { + return TRACECONTEXT; } @Override @@ -241,7 +261,7 @@ private boolean storeTraceState(String value) { */ @Override protected TagContext build() { - // If there is neither a traceparent or a tracestate header, then ignore this + // If there is neither a traceparent nor a tracestate header, then ignore this if (traceparentHeader == null && tracestateHeader == null) { onlyTagContext(); } @@ -252,8 +272,8 @@ protected TagContext build() { "Found no traceparent header but tracestate header '" + tracestateHeader + "'"); } // Now we know that we have at least a traceparent header - parseTraceParentHeader(traceparentHeader, this); - parseTraceStateHeader(tracestateHeader, this, datadogTagsFactory); + parseTraceParentHeader(traceparentHeader); + parseTraceStateHeader(tracestateHeader); } catch (RuntimeException e) { onlyTagContext(); log.debug("Exception when building context", e); @@ -262,7 +282,7 @@ protected TagContext build() { return super.build(); } - static void parseTraceParentHeader(String tp, ContextInterpreter context) { + void parseTraceParentHeader(String tp) { int length = tp == null ? 0 : tp.length(); if (length < TRACE_PARENT_LENGTH) { throw new IllegalStateException("The length of traceparent '" + tp + "' is too short"); @@ -279,9 +299,9 @@ static void parseTraceParentHeader(String tp, ContextInterpreter context) { "Illegal all zero 64 bit trace id " + tp.substring(TRACE_PARENT_TID_START, TRACE_PARENT_TID_END)); } - context.traceId = traceId; - context.spanId = DDSpanId.fromHex(tp, TRACE_PARENT_SID_START, 16, true); - if (context.spanId == 0) { + this.traceId = traceId; + this.spanId = DDSpanId.fromHex(tp, TRACE_PARENT_SID_START, 16, true); + if (this.spanId == 0) { throw new IllegalStateException( "Illegal all zero span id " + tp.substring(TRACE_PARENT_SID_START, TRACE_PARENT_SID_END)); @@ -291,35 +311,34 @@ static void parseTraceParentHeader(String tp, ContextInterpreter context) { } long flags = LongStringUtils.parseUnsignedLongHex(tp, TRACE_PARENT_FLAGS_START, 2, true); if ((flags & TRACE_PARENT_FLAGS_SAMPLED) != 0) { - context.samplingPriority = SAMPLER_KEEP; + this.samplingPriority = SAMPLER_KEEP; } else { - context.samplingPriority = SAMPLER_DROP; + this.samplingPriority = SAMPLER_DROP; } } - static void parseTraceStateHeader( - String ts, ContextInterpreter context, PropagationTags.Factory factory) { - if (ts == null || ts.isEmpty()) { - context.propagationTags = factory.empty(); + void parseTraceStateHeader(String tracestate) { + if (tracestate == null || tracestate.isEmpty()) { + this.propagationTags = this.propagationTagsFactory.empty(); } else { - context.propagationTags = factory.fromHeaderValue(PropagationTags.HeaderType.W3C, ts); + this.propagationTags = this.propagationTagsFactory.fromHeaderValue(W3C, tracestate); } - int ptagsPriority = context.propagationTags.getSamplingPriority(); - int contextPriority = context.samplingPriority; + int ptagsPriority = this.propagationTags.getSamplingPriority(); + int contextPriority = this.samplingPriority; if ((contextPriority == SAMPLER_DROP && ptagsPriority > 0) || (contextPriority == SAMPLER_KEEP && ptagsPriority <= 0) || ptagsPriority == PrioritySampling.UNSET) { // Override Datadog sampling priority with W3C one - context.propagationTags.updateTraceSamplingPriority( + this.propagationTags.updateTraceSamplingPriority( contextPriority, SamplingMechanism.EXTERNAL_OVERRIDE); } else { // Use more detailed Datadog sampling priority in context - context.samplingPriority = ptagsPriority; + this.samplingPriority = ptagsPriority; } // Use the origin - context.origin = context.propagationTags.getOrigin(); + this.origin = this.propagationTags.getOrigin(); // Ensure TraceId high-order bits match - context.propagationTags.updateTraceIdHighOrderBits(context.traceId.toHighOrderLong()); + this.propagationTags.updateTraceIdHighOrderBits(this.traceId.toHighOrderLong()); } private static String trim(String input) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/XRayHttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/XRayHttpCodec.java index 489e12abe1e..15e7d54e476 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/XRayHttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/XRayHttpCodec.java @@ -1,6 +1,7 @@ package datadog.trace.core.propagation; import static datadog.trace.api.DDTags.ORIGIN_KEY; +import static datadog.trace.api.TracePropagationStyle.XRAY; import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP; import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -12,6 +13,7 @@ import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.sampling.PrioritySampling; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.core.DDSpanContext; @@ -136,6 +138,11 @@ private XRayContextInterpreter(Config config) { super(config); } + @Override + public TracePropagationStyle style() { + return XRAY; + } + @Override public boolean accept(String key, String value) { if (null == key || key.isEmpty()) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java index 22c7f58cb7b..21ab986a16b 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java @@ -88,7 +88,15 @@ static class PTags extends PropagationTags { * of the trace id, wrapped into a {@link TagValue}, null if not set. */ private volatile TagValue traceIdHighOrderBitsHexTagValue; - + /** + * The original W3C tracestate + * header value. + */ + protected volatile String tracestate; + /** + * The {@link PTagsFactory#PROPAGATION_ERROR_TAG_KEY propagation tag error} value, {@code null + * if no error while parsing header}. + */ protected volatile String error; public PTags( @@ -292,6 +300,16 @@ TagValue getDecisionMakerTagValue() { return decisionMakerTagValue; } + @Override + public String getW3CTracestate() { + return this.tracestate; + } + + @Override + public void updateW3CTracestate(String tracestate) { + this.tracestate = tracestate; + } + String getError() { return this.error; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index 94c84d656d5..c88f1e6a95f 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -16,12 +16,14 @@ public class W3CPTagsCodec extends PTagsCodec { private static final Logger log = LoggerFactory.getLogger(W3CPTagsCodec.class); private static final int MAX_HEADER_SIZE = 256; - private static final int EMPTY_SIZE = 3; // 'dd=' + private static final String DATADOG_MEMBER_KEY = "dd="; + private static final int EMPTY_SIZE = DATADOG_MEMBER_KEY.length(); // 3 private static final char MEMBER_SEPARATOR = ','; private static final char ELEMENT_SEPARATOR = ';'; private static final char KEY_VALUE_SEPARATOR = ':'; private static final int MIN_ALLOWED_CHAR = 32; private static final int MAX_ALLOWED_CHAR = 126; + private static final int MAX_MEMBER_COUNT = 32; @Override PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { @@ -44,12 +46,12 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int memberIndex = 0; int ddMemberIndex = -1; while (memberStart < len) { - if (memberIndex == 32) { + if (memberIndex == MAX_MEMBER_COUNT) { // TODO should we return one with an error? // TODO should we try to pick up the `dd` member anyway? return tagsFactory.empty(); } - if (ddMemberIndex == -1 && value.startsWith("dd=", memberStart)) { + if (ddMemberIndex == -1 && value.startsWith(DATADOG_MEMBER_KEY, memberStart)) { ddMemberStart = memberStart; ddMemberIndex = memberIndex; } @@ -196,19 +198,25 @@ protected int estimateHeaderSize(PTags pTags) { W3CPTags w3CPTags = (W3CPTags) pTags; size += w3CPTags.maxUnknownSize; if (w3CPTags.ddMemberStart != -1) { - size += (w3CPTags.original.length() - (w3CPTags.ddMemberValueEnd - w3CPTags.ddMemberStart)); + size += + (w3CPTags.tracestate.length() - (w3CPTags.ddMemberValueEnd - w3CPTags.ddMemberStart)); } + } else if (pTags.tracestate != null) { + // We assume there is no Datadog list-member + size += pTags.tracestate.length(); } return size; } @Override protected int appendPrefix(StringBuilder sb, PTags ptags) { - sb.append("dd="); + sb.append(DATADOG_MEMBER_KEY); + // Append sampling priority (s) if (ptags.getSamplingPriority() != PrioritySampling.UNSET) { sb.append("s:"); sb.append(ptags.getSamplingPriority()); } + // Append origin (o) CharSequence origin = ptags.getOrigin(); if (origin != null) { if (sb.length() > EMPTY_SIZE) { @@ -231,24 +239,24 @@ protected int appendTag(StringBuilder sb, TagElement key, TagElement value, int @Override protected int appendSuffix(StringBuilder sb, PTags ptags, int size) { - if (size >= MAX_HEADER_SIZE || !(ptags instanceof W3CPTags)) { - return size; + // If there is room for appending unknown from W3CPTags + if (size < MAX_HEADER_SIZE && ptags instanceof W3CPTags) { + W3CPTags w3cPTags = (W3CPTags) ptags; + size = cleanUpAndAppendUnknown(sb, w3cPTags, size); } - W3CPTags w3cPTags = (W3CPTags) ptags; - size = cleanUpAndAppendUnknown(sb, w3cPTags, size); + // Check empty Datadog list-member only tracestate if (size == EMPTY_SIZE) { // If we haven't written anything but the 'dd=', then reset the StringBuilder sb.setLength(0); size = 0; } - // TODO we need to ensure that there are only 32 segments including our own :( - int newSize = cleanUpAndAppendSuffix(sb, w3cPTags, size); + // Append all other non-Datadog list-members + int newSize = cleanUpAndAppendSuffix(sb, ptags, size); if (newSize != size) { // We don't care about the total size in bytes here, but only the fact that we added something // that should be returned size = Math.max(size, EMPTY_SIZE + 1); } - return size; } @@ -584,7 +592,7 @@ private static int cleanUpAndAppendUnknown(StringBuilder sb, W3CPTags w3CPTags, || w3CPTags.ddMemberStart >= w3CPTags.ddMemberValueEnd) { return size; } - String original = w3CPTags.original; + String original = w3CPTags.tracestate; int elementStart = w3CPTags.ddMemberStart + EMPTY_SIZE; // skip over 'dd=' int okSize = size; while (elementStart < w3CPTags.ddMemberValueEnd && size < MAX_HEADER_SIZE) { @@ -619,16 +627,29 @@ private static int cleanUpAndAppendUnknown(StringBuilder sb, W3CPTags w3CPTags, return size; } - private static int cleanUpAndAppendSuffix(StringBuilder sb, W3CPTags w3CPTags, int size) { - String original = w3CPTags.original; + private static int cleanUpAndAppendSuffix(StringBuilder sb, PTags ptags, int size) { + String original = ptags.tracestate; + if (original == null) { + return size; + } + int ddMemberStart = (ptags instanceof W3CPTags) ? ((W3CPTags) ptags).ddMemberStart : -1; + int remainingMemberAllowed = size == 0 ? MAX_MEMBER_COUNT : MAX_MEMBER_COUNT - 1; int len = original.length(); int memberStart = findNextMember(original, 0); while (memberStart < len) { + // Look for member end position int memberEnd = original.indexOf(MEMBER_SEPARATOR, memberStart); if (memberEnd < 0) { memberEnd = len; } - if (memberStart != w3CPTags.ddMemberStart) { + // Try to define Datadog member start if not already found + if (ddMemberStart == -1) { + if (original.startsWith(DATADOG_MEMBER_KEY, memberStart)) { + ddMemberStart = memberStart; + } + } + // Skip Datadog member (already added with prefix and tags) + if (memberStart != ddMemberStart) { if (sb.length() > 0) { sb.append(MEMBER_SEPARATOR); size++; @@ -636,8 +657,14 @@ private static int cleanUpAndAppendSuffix(StringBuilder sb, W3CPTags w3CPTags, i int end = stripTrailingOWC(original, memberStart, memberEnd); sb.append(original, memberStart, end); size += (end - memberStart); + remainingMemberAllowed--; + } + // Check if remaining members are allowed + if (remainingMemberAllowed == 0) { + memberStart = len; + } else { + memberStart = findNextMember(original, memberEnd + 1); } - memberStart = findNextMember(original, memberEnd + 1); } return size; } @@ -667,12 +694,20 @@ private static W3CPTags empty( } private static class W3CPTags extends PTags { - private final String original; + /** The index of the first tracestate list-member position in {@link #tracestate}. */ private final int firstMemberStart; + /** + * The index of the Datadog tracestate list-member (dd=) position in {@link #tracestate}, {@code + * -1 if Datadog list-member not found}. + */ private final int ddMemberStart; + /** + * The index of the end Datadog tracestate list-member (dd=) in {@link #tracestate}, {@code -1 + * if Datadog list-member not found}. + */ private final int ddMemberValueEnd; + private final int maxUnknownSize; - private String error; public W3CPTags( PTagsFactory factory, @@ -687,20 +722,11 @@ public W3CPTags( int ddMemberValueEnd, int maxUnknownSize) { super(factory, tagPairs, decisionMakerTagValue, traceIdTagValue, samplingPriority, origin); - this.original = original; + this.tracestate = original; this.firstMemberStart = firstMemberStart; this.ddMemberStart = ddMemberStart; this.ddMemberValueEnd = ddMemberValueEnd; this.maxUnknownSize = maxUnknownSize; - this.error = null; - } - - @Override - String getError() { - if (this.error != null) { - return this.error; - } - return super.getError(); } @Override diff --git a/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaHandler.java b/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaHandler.java index bcf0ffc8834..43e791d3880 100644 --- a/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaHandler.java +++ b/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaHandler.java @@ -1,5 +1,6 @@ package datadog.trace.lambda; +import static datadog.trace.api.TracePropagationStyle.DATADOG; import static java.util.concurrent.TimeUnit.SECONDS; import com.squareup.moshi.JsonAdapter; @@ -92,7 +93,12 @@ public static AgentSpan.Context notifyStartInvocation( propagationTagsFactory.fromHeaderValue( PropagationTags.HeaderType.DATADOG, response.headers().get(DATADOG_TAGS_KEY)); return new ExtractedContext( - DDTraceId.from(traceID), DDSpanId.ZERO, samplingPriority, null, propagationTags); + DDTraceId.from(traceID), + DDSpanId.ZERO, + samplingPriority, + null, + propagationTags, + DATADOG); } else { log.debug( "could not find traceID or sampling priority in notifyStartInvocation, not injecting the context"); diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy index 735b6d313cc..f01640a5997 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy @@ -25,6 +25,7 @@ import static datadog.trace.api.DDTags.PID_TAG import static datadog.trace.api.DDTags.RUNTIME_ID_TAG import static datadog.trace.api.DDTags.THREAD_ID import static datadog.trace.api.DDTags.THREAD_NAME +import static datadog.trace.api.TracePropagationStyle.DATADOG import static java.util.concurrent.TimeUnit.MILLISECONDS class CoreSpanBuilderTest extends DDCoreSpecification { @@ -335,9 +336,9 @@ class CoreSpanBuilderTest extends DDCoreSpecification { span.context().propagationTags.headerValue(PropagationTags.HeaderType.DATADOG) == extractedContext.propagationTags.headerValue(PropagationTags.HeaderType.DATADOG) where: - extractedContext | _ - new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, null, 0, [:], [:], null, PropagationTags.factory().fromHeaderValue(PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), null) | _ - new ExtractedContext(DDTraceId.from(3), 4, PrioritySampling.SAMPLER_KEEP, "some-origin", 0, ["asdf": "qwer"], [(ORIGIN_KEY): "some-origin", "zxcv": "1234"], null, PropagationTags.factory().empty(), null) | _ + extractedContext | _ + new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, null, 0, [:], [:], null, PropagationTags.factory().fromHeaderValue(PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), null, DATADOG) | _ + new ExtractedContext(DDTraceId.from(3), 4, PrioritySampling.SAMPLER_KEEP, "some-origin", 0, ["asdf": "qwer"], [(ORIGIN_KEY): "some-origin", "zxcv": "1234"], null, PropagationTags.factory().empty(), null, DATADOG) | _ } def "TagContext should populate default span details"() { diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextPropagationTagsTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextPropagationTagsTest.groovy index fc56a3377c2..50ef4ff544f 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextPropagationTagsTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextPropagationTagsTest.groovy @@ -7,6 +7,7 @@ import datadog.trace.core.propagation.ExtractedContext import datadog.trace.core.propagation.PropagationTags import datadog.trace.core.test.DDCoreSpecification +import static datadog.trace.api.TracePropagationStyle.DATADOG import static datadog.trace.api.sampling.PrioritySampling.* import static datadog.trace.api.sampling.SamplingMechanism.* @@ -23,7 +24,7 @@ class DDSpanContextPropagationTagsTest extends DDCoreSpecification { setup: tracer = tracerBuilder().writer(writer).build() def propagationTags = tracer.propagationTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, header) - def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags) + def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) .withRequestContextDataAppSec("dummy") def span = (DDSpan) tracer.buildSpan("top") .asChildOf((AgentSpan.Context) extracted) @@ -52,7 +53,7 @@ class DDSpanContextPropagationTagsTest extends DDCoreSpecification { setup: tracer = tracerBuilder().writer(writer).build() def propagationTags = tracer.propagationTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, header) - def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags) + def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) .withRequestContextDataAppSec("dummy") def rootSpan = (DDSpan) tracer.buildSpan("top") .asChildOf((AgentSpan.Context) extracted) @@ -82,7 +83,7 @@ class DDSpanContextPropagationTagsTest extends DDCoreSpecification { setup: tracer = tracerBuilder().writer(writer).build() def propagationTags = tracer.propagationTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, header) - def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags) + def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) .withRequestContextDataAppSec("dummy") def span = (DDSpan) tracer.buildSpan("top") .asChildOf((AgentSpan.Context) extracted) @@ -109,7 +110,7 @@ class DDSpanContextPropagationTagsTest extends DDCoreSpecification { setup: tracer = tracerBuilder().writer(writer).build() def propagationTags = tracer.propagationTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, header) - def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags) + def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) .withRequestContextDataAppSec("dummy") def rootSpan = (DDSpan) tracer.buildSpan("top") .asChildOf((AgentSpan.Context) extracted) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextTest.groovy index 0f6c6db868b..bcd62a08b6d 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextTest.groovy @@ -9,6 +9,7 @@ import datadog.trace.common.writer.ListWriter import datadog.trace.core.propagation.ExtractedContext import datadog.trace.core.test.DDCoreSpecification +import static datadog.trace.api.TracePropagationStyle.DATADOG import static datadog.trace.api.sampling.PrioritySampling.* import static datadog.trace.api.sampling.SamplingMechanism.* import static datadog.trace.core.DDSpanContext.SPAN_SAMPLING_MECHANISM_TAG @@ -181,7 +182,7 @@ class DDSpanContextTest extends DDCoreSpecification { def "set TraceSegment tags and data on correct span"() { setup: - def extracted = new ExtractedContext(DDTraceId.from(123), 456, SAMPLER_KEEP, "789", tracer.getPropagationTagsFactory().empty()) + def extracted = new ExtractedContext(DDTraceId.from(123), 456, SAMPLER_KEEP, "789", tracer.getPropagationTagsFactory().empty(), DATADOG) .withRequestContextDataAppSec("dummy") def top = tracer.buildSpan("top").asChildOf((AgentSpan.Context) extracted).start() @@ -264,8 +265,8 @@ class DDSpanContextTest extends DDCoreSpecification { .start() then: "encoded operation name matches operation name" - 1 * profilingContextIntegration.encode("fakeOperation") >> 1 - 1 * profilingContextIntegration.encode("fakeResource") >> -1 + 1 * profilingContextIntegration.encodeOperationName("fakeOperation") >> 1 + 1 * profilingContextIntegration.encodeResourceName("fakeResource") >> -1 span.context.encodedOperationName == 1 span.context.encodedResourceName == -1 @@ -273,14 +274,14 @@ class DDSpanContextTest extends DDCoreSpecification { span.setOperationName("newOperationName") then: - 1 * profilingContextIntegration.encode("newOperationName") >> 2 + 1 * profilingContextIntegration.encodeOperationName("newOperationName") >> 2 span.context.encodedOperationName == 2 when: span.setResourceName("newResourceName") then: - 1 * profilingContextIntegration.encode("newResourceName") >> -2 + 1 * profilingContextIntegration.encodeResourceName("newResourceName") >> -2 span.context.encodedResourceName == -2 } @@ -297,6 +298,7 @@ class DDSpanContextTest extends DDCoreSpecification { sourceWithoutCommonTags.remove("process_id") sourceWithoutCommonTags.remove("_dd.trace_span_attribute_schema") sourceWithoutCommonTags.remove(DDTags.PROFILING_ENABLED) + sourceWithoutCommonTags.remove(DDTags.PROFILING_CONTEXT_ENGINE) if (removeThread) { sourceWithoutCommonTags.remove(DDTags.THREAD_ID) sourceWithoutCommonTags.remove(DDTags.THREAD_NAME) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy index ecccd01274d..8fbcd58005d 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy @@ -18,6 +18,7 @@ import spock.lang.Shared import java.util.concurrent.TimeUnit +import static datadog.trace.api.TracePropagationStyle.DATADOG import static datadog.trace.api.sampling.PrioritySampling.UNSET import static datadog.trace.api.sampling.SamplingMechanism.SPAN_SAMPLING_RATE import static datadog.trace.core.DDSpanContext.SPAN_SAMPLING_MAX_PER_SECOND_TAG @@ -272,9 +273,9 @@ class DDSpanTest extends DDCoreSpecification { child.@origin == null // Access field directly instead of getter. where: - extractedContext | _ - new TagContext("some-origin", [:]) | _ - new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, "some-origin", propagationTagsFactory.empty()) | _ + extractedContext | _ + new TagContext("some-origin", [:]) | _ + new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, "some-origin", propagationTagsFactory.empty(), DATADOG) | _ } def "isRootSpan() in and not in the context of distributed tracing"() { @@ -291,9 +292,9 @@ class DDSpanTest extends DDCoreSpecification { root.finish() where: - extractedContext | isTraceRootSpan - null | true - new ExtractedContext(DDTraceId.from(123), 456, PrioritySampling.SAMPLER_KEEP, "789", propagationTagsFactory.empty()) | false + extractedContext | isTraceRootSpan + null | true + new ExtractedContext(DDTraceId.from(123), 456, PrioritySampling.SAMPLER_KEEP, "789", propagationTagsFactory.empty(), DATADOG) | false } def "getApplicationRootSpan() in and not in the context of distributed tracing"() { @@ -313,9 +314,9 @@ class DDSpanTest extends DDCoreSpecification { root.finish() where: - extractedContext | isTraceRootSpan - null | true - new ExtractedContext(DDTraceId.from(123), 456, PrioritySampling.SAMPLER_KEEP, "789", propagationTagsFactory.empty()) | false + extractedContext | isTraceRootSpan + null | true + new ExtractedContext(DDTraceId.from(123), 456, PrioritySampling.SAMPLER_KEEP, "789", propagationTagsFactory.empty(), DATADOG) | false } def 'publishing of root span closes the request context data'() { diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/datastreams/DefaultPathwayContextTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/datastreams/DefaultPathwayContextTest.groovy index 5888c87a291..8cb0d4338ae 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/datastreams/DefaultPathwayContextTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/datastreams/DefaultPathwayContextTest.groovy @@ -16,6 +16,7 @@ import datadog.trace.core.test.DDCoreSpecification import java.util.function.Consumer +import static datadog.trace.api.TracePropagationStyle.DATADOG import static datadog.trace.api.config.GeneralConfig.PRIMARY_TAG import static datadog.trace.core.datastreams.DefaultDataStreamsMonitoring.DEFAULT_BUCKET_DURATION_NANOS import static java.util.concurrent.TimeUnit.MILLISECONDS @@ -717,7 +718,7 @@ class DefaultPathwayContextTest extends DDCoreSpecification { @Override TagContext extract(C carrier, AgentPropagation.ContextVisitor getter) { - return new ExtractedContext(DDTraceId.ONE, 1, 0, null, 0, null, null, null, null, traceConfig ) + return new ExtractedContext(DDTraceId.ONE, 1, 0, null, 0, null, null, null, null, traceConfig, DATADOG) } } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/B3HttpExtractorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/B3HttpExtractorTest.groovy index 67ab99ac372..073908513e1 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/B3HttpExtractorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/B3HttpExtractorTest.groovy @@ -293,7 +293,8 @@ class B3HttpExtractorTest extends DDSpecification { TagContext context = extractor.extract(headers, ContextVisitors.stringValuesMap()) then: - context == null + !(context instanceof ExtractedContext) + context.getTags() == ["some-tag": "my-interesting-info"] } def "extract http headers with out of range span ID"() { @@ -304,12 +305,12 @@ class B3HttpExtractorTest extends DDSpecification { SOME_HEADER : "my-interesting-info", ] - when: TagContext context = extractor.extract(headers, ContextVisitors.stringValuesMap()) then: - context == null + !(context instanceof ExtractedContext) + context.getTags() == ["some-tag": "my-interesting-info"] } def "extract ids while retaining the original string"() { diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CHttpInjectorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CHttpInjectorTest.groovy index 3b2aef1b762..c45b32c7e1a 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CHttpInjectorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CHttpInjectorTest.groovy @@ -50,10 +50,10 @@ class W3CHttpInjectorTest extends DDCoreSpecification { PropagationTags.factory().fromHeaderValue(PropagationTags.HeaderType.DATADOG, tracestate ? "_dd.p.usr=123" : "")) final Map carrier = [:] Map expected = [ - (TRACE_PARENT_KEY): buildTraceParent(traceId, spanId, samplingPriority), + (TRACE_PARENT_KEY) : buildTraceParent(traceId, spanId, samplingPriority), (OT_BAGGAGE_PREFIX + "k1"): "v1", (OT_BAGGAGE_PREFIX + "k2"): "v2", - "SOME_CUSTOM_HEADER": "some-value" + "SOME_CUSTOM_HEADER" : "some-value", ] if (tracestate) { expected.put(TRACE_STATE_KEY, tracestate) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CPropagationTagsTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CPropagationTagsTest.groovy index 1d03696c37c..4d725fb91c4 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CPropagationTagsTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CPropagationTagsTest.groovy @@ -145,7 +145,7 @@ class W3CPropagationTagsTest extends DDCoreSpecification { valueChar << (' '..'ÿ') - ((' '..'~') - [',', '=']) } - def "validate tracestate header number of members #memberCount"() { + def "validate tracestate header number of members #memberCount without Datadog member"() { setup: def config = Mock(Config) config.getxDatadogTagsMaxLength() >> 512 @@ -168,6 +168,57 @@ class W3CPropagationTagsTest extends DDCoreSpecification { memberCount << (1..37) // some arbitrary number larger than 32 } + def "validate tracestate header number of members #memberCount with Datadog member"() { + setup: + def config = Mock(Config) + config.getxDatadogTagsMaxLength() >> 512 + def propagationTagsFactory = PropagationTags.factory(config) + def header = 'dd=s:1,'+(1..memberCount).collect { "k$it=v$it" }.join(',') + + when: + def headerPT = propagationTagsFactory.fromHeaderValue(HeaderType.W3C, header) + + then: + if (memberCount + 1 <= 32) { + assert headerPT.headerValue(HeaderType.W3C) == header + } else { + assert headerPT.headerValue(HeaderType.W3C) == null + } + // we're not using any dd members in the tests + headerPT.createTagMap() == [:] + + where: + memberCount << (1..37) // some arbitrary number larger than 32 + } + + def "validate tracestate header number of members #memberCount when propagating original tracestate"() { + setup: + def config = Mock(Config) + config.getxDatadogTagsMaxLength() >> 512 + def propagationTagsFactory = PropagationTags.factory(config) + def header = (1..memberCount).collect { "k$it=v$it" }.join(',') + def expectedHeader = 'dd=t.dm:-4,' + ( + memberCount > 32 ? + '' : + (1..Math.min(memberCount, 31)).collect { "k$it=v$it" }.join(',')) + + when: + def datadogHeaderPT = propagationTagsFactory.fromHeaderValue(HeaderType.DATADOG, '_dd.p.dm=-4') + def headerPT = propagationTagsFactory.fromHeaderValue(HeaderType.W3C, header) + datadogHeaderPT.updateW3CTracestate(headerPT.getW3CTracestate()) + + then: + if (memberCount <= 32) { + assert datadogHeaderPT.headerValue(HeaderType.W3C) == expectedHeader // 'dd=t.dm:-4,' + header + } else { + assert datadogHeaderPT.headerValue(HeaderType.W3C) == 'dd=t.dm:-4' + } + datadogHeaderPT.createTagMap() == ['_dd.p.dm':'-4'] + + where: + memberCount << (1..37) // some arbitrary number larger than 32 + } + def "create propagation tags from header value #headerValue"() { setup: def config = Mock(Config) diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/scopemanager/ScopeManagerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/scopemanager/ScopeManagerTest.groovy index 6af108b16fc..bd616d07945 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/scopemanager/ScopeManagerTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/scopemanager/ScopeManagerTest.groovy @@ -554,8 +554,8 @@ class ScopeManagerTest extends DDCoreSpecification { 1 * rootSpanCheckpointer.onRootSpanStarted(_) 3 * profilingContext.setContext(_) 1 * profilingContext.onAttach() - 1 * profilingContext.encode("foo") - 1 * profilingContext.encode("bar") + 1 * profilingContext.encodeOperationName("foo") + 1 * profilingContext.encodeOperationName("bar") _ * profilingContext._ 0 * _ @@ -596,7 +596,7 @@ class ScopeManagerTest extends DDCoreSpecification { 1 * rootSpanCheckpointer.onRootSpanStarted(_) 1 * profilingContext.onAttach() 1 * profilingContext.setContext(_) - 1 * profilingContext.encode("foo") + 1 * profilingContext.encodeOperationName("foo") _ * profilingContext._ 0 * _ @@ -610,7 +610,7 @@ class ScopeManagerTest extends DDCoreSpecification { tracer.activeScope() == secondScope assertEvents([ACTIVATE, ACTIVATE]) 1 * profilingContext.setContext(_) - 1 * profilingContext.encode("bar") + 1 * profilingContext.encodeOperationName("bar") _ * profilingContext._ 0 * _ @@ -624,7 +624,7 @@ class ScopeManagerTest extends DDCoreSpecification { tracer.activeScope() == thirdScope assertEvents([ACTIVATE, ACTIVATE, ACTIVATE]) 1 * profilingContext.setContext(_) - 1 * profilingContext.encode("quux") + 1 * profilingContext.encodeOperationName("quux") _ * profilingContext._ 0 * _ @@ -709,7 +709,7 @@ class ScopeManagerTest extends DDCoreSpecification { tracer.activeScope() == thirdScope assertEvents([ACTIVATE, ACTIVATE]) 1 * profilingContext.setContext(_) - 1 * profilingContext.encode("quux") + 1 * profilingContext.encodeOperationName("quux") _ * profilingContext._ 0 * _ diff --git a/dd-trace-ot/src/ot31CompatibilityTest/groovy/OT31ApiTest.groovy b/dd-trace-ot/src/ot31CompatibilityTest/groovy/OT31ApiTest.groovy index 88135e62338..fc80375f786 100644 --- a/dd-trace-ot/src/ot31CompatibilityTest/groovy/OT31ApiTest.groovy +++ b/dd-trace-ot/src/ot31CompatibilityTest/groovy/OT31ApiTest.groovy @@ -1,4 +1,5 @@ import datadog.opentracing.DDTracer +import datadog.trace.api.DDSpanId import datadog.trace.api.DDTraceId import datadog.trace.api.internal.util.LongStringUtils import io.opentracing.util.ThreadLocalScopeManager @@ -108,17 +109,26 @@ class OT31ApiTest extends DDSpecification { tracer.inject(context, Format.Builtin.TEXT_MAP, adapter) then: + def traceId = span.delegate.context.traceId as DDTraceId + def spanId = span.delegate.context.spanId + def expectedTraceparent = "00-${traceId.toHexStringPadded(32)}" + + "-${DDSpanId.toHexStringPadded(spanId)}" + + "-" + (propagatedPriority > 0 ? "01" : "00") + def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism + def expectedTracestate = "dd=s:${propagatedPriority}" + + (propagatedPriority > 0 ? ";t.dm:-" + effectiveSamplingMechanism : "") + + ";t.tid:${traceId.toHexStringPadded(32).substring(0, 16)}" def expectedTextMap = [ "x-datadog-trace-id" : context.toTraceId(), "x-datadog-parent-id" : context.toSpanId(), "x-datadog-sampling-priority": propagatedPriority.toString(), + "traceparent" : expectedTraceparent, + "tracestate" : expectedTracestate, ] def datadogTags = [] if (propagatedPriority > 0) { - def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism datadogTags << "_dd.p.dm=-$effectiveSamplingMechanism" } - def traceId = span.delegate.context.traceId as DDTraceId if (traceId.toHighOrderLong() != 0) { datadogTags << "_dd.p.tid=" + LongStringUtils.toHexStringPadded(traceId.toHighOrderLong(), 16) } diff --git a/dd-trace-ot/src/ot33CompatibilityTest/groovy/OT33ApiTest.groovy b/dd-trace-ot/src/ot33CompatibilityTest/groovy/OT33ApiTest.groovy index 7edaa356dd2..a4cd6b38edd 100644 --- a/dd-trace-ot/src/ot33CompatibilityTest/groovy/OT33ApiTest.groovy +++ b/dd-trace-ot/src/ot33CompatibilityTest/groovy/OT33ApiTest.groovy @@ -1,4 +1,5 @@ import datadog.opentracing.DDTracer +import datadog.trace.api.DDSpanId import datadog.trace.api.DDTraceId import datadog.trace.api.internal.util.LongStringUtils import datadog.trace.common.writer.ListWriter @@ -97,17 +98,26 @@ class OT33ApiTest extends DDSpecification { tracer.inject(context, Format.Builtin.TEXT_MAP, adapter) then: + def traceId = span.delegate.context.traceId as DDTraceId + def spanId = span.delegate.context.spanId + def expectedTraceparent = "00-${traceId.toHexStringPadded(32)}" + + "-${DDSpanId.toHexStringPadded(spanId)}" + + "-" + (propagatedPriority > 0 ? "01" : "00") + def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism + def expectedTracestate = "dd=s:${propagatedPriority}" + + (propagatedPriority > 0 ? ";t.dm:-" + effectiveSamplingMechanism : "") + + ";t.tid:${traceId.toHexStringPadded(32).substring(0, 16)}" def expectedTextMap = [ "x-datadog-trace-id" : context.toTraceId(), "x-datadog-parent-id" : context.toSpanId(), "x-datadog-sampling-priority": propagatedPriority.toString(), + "traceparent" : expectedTraceparent, + "tracestate" : expectedTracestate, ] def datadogTags = [] if (propagatedPriority > 0) { - def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism datadogTags << "_dd.p.dm=-$effectiveSamplingMechanism" } - def traceId = span.delegate.context.traceId as DDTraceId if (traceId.toHighOrderLong() != 0) { datadogTags << "_dd.p.tid=" + LongStringUtils.toHexStringPadded(traceId.toHighOrderLong(), 16) } diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 4e3a4fc4f6a..b7cefa3c700 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -33,7 +33,7 @@ final class CachedData { testcontainers: '1.17.3', jmc : "8.1.0", autoservice : "1.0-rc7", - ddprof : "0.82.0", + ddprof : "0.86.0", asm : "9.6", cafe_crypto : "0.1.0" ] diff --git a/internal-api/build.gradle b/internal-api/build.gradle index af270aeadb9..67c3c24953d 100644 --- a/internal-api/build.gradle +++ b/internal-api/build.gradle @@ -42,6 +42,10 @@ excludedClassesCoverage += [ "datadog.trace.api.EndpointCheckpointerHolder", "datadog.trace.api.iast.IastAdvice.Kind", "datadog.trace.api.UserEventTrackingMode", + // This is almost fully abstract class so nothing to test + 'datadog.trace.api.profiling.RecordingData', + // A plain enum + 'datadog.trace.api.profiling.RecordingType', "datadog.trace.bootstrap.ActiveSubsystems", "datadog.trace.bootstrap.config.provider.ConfigProvider.Singleton", "datadog.trace.bootstrap.instrumentation.api.java.lang.ProcessImplInstrumentationHelpers", @@ -131,6 +135,8 @@ excludedClassesCoverage += [ // POJO 'datadog.trace.api.iast.util.Cookie', 'datadog.trace.api.iast.util.Cookie.Builder', + 'datadog.trace.api.LogCollector.RawLogMessage', + 'datadog.trace.api.LogCollector.Holder', // stubs 'datadog.trace.api.profiling.Timing.NoOp', 'datadog.trace.api.profiling.Timer.NoOp', diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 5f0aceaef72..bde6d0a780c 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -68,6 +68,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_IAST_REDACTION_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_IAST_REDACTION_NAME_PATTERN; import static datadog.trace.api.ConfigDefaults.DEFAULT_IAST_REDACTION_VALUE_PATTERN; +import static datadog.trace.api.ConfigDefaults.DEFAULT_IAST_STACKTRACE_LEAK_SUPPRESS; import static datadog.trace.api.ConfigDefaults.DEFAULT_IAST_TRUNCATION_MAX_VALUE_LENGTH; import static datadog.trace.api.ConfigDefaults.DEFAULT_IAST_WEAK_CIPHER_ALGORITHMS; import static datadog.trace.api.ConfigDefaults.DEFAULT_IAST_WEAK_HASH_ALGORITHMS; @@ -80,6 +81,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_PRIORITY_SAMPLING_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_PRIORITY_SAMPLING_FORCE; import static datadog.trace.api.ConfigDefaults.DEFAULT_PROPAGATION_EXTRACT_LOG_HEADER_NAMES_ENABLED; +import static datadog.trace.api.ConfigDefaults.DEFAULT_PROPAGATION_STYLE; import static datadog.trace.api.ConfigDefaults.DEFAULT_REMOTE_CONFIG_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_REMOTE_CONFIG_INTEGRITY_CHECK_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_REMOTE_CONFIG_MAX_PAYLOAD_SIZE; @@ -96,6 +98,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL; import static datadog.trace.api.ConfigDefaults.DEFAULT_TELEMETRY_HEARTBEAT_INTERVAL; +import static datadog.trace.api.ConfigDefaults.DEFAULT_TELEMETRY_LOG_COLLECTION_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TELEMETRY_METRICS_INTERVAL; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_128_BIT_TRACEID_GENERATION_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_AGENT_PORT; @@ -103,6 +106,8 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_ANALYTICS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_HTTP_RESOURCE_REMOVE_TRAILING_SLASH; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_LONG_RUNNING_FLUSH_INTERVAL; +import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_PROPAGATION_EXTRACT_FIRST; +import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_PROPAGATION_STYLE; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_RATE_LIMIT; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_REPORT_HOSTNAME; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_RESOLVER_ENABLED; @@ -154,11 +159,13 @@ import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GIT_UNSHALLOW_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_TIMEOUT_MILLIS; +import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_INJECTED_TRACER_VERSION; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_ITR_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_JACOCO_GRADLE_SOURCE_SETS; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_JACOCO_PLUGIN_EXCLUDES; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_JACOCO_PLUGIN_INCLUDES; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_JACOCO_PLUGIN_VERSION; +import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_JVM_INFO_CACHE_SIZE; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_MODULE_EXECUTION_SETTINGS_CACHE_SIZE; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_MODULE_ID; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_REPO_INDEX_SHARING_ENABLED; @@ -224,6 +231,7 @@ import static datadog.trace.api.config.GeneralConfig.TELEMETRY_DEPENDENCY_COLLECTION_ENABLED; import static datadog.trace.api.config.GeneralConfig.TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL; import static datadog.trace.api.config.GeneralConfig.TELEMETRY_HEARTBEAT_INTERVAL; +import static datadog.trace.api.config.GeneralConfig.TELEMETRY_LOG_COLLECTION_ENABLED; import static datadog.trace.api.config.GeneralConfig.TELEMETRY_METRICS_INTERVAL; import static datadog.trace.api.config.GeneralConfig.TRACER_METRICS_BUFFERING_ENABLED; import static datadog.trace.api.config.GeneralConfig.TRACER_METRICS_ENABLED; @@ -237,6 +245,7 @@ import static datadog.trace.api.config.IastConfig.IAST_REDACTION_ENABLED; import static datadog.trace.api.config.IastConfig.IAST_REDACTION_NAME_PATTERN; import static datadog.trace.api.config.IastConfig.IAST_REDACTION_VALUE_PATTERN; +import static datadog.trace.api.config.IastConfig.IAST_STACKTRACE_LEAK_SUPPRESS; import static datadog.trace.api.config.IastConfig.IAST_TELEMETRY_VERBOSITY; import static datadog.trace.api.config.IastConfig.IAST_TRUNCATION_MAX_VALUE_LENGTH; import static datadog.trace.api.config.IastConfig.IAST_WEAK_CIPHER_ALGORITHMS; @@ -306,6 +315,7 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.ELASTICSEARCH_BODY_AND_PARAMS_ENABLED; import static datadog.trace.api.config.TraceInstrumentationConfig.ELASTICSEARCH_BODY_ENABLED; import static datadog.trace.api.config.TraceInstrumentationConfig.ELASTICSEARCH_PARAMS_ENABLED; +import static datadog.trace.api.config.TraceInstrumentationConfig.GOOGLE_PUBSUB_IGNORED_GRPC_METHODS; import static datadog.trace.api.config.TraceInstrumentationConfig.GRPC_CLIENT_ERROR_STATUSES; import static datadog.trace.api.config.TraceInstrumentationConfig.GRPC_IGNORED_INBOUND_METHODS; import static datadog.trace.api.config.TraceInstrumentationConfig.GRPC_IGNORED_OUTBOUND_METHODS; @@ -389,6 +399,7 @@ import static datadog.trace.api.config.TracerConfig.TRACE_PEER_SERVICE_COMPONENT_OVERRIDES; import static datadog.trace.api.config.TracerConfig.TRACE_PEER_SERVICE_DEFAULTS_ENABLED; import static datadog.trace.api.config.TracerConfig.TRACE_PEER_SERVICE_MAPPING; +import static datadog.trace.api.config.TracerConfig.TRACE_PROPAGATION_EXTRACT_FIRST; import static datadog.trace.api.config.TracerConfig.TRACE_PROPAGATION_STYLE; import static datadog.trace.api.config.TracerConfig.TRACE_PROPAGATION_STYLE_EXTRACT; import static datadog.trace.api.config.TracerConfig.TRACE_PROPAGATION_STYLE_INJECT; @@ -475,7 +486,6 @@ * @see InstrumenterConfig for pre-instrumentation configurations * @see DynamicConfig for configuration that can be dynamically updated via remote-config */ -@Deprecated public class Config { private static final Logger log = LoggerFactory.getLogger(Config.class); @@ -574,6 +584,7 @@ static class HostNameHolder { private final boolean tracePropagationStyleB3PaddingEnabled; private final Set tracePropagationStylesToExtract; private final Set tracePropagationStylesToInject; + private final boolean tracePropagationExtractFirst; private final int clockSyncPeriod; private final boolean logsInjectionEnabled; @@ -677,6 +688,7 @@ static class HostNameHolder { private final String iastRedactionValuePattern; private final int iastMaxRangeCount; private final int iastTruncationMaxValueLength; + private final boolean iastStacktraceLeakSuppress; private final boolean ciVisibilityTraceSanitationEnabled; private final boolean ciVisibilityAgentlessEnabled; @@ -713,7 +725,9 @@ static class HostNameHolder { private final boolean ciVisibilityCiProviderIntegrationEnabled; private final boolean ciVisibilityRepoIndexSharingEnabled; private final int ciVisibilityModuleExecutionSettingsCacheSize; + private final int ciVisibilityJvmInfoCacheSize; private final boolean ciVisibilityCoverageSegmentsEnabled; + private final String ciVisibilityInjectedTracerVersion; private final boolean remoteConfigEnabled; private final boolean remoteConfigIntegrityCheckEnabled; @@ -817,6 +831,7 @@ static class HostNameHolder { private final float telemetryMetricsInterval; private final boolean isTelemetryDependencyServiceEnabled; private final boolean telemetryMetricsEnabled; + private final boolean isTelemetryLogCollectionEnabled; private final boolean azureAppServices; private final String traceAgentPath; @@ -1239,21 +1254,20 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins } // Now we can check if we should pick the default injection/extraction tracePropagationStylesToExtract = - extract.isEmpty() ? Collections.singleton(TracePropagationStyle.DATADOG) : extract; - tracePropagationStylesToInject = - inject.isEmpty() ? Collections.singleton(TracePropagationStyle.DATADOG) : inject; + extract.isEmpty() ? DEFAULT_TRACE_PROPAGATION_STYLE : extract; + tracePropagationStylesToInject = inject.isEmpty() ? DEFAULT_TRACE_PROPAGATION_STYLE : inject; // These setting are here for backwards compatibility until they can be removed in a major // release of the tracer propagationStylesToExtract = - deprecatedExtract.isEmpty() - ? Collections.singleton(PropagationStyle.DATADOG) - : deprecatedExtract; + deprecatedExtract.isEmpty() ? DEFAULT_PROPAGATION_STYLE : deprecatedExtract; propagationStylesToInject = - deprecatedInject.isEmpty() - ? Collections.singleton(PropagationStyle.DATADOG) - : deprecatedInject; + deprecatedInject.isEmpty() ? DEFAULT_PROPAGATION_STYLE : deprecatedInject; } + tracePropagationExtractFirst = + configProvider.getBoolean( + TRACE_PROPAGATION_EXTRACT_FIRST, DEFAULT_TRACE_PROPAGATION_EXTRACT_FIRST); + clockSyncPeriod = configProvider.getInteger(CLOCK_SYNC_PERIOD, DEFAULT_CLOCK_SYNC_PERIOD); logsInjectionEnabled = @@ -1467,6 +1481,10 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins TELEMETRY_DEPENDENCY_COLLECTION_ENABLED, DEFAULT_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED); + isTelemetryLogCollectionEnabled = + configProvider.getBoolean( + TELEMETRY_LOG_COLLECTION_ENABLED, DEFAULT_TELEMETRY_LOG_COLLECTION_ENABLED); + clientIpEnabled = configProvider.getBoolean(CLIENT_IP_ENABLED, DEFAULT_CLIENT_IP_ENABLED); appSecReportingInband = @@ -1532,6 +1550,9 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins configProvider.getInteger( IAST_TRUNCATION_MAX_VALUE_LENGTH, DEFAULT_IAST_TRUNCATION_MAX_VALUE_LENGTH); iastMaxRangeCount = iastDetectionMode.getIastMaxRangeCount(configProvider); + iastStacktraceLeakSuppress = + configProvider.getBoolean( + IAST_STACKTRACE_LEAK_SUPPRESS, DEFAULT_IAST_STACKTRACE_LEAK_SUPPRESS); ciVisibilityTraceSanitationEnabled = configProvider.getBoolean(CIVISIBILITY_TRACE_SANITATION_ENABLED, true); @@ -1644,8 +1665,11 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins configProvider.getBoolean(CIVISIBILITY_REPO_INDEX_SHARING_ENABLED, true); ciVisibilityModuleExecutionSettingsCacheSize = configProvider.getInteger(CIVISIBILITY_MODULE_EXECUTION_SETTINGS_CACHE_SIZE, 16); + ciVisibilityJvmInfoCacheSize = configProvider.getInteger(CIVISIBILITY_JVM_INFO_CACHE_SIZE, 8); ciVisibilityCoverageSegmentsEnabled = configProvider.getBoolean(CIVISIBILITY_COVERAGE_SEGMENTS_ENABLED, false); + ciVisibilityInjectedTracerVersion = + configProvider.getString(CIVISIBILITY_INJECTED_TRACER_VERSION); remoteConfigEnabled = configProvider.getBoolean(REMOTE_CONFIG_ENABLED, DEFAULT_REMOTE_CONFIG_ENABLED); @@ -1739,8 +1763,23 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins grpcIgnoredInboundMethods = tryMakeImmutableSet(configProvider.getList(GRPC_IGNORED_INBOUND_METHODS)); - grpcIgnoredOutboundMethods = - tryMakeImmutableSet(configProvider.getList(GRPC_IGNORED_OUTBOUND_METHODS)); + final List tmpGrpcIgnoredOutboundMethods = new ArrayList<>(); + tmpGrpcIgnoredOutboundMethods.addAll(configProvider.getList(GRPC_IGNORED_OUTBOUND_METHODS)); + // When tracing shadowing will be possible we can instrument the stubs to silent tracing + // starting from interception points + if (InstrumenterConfig.get() + .isIntegrationEnabled(Collections.singleton("google-pubsub"), true)) { + tmpGrpcIgnoredOutboundMethods.addAll( + configProvider.getList( + GOOGLE_PUBSUB_IGNORED_GRPC_METHODS, + Arrays.asList( + "google.pubsub.v1.Subscriber/ModifyAckDeadline", + "google.pubsub.v1.Subscriber/Acknowledge", + "google.pubsub.v1.Subscriber/Pull", + "google.pubsub.v1.Subscriber/StreamingPull", + "google.pubsub.v1.Publisher/Publish"))); + } + grpcIgnoredOutboundMethods = tryMakeImmutableSet(tmpGrpcIgnoredOutboundMethods); grpcServerTrimPackageResource = configProvider.getBoolean(GRPC_SERVER_TRIM_PACKAGE_RESOURCE, false); grpcServerErrorStatuses = @@ -2151,6 +2190,10 @@ public Set getTracePropagationStylesToInject() { return tracePropagationStylesToInject; } + public boolean isTracePropagationExtractFirst() { + return tracePropagationExtractFirst; + } + public int getClockSyncPeriod() { return clockSyncPeriod; } @@ -2412,6 +2455,10 @@ public static boolean isDatadogProfilerSafeInCurrentEnvironment() { return false; } } + if (Platform.isGraalVM()) { + // let's be conservative about GraalVM and require opt-in from the users + return false; + } boolean result = Platform.isJ9() || !Platform.isJavaVersion(18) // missing AGCT fixes @@ -2458,6 +2505,10 @@ public boolean isTelemetryMetricsEnabled() { return telemetryMetricsEnabled; } + public boolean isTelemetryLogCollectionEnabled() { + return isTelemetryLogCollectionEnabled; + } + public boolean isClientIpEnabled() { return clientIpEnabled; } @@ -2563,6 +2614,10 @@ public int getIastMaxRangeCount() { return iastMaxRangeCount; } + public boolean isIastStacktraceLeakSuppress() { + return iastStacktraceLeakSuppress; + } + public boolean isCiVisibilityEnabled() { return instrumenterConfig.isCiVisibilityEnabled(); } @@ -2720,10 +2775,18 @@ public int getCiVisibilityModuleExecutionSettingsCacheSize() { return ciVisibilityModuleExecutionSettingsCacheSize; } + public int getCiVisibilityJvmInfoCacheSize() { + return ciVisibilityJvmInfoCacheSize; + } + public boolean isCiVisibilityCoverageSegmentsEnabled() { return ciVisibilityCoverageSegmentsEnabled; } + public String getCiVisibilityInjectedTracerVersion() { + return ciVisibilityInjectedTracerVersion; + } + public String getAppSecRulesFile() { return appSecRulesFile; } @@ -3828,6 +3891,8 @@ public String toString() { + tracePropagationStylesToExtract + ", tracePropagationStylesToInject=" + tracePropagationStylesToInject + + ", tracePropagationExtractFirst=" + + tracePropagationExtractFirst + ", clockSyncPeriod=" + clockSyncPeriod + ", jmxFetchEnabled=" diff --git a/internal-api/src/main/java/datadog/trace/api/LogCollector.java b/internal-api/src/main/java/datadog/trace/api/LogCollector.java new file mode 100644 index 00000000000..6e26014cd45 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/LogCollector.java @@ -0,0 +1,109 @@ +package datadog.trace.api; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; + +public class LogCollector { + public static Marker SEND_TELEMETRY = MarkerFactory.getMarker("SEND_TELEMETRY"); + private static final int DEFAULT_MAX_CAPACITY = 1024; + private final Map rawLogMessages; + private final int maxCapacity; + + private static class Holder { + private static final LogCollector INSTANCE = new LogCollector(); + } + + public static LogCollector get() { + return Holder.INSTANCE; + } + + LogCollector() { + this(DEFAULT_MAX_CAPACITY); + } + + // For testing purpose + LogCollector(int maxCapacity) { + this.maxCapacity = maxCapacity; + this.rawLogMessages = new ConcurrentHashMap<>(maxCapacity); + } + + public void addLogMessage(String logLevel, String message, Throwable throwable) { + RawLogMessage rawLogMessage = + new RawLogMessage(logLevel, message, throwable, System.currentTimeMillis()); + + if (rawLogMessages.size() < maxCapacity) { + AtomicInteger count = rawLogMessages.computeIfAbsent(rawLogMessage, k -> new AtomicInteger()); + count.incrementAndGet(); + } + } + + public Collection drain() { + if (rawLogMessages.isEmpty()) { + return Collections.emptyList(); + } + + List list = new ArrayList<>(); + + Iterator> iterator = + rawLogMessages.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + + RawLogMessage logMessage = entry.getKey(); + logMessage.count = entry.getValue().get(); + + iterator.remove(); + + list.add(logMessage); + } + + return list; + } + + public static class RawLogMessage { + public final String messageOriginal; + public final String logLevel; + public final Throwable throwable; + public final long timestamp; + public int count; + + public RawLogMessage(String logLevel, String message, Throwable throwable, long timestamp) { + this.logLevel = logLevel; + this.messageOriginal = message; + this.throwable = throwable; + this.timestamp = timestamp; + } + + public String message() { + if (count > 1) { + return messageOriginal + ", {" + count + "} additional messages skipped"; + } + return messageOriginal; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RawLogMessage that = (RawLogMessage) o; + return Objects.equals(logLevel, that.logLevel) + && Objects.equals(messageOriginal, that.messageOriginal) + && Objects.equals(throwable, that.throwable); + } + + @Override + public int hashCode() { + return Objects.hash(logLevel, messageOriginal, throwable); + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/Platform.java b/internal-api/src/main/java/datadog/trace/api/Platform.java index 5e4da83fdbd..198cbf19b6c 100644 --- a/internal-api/src/main/java/datadog/trace/api/Platform.java +++ b/internal-api/src/main/java/datadog/trace/api/Platform.java @@ -186,6 +186,7 @@ static final class JvmRuntime { public final String vendor; public final String version; + public final String vendorVersion; public final String patches; public JvmRuntime() { @@ -193,15 +194,17 @@ public JvmRuntime() { System.getProperty("java.version"), System.getProperty("java.runtime.version"), System.getProperty("java.runtime.name"), - System.getProperty("java.vm.vendor")); + System.getProperty("java.vm.vendor"), + System.getProperty("java.vendor.version")); } // Only visible for testing - JvmRuntime(String javaVer, String rtVer, String name, String vendor) { + JvmRuntime(String javaVer, String rtVer, String name, String vendor, String vendorVersion) { this.name = name == null ? "" : name; this.vendor = vendor == null ? "" : vendor; javaVer = javaVer == null ? "" : javaVer; this.version = javaVer; + this.vendorVersion = vendorVersion == null ? "" : vendorVersion; rtVer = javaVer.isEmpty() || rtVer == null ? javaVer : rtVer; int patchStart = javaVer.length() + 1; this.patches = (patchStart >= rtVer.length()) ? "" : rtVer.substring(javaVer.length() + 1); @@ -304,6 +307,10 @@ public static boolean isJ9() { return System.getProperty("java.vm.name").contains("J9"); } + public static boolean isGraalVM() { + return RUNTIME.vendorVersion.toLowerCase().contains("graalvm"); + } + public static String getLangVersion() { return String.valueOf(JAVA_VERSION.major); } diff --git a/internal-api/src/main/java/datadog/trace/api/civisibility/config/Configurations.java b/internal-api/src/main/java/datadog/trace/api/civisibility/config/Configurations.java index 116d9bf800f..529598236de 100644 --- a/internal-api/src/main/java/datadog/trace/api/civisibility/config/Configurations.java +++ b/internal-api/src/main/java/datadog/trace/api/civisibility/config/Configurations.java @@ -1,5 +1,6 @@ package datadog.trace.api.civisibility.config; +import java.util.Map; import java.util.Objects; public final class Configurations { @@ -11,6 +12,7 @@ public final class Configurations { private final String runtimeVendor; private final String runtimeArchitecture; private final String testBundle; + private final Map custom; public Configurations( String osPlatform, @@ -20,7 +22,8 @@ public Configurations( String runtimeVersion, String runtimeVendor, String runtimeArchitecture, - String testBundle) { + String testBundle, + Map custom) { this.osPlatform = osPlatform; this.osArchitecture = osArchitecture; this.osVersion = osVersion; @@ -29,6 +32,7 @@ public Configurations( this.runtimeVendor = runtimeVendor; this.runtimeArchitecture = runtimeArchitecture; this.testBundle = testBundle; + this.custom = custom; } public String getOsPlatform() { @@ -63,6 +67,10 @@ public String getTestBundle() { return testBundle; } + public Map getCustom() { + return custom; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -79,7 +87,8 @@ public boolean equals(Object o) { && Objects.equals(runtimeVersion, that.runtimeVersion) && Objects.equals(runtimeVendor, that.runtimeVendor) && Objects.equals(runtimeArchitecture, that.runtimeArchitecture) - && Objects.equals(testBundle, that.testBundle); + && Objects.equals(testBundle, that.testBundle) + && Objects.equals(custom, that.custom); } @Override @@ -92,6 +101,7 @@ public int hashCode() { runtimeVersion, runtimeVendor, runtimeArchitecture, - testBundle); + testBundle, + custom); } } diff --git a/internal-api/src/main/java/datadog/trace/api/civisibility/events/BuildEventsHandler.java b/internal-api/src/main/java/datadog/trace/api/civisibility/events/BuildEventsHandler.java index da566843136..219529421c9 100644 --- a/internal-api/src/main/java/datadog/trace/api/civisibility/events/BuildEventsHandler.java +++ b/internal-api/src/main/java/datadog/trace/api/civisibility/events/BuildEventsHandler.java @@ -14,7 +14,8 @@ void onTestSessionStart( Path projectRoot, String startCommand, String buildSystemName, - String buildSystemVersion); + String buildSystemVersion, + Map additionalTags); void onTestSessionFail(T sessionKey, Throwable throwable); @@ -34,6 +35,8 @@ ModuleInfo onTestModuleStart( ModuleExecutionSettings getModuleExecutionSettings(T sessionKey, Path jvmExecutablePath); + ModuleInfo getModuleInfo(T sessionKey, String moduleName); + interface Factory { BuildEventsHandler create(); } diff --git a/internal-api/src/main/java/datadog/trace/api/http/StoredByteBody.java b/internal-api/src/main/java/datadog/trace/api/http/StoredByteBody.java index bfd1df6a8e4..8cda3dd3728 100644 --- a/internal-api/src/main/java/datadog/trace/api/http/StoredByteBody.java +++ b/internal-api/src/main/java/datadog/trace/api/http/StoredByteBody.java @@ -14,10 +14,14 @@ import java.nio.charset.StandardCharsets; import java.util.function.BiFunction; import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @see StoredCharBody */ public class StoredByteBody implements StoredBodySupplier { + private static final Logger LOGGER = LoggerFactory.getLogger(StoredByteBody.class); + static final Charset UTF_8 = StandardCharsets.UTF_8; static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; @@ -41,18 +45,21 @@ public StoredByteBody( } public synchronized void appendData(byte[] bytes, int start, int end) { - if (storedCharBody.isLimitReached()) { - return; - } - for (int i = start; i < end; ) { - if (!undecodedData.hasRemaining()) { - commit(false); + try { + if (storedCharBody.isLimitReached()) { + return; } - int write = Math.min(end - i, undecodedData.remaining()); - undecodedData.put(bytes, i, write); - i += write; + for (int i = start; i < end; ) { + if (!undecodedData.hasRemaining()) { + commit(false); + } + int write = Math.min(end - i, undecodedData.remaining()); + undecodedData.put(bytes, i, write); + i += write; + } + } catch (final Throwable e) { + LOGGER.debug("Failed to append byte array chunk", e); } - storedCharBody.maybeNotifyStart(); } @@ -66,41 +73,48 @@ public synchronized void appendData(byte[] bytes, int start, int end) { * @param len the amount of data available to write */ public synchronized void appendData(ByteBufferWriteCallback cb, int len) { - for (int i = 0; i < len; ) { - if (storedCharBody.isLimitReached()) { - return; - } - if (!undecodedData.hasRemaining()) { - commit(false); - } - int left = len - i; - int remainingInUndecoded = undecodedData.remaining(); - if (remainingInUndecoded > left) { - undecodedData.limit(left); - i += left; - } else { - i += remainingInUndecoded; + try { + for (int i = 0; i < len; ) { + if (storedCharBody.isLimitReached()) { + return; + } + if (!undecodedData.hasRemaining()) { + commit(false); + } + int left = len - i; + int remainingInUndecoded = undecodedData.remaining(); + if (remainingInUndecoded > left) { + undecodedData.limit(left); + i += left; + } else { + i += remainingInUndecoded; + } + cb.put(undecodedData); } - cb.put(undecodedData); - } - undecodedData.limit(undecodedData.capacity()); + undecodedData.limit(undecodedData.capacity()); + } catch (final Throwable e) { + LOGGER.debug("Failed to append byte buffer callback", e); + } storedCharBody.maybeNotifyStart(); } public synchronized void appendData(int byteValue) { - if (storedCharBody.isLimitReached()) { - return; - } - if (byteValue < 0 || byteValue > 255) { - return; - } + try { + if (storedCharBody.isLimitReached()) { + return; + } + if (byteValue < 0 || byteValue > 255) { + return; + } - if (!undecodedData.hasRemaining()) { - commit(false); + if (!undecodedData.hasRemaining()) { + commit(false); + } + undecodedData.put((byte) byteValue); + } catch (final Throwable e) { + LOGGER.debug("Failed to append byte", e); } - undecodedData.put((byte) byteValue); - storedCharBody.maybeNotifyStart(); } @@ -109,14 +123,22 @@ public synchronized void setCharset(Charset charset) { } public Flow maybeNotify() { - commit(true); + try { + commit(true); + } catch (final Throwable e) { + LOGGER.debug("Failed to commit end of input", e); + } return storedCharBody.maybeNotify(); } // may throw BlockingException. If used directly in advice, make use of @Advice.Throwable to // propagate public void maybeNotifyAndBlock() { - commit(true); + try { + commit(true); + } catch (final Throwable e) { + LOGGER.debug("Failed to commit end of input", e); + } storedCharBody.maybeNotifyAndBlock(); } @@ -137,6 +159,14 @@ private void commit(boolean endOfInput) { this.undecodedData.flip(); CoderResult decode = charsetDecoder.decode(this.undecodedData, this.decodedData, endOfInput); + if (endOfInput) { + /** + * Ensure the decoder is at a proper state in case the original input stream is reset, + * otherwise we will face: java.lang.IllegalStateException: Current state = CODING_END, new + * state = CODING + */ + charsetDecoder.reset(); + } this.decodedData.flip(); this.storedCharBody.appendData(this.decodedData); diff --git a/internal-api/src/main/java/datadog/trace/api/http/StoredCharBody.java b/internal-api/src/main/java/datadog/trace/api/http/StoredCharBody.java index b09d8967a0e..1af4b1e2274 100644 --- a/internal-api/src/main/java/datadog/trace/api/http/StoredCharBody.java +++ b/internal-api/src/main/java/datadog/trace/api/http/StoredCharBody.java @@ -7,9 +7,14 @@ import java.nio.CharBuffer; import java.util.Arrays; import java.util.function.BiFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Analogous to {@link StoredByteBody}, but Java doesn't support generics with scalar types. */ public class StoredCharBody implements StoredBodySupplier { + + private static final Logger LOGGER = LoggerFactory.getLogger(StoredCharBody.class); + private static final int MIN_BUFFER_SIZE = 128; // chars private static final int MAX_BUFFER_SIZE = 128 * 1024; // 256k (char == 2 bytes) private static final int GROW_FACTOR = 4; @@ -53,33 +58,41 @@ public StoredCharBody( } public synchronized void appendData(char[] chars, int start, int end) { - int newDataLen = end - start; - if (newDataLen <= 0) { - return; - } - if (!maybeExtendStorage(newDataLen)) { - return; - } + try { + int newDataLen = end - start; + if (newDataLen <= 0) { + return; + } + if (!maybeExtendStorage(newDataLen)) { + return; + } - int lenToCopy = Math.min(newDataLen, capacityLeft()); - System.arraycopy(chars, start, this.storedBody, this.storedBodyLen, lenToCopy); + int lenToCopy = Math.min(newDataLen, capacityLeft()); + System.arraycopy(chars, start, this.storedBody, this.storedBodyLen, lenToCopy); - this.storedBodyLen += lenToCopy; + this.storedBodyLen += lenToCopy; + } catch (final Throwable e) { + LOGGER.debug("Error appending char array chunk", e); + } maybeNotifyStart(); } public synchronized void appendData(CharBuffer buffer) { - int inputLen = buffer.remaining(); - if (inputLen == 0) { - return; - } - if (!maybeExtendStorage(inputLen)) { - return; - } - int lenToCopy = Math.min(inputLen, capacityLeft()); + try { + int inputLen = buffer.remaining(); + if (inputLen == 0) { + return; + } + if (!maybeExtendStorage(inputLen)) { + return; + } + int lenToCopy = Math.min(inputLen, capacityLeft()); - buffer.get(this.storedBody, this.storedBodyLen, lenToCopy); - this.storedBodyLen += lenToCopy; + buffer.get(this.storedBody, this.storedBodyLen, lenToCopy); + this.storedBodyLen += lenToCopy; + } catch (final Throwable e) { + LOGGER.debug("Error appending char buffer", e); + } maybeNotifyStart(); } @@ -100,16 +113,20 @@ private boolean maybeExtendStorage(int newDataLen) { } public synchronized void appendData(String s) { - int newDataLen = s.length(); - if (!maybeExtendStorage(newDataLen)) { - return; - } + try { + int newDataLen = s.length(); + if (!maybeExtendStorage(newDataLen)) { + return; + } - int lenToCopy = Math.min(newDataLen, capacityLeft()); - s.getChars(0, lenToCopy, this.storedBody, this.storedBodyLen); + int lenToCopy = Math.min(newDataLen, capacityLeft()); + s.getChars(0, lenToCopy, this.storedBody, this.storedBodyLen); - this.storedBodyLen += lenToCopy; + this.storedBodyLen += lenToCopy; + } catch (final Throwable e) { + LOGGER.debug("Error appending string", e); + } maybeNotifyStart(); } @@ -119,15 +136,19 @@ private int capacityLeft() { /** @param utf16CodeUnit an int in the range 0-0xFFFF */ public synchronized void appendData(int utf16CodeUnit) { - if (utf16CodeUnit < 0) { - return; - } - if (!maybeExtendStorage(1)) { - return; - } - this.storedBody[this.storedBodyLen] = (char) utf16CodeUnit; - this.storedBodyLen += 1; + try { + if (utf16CodeUnit < 0) { + return; + } + if (!maybeExtendStorage(1)) { + return; + } + this.storedBody[this.storedBodyLen] = (char) utf16CodeUnit; + this.storedBodyLen += 1; + } catch (final Throwable e) { + LOGGER.debug("Error appending code unit", e); + } maybeNotifyStart(); } diff --git a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java index d00e5473f2e..459ba10196a 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java @@ -13,6 +13,7 @@ import datadog.trace.api.iast.sink.PathTraversalModule; import datadog.trace.api.iast.sink.SqlInjectionModule; import datadog.trace.api.iast.sink.SsrfModule; +import datadog.trace.api.iast.sink.StacktraceLeakModule; import datadog.trace.api.iast.sink.TrustBoundaryViolationModule; import datadog.trace.api.iast.sink.UnvalidatedRedirectModule; import datadog.trace.api.iast.sink.WeakCipherModule; @@ -51,6 +52,8 @@ public abstract class InstrumentationBridge { public static volatile XssModule XSS; + public static volatile StacktraceLeakModule STACKTRACE_LEAK_MODULE; + private InstrumentationBridge() {} public static void registerIastModule(final IastModule module) { @@ -96,6 +99,8 @@ public static void registerIastModule(final IastModule module) { TRUST_BOUNDARY_VIOLATION = (TrustBoundaryViolationModule) module; } else if (module instanceof XssModule) { XSS = (XssModule) module; + } else if (module instanceof StacktraceLeakModule) { + STACKTRACE_LEAK_MODULE = (StacktraceLeakModule) module; } else { throw new UnsupportedOperationException("Module not yet supported: " + module); } @@ -167,6 +172,9 @@ public static E getIastModule(final Class type) { if (type == XssModule.class) { return (E) XSS; } + if (type == StacktraceLeakModule.class) { + return (E) STACKTRACE_LEAK_MODULE; + } throw new UnsupportedOperationException("Module not yet supported: " + type); } @@ -193,5 +201,6 @@ public static void clearIastModules() { XPATH_INJECTION = null; TRUST_BOUNDARY_VIOLATION = null; XSS = null; + STACKTRACE_LEAK_MODULE = null; } } diff --git a/internal-api/src/main/java/datadog/trace/api/iast/VulnerabilityTypes.java b/internal-api/src/main/java/datadog/trace/api/iast/VulnerabilityTypes.java index 74e5a8ce22b..1a21ce795e5 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/VulnerabilityTypes.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/VulnerabilityTypes.java @@ -38,6 +38,8 @@ private VulnerabilityTypes() {} public static final String TRUST_BOUNDARY_VIOLATION_STRING = "TRUST_BOUNDARY_VIOLATION"; public static final byte XSS = 16; public static final String XSS_STRING = "XSS"; + public static final byte STACKTRACE_LEAK = 17; + public static final String STACKTRACE_LEAK_STRING = "STACKTRACE_LEAK"; /** * Use for telemetry only, this is a special vulnerability type that is not reported, reported @@ -78,7 +80,8 @@ private VulnerabilityTypes() {} HSTS_HEADER_MISSING, XCONTENTTYPE_HEADER_MISSING, NO_SAMESITE_COOKIE, - XSS + XSS, + STACKTRACE_LEAK }; public static byte[] values() { @@ -121,6 +124,8 @@ public static String toString(final byte sourceType) { return VulnerabilityTypes.NO_SAMESITE_COOKIE_STRING; case VulnerabilityTypes.XSS: return VulnerabilityTypes.XSS_STRING; + case VulnerabilityTypes.STACKTRACE_LEAK: + return VulnerabilityTypes.STACKTRACE_LEAK_STRING; default: return null; } diff --git a/internal-api/src/main/java/datadog/trace/api/iast/propagation/PropagationModule.java b/internal-api/src/main/java/datadog/trace/api/iast/propagation/PropagationModule.java index 4ac30f9456e..fc72b58901c 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/propagation/PropagationModule.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/propagation/PropagationModule.java @@ -150,15 +150,16 @@ void taintIfAnyTainted( int mark); /** @see #taintDeeply(IastContext, Object, byte, Predicate) */ - void taintDeeply(@Nullable Object target, byte origin, Predicate> classFilter); + int taintDeeply(@Nullable Object target, byte origin, Predicate> classFilter); /** * Visit the graph of the object and taints all the string properties found using a source with * the selected origin and no name. * * @param classFilter filter for types that should be included in the visiting process + * @return number of tainted elements */ - void taintDeeply( + int taintDeeply( @Nullable IastContext ctx, @Nullable Object target, byte origin, diff --git a/internal-api/src/main/java/datadog/trace/api/iast/sink/StacktraceLeakModule.java b/internal-api/src/main/java/datadog/trace/api/iast/sink/StacktraceLeakModule.java new file mode 100644 index 00000000000..2a6a668303c --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/iast/sink/StacktraceLeakModule.java @@ -0,0 +1,9 @@ +package datadog.trace.api.iast.sink; + +import datadog.trace.api.iast.IastModule; +import javax.annotation.Nullable; + +public interface StacktraceLeakModule extends IastModule { + void onStacktraceLeak( + @Nullable final Throwable expression, String moduleName, String className, String methodName); +} diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v1/MessagingNamingV1.java b/internal-api/src/main/java/datadog/trace/api/naming/v1/MessagingNamingV1.java index 78f980539f6..e086d9e91fe 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v1/MessagingNamingV1.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v1/MessagingNamingV1.java @@ -10,6 +10,8 @@ private String normalizeForCloud(@Nonnull final String messagingSystem) { case "sns": case "sqs": return "aws." + messagingSystem; + case "google-pubsub": + return "gcp.pubsub"; default: return messagingSystem; } diff --git a/internal-api/src/main/java/datadog/trace/api/normalize/SQLNormalizer.java b/internal-api/src/main/java/datadog/trace/api/normalize/SQLNormalizer.java index 8400b26cd60..d8bfd67f076 100644 --- a/internal-api/src/main/java/datadog/trace/api/normalize/SQLNormalizer.java +++ b/internal-api/src/main/java/datadog/trace/api/normalize/SQLNormalizer.java @@ -40,6 +40,10 @@ public final class SQLNormalizer { } } + public static UTF8BytesString normalizeCharSequence(CharSequence sql) { + return normalize(sql.toString()); + } + public static UTF8BytesString normalize(String sql) { byte[] utf8 = sql.getBytes(UTF_8); try { diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingData.java b/internal-api/src/main/java/datadog/trace/api/profiling/RecordingData.java similarity index 96% rename from dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingData.java rename to internal-api/src/main/java/datadog/trace/api/profiling/RecordingData.java index 5b0836c4eec..c886ebcf81a 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingData.java +++ b/internal-api/src/main/java/datadog/trace/api/profiling/RecordingData.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datadog.profiling.controller; +package datadog.trace.api.profiling; -import datadog.trace.api.profiling.ProfilingSnapshot; import java.io.IOException; import java.time.Instant; import javax.annotation.Nonnull; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingDataListener.java b/internal-api/src/main/java/datadog/trace/api/profiling/RecordingDataListener.java similarity index 96% rename from dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingDataListener.java rename to internal-api/src/main/java/datadog/trace/api/profiling/RecordingDataListener.java index f84f923d061..29a5e4951bf 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingDataListener.java +++ b/internal-api/src/main/java/datadog/trace/api/profiling/RecordingDataListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datadog.profiling.controller; +package datadog.trace.api.profiling; /** Listener for getting notified when new recording data is becoming available. */ public interface RecordingDataListener { diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingInputStream.java b/internal-api/src/main/java/datadog/trace/api/profiling/RecordingInputStream.java similarity index 82% rename from dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingInputStream.java rename to internal-api/src/main/java/datadog/trace/api/profiling/RecordingInputStream.java index 5d584b73e09..0549635a027 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingInputStream.java +++ b/internal-api/src/main/java/datadog/trace/api/profiling/RecordingInputStream.java @@ -1,11 +1,10 @@ -package com.datadog.profiling.controller; +package datadog.trace.api.profiling; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; -/** A wrapper stream implementation for the recording data. */ -public final class RecordingInputStream extends BufferedInputStream { +public class RecordingInputStream extends BufferedInputStream { public RecordingInputStream(InputStream in) { super(in); } diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingType.java b/internal-api/src/main/java/datadog/trace/api/profiling/RecordingType.java similarity index 82% rename from dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingType.java rename to internal-api/src/main/java/datadog/trace/api/profiling/RecordingType.java index b8f6280b64d..639ccc39557 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/RecordingType.java +++ b/internal-api/src/main/java/datadog/trace/api/profiling/RecordingType.java @@ -1,4 +1,4 @@ -package com.datadog.profiling.controller; +package datadog.trace.api.profiling; public enum RecordingType { CONTINUOUS("continuous"); diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java index 20a22942540..463cfc4920e 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java @@ -6,6 +6,7 @@ import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.interceptor.MutableSpan; import datadog.trace.api.sampling.PrioritySampling; +import java.util.List; import java.util.Map; public interface AgentSpan extends MutableSpan, IGSpanInfo { @@ -178,6 +179,13 @@ interface Context { default void mergePathwayContext(PathwayContext pathwayContext) {} interface Extracted extends Context { + /** + * Gets the span links related to the other terminated context. + * + * @return The span links to other extracted contexts found but terminated. + */ + List getTerminatedContextLinks(); + String getForwarded(); String getFastlyClientIp(); diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java index 3a1542987ea..000bc2c7235 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java @@ -1,6 +1,7 @@ package datadog.trace.bootstrap.instrumentation.api; import static datadog.trace.api.ConfigDefaults.DEFAULT_ASYNC_PROPAGATING; +import static java.util.Collections.emptyList; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; @@ -25,6 +26,7 @@ import java.nio.ByteBuffer; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -915,7 +917,7 @@ public int getSamplingPriority() { @Override public Iterable> baggageItems() { - return Collections.emptyList(); + return emptyList(); } @Override @@ -923,6 +925,11 @@ public PathwayContext getPathwayContext() { return NoopPathwayContext.INSTANCE; } + @Override + public List getTerminatedContextLinks() { + return emptyList(); + } + @Override public String getForwarded() { return null; diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ProfilingContextIntegration.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ProfilingContextIntegration.java index 1bee6e1c48e..c643879e82b 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ProfilingContextIntegration.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ProfilingContextIntegration.java @@ -17,14 +17,20 @@ public interface ProfilingContextIntegration extends Profiling { void setContext(long rootSpanId, long spanId); - boolean isQueuingTimeEnabled(); + default int encode(CharSequence constant) { + return 0; + } - void recordQueueingTime(long duration); + default int encodeOperationName(CharSequence constant) { + return 0; + } - default int encode(CharSequence constant) { + default int encodeResourceName(CharSequence constant) { return 0; } + String name(); + final class NoOp implements ProfilingContextIntegration { public static final ProfilingContextIntegration INSTANCE = @@ -56,11 +62,8 @@ public void clearContext() {} public void setContext(long rootSpanId, long spanId) {} @Override - public boolean isQueuingTimeEnabled() { - return false; + public String name() { + return "none"; } - - @Override - public void recordQueueingTime(long duration) {} } } diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index 4c730abfdfc..303b78a9ebd 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -1,10 +1,16 @@ package datadog.trace.bootstrap.instrumentation.api; +import static datadog.trace.api.TracePropagationStyle.NONE; +import static java.util.Collections.emptyList; + import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; +import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.sampling.PrioritySampling; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Map; /** @@ -17,6 +23,7 @@ public class TagContext implements AgentSpan.Context.Extracted { private final CharSequence origin; private final Map tags; + private List terminatedContextLinks; private Object requestContextDataAppSec; private Object requestContextDataIast; private Object ciVisibilityContextData; @@ -25,13 +32,14 @@ public class TagContext implements AgentSpan.Context.Extracted { private final Map baggage; private final int samplingPriority; private final TraceConfig traceConfig; + private final TracePropagationStyle propagationStyle; public TagContext() { this(null, null); } public TagContext(final String origin, final Map tags) { - this(origin, tags, null, null, PrioritySampling.UNSET, null); + this(origin, tags, null, null, PrioritySampling.UNSET, null, NONE); } public TagContext( @@ -40,23 +48,42 @@ public TagContext( final HttpHeaders httpHeaders, final Map baggage, final int samplingPriority, - final TraceConfig traceConfig) { + final TraceConfig traceConfig, + final TracePropagationStyle propagationStyle) { this.origin = origin; this.tags = tags; + this.terminatedContextLinks = null; this.httpHeaders = httpHeaders == null ? EMPTY_HTTP_HEADERS : httpHeaders; this.baggage = baggage == null ? Collections.emptyMap() : baggage; this.samplingPriority = samplingPriority; this.traceConfig = traceConfig; + this.propagationStyle = propagationStyle; } public TraceConfig getTraceConfig() { return traceConfig; } + public TracePropagationStyle getPropagationStyle() { + return this.propagationStyle; + } + public final CharSequence getOrigin() { return origin; } + @Override + public List getTerminatedContextLinks() { + return this.terminatedContextLinks == null ? emptyList() : this.terminatedContextLinks; + } + + public void addTerminatedContextLink(AgentSpanLink link) { + if (this.terminatedContextLinks == null) { + this.terminatedContextLinks = new ArrayList<>(); + } + this.terminatedContextLinks.add(link); + } + @Override public String getForwarded() { return httpHeaders.forwarded; diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/Tags.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/Tags.java index 4c824529b36..cfe43ff6df0 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/Tags.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/Tags.java @@ -62,6 +62,7 @@ public class Tags { public static final String TEST_COMMAND = "test.command"; public static final String TEST_TOOLCHAIN = "test.toolchain"; public static final String TEST_EXECUTION = "test.execution"; + public static final String TEST_GRADLE_NESTED_BUILD = "test.gradle.nested_build"; public static final String TEST_SESSION_ID = "test_session_id"; public static final String TEST_MODULE_ID = "test_module_id"; diff --git a/internal-api/src/main/java/datadog/trace/util/stacktrace/StackUtils.java b/internal-api/src/main/java/datadog/trace/util/stacktrace/StackUtils.java index 7aafe8636c0..5f3cc6a22c8 100644 --- a/internal-api/src/main/java/datadog/trace/util/stacktrace/StackUtils.java +++ b/internal-api/src/main/java/datadog/trace/util/stacktrace/StackUtils.java @@ -49,6 +49,21 @@ public static E filterUntil( }); } + public static E filterPackagesIn( + final E exception, final String[] packages) { + return filter( + exception, + ste -> { + final String clazz = ste.getClassName(); + for (String p : packages) { + if (clazz.startsWith(p)) { + return true; + } + } + return false; + }); + } + private static class OneTimePredicate implements Predicate { private final Predicate delegate; diff --git a/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy index 78d96bcbf5b..5793b4d2b1b 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy @@ -25,6 +25,7 @@ import static datadog.trace.api.TracePropagationStyle.B3MULTI import static datadog.trace.api.TracePropagationStyle.B3SINGLE import static datadog.trace.api.TracePropagationStyle.DATADOG import static datadog.trace.api.TracePropagationStyle.HAYSTACK +import static datadog.trace.api.TracePropagationStyle.TRACECONTEXT import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_AGENTLESS_ENABLED import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_ENABLED import static datadog.trace.api.config.DebuggerConfig.DEBUGGER_CLASSFILE_DUMP_ENABLED @@ -113,6 +114,7 @@ import static datadog.trace.api.config.TracerConfig.SPAN_TAGS import static datadog.trace.api.config.TracerConfig.SPLIT_BY_TAGS import static datadog.trace.api.config.TracerConfig.TRACE_AGENT_PORT import static datadog.trace.api.config.TracerConfig.TRACE_AGENT_URL +import static datadog.trace.api.config.TracerConfig.TRACE_PROPAGATION_EXTRACT_FIRST import static datadog.trace.api.config.TracerConfig.TRACE_RATE_LIMIT import static datadog.trace.api.config.TracerConfig.TRACE_REPORT_HOSTNAME import static datadog.trace.api.config.TracerConfig.TRACE_RESOLVER_ENABLED @@ -144,6 +146,7 @@ class ConfigTest extends DDSpecification { private static final DD_JMX_TAGS_ENV = "DD_TRACE_JMX_TAGS" private static final DD_PROPAGATION_STYLE_EXTRACT = "DD_PROPAGATION_STYLE_EXTRACT" private static final DD_PROPAGATION_STYLE_INJECT = "DD_PROPAGATION_STYLE_INJECT" + private static final DD_TRACE_PROPAGATION_EXTRACT_FIRST = "DD_TRACE_PROPAGATION_EXTRACT_FIRST" private static final DD_JMXFETCH_METRICS_CONFIGS_ENV = "DD_JMXFETCH_METRICS_CONFIGS" private static final DD_TRACE_AGENT_PORT_ENV = "DD_TRACE_AGENT_PORT" private static final DD_AGENT_PORT_LEGACY_ENV = "DD_AGENT_PORT" @@ -190,6 +193,7 @@ class ConfigTest extends DDSpecification { prop.setProperty(RUNTIME_CONTEXT_FIELD_INJECTION, "false") prop.setProperty(PROPAGATION_STYLE_EXTRACT, "Datadog, B3") prop.setProperty(PROPAGATION_STYLE_INJECT, "B3, Datadog") + prop.setProperty(TRACE_PROPAGATION_EXTRACT_FIRST, "false") prop.setProperty(JMX_FETCH_ENABLED, "false") prop.setProperty(JMX_FETCH_METRICS_CONFIGS, "/foo.yaml,/bar.yaml") prop.setProperty(JMX_FETCH_CHECK_PERIOD, "100") @@ -278,6 +282,7 @@ class ConfigTest extends DDSpecification { config.propagationStylesToInject.toList() == [PropagationStyle.B3, PropagationStyle.DATADOG] config.tracePropagationStylesToExtract.toList() == [DATADOG, B3SINGLE, B3MULTI] config.tracePropagationStylesToInject.toList() == [B3SINGLE, B3MULTI, DATADOG] + config.tracePropagationExtractFirst == false config.jmxFetchEnabled == false config.jmxFetchMetricsConfigs == ["/foo.yaml", "/bar.yaml"] config.jmxFetchCheckPeriod == 100 @@ -367,6 +372,7 @@ class ConfigTest extends DDSpecification { System.setProperty(PREFIX + RUNTIME_CONTEXT_FIELD_INJECTION, "false") System.setProperty(PREFIX + PROPAGATION_STYLE_EXTRACT, "Datadog, B3") System.setProperty(PREFIX + PROPAGATION_STYLE_INJECT, "B3, Datadog") + System.setProperty(PREFIX + TRACE_PROPAGATION_EXTRACT_FIRST, "false") System.setProperty(PREFIX + JMX_FETCH_ENABLED, "false") System.setProperty(PREFIX + JMX_FETCH_METRICS_CONFIGS, "/foo.yaml,/bar.yaml") System.setProperty(PREFIX + JMX_FETCH_CHECK_PERIOD, "100") @@ -455,6 +461,7 @@ class ConfigTest extends DDSpecification { config.propagationStylesToInject.toList() == [PropagationStyle.B3, PropagationStyle.DATADOG] config.tracePropagationStylesToExtract.toList() == [DATADOG, B3SINGLE, B3MULTI] config.tracePropagationStylesToInject.toList() == [B3SINGLE, B3MULTI, DATADOG] + config.tracePropagationExtractFirst == false config.jmxFetchEnabled == false config.jmxFetchMetricsConfigs == ["/foo.yaml", "/bar.yaml"] config.jmxFetchCheckPeriod == 100 @@ -521,6 +528,7 @@ class ConfigTest extends DDSpecification { environmentVariables.set(DD_PRIORITIZATION_TYPE_ENV, "EnsureTrace") environmentVariables.set(DD_PROPAGATION_STYLE_EXTRACT, "B3 Datadog") environmentVariables.set(DD_PROPAGATION_STYLE_INJECT, "Datadog B3") + environmentVariables.set(DD_TRACE_PROPAGATION_EXTRACT_FIRST, "false") environmentVariables.set(DD_JMXFETCH_METRICS_CONFIGS_ENV, "some/file") environmentVariables.set(DD_TRACE_REPORT_HOSTNAME, "true") environmentVariables.set(DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH, "42") @@ -539,6 +547,7 @@ class ConfigTest extends DDSpecification { config.propagationStylesToInject.toList() == [PropagationStyle.DATADOG, PropagationStyle.B3] config.tracePropagationStylesToExtract.toList() == [B3SINGLE, B3MULTI, DATADOG] config.tracePropagationStylesToInject.toList() == [DATADOG, B3SINGLE, B3MULTI] + config.tracePropagationExtractFirst == false config.jmxFetchMetricsConfigs == ["some/file"] config.reportHostName == true config.xDatadogTagsMaxLength == 42 @@ -624,8 +633,8 @@ class ConfigTest extends DDSpecification { config.splitByTags == [].toSet() config.propagationStylesToExtract.toList() == [PropagationStyle.DATADOG] config.propagationStylesToInject.toList() == [PropagationStyle.DATADOG] - config.tracePropagationStylesToExtract.toList() == [DATADOG] - config.tracePropagationStylesToInject.toList() == [DATADOG] + config.tracePropagationStylesToExtract.toList() == [DATADOG, TRACECONTEXT] + config.tracePropagationStylesToInject.toList() == [DATADOG, TRACECONTEXT] config.longRunningTraceEnabled == false } @@ -2200,7 +2209,7 @@ class ConfigTest extends DDSpecification { return bs } - def "check trace propagation style overrides for "() { + def "check trace propagation style overrides for"() { setup: if (pse) { environmentVariables.set('DD_PROPAGATION_STYLE_EXTRACT', pse.toString()) @@ -2228,21 +2237,21 @@ class ConfigTest extends DDSpecification { where: // spotless:off - pse | psi | tps | tpse | tpsi | ePSE | ePSI | eTPSE | eTPSI - PropagationStyle.DATADOG | PropagationStyle.B3 | null | null | null | [PropagationStyle.DATADOG] | [PropagationStyle.B3] | [DATADOG] | [B3SINGLE, B3MULTI] - PropagationStyle.B3 | PropagationStyle.DATADOG | null | null | null | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [B3SINGLE, B3MULTI] | [DATADOG] - PropagationStyle.B3 | PropagationStyle.DATADOG | HAYSTACK | null | null | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [HAYSTACK] | [HAYSTACK] - PropagationStyle.B3 | PropagationStyle.DATADOG | HAYSTACK | B3SINGLE | null | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [B3SINGLE] | [HAYSTACK] - PropagationStyle.B3 | PropagationStyle.DATADOG | HAYSTACK | null | B3MULTI | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [HAYSTACK] | [B3MULTI] - PropagationStyle.B3 | PropagationStyle.DATADOG | HAYSTACK | B3SINGLE | B3MULTI | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [B3SINGLE] | [B3MULTI] - PropagationStyle.B3 | PropagationStyle.DATADOG | null | B3SINGLE | B3MULTI | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [B3SINGLE] | [B3MULTI] - null | null | HAYSTACK | null | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [HAYSTACK] | [HAYSTACK] - null | null | HAYSTACK | B3SINGLE | B3MULTI | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [B3SINGLE] | [B3MULTI] - null | null | null | B3SINGLE | B3MULTI | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [B3SINGLE] | [B3MULTI] - null | null | null | null | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [DATADOG] | [DATADOG] - null | null | null | null | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [DATADOG] | [DATADOG] - null | null | null | "b3 single header" | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [B3SINGLE] | [DATADOG] - null | null | null | "b3" | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [B3MULTI] | [DATADOG] + pse | psi | tps | tpse | tpsi | ePSE | ePSI | eTPSE | eTPSI + PropagationStyle.DATADOG | PropagationStyle.B3 | null | null | null | [PropagationStyle.DATADOG] | [PropagationStyle.B3] | [DATADOG] | [B3SINGLE, B3MULTI] + PropagationStyle.B3 | PropagationStyle.DATADOG | null | null | null | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [B3SINGLE, B3MULTI] | [DATADOG] + PropagationStyle.B3 | PropagationStyle.DATADOG | HAYSTACK | null | null | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [HAYSTACK] | [HAYSTACK] + PropagationStyle.B3 | PropagationStyle.DATADOG | HAYSTACK | B3SINGLE | null | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [B3SINGLE] | [HAYSTACK] + PropagationStyle.B3 | PropagationStyle.DATADOG | HAYSTACK | null | B3MULTI | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [HAYSTACK] | [B3MULTI] + PropagationStyle.B3 | PropagationStyle.DATADOG | HAYSTACK | B3SINGLE | B3MULTI | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [B3SINGLE] | [B3MULTI] + PropagationStyle.B3 | PropagationStyle.DATADOG | null | B3SINGLE | B3MULTI | [PropagationStyle.B3] | [PropagationStyle.DATADOG] | [B3SINGLE] | [B3MULTI] + null | null | HAYSTACK | null | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [HAYSTACK] | [HAYSTACK] + null | null | HAYSTACK | B3SINGLE | B3MULTI | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [B3SINGLE] | [B3MULTI] + null | null | null | B3SINGLE | B3MULTI | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [B3SINGLE] | [B3MULTI] + null | null | null | null | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [DATADOG, TRACECONTEXT] | [DATADOG, TRACECONTEXT] + null | null | null | null | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [DATADOG, TRACECONTEXT] | [DATADOG, TRACECONTEXT] + null | null | null | "b3 single header" | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [B3SINGLE] | [DATADOG, TRACECONTEXT] + null | null | null | "b3" | null | [PropagationStyle.DATADOG] | [PropagationStyle.DATADOG] | [B3MULTI] | [DATADOG, TRACECONTEXT] // spotless:on } @@ -2347,7 +2356,7 @@ class ConfigTest extends DDSpecification { "450" | 450 } - def "partial flush and min spans interaction #"() { + def "partial flush and min spans interaction"() { when: def prop = new Properties() if (configuredPartialEnabled != null) { diff --git a/internal-api/src/test/groovy/datadog/trace/api/PlatformTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/PlatformTest.groovy index f3ab7d47130..12777977d4c 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/PlatformTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/PlatformTest.groovy @@ -97,7 +97,7 @@ class PlatformTest extends DDSpecification { def "JVMRuntime is at least a bit resilient against weird version properties"() { when: - def runtime = new Platform.JvmRuntime(propVersion, rtVersion, propName, propVendor) + def runtime = new Platform.JvmRuntime(propVersion, rtVersion, propName, propVendor, null) then: runtime.version == version diff --git a/internal-api/src/test/groovy/datadog/trace/api/http/StoredByteBodyTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/http/StoredByteBodyTest.groovy index 43ea3d61e4d..fc993003a34 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/http/StoredByteBodyTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/http/StoredByteBodyTest.groovy @@ -26,7 +26,7 @@ class StoredByteBodyTest extends Specification { 1 * startCb.apply(requestContext, storedByteBody) when: - storedByteBody.appendData([(int)'a']* 127 as byte[], 0, 127) + storedByteBody.appendData([(int) 'a'] * 127 as byte[], 0, 127) def flow = storedByteBody.maybeNotify() then: @@ -46,7 +46,7 @@ class StoredByteBodyTest extends Specification { when: // last byte ignored - storedByteBody.appendData([(int)'a']* 128 * 1024 as byte[], 0, 128 * 1024) + storedByteBody.appendData([(int) 'a'] * 128 * 1024 as byte[], 0, 128 * 1024) // ignored storedByteBody.appendData(0) // ignored @@ -203,11 +203,27 @@ class StoredByteBodyTest extends Specification { StoredByteBody.ByteBufferWriteCallback mockCb = Mock() when: - storedByteBody.appendData([(int)'a']* 128 * 1024 as byte[], 0, 128 * 1024) + storedByteBody.appendData([(int) 'a'] * 128 * 1024 as byte[], 0, 128 * 1024) storedByteBody.get() // force commit storedByteBody.appendData(mockCb, 1) then: 0 * mockCb._(*_) } + + void 'does not fail on double commit'() { + given: + final charset = Charset.defaultCharset() + final body = (1..100).collect {'hello world'}.join().getBytes(charset) + storedByteBody = new StoredByteBody(requestContext, startCb, endCb, charset, 0) + storedByteBody.appendData(body, 0, body.length) + storedByteBody.maybeNotify() + + when: + storedByteBody.appendData(body, 0, body.length) + storedByteBody.maybeNotify() + + then: + noExceptionThrown() + } } diff --git a/internal-api/src/test/groovy/datadog/trace/api/telemetry/TelemetryCollectorsTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/telemetry/TelemetryCollectorsTest.groovy index c8db7b8953c..279acae9ad6 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/telemetry/TelemetryCollectorsTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/TelemetryCollectorsTest.groovy @@ -4,6 +4,7 @@ import datadog.trace.api.ConfigCollector import datadog.trace.api.ConfigOrigin import datadog.trace.api.ConfigSetting import datadog.trace.api.IntegrationsCollector +import datadog.trace.api.LogCollector import datadog.trace.api.metrics.SpanMetricRegistryImpl import datadog.trace.test.util.DDSpecification @@ -245,4 +246,48 @@ class TelemetryCollectorsTest extends DDSpecification { collector.prepareMetrics() collector.drain().size() == limit } + + void "limit log messages in LogCollector"() { + setup: + def logCollector = new LogCollector(3) + when: + logCollector.addLogMessage("ERROR", "Message 1", null) + logCollector.addLogMessage("ERROR", "Message 2", null) + logCollector.addLogMessage("ERROR", "Message 3", null) + logCollector.addLogMessage("ERROR", "Message 4", null) + + then: + logCollector.rawLogMessages.size() == 3 + } + + void "grouping messages in LogCollector"() { + when: + LogCollector.get().addLogMessage("ERROR", "First Message", null) + LogCollector.get().addLogMessage("ERROR", "Second Message", null) + LogCollector.get().addLogMessage("ERROR", "Third Message", null) + LogCollector.get().addLogMessage("ERROR", "Forth Message", null) + LogCollector.get().addLogMessage("ERROR", "Second Message", null) + LogCollector.get().addLogMessage("ERROR", "Third Message", null) + LogCollector.get().addLogMessage("ERROR", "Forth Message", null) + LogCollector.get().addLogMessage("ERROR", "Third Message", null) + LogCollector.get().addLogMessage("ERROR", "Forth Message", null) + LogCollector.get().addLogMessage("ERROR", "Forth Message", null) + + then: + def list = LogCollector.get().drain() + list.size() == 4 + listContains(list, 'ERROR', "First Message", null) + listContains(list, 'ERROR', "Second Message, {2} additional messages skipped", null) + listContains(list, 'ERROR', "Third Message, {3} additional messages skipped", null) + listContains(list, 'ERROR', "Forth Message, {4} additional messages skipped", null) + } + + boolean listContains(Collection list, String logLevel, String message, Throwable t) { + for (final def logMsg in list) { + if (logMsg.logLevel == logLevel && logMsg.message() == message && logMsg.throwable == t) { + return true + } + } + return false + } } diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/RecordingInputStreamTest.java b/internal-api/src/test/java/datadog/trace/api/profiling/RecordingInputStreamTest.java similarity index 94% rename from dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/RecordingInputStreamTest.java rename to internal-api/src/test/java/datadog/trace/api/profiling/RecordingInputStreamTest.java index 68570177211..9521e5ecb8a 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/RecordingInputStreamTest.java +++ b/internal-api/src/test/java/datadog/trace/api/profiling/RecordingInputStreamTest.java @@ -1,4 +1,4 @@ -package com.datadog.profiling.controller; +package datadog.trace.api.profiling; import static org.junit.jupiter.api.Assertions.*; diff --git a/internal-api/src/test/java/datadog/trace/util/stacktrace/StackUtilsTest.java b/internal-api/src/test/java/datadog/trace/util/stacktrace/StackUtilsTest.java index 47ffbc99aee..4d1ad86235f 100644 --- a/internal-api/src/test/java/datadog/trace/util/stacktrace/StackUtilsTest.java +++ b/internal-api/src/test/java/datadog/trace/util/stacktrace/StackUtilsTest.java @@ -38,7 +38,7 @@ public void test_filter_all_datadog() { } @Test - public void test_filter_first_datadog() { + public void test_stack_filters() { final StackTraceElement[] stack = new StackTraceElement[] { stack().className("org.junit.jupiter.api.Test").build(), @@ -57,6 +57,11 @@ public void test_filter_first_datadog() { final Throwable filtered2 = StackUtils.filterFirstDatadog(withStack(stack)); assertThat(filtered2.getStackTrace()).isEqualTo(expected); + + final String[] packages = {"datadog.trace.util.stacktrace"}; + final StackTraceElement[] expected2 = new StackTraceElement[] {stack[1], stack[3]}; + final Throwable filtered3 = StackUtils.filterPackagesIn(withStack(stack), packages); + assertThat(filtered3.getStackTrace()).isEqualTo(expected2); } @Test diff --git a/remote-config/src/main/java/datadog/remoteconfig/PollerRequestFactory.java b/remote-config/src/main/java/datadog/remoteconfig/PollerRequestFactory.java index 41da79f1b51..73e39a2fa65 100644 --- a/remote-config/src/main/java/datadog/remoteconfig/PollerRequestFactory.java +++ b/remote-config/src/main/java/datadog/remoteconfig/PollerRequestFactory.java @@ -5,6 +5,9 @@ import datadog.remoteconfig.tuf.RemoteConfigRequest.CachedTargetFile; import datadog.remoteconfig.tuf.RemoteConfigRequest.ClientInfo.ClientState; import datadog.trace.api.Config; +import datadog.trace.api.git.GitInfo; +import datadog.trace.api.git.GitInfoProvider; +import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.util.TagsHelper; import java.util.Arrays; import java.util.Collection; @@ -126,6 +129,15 @@ private List buildRequestTags() { Config.get().getGlobalTags().entrySet().stream() .map(entry -> entry.getKey() + ":" + entry.getValue()) .collect(Collectors.toList()); + GitInfo gitInfo = GitInfoProvider.INSTANCE.getGitInfo(); + String repositoryURL = gitInfo.getRepositoryURL(); + if (repositoryURL != null) { + tags.add(Tags.GIT_REPOSITORY_URL + ":" + repositoryURL); + } + String sha = gitInfo.getCommit().getSha(); + if (sha != null) { + tags.add(Tags.GIT_COMMIT_SHA + ":" + sha); + } tags.addAll( Arrays.asList( "env:" + this.env, diff --git a/remote-config/src/main/java/datadog/remoteconfig/tuf/RemoteConfigRequest.java b/remote-config/src/main/java/datadog/remoteconfig/tuf/RemoteConfigRequest.java index c5e350c5d36..fe5f84f7d6b 100644 --- a/remote-config/src/main/java/datadog/remoteconfig/tuf/RemoteConfigRequest.java +++ b/remote-config/src/main/java/datadog/remoteconfig/tuf/RemoteConfigRequest.java @@ -207,6 +207,10 @@ public String getServiceEnv() { public String getServiceVersion() { return this.serviceVersion; } + + public List getTags() { + return tags; + } } private static class AgentInfo { diff --git a/remote-config/src/test/groovy/datadog/remoteconfig/PollerRequestFactoryTest.groovy b/remote-config/src/test/groovy/datadog/remoteconfig/PollerRequestFactoryTest.groovy index 911ffb805bf..28bfb47ee5c 100644 --- a/remote-config/src/test/groovy/datadog/remoteconfig/PollerRequestFactoryTest.groovy +++ b/remote-config/src/test/groovy/datadog/remoteconfig/PollerRequestFactoryTest.groovy @@ -1,6 +1,7 @@ package datadog.remoteconfig import datadog.remoteconfig.tuf.RemoteConfigRequest +import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.test.util.DDSpecification import datadog.trace.api.Config @@ -15,6 +16,7 @@ class PollerRequestFactoryTest extends DDSpecification { System.setProperty("dd.service", "Service Name") System.setProperty("dd.env", "PROD") System.setProperty("dd.tags", "version:1.0.0-SNAPSHOT") + System.setProperty("dd.trace.global.tags", Tags.GIT_REPOSITORY_URL+":https://github.com/DataDog/dd-trace-java,"+Tags.GIT_COMMIT_SHA + ":1234") rebuildConfig() PollerRequestFactory factory = new PollerRequestFactory(Config.get(), TRACER_VERSION, CONTAINER_ID, INVALID_REMOTE_CONFIG_URL, null) @@ -25,5 +27,8 @@ class PollerRequestFactoryTest extends DDSpecification { request.client.tracerInfo.serviceName == "service_name" request.client.tracerInfo.serviceEnv == "prod" request.client.tracerInfo.serviceVersion == "1.0.0-snapshot" + request.client.tracerInfo.tags.contains("env:PROD") + request.client.tracerInfo.tags.contains(Tags.GIT_REPOSITORY_URL + ":https://github.com/DataDog/dd-trace-java") + request.client.tracerInfo.tags.contains(Tags.GIT_COMMIT_SHA + ":1234") } } diff --git a/settings.gradle b/settings.gradle index d5917b55a68..252c151feed 100644 --- a/settings.gradle +++ b/settings.gradle @@ -141,6 +141,7 @@ include ':dd-smoke-tests:vertx-3.9-resteasy' include ':dd-smoke-tests:vertx-4.2' include ':dd-smoke-tests:wildfly' include ':dd-smoke-tests:appsec' +include ':dd-smoke-tests:appsec:spring-tomcat7' include ':dd-smoke-tests:appsec:springboot' include ':dd-smoke-tests:appsec:springboot-grpc' include ':dd-smoke-tests:appsec:springboot-security' @@ -174,6 +175,7 @@ include ':dd-java-agent:instrumentation:classloading:jsr14-testing' include ':dd-java-agent:instrumentation:classloading:osgi-testing' include ':dd-java-agent:instrumentation:classloading:tomcat-testing' include ':dd-java-agent:instrumentation:commons-codec-1' +include ':dd-java-agent:instrumentation:commons-fileupload' include ':dd-java-agent:instrumentation:commons-httpclient-2' include ':dd-java-agent:instrumentation:commons-lang-2' include ':dd-java-agent:instrumentation:commons-lang-3' @@ -205,6 +207,7 @@ include ':dd-java-agent:instrumentation:finatra-2.9' include ':dd-java-agent:instrumentation:freemarker' include ':dd-java-agent:instrumentation:glassfish' include ':dd-java-agent:instrumentation:google-http-client' +include ':dd-java-agent:instrumentation:google-pubsub' include ':dd-java-agent:instrumentation:graal:native-image' include ':dd-java-agent:instrumentation:gradle' include ':dd-java-agent:instrumentation:graphql-java-14.0' @@ -379,6 +382,7 @@ include ':dd-java-agent:instrumentation:sparkjava-2.3' include ':dd-java-agent:instrumentation:spray-1.3' include ':dd-java-agent:instrumentation:spring-beans' include ':dd-java-agent:instrumentation:spring-cloud-zuul-2' +include ':dd-java-agent:instrumentation:spring-core' include ':dd-java-agent:instrumentation:spring-data-1.8' include ':dd-java-agent:instrumentation:spring-jms-3.1' include ':dd-java-agent:instrumentation:spring-messaging-4' diff --git a/telemetry/build.gradle b/telemetry/build.gradle index 022babe8d0e..23098ec1968 100644 --- a/telemetry/build.gradle +++ b/telemetry/build.gradle @@ -29,6 +29,7 @@ dependencies { compileOnly project(':dd-java-agent:agent-tooling') testImplementation project(':dd-java-agent:agent-tooling') + testImplementation project(':dd-java-agent:agent-logging') compileOnly project(':communication') testImplementation project(':communication') diff --git a/telemetry/src/main/java/datadog/telemetry/TelemetryClient.java b/telemetry/src/main/java/datadog/telemetry/TelemetryClient.java index 3e218a0f6b7..be0321fdd9b 100644 --- a/telemetry/src/main/java/datadog/telemetry/TelemetryClient.java +++ b/telemetry/src/main/java/datadog/telemetry/TelemetryClient.java @@ -25,7 +25,7 @@ public static TelemetryClient buildAgentClient(OkHttpClient okHttpClient, HttpUr public static TelemetryClient buildIntakeClient(String site, long timeoutMillis, String apiKey) { if (apiKey == null) { - log.warn("Cannot create Telemetry Intake because API_KEY unspecified."); + log.debug("Cannot create Telemetry Intake because DD_API_KEY unspecified."); return null; } diff --git a/telemetry/src/main/java/datadog/telemetry/TelemetryService.java b/telemetry/src/main/java/datadog/telemetry/TelemetryService.java index 2e2bffadb44..eea53e7b7c2 100644 --- a/telemetry/src/main/java/datadog/telemetry/TelemetryService.java +++ b/telemetry/src/main/java/datadog/telemetry/TelemetryService.java @@ -102,7 +102,6 @@ public boolean addMetric(Metric metric) { } public boolean addLogMessage(LogMessage message) { - // TODO doesn't seem to be used return this.logMessages.offer(message); } diff --git a/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java b/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java index e249239f5bc..5f4b8282768 100644 --- a/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java +++ b/telemetry/src/main/java/datadog/telemetry/TelemetrySystem.java @@ -6,6 +6,7 @@ import datadog.telemetry.dependency.DependencyPeriodicAction; import datadog.telemetry.dependency.DependencyService; import datadog.telemetry.integration.IntegrationPeriodicAction; +import datadog.telemetry.log.LogPeriodicAction; import datadog.telemetry.metric.CoreMetricsPeriodicAction; import datadog.telemetry.metric.IastMetricPeriodicAction; import datadog.telemetry.metric.WafMetricPeriodicAction; @@ -55,6 +56,10 @@ static Thread createTelemetryRunnable( if (null != dependencyService) { actions.add(new DependencyPeriodicAction(dependencyService)); } + if (Config.get().isTelemetryLogCollectionEnabled()) { + actions.add(new LogPeriodicAction()); + log.debug("Telemetry log collection enabled"); + } TelemetryRunnable telemetryRunnable = new TelemetryRunnable(telemetryService, actions); return AgentThreadFactory.newAgentThread( diff --git a/telemetry/src/main/java/datadog/telemetry/api/LogMessage.java b/telemetry/src/main/java/datadog/telemetry/api/LogMessage.java index 7a4b91a213d..a2d717e8b87 100644 --- a/telemetry/src/main/java/datadog/telemetry/api/LogMessage.java +++ b/telemetry/src/main/java/datadog/telemetry/api/LogMessage.java @@ -5,7 +5,7 @@ public class LogMessage { private LogMessageLevel level; private String tags; private String stackTrace; - private Integer tracerTime; + private Long tracerTime; public String getMessage() { return message; @@ -43,11 +43,11 @@ public LogMessage stackTrace(String stackTrace) { return this; } - public Integer getTracerTime() { + public Long getTracerTime() { return tracerTime; } - public LogMessage tracerTime(Integer tracerTime) { + public LogMessage tracerTime(Long tracerTime) { this.tracerTime = tracerTime; return this; } diff --git a/telemetry/src/main/java/datadog/telemetry/api/LogMessageLevel.java b/telemetry/src/main/java/datadog/telemetry/api/LogMessageLevel.java index 2d75928faa5..5cb9ae30c27 100644 --- a/telemetry/src/main/java/datadog/telemetry/api/LogMessageLevel.java +++ b/telemetry/src/main/java/datadog/telemetry/api/LogMessageLevel.java @@ -1,18 +1,23 @@ package datadog.telemetry.api; -public enum LogMessageLevel { - ERROR("ERROR"), - WARN("WARN"), - DEBUG("DEBUG"); - - private final String value; +import javax.annotation.Nullable; - LogMessageLevel(String value) { - this.value = value; - } +public enum LogMessageLevel { + ERROR, + WARN, + DEBUG; - @Override - public String toString() { - return value; + @Nullable + public static LogMessageLevel fromString(String value) { + switch (value) { + case "ERROR": + return ERROR; + case "WARN": + return WARN; + case "DEBUG": + return DEBUG; + default: + return null; + } } } diff --git a/telemetry/src/main/java/datadog/telemetry/log/LogPeriodicAction.java b/telemetry/src/main/java/datadog/telemetry/log/LogPeriodicAction.java new file mode 100644 index 00000000000..248aea3429d --- /dev/null +++ b/telemetry/src/main/java/datadog/telemetry/log/LogPeriodicAction.java @@ -0,0 +1,81 @@ +package datadog.telemetry.log; + +import datadog.telemetry.TelemetryRunnable; +import datadog.telemetry.TelemetryService; +import datadog.telemetry.api.LogMessage; +import datadog.telemetry.api.LogMessageLevel; +import datadog.trace.api.LogCollector; +import datadog.trace.util.stacktrace.StackUtils; + +public class LogPeriodicAction implements TelemetryRunnable.TelemetryPeriodicAction { + + /** + * The current list of packages passed in is small, but if it kept growing and this did become a + * performance issue then we could consider using ClassNameTrie instead (ie. use the builder to + * create the trie and store it as a constant in LogPeriodicAction to be passed in here and used + * as a filter) + */ + static final String[] PACKAGE_LIST = {"datadog.", "com.datadog.", "java.", "javax.", "jakarta."}; + + private static final String RET = "\r\n"; + private static final String UNKNOWN = ""; + + @Override + public void doIteration(TelemetryService service) { + for (LogCollector.RawLogMessage rawLogMsg : LogCollector.get().drain()) { + + LogMessage logMessage = + new LogMessage().message(rawLogMsg.message()).tracerTime(rawLogMsg.timestamp); + + if (rawLogMsg.logLevel != null) { + logMessage.level(LogMessageLevel.fromString(rawLogMsg.logLevel)); + } + + if (rawLogMsg.throwable != null) { + logMessage.stackTrace(renderStackTrace(rawLogMsg.throwable)); + } + + service.addLogMessage(logMessage); + } + } + + private static String renderStackTrace(Throwable t) { + StringBuilder stackTrace = new StringBuilder(); + + String name = t.getClass().getCanonicalName(); + if (name == null || name.isEmpty()) { + stackTrace.append(UNKNOWN); + } else { + stackTrace.append(name); + } + + if (isDataDogCode(t)) { + String msg = t.getMessage(); + stackTrace.append(": "); + if (msg == null || msg.isEmpty()) { + stackTrace.append(UNKNOWN); + } else { + stackTrace.append(msg); + } + } + stackTrace.append(RET); + + Throwable filtered = StackUtils.filterPackagesIn(t, PACKAGE_LIST); + for (StackTraceElement stackTraceElement : filtered.getStackTrace()) { + stackTrace.append(" at ").append(stackTraceElement).append(RET); + } + return stackTrace.toString(); + } + + private static boolean isDataDogCode(Throwable t) { + StackTraceElement[] stackTrace = t.getStackTrace(); + if (stackTrace == null || stackTrace.length == 0) { + return false; + } + String cn = stackTrace[0].getClassName(); + if (cn.isEmpty()) { + return false; + } + return cn.startsWith("datadog.") || cn.startsWith("com.datadog."); + } +} diff --git a/telemetry/src/test/groovy/datadog/telemetry/log/LogFilteringTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/log/LogFilteringTest.groovy new file mode 100644 index 00000000000..612a765a140 --- /dev/null +++ b/telemetry/src/test/groovy/datadog/telemetry/log/LogFilteringTest.groovy @@ -0,0 +1,69 @@ +package datadog.telemetry.log + +import datadog.telemetry.TelemetryService +import datadog.telemetry.api.LogMessage +import datadog.trace.api.LogCollector +import datadog.trace.test.util.DDSpecification +import org.slf4j.Logger +import datadog.slf4j.impl.StaticLoggerBinder + +class LogFilteringTest extends DDSpecification { + Logger logger + TelemetryService telemetryService = Mock() + LogPeriodicAction periodicAction = new LogPeriodicAction() + + @Override + void setup(){ + injectSysConfig("dd.instrumentation.telemetry.debug", "true") + injectSysConfig("dd.telemetry.log-collection.enabled", "true") + logger = StaticLoggerBinder.getSingleton().getLoggerFactory().getLogger("pepe") + LogCollector.get().drain() + } + + void 'stack trace filtering for non datadog exceptions'(){ + setup: + logger.error("Debug message", new Exception("Exception message")) + + when: + periodicAction.doIteration(telemetryService) + + then: + 1 * telemetryService.addLogMessage( { LogMessage logMessage -> + logMessage.getMessage() == 'Debug message' + String[] stackTraceLines = logMessage.stackTrace.split("\r\n") + + stackTraceLines[0] == "java.lang.Exception" + ExceptionHelper.isDataDogOrJava(stackTraceLines[1]) + ExceptionHelper.isDataDogOrJava(stackTraceLines[2]) + ExceptionHelper.isDataDogOrJava(stackTraceLines[3]) + ExceptionHelper.isDataDogOrJava(stackTraceLines[4]) + } ) + 0 * _._ + } + + void 'stack trace filtering for datadog exceptions'(){ + setup: + try { + ExceptionHelper.throwExceptionFromDatadogCode("Exception Message") + } + catch (Exception e){ + logger.error("Debug message", e) + } + + when: + periodicAction.doIteration(telemetryService) + + then: + 1 * telemetryService.addLogMessage( { LogMessage logMessage -> + logMessage.getMessage() == 'Debug message' + String[] stackTraceLines = logMessage.stackTrace.split("\r\n") + + stackTraceLines[0] == "java.lang.Exception: Exception Message" + ExceptionHelper.isDataDogOrJava(stackTraceLines[1]) + ExceptionHelper.isDataDogOrJava(stackTraceLines[2]) + ExceptionHelper.isDataDogOrJava(stackTraceLines[3]) + ExceptionHelper.isDataDogOrJava(stackTraceLines[4]) + } ) + 0 * _._ + } +} diff --git a/telemetry/src/test/groovy/datadog/telemetry/log/LogPeriodicActionTest.groovy b/telemetry/src/test/groovy/datadog/telemetry/log/LogPeriodicActionTest.groovy new file mode 100644 index 00000000000..373105826cc --- /dev/null +++ b/telemetry/src/test/groovy/datadog/telemetry/log/LogPeriodicActionTest.groovy @@ -0,0 +1,59 @@ +package datadog.telemetry.log + +import datadog.telemetry.TelemetryService +import datadog.telemetry.api.LogMessage +import datadog.telemetry.api.LogMessageLevel +import datadog.trace.api.LogCollector +import datadog.trace.test.util.DDSpecification + +class LogPeriodicActionTest extends DDSpecification { + LogPeriodicAction periodicAction = new LogPeriodicAction() + TelemetryService telemetryService = Mock() + + @Override + void setup(){ + injectSysConfig("dd.instrumentation.telemetry.debug", "true") + LogCollector.get().drain() + } + + @Override + void cleanup(){ + LogCollector.get().drain() + } + + void 'push exception into the telemetry service'() { + setup: + try { + ExceptionHelper.throwExceptionFromDatadogCode(null) + } catch (Exception e) { + LogCollector.get().addLogMessage(LogMessageLevel.ERROR.toString(), "test", e) + } + + when: + periodicAction.doIteration(telemetryService) + + then: + 1 * telemetryService.addLogMessage( { LogMessage logMessage -> + logMessage.getMessage() == 'test' + } ) + 0 * _._ + } + + void 'push exception (without stacktrace) into the telemetry service'() { + setup: + try { + ExceptionHelper.throwExceptionFromDatadogCodeWithoutStacktrace(null) + } catch (Exception e) { + LogCollector.get().addLogMessage(LogMessageLevel.ERROR.toString(), "test", e) + } + + when: + periodicAction.doIteration(telemetryService) + + then: + 1 * telemetryService.addLogMessage( { LogMessage logMessage -> + logMessage.getMessage() == 'test' + } ) + 0 * _._ + } +} diff --git a/telemetry/src/test/java/datadog/telemetry/log/ExceptionHelper.java b/telemetry/src/test/java/datadog/telemetry/log/ExceptionHelper.java new file mode 100644 index 00000000000..75a213a5792 --- /dev/null +++ b/telemetry/src/test/java/datadog/telemetry/log/ExceptionHelper.java @@ -0,0 +1,32 @@ +package datadog.telemetry.log; + +class ExceptionHelper { + + static class MutableException extends Exception { + public MutableException(String message) { + super(message, null, true, true); + } + } + + static void throwExceptionFromDatadogCode(String message) throws Exception { + throw new Exception(message); + } + + static void throwExceptionFromDatadogCodeWithoutStacktrace(String message) throws Exception { + Exception ex = new MutableException(message); + ex.setStackTrace(new StackTraceElement[0]); + throw ex; + } + + static boolean isDataDogOrJava(String stackLine) { + int slashPosition = stackLine.indexOf('/'); + int startOfLine = (-1 != slashPosition ? slashPosition : stackLine.indexOf(" at ") + 3); + String callSpec = stackLine.substring(startOfLine + 1, stackLine.length()); + for (String prefix : LogPeriodicAction.PACKAGE_LIST) { + if (callSpec.startsWith(prefix)) { + return true; + } + } + return false; + } +} diff --git a/telemetry/src/test/resources/telemetry_intake_emu.py b/telemetry/src/test/resources/telemetry_intake_emu.py index 0769a108ddd..eb33f9ba1f0 100644 --- a/telemetry/src/test/resources/telemetry_intake_emu.py +++ b/telemetry/src/test/resources/telemetry_intake_emu.py @@ -12,7 +12,8 @@ 'app-integrations-change', 'app-closing', 'app-heartbeat', - 'generate-metrics' + 'generate-metrics', + 'logs' ] def print_line(): @@ -34,13 +35,25 @@ def do_POST(self): self.send_response(HTTPStatus.ACCEPTED) self.end_headers() - hdr = self.headers.get('Content-Length') - if hdr is not None: - content_len = int(hdr) - body = str(self.rfile.read(content_len), 'utf-8') - print(body) - #print(f'\033[0;33m{body}\033[0m') - #self.wfile.write(b'Hello world') + + if 'Content-Length' in self.headers: + content_len = int(self.headers.get('Content-Length')) + body = str(self.rfile.read(content_len), 'utf-8') + print(body) + elif 'chunked' in self.headers.get("Transfer-Encoding", ""): + while True: + line = self.rfile.readline().strip() + chunk_length = int(line, 16) + if chunk_length != 0: + chunk = str(self.rfile.read(chunk_length), 'utf-8') + print(chunk) + + self.rfile.readline() + + if chunk_length == 0: + break + + def log_message(self, format, *args): return