diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 856971708..388041277 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -59,7 +59,7 @@ lmax-disruptor = "com.lmax:disruptor:3.4.4" jctools = "org.jctools:jctools-core:4.0.3" github-api = "org.kohsuke:github-api:1.321" apachecommons-compress = "org.apache.commons:commons-compress:1.26.1" -asyncprofiler = "tools.profiler:async-profiler:1.8.3" +asyncprofiler = "tools.profiler:async-profiler:3.0" freemarker = "org.freemarker:freemarker:2.3.32" diff --git a/inferred-spans/build.gradle.kts b/inferred-spans/build.gradle.kts index 567dbb8f7..fafbc1dc4 100644 --- a/inferred-spans/build.gradle.kts +++ b/inferred-spans/build.gradle.kts @@ -13,8 +13,9 @@ dependencies { compileOnly(libs.findbugs.jsr305) implementation(libs.lmax.disruptor) implementation(libs.jctools) - implementation(project(":common")) + implementation(libs.asyncprofiler) implementation(libs.bundles.semconv) + implementation(project(":common")) testAnnotationProcessor(libs.autoservice.processor) testCompileOnly(libs.autoservice.annotations) @@ -25,7 +26,6 @@ dependencies { testImplementation(libs.awaitility) testImplementation(libs.github.api) testImplementation(libs.apachecommons.compress) - testImplementation(libs.asyncprofiler) testImplementation(libs.bundles.semconv) } diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/SamplingProfiler.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/SamplingProfiler.java index 9b4eb7266..01d6390a3 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/SamplingProfiler.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/SamplingProfiler.java @@ -23,7 +23,6 @@ import co.elastic.otel.common.config.WildcardMatcher; import co.elastic.otel.common.util.ExecutorUtils; -import co.elastic.otel.profiler.asyncprofiler.AsyncProfiler; import co.elastic.otel.profiler.asyncprofiler.JfrParser; import co.elastic.otel.profiler.collections.Long2ObjectHashMap; import co.elastic.otel.profiler.pooling.Allocator; @@ -60,6 +59,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; +import one.profiler.AsyncProfiler; /** * Correlates {@link ActivationEvent}s with {@link StackFrame}s which are recorded by {@link @@ -112,6 +112,8 @@ */ class SamplingProfiler implements Runnable { + private static final String LIB_DIR_PROPERTY_NAME = "one.profiler.extractPath"; + private static final Logger logger = Logger.getLogger(SamplingProfiler.class.getName()); private static final int ACTIVATION_EVENTS_IN_FILE = 1_000_000; private static final int MAX_STACK_DEPTH = 256; @@ -179,6 +181,8 @@ public void translateTo( private final Supplier tracerProvider; + private final AsyncProfiler profiler; + /** * Creates a sampling profiler, optionally relying on existing files. * @@ -230,9 +234,21 @@ public CallTree.Root createInstance() { this.jfrFile = jfrFile; activationEventsBuffer = ByteBuffer.allocateDirect(ACTIVATION_EVENTS_BUFFER_SIZE); this.activationEventsFile = activationEventsFile; + profiler = loadProfiler(); activationListener = ProfilingActivationListener.register(this); } + private AsyncProfiler loadProfiler() { + String libDir = config.getProfilerLibDirectory(); + try { + Files.createDirectories(Paths.get(libDir)); + } catch (IOException e) { + throw new RuntimeException("Failed to create directory to extract lib to", e); + } + System.setProperty(LIB_DIR_PROPERTY_NAME, libDir); + return AsyncProfiler.getInstance(); + } + /** * For testing only! This method must only be called in tests and some period after activation / * deactivation events, as otherwise it is racy. @@ -309,9 +325,7 @@ public ActivationEvent newInstance() { public boolean onActivation(Span activeSpan, @Nullable Span previouslyActive) { if (profilingSessionOngoing) { if (previouslyActive == null) { - AsyncProfiler.getInstance( - config.getProfilerLibDirectory(), config.getAsyncProfilerSafeMode()) - .enableProfilingCurrentThread(); + profiler.addThread(Thread.currentThread()); } boolean success = eventBuffer.tryPublishEvent(ACTIVATION_EVENT_TRANSLATOR, activeSpan, previouslyActive); @@ -337,9 +351,7 @@ public boolean onActivation(Span activeSpan, @Nullable Span previouslyActive) { public boolean onDeactivation(Span activeSpan, @Nullable Span previouslyActive) { if (profilingSessionOngoing) { if (previouslyActive == null) { - AsyncProfiler.getInstance( - config.getProfilerLibDirectory(), config.getAsyncProfilerSafeMode()) - .disableProfilingCurrentThread(); + profiler.removeThread(Thread.currentThread()); } boolean success = eventBuffer.tryPublishEvent(DEACTIVATION_EVENT_TRANSLATOR, activeSpan, previouslyActive); @@ -393,15 +405,12 @@ public void run() { } private void profile(Duration profilingDuration) throws Exception { - AsyncProfiler asyncProfiler = - AsyncProfiler.getInstance( - config.getProfilerLibDirectory(), config.getAsyncProfilerSafeMode()); try { String startCommand = createStartCommand(); - String startMessage = asyncProfiler.execute(startCommand); + String startMessage = profiler.execute(startCommand); logger.fine(startMessage); if (!profiledThreads.isEmpty()) { - restoreFilterState(asyncProfiler); + restoreFilterState(profiler); } // Doesn't need to be atomic as this field is being updated only by a single thread //noinspection NonAtomicOperationOnVolatileField @@ -414,7 +423,7 @@ private void profile(Duration profilingDuration) throws Exception { // residual activation events if post-processing is disabled dynamically consumeActivationEventsFromRingBufferAndWriteToFile(profilingDuration); - String stopMessage = asyncProfiler.execute("stop"); + String stopMessage = profiler.execute("stop"); logger.fine(stopMessage); // When post-processing is disabled, jfr file will not be parsed and the heavy processing will @@ -423,7 +432,7 @@ private void profile(Duration profilingDuration) throws Exception { processTraces(); } catch (InterruptedException | ClosedByInterruptException e) { try { - asyncProfiler.stop(); + profiler.stop(); } catch (IllegalStateException ignore) { } Thread.currentThread().interrupt(); @@ -439,7 +448,7 @@ String createStartCommand() { .append(",safemode=") .append(config.getAsyncProfilerSafeMode()); if (!config.isProfilingLoggingEnabled()) { - startCommand.append(",log=none"); + startCommand.append(",loglevel=none"); } return startCommand.toString(); } @@ -460,7 +469,7 @@ public boolean test(Thread thread, Long2ObjectHashMap.KeySet profiledThreads) new ThreadMatcher.NonCapturingConsumer() { @Override public void accept(Thread thread, AsyncProfiler asyncProfiler) { - asyncProfiler.enableProfilingThread(thread); + asyncProfiler.addThread(thread); } }, asyncProfiler); @@ -531,7 +540,7 @@ public void processTraces() throws IOException { processActivationEventsUpTo(stackTrace.nanoTime, event, eof); CallTree.Root root = profiledThreads.get(stackTrace.threadId); if (root != null) { - jfrParser.resolveStackTrace(stackTrace.stackTraceId, true, stackFrames, MAX_STACK_DEPTH); + jfrParser.resolveStackTrace(stackTrace.stackTraceId, stackFrames, MAX_STACK_DEPTH); if (stackFrames.size() == MAX_STACK_DEPTH) { logger.fine( "Max stack depth reached. Set profiling_included_classes or profiling_excluded_classes."); diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfiler.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfiler.java deleted file mode 100644 index 0be4ac356..000000000 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfiler.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.otel.profiler.asyncprofiler; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import javax.annotation.Nullable; - -/** - * Java API for in-process profiling. Serves as a wrapper around async-profiler native library. This - * class is a singleton. The first call to {@link #getInstance(String, int)} initiates loading of - * libasyncProfiler.so. - * - *

This is based on - * https://github.com/jvm-profiling-tools/async-profiler/blob/master/src/java/one/profiler/AsyncProfiler.java, - * under Apache License 2.0. It is modified to allow it to be shaded into the {@code co.elastic.apm} - * namespace - */ -public class AsyncProfiler { - - public static final String SAFEMODE_SYSTEM_PROPERTY_NAME = "AsyncProfiler.safemode"; - - @Nullable private static volatile AsyncProfiler instance; - - private AsyncProfiler() {} - - public static AsyncProfiler getInstance(String profilerLibDirectory, int safemode) { - AsyncProfiler result = AsyncProfiler.instance; - if (result != null) { - return result; - } - synchronized (AsyncProfiler.class) { - if (instance == null) { - if (System.getProperty("java.vm.name").contains("J9")) { - throw new IllegalStateException( - "OpenJ9 JVMs are not supported by async profiler. Please set " - + "profiling_inferred_spans_enabled to false"); - } - try { - // set the AsyncProfiler.safemode system property with the configured safemode, so that - // optimizations - // can be applied already at load time. Specifically, if (safemode & 14) == 14 (2, 4 and 8 - // bits are set), then - // async profiler will avoid enabling CompiledMethodLoad events at load time, so to - // workaround a relatd JVM bug - // (https://bugs.openjdk.java.net/browse/JDK-8202883, - // https://bugs.openjdk.java.net/browse/JDK-8173361 and friends). - // safemode can still be set for each profiling session, but it can only be stricter than - // the safemode - // configured at load time. - System.setProperty(SAFEMODE_SYSTEM_PROPERTY_NAME, String.valueOf(safemode)); - loadNativeLibrary(profilerLibDirectory); - } catch (UnsatisfiedLinkError e) { - throw new IllegalStateException( - String.format( - "It is likely that %s is not an executable location. Consider setting " - + "the profiling_inferred_spans_lib_directory property to a directory on a partition that allows execution", - profilerLibDirectory), - e); - } - - instance = new AsyncProfiler(); - } - return instance; - } - } - - static void reset() { - synchronized (AsyncProfiler.class) { - instance = null; - } - } - - private static void loadNativeLibrary(String libraryDirectory) { - String libraryName = getLibraryFileName(); - Path file = - ResourceExtractionUtil.extractResourceToDirectory( - "asyncprofiler/" + libraryName + ".so", - libraryName, - ".so", - Paths.get(libraryDirectory)); - System.load(file.toString()); - } - - static String getLibraryFileName() { - String os = System.getProperty("os.name").toLowerCase(); - String arch = System.getProperty("os.arch").toLowerCase(); - if (os.contains("linux")) { - if (arch.contains("arm") || arch.contains("aarch32")) { - return "libasyncProfiler-linux-arm"; - } else if (arch.contains("aarch")) { - return "libasyncProfiler-linux-aarch64"; - } else if (arch.contains("64")) { - return "libasyncProfiler-linux-x64"; - } else if (arch.contains("86")) { - return "libasyncProfiler-linux-x86"; - } else { - throw new IllegalStateException("Async-profiler does not work on Linux " + arch); - } - } else if (os.contains("mac")) { - if (arch.contains("aarch")) { - throw new IllegalStateException("Async-profiler 1.x does not work on Apple silicon"); - } else { - return "libasyncProfiler-macos-x64"; - } - } else { - throw new IllegalStateException("Async-profiler does not work on " + os); - } - } - - /** - * Stop profiling (without dumping results) - * - * @throws IllegalStateException If profiler is not running - */ - public void stop() throws IllegalStateException { - stop0(); - } - - /** - * Execute an agent-compatible profiling command - the comma-separated list of arguments described - * in arguments.cpp - * - * @param command Profiling command - * @return The command result - * @throws IllegalArgumentException If failed to parse the command - * @throws java.io.IOException If failed to create output file - */ - public String execute(String command) throws IllegalArgumentException, java.io.IOException { - return execute0(command); - } - - /** - * Adds the given thread to the set of profiled threads - * - * @param thread A thread to add; null means current thread - * @throws IllegalStateException If thread has not yet started or has already finished - */ - public void enableProfilingThread(Thread thread) throws IllegalStateException { - filterThread(thread, true); - } - - /** - * Removes the given thread to the set of profiled threads - * - * @param thread A thread to remove; null means current thread - * @throws IllegalStateException If thread has not yet started or has already finished - */ - public void disableProfilingThread(Thread thread) throws IllegalStateException { - filterThread(thread, false); - } - - /** Adds the current thread to the set of profiled threads */ - public void enableProfilingCurrentThread() { - filterThread0(null, true); - } - - /** Removes the current thread to the set of profiled threads */ - public void disableProfilingCurrentThread() throws IllegalStateException { - filterThread0(null, false); - } - - private void filterThread(Thread thread, boolean enable) throws IllegalStateException { - synchronized (thread) { - Thread.State state = thread.getState(); - if (state == Thread.State.NEW || state == Thread.State.TERMINATED) { - return; - } - filterThread0(thread, enable); - } - } - - private native long getSamples(); - - private native void start0(String event, long interval, boolean reset) - throws IllegalStateException; - - private native void stop0() throws IllegalStateException; - - private native String execute0(String command) throws IllegalArgumentException, IOException; - - private native void filterThread0(Thread thread, boolean enable); -} diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/BufferedFile.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/BufferedFile.java index 688c2e57a..33014888c 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/BufferedFile.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/BufferedFile.java @@ -25,6 +25,7 @@ import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.StandardOpenOption; import javax.annotation.Nullable; @@ -52,6 +53,15 @@ class BufferedFile implements Recyclable { private static final int SIZE_OF_SHORT = 2; private static final int SIZE_OF_INT = 4; private static final int SIZE_OF_LONG = 8; + + // The following constant are defined by the JFR file format for identifying the string encoding + private static final int STRING_ENCODING_NULL = 0; + private static final int STRING_ENCODING_EMPTY = 1; + private static final int STRING_ENCODING_CONSTANTPOOL = 2; + private static final int STRING_ENCODING_UTF8 = 3; + private static final int STRING_ENCODING_CHARARRAY = 4; + private static final int STRING_ENCODING_LATIN1 = 5; + private ByteBuffer buffer; private final ByteBuffer bigBuffer; private final ByteBuffer smallBuffer; @@ -110,6 +120,88 @@ public void skip(int bytesToSkip) { position(position() + bytesToSkip); } + public void skipString() throws IOException { + readOrSkipString(get(), null); + } + + /** + * @param output the buffer to place the string intro + * @return false, if the string to read is null, true otherwise + */ + @Nullable + public boolean readString(StringBuilder output) throws IOException { + byte encoding = get(); + if (encoding == 0) { // 0 encoding represents a null string + return false; + } + readOrSkipString(encoding, output); + return true; + } + + @Nullable + public String readString() throws IOException { + byte encoding = get(); + if (encoding == STRING_ENCODING_NULL) { + return null; + } + if (encoding == STRING_ENCODING_EMPTY) { + return ""; + } + StringBuilder output = new StringBuilder(); + readOrSkipString(encoding, output); + return output.toString(); + } + + private void readOrSkipString(byte encoding, @Nullable StringBuilder output) throws IOException { + switch (encoding) { + case STRING_ENCODING_NULL: + case STRING_ENCODING_EMPTY: + return; + case STRING_ENCODING_CONSTANTPOOL: + if (output != null) { + throw new IllegalStateException("Reading constant pool string is not supported"); + } + getVarLong(); + return; + case STRING_ENCODING_UTF8: + readOrSkipUtf8(output); + return; + case STRING_ENCODING_CHARARRAY: + throw new IllegalStateException("Char-array encoding is not supported by the parser yet"); + case STRING_ENCODING_LATIN1: + if (output != null) { + throw new IllegalStateException("Reading LATIN1 encoded string is not supported"); + } + skip(getVarInt()); + return; + default: + throw new IllegalStateException("Unknown string encoding type: " + encoding); + } + } + + private void readOrSkipUtf8(@Nullable StringBuilder output) throws IOException { + int len = getVarInt(); + if (output == null) { + skip(len); + return; + } + ensureRemaining(len, len); + + for (int i = 0; i < len; i++) { + byte hopefullyAscii = getUnsafe(); + if (hopefullyAscii > 0) { + output.append((char) hopefullyAscii); + } else { + // encountered non-ascii character: fallback to allocating and UTF8-decoding + position(position() - 1); // reset position before the just read byte + byte[] utf8Data = new byte[len - i]; + buffer.get(utf8Data); + output.append(new String(utf8Data, StandardCharsets.UTF_8)); + return; + } + } + } + /** * Sets the position of the file without reading new data. * @@ -173,7 +265,7 @@ public void ensureRemaining(int minRemaining, int maxRead) throws IOException { * @return The byte at the file's current position * @throws IOException If some I/O error occurs */ - public short get() throws IOException { + public byte get() throws IOException { ensureRemaining(SIZE_OF_BYTE); return buffer.get(); } @@ -229,6 +321,28 @@ public long getLong() throws IOException { return buffer.getLong(); } + /** Reads LEB-128 variable length encoded values of a size of up to 64 bit. */ + public long getVarLong() throws IOException { + long value = 0; + boolean hasNext = true; + int shift = 0; + while (hasNext) { + long byteVal = ((int) get()); + hasNext = (byteVal & 0x80) != 0; + value |= (byteVal & 0x7F) << shift; + shift += 7; + } + return value; + } + + public int getVarInt() throws IOException { + long val = getVarLong(); + if ((int) val != val) { + throw new IllegalArgumentException("The LEB128 encoded value does not fit in an int"); + } + return (int) val; + } + /** * Gets a byte from the underlying buffer without checking if this part of the file is actually in * the buffer. diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/JfrParser.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/JfrParser.java index ad7656c03..0ee5172ff 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/JfrParser.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/JfrParser.java @@ -65,14 +65,14 @@ public class JfrParser implements Recyclable { private final Int2ObjectHashMap symbolIdToString = new Int2ObjectHashMap(); private final Int2IntHashMap stackTraceIdToFilePositions = new Int2IntHashMap(-1); private final Long2LongHashMap nativeTidToJavaTid = new Long2LongHashMap(-1); - private final Long2ObjectHashMap frameIdToFrame = + private final Long2ObjectHashMap methodIdToFrame = new Long2ObjectHashMap(); - private final Long2LongHashMap frameIdToMethodSymbol = new Long2LongHashMap(-1); - private final Long2LongHashMap frameIdToClassId = new Long2LongHashMap(-1); + private final Long2LongHashMap methodIdToMethodNameSymbol = new Long2LongHashMap(-1); + private final Long2LongHashMap methodIdToClassId = new Long2LongHashMap(-1); // used to resolve a symbol with minimal allocations private final StringBuilder symbolBuilder = new StringBuilder(); - private long eventsOffset; - private long metadataOffset; + private long eventsFilePosition; + private long metadataFilePosition; @Nullable private boolean[] isJavaFrameType; @Nullable private List excludedClasses; @Nullable private List includedClasses; @@ -88,14 +88,14 @@ public JfrParser() { } /** - * Initializes the parser to make it ready for {@link #resolveStackTrace(long, boolean, List, - * int)} to be called. + * Initializes the parser to make it ready for {@link #resolveStackTrace(long, List, int)} to be + * called. * * @param file the JFR file to parse * @param excludedClasses Class names to exclude in stack traces (has an effect on {@link - * #resolveStackTrace(long, boolean, List, int)}) + * #resolveStackTrace(long, List, int)}) * @param includedClasses Class names to include in stack traces (has an effect on {@link - * #resolveStackTrace(long, boolean, List, int)}) + * #resolveStackTrace(long, List, int)}) * @throws IOException if some I/O error occurs */ public void parse( @@ -105,15 +105,19 @@ public void parse( this.includedClasses = includedClasses; bufferedFile.setFile(file); long fileSize = bufferedFile.size(); - if (fileSize < 16) { + + int chunkSize = readChunk(0); + if (chunkSize < fileSize) { throw new IllegalStateException( - "Unexpected sampling profiler error, everything else should work as expected. " - + "Please report to us with as many details, including OS and JVM details."); + "This implementation does not support reading JFR files containing multiple chunks"); } + } + + private int readChunk(int position) throws IOException { + bufferedFile.position(position); if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Parsing {0} ({1} bytes)", new Object[] {file, fileSize}); + logger.log(Level.FINE, "Parsing JFR chunk at offset", new Object[] {position}); } - bufferedFile.ensureRemaining(16, 16); for (byte magicByte : MAGIC_BYTES) { if (bufferedFile.get() != magicByte) { throw new IllegalArgumentException("Not a JFR file"); @@ -121,153 +125,175 @@ public void parse( } short major = bufferedFile.getShort(); short minor = bufferedFile.getShort(); - if (major != 0 || minor != 9) { + if (major != 2 || minor != 0) { throw new IllegalArgumentException( - String.format("Can only parse version 0.9. Was %d.%d", major, minor)); + String.format("Can only parse version 2.0. Was %d.%d", major, minor)); } - metadataOffset = bufferedFile.getLong(); - eventsOffset = bufferedFile.position(); + long chunkSize = bufferedFile.getLong(); + long constantPoolOffset = bufferedFile.getLong(); + metadataFilePosition = position + bufferedFile.getLong(); + bufferedFile.getLong(); // startTimeNanos + bufferedFile.getLong(); // durationNanos + bufferedFile.getLong(); // startTicks + bufferedFile.getLong(); // ticksPerSecond + bufferedFile.getInt(); // features - long checkpointOffset = parseMetadata(metadataOffset); - parseCheckpoint(checkpointOffset); + // Events start right after metadata + eventsFilePosition = metadataFilePosition + parseMetadata(metadataFilePosition); + parseCheckpointEvents(position + constantPoolOffset); + return (int) chunkSize; } private long parseMetadata(long metadataOffset) throws IOException { bufferedFile.position(metadataOffset); - bufferedFile.ensureRemaining(8, 8); - int size = bufferedFile.getInt(); + int size = bufferedFile.getVarInt(); expectEventType(EventTypeId.EVENT_METADATA); - bufferedFile.skip(size - 16); - bufferedFile.ensureRemaining(8, 8); - return bufferedFile.getLong(); + return size; } private void expectEventType(int expectedEventType) throws IOException { - int eventType = bufferedFile.getInt(); + long eventType = bufferedFile.getVarLong(); if (eventType != expectedEventType) { throw new IOException("Expected " + expectedEventType + " but got " + eventType); } } - private void parseCheckpoint(long checkpointOffset) throws IOException { + private void parseCheckpointEvents(long checkpointOffset) throws IOException { bufferedFile.position(checkpointOffset); - int size = bufferedFile.getInt(); // size + bufferedFile.getVarInt(); // size expectEventType(EventTypeId.EVENT_CHECKPOINT); - bufferedFile.getLong(); // stop timestamp - bufferedFile.getLong(); // previous checkpoint - always 0 in async-profiler - while (bufferedFile.position() < metadataOffset) { - parseContent(); + bufferedFile.getVarLong(); // start + bufferedFile.getVarLong(); // duration + long delta = bufferedFile.getVarLong(); + if (delta != 0) { + throw new IllegalStateException( + "Expected only one checkpoint event, but file contained multiple, delta is " + delta); + } + bufferedFile.get(); // typeMask + long poolCount = bufferedFile.getVarLong(); + for (int i = 0; i < poolCount; i++) { + parseConstantPool(); } } - private void parseContent() throws IOException { - BufferedFile bufferedFile = this.bufferedFile; - int contentTypeId = bufferedFile.getInt(); - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Parsing content type {0}", contentTypeId); - } - int count = bufferedFile.getInt(); - switch (contentTypeId) { - case ContentTypeId.CONTENT_THREAD: - for (int i = 0; i < count; i++) { - int threadId = bufferedFile.getInt(); - String threadName = readUtf8String().toString(); - } + private void parseConstantPool() throws IOException { + long typeId = bufferedFile.getVarLong(); + int count = bufferedFile.getVarInt(); + + switch ((int) typeId) { + case ContentTypeId.CONTENT_FRAME_TYPE: + readFrameTypeConstants(count); break; - case ContentTypeId.CONTENT_JAVA_THREAD: + case ContentTypeId.CONTENT_THREAD_STATE: + case ContentTypeId.CONTENT_GC_WHEN: + case ContentTypeId.CONTENT_LOG_LEVELS: + // We are not interested in those types, but still have to consume the bytes for (int i = 0; i < count; i++) { - bufferedFile.ensureRemaining(16); - long javaThreadId = bufferedFile.getUnsafeLong(); - int nativeThreadId = bufferedFile.getUnsafeInt(); - int threadGroup = bufferedFile.getUnsafeInt(); - nativeTidToJavaTid.put(nativeThreadId, javaThreadId); + bufferedFile.getVarInt(); + bufferedFile.skipString(); } break; - case ContentTypeId.CONTENT_THREAD_GROUP: - // no info + case ContentTypeId.CONTENT_THREAD: + readThreadConstants(count); break; case ContentTypeId.CONTENT_STACKTRACE: - for (int i = 0; i < count; i++) { - bufferedFile.ensureRemaining(13); - int pos = (int) bufferedFile.position(); - // always an integer - // see profiler.h - // MAX_CALLTRACES = 65536 - int stackTraceKey = (int) bufferedFile.getUnsafeLong(); - this.stackTraceIdToFilePositions.put(stackTraceKey, pos); - bufferedFile.getUnsafe(); // truncated - int numFrames = bufferedFile.getUnsafeInt(); - int sizeOfFrame = 13; - bufferedFile.skip(numFrames * sizeOfFrame); - } - break; - case ContentTypeId.CONTENT_CLASS: - for (int i = 0; i < count; i++) { - bufferedFile.ensureRemaining(26); - // classId is an incrementing integer, no way there are more than 2 billion distinct ones - int classId = (int) bufferedFile.getUnsafeLong(); - bufferedFile.getUnsafeLong(); // loader class - // symbol ids are incrementing integers, no way there are more than 2 billion distinct - // ones - int classNameSymbolId = (int) bufferedFile.getUnsafeLong(); - classIdToClassNameSymbolId.put(classId, classNameSymbolId); // class name - bufferedFile.getUnsafeShort(); // access flags - } + readStackTraceConstants(count); break; case ContentTypeId.CONTENT_METHOD: - for (int i = 1; i <= count; i++) { - bufferedFile.ensureRemaining(35); - long id = bufferedFile.getUnsafeLong(); - // classId is an incrementing integer, no way there are more than 2 billion distinct ones - int classId = (int) bufferedFile.getUnsafeLong(); - // symbol ids are incrementing integers, no way there are more than 2 billion distinct - // ones - int methodNameSymbolId = (int) bufferedFile.getUnsafeLong(); - frameIdToFrame.put(id, FRAME_NULL); - frameIdToClassId.put(id, classId); - frameIdToMethodSymbol.put(id, methodNameSymbolId); - bufferedFile.getUnsafeLong(); // signature - bufferedFile.getUnsafeShort(); // modifiers - bufferedFile.getUnsafe(); // hidden - } + readMethodConstants(count); break; - case ContentTypeId.CONTENT_SYMBOL: - for (int i = 0; i < count; i++) { - // symbol ids are incrementing integers, no way there are more than 2 billion distinct - // ones - int symbolId = (int) bufferedFile.getLong(); - int pos = (int) bufferedFile.position(); - symbolIdToPos.put(symbolId, pos); - symbolIdToString.put(symbolId, SYMBOL_NULL); - skipString(); - } + case ContentTypeId.CONTENT_CLASS: + readClassConstants(count); break; - case ContentTypeId.CONTENT_STATE: - // we're not really interested in the thread states - // but we sill have to consume the bytes - for (int i = 1; i <= count; i++) { - bufferedFile.getShort(); - skipString(); - } + case ContentTypeId.CONTENT_PACKAGE: + readPackageConstants(count); break; - case ContentTypeId.CONTENT_FRAME_TYPE: - isJavaFrameType = new boolean[count + 1]; - for (int i = 1; i <= count; i++) { - int id = bufferedFile.get(); - if (i != id) { - throw new IllegalStateException("Expecting ids to be incrementing"); - } - isJavaFrameType[id] = JAVA_FRAME_TYPES.contains(readUtf8String().toString()); - } + case ContentTypeId.CONTENT_SYMBOL: + readSymbolConstants(count); break; default: - throw new IOException("Unknown content type " + contentTypeId); + throw new IllegalStateException("Unhandled constant pool type: " + typeId); + } + } + + private void readSymbolConstants(int count) throws IOException { + for (int i = 0; i < count; i++) { + int symbolId = bufferedFile.getVarInt(); + int pos = (int) bufferedFile.position(); + bufferedFile.skipString(); + symbolIdToPos.put(symbolId, pos); + symbolIdToString.put(symbolId, SYMBOL_NULL); + } + } + + private void readClassConstants(int count) throws IOException { + for (int i = 0; i < count; i++) { + int classId = bufferedFile.getVarInt(); + bufferedFile.getVarInt(); // classloader, always zero in async-profiler JFR files + int classNameSymbolId = bufferedFile.getVarInt(); + classIdToClassNameSymbolId.put(classId, classNameSymbolId); // class name + bufferedFile.getVarInt(); // package symbol id + bufferedFile.getVarInt(); // access flags + } + } + + private void readMethodConstants(int count) throws IOException { + for (int i = 0; i < count; i++) { + long id = bufferedFile.getVarLong(); + int classId = bufferedFile.getVarInt(); + // symbol ids are incrementing integers, no way there are more than 2 billion distinct + // ones + int methodNameSymbolId = bufferedFile.getVarInt(); + methodIdToFrame.put(id, FRAME_NULL); + methodIdToClassId.put(id, classId); + methodIdToMethodNameSymbol.put(id, methodNameSymbolId); + bufferedFile.getVarLong(); // signature + bufferedFile.getVarInt(); // modifiers + bufferedFile.get(); // hidden } } - private void skipString() throws IOException { - int stringLength = bufferedFile.getUnsignedShort(); - bufferedFile.skip(stringLength); + private void readPackageConstants(int count) throws IOException { + for (int i = 0; i < count; i++) { + bufferedFile.getVarLong(); // id + bufferedFile.getVarLong(); // symbol-id of package name + } + } + + private void readThreadConstants(int count) throws IOException { + for (int i = 0; i < count; i++) { + int nativeThreadId = bufferedFile.getVarInt(); + bufferedFile.skipString(); // native thread name + bufferedFile.getVarInt(); // native thread ID again + bufferedFile.skipString(); // java thread name + long javaThreadId = bufferedFile.getVarLong(); + if (javaThreadId != 0) { // javaThreadId will be null for native-only threads + nativeTidToJavaTid.put(nativeThreadId, javaThreadId); + } + } + } + + private void readStackTraceConstants(int count) throws IOException { + for (int i = 0; i < count; i++) { + + int stackTraceId = bufferedFile.getVarInt(); + bufferedFile.get(); // truncated byte, always zero anyway + + this.stackTraceIdToFilePositions.put(stackTraceId, (int) bufferedFile.position()); + // We need to skip the stacktrace to get to the position of the next one + readOrSkipStacktraceFrames(null, 0); + } + } + + private void readFrameTypeConstants(int count) throws IOException { + isJavaFrameType = new boolean[count]; + for (int i = 0; i < count; i++) { + int id = bufferedFile.getVarInt(); + if (i != id) { + throw new IllegalStateException("Expecting ids to be incrementing"); + } + isJavaFrameType[id] = JAVA_FRAME_TYPES.contains(bufferedFile.readString()); + } } /** @@ -280,31 +306,28 @@ public void consumeStackTraces(StackTraceConsumer callback) throws IOException { if (!bufferedFile.isSet()) { throw new IllegalStateException("consumeStackTraces was called before parse"); } - bufferedFile.position(eventsOffset); - while (bufferedFile.position() < metadataOffset) { - bufferedFile.ensureRemaining(30); - int size = bufferedFile.getUnsafeInt(); - int eventType = bufferedFile.getUnsafeInt(); - if (eventType == EventTypeId.EVENT_RECORDING) { - return; - } - if (eventType != EventTypeId.EVENT_EXECUTION_SAMPLE) { - throw new IOException( - "Expected " + EventTypeId.EVENT_EXECUTION_SAMPLE + " but got " + eventType); - } - long nanoTime = bufferedFile.getUnsafeLong(); - int tid = bufferedFile.getUnsafeInt(); - long stackTraceId = bufferedFile.getUnsafeLong(); - short threadState = bufferedFile.getUnsafeShort(); - long javaThreadId = nativeTidToJavaTid.get(tid); - if (javaThreadId != -1) { + bufferedFile.position(eventsFilePosition); + long fileSize = bufferedFile.size(); + long eventStart = eventsFilePosition; + while (eventStart < fileSize) { + bufferedFile.position(eventStart); + int eventSize = bufferedFile.getVarInt(); + long eventType = bufferedFile.getVarLong(); + if (eventType == EventTypeId.EVENT_EXECUTION_SAMPLE) { + long nanoTime = bufferedFile.getVarLong(); + int tid = bufferedFile.getVarInt(); + int stackTraceId = bufferedFile.getVarInt(); + bufferedFile.getVarInt(); // thread state + long javaThreadId = nativeTidToJavaTid.get(tid); callback.onCallTree(javaThreadId, stackTraceId, nanoTime); } + eventStart += eventSize; } } /** - * Resolves the stack trace with the given {@code stackTraceId}. + * Resolves the stack trace with the given {@code stackTraceId}. Only java frames will be + * included. * *

Note that his allocates strings for symbols in case a stack frame has not already been * resolved for the current JFR file yet. These strings are currently not cached so this can @@ -315,9 +338,6 @@ public void consumeStackTraces(StackTraceConsumer callback) throws IOException { * * @param stackTraceId The id of the stack traced. Used to look up the position of the file in * which the given stack trace is stored via {@link #stackTraceIdToFilePositions}. - * @param onlyJavaFrames If {@code true}, will only resolve {@code Interpreted}, {@code JIT - * compiled} and {@code Inlined} frames. If {@code false}, will also resolve {@code Native}, - * {@code Kernel} and {@code C++} frames. * @param stackFrames The mutable list where the stack frames are written to. Don't forget to * {@link List#clear()} the list before calling this method if the list is reused. * @param maxStackDepth The max size of the stackFrames list (excluded frames don't take up @@ -326,37 +346,36 @@ public void consumeStackTraces(StackTraceConsumer callback) throws IOException { * without making it overly complex. * @throws IOException if there is an error reading in current buffer */ - public void resolveStackTrace( - long stackTraceId, boolean onlyJavaFrames, List stackFrames, int maxStackDepth) + public void resolveStackTrace(long stackTraceId, List stackFrames, int maxStackDepth) throws IOException { if (!bufferedFile.isSet()) { throw new IllegalStateException("getStackTrace was called before parse"); } - long position = bufferedFile.position(); bufferedFile.position(stackTraceIdToFilePositions.get((int) stackTraceId)); - bufferedFile.ensureRemaining(13); - long stackTraceIdFromFile = bufferedFile.getUnsafeLong(); - assert stackTraceId == stackTraceIdFromFile; - bufferedFile.getUnsafe(); // truncated - int numFrames = bufferedFile.getUnsafeInt(); - for (int i = 0; i < numFrames; i++) { - bufferedFile.ensureRemaining(13); - long frameId = bufferedFile.getUnsafeLong(); - bufferedFile.getUnsafeInt(); // bci (always set to 0 by async-profiler) - byte frameType = bufferedFile.getUnsafe(); - addFrameIfIncluded(stackFrames, onlyJavaFrames, frameId, frameType); - if (stackFrames.size() > maxStackDepth) { - stackFrames.remove(0); + readOrSkipStacktraceFrames(stackFrames, maxStackDepth); + } + + private void readOrSkipStacktraceFrames(@Nullable List stackFrames, int maxStackDepth) + throws IOException { + int frameCount = bufferedFile.getVarInt(); + for (int i = 0; i < frameCount; i++) { + int methodId = bufferedFile.getVarInt(); + bufferedFile.getVarInt(); // line number + bufferedFile.getVarInt(); // bytecode index + byte type = bufferedFile.get(); + if (stackFrames != null) { + addFrameIfIncluded(stackFrames, methodId, type); + if (stackFrames.size() > maxStackDepth) { + stackFrames.remove(0); + } } } - bufferedFile.position(position); } - private void addFrameIfIncluded( - List stackFrames, boolean onlyJavaFrames, long frameId, byte frameType) + private void addFrameIfIncluded(List stackFrames, int methodId, byte frameType) throws IOException { - if (!onlyJavaFrames || isJavaFrameType(frameType)) { - StackFrame stackFrame = resolveStackFrame(frameId); + if (isJavaFrameType(frameType)) { + StackFrame stackFrame = resolveStackFrame(methodId); if (stackFrame != FRAME_EXCLUDED) { stackFrames.add(stackFrame); } @@ -372,7 +391,18 @@ private String resolveSymbol(int id, boolean classSymbol) throws IOException { if (symbol != SYMBOL_NULL) { return symbol; } - StringBuilder symbolBuilder = resolveSymbolBuilder(symbolIdToPos.get(id), classSymbol); + + long previousPosition = bufferedFile.position(); + int position = symbolIdToPos.get(id); + bufferedFile.position(position); + symbolBuilder.setLength(0); + bufferedFile.readString(symbolBuilder); + bufferedFile.position(previousPosition); + + if (classSymbol) { + replaceSlashesWithDots(symbolBuilder); + } + if (classSymbol && !isClassIncluded(symbolBuilder)) { symbol = SYMBOL_EXCLUDED; } else { @@ -382,14 +412,11 @@ private String resolveSymbol(int id, boolean classSymbol) throws IOException { return symbol; } - private StringBuilder resolveSymbolBuilder(int pos, boolean replaceSlashWithDot) - throws IOException { - long currentPos = bufferedFile.position(); - bufferedFile.position(pos); - try { - return readUtf8String(replaceSlashWithDot); - } finally { - bufferedFile.position(currentPos); + private static void replaceSlashesWithDots(StringBuilder builder) { + for (int i = 0; i < builder.length(); i++) { + if (builder.charAt(i) == '/') { + builder.setCharAt(i, '.'); + } } } @@ -399,53 +426,33 @@ private boolean isClassIncluded(CharSequence className) { } private StackFrame resolveStackFrame(long frameId) throws IOException { - StackFrame stackFrame = frameIdToFrame.get(frameId); + StackFrame stackFrame = methodIdToFrame.get(frameId); if (stackFrame != FRAME_NULL) { return stackFrame; } String className = - resolveSymbol(classIdToClassNameSymbolId.get((int) frameIdToClassId.get(frameId)), true); + resolveSymbol(classIdToClassNameSymbolId.get((int) methodIdToClassId.get(frameId)), true); if (className == SYMBOL_EXCLUDED) { stackFrame = FRAME_EXCLUDED; } else { - String method = resolveSymbol((int) frameIdToMethodSymbol.get(frameId), false); + String method = resolveSymbol((int) methodIdToMethodNameSymbol.get(frameId), false); stackFrame = new StackFrame(className, Objects.requireNonNull(method)); } - frameIdToFrame.put(frameId, stackFrame); + methodIdToFrame.put(frameId, stackFrame); return stackFrame; } - private StringBuilder readUtf8String() throws IOException { - return readUtf8String(false); - } - - private StringBuilder readUtf8String(boolean replaceSlashWithDot) throws IOException { - int size = bufferedFile.getUnsignedShort(); - bufferedFile.ensureRemaining(size); - StringBuilder symbolBuilder = this.symbolBuilder; - symbolBuilder.setLength(0); - for (int i = 0; i < size; i++) { - char c = (char) bufferedFile.getUnsafe(); - if (replaceSlashWithDot && c == '/') { - symbolBuilder.append('.'); - } else { - symbolBuilder.append(c); - } - } - return symbolBuilder; - } - @Override public void resetState() { bufferedFile.resetState(); - eventsOffset = 0; - metadataOffset = 0; + eventsFilePosition = 0; + metadataFilePosition = 0; isJavaFrameType = null; classIdToClassNameSymbolId.clear(); stackTraceIdToFilePositions.clear(); - frameIdToFrame.clear(); - frameIdToMethodSymbol.clear(); - frameIdToClassId.clear(); + methodIdToFrame.clear(); + methodIdToMethodNameSymbol.clear(); + methodIdToClassId.clear(); symbolBuilder.setLength(0); excludedClasses = null; includedClasses = null; @@ -459,7 +466,7 @@ public interface StackTraceConsumer { * @param threadId The {@linkplain Thread#getId() Java thread id} for with the event was * recorded. * @param stackTraceId The id of the stack trace event. Can be used to resolve the stack trace - * via {@link #resolveStackTrace(long, boolean, List, int)} + * via {@link #resolveStackTrace(long, List, int)} * @param nanoTime The timestamp of the event which can be correlated with {@link * System#nanoTime()} * @throws IOException if there is any error reading stack trace @@ -470,19 +477,23 @@ public interface StackTraceConsumer { private interface EventTypeId { int EVENT_METADATA = 0; int EVENT_CHECKPOINT = 1; - int EVENT_RECORDING = 10; - int EVENT_EXECUTION_SAMPLE = 20; + + // The following event types actually are defined in the metadata of the JFR file itself + // for simplicity and performance, we hardcode the values used by the async-profiler + // implementation + int EVENT_EXECUTION_SAMPLE = 101; } private interface ContentTypeId { - int CONTENT_THREAD = 7; - int CONTENT_JAVA_THREAD = 8; - int CONTENT_STACKTRACE = 9; - int CONTENT_CLASS = 10; - int CONTENT_THREAD_GROUP = 31; - int CONTENT_METHOD = 32; - int CONTENT_SYMBOL = 33; - int CONTENT_STATE = 34; - int CONTENT_FRAME_TYPE = 47; + int CONTENT_THREAD = 22; + int CONTENT_LOG_LEVELS = 33; + int CONTENT_STACKTRACE = 26; + int CONTENT_CLASS = 21; + int CONTENT_METHOD = 28; + int CONTENT_SYMBOL = 31; + int CONTENT_THREAD_STATE = 25; + int CONTENT_FRAME_TYPE = 24; + int CONTENT_GC_WHEN = 32; + int CONTENT_PACKAGE = 30; } } diff --git a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-aarch64.so b/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-aarch64.so deleted file mode 100755 index cbbfad660..000000000 Binary files a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-aarch64.so and /dev/null differ diff --git a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-arm.so b/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-arm.so deleted file mode 100755 index 1ddf05689..000000000 Binary files a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-arm.so and /dev/null differ diff --git a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-x64.so b/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-x64.so deleted file mode 100755 index 4da269e73..000000000 Binary files a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-x64.so and /dev/null differ diff --git a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-x86.so b/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-x86.so deleted file mode 100755 index c90985fc7..000000000 Binary files a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-linux-x86.so and /dev/null differ diff --git a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-macos-x64.so b/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-macos-x64.so deleted file mode 100755 index c6c66e27d..000000000 Binary files a/inferred-spans/src/main/resources/asyncprofiler/libasyncProfiler-macos-x64.so and /dev/null differ diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/CallTreeSpanifyTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/CallTreeSpanifyTest.java index c0349f205..ce04652b5 100644 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/CallTreeSpanifyTest.java +++ b/inferred-spans/src/test/java/co/elastic/otel/profiler/CallTreeSpanifyTest.java @@ -22,7 +22,6 @@ import co.elastic.otel.common.ElasticAttributes; import co.elastic.otel.profiler.pooling.ObjectPool; -import co.elastic.otel.testing.DisabledOnAppleSilicon; import co.elastic.otel.testing.DisabledOnOpenJ9; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.TraceFlags; @@ -52,7 +51,6 @@ class CallTreeSpanifyTest { @Test @DisabledOnOs(OS.WINDOWS) - @DisabledOnAppleSilicon @DisabledOnOpenJ9 void testSpanification() throws Exception { FixedClock nanoClock = new FixedClock(); diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/CallTreeTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/CallTreeTest.java index 100ad0ad8..49bc4cc16 100644 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/CallTreeTest.java +++ b/inferred-spans/src/test/java/co/elastic/otel/profiler/CallTreeTest.java @@ -23,7 +23,6 @@ import co.elastic.otel.common.ElasticAttributes; import co.elastic.otel.profiler.pooling.ObjectPool; -import co.elastic.otel.testing.DisabledOnAppleSilicon; import co.elastic.otel.testing.DisabledOnOpenJ9; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; @@ -53,7 +52,6 @@ import org.junit.jupiter.api.condition.OS; @DisabledOnOs(OS.WINDOWS) -@DisabledOnAppleSilicon @DisabledOnOpenJ9 class CallTreeTest { diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/InferredSpansAutoConfigTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/InferredSpansAutoConfigTest.java index e52bcba53..e64697c39 100644 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/InferredSpansAutoConfigTest.java +++ b/inferred-spans/src/test/java/co/elastic/otel/profiler/InferredSpansAutoConfigTest.java @@ -24,7 +24,6 @@ import co.elastic.otel.common.config.WildcardMatcher; import co.elastic.otel.testing.AutoConfigTestProperties; import co.elastic.otel.testing.AutoConfiguredDataCapture; -import co.elastic.otel.testing.DisabledOnAppleSilicon; import co.elastic.otel.testing.DisabledOnOpenJ9; import co.elastic.otel.testing.OtelReflectionUtils; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -33,6 +32,7 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.trace.SpanProcessor; +import java.nio.file.Path; import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; @@ -42,6 +42,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; public class InferredSpansAutoConfigTest { @@ -53,7 +54,9 @@ public void resetGlobalOtel() { } @Test - public void checkAllOptions() { + @DisabledOnOpenJ9 + public void checkAllOptions(@TempDir Path tmpDir) { + String libDir = tmpDir.resolve("foo").resolve("bar").toString(); try (AutoConfigTestProperties props = new AutoConfigTestProperties() .put(InferredSpansAutoConfig.ENABLED_OPTION, "true") @@ -67,7 +70,7 @@ public void checkAllOptions() { .put(InferredSpansAutoConfig.EXCLUDED_CLASSES_OPTION, "blub,test*.test2") .put(InferredSpansAutoConfig.INTERVAL_OPTION, "2s") .put(InferredSpansAutoConfig.DURATION_OPTION, "3s") - .put(InferredSpansAutoConfig.LIB_DIRECTORY_OPTION, "/tmp/somewhere")) { + .put(InferredSpansAutoConfig.LIB_DIRECTORY_OPTION, libDir)) { OpenTelemetry otel = GlobalOpenTelemetry.get(); List processors = OtelReflectionUtils.getSpanProcessors(otel); @@ -90,7 +93,7 @@ public void checkAllOptions() { .containsExactly("blub", "test*.test2"); assertThat(config.getProfilingInterval()).isEqualTo(Duration.ofSeconds(2)); assertThat(config.getProfilingDuration()).isEqualTo(Duration.ofSeconds(3)); - assertThat(config.getProfilerLibDirectory()).isEqualTo("/tmp/somewhere"); + assertThat(config.getProfilerLibDirectory()).isEqualTo(libDir); } } @@ -103,7 +106,6 @@ public void checkDisabledbyDefault() { } } - @DisabledOnAppleSilicon @DisabledOnOpenJ9 @DisabledOnOs(OS.WINDOWS) @Test diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/SamplingProfilerQueueTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/SamplingProfilerQueueTest.java index 756278fd8..2e8926f1a 100644 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/SamplingProfilerQueueTest.java +++ b/inferred-spans/src/test/java/co/elastic/otel/profiler/SamplingProfilerQueueTest.java @@ -20,7 +20,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import co.elastic.otel.testing.DisabledOnAppleSilicon; import co.elastic.otel.testing.DisabledOnOpenJ9; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; @@ -34,7 +33,6 @@ public class SamplingProfilerQueueTest { @Test @DisabledOnOs(OS.WINDOWS) - @DisabledOnAppleSilicon @DisabledOnOpenJ9 void testFillQueue() throws Exception { diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/SamplingProfilerTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/SamplingProfilerTest.java index ea6b41712..4cce41e57 100644 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/SamplingProfilerTest.java +++ b/inferred-spans/src/test/java/co/elastic/otel/profiler/SamplingProfilerTest.java @@ -21,7 +21,6 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.awaitility.Awaitility.await; -import co.elastic.otel.testing.DisabledOnAppleSilicon; import co.elastic.otel.testing.DisabledOnOpenJ9; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; @@ -51,7 +50,6 @@ // async-profiler doesn't work on Windows @DisabledOnOs(OS.WINDOWS) -@DisabledOnAppleSilicon @DisabledOnOpenJ9 class SamplingProfilerTest { @@ -160,7 +158,7 @@ void testStartCommand() { setupProfiler(config -> config.startScheduledProfiling(false).profilerLoggingEnabled(false)); assertThat(setup.profiler.createStartCommand()) .isEqualTo( - "start,jfr,event=wall,cstack=n,interval=5ms,filter,file=null,safemode=0,log=none"); + "start,jfr,event=wall,cstack=n,interval=5ms,filter,file=null,safemode=0,loglevel=none"); setup.close(); setupProfiler( @@ -172,7 +170,7 @@ void testStartCommand() { .asyncProfilerSafeMode(14)); assertThat(setup.profiler.createStartCommand()) .isEqualTo( - "start,jfr,event=wall,cstack=n,interval=10ms,filter,file=null,safemode=14,log=none"); + "start,jfr,event=wall,cstack=n,interval=10ms,filter,file=null,safemode=14,loglevel=none"); } @Test diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfilerTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfilerTest.java deleted file mode 100644 index d2f96b228..000000000 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfilerTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.otel.profiler.asyncprofiler; - -import static co.elastic.otel.profiler.asyncprofiler.AsyncProfiler.SAFEMODE_SYSTEM_PROPERTY_NAME; -import static org.assertj.core.api.Assertions.assertThat; - -import co.elastic.otel.testing.DisabledOnAppleSilicon; -import co.elastic.otel.testing.DisabledOnOpenJ9; -import java.io.File; -import java.io.FilenameFilter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.api.io.TempDir; - -@DisabledOnOs(OS.WINDOWS) -@DisabledOnAppleSilicon -@DisabledOnOpenJ9 -public class AsyncProfilerTest { - - @BeforeEach - void setUp() { - AsyncProfiler.reset(); - } - - @Test - void testShouldCopyLibToTempDirectory() { - String defaultTempDirectory = System.getProperty("java.io.tmpdir"); - AsyncProfiler.getInstance(defaultTempDirectory, 0); - assertThat(Integer.valueOf(System.getProperty(SAFEMODE_SYSTEM_PROPERTY_NAME))).isEqualTo(0); - - File libDirectory = new File(defaultTempDirectory); - File[] libasyncProfilers = libDirectory.listFiles(getLibasyncProfilerFilenameFilter()); - assertThat(libasyncProfilers).hasSizeGreaterThanOrEqualTo(1); - } - - @Test - void testShouldCopyLibToSpecifiedDirectory(@TempDir File nonDefaultTempDirectory) { - AsyncProfiler.getInstance(nonDefaultTempDirectory.getAbsolutePath(), 6); - assertThat(Integer.valueOf(System.getProperty(SAFEMODE_SYSTEM_PROPERTY_NAME))).isEqualTo(6); - - File[] libasyncProfilers = - nonDefaultTempDirectory.listFiles(getLibasyncProfilerFilenameFilter()); - assertThat(libasyncProfilers).hasSizeGreaterThanOrEqualTo(1); - } - - private FilenameFilter getLibasyncProfilerFilenameFilter() { - return (dir, name) -> name.startsWith("libasyncProfiler") && name.endsWith(".so"); - } -} diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfilerUpgrader.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfilerUpgrader.java deleted file mode 100644 index 599f39ff5..000000000 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/AsyncProfilerUpgrader.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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.otel.profiler.asyncprofiler; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.PosixFilePermission; -import java.util.Set; -import java.util.zip.GZIPInputStream; -import javax.annotation.Nullable; -import one.profiler.AsyncProfiler; -import org.apache.commons.compress.archivers.ArchiveEntry; -import org.apache.commons.compress.archivers.ArchiveInputStream; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.kohsuke.github.GHAsset; -import org.kohsuke.github.GHRelease; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.PagedIterable; - -/** - * This test class is disabled by default. It is used as a utility for manually upgrading async - * profiler - update the {@link #TARGET_VERSION} and run on a POSIX-compatible file system. - */ -@Disabled -public class AsyncProfilerUpgrader { - - static final String TARGET_VERSION = "1.8.8"; - static final String TAR_GZ_FILE_EXTENSION = ".tar.gz"; - static final String COMMON_BINARY_FILE_NAME = "libasyncProfiler.so"; - - static final String[] USED_ARTIFACTS = { - "linux-aarch64", "linux-arm", "linux-x64", "linux-x86", "macos-x64" - }; - - @Test - void updateAsyncProfilerBinaries() throws Exception { - GitHub github = GitHub.connectAnonymously(); - GHRepository repository = github.getRepository("jvm-profiling-tools/async-profiler"); - GHRelease release = repository.getReleaseByTagName("v" + TARGET_VERSION); - PagedIterable releaseAssets = release.listAssets(); - Path downloadDirPath = - Files.createTempDirectory(String.format("AsyncProfiler_%s_", TARGET_VERSION)); - for (GHAsset releaseAsset : releaseAssets) { - if (releaseAsset.getContentType().equals("application/x-gzip")) { - downloadAndReplaceBinary( - releaseAsset.getBrowserDownloadUrl(), - releaseAsset.getName(), - downloadDirPath, - releaseAsset.getSize()); - } - } - - // test we are now using the right version - Path thisOsLib = - getBinariesResourceDir() - .resolve( - co.elastic.otel.profiler.asyncprofiler.AsyncProfiler.getLibraryFileName() + ".so"); - AsyncProfiler asyncProfiler = AsyncProfiler.getInstance(thisOsLib.toString()); - assertThat(asyncProfiler.getVersion()).isEqualTo(TARGET_VERSION); - } - - private void downloadAndReplaceBinary( - String ghAssetDownloadUrl, String ghAssetName, Path targetDownloadDir, long expectedSize) - throws Exception { - String artifactNamePattern = null; - for (String artifact : USED_ARTIFACTS) { - if (ghAssetName.contains(artifact)) { - artifactNamePattern = artifact; - break; - } - } - if (artifactNamePattern == null) { - System.out.println( - ghAssetName + " is not within the list of used artifacts, skipping download"); - return; - } - Path targetDownloadPath = targetDownloadDir.resolve(ghAssetName); - System.out.println( - String.format( - "Downloading from %s into %s and extracting binary", ghAssetName, targetDownloadDir)); - Path localBinaryPath = - downloadAndExtractBinary(ghAssetDownloadUrl, targetDownloadPath, expectedSize); - assertThat(localBinaryPath) - .describedAs("Failed to download and extract binary file from " + ghAssetDownloadUrl) - .isNotNull(); - System.out.println( - String.format("Binary file for %s was extracted into %s", ghAssetName, localBinaryPath)); - replaceBinary(localBinaryPath, artifactNamePattern); - } - - @Nullable - private Path downloadAndExtractBinary( - String ghAssetDownloadUrl, Path targetDownloadPath, long expectedSize) throws IOException { - System.out.println("Downloading from " + ghAssetDownloadUrl); - URLConnection assetUrlConnection = new URL(ghAssetDownloadUrl).openConnection(); - long actualSize; - try (InputStream in = assetUrlConnection.getInputStream()) { - actualSize = Files.copy(in, targetDownloadPath); - } - assertThat(actualSize).isEqualTo(expectedSize); - return extractBinaryFileFromArchive(targetDownloadPath); - } - - @Nullable - private Path extractBinaryFileFromArchive(Path assetArchivePath) throws IOException { - String archiveFileName = assetArchivePath.getFileName().toString(); - if (!archiveFileName.endsWith(TAR_GZ_FILE_EXTENSION)) { - throw new IllegalArgumentException( - String.format( - "Cannot extract %s - expecting a path to a %s file", - archiveFileName, TAR_GZ_FILE_EXTENSION)); - } - - Path assetDirPath = assetArchivePath.getParent(); - if (!Files.exists(assetDirPath)) { - Files.createDirectory(assetDirPath); - } - String extractedDirName = - archiveFileName.substring(0, archiveFileName.length() - TAR_GZ_FILE_EXTENSION.length()); - Path extractedDirPath = assetDirPath.resolve(extractedDirName); - if (!Files.exists(extractedDirPath)) { - Files.createDirectory(extractedDirPath); - } - - Path binaryFilePath = null; - try (InputStream fis = Files.newInputStream(assetArchivePath); - GZIPInputStream gis = new GZIPInputStream(fis); - ArchiveInputStream ais = new TarArchiveInputStream(gis)) { - - ArchiveEntry entry; - while ((entry = ais.getNextEntry()) != null) { - if (!ais.canReadEntryData(entry)) { - throw new IllegalStateException("Cannot read an archive entry - " + entry.getName()); - } - if (entry.getName().endsWith(COMMON_BINARY_FILE_NAME)) { - Path filePath = extractedDirPath.resolve(COMMON_BINARY_FILE_NAME); - Files.copy(ais, filePath); - binaryFilePath = filePath; - } - } - } - return binaryFilePath; - } - - /** - * Replaces an existing binary file with its downloaded counterpart. - * - *

NOTE: when replacing the existing binary file, this method attempts to apply the current - * binary file's permissions to the one replacing it, assuming the underlying file system is - * POSIX-compatible. If this is not the case, and error will occur - * - * @param downloadedArtifact the path to the downloaded binary file - * @param artifactName the name of the artifact to replace, see {@link #USED_ARTIFACTS} - * @throws Exception thrown when an error occurs while trying to replace, or when running on non - * POSIX file system - */ - private void replaceBinary(Path downloadedArtifact, String artifactName) throws Exception { - if (!downloadedArtifact.toString().contains(artifactName)) { - throw new IllegalArgumentException( - String.format( - "the provided path for the downloaded artifact [%s] must " - + "be of a file containing the provided artifact name: %s", - downloadedArtifact, artifactName)); - } - - Path binariesResourceDir = getBinariesResourceDir(); - String binaryResourceName = String.format("libasyncProfiler-%s.so", artifactName); - Path binaryResourcePath = binariesResourceDir.resolve(binaryResourceName); - if (!Files.exists(binaryResourcePath)) { - throw new IllegalStateException( - String.format("Expected binary file does not exist: %s", binaryResourcePath.toString())); - } - System.out.println( - String.format("Replacing %s with %s", binaryResourcePath, downloadedArtifact)); - Set posixFilePermissions = - Files.getPosixFilePermissions(binaryResourcePath); - Files.move(downloadedArtifact, binaryResourcePath, StandardCopyOption.REPLACE_EXISTING); - Files.setPosixFilePermissions(binaryResourcePath, posixFilePermissions); - } - - private Path getBinariesResourceDir() throws URISyntaxException { - // /inferred-spans/build/resources/main/asyncprofiler - Path asyncProfilerTestResourcePath = - Paths.get(AsyncProfilerUpgrader.class.getResource("/asyncprofiler").toURI()); - // /inferred-spans/build/resources/main/asyncprofiler - Path projectRootDir = - asyncProfilerTestResourcePath.getParent().getParent().getParent().getParent(); - // We are looking for // - // </inferred-spans/src/main/resources/asyncprofiler - return projectRootDir - .resolve("src") - .resolve("main") - .resolve("resources") - .resolve("asyncprofiler"); - } -} diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/JfrParserTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/JfrParserTest.java index 0302ae4fa..5e9fd1eed 100644 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/JfrParserTest.java +++ b/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/JfrParserTest.java @@ -36,9 +36,9 @@ class JfrParserTest { @Test void name() throws Exception { - // using the smallest prime number possible for the buffer - // should trigger most edge cases in the buffer being exhausted - JfrParser jfrParser = new JfrParser(ByteBuffer.allocate(113), ByteBuffer.allocate(113)); + // Using a small buffer, but big enough to fit the largest string in the JFR file to test edge + // cases + JfrParser jfrParser = new JfrParser(ByteBuffer.allocate(368), ByteBuffer.allocate(368)); File file = Paths.get(JfrParserTest.class.getClassLoader().getResource("recording.jfr").toURI()) @@ -47,12 +47,12 @@ void name() throws Exception { jfrParser.parse( file, Collections.emptyList(), - Collections.singletonList(caseSensitiveMatcher("co.elastic.apm.*"))); + Collections.singletonList(caseSensitiveMatcher("co.elastic.otel.*"))); AtomicInteger stackTraces = new AtomicInteger(); ArrayList stackFrames = new ArrayList<>(); jfrParser.consumeStackTraces( (threadId, stackTraceId, nanoTime) -> { - jfrParser.resolveStackTrace(stackTraceId, true, stackFrames, MAX_STACK_DEPTH); + jfrParser.resolveStackTrace(stackTraceId, stackFrames, MAX_STACK_DEPTH); if (!stackFrames.isEmpty()) { stackTraces.incrementAndGet(); assertThat(stackFrames.get(stackFrames.size() - 1).getMethodName()) @@ -61,6 +61,6 @@ void name() throws Exception { } stackFrames.clear(); }); - assertThat(stackTraces.get()).isEqualTo(97); + assertThat(stackTraces.get()).isEqualTo(98); } } diff --git a/inferred-spans/src/test/resources/recording.jfr b/inferred-spans/src/test/resources/recording.jfr index f8122c798..030c3f5a9 100644 Binary files a/inferred-spans/src/test/resources/recording.jfr and b/inferred-spans/src/test/resources/recording.jfr differ diff --git a/licenses/more-licences.md b/licenses/more-licences.md index 3104d919d..f5a71bc33 100644 --- a/licenses/more-licences.md +++ b/licenses/more-licences.md @@ -91,7 +91,7 @@ > - **Embedded license files**: [byte-buddy-dep-1.14.13.jar/META-INF/LICENSE](byte-buddy-dep-1.14.13.jar/META-INF/LICENSE) - [byte-buddy-dep-1.14.13.jar/META-INF/NOTICE](byte-buddy-dep-1.14.13.jar/META-INF/NOTICE) -**22** **Group:** `org.jctools` **Name:** `jctools-core` **Version:** `4.0.1` +**22** **Group:** `org.jctools` **Name:** `jctools-core` **Version:** `4.0.3` > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM Project URL**: [https://github.com/JCTools](https://github.com/JCTools) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) @@ -110,9 +110,13 @@ > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) +**25** **Group:** `tools.profiler` **Name:** `async-profiler` **Version:** `3.0` +> - **POM Project URL**: [https://profiler.tools](https://profiler.tools) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + ## Creative Commons Legal Code -**25** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` +**26** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` > - **Manifest License**: The 2-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://hdrhistogram.github.io/HdrHistogram/](http://hdrhistogram.github.io/HdrHistogram/) > - **POM License**: Creative Commons Legal Code - [https://creativecommons.org/publicdomain/zero/1.0/legalcode](https://creativecommons.org/publicdomain/zero/1.0/legalcode) @@ -121,7 +125,7 @@ ## PUBLIC DOMAIN -**26** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` +**27** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` > - **Manifest License**: The 2-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://hdrhistogram.github.io/HdrHistogram/](http://hdrhistogram.github.io/HdrHistogram/) > - **POM License**: Creative Commons Legal Code - [https://creativecommons.org/publicdomain/zero/1.0/legalcode](https://creativecommons.org/publicdomain/zero/1.0/legalcode) @@ -130,7 +134,7 @@ ## The 2-Clause BSD License -**27** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` +**28** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` > - **Manifest License**: The 2-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://hdrhistogram.github.io/HdrHistogram/](http://hdrhistogram.github.io/HdrHistogram/) > - **POM License**: Creative Commons Legal Code - [https://creativecommons.org/publicdomain/zero/1.0/legalcode](https://creativecommons.org/publicdomain/zero/1.0/legalcode) @@ -139,14 +143,14 @@ ## The 3-Clause BSD License -**28** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.6` +**29** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.6` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**29** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.6` +**30** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.6` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) diff --git a/testing/integration-tests/inferred-spans-test/src/test/java/InferredSpansTest.java b/testing/integration-tests/inferred-spans-test/src/test/java/InferredSpansTest.java index af7b9d6bd..2f51071da 100644 --- a/testing/integration-tests/inferred-spans-test/src/test/java/InferredSpansTest.java +++ b/testing/integration-tests/inferred-spans-test/src/test/java/InferredSpansTest.java @@ -20,7 +20,6 @@ import static org.awaitility.Awaitility.await; import co.elastic.otel.common.ElasticAttributes; -import co.elastic.otel.testing.DisabledOnAppleSilicon; import co.elastic.otel.testing.DisabledOnOpenJ9; import io.opentelemetry.instrumentation.annotations.WithSpan; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; @@ -33,7 +32,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; @DisabledOnOs(OS.WINDOWS) -@DisabledOnAppleSilicon @DisabledOnOpenJ9 public class InferredSpansTest {