GraalVM Polyglot API for embedding multiple programming languages in Java applications with secure language interoperability
—
Execution monitoring provides advanced capabilities for observing, profiling, and debugging polyglot execution environments. It enables detailed execution events, performance metrics, and instrumentation across all supported languages with minimal performance overhead.
Monitor execution events across polyglot contexts.
public final class ExecutionListener implements AutoCloseable {
public static ExecutionListener.Builder newBuilder();
public ExecutionListener attach(Engine engine);
public void close();
}
public static final class ExecutionListener.Builder {
public ExecutionListener.Builder onEnter(Consumer<ExecutionEvent> callback);
public ExecutionListener.Builder onReturn(Consumer<ExecutionEvent> callback);
public ExecutionListener.Builder expressions(boolean enabled);
public ExecutionListener.Builder statements(boolean enabled);
public ExecutionListener.Builder roots(boolean enabled);
public ExecutionListener.Builder sourceFilter(Predicate<Source> filter);
public ExecutionListener.Builder rootNameFilter(Predicate<String> filter);
public ExecutionListener build();
}Usage:
// Basic execution monitoring
ExecutionListener listener = ExecutionListener.newBuilder()
.onEnter(event -> {
System.out.println("Entering: " + event.getLocation());
})
.onReturn(event -> {
System.out.println("Returning: " + event.getReturnValue());
})
.statements(true)
.expressions(true)
.build();
try (Engine engine = Engine.create()) {
ExecutionListener attached = listener.attach(engine);
try (Context context = Context.newBuilder("js").engine(engine).build()) {
// All execution in this context will be monitored
context.eval("js", "function add(a, b) { return a + b; } add(2, 3);");
}
attached.close();
}Access detailed information about execution events.
public final class ExecutionEvent {
public SourceSection getLocation();
public Value getReturnValue();
public RuntimeException getException();
public List<Value> getInputValues();
public boolean hasReturnValue();
public boolean hasException();
}Usage:
ExecutionListener listener = ExecutionListener.newBuilder()
.onEnter(event -> {
SourceSection location = event.getLocation();
System.out.println("Executing at " + location.getSource().getName() +
":" + location.getStartLine());
// Access input values (function parameters, etc.)
List<Value> inputs = event.getInputValues();
System.out.println("Input values: " + inputs);
})
.onReturn(event -> {
if (event.hasReturnValue()) {
Value returnValue = event.getReturnValue();
System.out.println("Returned: " + returnValue);
}
if (event.hasException()) {
RuntimeException exception = event.getException();
System.out.println("Exception: " + exception.getMessage());
}
})
.statements(true)
.build();Monitor specific sources or functions only.
// Monitor only specific sources
ExecutionListener sourceFiltered = ExecutionListener.newBuilder()
.sourceFilter(source -> source.getName().endsWith(".js"))
.onEnter(event -> System.out.println("JS execution: " + event.getLocation()))
.statements(true)
.build();
// Monitor only specific function names
ExecutionListener functionFiltered = ExecutionListener.newBuilder()
.rootNameFilter(name -> name.equals("criticalFunction"))
.onEnter(event -> System.out.println("Critical function called"))
.roots(true)
.build();
// Monitor expressions only (no statements)
ExecutionListener expressionOnly = ExecutionListener.newBuilder()
.expressions(true)
.statements(false)
.onReturn(event -> {
if (event.hasReturnValue()) {
System.out.println("Expression result: " + event.getReturnValue());
}
})
.build();Create custom profilers using execution monitoring:
public class SimpleProfiler {
private final Map<String, Long> executionTimes = new ConcurrentHashMap<>();
private final Map<String, AtomicLong> callCounts = new ConcurrentHashMap<>();
private final ThreadLocal<Long> enterTime = new ThreadLocal<>();
public ExecutionListener createListener() {
return ExecutionListener.newBuilder()
.onEnter(event -> {
enterTime.set(System.nanoTime());
})
.onReturn(event -> {
long duration = System.nanoTime() - enterTime.get();
String location = getLocationKey(event.getLocation());
executionTimes.merge(location, duration, Long::sum);
callCounts.computeIfAbsent(location, k -> new AtomicLong(0)).incrementAndGet();
})
.statements(true)
.roots(true)
.build();
}
public void printProfile() {
System.out.println("Execution Profile:");
executionTimes.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.forEach(entry -> {
String location = entry.getKey();
long totalTime = entry.getValue();
long callCount = callCounts.get(location).get();
long avgTime = totalTime / callCount;
System.out.printf("%s: %d calls, %d ns total, %d ns avg%n",
location, callCount, totalTime, avgTime);
});
}
private String getLocationKey(SourceSection location) {
return location.getSource().getName() + ":" + location.getStartLine();
}
}
// Usage
SimpleProfiler profiler = new SimpleProfiler();
ExecutionListener listener = profiler.createListener();
try (Engine engine = Engine.create()) {
ExecutionListener attached = listener.attach(engine);
try (Context context = Context.newBuilder("js").engine(engine).build()) {
context.eval("js", "function fib(n) { return n < 2 ? n : fib(n-1) + fib(n-2); } fib(10);");
}
profiler.printProfile();
attached.close();
}Track code coverage across polyglot execution:
public class CoverageAnalyzer {
private final Set<String> executedLines = ConcurrentHashMap.newKeySet();
private final Map<String, Set<Integer>> sourceLines = new ConcurrentHashMap<>();
public ExecutionListener createListener() {
return ExecutionListener.newBuilder()
.onEnter(event -> {
SourceSection location = event.getLocation();
String sourceName = location.getSource().getName();
int lineNumber = location.getStartLine();
executedLines.add(sourceName + ":" + lineNumber);
// Track all lines in this source
sourceLines.computeIfAbsent(sourceName, k -> ConcurrentHashMap.newKeySet())
.add(lineNumber);
})
.statements(true)
.build();
}
public void printCoverage() {
System.out.println("Code Coverage Report:");
sourceLines.forEach((sourceName, lines) -> {
long totalLines = lines.size();
long executedCount = lines.stream()
.map(line -> sourceName + ":" + line)
.mapToLong(key -> executedLines.contains(key) ? 1 : 0)
.sum();
double coverage = (double) executedCount / totalLines * 100;
System.out.printf("%s: %.1f%% (%d/%d lines)%n",
sourceName, coverage, executedCount, totalLines);
});
}
}Monitor and analyze exceptions across polyglot execution:
public class ExceptionTracker {
private final List<ExceptionInfo> exceptions = new CopyOnWriteArrayList<>();
public ExecutionListener createListener() {
return ExecutionListener.newBuilder()
.onReturn(event -> {
if (event.hasException()) {
RuntimeException exception = event.getException();
SourceSection location = event.getLocation();
exceptions.add(new ExceptionInfo(
exception.getClass().getSimpleName(),
exception.getMessage(),
location.getSource().getName(),
location.getStartLine(),
System.currentTimeMillis()
));
}
})
.statements(true)
.expressions(true)
.build();
}
public void printExceptionSummary() {
Map<String, Long> exceptionCounts = exceptions.stream()
.collect(Collectors.groupingBy(
ExceptionInfo::getType,
Collectors.counting()
));
System.out.println("Exception Summary:");
exceptionCounts.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.forEach(entry -> {
System.out.printf("%s: %d occurrences%n", entry.getKey(), entry.getValue());
});
}
private static class ExceptionInfo {
private final String type;
private final String message;
private final String source;
private final int line;
private final long timestamp;
// Constructor and getters...
}
}Create real-time monitoring dashboards:
public class RealTimeMonitor {
private final AtomicLong executionCount = new AtomicLong(0);
private final AtomicLong exceptionCount = new AtomicLong(0);
private final ConcurrentHashMap<String, AtomicLong> languageStats = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public ExecutionListener createListener() {
// Start periodic reporting
scheduler.scheduleAtFixedRate(this::printStats, 0, 5, TimeUnit.SECONDS);
return ExecutionListener.newBuilder()
.onEnter(event -> {
executionCount.incrementAndGet();
String language = event.getLocation().getSource().getLanguage();
languageStats.computeIfAbsent(language, k -> new AtomicLong(0))
.incrementAndGet();
})
.onReturn(event -> {
if (event.hasException()) {
exceptionCount.incrementAndGet();
}
})
.statements(true)
.build();
}
private void printStats() {
System.out.println("=== Real-time Stats ===");
System.out.println("Total executions: " + executionCount.get());
System.out.println("Total exceptions: " + exceptionCount.get());
System.out.println("By language:");
languageStats.forEach((lang, count) -> {
System.out.println(" " + lang + ": " + count.get());
});
System.out.println();
}
public void shutdown() {
scheduler.shutdown();
}
}Monitor execution across multiple contexts:
public class MultiContextMonitor {
private final Map<Context, String> contextNames = new ConcurrentHashMap<>();
private final Map<String, AtomicLong> contextStats = new ConcurrentHashMap<>();
public void registerContext(Context context, String name) {
contextNames.put(context, name);
contextStats.put(name, new AtomicLong(0));
}
public ExecutionListener createListener() {
return ExecutionListener.newBuilder()
.onEnter(event -> {
Context currentContext = Context.getCurrent();
String contextName = contextNames.get(currentContext);
if (contextName != null) {
contextStats.get(contextName).incrementAndGet();
}
})
.statements(true)
.build();
}
public void printContextStats() {
System.out.println("Context Execution Stats:");
contextStats.forEach((name, count) -> {
System.out.println(name + ": " + count.get() + " statements");
});
}
}Export execution data for IDE consumption:
public class IDEIntegration {
public void exportExecutionTrace(List<ExecutionEvent> events, Path outputFile) {
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputFile))) {
writer.println("timestamp,source,line,event_type,value");
events.forEach(event -> {
SourceSection location = event.getLocation();
writer.printf("%d,%s,%d,%s,%s%n",
System.currentTimeMillis(),
location.getSource().getName(),
location.getStartLine(),
event.hasReturnValue() ? "return" : "enter",
event.hasReturnValue() ? event.getReturnValue().toString() : ""
);
});
} catch (IOException e) {
throw new RuntimeException("Failed to export execution trace", e);
}
}
}Integrate with performance testing frameworks:
public class PerformanceTestIntegration {
public void runPerformanceTest(String testName, Runnable test) {
ExecutionListener listener = ExecutionListener.newBuilder()
.onEnter(event -> recordMetric("execution.enter", 1))
.onReturn(event -> {
recordMetric("execution.return", 1);
if (event.hasException()) {
recordMetric("execution.exception", 1);
}
})
.statements(true)
.build();
try (Engine engine = Engine.create()) {
ExecutionListener attached = listener.attach(engine);
long startTime = System.nanoTime();
test.run();
long duration = System.nanoTime() - startTime;
recordMetric("test.duration", duration);
attached.close();
}
}
private void recordMetric(String name, long value) {
// Integration with metrics collection system
// MetricsRegistry.record(name, value);
}
}All monitoring components are thread-safe and can be used across multiple contexts and threads simultaneously. However, be careful with shared data structures and consider using concurrent collections for custom monitoring implementations.
Install with Tessl CLI
npx tessl i tessl/maven-org-graalvm-polyglot--polyglot