diff --git a/marshallingperf/src/main/java/run/chronicle/wire/perf/JSONWireMultiLayeredPerfJLBH.java b/marshallingperf/src/main/java/run/chronicle/wire/perf/JSONWireMultiLayeredPerfJLBH.java
new file mode 100644
index 000000000..042115436
--- /dev/null
+++ b/marshallingperf/src/main/java/run/chronicle/wire/perf/JSONWireMultiLayeredPerfJLBH.java
@@ -0,0 +1,292 @@
+package run.chronicle.wire.perf;
+
+import net.openhft.affinity.AffinityLock;
+import net.openhft.chronicle.bytes.Bytes;
+import net.openhft.chronicle.core.OS;
+import net.openhft.chronicle.core.util.NanoSampler;
+import net.openhft.chronicle.jlbh.JLBH;
+import net.openhft.chronicle.jlbh.JLBHOptions;
+import net.openhft.chronicle.jlbh.JLBHTask;
+import net.openhft.chronicle.jlbh.TeamCityHelper;
+import net.openhft.chronicle.wire.SelfDescribingMarshallable;
+import net.openhft.chronicle.wire.Wire;
+import net.openhft.chronicle.wire.WireType;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.IntFunction;
+
+/**
+ * This runs a JLBH test and triggers the async profiler to record each run after the warmup is completed
+ *
+ * The async profiler can be downloaded from here
+ *
+ *
+ *
+ * The profiler.location system property should be set to the directory of the async profiler executable
+ *
+ *
+ * The profiler.sampleIntervalNanos system property should be set to the desired sampling interval in nanoseconds.
+ *
+ *
+ * The jlbh.runs system property can be set to the number of runs to profile.
+ *
+ *
+ */
+public class JSONWireMultiLayeredPerfJLBH implements JLBHTask {
+
+ private static final int ITERATIONS = 1_000_000;
+
+ static {
+ System.setProperty("jvm.resource.tracing", "false");
+ ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(false);
+ }
+
+ public static void main(String[] args) {
+ String profilerLocation = System.getProperty("profiler.location");
+ int sampleIntervalNanos = Integer.getInteger("profiler.sampleIntervalNanos", 10);
+ int runs = Integer.getInteger("jlbh.runs", 1);
+ JSONWireMultiLayeredPerfJLBH benchmark = new JSONWireMultiLayeredPerfJLBH(profilerLocation, sampleIntervalNanos, runs);
+ JLBHOptions jlbhOptions = new JLBHOptions()
+ .iterations(ITERATIONS)
+ .throughput(100_000)
+ .runs(benchmark.runsTotal)
+ .recordOSJitter(false)
+ .accountForCoordinatedOmission(true)
+ .warmUpIterations(10_000)
+ .acquireLock(AffinityLock::acquireCore)
+ .jlbhTask(benchmark);
+ JLBH jlbh = new JLBH(jlbhOptions);
+ jlbh.start();
+ }
+
+
+ private MultiLayeredExample singleLayer = new MultiLayeredExample();
+
+ private MultiLayeredExample doubleLayer = new MultiLayeredExample();
+ private MultiLayeredExample tripleLayer = new MultiLayeredExample();
+ private NanoSampler singleLayerWriteSample;
+ private NanoSampler doubleLayerWriteSample;
+ private NanoSampler tripleLayerWriteSample;
+ private NanoSampler singleLayerReadSample;
+ private NanoSampler doubleLayerReadSample;
+ private NanoSampler tripleLayerReadSample;
+ private NanoSampler singleLayerToStringSample;
+
+ private NanoSampler doubleLayerToStringSample;
+ private NanoSampler tripleLayerToStringSample;
+ private NanoSampler singleLayerFromStringSample;
+ private NanoSampler doubleLayerFromStringSample;
+ private NanoSampler tripleLayerFromStringSample;
+ private JLBH jlbh;
+
+ private String profilerStartCall;
+ private IntFunction profilerEndCall;
+ private Process profiler;
+
+ private int runsTotal;
+ private int runNumber = 0;
+ private static int pid = OS.getProcessId();
+
+ public JSONWireMultiLayeredPerfJLBH(String profilerLocation, int sampleIntervalNanos, int runs) {
+ this.runsTotal = runs;
+ if (profilerLocation == null || profilerLocation.isEmpty() || OS.isWindows()) {
+ profilerStartCall = null;
+ } else {
+ profilerStartCall = profilerLocation + File.separator + "asprof start" +
+ " -i " + sampleIntervalNanos +
+ " -e cpu " +
+ pid;
+ profilerEndCall = run -> profilerLocation + File.separator + "asprof stop" +
+ " -o jfr" +
+ " -f json_jlbh_run_" + run + ".jfr " + pid;
+ }
+ }
+
+ private Wire jsonWire = WireType.JSON.apply(Bytes.elasticByteBuffer());
+
+ @Override
+ public void init(JLBH jlbh) {
+ this.jlbh = jlbh;
+ // single layer
+ createSingleLayerExample();
+ singleLayerWriteSample = jlbh.addProbe("singleLayerWrite");
+ singleLayerReadSample = jlbh.addProbe("singleLayerRead");
+ singleLayerToStringSample = jlbh.addProbe("singleLayerToString");
+ singleLayerFromStringSample = jlbh.addProbe("singleLayerFromString");
+
+ System.out.println("Single Layer: " + WireType.JSON.asString(singleLayer));
+
+ // double layer
+ createDoubleLayerExample();
+ doubleLayerWriteSample = jlbh.addProbe("doubleLayerWrite");
+ doubleLayerReadSample = jlbh.addProbe("doubleLayerRead");
+ doubleLayerToStringSample = jlbh.addProbe("doubleLayerToString");
+ doubleLayerFromStringSample = jlbh.addProbe("doubleLayerFromString");
+
+ System.out.println("Double Layer: " + WireType.JSON.asString(doubleLayer));
+
+ // triple layer
+ createTripleLayerExample();
+ tripleLayerWriteSample = jlbh.addProbe("tripleLayerWrite");
+ tripleLayerReadSample = jlbh.addProbe("tripleLayerRead");
+ tripleLayerToStringSample = jlbh.addProbe("tripleLayerToString");
+ tripleLayerFromStringSample = jlbh.addProbe("tripleLayerFromString");
+
+ System.out.println("Triple Layer: " + WireType.JSON.asString(tripleLayer));
+ }
+
+ private void createSingleLayerExample() {
+ singleLayer.b = 1;
+ singleLayer.s = 2;
+ singleLayer.i = 3;
+ singleLayer.l = 4;
+ singleLayer.f = 5;
+ singleLayer.d = 6;
+ singleLayer.bool = true;
+ singleLayer.text = "layer1";
+ singleLayer.textList.add("single");
+ // empty layered list and map
+ }
+
+ private void createDoubleLayerExample() {
+ doubleLayer.b = 11;
+ doubleLayer.s = 12;
+ doubleLayer.i = 13;
+ doubleLayer.l = 14;
+ doubleLayer.f = 15;
+ doubleLayer.d = 16;
+ doubleLayer.bool = true;
+ doubleLayer.text = "layer2";
+ doubleLayer.textList.add("single");
+ doubleLayer.textList.add("double");
+ doubleLayer.example = singleLayer;
+ doubleLayer.exampleMap.put("single", singleLayer);
+ }
+
+ private void createTripleLayerExample() {
+ tripleLayer.b = 21;
+ tripleLayer.s = 22;
+ tripleLayer.i = 23;
+ tripleLayer.l = 24;
+ tripleLayer.f = 25;
+ tripleLayer.d = 26;
+ tripleLayer.bool = true;
+ tripleLayer.text = "layer3";
+ tripleLayer.textList.add("single");
+ tripleLayer.textList.add("double");
+ tripleLayer.textList.add("triple");
+ tripleLayer.example = doubleLayer;
+ tripleLayer.exampleMap.put("single", singleLayer);
+ tripleLayer.exampleMap.put("double", doubleLayer);
+ }
+
+ @Override
+ public void run(long startTimeNS) {
+ // single layer
+ marshallTest(singleLayer, singleLayerWriteSample, singleLayerReadSample);
+ stringTest(singleLayer, singleLayerToStringSample, singleLayerFromStringSample);
+
+ // double layer
+ marshallTest(doubleLayer, doubleLayerWriteSample, doubleLayerReadSample);
+ stringTest(doubleLayer, doubleLayerToStringSample, doubleLayerFromStringSample);
+
+ // triple layer
+ marshallTest(tripleLayer, tripleLayerWriteSample, tripleLayerReadSample);
+ stringTest(tripleLayer, tripleLayerToStringSample, tripleLayerFromStringSample);
+ jlbh.sampleNanos(System.nanoTime() - startTimeNS);
+ }
+
+ private void marshallTest(MultiLayeredExample example, NanoSampler writeSampler, NanoSampler readSampler) {
+ jsonWire.clear();
+ long start, end;
+
+ start = System.nanoTime();
+ example.writeMarshallable(jsonWire);
+ end = System.nanoTime();
+ writeSampler.sampleNanos(end - start);
+
+ start = System.nanoTime();
+ example.readMarshallable(jsonWire);
+ end = System.nanoTime();
+ readSampler.sampleNanos(end - start);
+ }
+
+ private void stringTest(MultiLayeredExample example, NanoSampler writeSampler, NanoSampler readSampler) {
+ long start, end;
+
+ start = System.nanoTime();
+ String json = WireType.JSON.asString(example);
+ end = System.nanoTime();
+ writeSampler.sampleNanos(end - start);
+
+ start = System.nanoTime();
+ WireType.JSON.fromString(MultiLayeredExample.class, json);
+ end = System.nanoTime();
+ readSampler.sampleNanos(end - start);
+ }
+
+ @Override
+ public void warmedUp() {
+ // attach async profiler
+ beforeRun();
+ }
+
+ private void beforeRun() {
+ runNumber++;
+ if (profilerStartCall != null) {
+ try {
+ profiler = Runtime.getRuntime().exec(profilerStartCall);
+ System.out.println("Started profiler for run " + runNumber + " with call: " + profilerStartCall);
+ } catch (IOException e) {
+ System.err.println("Failed to start profiler: " + e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public void runComplete() {
+ if (profiler != null) {
+ try {
+ String profilerEnd = profilerEndCall.apply(runNumber);
+ Process profilerProcess = Runtime.getRuntime().exec(profilerEnd);
+ System.out.println("Stopped profiler for run " + runNumber + " with call: " + profilerEnd);
+ profilerProcess.waitFor();
+ profiler.waitFor();
+ } catch (Exception e) {
+ System.err.println("Failed to stop profiler: " + e.getMessage());
+ }
+ }
+
+ if (runNumber < runsTotal) {
+ System.out.println("Run " + runNumber + " complete");
+ beforeRun();
+ }
+ }
+
+ @Override
+ public void complete() {
+ TeamCityHelper.teamCityStatsLastRun(this.getClass().getSimpleName(), jlbh, ITERATIONS, System.out);
+ }
+
+ /**
+ * An example class which covers all primative types plus a list of Strings and a map of Strings to Example objects.
+ */
+ public static class MultiLayeredExample extends SelfDescribingMarshallable {
+ byte b;
+ short s;
+ int i;
+ long l;
+ float f;
+ double d;
+ boolean bool = true;
+ String text = "example";
+ List textList = new ArrayList<>();
+ MultiLayeredExample example = null;
+ Map exampleMap = new HashMap<>();
+ }
+}