Test OTLP output format (#1331)

This commit is contained in:
Francesco Andreuzzi
2025-07-09 13:33:39 +01:00
committed by GitHub
parent f627b3157b
commit f79729167a
14 changed files with 225 additions and 6 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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

Binary file not shown.

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -10,4 +10,5 @@ public enum TestStatus {
FAIL,
SKIP_DISABLED,
SKIP_CONFIG_MISMATCH,
SKIP_MISSING_JAR,
}

View File

@@ -0,0 +1,8 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.protobuf;
public abstract class ByteString {}

View File

@@ -0,0 +1,8 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.protobuf;
public abstract class CodedInputStream {}

View File

@@ -0,0 +1,8 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.protobuf;
public abstract class GeneratedMessageV3 {}

View File

@@ -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 {}

View File

@@ -0,0 +1,8 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.protobuf;
public interface MessageOrBuilder {}

View 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();
}
}
}

View 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);
}
}