Skip to content

Commit

Permalink
[GR-54674] Trace Polyglot Source Cache
Browse files Browse the repository at this point in the history
PullRequest: graal/18393
  • Loading branch information
jchalou committed Jul 27, 2024
2 parents f58fbc1 + ef551b2 commit aac25d1
Show file tree
Hide file tree
Showing 17 changed files with 587 additions and 29 deletions.
1 change: 1 addition & 0 deletions sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This changelog summarizes major changes between GraalVM SDK versions. The main f
* GR-40931 Using virtual threads in a native-image will now emulate virtual threads using platform threads until Loom support for Truffle languages in native-image is implemented.
* GR-48481: It is now possible to depend on platform-specific Maven artifacts when using polyglot isolates. The `-isolate` Maven artifacts remain platform-independent. However, if platform specific builds are required, consider adding a platform specific suffix to the Maven artifact name, such as `-isolate-linux-amd64`. The reduced binary size of platform specific dependencies can improve build times and reduce the distribution size of your application. For more details, see [Embedding Languages](https://www.graalvm.org/latest/reference-manual/embed-languages/#polyglot-isolates).
* GR-48481: The Python language is now available as a polyglot isolate.
* GR-54674: Added the options `engine.TraceSourceCache` and `engine.TraceSourceCacheDetails` to allow tracing of source cache hits, misses, failures and evictions.

## Version 24.0.0
* (GR-49334) Deprecated the `FileSystems#allowLanguageHomeAccess()` method and introduced `FileSystem#allowInternalResourceAccess()` as a replacement. To ensure compatibility, both methods now provide support for language homes and internal resources.
Expand Down
2 changes: 2 additions & 0 deletions truffle/docs/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ They are useful to users and language and tool implementers.
- `--engine.RelaxStaticObjectSafetyChecks` : On property accesses, the Static Object Model does not perform shape checks and uses unsafe casts
- `--engine.SynchronousThreadLocalActionMaxWait=[0, inf)` : How long to wait for other threads to reach a synchronous ThreadLocalAction before cancelling it, in seconds. 0 means no limit.
- `--engine.SynchronousThreadLocalActionPrintStackTraces` : Print thread stacktraces when a synchronous ThreadLocalAction is waiting for more than SynchronousThreadLocalActionMaxWait seconds.
- `--engine.TraceSourceCache` : Print information for source cache misses/evictions/failures.
- `--engine.TraceSourceCacheDetails` : Print information for all source cache events including hits and uncached misses.
- `--engine.TraceStackTraceInterval=[1, inf)` : Prints the stack trace for all threads for a time interval. By default 0, which disables the output.
- `--engine.DebugCacheCompileUseLastTier=true|false` : If true uses the last tier instead of the first tier compiler. By default the last tier compiler is used (default: true).
- `--engine.BackgroundCompilation=true|false` : Enable asynchronous truffle compilation in background threads (default: true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.api.test.source;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Source;
import org.junit.Assert;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.test.GCUtils;
import com.oracle.truffle.api.test.common.TestUtils;
import com.oracle.truffle.tck.tests.TruffleTestAssumptions;

public class SourceCacheTest {

@Test
public void testTraceSourceCache() throws Throwable {
testCommon(SourceCacheTestLanguage.ID, Map.of("engine.TraceSourceCache", "true"), "[miss]", null);
}

@Test
public void testTraceSourceCacheDetails() throws Throwable {
testCommon(SourceCacheTestLanguage.ID, Map.of("engine.TraceSourceCacheDetails", "true"), "[miss, hit]", null);
}

@Test
public void testTraceSourceCacheFailure() throws Throwable {
testCommon(SourceCacheFailureTestLanguage.ID, Map.of("engine.TraceSourceCache", "true"), "[fail]", "DummyParseException");
}

@Test
public void testTraceSourceCacheEviction() throws IOException {
TruffleTestAssumptions.assumeWeakEncapsulation();
try (ByteArrayOutputStream out = new ByteArrayOutputStream(); Context context = Context.newBuilder().option("engine.TraceSourceCache", "true").out(out).err(out).build()) {
Source auxiliarySource = Source.newBuilder(SourceCacheTestLanguage.ID, "x", "AuxiliarySource").build();
String sourceName = "TestSource";
String[] sourceHash = new String[1];
for (int i = 0; i < 2; i++) {
Source source = Source.newBuilder(SourceCacheTestLanguage.ID, "", sourceName).build();
int sourceHashCode = context.eval(source).asInt();
sourceHash[0] = String.format("0x%08x", sourceHashCode);
WeakReference<Source> souceRef = new WeakReference<>(source);
source = null;
GCUtils.assertGc("Source was not collected", souceRef);
context.eval(auxiliarySource);
}
List<String> logs = new ArrayList<>();
forEachLog(out.toByteArray(), (matcher) -> {
String logType = matcher.group(1);
if ("evict".equals(logType)) {
logs.add(logType);
Assert.assertEquals(sourceHash[0], matcher.group(2));
Assert.assertEquals(sourceName, matcher.group(3));
}
});
// at least one
Assert.assertFalse(logs.isEmpty());
Assert.assertEquals("evict", logs.get(1));
}
}

private static final Pattern LOG_PATTERN = Pattern.compile("^\\[engine] source-cache-(\\w+)\\s+(0x[0-9a-f]+) (\\w+).*UTC \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}((?:\\|Error .*)?)$");

private static void forEachLog(byte[] logBytes, Consumer<Matcher> logConsumer) throws IOException {
try (BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(logBytes)))) {
String line;
while ((line = br.readLine()) != null) {
Matcher matcher = LOG_PATTERN.matcher(line);
if (matcher.find()) {
logConsumer.accept(matcher);
}
}
}
}

private static void testCommon(String languageId, Map<String, String> options, String expectedLogs, String failMessage) throws Throwable {
try (ByteArrayOutputStream out = new ByteArrayOutputStream(); Context context = Context.newBuilder().options(options).out(out).err(out).build()) {
String sourceName = "TestSource";
Source source = Source.newBuilder(languageId, "", sourceName).build();
String[] sourceHash = new String[1];
ThrowingRunnable testRunnable = () -> {
int sourceHashCode = context.eval(source).asInt();
sourceHash[0] = String.format("0x%08x", sourceHashCode);
};
if (failMessage != null) {
Assert.assertThrows(PolyglotException.class, testRunnable);
} else {
testRunnable.run();
context.eval(source);
}
List<String> logs = new ArrayList<>();
forEachLog(out.toByteArray(), (matcher) -> {
String logType = matcher.group(1);
logs.add(logType);
if (!"fail".equals(logType)) {
Assert.assertEquals(sourceHash[0], matcher.group(2));
} else {
Assert.assertTrue(matcher.group().endsWith("Error " + failMessage));
}
Assert.assertEquals(sourceName, matcher.group(3));
});
Assert.assertEquals(expectedLogs, Arrays.toString(logs.toArray()));
}
}

@TruffleLanguage.Registration
static class SourceCacheTestLanguage extends TruffleLanguage<TruffleLanguage.Env> {
static final String ID = TestUtils.getDefaultLanguageId(SourceCacheTestLanguage.class);

@Override
protected Env createContext(Env env) {
return env;
}

@Override
protected CallTarget parse(ParsingRequest request) throws Exception {
int sourceHashCode = request.getSource().hashCode();
return new RootNode(this) {
@Override
public Object execute(VirtualFrame frame) {
return sourceHashCode;
}
}.getCallTarget();
}
}

@SuppressWarnings("serial")
static class ParseException extends AbstractTruffleException {

ParseException() {
super("DummyParseException");
}
}

@TruffleLanguage.Registration
static class SourceCacheFailureTestLanguage extends TruffleLanguage<TruffleLanguage.Env> {
static final String ID = TestUtils.getDefaultLanguageId(SourceCacheFailureTestLanguage.class);

@Override
protected Env createContext(Env env) {
return env;
}

@Override
protected CallTarget parse(ParsingRequest request) throws Exception {
throw new ParseException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,8 @@ public abstract <T, G> Iterator<T> mergeHostGuestFrames(Object polyglotEngine, S
public abstract void setIsolatePolyglot(AbstractPolyglotImpl instance);

public abstract Object getEngineData(Object polyglotEngine);

public abstract long getEngineId(Object polyglotEngine);
}

public abstract static class LanguageSupport extends Support {
Expand Down Expand Up @@ -1145,6 +1147,8 @@ protected RuntimeSupport(Object permission) {

public abstract RootCallTarget newCallTarget(CallTarget source, RootNode rootNode);

public abstract long getCallTargetId(CallTarget target);

public abstract boolean isLoaded(CallTarget callTarget);

public abstract void notifyOnLoad(CallTarget callTarget);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@

import static com.oracle.truffle.api.impl.DefaultTruffleRuntime.getRuntime;

import java.util.concurrent.atomic.AtomicLong;

import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.TruffleSafepoint;
import com.oracle.truffle.api.frame.VirtualFrame;
Expand All @@ -61,9 +63,14 @@ public final class DefaultCallTarget implements RootCallTarget {
private volatile boolean initialized;
private volatile boolean loaded;

public final long id;

private static final AtomicLong ID_COUNTER = new AtomicLong(0);

DefaultCallTarget(RootNode function) {
this.rootNode = function;
this.rootNode.adoptChildren();
this.id = ID_COUNTER.incrementAndGet();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ public RootCallTarget newCallTarget(CallTarget sourceCallTarget, RootNode rootNo
return new DefaultCallTarget(rootNode);
}

@Override
public long getCallTargetId(CallTarget target) {
if (target instanceof DefaultCallTarget) {
return ((DefaultCallTarget) target).id;
} else {
return 0;
}
}

@Override
public boolean isLegacyCompilerOption(String key) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
import java.util.logging.Level;
import java.util.logging.LogRecord;

import com.oracle.truffle.api.impl.JDKAccessor;
import org.graalvm.collections.Pair;
import org.graalvm.nativeimage.ImageInfo;
import org.graalvm.options.OptionKey;
Expand Down Expand Up @@ -107,6 +106,7 @@
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.TruffleSafepoint;
import com.oracle.truffle.api.impl.Accessor;
import com.oracle.truffle.api.impl.JDKAccessor;
import com.oracle.truffle.api.impl.TruffleLocator;
import com.oracle.truffle.api.instrumentation.ContextsListener;
import com.oracle.truffle.api.instrumentation.ProbeNode;
Expand Down Expand Up @@ -2184,6 +2184,11 @@ public void setIsolatePolyglot(AbstractPolyglotImpl instance) {
public Object getEngineData(Object polyglotEngine) {
return ((PolyglotEngineImpl) polyglotEngine).runtimeData;
}

@Override
public long getEngineId(Object polyglotEngine) {
return ((PolyglotEngineImpl) polyglotEngine).engineId;
}
}

abstract static class AbstractClassLoaderSupplier implements Supplier<ClassLoader> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3527,6 +3527,10 @@ boolean patch(PolyglotContextConfig newConfig) {
} finally {
engine.leave(prev, this);
}
PolyglotSharingLayer.Shared s = layer.shared;
if (s != null) {
s.sourceCache.patch(TracingSourceCacheListener.createOrNull(engine));
}
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Predicate;
Expand Down Expand Up @@ -144,6 +145,7 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm
static final int HOST_LANGUAGE_INDEX = 0;
static final String HOST_LANGUAGE_ID = "host";

private static final AtomicLong ENGINE_COUNTER = new AtomicLong();
static final String ENGINE_ID = "engine";
static final String OPTION_GROUP_ENGINE = ENGINE_ID;
static final String OPTION_GROUP_COMPILER = "compiler";
Expand Down Expand Up @@ -248,6 +250,8 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm

final InternalResourceRoots internalResourceRoots;

final long engineId;

@SuppressWarnings("unchecked")
PolyglotEngineImpl(PolyglotImpl impl, SandboxPolicy sandboxPolicy, String[] permittedLanguages,
DispatchOutputStream out, DispatchOutputStream err, InputStream in, OptionValuesImpl engineOptions,
Expand All @@ -256,6 +260,7 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm
boolean allowExperimentalOptions, boolean boundEngine, boolean preInitialization,
MessageTransport messageInterceptor, LogHandler logHandler,
TruffleLanguage<Object> hostImpl, boolean hostLanguageOnly, AbstractPolyglotHostService polyglotHostService) {
this.engineId = ENGINE_COUNTER.incrementAndGet();
this.apiAccess = impl.getAPIAccess();
this.sandboxPolicy = sandboxPolicy;
this.messageInterceptor = messageInterceptor;
Expand Down Expand Up @@ -496,6 +501,7 @@ void notifyCreated() {

@SuppressWarnings("unchecked")
PolyglotEngineImpl(PolyglotEngineImpl prototype) {
this.engineId = ENGINE_COUNTER.incrementAndGet();
this.apiAccess = prototype.apiAccess;
this.sandboxPolicy = prototype.sandboxPolicy;
this.messageInterceptor = prototype.messageInterceptor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ final class PolyglotEngineOptions {
"Asserts that enter and return are always called in pairs on ProbeNode, verifies correct behavior of wrapper nodes. Java asserts need to be turned on for this option to have an effect. (default: false)")//
static final OptionKey<Boolean> AssertProbes = new OptionKey<>(false);

@Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "Print information for source cache misses/evictions/failures.")//
static final OptionKey<Boolean> TraceSourceCache = new OptionKey<>(false);

@Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "Print information for all source cache events including hits and uncached misses.")//
static final OptionKey<Boolean> TraceSourceCacheDetails = new OptionKey<>(false);

enum StaticObjectStorageStrategies {
DEFAULT,
ARRAY_BASED,
Expand Down
Loading

0 comments on commit aac25d1

Please sign in to comment.