Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package de.peeeq.wurstio;

import org.wurstscript.projectconfig.WurstProjectConfigData;
import de.peeeq.wurstio.benchmark.BenchmarkOptions;
import de.peeeq.wurstio.benchmark.BenchmarkResult;
import de.peeeq.wurstio.benchmark.BenchmarkWorkerOutput;
import de.peeeq.wurstio.benchmark.RunBenchmarks;
import de.peeeq.wurstio.languageserver.requests.RunTests;
import de.peeeq.wurstio.mpq.MpqEditor;
import de.peeeq.wurstio.utils.FileUtils;
Expand All @@ -19,6 +23,8 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.function.Supplier;

Expand Down Expand Up @@ -80,6 +86,11 @@ public CompilationProcess(WurstGui gui, RunArgs runArgs) {
return null;
}

if (runArgs.isRunBenchmarks()) {
timeTaker.measure("Run benchmark worker", () -> runBenchmarks(compiler));
return null;
}

if (runArgs.isRunTests()) {
timeTaker.measure("Run tests",
() -> runTests(compiler.getImTranslator(), compiler, runArgs.getTestTimeout(), runArgs.getTestFilter()));
Expand Down Expand Up @@ -116,6 +127,32 @@ public CompilationProcess(WurstGui gui, RunArgs runArgs) {
return mapScript;
}

private void runBenchmarks(WurstCompilerJassImpl compiler) {
try {
RunBenchmarks runner = new RunBenchmarks();
Path output = Paths.get(runArgs.getBenchmarkOutput());
if (runArgs.isBenchmarkList()) {
BenchmarkWorkerOutput.writeDiscovery(
output,
runner.discover(compiler.getImProg(), Optional.ofNullable(runArgs.getBenchmarkFilter())));
} else {
BenchmarkResult result = runner.run(
compiler.getImTranslator(),
compiler.getImProg(),
runArgs.getBenchmarkName(),
new BenchmarkOptions(runArgs.getBenchmarkWarmup(), runArgs.getBenchmarkIterations(), 1_000_000L));
BenchmarkWorkerOutput.writeResult(output, result);
}
} catch (Throwable e) {
String message = e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage();
gui.sendError(new CompileError(
null,
"Benchmark worker failed: " + message,
CompileError.ErrorType.ERROR,
e));
}
}

private boolean runPjass(File outputMapscript) {
File commonJ = new File(outputMapscript.getParent(), "common.j");
File blizzJ = new File(outputMapscript.getParent(), "blizzard.j");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,10 @@ public static void main(String[] args) {
compiledScript = compilationProcess.doCompilation(null, true);
}

if (compiledScript != null) {
if (runArgs.isRunBenchmarks()) {
// Benchmark workers write their JSON result during compilation;
// a null script is the successful worker result, not a failure.
} else if (compiledScript != null) {
File scriptFile = new File("compiled.j.txt");
Files.write(compiledScript.toString().getBytes(Charsets.UTF_8), scriptFile);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package de.peeeq.wurstio.benchmark;

@FunctionalInterface
public interface BenchmarkClock {
long nanoTime();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.peeeq.wurstio.benchmark;

public record BenchmarkOptions(int warmupIterations, int measurementIterations, long minimumSampleNanos) {
public BenchmarkOptions {
if (warmupIterations < 0) {
throw new IllegalArgumentException("warmupIterations must be non-negative");
}
if (measurementIterations <= 0) {
throw new IllegalArgumentException("measurementIterations must be positive");
}
if (minimumSampleNanos < 0) {
throw new IllegalArgumentException("minimumSampleNanos must be non-negative");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.peeeq.wurstio.benchmark;

import java.util.List;
import java.util.Objects;

public record BenchmarkResult(
String qualifiedName,
int checksum,
int batchSize,
List<Long> samplesNanos
) {
public BenchmarkResult {
Objects.requireNonNull(qualifiedName, "qualifiedName");
if (batchSize <= 0) {
throw new IllegalArgumentException("batchSize must be positive");
}
samplesNanos = List.copyOf(Objects.requireNonNull(samplesNanos, "samplesNanos"));
if (samplesNanos.isEmpty()) {
throw new IllegalArgumentException("at least one benchmark sample is required");
}
for (Long sample : samplesNanos) {
if (sample < 0) {
throw new IllegalArgumentException("benchmark samples must be non-negative");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package de.peeeq.wurstio.benchmark;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.Objects;

/** Writes the machine-readable result of one isolated benchmark compiler worker. */
public final class BenchmarkWorkerOutput {
public static final String SCHEMA = "wurst-benchmark-worker-v2";

@FunctionalInterface
public interface TemporaryFileWriter {
void write(Path temporary, String json) throws IOException;
}

private static final Gson GSON = new GsonBuilder()
.disableHtmlEscaping()
.create();

private BenchmarkWorkerOutput() {
}

public static void writeDiscovery(Path output, List<String> benchmarkNames) throws IOException {
Objects.requireNonNull(benchmarkNames, "benchmarkNames");
JsonObject json = envelope("discovery");
JsonArray benchmarks = new JsonArray();
for (String benchmarkName : benchmarkNames) {
benchmarks.add(Objects.requireNonNull(benchmarkName, "benchmarkName"));
}
json.add("benchmarks", benchmarks);
writeAtomically(output, GSON.toJson(json));
}

public static void writeResult(Path output, BenchmarkResult result) throws IOException {
Objects.requireNonNull(result, "result");
JsonObject json = envelope("execution");
json.addProperty("qualifiedName", result.qualifiedName());
json.addProperty("checksum", result.checksum());
json.addProperty("batchSize", result.batchSize());
json.add("samplesNanos", GSON.toJsonTree(result.samplesNanos()));
writeAtomically(output, GSON.toJson(json));
}

/**
* Write a complete JSON document to a sibling temporary file, then rename it
* over the destination. Serialization happens before touching the destination.
*/
public static void writeAtomically(Path output, String json) throws IOException {
writeAtomically(output, json, BenchmarkWorkerOutput::writeTemporaryFile);
}

public static void writeAtomically(
Path output,
String json,
TemporaryFileWriter temporaryFileWriter
) throws IOException {
Objects.requireNonNull(output, "output");
Objects.requireNonNull(json, "json");
Objects.requireNonNull(temporaryFileWriter, "temporaryFileWriter");
Path absoluteOutput = output.toAbsolutePath();
Path parent = absoluteOutput.getParent();
if (parent == null) {
throw new IOException("benchmark output has no parent directory: " + output);
}
Files.createDirectories(parent);
Path temporary = Files.createTempFile(parent, "wurst-benchmark-", ".tmp");
try {
temporaryFileWriter.write(temporary, json);
try {
Files.move(
temporary,
absoluteOutput,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
} catch (AtomicMoveNotSupportedException e) {
Files.move(temporary, absoluteOutput, StandardCopyOption.REPLACE_EXISTING);
}
} finally {
Files.deleteIfExists(temporary);
}
}

private static void writeTemporaryFile(Path temporary, String json) throws IOException {
Files.writeString(
temporary,
json,
StandardCharsets.UTF_8,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING);
}

private static JsonObject envelope(String mode) {
JsonObject json = new JsonObject();
json.addProperty("schema", SCHEMA);
json.addProperty("mode", mode);
return json;
}
}
Loading
Loading