Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into feature/support-ben…
Browse files Browse the repository at this point in the history
…chmark-otel-in-buildkite

* upstream/main:
  Process otel benchmark (#3384)
  Update tests for Jedis 5 (#3382)
  added entry to community plugins (#3383)
  added capturing s3 otel attributes from lamba S3Event (#3364)
  Bump version.log4j from 2.12.4 to 2.21.0 (#3366)
  Fix aws sdk instrumentation (#3373)
  • Loading branch information
v1v committed Oct 26, 2023
2 parents 5c35ae1 + d49065d commit 04041aa
Show file tree
Hide file tree
Showing 19 changed files with 508 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.asciidoc
Expand Up @@ -34,6 +34,8 @@ Use subheadings with the "=====" level for adding notes for unreleased changes:
[float]
===== Features
* Added protection against invalid timestamps provided by manual instrumentation - {pull}3363[#3363]
* Added support for AWS SDK 2.21 - {pull}3373[#3373]
* Capture bucket and object key to Lambda transaction as OTel attributes - `aws.s3.bueckt`, `aws.s3.key` - {pull}3364[#3364]
[float]
===== Bug fixes
Expand Down
@@ -0,0 +1,236 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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.
*/
package co.elastic.apm.agent.benchmark;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarFile;

public class ProcessOtelBenchmarkResults {

private final ArrayNode bechmarkResultJson;
private final String resultFilePath;
private final String elasticVersion;
private final String otelVersion;
private final ObjectMapper objectMapper;

private ProcessOtelBenchmarkResults(OtelBenchParser parser, String resultFilePath, String elasticVersion, String otelVersion) throws IOException {
this.resultFilePath = resultFilePath;
this.elasticVersion = elasticVersion;
this.otelVersion = otelVersion;
objectMapper = new ObjectMapper();
bechmarkResultJson = parser.createTree(objectMapper);
}

/**
* Example file contents for args 0:
* <p>
* <pre>
* Performing startup warming phase for 60 seconds...
* Starting disposable JFR warmup recording...
* Stopping disposable JFR warmup recording...
* Warmup complete.
* ----------------------------------------------------------
* Run at Tue Oct 24 16:11:38 UTC 2023
* release : compares no agent, latest stable, and latest snapshot agents
* 5 users, 5000 iterations
* ----------------------------------------------------------
* Agent : none latest snapshot elastic-latest elastic-async elastic-snapshot
* Run duration : 00:00:57 00:01:07 00:01:08 00:01:01 00:01:03 00:01:00
* Avg. CPU (user) % : 0.3620707 0.4131534 0.40864184 0.39786905 0.42508292 0.3923088
* Max. CPU (user) % : 0.5321782 0.57107234 0.5597015 0.5860349 0.58168316 0.54613465
* Avg. mch tot cpu % : 0.94261116 0.95221955 0.93572134 0.9339143 0.9495207 0.93751615
* Startup time (ms) : 11378 12870 12890 12861 8686 12868
* Total allocated MB : 15069.28 20168.60 20155.76 16018.64 15854.65 15904.63
* Min heap used (MB) : 74.39 123.06 115.00 139.10 114.14 108.85
* Max heap used (MB) : 430.09 414.73 391.81 543.44 474.40 387.21
* Thread switch rate : 54890.133 54588.465 54918.758 54230.484 53694.992 53908.035
* GC time (ms) : 417 606 669 920 406 520
* GC pause time (ms) : 423 606 669 920 406 520
* Req. mean (ms) : 4.21 4.97 5.01 4.51 4.67 4.40
* Req. p95 (ms) : 10.66 12.96 13.06 11.74 11.96 11.29
* Iter. mean (ms) : 55.19 64.99 65.60 58.89 61.06 57.74
* Iter. p95 (ms) : 85.51 99.71 100.96 89.89 95.43 88.39
* Net read avg (bps) : 13733443.00 12234844.00 12082882.00 12154885.00 10863340.00 11873964.00
* Net write avg (bps) : 18365140.00 66959119.00 66142391.00 16239026.00 14507576.00 15864209.00
* Peak threads : 40 54 53 48 48 48
* </pre>
*
*
* @param args 0 path to the test results (normally `build/reports/tests/test/classes/io.opentelemetry.OverheadTests.html`)
* 1 path to output file to be created by this class (eg `output.json`)
* 2 elastic version used in the test (eg `1.43.0`)
* 3 path to otel "latest" jar file used in the test (normally `opentelemetry-javaagent.jar`)
* @throws Exception io and parsing errors
*/
public static void main(String[] args) throws Exception {
OtelBenchParser parser = new OtelBenchParser();
try (BufferedReader rdr = new BufferedReader(new FileReader(args[0]))) {
String line;
while ((line = rdr.readLine()) != null) {
parser.parse(line);
}
}
new ProcessOtelBenchmarkResults(parser, args[1], args[2], getVersionFromJar(args[3])).process();
}

private void process() throws IOException {
final JsonNode meta = bechmarkResultJson.objectNode()
.put("os_name", System.getProperty("os.name"))
.put("os_version", System.getProperty("os.version"))
.put("jdk_version", System.getProperty("java.version"))
.put("elastic_version", elasticVersion)
.put("otel_version", otelVersion)
.put("executed_at", Instant.now().toString());
for (JsonNode benchmark : bechmarkResultJson) {
((ObjectNode) benchmark).put("@timestamp", System.currentTimeMillis());
((ObjectNode) benchmark).set("meta", meta);
}
writeBulkFile(this.resultFilePath);
}

private static String getVersionFromJar(String jarPath) throws IOException {
try (JarFile jar = new JarFile(jarPath)) {
return jar.getManifest().getMainAttributes().getValue("Implementation-Version");
}
}

private void writeBulkFile(String resultFilePath) throws IOException {
final File file = new File(resultFilePath);
final FileWriter fileWriter = new FileWriter(file);
for (JsonNode benchmark : bechmarkResultJson) {
fileWriter.append("{ \"index\" : { \"_index\" : \"microbenchmarks\" } }\n");
fileWriter.append(objectMapper.writer().writeValueAsString(benchmark));
fileWriter.append("\n");
}
fileWriter.close();
}

public static class OtelBenchParser {

private boolean inResults;
private String runAtComment;
private String releaseComment;
private String configComment;
private String[] agentNames;
private String[] runDurations;
private final List<ResultValues> results = new ArrayList<>();

public ArrayNode createTree(ObjectMapper objectMapper) {
ArrayNode rootNode = objectMapper.createArrayNode();
for (int i = 0; i < agentNames.length; i++) {
ObjectNode benchmarkNode = rootNode.objectNode();
String agentName = agentNames[i].toLowerCase().contains("elastic") ? agentNames[i] : "otel-"+agentNames[i] ;
if (agentNames[i].equals("none")) {
agentName = "none";
}
benchmarkNode.put("agentName", agentName);
benchmarkNode.put("benchmark", "benchmarkJavaApmWithOtel."+agentName);
for (ResultValues values: results) {
benchmarkNode.put(values.getJsonTitle(), values.get(i));
}

rootNode.add(benchmarkNode);
}
return rootNode;
}

public void parse(String result) throws ParseException {
if (inResults) {
if (runAtComment == null) {
runAtComment = result.strip();
} else if (releaseComment == null) {
releaseComment = result.strip();
} else if (configComment == null) {
configComment = result.strip();
} else if (result.matches("\\s*\\-+\\s*")) {
//skip
} else if (agentNames == null) {
String[] agentAndNames = result.split(":");
if (agentAndNames[0].trim().equals("Agent")) {
agentNames = agentAndNames[1].trim().split("\\s+");
} else {
throw new ParseException("Expecting 'Agent : none latest ....' but got: "+result);
}
} else if (runDurations == null) {
String[] durations = result.split("\\s+:\\s+");
if (durations[0].trim().equals("Run duration")) {
runDurations = durations[1].trim().split("\\s+");
} else {
throw new ParseException("Expecting 'Run duration : nn:nn:nn nn:nn:nn ....' but got: "+result);
}
} else if (result.matches("\\s*\\<.*")) {
inResults = false;
} else {
results.add(new ResultValues(result, agentNames.length));
}
} else if (result.matches("\\s*\\-+\\s*")) {
inResults = true;
} else {
System.out.println("X "+result);
}
}

public static class ResultValues {
String valuesTitle;
double[] values;
public ResultValues(String resultLine, int expectedValuesCount) throws ParseException {
String[] titleAndResults = resultLine.split(":");
if (titleAndResults.length != 2) {
throw new ParseException("Expected '<title> : <value1 value2 ...>' but got: "+resultLine);
}
valuesTitle = titleAndResults[0].trim();
String[] resultValues = titleAndResults[1].trim().split("\\s+");
if (resultValues.length != expectedValuesCount) {
throw new ParseException("Expected "+expectedValuesCount+" values but got "+resultValues.length +" for line: "+resultLine);
}
values = new double[expectedValuesCount];
for (int i = 0; i < resultValues.length; i++) {
try {
values[i] = Double.parseDouble(resultValues[i]);
} catch (NumberFormatException e) {
throw new ParseException("Expected but didn't get numeric float value at result index "+i+" in results: "+resultLine);
}
}
}

public String getJsonTitle() {
return valuesTitle.toLowerCase().replaceAll("[^\\w ]","").trim().replace(' ','_');
}

public double get(int i) {
return values[i];
}
}

public static class ParseException extends Exception {
public ParseException(String string) {
super(string);
}

}
}
}
2 changes: 1 addition & 1 deletion apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-2-plugin/pom.xml
Expand Up @@ -13,7 +13,7 @@

<properties>
<apm-agent-parent.base.dir>${project.basedir}/../../..</apm-agent-parent.base.dir>
<version.aws.sdk>2.20.157</version.aws.sdk>
<version.aws.sdk>2.21.1</version.aws.sdk>
<version.aws.jms>2.0.0</version.aws.jms>
<!-- AWS SDK v2 is compiled for Java 8 -->
<maven.compiler.target>8</maven.compiler.target>
Expand Down
Expand Up @@ -22,6 +22,8 @@
import co.elastic.apm.agent.awssdk.v2.helper.S3Helper;
import co.elastic.apm.agent.awssdk.v2.helper.SQSHelper;
import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;
import co.elastic.apm.agent.sdk.logging.Logger;
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
import co.elastic.apm.agent.tracer.GlobalTracer;
import co.elastic.apm.agent.tracer.Span;
import co.elastic.apm.agent.tracer.Tracer;
Expand All @@ -35,6 +37,7 @@
import software.amazon.awssdk.core.client.config.SdkClientOption;
import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
import software.amazon.awssdk.core.http.ExecutionContext;
import software.amazon.awssdk.core.internal.handler.BaseAsyncClientHandler;
import software.amazon.awssdk.core.internal.http.TransformingAsyncResponseHandler;

import java.net.URI;
Expand Down Expand Up @@ -70,12 +73,21 @@ public Collection<String> getInstrumentationGroupNames() {
@SuppressWarnings({"unchecked", "rawtypes"})
public static class AdviceClass {

private static final Logger logger = LoggerFactory.getLogger(AdviceClass.class);

@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
@Advice.AssignReturned.ToArguments(@Advice.AssignReturned.ToArguments.ToArgument(value = 2))
public static TransformingAsyncResponseHandler<?> enterDoExecute(@Advice.Argument(value = 0) ClientExecutionParams clientExecutionParams,
@Advice.Argument(value = 1) ExecutionContext executionContext,
@Advice.Argument(value = 2) TransformingAsyncResponseHandler<?> responseHandler,
@Advice.FieldValue("clientConfiguration") SdkClientConfiguration clientConfiguration) {
@Advice.This BaseAsyncClientHandler handler) {

SdkClientConfiguration clientConfiguration = ClientHandlerConfigInstrumentation.AdviceClass.getConfig(handler);
if(clientConfiguration == null) {
logger.warn("Not tracing AWS request due to being unable to resolve the configuration");
return responseHandler;
}

String awsService = executionContext.executionAttributes().getAttribute(AwsSignerExecutionAttribute.SERVICE_NAME);
SdkRequest sdkRequest = clientExecutionParams.getInput();
URI uri = clientConfiguration.option(SdkClientOption.ENDPOINT);
Expand Down
Expand Up @@ -24,6 +24,8 @@
import co.elastic.apm.agent.awssdk.v2.helper.sqs.wrapper.MessageListWrapper;
import co.elastic.apm.agent.common.JvmRuntimeInfo;
import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;
import co.elastic.apm.agent.sdk.logging.Logger;
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
import co.elastic.apm.agent.tracer.GlobalTracer;
import co.elastic.apm.agent.tracer.Outcome;
import co.elastic.apm.agent.tracer.Span;
Expand All @@ -39,6 +41,8 @@
import software.amazon.awssdk.core.client.config.SdkClientOption;
import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
import software.amazon.awssdk.core.http.ExecutionContext;
import software.amazon.awssdk.core.internal.handler.BaseAsyncClientHandler;
import software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler;

import javax.annotation.Nullable;
import java.net.URI;
Expand Down Expand Up @@ -81,11 +85,20 @@ public Collection<String> getInstrumentationGroupNames() {
@SuppressWarnings("rawtypes")
public static class AdviceClass {

private static final Logger logger = LoggerFactory.getLogger(AdviceClass.class);

@Nullable
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static Object enterDoExecute(@Advice.Argument(value = 0) ClientExecutionParams clientExecutionParams,
@Advice.Argument(value = 1) ExecutionContext executionContext,
@Advice.FieldValue("clientConfiguration") SdkClientConfiguration clientConfiguration) {
@Advice.This BaseSyncClientHandler handler) {

SdkClientConfiguration clientConfiguration = ClientHandlerConfigInstrumentation.AdviceClass.getConfig(handler);
if(clientConfiguration == null) {
logger.warn("Not tracing AWS request due to being unable to resolve the configuration");
return null;
}

String awsService = executionContext.executionAttributes().getAttribute(AwsSignerExecutionAttribute.SERVICE_NAME);
SdkRequest sdkRequest = clientExecutionParams.getInput();
URI uri = clientConfiguration.option(SdkClientOption.ENDPOINT);
Expand Down

0 comments on commit 04041aa

Please sign in to comment.