Skip to content

Commit 98608e1

Browse files
committed
[GR-55996] Implement source cache statistics for Truffle.
PullRequest: graal/20958
2 parents 6b2c0ff + 680a6b6 commit 98608e1

File tree

15 files changed

+842
-67
lines changed

15 files changed

+842
-67
lines changed

sdk/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ This changelog summarizes major changes between GraalVM SDK versions. The main f
1515
* GR-22699(EE-only) Added the ability to spawn `Engine` or `Context` isolated in a separate process by setting `Context.Builder.option("engine.IsolateMode", "external").`.
1616
* GR-64087 Removed the dependency on `org.graalvm.truffle:truffle-enterprise` from all language and tool POM artifacts. As a result, the community Maven artifacts (those with an artifact ID ending in `-community`) are now identical to their corresponding non-community artifacts. Consequently, all community language and tool POM artifacts have been deprecated. The only exception is `org.graalvm.truffle:java-community` vs. `org.graalvm.truffle:java`, which differ in the bundled Java runtime. Embedders using auxiliary engine caching, polyglot isolates, a isolated/untrusted sandbox policy, or the sandbox resource limits must now explicitly add the `org.graalvm.truffle:truffle-enterprise` Maven artifact to the classpath or module path.
1717
* GR-66339 Truffle now checks that its multi-release classes ([JEP 238](https://openjdk.org/jeps/238)) are loaded correctly and provides a descriptive error message if they are not.
18+
* GR-55996 Added the options `engine.SourceCacheStatistics` and `engine.SourceCacheStatisticDetails` to print polyglot source cache statistics on engine close.
19+
1820

1921
## Version 24.2.0
2022
* GR-54905 When using Truffle NFI with the Panama backend, native access must now be granted to the Truffle module instead of the NFI Panama module. Use the `--enable-native-access=org.graalvm.truffle` Java command line option to enable the native access for the NFI Panama backend.

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,8 @@ protected AbstractSourceDispatch(AbstractPolyglotImpl engineImpl) {
668668

669669
public abstract URI getURI(Object impl);
670670

671+
public abstract URI getOriginalURI(Object impl);
672+
671673
public abstract Reader getReader(Object impl);
672674

673675
public abstract InputStream getInputStream(Object impl);

truffle/docs/Options.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ They are useful to users and language and tool implementers.
5050
```shell
5151
- `--engine.PreinitializeContexts` : Preinitialize language contexts for given languages.
5252
- `--engine.RelaxStaticObjectSafetyChecks` : On property accesses, the Static Object Model does not perform shape checks and uses unsafe casts
53+
- `--engine.SourceCacheStatisticDetails` : Print source cache statistics for an engine when the engine is closed. With the details enabled, statistics for all individual sources are printed.
54+
- `--engine.SourceCacheStatistics` : Print source cache statistics for an engine when the engine is closed.
5355
- `--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.
5456
- `--engine.SynchronousThreadLocalActionPrintStackTraces` : Print thread stacktraces when a synchronous ThreadLocalAction is waiting for more than SynchronousThreadLocalActionMaxWait seconds.
5557
- `--engine.TraceSourceCache` : Print information for source cache misses/evictions/failures.

truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceCacheTest.java

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@
6464
import org.graalvm.polyglot.Engine;
6565
import org.graalvm.polyglot.PolyglotException;
6666
import org.graalvm.polyglot.Source;
67+
import org.graalvm.polyglot.io.ByteSequence;
6768
import org.junit.Assert;
6869
import org.junit.Test;
69-
import org.junit.function.ThrowingRunnable;
7070

7171
import com.oracle.truffle.api.CallTarget;
7272
import com.oracle.truffle.api.Option;
@@ -81,19 +81,74 @@
8181

8282
public class SourceCacheTest {
8383

84+
private static final String STATISTICS_RESULT_PATTERN_STRING = """
85+
\\[engine] Polyglot source cache statistics for engine \\d+\\s?
86+
--- SHARING LAYER \\d+; WEAK CACHE -------------------------\\s?
87+
Languages :\\s?
88+
com_oracle_truffle_api_test_source_sourcecachetest_sourcecachetestlanguage:\\s?
89+
Character Based Sources Stats :\\s?
90+
Sources : count= 1\\s?
91+
Size \\(C\\) : count= 1, sum= 0, min= 0, avg= 0\\.00, max= 0, maxSource=0x12ea5c6b TestSource\\s?
92+
Biggest Sources :\\s?
93+
0x12ea5c6b : size= 0, name=TestSource\\s?
94+
Cache : parse time\\(ms\\)= +\\d+, parse rate\\(C/s\\)= +(\\d+\\.\\d+|NaN), hits= 1, misses= 1, evictions= 0, failures= 0, hit rate=50%\\s?
95+
Parse Successful : count= 1\\s?
96+
Time \\(ms\\) : count= 1, sum= +\\d+, min= +\\d+, avg= +\\d+\\.\\d+, max= +\\d+, maxSource=0x12ea5c6b TestSource\\s?
97+
Size \\(C\\) : count= 1, sum= 0, min= 0, avg= 0\\.00, max= 0, maxSource=0x12ea5c6b TestSource\\s?
98+
Sources With Most Hits :\\s?
99+
0x12ea5c6b : parse time\\(ms\\)= +\\d+, parse rate\\(C/s\\)= +(\\d+\\.\\d+|NaN), hits= 1, misses= 1, evictions= 0, failures= 0, hit rate=50%, name=TestSource\\s?
100+
Sources With Most Misses :\\s?
101+
0x12ea5c6b : parse time\\(ms\\)= +\\d+, parse rate\\(C/s\\)= +(\\d+\\.\\d+|NaN), hits= 1, misses= 1, evictions= 0, failures= 0, hit rate=50%, name=TestSource\\s?
102+
Sources With Most Evictions:\\s?
103+
0x12ea5c6b : parse time\\(ms\\)= +\\d+, parse rate\\(C/s\\)= +(\\d+\\.\\d+|NaN), hits= 1, misses= 1, evictions= 0, failures= 0, hit rate=50%, name=TestSource\\s?
104+
Failures : count= 0\\s?
105+
Byte Based Sources Stats :\\s?
106+
Sources : count= 1\\s?
107+
Size \\(B\\) : count= 1, sum= 0, min= 0, avg= 0\\.00, max= 0, maxSource=0xb9ecef28 TestSource\\s?
108+
Biggest Sources :\\s?
109+
0xb9ecef28 : size= 0, name=TestSource\\s?
110+
Cache : parse time\\(ms\\)= +\\d+, parse rate\\(B/s\\)= +(\\d+\\.\\d+|NaN), hits= 1, misses= 1, evictions= 0, failures= 0, hit rate=50%\\s?
111+
Parse Successful : count= 1\\s?
112+
Time \\(ms\\) : count= 1, sum= +\\d+, min= +\\d+, avg= +\\d+\\.\\d+, max= +\\d+, maxSource=0xb9ecef28 TestSource\\s?
113+
Size \\(B\\) : count= 1, sum= 0, min= 0, avg= 0\\.00, max= 0, maxSource=0xb9ecef28 TestSource\\s?
114+
Sources With Most Hits :\\s?
115+
0xb9ecef28 : parse time\\(ms\\)= +\\d+, parse rate\\(B/s\\)= +(\\d+\\.\\d+|NaN), hits= 1, misses= 1, evictions= 0, failures= 0, hit rate=50%, name=TestSource\\s?
116+
Sources With Most Misses :\\s?
117+
0xb9ecef28 : parse time\\(ms\\)= +\\d+, parse rate\\(B/s\\)= +(\\d+\\.\\d+|NaN), hits= 1, misses= 1, evictions= 0, failures= 0, hit rate=50%, name=TestSource\\s?
118+
Sources With Most Evictions:\\s?
119+
0xb9ecef28 : parse time\\(ms\\)= +\\d+, parse rate\\(B/s\\)= +(\\d+\\.\\d+|NaN), hits= 1, misses= 1, evictions= 0, failures= 0, hit rate=50%, name=TestSource\\s?
120+
Failures : count= 0\\s?
121+
\\s?
122+
""";
123+
124+
private static final String FAILURE_RESULT_PATTERN_STRING = """
125+
Failures : count= 1\\s?
126+
Sources With Failures : count= 1\\s?
127+
0x7595adef : parse time\\(ms\\)= +\\d+, parse rate\\(C/s\\)= +(\\d+\\.\\d+|NaN), hits= 0, misses= 1, evictions= 0, failures= 1, hit rate= 0%, name=TestSource\\s?
128+
Failure #1 : count= 1, failure=com\\.oracle\\.truffle\\.api\\.test\\.source\\.SourceCacheTest\\$ParseException: DummyParseException\\s?
129+
[\\s\\S]*
130+
Failures : count= 1\\s?
131+
Sources With Failures : count= 1\\s?
132+
0x1c9840ac : parse time\\(ms\\)= +\\d+, parse rate\\(B/s\\)= +(\\d+\\.\\d+|NaN), hits= 0, misses= 1, evictions= 0, failures= 1, hit rate= 0%, name=TestSource\\s?
133+
Failure #1 : count= 1, failure=com\\.oracle\\.truffle\\.api\\.test\\.source\\.SourceCacheTest\\$ParseException: DummyParseException\\s?
134+
""";
135+
136+
private static final Pattern STATISTICS_RESULT_PATTERN = Pattern.compile(STATISTICS_RESULT_PATTERN_STRING);
137+
private static final Pattern FAILURE_RESULT_PATTERN = Pattern.compile(FAILURE_RESULT_PATTERN_STRING);
138+
84139
@Test
85140
public void testTraceSourceCache() throws Throwable {
86-
testCommon(SourceCacheTestLanguage.ID, Map.of("engine.TraceSourceCache", "true"), "[miss]", null);
141+
testCommon(SourceCacheTestLanguage.ID, Map.of("engine.TraceSourceCache", "true", "engine.SourceCacheStatistics", "true"), "[miss, miss]", null);
87142
}
88143

89144
@Test
90145
public void testTraceSourceCacheDetails() throws Throwable {
91-
testCommon(SourceCacheTestLanguage.ID, Map.of("engine.TraceSourceCacheDetails", "true"), "[miss, hit]", null);
146+
testCommon(SourceCacheTestLanguage.ID, Map.of("engine.TraceSourceCacheDetails", "true", "engine.SourceCacheStatistics", "true"), "[miss, miss, hit, hit]", null);
92147
}
93148

94149
@Test
95150
public void testTraceSourceCacheFailure() throws Throwable {
96-
testCommon(SourceCacheFailureTestLanguage.ID, Map.of("engine.TraceSourceCache", "true"), "[fail]", "DummyParseException");
151+
testCommon(SourceCacheFailureTestLanguage.ID, Map.of("engine.TraceSourceCache", "true", "engine.SourceCacheStatistics", "true"), "[fail, fail]", "DummyParseException");
97152
}
98153

99154
@Test
@@ -151,36 +206,58 @@ private static void forEachLog(byte[] logBytes, Consumer<Matcher> logConsumer) t
151206
}
152207

153208
private static void testCommon(String languageId, Map<String, String> options, String expectedLogs, String failMessage) throws Throwable {
154-
try (ByteArrayOutputStream out = new ByteArrayOutputStream(); Context context = Context.newBuilder().options(options).out(out).err(out).build()) {
209+
ByteArrayOutputStream out = new ByteArrayOutputStream();
210+
try (Context context = Context.newBuilder().options(options).out(out).err(out).build()) {
155211
String sourceName = "TestSource";
156-
Source source = Source.newBuilder(languageId, "", sourceName).build();
157-
String[] sourceHash = new String[1];
158-
ThrowingRunnable testRunnable = () -> {
159-
int sourceHashCode = context.eval(source).asInt();
160-
sourceHash[0] = String.format("0x%08x", sourceHashCode);
161-
};
212+
Source source1 = Source.newBuilder(languageId, "", sourceName).build();
213+
Source source2 = Source.newBuilder(languageId, ByteSequence.create(new byte[0]), sourceName).mimeType("text/binary").build();
214+
String[] sourceHash = new String[2];
162215
if (failMessage != null) {
163-
Assert.assertThrows(PolyglotException.class, testRunnable);
216+
Assert.assertThrows(PolyglotException.class, () -> context.eval(source1));
217+
Assert.assertThrows(PolyglotException.class, () -> context.eval(source2));
164218
} else {
165-
testRunnable.run();
166-
context.eval(source);
219+
int sourceHashCode = context.eval(source1).asInt();
220+
sourceHash[0] = String.format("0x%08x", sourceHashCode);
221+
sourceHashCode = context.eval(source2).asInt();
222+
sourceHash[1] = String.format("0x%08x", sourceHashCode);
223+
context.eval(source1);
224+
context.eval(source2);
167225
}
168226
List<String> logs = new ArrayList<>();
227+
int[] cnt = new int[1];
169228
forEachLog(out.toByteArray(), (matcher) -> {
170229
String logType = matcher.group(1);
171230
logs.add(logType);
172231
if (!"fail".equals(logType)) {
173-
Assert.assertEquals(sourceHash[0], matcher.group(2));
232+
Assert.assertEquals(sourceHash[cnt[0] % 2], matcher.group(2));
174233
} else {
175-
Assert.assertTrue(matcher.group().endsWith("Error " + failMessage));
234+
Assert.assertTrue(matcher.group(), matcher.group().endsWith("Error " + ParseException.class.getName() + ": " + failMessage));
176235
}
177236
Assert.assertEquals(sourceName, matcher.group(3));
237+
cnt[0]++;
178238
});
179239
Assert.assertEquals(expectedLogs, Arrays.toString(logs.toArray()));
180240
}
241+
String fullOutput = out.toString();
242+
int statisticsBeginIndex = fullOutput.indexOf("[engine] Polyglot source cache statistics for engine");
243+
if (statisticsBeginIndex < 0) {
244+
throw new AssertionError("Source cache statistics not found in the output: " + fullOutput);
245+
}
246+
String statisticsOutput = fullOutput.substring(statisticsBeginIndex);
247+
if (failMessage == null) {
248+
Matcher matcher = STATISTICS_RESULT_PATTERN.matcher(statisticsOutput);
249+
if (!matcher.matches()) {
250+
throw new AssertionError("Statistics output doesn't match the expected pattern. Check the output: " + statisticsOutput);
251+
}
252+
} else {
253+
Matcher matcher = FAILURE_RESULT_PATTERN.matcher(statisticsOutput);
254+
if (!matcher.find()) {
255+
throw new AssertionError("Statistics output doesn't match the expected pattern. Check the output: " + statisticsOutput);
256+
}
257+
}
181258
}
182259

183-
@TruffleLanguage.Registration(contextPolicy = TruffleLanguage.ContextPolicy.SHARED)
260+
@TruffleLanguage.Registration(contextPolicy = TruffleLanguage.ContextPolicy.SHARED, characterMimeTypes = "text/plain", byteMimeTypes = "text/binary", defaultMimeType = "text/plain")
184261
static class SourceCacheTestLanguage extends TruffleLanguage<TruffleLanguage.Env> {
185262
static final String ID = TestUtils.getDefaultLanguageId(SourceCacheTestLanguage.class);
186263

@@ -226,7 +303,7 @@ static class ParseException extends AbstractTruffleException {
226303
}
227304
}
228305

229-
@TruffleLanguage.Registration
306+
@TruffleLanguage.Registration(characterMimeTypes = "text/plain", byteMimeTypes = "text/binary", defaultMimeType = "text/plain")
230307
static class SourceCacheFailureTestLanguage extends TruffleLanguage<TruffleLanguage.Env> {
231308
static final String ID = TestUtils.getDefaultLanguageId(SourceCacheFailureTestLanguage.class);
232309

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ public abstract Object getOrCreatePolyglotSource(Source source,
262262
public abstract void setPath(SourceBuilder builder, String path);
263263

264264
public abstract Map<String, String> getSourceOptions(Source source);
265+
266+
public abstract URI getOriginalURI(Source source);
265267
}
266268

267269
public abstract static class InteropSupport extends Support {

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -796,13 +796,9 @@ TextMap createTextMap() {
796796
}
797797

798798
private URI getNamedURI(String name, byte[] bytes) {
799-
return getNamedURI(name, bytes, 0, bytes.length);
800-
}
801-
802-
private URI getNamedURI(String name, byte[] bytes, int byteIndex, int length) {
803799
String digest;
804800
if (bytes != null) {
805-
digest = digest(bytes, byteIndex, length);
801+
digest = digest(bytes, 0, bytes.length);
806802
} else {
807803
digest = Integer.toString(System.identityHashCode(this), 16);
808804
}

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceAccessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,10 @@ public void mergeLoadedSources(Source[] sources) {
179179
}
180180
}
181181
}
182+
183+
@Override
184+
public URI getOriginalURI(Source source) {
185+
return source.getOriginalURI();
186+
}
182187
}
183188
}

truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3779,7 +3779,7 @@ boolean patch(PolyglotContextConfig newConfig) {
37793779
}
37803780
PolyglotSharingLayer.Shared s = layer.shared;
37813781
if (s != null) {
3782-
s.sourceCache.patch(TracingSourceCacheListener.createOrNull(engine));
3782+
s.sourceCache.patch(TracingSourceCacheListener.createOrNull(engine), engine.sourceCacheStatisticsListener);
37833783
}
37843784
return true;
37853785
}

truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm
265265
final long engineId;
266266
final boolean allowExperimentalOptions;
267267

268+
SourceCacheStatisticsListener sourceCacheStatisticsListener; // effectively final
269+
268270
@SuppressWarnings("unchecked")
269271
PolyglotEngineImpl(PolyglotImpl impl, SandboxPolicy sandboxPolicy, String[] permittedLanguages,
270272
DispatchOutputStream out, DispatchOutputStream err, InputStream in, OptionValuesImpl engineOptions,
@@ -320,6 +322,8 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm
320322
this.engineLogger = initializeEngineLogger(engineLoggerSupplier, logLevels);
321323
this.engineOptionValues = engineOptions;
322324

325+
this.sourceCacheStatisticsListener = SourceCacheStatisticsListener.createOrNull(this);
326+
323327
Map<String, PolyglotLanguage> publicLanguages = new LinkedHashMap<>();
324328
for (String key : this.idToLanguage.keySet()) {
325329
PolyglotLanguage languageImpl = idToLanguage.get(key);
@@ -559,6 +563,7 @@ void notifyCreated() {
559563
this.hostLanguage = createLanguage(LanguageCache.createHostLanguageCache(prototype.getHostLanguageSPI()), HOST_LANGUAGE_INDEX, null);
560564
this.engineLoggerSupplier = prototype.engineLoggerSupplier;
561565
this.engineLogger = prototype.engineLogger;
566+
this.sourceCacheStatisticsListener = prototype.sourceCacheStatisticsListener;
562567

563568
this.polyglotHostService = prototype.polyglotHostService;
564569
this.internalResourceRoots = prototype.internalResourceRoots;
@@ -742,6 +747,8 @@ boolean patch(SandboxPolicy newSandboxPolicy,
742747
Map<PolyglotInstrument, Map<String, String>> instrumentsOptions = new HashMap<>();
743748
parseOptions(newOptions, languagesOptions, instrumentsOptions);
744749

750+
sourceCacheStatisticsListener = SourceCacheStatisticsListener.createOrNull(this);
751+
745752
RUNTIME.onEnginePatch(this.runtimeData, engineOptions, logSupplier, sandboxPolicy);
746753

747754
List<OptionDescriptor> deprecatedDescriptors = new ArrayList<>();
@@ -1363,6 +1370,9 @@ void ensureClosed(boolean force, boolean initiatedByContext) {
13631370
}
13641371

13651372
RUNTIME.onEngineClosed(this.runtimeData);
1373+
if (sourceCacheStatisticsListener != null) {
1374+
sourceCacheStatisticsListener.onEngineClose(this);
1375+
}
13661376

13671377
Object loggers = getEngineLoggers();
13681378
if (loggers != null) {

0 commit comments

Comments
 (0)