Skip to content

Commit b9a1f77

Browse files
committed
Add an option to write replay benchmark statistics to CSV.
1 parent aae09c1 commit b9a1f77

File tree

3 files changed

+227
-68
lines changed

3 files changed

+227
-68
lines changed

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public void recordAndExecuteReplayRunner() throws Throwable {
144144
Path replayFile = findReplayCompFile(temp.path);
145145
String[][] argumentLists = new String[][]{
146146
new String[]{"--compare-graphs=true", replayFile.toString()},
147-
new String[]{"--compare-graphs=false", "--benchmark=true", "--iterations=1", temp.path.toString()}
147+
new String[]{"--compare-graphs=false", temp.path.toString(), "--benchmark", "--iterations=1"}
148148
};
149149
for (String[] arguments : argumentLists) {
150150
ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(arguments, TTY.out().out());

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java

Lines changed: 215 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
package jdk.graal.compiler.hotspot.replaycomp;
2626

2727
import static jdk.graal.compiler.core.common.NativeImageSupport.inRuntimeCode;
28+
import static jdk.graal.compiler.serviceprovider.GraalServices.getCurrentThreadAllocatedBytes;
29+
import static jdk.graal.compiler.serviceprovider.GraalServices.getCurrentThreadCpuTime;
2830

2931
import java.io.Closeable;
3032
import java.io.FileReader;
@@ -43,9 +45,11 @@
4345

4446
import org.graalvm.collections.EconomicMap;
4547

48+
import jdk.graal.compiler.code.CompilationResult;
4649
import jdk.graal.compiler.core.GraalCompilerOptions;
4750
import jdk.graal.compiler.core.common.LibGraalSupport;
4851
import jdk.graal.compiler.debug.GlobalMetrics;
52+
import jdk.graal.compiler.debug.PathUtilities;
4953
import jdk.graal.compiler.hotspot.CompilerConfigurationFactory;
5054
import jdk.graal.compiler.hotspot.HotSpotGraalCompiler;
5155
import jdk.graal.compiler.hotspot.HotSpotGraalCompilerFactory;
@@ -56,6 +60,8 @@
5660
import jdk.graal.compiler.hotspot.Platform;
5761
import jdk.graal.compiler.options.OptionValues;
5862
import jdk.graal.compiler.util.args.BooleanValue;
63+
import jdk.graal.compiler.util.args.Command;
64+
import jdk.graal.compiler.util.args.CommandGroup;
5965
import jdk.graal.compiler.util.args.IntegerValue;
6066
import jdk.graal.compiler.util.args.OptionValue;
6167
import jdk.graal.compiler.util.args.Program;
@@ -118,33 +124,111 @@ public int getStatus() {
118124
@SuppressWarnings("try")
119125
public static ExitStatus run(String[] args, PrintStream out) {
120126
Program program = new Program("mx replaycomp", "Replay compilations from files.");
121-
OptionValue<Boolean> benchmarkArg = program.addNamed("--benchmark", new BooleanValue("true|false", false, "Replay compilations as a benchmark."));
122-
OptionValue<Integer> iterationsArg = program.addNamed("--iterations", new IntegerValue("n", 10, "The number of benchmark iterations."));
123127
OptionValue<Boolean> compareGraphsArg = program.addNamed("--compare-graphs", new BooleanValue("true|false", false, "Verify that the replayed graph equals the recorded one."));
124128
OptionValue<String> inputPathArg = program.addPositional(new StringValue("TARGET", "Path to a directory with replay compilation files (or path to a single file)."));
129+
CommandGroup<LauncherCommand> commandGroup = new CommandGroup<>("COMMAND", new ReplayCommand(), "The mode to replay in.");
130+
commandGroup.addCommand(new BenchmarkCommand());
131+
program.addCommandGroup(commandGroup);
125132
program.parseAndValidate(args, true);
126-
Path inputPath = Path.of(inputPathArg.getValue());
127-
List<Path> inputFiles;
128-
try {
129-
inputFiles = findJsonFiles(inputPath);
130-
} catch (IOException e) {
131-
out.println(e.getMessage());
133+
List<Path> inputFiles = getInputFiles(out, inputPathArg);
134+
if (inputFiles == null) {
132135
return ExitStatus.Failure;
133136
}
134-
if (inputFiles.isEmpty()) {
135-
out.println("No replay files found in " + inputPath);
136-
return ExitStatus.Failure;
137+
return commandGroup.getSelectedCommand().run(out, inputFiles, compareGraphsArg.getValue());
138+
}
139+
140+
/**
141+
* A command implementing one use case of the replay compilation launcher.
142+
*/
143+
private abstract static class LauncherCommand extends Command {
144+
/**
145+
* Constructs a launcher command.
146+
*
147+
* @param name the name of the command
148+
* @param description the description of the command
149+
*/
150+
private LauncherCommand(String name, String description) {
151+
super(name, description);
137152
}
138153

139-
OptionValues systemOptions = new OptionValues(HotSpotGraalOptionValues.parseOptions());
140-
OptionValues options = new OptionValues(systemOptions, GraalCompilerOptions.SystemicCompilationFailureRate, 0);
141-
CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build();
142-
HotSpotJVMCIRuntime runtime = HotSpotJVMCIRuntime.runtime();
143-
CompilerConfigurationFactory factory = CompilerConfigurationFactory.selectFactory(null, options, runtime);
154+
/**
155+
* Runs the command.
156+
*
157+
* @param out the stream for output
158+
* @param inputFiles the files that should be replayed
159+
* @param compareGraphs whether the replayed graph should be compared with the recorded one
160+
* @return the exit status of the launcher
161+
*/
162+
public abstract ExitStatus run(PrintStream out, List<Path> inputFiles, boolean compareGraphs);
163+
}
164+
165+
/**
166+
* A command that replays a set of files (e.g., for debugging purposes).
167+
*/
168+
private static final class ReplayCommand extends LauncherCommand {
169+
private ReplayCommand() {
170+
super("--replay", "Replay compilations.");
171+
}
144172

145-
LibGraalSupport libgraal = LibGraalSupport.INSTANCE;
146-
GlobalMetrics globalMetrics = new GlobalMetrics();
147-
if (benchmarkArg.getValue()) {
173+
@SuppressWarnings("try")
174+
@Override
175+
public ExitStatus run(PrintStream out, List<Path> inputFiles, boolean compareGraphs) {
176+
OptionValues systemOptions = new OptionValues(HotSpotGraalOptionValues.parseOptions());
177+
OptionValues options = new OptionValues(systemOptions, GraalCompilerOptions.SystemicCompilationFailureRate, 0);
178+
CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build();
179+
HotSpotJVMCIRuntime runtime = HotSpotJVMCIRuntime.runtime();
180+
CompilerConfigurationFactory factory = CompilerConfigurationFactory.selectFactory(null, options, runtime);
181+
LibGraalSupport libgraal = LibGraalSupport.INSTANCE;
182+
GlobalMetrics globalMetrics = new GlobalMetrics();
183+
ReplayTaskStatistics statistics = new ReplayTaskStatistics();
184+
for (Path file : inputFiles) {
185+
ReplayCompilationTask task = statistics.startTask(file.toString());
186+
try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null;
187+
Reproducer reproducer = Reproducer.initializeFromFile(file.toString(), declarations, runtime,
188+
options, factory, globalMetrics, out, EconomicMap.create())) {
189+
reproducer.compile().verify(compareGraphs);
190+
out.println("Successfully replayed " + reproducer.request);
191+
} catch (ReplayParserFailure failure) {
192+
out.println("Replay failed: " + failure.getMessage());
193+
task.setFailureReason(failure.getMessage());
194+
} catch (Exception e) {
195+
out.println("Replay failed: " + e);
196+
e.printStackTrace(out);
197+
return ExitStatus.Failure;
198+
}
199+
}
200+
out.println();
201+
statistics.printStatistics(out);
202+
globalMetrics.print(options);
203+
return ExitStatus.Success;
204+
}
205+
}
206+
207+
private static final double ONE_MILLION = 1_000_000d;
208+
209+
/**
210+
* A command that runs replay compilation as a benchmark.
211+
*/
212+
private static final class BenchmarkCommand extends LauncherCommand {
213+
private final OptionValue<Integer> iterationsArg;
214+
215+
private final OptionValue<String> resultsFileArg;
216+
217+
private BenchmarkCommand() {
218+
super("--benchmark", "Replay compilations as a benchmark.");
219+
iterationsArg = addNamed("--iterations", new IntegerValue("N", 10, "The number of benchmark iterations."));
220+
resultsFileArg = addNamed("--results-file", new StringValue("RESULTS_FILE", null, "Write benchmark metrics to the file in CSV format."));
221+
}
222+
223+
@SuppressWarnings("try")
224+
@Override
225+
public ExitStatus run(PrintStream out, List<Path> inputFiles, boolean compareGraphs) {
226+
OptionValues options = new OptionValues(HotSpotGraalOptionValues.parseOptions());
227+
CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build();
228+
HotSpotJVMCIRuntime runtime = HotSpotJVMCIRuntime.runtime();
229+
CompilerConfigurationFactory factory = CompilerConfigurationFactory.selectFactory(null, options, runtime);
230+
LibGraalSupport libgraal = LibGraalSupport.INSTANCE;
231+
GlobalMetrics globalMetrics = new GlobalMetrics();
148232
List<Reproducer> reproducers = new ArrayList<>();
149233
EconomicMap<Object, Object> internPool = EconomicMap.create();
150234
for (Path file : inputFiles) {
@@ -160,59 +244,65 @@ public static ExitStatus run(String[] args, PrintStream out) {
160244
}
161245
}
162246
internPool.clear();
163-
Runtime javaRuntime = Runtime.getRuntime();
164-
for (int i = 0; i < iterationsArg.getValue(); i++) {
165-
out.printf("====== replaycomp iteration %d started ======%n", i);
166-
double memBefore = (javaRuntime.totalMemory() - javaRuntime.freeMemory()) / 1_000_000d;
167-
long before = System.nanoTime();
168-
System.gc();
169-
long afterGC = System.nanoTime();
170-
double memAfter = (javaRuntime.totalMemory() - javaRuntime.freeMemory()) / 1_000_000d;
171-
double gcMillis = (afterGC - before) / 1_000_000d;
172-
out.printf("GC before operation: completed in %.3f ms, heap usage %.3f MB -> %.3f MB.%n", gcMillis, memBefore, memAfter);
173-
int codeHash = 0;
174-
for (Reproducer reproducer : reproducers) {
175-
try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null) {
176-
ReplayResult replayResult = reproducer.compile();
177-
replayResult.verify(compareGraphsArg.getValue());
178-
codeHash = codeHash * 31 + Arrays.hashCode(replayResult.replayedArtifacts().result().getTargetCode());
179-
} catch (Exception e) {
180-
out.println("Replay failed: " + e);
181-
e.printStackTrace(out);
182-
return ExitStatus.Failure;
247+
if (reproducers.isEmpty()) {
248+
out.println("There are no compilations to replay");
249+
return ExitStatus.Failure;
250+
}
251+
try (PrintStream outStat = (resultsFileArg.isSet()) ? new PrintStream(PathUtilities.openOutputStream(resultsFileArg.getValue())) : null) {
252+
for (int i = 0; i < iterationsArg.getValue(); i++) {
253+
performCollection(out);
254+
BenchmarkIterationMetrics metrics = new BenchmarkIterationMetrics(i);
255+
metrics.beginIteration(out, outStat);
256+
for (Reproducer reproducer : reproducers) {
257+
try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null) {
258+
ReplayResult replayResult = reproducer.compile();
259+
replayResult.verify(compareGraphs);
260+
metrics.addVerifiedResult(replayResult);
261+
} catch (Exception e) {
262+
out.println("Replay failed: " + e);
263+
e.printStackTrace(out);
264+
return ExitStatus.Failure;
265+
}
183266
}
267+
metrics.endIteration(out, outStat);
184268
}
185-
out.printf("Compiled code hash: %d%n", codeHash);
186-
long after = System.nanoTime();
187-
double iterMillis = (after - before) / 1_000_000d;
188-
out.printf("====== replaycomp iteration %d completed (%.3f ms) ======%n", i, iterMillis);
269+
} catch (IOException e) {
270+
out.println("Failed to write benchmark statistics to " + resultsFileArg.getValue());
271+
return ExitStatus.Failure;
189272
}
190273
for (Reproducer reproducer : reproducers) {
191274
reproducer.close();
192275
}
193-
} else {
194-
ReplayCompilationStatistics statistics = new ReplayCompilationStatistics();
195-
for (Path file : inputFiles) {
196-
ReplayCompilationTask task = statistics.startTask(file.toString());
197-
try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null;
198-
Reproducer reproducer = Reproducer.initializeFromFile(file.toString(), declarations, runtime,
199-
options, factory, globalMetrics, out, EconomicMap.create())) {
200-
reproducer.compile().verify(compareGraphsArg.getValue());
201-
out.println("Successfully replayed " + reproducer.request);
202-
} catch (ReplayParserFailure failure) {
203-
out.println("Replay failed: " + failure.getMessage());
204-
task.setFailureReason(failure.getMessage());
205-
} catch (Exception e) {
206-
out.println("Replay failed: " + e);
207-
e.printStackTrace(out);
208-
return ExitStatus.Failure;
209-
}
210-
}
211-
out.println();
212-
statistics.printStatistics(out);
276+
globalMetrics.print(options);
277+
return ExitStatus.Success;
278+
}
279+
280+
private static void performCollection(PrintStream out) {
281+
Runtime javaRuntime = Runtime.getRuntime();
282+
double memBefore = (javaRuntime.totalMemory() - javaRuntime.freeMemory()) / ONE_MILLION;
283+
long gcBeforeTimestamp = System.nanoTime();
284+
System.gc();
285+
long gcAfterTimestamp = System.nanoTime();
286+
double memAfter = (javaRuntime.totalMemory() - javaRuntime.freeMemory()) / ONE_MILLION;
287+
double gcMillis = (gcAfterTimestamp - gcBeforeTimestamp) / ONE_MILLION;
288+
out.printf("GC before operation: completed in %.3f ms, heap usage %.3f MB -> %.3f MB.%n", gcMillis, memBefore, memAfter);
213289
}
214-
globalMetrics.print(options);
215-
return ExitStatus.Success;
290+
}
291+
292+
private static List<Path> getInputFiles(PrintStream out, OptionValue<String> inputPathArg) {
293+
Path inputPath = Path.of(inputPathArg.getValue());
294+
List<Path> inputFiles;
295+
try {
296+
inputFiles = findJsonFiles(inputPath);
297+
} catch (IOException e) {
298+
out.println(e.getMessage());
299+
return null;
300+
}
301+
if (inputFiles.isEmpty()) {
302+
out.println("No replay files found in " + inputPath);
303+
return null;
304+
}
305+
return inputFiles;
216306
}
217307

218308
/**
@@ -434,10 +524,10 @@ public boolean isFailure() {
434524
* Tracks the outcomes of all replayed compilations, which is used to print summary statistics
435525
* at the end.
436526
*/
437-
private static class ReplayCompilationStatistics {
527+
private static class ReplayTaskStatistics {
438528
private final List<ReplayCompilationTask> tasks;
439529

440-
ReplayCompilationStatistics() {
530+
ReplayTaskStatistics() {
441531
this.tasks = new ArrayList<>();
442532
}
443533

@@ -468,4 +558,62 @@ private List<ReplayCompilationTask> failedTasks() {
468558
return tasks.stream().filter(ReplayCompilationTask::isFailure).toList();
469559
}
470560
}
561+
562+
/**
563+
* Collects and prints compilation metrics for one iteration of a replay benchmark.
564+
*/
565+
private static final class BenchmarkIterationMetrics {
566+
private final int iteration;
567+
568+
private long beginWallTime;
569+
570+
private long beginThreadTime;
571+
572+
private long beginMemory;
573+
574+
private int compiledBytecodes;
575+
576+
private int targetCodeSize;
577+
578+
private int targetCodeHash;
579+
580+
private BenchmarkIterationMetrics(int iteration) {
581+
this.iteration = iteration;
582+
}
583+
584+
public void beginIteration(PrintStream out, PrintStream outStat) {
585+
if (iteration == 0 && outStat != null) {
586+
outStat.println("iteration,wall_time_ns,thread_time_ns,allocated_memory,compiled_bytecodes,target_code_size,target_code_hash");
587+
}
588+
out.printf("====== replaycomp iteration %d started ======%n", iteration);
589+
beginMemory = getCurrentThreadAllocatedBytes();
590+
beginWallTime = System.nanoTime();
591+
beginThreadTime = getCurrentThreadCpuTime();
592+
}
593+
594+
public void addVerifiedResult(ReplayResult replayResult) {
595+
CompilationResult result = replayResult.replayedArtifacts().result();
596+
compiledBytecodes += result.getBytecodeSize();
597+
targetCodeSize += result.getTargetCodeSize();
598+
targetCodeHash = targetCodeHash * 31 + Arrays.hashCode(result.getTargetCode());
599+
}
600+
601+
public void endIteration(PrintStream out, PrintStream outStat) {
602+
long endThreadTime = getCurrentThreadCpuTime();
603+
long endWallTime = System.nanoTime();
604+
long endMemory = getCurrentThreadAllocatedBytes();
605+
long wallTimeNanos = endWallTime - beginWallTime;
606+
long threadTimeNanos = endThreadTime - beginThreadTime;
607+
long allocatedMemory = endMemory - beginMemory;
608+
if (outStat != null) {
609+
outStat.printf("%d,%d,%d,%d,%d,%d,%08x%n", iteration, wallTimeNanos, threadTimeNanos, allocatedMemory, compiledBytecodes, targetCodeSize, targetCodeHash);
610+
}
611+
out.printf(" Thread time: %12.3f ms%n", threadTimeNanos / ONE_MILLION);
612+
out.printf(" Allocated memory: %12.3f MB%n", allocatedMemory / ONE_MILLION);
613+
out.printf(" Compiled bytecodes: %12d B%n", compiledBytecodes);
614+
out.printf(" Target code size: %12d B%n", targetCodeSize);
615+
out.printf(" Target code hash: %08x%n", targetCodeHash);
616+
out.printf("====== replaycomp iteration %d completed (%.3f ms) ======%n", iteration, wallTimeNanos / ONE_MILLION);
617+
}
618+
}
471619
}

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/args/CommandGroup.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ public CommandGroup(String name, String help) {
4343
super(name, help);
4444
}
4545

46+
/**
47+
* Constructs an optional command argument with a specific default command.
48+
*
49+
* @param name the name of the command group
50+
* @param defaultCommand the command selected by default
51+
* @param help the help message
52+
*/
53+
public CommandGroup(String name, C defaultCommand, String help) {
54+
super(name, defaultCommand, help);
55+
}
56+
4657
/**
4758
* Parses and updates the selected subcommand based on {@code args[offset]}.
4859
*

0 commit comments

Comments
 (0)