Main.java
package lab.sample;
import lab.sample.model.Trip;
import java.util.ArrayList;
import java.util.List;
/**
* Launcher for the target application.
*
* <p>Two options exist only for the needs of the analysis:
* <ul>
* <li>{@code --iterations} — a sampling profiler has nothing to show on a 5 ms run; CPU
* time is needed for frames to be collected.</li>
* <li>{@code --hold-seconds} — tools that <em>attach</em> to a live process (VisualVM, JMC,
* JProfiler, YourKit) need the JVM to still be there when the operator clicks. Without
* this pause, the target is dead before it has been selected from the list.</li>
* </ul>
*/
public final class Main {
/** Number of distinct trips built before the measurement. Enough to cover every
* mode × weather × time × luggage combination, few enough that building them weighs
* nothing against the computation loop. */
private static final int TRIP_VARIANTS = 240;
/** Calibrated so the computation lasts ~10 s on an Apple Silicon Mac. That is the
* comfortable minimum for a tool attaching to a live process (VisualVM, JMC, JProfiler)
* to have time to be launched, to select the JVM and to record something. A one-second
* run offers no grip at all. */
private static final int DEFAULT_ITERATIONS = 16_000_000;
public static void main(String[] args) throws InterruptedException {
int iterations = intArg(args, "--iterations", DEFAULT_ITERATIONS);
int holdSeconds = intArg(args, "--hold-seconds", 0);
System.out.printf("sample-app: %d trips evaluated, final pause %d s%n", iterations, holdSeconds);
Metrics metrics = new Metrics();
metrics.stage("JVM started");
// The trips are built ONCE, outside the measured loop. Without that separation,
// generating the test set (string concatenations, list allocations) captures ~70 % of
// the samples and the call tree shows java.lang.StringConcatHelper instead of the
// route computation. Measured, not assumed.
List<Trip> trips = new ArrayList<>(TRIP_VARIANTS);
for (int i = 0; i < TRIP_VARIANTS; i++) {
trips.add(Scenarios.at(i));
}
metrics.stage("trip set built");
double total = 0;
for (int i = 0; i < iterations; i++) {
total += RoutePlanner.travelTimeMinutes(trips.get(i % TRIP_VARIANTS));
}
// Printed so the computation cannot be eliminated, and to check at a glance that two
// runs being compared really did the same work.
metrics.stage("computation finished");
System.out.printf("total duration = %.0f minutes%n", total);
if (holdSeconds > 0) {
System.out.printf("JVM kept alive %d s — attach your profiler (pid %d)%n",
holdSeconds, ProcessHandle.current().pid());
Thread.sleep(holdSeconds * 1000L);
}
}
private static int intArg(String[] args, String name, int fallback) {
for (int i = 0; i < args.length - 1; i++) {
if (args[i].equals(name)) {
return Integer.parseInt(args[i + 1]);
}
}
return fallback;
}
}