From 93d1e42c714a8aa7ae10c3cad49eafd887106e70 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 13 Dec 2018 16:14:11 +0100 Subject: [PATCH] Merge remote-tracking branch 'origin/master' into application-metrics --- .gitignore | 1 + CHANGELOG.md | 4 + Jenkinsfile | 296 ++++++++++++++++++ .../co/elastic/apm/api/AbstractSpanImpl.java | 73 +++++ .../java/co/elastic/apm/api/NoopSpan.java | 13 +- .../co/elastic/apm/api/NoopTransaction.java | 19 +- .../main/java/co/elastic/apm/api/Span.java | 10 +- .../java/co/elastic/apm/api/SpanImpl.java | 73 +---- .../java/co/elastic/apm/api/Transaction.java | 14 +- .../co/elastic/apm/api/TransactionImpl.java | 31 +- .../ElasticApmContinuousBenchmark.java | 1 + .../benchmark/MockHttpServletRequest.java | 4 +- .../apm/agent/impl/context/Request.java | 10 + .../apm/agent/impl/context/Response.java | 12 + .../elastic/apm/agent/impl/package-info.java | 23 -- .../serialize/DslJsonSerializerTest.java | 5 +- ....java => AbstractSpanInstrumentation.java} | 36 +-- .../plugin/api/LegacySpanInstrumentation.java | 194 ++++++++++++ .../api/TransactionInstrumentation.java | 2 + ...ic.apm.agent.bci.ElasticApmInstrumentation | 31 +- .../plugin/api/SpanInstrumentationTest.java | 9 + .../api/TransactionInstrumentationTest.java | 15 +- .../elastic/apm/agent/jaxrs/package-info.java | 23 -- .../apm/agent/servlet/ServletApiAdvice.java | 41 ++- .../servlet/ServletTransactionHelper.java | 32 +- .../servlet/helper/ApmAsyncListener.java | 21 +- .../apm/agent/servlet/package-info.java | 23 -- .../apm/agent/servlet/ApmFilterTest.java | 43 +++ .../apm/agent/web/WebConfiguration.java | 16 + docs/configuration.asciidoc | 34 ++ docs/public-api.asciidoc | 16 +- pom.xml | 31 ++ scripts/jenkins/docs.sh | 9 + scripts/jenkins/run-benchmarks.sh | 91 ++++++ scripts/jenkins/smoketests-01.sh | 7 + scripts/jenkins/smoketests-02.sh | 7 + 36 files changed, 1053 insertions(+), 217 deletions(-) create mode 100644 Jenkinsfile create mode 100644 apm-agent-api/src/main/java/co/elastic/apm/api/AbstractSpanImpl.java delete mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/package-info.java rename apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/{SpanInstrumentation.java => AbstractSpanInstrumentation.java} (82%) create mode 100644 apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/LegacySpanInstrumentation.java delete mode 100644 apm-agent-plugins/apm-jaxrs-plugin/src/main/java/co/elastic/apm/agent/jaxrs/package-info.java delete mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/package-info.java create mode 100755 scripts/jenkins/docs.sh create mode 100755 scripts/jenkins/run-benchmarks.sh create mode 100755 scripts/jenkins/smoketests-01.sh create mode 100755 scripts/jenkins/smoketests-02.sh diff --git a/.gitignore b/.gitignore index b089b54080a..45dee8ef1a8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dependency-reduced-pom.xml *.iml .DS_Store html_docs +docs/html/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ccd3d201170..eee517a3bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # next ## Features + * Added `capture_headers` configuration option. + Set to `false` to disable capturing request and response headers. + This will reduce the allocation rate of the agent and can save you network bandwidth and disk space. + * Makes the API methods `addTag`, `setName`, `setType`, `setUser` and `setResult` fluent, so that calls can be chained. ## Bug Fixes diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000000..5818897500d --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,296 @@ +#!/usr/bin/env groovy + +pipeline { + agent none + environment { + BASE_DIR="src/github.com/elastic/apm-agent-java" + } + options { + timeout(time: 1, unit: 'HOURS') + buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20', daysToKeepStr: '30')) + timestamps() + ansiColor('xterm') + disableResume() + durabilityHint('PERFORMANCE_OPTIMIZED') + } + parameters { + string(name: 'MAVEN_CONFIG', defaultValue: "-B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn", description: "Additional maven options.") + booleanParam(name: 'Run_As_Master_Branch', defaultValue: false, description: 'Allow to run any steps on a PR, some steps normally only run on master branch.') + booleanParam(name: 'test_ci', defaultValue: true, description: 'Enable test') + booleanParam(name: 'smoketests_ci', defaultValue: true, description: 'Enable Smoke tests') + booleanParam(name: 'bench_ci', defaultValue: true, description: 'Enable benchmarks') + booleanParam(name: 'doc_ci', defaultValue: true, description: 'Enable build documentation') + } + + stages { + stage('Initializing'){ + agent { label 'linux && immutable' } + options { skipDefaultCheckout() } + environment { + HOME = "${env.WORKSPACE}" + JAVA_HOME = "${env.HUDSON_HOME}/.java/java10" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + MAVEN_CONFIG = "${params.MAVEN_CONFIG}" + } + stages(){ + /** + Checkout the code and stash it, to use it on other stages. + */ + stage('Checkout') { + steps { + gitCheckout(basedir: "${BASE_DIR}") + stash allowEmpty: true, name: 'source', useDefaultExcludes: false + } + } + /** + Build on a linux environment. + */ + stage('build') { + steps { + withEnvWrapper() { + unstash 'source' + dir("${BASE_DIR}"){ + sh """#!/bin/bash + set -euxo pipefail + ./mvnw clean package -DskipTests=true -Dmaven.javadoc.skip=true + """ + } + stash allowEmpty: true, name: 'build', useDefaultExcludes: false + } + } + } + } + } + stage('Tests') { + failFast true + parallel { + /** + Run only unit test. + */ + stage('Unit Tests') { + agent { label 'linux && immutable' } + options { skipDefaultCheckout() } + environment { + HOME = "${env.WORKSPACE}" + JAVA_HOME = "${env.HUDSON_HOME}/.java/java10" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } + when { + beforeAgent true + expression { return params.test_ci } + } + steps { + withEnvWrapper() { + unstash 'build' + dir("${BASE_DIR}"){ + sh """#!/bin/bash + set -euxo pipefail + ./mvnw test + """ + } + } + } + post { + always { + junit(allowEmptyResults: true, + keepLongStdio: true, + testResults: "${BASE_DIR}/**/junit-*.xml,${BASE_DIR}/**/TEST-*.xml") + codecov(repo: 'apm-agent-java', basedir: "${BASE_DIR}") + } + } + } + /** + Run smoke tests for different servers and databases. + */ + stage('Smoke Tests 01') { + agent { label 'linux && immutable' } + options { skipDefaultCheckout() } + environment { + HOME = "${env.WORKSPACE}" + JAVA_HOME = "${env.HUDSON_HOME}/.java/java10" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } + when { + beforeAgent true + expression { return params.smoketests_ci } + } + steps { + withEnvWrapper() { + unstash 'build' + dir("${BASE_DIR}"){ + sh './scripts/jenkins/smoketests-01.sh' + } + } + } + post { + always { + junit(allowEmptyResults: true, + keepLongStdio: true, + testResults: "${BASE_DIR}/**/junit-*.xml,${BASE_DIR}/**/TEST-*.xml") + codecov(repo: 'apm-agent-java', basedir: "${BASE_DIR}") + } + } + } + /** + Run smoke tests for different servers and databases. + */ + stage('Smoke Tests 02') { + agent { label 'linux && immutable' } + options { skipDefaultCheckout() } + environment { + HOME = "${env.WORKSPACE}" + JAVA_HOME = "${env.HUDSON_HOME}/.java/java10" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } + when { + beforeAgent true + expression { return params.smoketests_ci } + } + steps { + withEnvWrapper() { + unstash 'build' + dir("${BASE_DIR}"){ + sh './scripts/jenkins/smoketests-02.sh' + } + } + } + post { + always { + junit(allowEmptyResults: true, + keepLongStdio: true, + testResults: "${BASE_DIR}/**/junit-*.xml,${BASE_DIR}/**/TEST-*.xml") + codecov(repo: 'apm-agent-java', basedir: "${BASE_DIR}") + } + } + } + /** + Run the benchmarks and store the results on ES. + The result JSON files are also archive into Jenkins. + */ + stage('Benchmarks') { + agent { label 'metal' } + options { skipDefaultCheckout() } + environment { + HOME = "${env.WORKSPACE}" + JAVA_HOME = "${env.HUDSON_HOME}/.java/java10" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + NO_BUILD = "true" + } + when { + beforeAgent true + allOf { + anyOf { + not { + changeRequest() + } + branch 'master' + branch "\\d+\\.\\d+" + branch "v\\d?" + tag "v\\d+\\.\\d+\\.\\d+*" + expression { return params.Run_As_Master_Branch } + } + expression { return params.bench_ci } + } + } + steps { + withEnvWrapper() { + unstash 'build' + dir("${BASE_DIR}"){ + script { + env.COMMIT_ISO_8601 = sh(script: 'git log -1 -s --format=%cI', returnStdout: true).trim() + env.NOW_ISO_8601 = sh(script: 'date -u "+%Y-%m-%dT%H%M%SZ"', returnStdout: true).trim() + env.RESULT_FILE = "apm-agent-benchmark-results-${env.COMMIT_ISO_8601}.json" + env.BULK_UPLOAD_FILE = "apm-agent-bulk-${env.NOW_ISO_8601}.json" + } + sh './scripts/jenkins/run-benchmarks.sh' + } + } + } + post { + always { + archiveArtifacts(allowEmptyArchive: true, + artifacts: "${BASE_DIR}/${RESULT_FILE}", + onlyIfSuccessful: false) + sendBenchmarks(file: "${BASE_DIR}/${BULK_UPLOAD_FILE}", index: "benchmark-java") + } + } + } + /** + Build javadoc files. + */ + stage('Javadoc') { + agent { label 'linux && immutable' } + options { skipDefaultCheckout() } + environment { + HOME = "${env.WORKSPACE}" + JAVA_HOME = "${env.HUDSON_HOME}/.java/java10" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + } + when { + beforeAgent true + expression { return params.doc_ci } + } + steps { + withEnvWrapper() { + unstash 'build' + dir("${BASE_DIR}"){ + sh """#!/bin/bash + set -euxo pipefail + ./mvnw compile javadoc:javadoc + """ + } + } + } + } + } + } + /** + Build the documentation. + */ + stage('Documentation') { + options { skipDefaultCheckout() } + environment { + HOME = "${env.WORKSPACE}" + JAVA_HOME = "${env.HUDSON_HOME}/.java/java10" + PATH = "${env.JAVA_HOME}/bin:${env.PATH}" + ELASTIC_DOCS = "${env.WORKSPACE}/elastic/docs" + } + when { + beforeAgent true + allOf { + branch 'master' + expression { return params.doc_ci } + } + } + steps { + withEnvWrapper() { + unstash 'source' + checkoutElasticDocsTools(basedir: "${ELASTIC_DOCS}") + dir("${BASE_DIR}"){ + sh './scripts/jenkins/docs.sh' + } + } + } + post{ + success { + tar(file: "doc-files.tgz", archive: true, dir: "html", pathPrefix: "${BASE_DIR}/docs") + } + } + } + } + post { + success { + echoColor(text: '[SUCCESS]', colorfg: 'green', colorbg: 'default') + } + aborted { + echoColor(text: '[ABORTED]', colorfg: 'magenta', colorbg: 'default') + } + failure { + echoColor(text: '[FAILURE]', colorfg: 'red', colorbg: 'default') + //step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: "${NOTIFY_TO}", sendToIndividuals: false]) + } + unstable { + echoColor(text: '[UNSTABLE]', colorfg: 'yellow', colorbg: 'default') + } + } +} diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/AbstractSpanImpl.java b/apm-agent-api/src/main/java/co/elastic/apm/api/AbstractSpanImpl.java new file mode 100644 index 00000000000..5635439e643 --- /dev/null +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/AbstractSpanImpl.java @@ -0,0 +1,73 @@ +package co.elastic.apm.api; + +import javax.annotation.Nonnull; + +public abstract class AbstractSpanImpl implements Span { + @Nonnull + // co.elastic.apm.impl.transaction.AbstractSpan + protected final Object span; + + AbstractSpanImpl(@Nonnull Object span) { + this.span = span; + } + + @Nonnull + @Override + public Span createSpan() { + Object span = doCreateSpan(); + return span != null ? new SpanImpl(span) : NoopSpan.INSTANCE; + } + + private Object doCreateSpan() { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$DoCreateSpanInstrumentation.doCreateSpan + return null; + } + + void doSetName(String name) { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$SetNameInstrumentation.doSetName + } + + void doSetType(String type) { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$SetTypeInstrumentation.doSetType + } + + void doAddTag(String key, String value) { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$AddTagInstrumentation.doAddTag + } + + @Override + public void end() { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$EndInstrumentation.end + } + + @Override + public void captureException(Throwable throwable) { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation.CaptureExceptionInstrumentation + } + + @Nonnull + @Override + public String getId() { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation.GetIdInstrumentation + return ""; + } + + @Nonnull + @Override + public String getTraceId() { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation.GetTraceIdInstrumentation + return ""; + } + + @Override + public Scope activate() { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation.ActivateInstrumentation + return new ScopeImpl(span); + } + + @Override + public boolean isSampled() { + // co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation.IsSampledInstrumentation + return false; + } +} diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/NoopSpan.java b/apm-agent-api/src/main/java/co/elastic/apm/api/NoopSpan.java index 848fe7b8fd2..2a6de073811 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/NoopSpan.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/NoopSpan.java @@ -24,19 +24,25 @@ enum NoopSpan implements Span { INSTANCE; + @Nonnull @Override - public void setName(String name) { + public Span setName(String name) { // noop + return this; } + @Nonnull @Override - public void setType(String type) { + public Span setType(String type) { // noop + return this; } + @Nonnull @Override - public void addTag(String key, String value) { + public Span addTag(String key, String value) { // noop + return this; } @Override @@ -66,6 +72,7 @@ public Scope activate() { return NoopScope.INSTANCE; } + @Nonnull @Override public boolean isSampled() { return false; diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java b/apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java index 6f20422a69a..6bdb28085d8 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/NoopTransaction.java @@ -25,29 +25,37 @@ enum NoopTransaction implements Transaction { INSTANCE; + @Nonnull @Override - public void setName(String name) { + public Transaction setName(String name) { // noop + return this; } + @Nonnull @Override - public void setType(String type) { + public Transaction setType(String type) { // noop + return this; } + @Nonnull @Override - public void addTag(String key, String value) { + public Transaction addTag(String key, String value) { // noop + return this; } @Override - public void setUser(String id, String email, String username) { + public Transaction setUser(String id, String email, String username) { // noop + return this; } @Override - public void setResult(String result) { + public Transaction setResult(String result) { // noop + return this; } @Override @@ -83,6 +91,7 @@ public Scope activate() { return NoopScope.INSTANCE; } + @Nonnull @Override public boolean isSampled() { return false; diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/Span.java b/apm-agent-api/src/main/java/co/elastic/apm/api/Span.java index b4ca1e1c805..d23ec0aaac6 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/Span.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/Span.java @@ -46,7 +46,8 @@ public interface Span { * * @param name the name of the span */ - void setName(String name); + @Nonnull + Span setName(String name); /** * Sets the type of span. @@ -61,7 +62,8 @@ public interface Span { * * @param type the type of the span */ - void setType(String type); + @Nonnull + Span setType(String type); /** * A flat mapping of user-defined tags with string values. @@ -76,7 +78,8 @@ public interface Span { * @param key The tag key. * @param value The tag value. */ - void addTag(String key, String value); + @Nonnull + Span addTag(String key, String value); /** * Start and return a new custom span as a child of this span. @@ -105,6 +108,7 @@ public interface Span { * * @return the started span, never {@code null} */ + @Nonnull Span createSpan(); /** diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/SpanImpl.java b/apm-agent-api/src/main/java/co/elastic/apm/api/SpanImpl.java index 28c92c62e59..0f68f54ed34 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/SpanImpl.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/SpanImpl.java @@ -22,83 +22,36 @@ import javax.annotation.Nonnull; /** - * If the agent is active, it injects the implementation from - * co.elastic.apm.agent.plugin.api.SpanInstrumentation - * into this class. + * If the agent is active, it injects the implementation into this class. *

