diff --git a/.ci/es-mapping.json b/.ci/es-mapping.json index a5af793f38..eff5ae94d0 100644 --- a/.ci/es-mapping.json +++ b/.ci/es-mapping.json @@ -16,17 +16,37 @@ } } }, + { + "primary_metric_long": { + "match_mapping_type": "long", + "path_match": "primaryMetric.*", + "mapping": { + "type": "double", + "index": false + } + } + }, + { + "secondary_metric_long": { + "match_mapping_type": "long", + "path_match": "secondaryMetrics.*", + "mapping": { + "type": "double", + "index": false + } + } + }, { "unindexed_doubles": { "match_mapping_type": "double", "mapping": { - "type": "float", + "type": "double", "index": false } } }, { - "unindexed_doubles": { + "strings": { "match_mapping_type": "string", "mapping": { "type": "keyword" diff --git a/.ci/postprocess.py b/.ci/postprocess.py deleted file mode 100755 index 1faa4be73e..0000000000 --- a/.ci/postprocess.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function - -import argparse -import collections -import json -import os -import platform -import re -import subprocess -import time - - -def cpu_model(): - cmd = "lscpu | grep \"Model name\" | awk '{for(i=3;i<=NF;i++){printf \"%s \", $i}; printf \"\\n\"}'" - try: - return subprocess.check_output(cmd, shell=True).strip() - except subprocess.CalledProcessError: - return "unknown" - - -def os_name(): - return platform.system() - - -def os_version(): - return platform.release() - - -def jdk_version(): - cmd = "java -version 2>&1 | grep \"java version\" | awk '{ print substr($3, 2, length($3) - 2) }'" - return subprocess.check_output(cmd, shell=True).strip() - - -def src_revision(): - cmd = "git -C %s rev-parse --short HEAD" % os.getenv("WORKSPACE") - try: - return subprocess.check_output(cmd, shell=True).strip() - except subprocess.CalledProcessError: - return "unknown" - - -def commit_message(): - cmd = "git -C %s log --format=%%s -n 1 HEAD" % os.getenv("WORKSPACE") - try: - return subprocess.check_output(cmd, shell=True).strip() - except subprocess.CalledProcessError: - return "unknown" - - -def flatten_multi_valued_param(k, v): - if "|" in v: - keys = [key[0].lower() + key[1:] for key in re.findall(r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)', k)] - values = [val.strip() for val in v.split("|")] - - res = collections.OrderedDict() - for kk, vv in zip(keys, values): - res[kk] = vv - return res - else: - return {k: v} - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--input") - parser.add_argument("--output") - parser.add_argument("--timestamp", type=int) - - args = parser.parse_args() - benchmarks = json.loads(open(args.input).read()) - meta = { - # 'cpu_model': cpu_model(), - 'os_name': os_name(), - 'os_version': os_version(), - 'jdk_version': jdk_version(), - 'revision': src_revision(), - 'commit_message': commit_message(), - 'executed_at': int(time.time()) - } - with open(args.output, "w") as out: - for benchmark in benchmarks: - benchmark['@timestamp'] = args.timestamp - benchmark['meta'] = meta - deleteRawData(benchmark['primaryMetric']) - for metricName in benchmark['secondaryMetrics']: - deleteRawData(benchmark['secondaryMetrics'][metricName]) - print('{ "index" : { "_index" : "microbenchmarks", "_type" : "_doc" } }', file=out) - json.dump(benchmark, out) - print('', file=out) - - -def deleteRawData(dict): - if 'rawData' in dict: - del dict['rawData'] - if 'rawDataHistogram' in dict: - del dict['rawDataHistogram'] - - -if __name__ == "__main__": - main() diff --git a/.ci/run-benchmarks.sh b/.ci/run-benchmarks.sh index e223ca8074..13a355cb96 100755 --- a/.ci/run-benchmarks.sh +++ b/.ci/run-benchmarks.sh @@ -74,11 +74,13 @@ function benchmark() { java -jar ${WORKSPACE}/apm-agent-benchmarks/target/benchmarks.jar ".*ContinuousBenchmark" -prof gc -rf json -rff ~/${RESULT_FILE} cd ~ + # 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} mv "${RESULT_FILE}.clean" ${RESULT_FILE} - ${MICRO_BENCHMARK_HOME}/postprocess.py --input=${RESULT_FILE} --output=${BULK_UPLOAD_FILE} --timestamp=${COMMIT_UNIX} + + java -cp ${WORKSPACE}/apm-agent-benchmarks/target/benchmarks.jar co.elastic.apm.benchmark.PostProcessBenchmarkResults ${RESULT_FILE} ${BULK_UPLOAD_FILE} ${COMMIT_UNIX} setCloudCredentials curl --user ${CLOUD_USERNAME}:${CLOUD_PASSWORD} -XPOST 'https://1ec92c339f616ca43771bff669cc419c.europe-west3.gcp.cloud.es.io:9243/_bulk' -H 'Content-Type: application/json' --data-binary @${BULK_UPLOAD_FILE} unset CLOUD_USERNAME diff --git a/apm-agent-benchmarks/src/main/java/co/elastic/apm/benchmark/PostProcessBenchmarkResults.java b/apm-agent-benchmarks/src/main/java/co/elastic/apm/benchmark/PostProcessBenchmarkResults.java new file mode 100644 index 0000000000..616353707b --- /dev/null +++ b/apm-agent-benchmarks/src/main/java/co/elastic/apm/benchmark/PostProcessBenchmarkResults.java @@ -0,0 +1,154 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 the original author or authors + * %% + * 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.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.File; +import java.io.FileWriter; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class PostProcessBenchmarkResults { + + private final ArrayNode jmhResultJson; + private final String resultFilePath; + private final long timestamp; + private ObjectMapper objectMapper; + + private PostProcessBenchmarkResults(String jmhResultJsonPath, String resultFilePath, long timestamp) throws IOException { + this.resultFilePath = resultFilePath; + this.timestamp = timestamp; + objectMapper = new ObjectMapper(); + jmhResultJson = (ArrayNode) objectMapper.readTree(new File(jmhResultJsonPath)); + } + + public static void main(String[] args) throws Exception { + final long timestamp; + if (args.length > 2) { + timestamp = Long.parseLong(args[2]); + } else { + timestamp = System.currentTimeMillis(); + } + new PostProcessBenchmarkResults(args[0], args[1], timestamp).process(); + } + + private void process() throws IOException { + final JsonNode meta = jmhResultJson.objectNode() + .put("cpu_model", execCmd("lscpu | grep \"Model name\" | awk '{for(i=3;i<=NF;i++){printf \"%s \", $i}; printf \"\\n\"}'")) + .put("os_name", System.getProperty("os.name")) + .put("os_version", System.getProperty("os.version")) + .put("jdk_version", System.getProperty("java.version")) + .put("revision", execCmd(String.format("git -C %s rev-parse --short HEAD", System.getenv("WORKSPACE")))) + .put("commit_message", execCmd(String.format("git -C %s log --format=%%s -n 1 HEAD", System.getenv("WORKSPACE")))) + .put("executed_at", Instant.now().toString()); + for (JsonNode benchmark : jmhResultJson) { + final String benchmarkName = benchmark.get("benchmark").textValue(); + ((ObjectNode) benchmark).put("benchmark", benchmarkName.substring(benchmarkName.lastIndexOf('.') + 1)); + ((ObjectNode) benchmark).put("@timestamp", timestamp); + ((ObjectNode) benchmark).set("meta", meta); + + final ObjectNode primaryMetric = (ObjectNode) benchmark.get("primaryMetric"); + removeRawData(primaryMetric); + removePercentileSecondaryMetrics((ObjectNode) benchmark.get("secondaryMetrics")); + for (JsonNode secondaryMetric : benchmark.get("secondaryMetrics")) { + removeRawData((ObjectNode) secondaryMetric); + } + } + jmhResultJson.add(subtractBenchmarkResults(getBenchmarkByName("benchmarkWithApm"), getBenchmarkByName("benchmarkWithoutApm"))); + writeBulkFile(this.resultFilePath); + } + + private static String execCmd(String cmd) { + try { + java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\n"); + return s.hasNext() ? s.next() : null; + } catch (Exception e) { + return null; + } + } + + private void writeBulkFile(String resultFilePath) throws IOException { + final File file = new File(resultFilePath); + final FileWriter fileWriter = new FileWriter(file); + for (JsonNode benchmark : jmhResultJson) { + fileWriter.append("{ \"index\" : { \"_index\" : \"microbenchmarks\", \"_type\" : \"_doc\" } }\n"); + fileWriter.append(objectMapper.writer().writeValueAsString(benchmark)); + fileWriter.append("\n"); + } + fileWriter.close(); + } + + private void removeRawData(ObjectNode node) { + for (Iterator it = node.fieldNames(); it.hasNext(); ) { + final String fieldName = it.next(); + if (fieldName.startsWith("raw")) { + node.remove(fieldName); + } + } + } + + private void removePercentileSecondaryMetrics(ObjectNode secondaryMetrics) { + List fieldNamesToRemove = new ArrayList<>(); + for (Iterator it = secondaryMetrics.fieldNames(); it.hasNext(); ) { + final String fieldName = it.next(); + if (fieldName.contains("p1.") || fieldName.contains("p0.")) { + fieldNamesToRemove.add(fieldName); + } + } + secondaryMetrics.remove(fieldNamesToRemove); + } + + private ObjectNode subtractBenchmarkResults(ObjectNode benchmark, ObjectNode benchmarkBaseline) { + final ObjectNode result = benchmark.deepCopy(); + result.put("benchmark", result.get("benchmark").textValue() + ".delta"); + subtract((ObjectNode) result.get("primaryMetric"), (ObjectNode) benchmarkBaseline.get("primaryMetric")); + subtract((ObjectNode) result.get("secondaryMetrics"), (ObjectNode) benchmarkBaseline.get("secondaryMetrics")); + return result; + } + + private void subtract(ObjectNode benchmark1, ObjectNode benchmark2) { + for (Iterator it = benchmark1.fieldNames(); it.hasNext(); ) { + final String fieldName = it.next(); + final JsonNode jsonNode1 = benchmark1.get(fieldName); + final JsonNode jsonNode2 = benchmark2.get(fieldName); + if (jsonNode1.isObject() && jsonNode2 != null && jsonNode2.isObject()) { + subtract((ObjectNode) jsonNode1, (ObjectNode) jsonNode2); + } else if (jsonNode1.isNumber() && jsonNode2 != null) { + benchmark1.put(fieldName, jsonNode1.decimalValue().subtract(jsonNode2.decimalValue()).doubleValue()); + } + } + } + + private ObjectNode getBenchmarkByName(String benchmarkName) { + for (JsonNode benchmark : jmhResultJson) { + if (benchmark.get("benchmark").textValue().equals(benchmarkName)) { + return (ObjectNode) benchmark; + } + } + return null; + } +}