) 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 super MethodDescription> methodMatcher;
- public SpanInstrumentation(ElementMatcher super MethodDescription> methodMatcher) {
+ public AbstractSpanInstrumentation(ElementMatcher super MethodDescription> methodMatcher) {
this.methodMatcher = methodMatcher;
}
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return named("co.elastic.apm.api.SpanImpl");
+ return named("co.elastic.apm.api.AbstractSpanImpl");
}
@Override
@@ -51,9 +51,9 @@ public ElementMatcher super MethodDescription> 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 super MethodDescription> methodMatcher;
+
+ public LegacySpanInstrumentation(ElementMatcher super MethodDescription> methodMatcher) {
+ this.methodMatcher = methodMatcher;
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named("co.elastic.apm.api.SpanImpl").and(not(hasSuperType(named("co.elastic.apm.api.AbstractSpanImpl"))));
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> 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