* Otherwise, this class is a noop. *

*/ -class SpanImpl implements Span { - - @Nonnull - // co.elastic.apm.agent.impl.transaction.AbstractSpan - protected final Object span; +class SpanImpl extends AbstractSpanImpl { SpanImpl(@Nonnull Object span) { - this.span = span; - } - - @Override - public void setName(String name) { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation$SetNameInstrumentation.setName - } - - @Override - public void setType(String type) { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation$SetTypeInstrumentation.setType - } - - @Override - public void addTag(String key, String value) { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation$AddTagInstrumentation.addTag - } - - @Override - public Span createSpan() { - Object span = doCreateSpan(); - return span != null ? new SpanImpl(span) : NoopSpan.INSTANCE; - } - - private Object doCreateSpan() { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation$DoCreateSpanInstrumentation.doCreateSpan - return null; - } - - @Override - public void end() { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation$EndInstrumentation.end - } - - @Override - public void captureException(Throwable throwable) { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation.CaptureExceptionInstrumentation + super(span); } @Nonnull @Override - public String getId() { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation.GetIdInstrumentation - return ""; + public Span setName(String name) { + doSetName(name); + return this; } @Nonnull @Override - public String getTraceId() { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation.GetTraceIdInstrumentation - return ""; - } - - @Override - public Scope activate() { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation.ActivateInstrumentation - return new ScopeImpl(span); + public Span setType(String type) { + doSetType(type); + return this; } + @Nonnull @Override - public boolean isSampled() { - // co.elastic.apm.agent.plugin.api.SpanInstrumentation.IsSampledInstrumentation - return false; + public Span addTag(String key, String value) { + doAddTag(key, value); + return this; } } diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java b/apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java index 87f3e55c2f9..fc2d311c583 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/Transaction.java @@ -42,7 +42,8 @@ public interface Transaction extends Span { * * @param name A string describing name of the transaction. */ - void setName(String name); + @Nonnull + Transaction setName(String name); /** * The type of the transaction. @@ -54,7 +55,11 @@ public interface Transaction extends Span { * * @param type The type of the transaction. */ - void setType(String type); + @Nonnull + Transaction setType(String type); + + @Nonnull + Transaction addTag(String key, String value); /** * Call this to enrich collected performance data and errors with information about the user/client. @@ -70,7 +75,7 @@ public interface Transaction extends Span { * @param email The user's email address or {@code null}, if not applicable. * @param username The user's name or {@code null}, if not applicable. */ - void setUser(String id, String email, String username); + Transaction setUser(String id, String email, String username); /** * A string describing the result of the transaction. @@ -78,7 +83,7 @@ public interface Transaction extends Span { * * @param result a string describing the result of the transaction */ - void setResult(String result); + Transaction setResult(String result); /** * End tracking the transaction. @@ -111,6 +116,7 @@ public interface Transaction extends Span { * * @return the started span, never {@code null} */ + @Nonnull Span createSpan(); /** diff --git a/apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java b/apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java index 3f256675362..a9fe1449c7d 100644 --- a/apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java +++ b/apm-agent-api/src/main/java/co/elastic/apm/api/TransactionImpl.java @@ -29,20 +29,43 @@ * Otherwise, this class is a noop. *

*/ -class TransactionImpl extends SpanImpl implements Transaction { +class TransactionImpl extends AbstractSpanImpl implements Transaction { TransactionImpl(@Nonnull Object transaction) { super(transaction); } + @Nonnull @Override - public void setUser(String id, String email, String username) { - // co.elastic.apm.agent.plugin.api.TransactionInstrumentation$SetUserInstrumentation.setUser + public Transaction setName(String name) { + doSetName(name); + return this; } + @Nonnull @Override - public void setResult(String result) { + public Transaction setType(String type) { + doSetType(type); + return this; + } + @Nonnull + @Override + public Transaction addTag(String key, String value) { + doAddTag(key, value); + return this; + } + + @Override + public Transaction setUser(String id, String email, String username) { + // co.elastic.apm.agent.plugin.api.TransactionInstrumentation$SetUserInstrumentation.setUser + return this; + } + + @Override + public Transaction setResult(String result) { + // co.elastic.apm.agent.plugin.api.TransactionInstrumentation.SetResultInstrumentation + return this; } @Nonnull diff --git a/apm-agent-benchmarks/src/main/java/co/elastic/apm/agent/benchmark/ElasticApmContinuousBenchmark.java b/apm-agent-benchmarks/src/main/java/co/elastic/apm/agent/benchmark/ElasticApmContinuousBenchmark.java index 315e25a3cee..555911a23c0 100644 --- a/apm-agent-benchmarks/src/main/java/co/elastic/apm/agent/benchmark/ElasticApmContinuousBenchmark.java +++ b/apm-agent-benchmarks/src/main/java/co/elastic/apm/agent/benchmark/ElasticApmContinuousBenchmark.java @@ -131,6 +131,7 @@ public void setUp(Blackhole blackhole) { .add(CoreConfiguration.INSTRUMENT, Boolean.toString(apmEnabled)) .add(CoreConfiguration.ACTIVE, Boolean.toString(apmEnabled)) .add("api_request_size", "10mb") + .add("capture_headers", "false") .add("server_urls", "http://localhost:" + port)) .optionProviders(ServiceLoader.load(ConfigurationOptionProvider.class)) .build()) diff --git a/apm-agent-benchmarks/src/main/java/co/elastic/apm/agent/benchmark/MockHttpServletRequest.java b/apm-agent-benchmarks/src/main/java/co/elastic/apm/agent/benchmark/MockHttpServletRequest.java index dea20cef816..bd03c7fcac1 100644 --- a/apm-agent-benchmarks/src/main/java/co/elastic/apm/agent/benchmark/MockHttpServletRequest.java +++ b/apm-agent-benchmarks/src/main/java/co/elastic/apm/agent/benchmark/MockHttpServletRequest.java @@ -101,7 +101,7 @@ public Enumeration getHeaders(String name) { @Override public Enumeration getHeaderNames() { - return null; + return Collections.enumeration(headers.keySet()); } @Override @@ -277,7 +277,7 @@ public String[] getParameterValues(String name) { @Override public Map getParameterMap() { - return null; + return parameters; } @Override diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Request.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Request.java index 423f4559999..f5e692e03b9 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Request.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Request.java @@ -23,6 +23,7 @@ import co.elastic.apm.agent.util.PotentiallyMultiValuedMap; import javax.annotation.Nullable; +import java.util.Enumeration; /** @@ -119,6 +120,15 @@ public Request addHeader(String headerName, @Nullable String headerValue) { return this; } + public Request addHeader(String headerName, @Nullable Enumeration headerValues) { + if (headerValues != null) { + while (headerValues.hasMoreElements()) { + headers.add(headerName, headerValues.nextElement()); + } + } + return this; + } + /** * Should include any headers sent by the requester. */ diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Response.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Response.java index 599ada155f5..b76d899260a 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Response.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Response.java @@ -22,6 +22,9 @@ import co.elastic.apm.agent.objectpool.Recyclable; import co.elastic.apm.agent.util.PotentiallyMultiValuedMap; +import javax.annotation.Nullable; +import java.util.Collection; + public class Response implements Recyclable { /** @@ -65,6 +68,15 @@ public Response addHeader(String headerName, String headerValue) { return this; } + public Response addHeader(String headerName, @Nullable Collection headerValues) { + if (headerValues != null) { + for (String headerValue : headerValues) { + headers.add(headerName, headerValue); + } + } + return this; + } + /** * A mapping of HTTP headers of the response object */ diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/package-info.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/package-info.java deleted file mode 100644 index 64fe3984728..00000000000 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/*- - * #%L - * Elastic APM Java agent - * %% - * Copyright (C) 2018 Elastic and contributors - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -@NonnullApi -package co.elastic.apm.agent.impl; - -import co.elastic.apm.agent.annotation.NonnullApi; diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java index c35394b89fe..a6417e84bb4 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.Enumeration; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -90,12 +91,14 @@ void testLimitStringValueLength() throws IOException { @Test void testNullHeaders() throws IOException { Transaction transaction = new Transaction(mock(ElasticApmTracer.class)); - transaction.getContext().getRequest().addHeader("foo", null); + transaction.getContext().getRequest().addHeader("foo", (String) null); + transaction.getContext().getRequest().addHeader("baz", (Enumeration) null); transaction.getContext().getRequest().getHeaders().add("bar", null); JsonNode jsonNode = objectMapper.readTree(serializer.toJsonString(transaction)); System.out.println(jsonNode); // calling addHeader with a null value ignores the header assertThat(jsonNode.get("context").get("request").get("headers").get("foo")).isNull(); + assertThat(jsonNode.get("context").get("request").get("headers").get("baz")).isNull(); // should a null value sneak in, it should not break assertThat(jsonNode.get("context").get("request").get("headers").get("bar").isNull()).isTrue(); } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/SpanInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java similarity index 82% rename from apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/SpanInstrumentation.java rename to apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java index 5aca5fc3952..6e252e01001 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/SpanInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java @@ -33,17 +33,17 @@ /** * Injects the actual implementation of the public API class co.elastic.apm.api.SpanImpl. */ -public class SpanInstrumentation extends ApiInstrumentation { +public class AbstractSpanInstrumentation extends ApiInstrumentation { private final ElementMatcher methodMatcher; - public SpanInstrumentation(ElementMatcher methodMatcher) { + public AbstractSpanInstrumentation(ElementMatcher methodMatcher) { this.methodMatcher = methodMatcher; } @Override public ElementMatcher getTypeMatcher() { - return named("co.elastic.apm.api.SpanImpl"); + return named("co.elastic.apm.api.AbstractSpanImpl"); } @Override @@ -51,9 +51,9 @@ public ElementMatcher getMethodMatcher() { return methodMatcher; } - public static class SetNameInstrumentation extends SpanInstrumentation { + public static class SetNameInstrumentation extends AbstractSpanInstrumentation { public SetNameInstrumentation() { - super(named("setName")); + super(named("doSetName")); } @VisibleForAdvice @@ -64,9 +64,9 @@ public static void setName(@Advice.FieldValue(value = "span", typing = Assigner. } } - public static class SetTypeInstrumentation extends SpanInstrumentation { + public static class SetTypeInstrumentation extends AbstractSpanInstrumentation { public SetTypeInstrumentation() { - super(named("setType")); + super(named("doSetType")); } @VisibleForAdvice @@ -77,7 +77,7 @@ public static void setType(@Advice.FieldValue(value = "span", typing = Assigner. } } - public static class DoCreateSpanInstrumentation extends SpanInstrumentation { + public static class DoCreateSpanInstrumentation extends AbstractSpanInstrumentation { public DoCreateSpanInstrumentation() { super(named("doCreateSpan")); } @@ -90,7 +90,7 @@ public static void doCreateSpan(@Advice.FieldValue(value = "span", typing = Assi } } - public static class EndInstrumentation extends SpanInstrumentation { + public static class EndInstrumentation extends AbstractSpanInstrumentation { public EndInstrumentation() { super(named("end")); } @@ -103,7 +103,7 @@ public static void end(@Advice.FieldValue(value = "span", typing = Assigner.Typi } - public static class CaptureExceptionInstrumentation extends SpanInstrumentation { + public static class CaptureExceptionInstrumentation extends AbstractSpanInstrumentation { public CaptureExceptionInstrumentation() { super(named("captureException").and(takesArguments(Throwable.class))); } @@ -116,7 +116,7 @@ public static void doCreateSpan(@Advice.FieldValue(value = "span", typing = Assi } } - public static class GetIdInstrumentation extends SpanInstrumentation { + public static class GetIdInstrumentation extends AbstractSpanInstrumentation { public GetIdInstrumentation() { super(named("getId").and(takesArguments(0))); } @@ -124,14 +124,14 @@ public GetIdInstrumentation() { @VisibleForAdvice @Advice.OnMethodExit(suppress = Throwable.class) public static void getId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, - @Advice.Return(readOnly = false) String id) { + @Advice.Return(readOnly = false) String id) { if (tracer != null) { id = span.getTraceContext().getId().toString(); } } } - public static class GetTraceIdInstrumentation extends SpanInstrumentation { + public static class GetTraceIdInstrumentation extends AbstractSpanInstrumentation { public GetTraceIdInstrumentation() { super(named("getTraceId").and(takesArguments(0))); } @@ -139,16 +139,16 @@ public GetTraceIdInstrumentation() { @VisibleForAdvice @Advice.OnMethodExit(suppress = Throwable.class) public static void getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, - @Advice.Return(readOnly = false) String traceId) { + @Advice.Return(readOnly = false) String traceId) { if (tracer != null) { traceId = span.getTraceContext().getTraceId().toString(); } } } - public static class AddTagInstrumentation extends SpanInstrumentation { + public static class AddTagInstrumentation extends AbstractSpanInstrumentation { public AddTagInstrumentation() { - super(named("addTag")); + super(named("doAddTag")); } @VisibleForAdvice @@ -159,7 +159,7 @@ public static void addTag(@Advice.FieldValue(value = "span", typing = Assigner.T } } - public static class ActivateInstrumentation extends SpanInstrumentation { + public static class ActivateInstrumentation extends AbstractSpanInstrumentation { public ActivateInstrumentation() { super(named("activate")); } @@ -171,7 +171,7 @@ public static void addTag(@Advice.FieldValue(value = "span", typing = Assigner.T } } - public static class IsSampledInstrumentation extends SpanInstrumentation { + public static class IsSampledInstrumentation extends AbstractSpanInstrumentation { public IsSampledInstrumentation() { super(named("isSampled")); } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/LegacySpanInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/LegacySpanInstrumentation.java new file mode 100644 index 00000000000..69088d88910 --- /dev/null +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/LegacySpanInstrumentation.java @@ -0,0 +1,194 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package co.elastic.apm.agent.plugin.api; + +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.transaction.AbstractSpan; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; + +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +/** + * Injects the actual implementation of the public API class co.elastic.apm.api.SpanImpl. + *

+ * Used for older versions of the API, for example 1.1.0 where there was no AbstractSpanImpl + * + * @deprecated can be removed in version 3.0. + * Users should be able to update the agent to 2.0, without having to simultaneously update the API. + */ +@Deprecated +public class LegacySpanInstrumentation extends ApiInstrumentation { + + private final ElementMatcher methodMatcher; + + public LegacySpanInstrumentation(ElementMatcher methodMatcher) { + this.methodMatcher = methodMatcher; + } + + @Override + public ElementMatcher getTypeMatcher() { + return named("co.elastic.apm.api.SpanImpl").and(not(hasSuperType(named("co.elastic.apm.api.AbstractSpanImpl")))); + } + + @Override + public ElementMatcher getMethodMatcher() { + return methodMatcher; + } + + public static class SetNameInstrumentation extends LegacySpanInstrumentation { + public SetNameInstrumentation() { + super(named("setName")); + } + + @VisibleForAdvice + @Advice.OnMethodEnter + public static void setName(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, + @Advice.Argument(0) String name) { + span.setName(name); + } + } + + public static class SetTypeInstrumentation extends LegacySpanInstrumentation { + public SetTypeInstrumentation() { + super(named("setType")); + } + + @VisibleForAdvice + @Advice.OnMethodEnter + public static void setType(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, + @Advice.Argument(0) String type) { + span.withType(type); + } + } + + public static class DoCreateSpanInstrumentation extends LegacySpanInstrumentation { + public DoCreateSpanInstrumentation() { + super(named("doCreateSpan")); + } + + @VisibleForAdvice + @Advice.OnMethodExit + public static void doCreateSpan(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, + @Advice.Return(readOnly = false) Object result) { + result = span.createSpan(); + } + } + + public static class EndInstrumentation extends LegacySpanInstrumentation { + public EndInstrumentation() { + super(named("end")); + } + + @VisibleForAdvice + @Advice.OnMethodEnter + public static void end(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span) { + span.end(); + } + } + + + public static class CaptureExceptionInstrumentation extends LegacySpanInstrumentation { + public CaptureExceptionInstrumentation() { + super(named("captureException").and(takesArguments(Throwable.class))); + } + + @VisibleForAdvice + @Advice.OnMethodExit + public static void doCreateSpan(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, + @Advice.Argument(0) Throwable t) { + span.captureException(t); + } + } + + public static class GetIdInstrumentation extends LegacySpanInstrumentation { + public GetIdInstrumentation() { + super(named("getId").and(takesArguments(0))); + } + + @VisibleForAdvice + @Advice.OnMethodExit + public static void getId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, + @Advice.Return(readOnly = false) String id) { + if (tracer != null) { + id = span.getTraceContext().getId().toString(); + } + } + } + + public static class GetTraceIdInstrumentation extends LegacySpanInstrumentation { + public GetTraceIdInstrumentation() { + super(named("getTraceId").and(takesArguments(0))); + } + + @VisibleForAdvice + @Advice.OnMethodExit + public static void getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, + @Advice.Return(readOnly = false) String traceId) { + if (tracer != null) { + traceId = span.getTraceContext().getTraceId().toString(); + } + } + } + + public static class AddTagInstrumentation extends LegacySpanInstrumentation { + public AddTagInstrumentation() { + super(named("addTag")); + } + + @VisibleForAdvice + @Advice.OnMethodEnter + public static void addTag(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, + @Advice.Argument(0) String key, @Advice.Argument(1) String value) { + span.addTag(key, value); + } + } + + public static class ActivateInstrumentation extends LegacySpanInstrumentation { + public ActivateInstrumentation() { + super(named("activate")); + } + + @VisibleForAdvice + @Advice.OnMethodEnter + public static void addTag(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span) { + span.activate(); + } + } + + public static class IsSampledInstrumentation extends LegacySpanInstrumentation { + public IsSampledInstrumentation() { + super(named("isSampled")); + } + + @VisibleForAdvice + @Advice.OnMethodExit + public static void addTag(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan span, + @Advice.Return(readOnly = false) boolean sampled) { + sampled = span.isSampled(); + } + } +} diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java index f99836a255a..6d529b7972f 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java @@ -28,7 +28,9 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.matcher.ElementMatcher; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; /** * Injects the actual implementation of the public API class co.elastic.apm.api.TransactionImpl. diff --git a/apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation b/apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation index 01941e79d9b..07849012afb 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-api-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation @@ -5,17 +5,28 @@ co.elastic.apm.agent.plugin.api.ElasticApmApiInstrumentation$CaptureExceptionIns co.elastic.apm.agent.plugin.api.TransactionInstrumentation$SetUserInstrumentation co.elastic.apm.agent.plugin.api.TransactionInstrumentation$EnsureParentIdInstrumentation co.elastic.apm.agent.plugin.api.TransactionInstrumentation$SetResultInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$SetNameInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$SetTypeInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$DoCreateSpanInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$EndInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$CaptureExceptionInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$GetIdInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$GetTraceIdInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$AddTagInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$ActivateInstrumentation -co.elastic.apm.agent.plugin.api.SpanInstrumentation$IsSampledInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$SetNameInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$SetTypeInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$DoCreateSpanInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$EndInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$CaptureExceptionInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$GetIdInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$GetTraceIdInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$AddTagInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$ActivateInstrumentation +co.elastic.apm.agent.plugin.api.AbstractSpanInstrumentation$IsSampledInstrumentation co.elastic.apm.agent.plugin.api.CaptureExceptionInstrumentation co.elastic.apm.agent.plugin.api.ApiScopeInstrumentation co.elastic.apm.agent.plugin.api.CaptureTransactionInstrumentation co.elastic.apm.agent.plugin.api.CaptureSpanInstrumentation +# legacy +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$SetNameInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$SetTypeInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$DoCreateSpanInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$EndInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$CaptureExceptionInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$GetIdInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$GetTraceIdInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$AddTagInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$ActivateInstrumentation +co.elastic.apm.agent.plugin.api.LegacySpanInstrumentation$IsSampledInstrumentation diff --git a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/SpanInstrumentationTest.java b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/SpanInstrumentationTest.java index 51a753de7a6..cc7d3b36fd4 100644 --- a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/SpanInstrumentationTest.java +++ b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/SpanInstrumentationTest.java @@ -54,6 +54,15 @@ void testSetType() { assertThat(reporter.getFirstSpan().getType()).isEqualTo("foo"); } + @Test + void testChaining() { + span.setType("foo").setName("foo").addTag("foo", "bar"); + endSpan(); + assertThat(reporter.getFirstSpan().getName().toString()).isEqualTo("foo"); + assertThat(reporter.getFirstSpan().getType()).isEqualTo("foo"); + assertThat(reporter.getFirstSpan().getContext().getTags()).containsEntry("foo", "bar"); + } + private void endSpan() { span.end(); transaction.end(); diff --git a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentationTest.java b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentationTest.java index ecc65028413..7b4fb19861d 100644 --- a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentationTest.java +++ b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentationTest.java @@ -76,7 +76,20 @@ void testResult() { endTransaction(); assertThat(reporter.getFirstTransaction().getResult()).isEqualTo("foo"); } - + + @Test + void testChaining() { + transaction.setType("foo").setName("foo").addTag("foo", "bar").setUser("foo", "bar", "baz").setResult("foo"); + endTransaction(); + assertThat(reporter.getFirstTransaction().getName().toString()).isEqualTo("foo"); + assertThat(reporter.getFirstTransaction().getType()).isEqualTo("foo"); + assertThat(reporter.getFirstTransaction().getContext().getTags()).containsEntry("foo", "bar"); + assertThat(reporter.getFirstTransaction().getContext().getUser().getId()).isEqualTo("foo"); + assertThat(reporter.getFirstTransaction().getContext().getUser().getEmail()).isEqualTo("bar"); + assertThat(reporter.getFirstTransaction().getContext().getUser().getUsername()).isEqualTo("baz"); + assertThat(reporter.getFirstTransaction().getResult()).isEqualTo("foo"); + } + @Test public void createSpan() throws Exception { Span span = transaction.createSpan(); diff --git a/apm-agent-plugins/apm-jaxrs-plugin/src/main/java/co/elastic/apm/agent/jaxrs/package-info.java b/apm-agent-plugins/apm-jaxrs-plugin/src/main/java/co/elastic/apm/agent/jaxrs/package-info.java deleted file mode 100644 index bd5943e772b..00000000000 --- a/apm-agent-plugins/apm-jaxrs-plugin/src/main/java/co/elastic/apm/agent/jaxrs/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/*- - * #%L - * Elastic APM Java agent - * %% - * Copyright (C) 2018 Elastic and contributors - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -@NonnullApi -package co.elastic.apm.agent.jaxrs; - -import co.elastic.apm.agent.annotation.NonnullApi; diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java index 8a104fe0429..835b742a8a3 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java @@ -26,6 +26,7 @@ import co.elastic.apm.agent.impl.context.Response; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.web.WebConfiguration; import net.bytebuddy.asm.Advice; import javax.annotation.Nullable; @@ -38,6 +39,7 @@ import javax.servlet.http.HttpServletResponse; import java.security.Principal; import java.util.Enumeration; +import java.util.Map; /** * Only the methods annotated with {@link Advice.OnMethodEnter} and {@link Advice.OnMethodExit} may contain references to @@ -97,16 +99,18 @@ public static void onEnterServletService(@Advice.Argument(0) ServletRequest serv return; } final Request req = transaction.getContext().getRequest(); - if (transaction.isSampled() && request.getCookies() != null) { - for (Cookie cookie : request.getCookies()) { - req.addCookie(cookie.getName(), cookie.getValue()); + if (transaction.isSampled() && tracer.getConfig(WebConfiguration.class).isCaptureHeaders()) { + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + req.addCookie(cookie.getName(), cookie.getValue()); + } } - } - final Enumeration headerNames = request.getHeaderNames(); - if (headerNames != null) { - while (headerNames.hasMoreElements()) { - final String headerName = (String) headerNames.nextElement(); - req.addHeader(headerName, request.getHeader(headerName)); + final Enumeration headerNames = request.getHeaderNames(); + if (headerNames != null) { + while (headerNames.hasMoreElements()) { + final String headerName = (String) headerNames.nextElement(); + req.addHeader(headerName, request.getHeaders(headerName)); + } } } @@ -150,14 +154,25 @@ public static void onExitServletService(@Advice.Argument(0) ServletRequest servl transaction.deactivate(); } else { // this is not an async request, so we can end the transaction immediately - final Response resp = transaction.getContext().getResponse(); final HttpServletResponse response = (HttpServletResponse) servletResponse; - for (String headerName : response.getHeaderNames()) { - resp.addHeader(headerName, response.getHeader(headerName)); + if (transaction.isSampled() && tracer.getConfig(WebConfiguration.class).isCaptureHeaders()) { + final Response resp = transaction.getContext().getResponse(); + for (String headerName : response.getHeaderNames()) { + resp.addHeader(headerName, response.getHeaders(headerName)); + } + } + // request.getParameterMap() may allocate a new map, depending on the servlet container implementation + // so only call this method if necessary + final String contentTypeHeader = request.getHeader("Content-Type"); + final Map parameterMap; + if (transaction.isSampled() && servletTransactionHelper.captureParameters(request.getMethod(), contentTypeHeader)) { + parameterMap = request.getParameterMap(); + } else { + parameterMap = null; } request.removeAttribute(TRANSACTION_ATTRIBUTE); servletTransactionHelper.onAfter(transaction, t, response.isCommitted(), response.getStatus(), request.getMethod(), - request.getParameterMap(), request.getServletPath(), request.getPathInfo()); + parameterMap, request.getServletPath(), request.getPathInfo(), contentTypeHeader); } } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java index edce425e6a3..d716f3cc509 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java @@ -29,7 +29,6 @@ import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.matcher.WildcardMatcher; -import co.elastic.apm.agent.util.PotentiallyMultiValuedMap; import co.elastic.apm.agent.web.ClientIpUtils; import co.elastic.apm.agent.web.ResultUtil; import co.elastic.apm.agent.web.WebConfiguration; @@ -123,9 +122,9 @@ public static void setUsernameIfUnset(@Nullable String userName, TransactionCont @VisibleForAdvice public void onAfter(Transaction transaction, @Nullable Throwable exception, boolean committed, int status, String method, - Map parameterMap, String servletPath, @Nullable String pathInfo) { + @Nullable Map parameterMap, String servletPath, @Nullable String pathInfo, @Nullable String contentTypeHeader) { try { - fillRequestParameters(transaction, method, parameterMap); + fillRequestParameters(transaction, method, parameterMap, contentTypeHeader); if(exception != null && status == 200) { // Probably shouldn't be 200 but 5XX, but we are going to miss this... status = 500; @@ -174,17 +173,25 @@ void applyDefaultTransactionName(String method, String servletPath, @Nullable St * for example when the amount of query parameters is longer than the application server allows. * In that case, we rather not want that the agent looks like the cause for this. */ - private void fillRequestParameters(Transaction transaction, String method, Map parameterMap) { + private void fillRequestParameters(Transaction transaction, String method, @Nullable Map parameterMap, @Nullable String contentTypeHeader) { Request request = transaction.getContext().getRequest(); - if (hasBody(request.getHeaders(), method)) { - if (webConfiguration.getCaptureBody() != OFF) { - captureBody(request, parameterMap); + if (hasBody(contentTypeHeader, method)) { + if (webConfiguration.getCaptureBody() != OFF && parameterMap != null) { + captureBody(request, parameterMap, contentTypeHeader); } else { request.redactBody(); } } } + @VisibleForAdvice + public boolean captureParameters(String method, @Nullable String contentTypeHeader) { + return contentTypeHeader != null + && contentTypeHeader.startsWith("application/x-www-form-urlencoded") + && hasBody(contentTypeHeader, method) + && webConfiguration.getCaptureBody() != OFF; + } + private boolean isExcluded(String servletPath, String pathInfo, String requestURI, @Nullable String userAgentHeader) { final List ignoreUrls = webConfiguration.getIgnoreUrls(); boolean excludeUrl = WildcardMatcher.anyMatch(ignoreUrls, servletPath, pathInfo) != null; @@ -228,12 +235,11 @@ private void fillRequest(Request request, String protocol, String method, boolea fillFullUrl(request.getUrl(), scheme, serverPort, serverName, requestURI, queryString); } - private boolean hasBody(PotentiallyMultiValuedMap headers, String method) { - return METHODS_WITH_BODY.contains(method) && headers.containsIgnoreCase("Content-Type"); + private boolean hasBody(@Nullable String contentTypeHeader, String method) { + return METHODS_WITH_BODY.contains(method) && contentTypeHeader != null; } - private void captureBody(Request request, Map params) { - String contentTypeHeader = request.getHeaders().getFirst("Content-Type"); + private void captureBody(Request request, Map params, @Nullable String contentTypeHeader) { if (contentTypeHeader != null && contentTypeHeader.startsWith("application/x-www-form-urlencoded")) { for (Map.Entry param : params.entrySet()) { request.addFormUrlEncodedParameters(param.getKey(), param.getValue()); @@ -314,4 +320,8 @@ public static void setTransactionNameByServletClass(String method, @Nullable Cla transactionName.append(method); } } + + public boolean isCaptureHeaders() { + return webConfiguration.isCaptureHeaders(); + } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ApmAsyncListener.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ApmAsyncListener.java index 5b0abe05bbd..84178b199ee 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ApmAsyncListener.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ApmAsyncListener.java @@ -28,6 +28,7 @@ import javax.servlet.AsyncListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.Map; /** * Based on brave.servlet.ServletRuntime$TracingAsyncListener (under Apache license 2.0) @@ -84,12 +85,22 @@ private void endTransaction(AsyncEvent event) { HttpServletRequest request = (HttpServletRequest) event.getSuppliedRequest(); HttpServletResponse response = (HttpServletResponse) event.getSuppliedResponse(); final Response resp = transaction.getContext().getResponse(); - for (String headerName : response.getHeaderNames()) { - resp.addHeader(headerName, response.getHeader(headerName)); + if (transaction.isSampled() && servletTransactionHelper.isCaptureHeaders()) { + for (String headerName : response.getHeaderNames()) { + resp.addHeader(headerName, response.getHeaders(headerName)); + } + } + // request.getParameterMap() may allocate a new map, depending on the servlet container implementation + // so only call this method if necessary + final String contentTypeHeader = request.getHeader("Content-Type"); + final Map parameterMap; + if (transaction.isSampled() && servletTransactionHelper.captureParameters(request.getMethod(), contentTypeHeader)) { + parameterMap = request.getParameterMap(); + } else { + parameterMap = null; } - servletTransactionHelper.onAfter(transaction, event.getThrowable(), - response.isCommitted(), response.getStatus(), request.getMethod(), request.getParameterMap(), - request.getServletPath(), request.getPathInfo()); + response.isCommitted(), response.getStatus(), request.getMethod(), parameterMap, + request.getServletPath(), request.getPathInfo(), contentTypeHeader); } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/package-info.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/package-info.java deleted file mode 100644 index 013732c4364..00000000000 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/*- - * #%L - * Elastic APM Java agent - * %% - * Copyright (C) 2018 Elastic and contributors - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -@NonnullApi -package co.elastic.apm.agent.servlet; - -import co.elastic.apm.agent.annotation.NonnullApi; diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ApmFilterTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ApmFilterTest.java index 098f4410f7c..2bd8604695a 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ApmFilterTest.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/ApmFilterTest.java @@ -21,6 +21,8 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.configuration.CoreConfiguration; +import co.elastic.apm.agent.impl.context.Request; +import co.elastic.apm.agent.impl.context.Response; import co.elastic.apm.agent.impl.context.Url; import co.elastic.apm.agent.matcher.WildcardMatcher; import co.elastic.apm.agent.util.PotentiallyMultiValuedMap; @@ -39,6 +41,7 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -258,6 +261,46 @@ void captureTransactionNameManuallySetInFilter() throws IOException, ServletExce assertThat(reporter.getFirstTransaction().getName().toString()).isEqualTo("CustomName"); } + @Test + void testNoHeaderRecording() throws IOException, ServletException { + when(webConfiguration.isCaptureHeaders()).thenReturn(false); + filterChain = new MockFilterChain(new TestServlet()); + final MockHttpServletRequest get = new MockHttpServletRequest("GET", "/foo"); + get.addHeader("Elastic-Apm-Traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + get.setCookies(new Cookie("foo", "bar")); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + mockResponse.addHeader("foo", "bar"); + mockResponse.addHeader("bar", "baz"); + filterChain.doFilter(get, mockResponse); + assertThat(reporter.getTransactions()).hasSize(1); + assertThat(reporter.getFirstTransaction().getContext().getResponse().getHeaders().isEmpty()).isTrue(); + assertThat(reporter.getFirstTransaction().getContext().getRequest().getHeaders().isEmpty()).isTrue(); + assertThat(reporter.getFirstTransaction().getContext().getRequest().getCookies().isEmpty()).isTrue(); + assertThat(reporter.getFirstTransaction().getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); + assertThat(reporter.getFirstTransaction().getTraceContext().getParentId().toString()).isEqualTo("b9c7c989f97918e1"); + } + + @Test + void testAllHeaderRecording() throws IOException, ServletException { + when(webConfiguration.isCaptureHeaders()).thenReturn(true); + filterChain = new MockFilterChain(new TestServlet()); + final MockHttpServletRequest get = new MockHttpServletRequest("GET", "/foo"); + get.addHeader("foo", "bar"); + get.setCookies(new Cookie("foo", "bar")); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + mockResponse.addHeader("foo", "bar"); + mockResponse.addHeader("bar", "baz"); + filterChain.doFilter(get, mockResponse); + assertThat(reporter.getTransactions()).hasSize(1); + final Request request = reporter.getFirstTransaction().getContext().getRequest(); + assertThat(request.getHeaders().isEmpty()).isFalse(); + assertThat(request.getHeaders().get("foo")).isEqualTo("bar"); + assertThat(request.getCookies().get("foo")).isEqualTo("bar"); + final Response response = reporter.getFirstTransaction().getContext().getResponse(); + assertThat(response.getHeaders().get("foo")).isEqualTo("bar"); + assertThat(response.getHeaders().get("bar")).isEqualTo("baz"); + } + public static class TestServlet extends HttpServlet { } diff --git a/apm-agent-plugins/apm-web-plugin/src/main/java/co/elastic/apm/agent/web/WebConfiguration.java b/apm-agent-plugins/apm-web-plugin/src/main/java/co/elastic/apm/agent/web/WebConfiguration.java index 32847c11ddb..e74ef519872 100644 --- a/apm-agent-plugins/apm-web-plugin/src/main/java/co/elastic/apm/agent/web/WebConfiguration.java +++ b/apm-agent-plugins/apm-web-plugin/src/main/java/co/elastic/apm/agent/web/WebConfiguration.java @@ -35,6 +35,7 @@ public class WebConfiguration extends ConfigurationOptionProvider { private final ConfigurationOption captureBody = ConfigurationOption.enumOption(EventType.class) .key("capture_body") .configurationCategory(HTTP_CATEGORY) + .tags("performance") .description("For transactions that are HTTP requests, the Java agent can optionally capture the request body (e.g. POST " + "variables).\n" + "\n" + @@ -50,6 +51,17 @@ public class WebConfiguration extends ConfigurationOptionProvider { .dynamic(true) .buildWithDefault(EventType.OFF); + private final ConfigurationOption captureHeaders = ConfigurationOption.booleanOption() + .key("capture_headers") + .configurationCategory(HTTP_CATEGORY) + .tags("performance") + .description("If set to `true`,\n" + + "the agent will capture request and response headers, including cookies.\n" + + "\n" + + "NOTE: Setting this to `false` reduces network bandwidth, disk space and object allocations.") + .dynamic(true) + .buildWithDefault(true); + private final ConfigurationOption> ignoreUrls = ConfigurationOption .builder(new ListValueConverter<>(new WildcardMatcherValueConverter()), List.class) .key("ignore_urls") @@ -141,6 +153,10 @@ public List getUrlGroups() { return urlGroups.get(); } + public boolean isCaptureHeaders() { + return captureHeaders.get(); + } + public enum EventType { /** * Request bodies will never be reported diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index d2216c5de5e..6d5005b8f4c 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -330,6 +330,29 @@ Valid options: `off`, `errors`, `transactions`, `all` | `elastic.apm.capture_body` | `capture_body` | `ELASTIC_APM_CAPTURE_BODY` |============ +[float] +[[config-capture-headers]] +==== `capture_headers` + +If set to `true`, +the agent will capture request and response headers, including cookies. + +NOTE: Setting this to `false` reduces network bandwidth, disk space and object allocations. + + +[options="header"] +|============ +| Default | Type | Dynamic +| `true` | Boolean | true +|============ + + +[options="header"] +|============ +| Java System Properties | Property file | Environment +| `elastic.apm.capture_headers` | `capture_headers` | `ELASTIC_APM_CAPTURE_HEADERS` +|============ + [float] [[config-ignore-urls]] ==== `ignore_urls` @@ -979,6 +1002,17 @@ The default unit for this option is `ms` # # capture_body=OFF +# If set to `true`, +# the agent will capture request and response headers, including cookies. +# +# NOTE: Setting this to `false` reduces network bandwidth, disk space and object allocations. +# +# This setting can be changed at runtime +# Type: Boolean +# Default value: true +# +# capture_headers=true + # Used to restrict requests to certain URLs from being instrumented. # # This property should be set to an array containing one or more strings. diff --git a/docs/public-api.asciidoc b/docs/public-api.asciidoc index daf0db5a96b..7420a44279b 100644 --- a/docs/public-api.asciidoc +++ b/docs/public-api.asciidoc @@ -184,7 +184,7 @@ and the <> method. [float] [[api-set-name]] -==== `void setName(String name)` +==== `Transaction setName(String name)` Override the name of the current transaction. For supported frameworks, the transaction name is determined automatically, @@ -202,7 +202,7 @@ transaction.setName("My Transaction"); [float] [[api-transaction-set-type]] -==== `void setType(String type)` +==== `Transaction setType(String type)` Sets the type of the transaction. There’s a special type called `request`, which is used by the agent for the transactions automatically created when an incoming HTTP request is detected. @@ -218,7 +218,7 @@ transaction.setType(Transaction.TYPE_REQUEST); [float] [[api-transaction-add-tag]] -==== `void addTag(String key, String value)` +==== `Transaction addTag(String key, String value)` A flat mapping of user-defined tags with string values. Note: the tags are indexed in Elasticsearch so that they are searchable and aggregatable. By all means, @@ -236,7 +236,7 @@ transaction.setTag("foo", "bar"); [float] [[api-transaction-set-user]] -==== `void setUser(String id, String email, String username)` +==== `Transaction setUser(String id, String email, String username)` Call this to enrich collected performance data and errors with information about the user/client. This method can be called at any point during the request/response life cycle (i.e. while a transaction is active). The given context will be added to the active transaction. @@ -341,7 +341,7 @@ NOTE: Spans created via this method can not be retrieved by calling <> on how to get a reference of the current span. [float] [[api-span-set-name]] -==== `void setName(String name)` +==== `Span setName(String name)` Override the name of the current span. Example: @@ -425,7 +425,7 @@ span.setName("SELECT FROM customer"); [float] [[api-span-set-type]] -==== `void setType(String type)` +==== `Span setType(String type)` Sets the type of span. The type is a hierarchical string used to group similar spans together. For instance, all spans of MySQL queries are given the type `db.mysql.query`. @@ -438,7 +438,7 @@ the following are standardized across all Elastic APM agents: `app`, `db`, `cach [float] [[api-span-add-tag]] -==== `void addTag(String key, String value)` +==== `Span addTag(String key, String value)` A flat mapping of user-defined tags with string values. Note: the tags are indexed in Elasticsearch so that they are searchable and aggregatable. By all means, diff --git a/pom.xml b/pom.xml index 340cc255b50..24bcfab2767 100644 --- a/pom.xml +++ b/pom.xml @@ -114,6 +114,37 @@ 10 + + integration-test-only + + false + + + true + false + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${version.plugin.surefire} + + ${skip.integration.test} + + + + maven-surefire-plugin + ${version.plugin.surefire} + + true + false + ${skip.unit.test} + + + + + no-errorprone diff --git a/scripts/jenkins/docs.sh b/scripts/jenkins/docs.sh new file mode 100755 index 00000000000..e5df859d4e4 --- /dev/null +++ b/scripts/jenkins/docs.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -z "${ELASTIC_DOCS}" -o ! -d "${ELASTIC_DOCS}" ]; then + echo "ELASTIC_DOCS is not defined, it should point to a folder where you checkout https://github.com/elastic/docs.git." + echo "You also can define BUILD_DOCS_ARGS for aditional build options." + exit 1 +fi + +${ELASTIC_DOCS}/build_docs.pl --chunk=1 ${BUILD_DOCS_ARGS} --doc docs/index.asciidoc -out docs/html diff --git a/scripts/jenkins/run-benchmarks.sh b/scripts/jenkins/run-benchmarks.sh new file mode 100755 index 00000000000..822cdaee26a --- /dev/null +++ b/scripts/jenkins/run-benchmarks.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +set -exuo pipefail + +NOW_ISO_8601=${NOW_ISO_8601:-$(date -u "+%Y-%m-%dT%H%M%SZ")} +MAVEN_CONFIG="-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" + +echo $(pwd) + +function setUp() { + echo "Setting CPU frequency to base frequency" + + CPU_MODEL=$(lscpu | grep "Model name" | awk '{for(i=3;i<=NF;i++){printf "%s ", $i}; printf "\n"}') + if [ "${CPU_MODEL}" == "Intel(R) Xeon(R) CPU E3-1246 v3 @ 3.50GHz " ] + then + # could also use `nproc` + CORE_INDEX=7 + BASE_FREQ="3.5GHz" + elif [ "${CPU_MODEL}" == "Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz " ] + then + CORE_INDEX=7 + BASE_FREQ="3.4GHz" + elif [ "${CPU_MODEL}" == "Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz " ] + then + CORE_INDEX=7 + BASE_FREQ="3.6GHz" + else + >&2 echo "Cannot determine base frequency for CPU model [${CPU_MODEL}]. Please adjust the build script." + exit 1 + fi + MIN_FREQ=$(cpufreq-info -l -c 0 | awk '{print $1}') + # This is the frequency including Turbo Boost. See also http://ark.intel.com/products/80916/Intel-Xeon-Processor-E3-1246-v3-8M-Cache-3_50-GHz + MAX_FREQ=$(cpufreq-info -l -c 0 | awk '{print $2}') + + # set all CPUs to the base frequency + for (( cpu=0; cpu<=${CORE_INDEX}; cpu++ )) + do + sudo -n cpufreq-set -c ${cpu} --min ${BASE_FREQ} --max ${BASE_FREQ} + done + + # Build cgroups to isolate microbenchmarks and JVM threads + echo "Creating groups for OS and microbenchmarks" + # Isolate the OS to the first core + sudo -n cset set --set=/os --cpu=0-1 + sudo -n cset proc --move --fromset=/ --toset=/os + + # Isolate the microbenchmarks to all cores except the first two (first physical core) + # On a 4 core CPU with hyper threading, this would be 6 cores (3 physical cores) + sudo -n cset set --set=/benchmark --cpu=2-${CORE_INDEX} +} + +function benchmark() { + COMMIT_ISO_8601=$(git log -1 -s --format=%cI) + COMMIT_UNIX=$(git log -1 -s --format=%ct) + + [ -z "${NO_BUILD}" ] && ./mvnw clean package -DskipTests=true + + RESULT_FILE=apm-agent-benchmark-results-${COMMIT_ISO_8601}.json + BULK_UPLOAD_FILE=apm-agent-bulk-${NOW_ISO_8601}.json + + sudo -n cset proc --exec /benchmark -- \ + $JAVA_HOME/bin/java -jar apm-agent-benchmarks/target/benchmarks.jar ".*ContinuousBenchmark" \ + -prof gc \ + -prof co.elastic.apm.benchmark.profiler.ReporterProfiler \ + -rf json \ + -rff ${RESULT_FILE} + + # remove strange non unicode chars inserted by JMH; see org.openjdk.jmh.results.Defaults.PREFIX + tr -cd '\11\12\40-\176' < ${RESULT_FILE} > "${RESULT_FILE}.clean" + rm -f ${RESULT_FILE} ${BULK_UPLOAD_FILE} + mv "${RESULT_FILE}.clean" ${RESULT_FILE} + + $JAVA_HOME/bin/java -cp apm-agent-benchmarks/target/benchmarks.jar co.elastic.apm.benchmark.PostProcessBenchmarkResults ${RESULT_FILE} ${BULK_UPLOAD_FILE} ${COMMIT_UNIX} +} + +function tearDown() { + echo "Destroying cgroups" + sudo -n cset set --destroy /os + sudo -n cset set --destroy /benchmark + + echo "Setting normal frequency range" + for (( cpu=0; cpu<=${CORE_INDEX}; cpu++ )) + do + sudo -n cpufreq-set -c ${cpu} --min ${MIN_FREQ} --max ${MAX_FREQ} + done +} + +trap "tearDown" EXIT + +setUp +benchmark diff --git a/scripts/jenkins/smoketests-01.sh b/scripts/jenkins/smoketests-01.sh new file mode 100755 index 00000000000..75c3ce5e7a8 --- /dev/null +++ b/scripts/jenkins/smoketests-01.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +MOD=$(find apm-agent-plugins -maxdepth 1 -mindepth 1 -type d|grep -v "target"|tr "\n" ",") + +./mvnw -q -Dmaven.javadoc.skip=true -am -amd -pl ${MOD} -P integration-test-only verify diff --git a/scripts/jenkins/smoketests-02.sh b/scripts/jenkins/smoketests-02.sh new file mode 100755 index 00000000000..81171b1096c --- /dev/null +++ b/scripts/jenkins/smoketests-02.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +MOD=$(find integration-tests -maxdepth 1 -mindepth 1 -type d|grep -v "target"|tr "\n" ",") + +./mvnw -q -Dmaven.javadoc.skip=true -am -amd -pl ${MOD} -P integration-test-only verify