Last active
October 14, 2018 02:36
-
-
Save koduki/fdeb93b87dfd41aa7d8add9c5d4f3815 to your computer and use it in GitHub Desktop.
Java 11でJMC/Flight Recorderが完全OSS化したのでJFR APIを使ったマイクロベンチマークツールのサンプル。解説は https://qiita.com/koduki/items/6e8fbcbafdcc2f743f56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* To change this license header, choose License Headers in Project Properties. | |
* To change this template file, choose Tools | Templates | |
* and open the template in the editor. | |
*/ | |
package cn.orz.pascal.tinybench; | |
import java.io.IOException; | |
import java.nio.file.Path; | |
import java.text.ParseException; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.function.Consumer; | |
import java.util.stream.Collectors; | |
import jdk.jfr.Configuration; | |
import jdk.jfr.Event; | |
import jdk.jfr.Label; | |
import jdk.jfr.Recording; | |
import jdk.jfr.consumer.RecordingFile; | |
import static cn.orz.pascal.jl2.Extentions.*; | |
import cn.orz.pascal.jl2.collections.Tuples; | |
import java.util.Comparator; | |
import java.util.Map; | |
import java.util.stream.LongStream; | |
import jdk.jfr.consumer.RecordedEvent; | |
/** | |
* | |
* @author koduki | |
*/ | |
public class TinyBenchmark { | |
@Label("Benchmark") | |
static class BenchmarkEvent extends Event { | |
@Label("Test Case") | |
String testcase; | |
@Label("Loop Count") | |
int loop; | |
@Label("Response") | |
long response; | |
@Label("Arguments") | |
String arguments; | |
@Override | |
public String toString() { | |
return "BenchmarkEvent{" + "testcase=" + testcase + ", loop=" + loop + ", response=" + response + ", arguments=" + arguments + '}'; | |
} | |
} | |
public static void benchmarks(Path path, String name, int loopCount, Consumer<Integer> callback) throws IOException, ParseException { | |
Configuration jfrConfig = Configuration.getConfiguration("default"); | |
try (Recording recording = new Recording(jfrConfig)) { | |
recording.setName(name); | |
recording.start(); | |
recording.enable(BenchmarkEvent.class); | |
for (int i = 0; i < loopCount; i++) { | |
callback.accept(i); | |
} | |
recording.stop(); | |
recording.dump(path); | |
} | |
} | |
public static void benchmark(int loopCount, String methodName, Runnable callback, Object... arguments) { | |
BenchmarkEvent event = new BenchmarkEvent(); | |
event.begin(); | |
long s = System.nanoTime(); | |
callback.run(); | |
long e = System.nanoTime(); | |
event.loop = loopCount; | |
event.testcase = methodName; | |
event.response = (e - s); | |
List<String> args = Arrays.stream(arguments).map(x -> x.toString()).collect(Collectors.toList()); | |
event.arguments = "[" + String.join(",", args) + "]"; | |
event.commit(); | |
} | |
public static void printReport(List<Map<String, Object>> result) { | |
result.forEach(e -> { | |
String fmt = String.join("\t", | |
"testcase:%s", | |
"loop:%d", | |
"arguments:%s", | |
"response(ms):%d", | |
"gc_count:%d", | |
"gc_total(ms):%d", | |
"gc_max(ms):%d", | |
"gc_avg(ms):%f" | |
); | |
String msg = String.format(fmt, | |
e.get("testcase"), | |
e.get("loop"), | |
e.get("arguments"), | |
(Long) (e.get("response")) / 100_0000, | |
e.get("gc_count"), | |
e.get("gc_total"), | |
e.get("gc_max"), | |
e.get("gc_avg") | |
); | |
System.out.println(msg); | |
}); | |
} | |
public static List<Map<String, Object>> makeReport(Path path) throws IOException { | |
List<RecordedEvent> gcEvents = getGCEvents(path); | |
List<RecordedEvent> bmEvent = getBenchmarkEvent(path); | |
List<Map<String, Object>> result = bmEvent.stream() | |
.map((event) -> parseBenchmarkEvent(gcEvents, event)) | |
.sorted(Comparator.comparing(x -> (Long) (x.get("startTime")))) | |
.collect(Collectors.toList()); | |
return result; | |
} | |
static Map<String, Object> parseBenchmarkEvent(List<RecordedEvent> gcEvents, RecordedEvent event) { | |
Tuples.Tuple4 gc = calcGCValues(gcEvents, event); | |
return map( | |
$("testcase", event.getValue("testcase")), | |
$("loop", event.getValue("loop")), | |
$("response", event.getValue("response")), | |
$("arguments", event.getValue("arguments")), | |
$("gc_count", gc._1()), | |
$("gc_total", gc._2()), | |
$("gc_max", gc._3()), | |
$("gc_avg", gc._4()), | |
$("startTime", event.getStartTime().toEpochMilli()) | |
); | |
} | |
static List<RecordedEvent> getBenchmarkEvent(Path path) throws IOException { | |
List<RecordedEvent> bmEvent = RecordingFile.readAllEvents(path).stream() | |
.filter((e) -> e.getEventType().getName().equals(BenchmarkEvent.class.getName())) | |
.map(e -> e) | |
.collect(Collectors.toList()); | |
return bmEvent; | |
} | |
static List<RecordedEvent> getGCEvents(Path path) throws IOException { | |
List<RecordedEvent> gcEvents = RecordingFile.readAllEvents(path).stream() | |
.filter((e) -> e.getEventType().getName().equals("jdk.G1GarbageCollection")) | |
.collect(Collectors.toList()); | |
return gcEvents; | |
} | |
static Tuples.Tuple4<Integer, Long, Long, Double> calcGCValues(List<RecordedEvent> gcEvents, RecordedEvent bmEvent) { | |
long[] gc = gcEvents.stream() | |
.filter(e -> isContain(bmEvent, e)) | |
.mapToLong(e -> e.getDuration().toMillis()) | |
.toArray(); | |
long max = (gc.length == 0) ? 0 : LongStream.of(gc).max().getAsLong(); | |
double avg = (gc.length == 0) ? 0 : LongStream.of(gc).average().getAsDouble(); | |
long total = LongStream.of(gc).sum(); | |
int count = gc.length; | |
return $(count, total, max, avg); | |
} | |
static boolean isContain(RecordedEvent longEvent, RecordedEvent shortEvent) { | |
return longEvent.getStartTime().compareTo(shortEvent.getStartTime()) == -1 | |
&& shortEvent.getEndTime().compareTo(longEvent.getEndTime()) == -1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment