mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 10:53:49 +00:00
Test OTLP output format (#1331)
This commit is contained in:
committed by
GitHub
parent
f627b3157b
commit
f79729167a
@@ -213,6 +213,13 @@ jobs:
|
||||
esac
|
||||
echo "jars_directory=jar_artifacts" >> $GITHUB_OUTPUT
|
||||
echo "release_directory=$(basename $(find . -type d -iname "async-profiler-*" ))" >> $GITHUB_OUTPUT
|
||||
- name: Download Protobuf Java runtime
|
||||
run: |
|
||||
mkdir -p test/deps
|
||||
cd test/deps
|
||||
curl -L -O "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/$PB_JAVA_VERSION/protobuf-java-$PB_JAVA_VERSION.jar"
|
||||
env:
|
||||
PB_JAVA_VERSION: "4.31.1"
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
mkdir -p build/jar
|
||||
|
||||
36
Makefile
36
Makefile
@@ -6,9 +6,10 @@ else ifneq ($(COMMIT_TAG),)
|
||||
PROFILER_VERSION := $(PROFILER_VERSION)-$(COMMIT_TAG)
|
||||
endif
|
||||
|
||||
TMP_DIR=/tmp
|
||||
COMMA=,
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)-$(ARCH_TAG)
|
||||
PACKAGE_DIR=/tmp/$(PACKAGE_NAME)
|
||||
PACKAGE_DIR=$(TMP_DIR)/$(PACKAGE_NAME)
|
||||
DEBUG_PACKAGE_NAME=$(PACKAGE_NAME)-debug
|
||||
DEBUG_PACKAGE_DIR=$(PACKAGE_DIR)-debug
|
||||
|
||||
@@ -52,6 +53,8 @@ JAVAC_OPTIONS=--release $(JAVA_TARGET) -Xlint:-options
|
||||
|
||||
TEST_LIB_DIR=build/test/lib
|
||||
TEST_BIN_DIR=build/test/bin
|
||||
TEST_DEPS_DIR=test/deps
|
||||
TEST_GEN_DIR=test/gen
|
||||
LOG_DIR=build/test/logs
|
||||
LOG_LEVEL=
|
||||
SKIP=
|
||||
@@ -64,7 +67,7 @@ RESOURCES := $(wildcard src/res/*)
|
||||
JAVA_HELPER_CLASSES := $(wildcard src/helper/one/profiler/*.class)
|
||||
API_SOURCES := $(wildcard src/api/one/profiler/*.java)
|
||||
CONVERTER_SOURCES := $(shell find src/converter -name '*.java')
|
||||
TEST_SOURCES := $(shell find test -name '*.java')
|
||||
TEST_SOURCES := $(shell find test -name '*.java' ! -path 'test/stubs/*')
|
||||
TESTS ?=
|
||||
CPP_TEST_SOURCES := test/native/testRunner.cpp $(shell find test/native -name '*Test.cpp')
|
||||
CPP_TEST_HEADER := test/native/testRunner.hpp
|
||||
@@ -258,7 +261,7 @@ test-cpp: build-test-cpp
|
||||
|
||||
test-java: build-test-java
|
||||
echo "Running tests against $(LIB_PROFILER)"
|
||||
$(JAVA) "-Djava.library.path=$(TEST_LIB_DIR)" $(TEST_FLAGS) -ea -cp "build/test.jar:build/jar/*:build/lib/*" one.profiler.test.Runner $(subst $(COMMA), ,$(TESTS))
|
||||
$(JAVA) "-Djava.library.path=$(TEST_LIB_DIR)" $(TEST_FLAGS) -ea -cp "build/$(TEST_JAR):build/jar/*:build/lib/*:$(TEST_DEPS_DIR)/*:$(TEST_GEN_DIR)/*" one.profiler.test.Runner $(subst $(COMMA), ,$(TESTS))
|
||||
|
||||
coverage: override FAT_BINARY=false
|
||||
coverage: clean-coverage
|
||||
@@ -269,12 +272,35 @@ coverage: clean-coverage
|
||||
|
||||
test: test-cpp test-java
|
||||
|
||||
build/$(TEST_JAR): $(TEST_SOURCES) build/$(CONVERTER_JAR)
|
||||
build/$(TEST_JAR): build/$(API_JAR) $(TEST_SOURCES) build/$(CONVERTER_JAR) $(TEST_DEPS_DIR)
|
||||
rm -rf build/test/classes
|
||||
mkdir -p build/test/classes
|
||||
$(JAVAC) -source $(JAVA_TARGET) -target $(JAVA_TARGET) -Xlint:-options -XDignore.symbol.file \
|
||||
-cp "build/jar/*:build/converter/*" -d build/test/classes $(TEST_SOURCES)
|
||||
-implicit:none \
|
||||
-cp "build/jar/*:$(TEST_DEPS_DIR)/*:$(TEST_GEN_DIR)/*:test/stubs" \
|
||||
-d build/test/classes \
|
||||
$(TEST_SOURCES)
|
||||
$(JAR) cf $@ -C build/test/classes .
|
||||
|
||||
update-otlp-classes-jar:
|
||||
@if [ -z "$(OTEL_PROTO_PATH)" ]; then \
|
||||
echo "'OTEL_PROTO_PATH' is empty"; \
|
||||
exit 1; \
|
||||
fi
|
||||
rm -rf $(TMP_DIR)/gen/java $(TMP_DIR)/build
|
||||
mkdir -p $(TMP_DIR)/gen/java $(TMP_DIR)/build $(TEST_GEN_DIR)
|
||||
cd $(OTEL_PROTO_PATH) && protoc --java_out=$(TMP_DIR)/gen/java $$(find . \
|
||||
-type f \
|
||||
-name '*.proto' \
|
||||
-not \( -name 'logs*.proto' -o -name 'metrics*.proto' -o -name 'trace*.proto' -o -name '*service.proto' \))
|
||||
$(JAVAC) -source $(JAVA_TARGET) \
|
||||
-target $(JAVA_TARGET) \
|
||||
-cp $(TEST_DEPS_DIR)/* \
|
||||
-d $(TMP_DIR)/build \
|
||||
-Xlint:-options \
|
||||
$$(find $(TMP_DIR)/gen/java -name "*.java")
|
||||
$(JAR) cvf $(TEST_GEN_DIR)/opentelemetry-gen-classes.jar -C $(TMP_DIR)/build .
|
||||
|
||||
LINT_SOURCES=`ls -1 src/*.cpp src/*/*.cpp | grep -v rustDemangle.cpp`
|
||||
CLANG_TIDY_ARGS_EXTRA=
|
||||
cpp-lint:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM public.ecr.aws/docker/library/amazoncorretto:11-alpine-jdk
|
||||
|
||||
RUN apk add --no-cache make gcc g++ linux-headers musl-dev util-linux patchelf gcovr bash tar
|
||||
RUN apk add --no-cache make gcc g++ linux-headers musl-dev util-linux patchelf gcovr bash tar curl
|
||||
|
||||
BIN
test/gen/opentelemetry-gen-classes.jar
Normal file
BIN
test/gen/opentelemetry-gen-classes.jar
Normal file
Binary file not shown.
@@ -123,6 +123,9 @@ public class Runner {
|
||||
rt.method().getDeclaringClass().getDeclaredConstructor().newInstance() : null;
|
||||
rt.method().invoke(holder, p);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getTargetException() instanceof NoClassDefFoundError) {
|
||||
return TestResult.skipMissingJar();
|
||||
}
|
||||
return TestResult.fail(e.getTargetException());
|
||||
} catch (Throwable e) {
|
||||
return TestResult.fail(e);
|
||||
@@ -166,6 +169,7 @@ public class Runner {
|
||||
System.out.println("FAIL: " + fail);
|
||||
System.out.println("SKIP (disabled): " + statusCounts.getOrDefault(TestStatus.SKIP_DISABLED, 0));
|
||||
System.out.println("SKIP (config mismatch): " + statusCounts.getOrDefault(TestStatus.SKIP_CONFIG_MISMATCH, 0));
|
||||
System.out.println("SKIP (missing JAR): " + statusCounts.getOrDefault(TestStatus.SKIP_MISSING_JAR, 0));
|
||||
System.out.println("TOTAL: " + testCount);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ public class TestResult {
|
||||
return new TestResult(TestStatus.SKIP_DISABLED, null);
|
||||
}
|
||||
|
||||
public static TestResult skipMissingJar() {
|
||||
return new TestResult(TestStatus.SKIP_MISSING_JAR, null);
|
||||
}
|
||||
|
||||
public static TestResult pass() {
|
||||
return new TestResult(TestStatus.PASS, null);
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ public enum TestStatus {
|
||||
FAIL,
|
||||
SKIP_DISABLED,
|
||||
SKIP_CONFIG_MISMATCH,
|
||||
SKIP_MISSING_JAR,
|
||||
}
|
||||
|
||||
8
test/stubs/com/google/protobuf/ByteString.java
Normal file
8
test/stubs/com/google/protobuf/ByteString.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public abstract class ByteString {}
|
||||
8
test/stubs/com/google/protobuf/CodedInputStream.java
Normal file
8
test/stubs/com/google/protobuf/CodedInputStream.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public abstract class CodedInputStream {}
|
||||
8
test/stubs/com/google/protobuf/GeneratedMessageV3.java
Normal file
8
test/stubs/com/google/protobuf/GeneratedMessageV3.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public abstract class GeneratedMessageV3 {}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public class InvalidProtocolBufferException extends java.io.IOException {}
|
||||
8
test/stubs/com/google/protobuf/MessageOrBuilder.java
Normal file
8
test/stubs/com/google/protobuf/MessageOrBuilder.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.protobuf;
|
||||
|
||||
public interface MessageOrBuilder {}
|
||||
29
test/test/otlp/CpuBurner.java
Normal file
29
test/test/otlp/CpuBurner.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package test.otlp;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Random;
|
||||
|
||||
public class CpuBurner {
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final Duration TEST_DURATION = Duration.ofSeconds(1);
|
||||
|
||||
static void burn() {
|
||||
long n = RANDOM.nextLong();
|
||||
if (Long.toString(n).hashCode() == 0) {
|
||||
System.out.println(n);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Instant start = Instant.now();
|
||||
while (Duration.between(start, Instant.now()).compareTo(TEST_DURATION) < 0) {
|
||||
burn();
|
||||
}
|
||||
}
|
||||
}
|
||||
108
test/test/otlp/OtlpTests.java
Normal file
108
test/test/otlp/OtlpTests.java
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package test.otlp;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import one.profiler.test.*;
|
||||
import io.opentelemetry.proto.profiles.v1development.*;
|
||||
|
||||
public class OtlpTests {
|
||||
@Test(mainClass = CpuBurner.class, agentArgs = "start,otlp,file=%f.pb")
|
||||
public void testOtlpReadable(TestProcess p) throws Exception {
|
||||
ProfilesData profilesData = waitAndGetProfilesData(p);
|
||||
|
||||
assert getFirstProfile(profilesData) != null;
|
||||
}
|
||||
|
||||
@Test(mainClass = CpuBurner.class, agentArgs = "start,otlp,event=itimer,file=%f.pb")
|
||||
public void testSampleType(TestProcess p) throws Exception {
|
||||
ProfilesData profilesData = waitAndGetProfilesData(p);
|
||||
|
||||
Profile profile = getFirstProfile(profilesData);
|
||||
assert profile.getSampleTypeList().size() == 2;
|
||||
|
||||
ValueType sampleType0 = profile.getSampleType(0);
|
||||
assert profilesData.getDictionary().getStringTable(sampleType0.getTypeStrindex()).equals("itimer");
|
||||
assert profilesData.getDictionary().getStringTable(sampleType0.getUnitStrindex()).equals("count");
|
||||
assert sampleType0.getAggregationTemporality() == AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE;
|
||||
|
||||
ValueType sampleType1 = profile.getSampleType(1);
|
||||
assert profilesData.getDictionary().getStringTable(sampleType1.getTypeStrindex()).equals("itimer");
|
||||
assert profilesData.getDictionary().getStringTable(sampleType1.getUnitStrindex()).equals("ns");
|
||||
assert sampleType1.getAggregationTemporality() == AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE;
|
||||
}
|
||||
|
||||
@Test(mainClass = CpuBurner.class, agentArgs = "start,otlp,file=%f.pb")
|
||||
public void testSamples(TestProcess p) throws Exception {
|
||||
ProfilesData profilesData = waitAndGetProfilesData(p);
|
||||
|
||||
Profile profile = getFirstProfile(profilesData);
|
||||
ProfilesDictionary dictionary = profilesData.getDictionary();
|
||||
|
||||
Output collapsed = toCollapsed(profile, dictionary);
|
||||
assert collapsed.contains("test/otlp/CpuBurner.main;test/otlp/CpuBurner.burn");
|
||||
}
|
||||
|
||||
private static ProfilesData waitAndGetProfilesData(TestProcess p) throws Exception {
|
||||
p.waitForExit();
|
||||
assert p.exitCode() == 0;
|
||||
|
||||
byte[] profileBytes = Files.readAllBytes(p.getFile("%f").toPath());
|
||||
return ProfilesData.parseFrom(profileBytes);
|
||||
}
|
||||
|
||||
private static Output toCollapsed(Profile profile, ProfilesDictionary dictionary) {
|
||||
return toCollapsed(profile, dictionary, 0);
|
||||
}
|
||||
|
||||
private static Output toCollapsed(Profile profile, ProfilesDictionary dictionary, int valueIdx) {
|
||||
Map<String, Long> stackTracesCount = new HashMap<>();
|
||||
for (Sample sample : profile.getSampleList()) {
|
||||
StringBuilder stackTrace = new StringBuilder();
|
||||
for (int i = sample.getLocationsLength() - 1; i > 0; --i) {
|
||||
int locationIndex = profile.getLocationIndices(sample.getLocationsStartIndex() + i);
|
||||
stackTrace.append(getFrameName(locationIndex, dictionary)).append(';');
|
||||
}
|
||||
int locationIndex = profile.getLocationIndices(sample.getLocationsStartIndex());
|
||||
stackTrace.append(getFrameName(locationIndex, dictionary));
|
||||
|
||||
stackTracesCount.compute(stackTrace.toString(), (key, oldValue) -> sample.getValue(valueIdx) + (oldValue == null ? 0 : oldValue));
|
||||
}
|
||||
List<String> lines = stackTracesCount.entrySet().stream().map(entry -> String.format("%s %d", entry.getKey(), entry.getValue())).collect(Collectors.toList());
|
||||
return new Output(lines.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static String getFrameName(int locationIndex, ProfilesDictionary dictionary) {
|
||||
Location location = dictionary.getLocationTable(locationIndex);
|
||||
Line line = location.getLine(location.getLineList().size() - 1);
|
||||
Function function = dictionary.getFunctionTable(line.getFunctionIndex());
|
||||
return dictionary.getStringTable(function.getNameStrindex());
|
||||
}
|
||||
|
||||
private static Profile getFirstProfile(ProfilesData profilesData) {
|
||||
assert profilesData.getResourceProfilesList().size() == 1;
|
||||
|
||||
ResourceProfiles resourceProfiles = profilesData.getResourceProfiles(0);
|
||||
assert resourceProfiles.getScopeProfilesList().size() == 1;
|
||||
|
||||
ScopeProfiles scopeProfiles = resourceProfiles.getScopeProfiles(0);
|
||||
assert scopeProfiles.getProfilesList().size() == 1;
|
||||
|
||||
return scopeProfiles.getProfiles(0);
|
||||
}
|
||||
|
||||
private static void assertCloseTo(long value, long target, String message) {
|
||||
Assert.isGreaterOrEqual(value, target * 0.75, message);
|
||||
Assert.isLessOrEqual(value, target * 1.25, message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user