From 90340a31939d160b4de1216667523f7375ae34ee Mon Sep 17 00:00:00 2001 From: Berack96 Date: Wed, 22 Jan 2025 22:33:38 +0100 Subject: [PATCH] Refactoring - better naming - removed extra functions - updated better statistics --- src/main/java/net/berack/upo/valpre/Main.java | 2 +- .../net/berack/upo/valpre/sim/Simulation.java | 87 +++++---------- .../berack/upo/valpre/sim/stats/Result.java | 38 +------ .../upo/valpre/sim/stats/ResultMultiple.java | 44 ++------ .../upo/valpre/sim/stats/Statistics.java | 102 ++++++++++++++---- 5 files changed, 121 insertions(+), 152 deletions(-) diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index f996bbe..90969e6 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -32,7 +32,7 @@ public class Main { nano = System.nanoTime() - nano; System.out.print(results.average.getHeader()); - System.out.print(results.average.getSummaryAsTable()); + System.out.print(results.average.getSummary()); System.out.println("Final time " + nano / 1e6 + "ms"); } } \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/sim/Simulation.java b/src/main/java/net/berack/upo/valpre/sim/Simulation.java index fb1f309..31a4c75 100644 --- a/src/main/java/net/berack/upo/valpre/sim/Simulation.java +++ b/src/main/java/net/berack/upo/valpre/sim/Simulation.java @@ -14,7 +14,7 @@ import net.berack.upo.valpre.sim.stats.Statistics; */ public final class Simulation { private final Net net; - private final Map nodes; + private final Map states; private final PriorityQueue fel; private final EndCriteria[] criterias; private final long timeStartedNano; @@ -26,13 +26,13 @@ public final class Simulation { * Creates a new run of the simulation with the given nodes and random number * generator. * - * @param nodes The nodes in the network. + * @param states The nodes in the network. * @param rng The random number generator to use. * @param criterias when the simulation has to end. */ public Simulation(Net net, Rng rng, EndCriteria... criterias) { this.net = net; - this.nodes = new HashMap<>(); + this.states = new HashMap<>(); this.fel = new PriorityQueue<>(); this.criterias = criterias; this.timeStartedNano = System.nanoTime(); @@ -42,7 +42,7 @@ public final class Simulation { // Initial arrivals (if spawned) net.forEachNode(node -> { - this.nodes.put(node.name, new NodeBehavior()); + this.states.put(node.name, new NodeState()); if (node.shouldSpawnArrival(0)) this.addArrival(node); }); @@ -69,23 +69,34 @@ public final class Simulation { public void processNextEvent() { var event = fel.poll(); var node = event.node; - var behaviour = this.nodes.get(node.name); + var state = this.states.get(node.name); this.time = event.time; switch (event.type) { case ARRIVAL -> { - if (behaviour.updateArrival(event.time, node.maxServers)) + state.queue.add(this.time); + state.stats.updateArrival(this.time, state.queue.size(), state.numServerBusy != 0); + + if (state.numServerBusy < node.maxServers) { + state.numServerBusy++; this.addDeparture(node); + } } case DEPARTURE -> { - if (behaviour.updateDeparture(event.time)) + var startService = state.queue.poll(); + state.stats.updateDeparture(this.time, this.time - startService); + + if (state.numServerBusy > state.queue.size()) { + state.numServerBusy--; + } else { this.addDeparture(node); + } var next = this.net.getChildOf(node, this.rng); if (next != null) { this.addArrival(next); } - if (node.shouldSpawnArrival(behaviour.stats.numArrivals)) { + if (node.shouldSpawnArrival(state.stats.numArrivals)) { this.addArrival(node); } } @@ -100,7 +111,7 @@ public final class Simulation { public Result endSimulation() { var elapsed = System.nanoTime() - this.timeStartedNano; var nodes = new HashMap(); - for (var entry : this.nodes.entrySet()) + for (var entry : this.states.entrySet()) nodes.put(entry.getKey(), entry.getValue().stats); return new Result(this.seed, this.time, elapsed, nodes); @@ -116,13 +127,13 @@ public final class Simulation { } /** - * Get the node requested by the name passed as a string. + * Get the node state requested by the name passed as a string. * * @param node the name of the node * @return the node */ - public NodeBehavior getNode(String node) { - return this.getNode(node); + public NodeState getNode(String node) { + return this.states.get(node); } /** @@ -166,61 +177,13 @@ public final class Simulation { } /** - * Represents a summary of the behavior of a server node in the network. + * Represents a summary of the state of a server node in the network. * It is used by the simulation to track the number of arrivals and departures, * the maximum queue length, the busy time, and the response time. */ - public static class NodeBehavior { + public static class NodeState { public int numServerBusy = 0; public final Statistics stats = new Statistics(); private final ArrayDeque queue = new ArrayDeque<>(); - - /** - * TODO - * - * @param time - * @param maxServers - * @return - */ - public boolean updateArrival(double time, int maxServers) { - var total = this.stats.averageQueueLength * this.stats.numArrivals; - - this.queue.add(time); - this.stats.numArrivals++; - this.stats.averageQueueLength = (total + this.queue.size()) / this.stats.numArrivals; - this.stats.maxQueueLength = Math.max(this.stats.maxQueueLength, this.queue.size()); - - var startDeparture = maxServers > this.numServerBusy; - if (startDeparture) { - this.numServerBusy++; - } else { - this.stats.busyTime += time - this.stats.lastEventTime; - } - - this.stats.lastEventTime = time; - return startDeparture; - } - - /** - * TODO - * - * @param time - * @return - */ - public boolean updateDeparture(double time) { - var startService = this.queue.poll(); - var response = time - startService; - - var startDeparture = this.queue.size() >= this.numServerBusy; - if (!startDeparture) { - this.numServerBusy--; - } - - this.stats.numDepartures++; - this.stats.responseTime += response; - this.stats.busyTime += time - this.stats.lastEventTime; - this.stats.lastEventTime = time; - return startDeparture; - } } } \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/sim/stats/Result.java b/src/main/java/net/berack/upo/valpre/sim/stats/Result.java index 5c6d12c..9fc119d 100644 --- a/src/main/java/net/berack/upo/valpre/sim/stats/Result.java +++ b/src/main/java/net/berack/upo/valpre/sim/stats/Result.java @@ -37,47 +37,19 @@ public class Result { var size = (int) Math.ceil(Math.log10(this.simulationTime)); var iFormat = "%" + size + ".0f"; var fFormat = "%" + (size + 4) + ".3f"; - var builder = new StringBuilder(); - for (var entry : this.nodes.entrySet()) { - var stats = entry.getValue(); - var busy = stats.busyTime * 100 / stats.lastEventTime; - var avgResp = stats.responseTime / stats.numDepartures; - - builder.append("===== " + entry.getKey() + " =====\n"); - builder.append(String.format(" Arrivals: \t" + iFormat + "\n", stats.numArrivals)); - builder.append(String.format(" Departures:\t" + iFormat + "\n", stats.numDepartures)); - builder.append(String.format(" Max Queue: \t" + iFormat + "\n", stats.maxQueueLength)); - builder.append(String.format(" Avg Queue: \t" + fFormat + "\n", stats.averageQueueLength)); - builder.append(String.format(" Response: \t" + fFormat + "\n", avgResp)); - builder.append(String.format(" Busy %%: \t" + fFormat + "\n", busy)); - builder.append(String.format(" Last Event:\t" + fFormat + "\n", stats.lastEventTime)); - } - return builder.toString(); - } - - /** - * TODO - */ - public String getSummaryAsTable() { - var size = (int) Math.ceil(Math.log10(this.simulationTime)); - var iFormat = "%" + size + ".0f"; - var fFormat = "%" + (size + 4) + ".3f"; - - String[] h = { "Node", "Arrivals", "Departures", "Max Queue", "Avg Queue", "Response", "Busy %", - "Last Event" }; + String[] h = { "Node", "Departures", "Avg Queue", "Avg Response", "Throughput", "Utilization %", "Last Event" }; var table = new ConsoleTable(h); for (var entry : this.nodes.entrySet()) { var stats = entry.getValue(); table.addRow( entry.getKey(), - String.format(iFormat, stats.numArrivals), String.format(iFormat, stats.numDepartures), - String.format(iFormat, stats.maxQueueLength), - String.format(fFormat, stats.averageQueueLength), - String.format(fFormat, stats.responseTime / stats.numDepartures), - String.format(fFormat, stats.busyTime * 100 / stats.lastEventTime), + String.format(fFormat, stats.avgQueueLength), + String.format(fFormat, stats.avgResponse), + String.format(fFormat, stats.troughput), + String.format(fFormat, stats.utilization * 100), String.format(fFormat, stats.lastEventTime)); } return table.toString(); diff --git a/src/main/java/net/berack/upo/valpre/sim/stats/ResultMultiple.java b/src/main/java/net/berack/upo/valpre/sim/stats/ResultMultiple.java index 9327def..45fd9ca 100644 --- a/src/main/java/net/berack/upo/valpre/sim/stats/ResultMultiple.java +++ b/src/main/java/net/berack/upo/valpre/sim/stats/ResultMultiple.java @@ -17,8 +17,8 @@ public class ResultMultiple { */ public ResultMultiple(Result... runs) { this.runs = runs; - this.average = calcAvg(runs); - this.variance = calcVar(this.average, runs); + this.average = ResultMultiple.calcAvg(runs); + this.variance = ResultMultiple.calcVar(this.average, runs); } /** @@ -37,28 +37,15 @@ public class ResultMultiple { avgElapsed += run.timeElapsedNano; for (var entry : run.nodes.entrySet()) { - var stat = nodes.computeIfAbsent(entry.getKey(), _ -> new Statistics()); - var other = entry.getValue(); - stat.numDepartures += other.numDepartures; - stat.numArrivals += other.numArrivals; - stat.busyTime += other.busyTime; - stat.responseTime += other.responseTime; - stat.lastEventTime += other.lastEventTime; - stat.averageQueueLength += other.averageQueueLength; - stat.maxQueueLength = Math.max(stat.maxQueueLength, other.maxQueueLength); + var stats = nodes.computeIfAbsent(entry.getKey(), _ -> new Statistics()); + stats.merge(entry.getValue(), (val1, val2) -> val1 + val2); } } avgTime /= runs.length; avgElapsed /= runs.length; - for (var stat : nodes.values()) { - stat.numDepartures /= runs.length; - stat.numArrivals /= runs.length; - stat.busyTime /= runs.length; - stat.responseTime /= runs.length; - stat.lastEventTime /= runs.length; - stat.averageQueueLength /= runs.length; - } + for (var stat : nodes.values()) + stat.apply(val -> val / runs.length); return new Result(runs[0].seed, avgTime, avgElapsed, nodes); } @@ -82,25 +69,16 @@ public class ResultMultiple { var stat = nodes.computeIfAbsent(entry.getKey(), _ -> new Statistics()); var average = avg.nodes.get(entry.getKey()); var other = entry.getValue(); - stat.numDepartures += Math.pow(other.numDepartures - average.numDepartures, 2); - stat.numArrivals += Math.pow(other.numArrivals - average.numArrivals, 2); - stat.busyTime += Math.pow(other.busyTime - average.busyTime, 2); - stat.responseTime += Math.pow(other.responseTime - average.responseTime, 2); - stat.lastEventTime += Math.pow(other.lastEventTime - average.lastEventTime, 2); - stat.averageQueueLength += Math.pow(other.averageQueueLength - average.averageQueueLength, 2); + var temp = new Statistics(); + Statistics.apply(temp, other, average, (o, a) -> Math.pow(o - a, 2)); + stat.merge(temp, (var1, var2) -> var1 + var2); } } varTime /= runs.length - 1; varElapsed /= runs.length - 1; - for (var stat : nodes.values()) { - stat.numDepartures /= runs.length - 1; - stat.numArrivals /= runs.length - 1; - stat.busyTime /= runs.length - 1; - stat.responseTime /= runs.length - 1; - stat.lastEventTime /= runs.length - 1; - stat.averageQueueLength /= runs.length - 1; - } + for (var stat : nodes.values()) + stat.apply(val -> val / (runs.length - 1)); return new Result(runs[0].seed, varTime, varElapsed, nodes); } diff --git a/src/main/java/net/berack/upo/valpre/sim/stats/Statistics.java b/src/main/java/net/berack/upo/valpre/sim/stats/Statistics.java index 782766f..34bde62 100644 --- a/src/main/java/net/berack/upo/valpre/sim/stats/Statistics.java +++ b/src/main/java/net/berack/upo/valpre/sim/stats/Statistics.java @@ -7,49 +7,105 @@ import java.util.function.Function; * TODO */ public class Statistics { - public double numArrivals = 0; - public double numDepartures = 0; - public double maxQueueLength = 0; - public double averageQueueLength = 0.0d; + public double numArrivals = 0.0d; + public double numDepartures = 0.0d; + public double maxQueueLength = 0.0d; + public double avgQueueLength = 0.0d; public double busyTime = 0.0d; public double responseTime = 0.0d; public double lastEventTime = 0.0d; + // derived stats, you can calculate them even at the end + public double avgResponse = 0.0d; + public double troughput = 0.0d; + public double utilization = 0.0d; + /** - * Resets the statistics to their initial values. + * TODO + * + * @param time + * @param newQueueSize + * @param updateBusy + */ + public void updateArrival(double time, double newQueueSize, boolean updateBusy) { + var total = this.avgQueueLength * this.numArrivals; + + this.numArrivals++; + this.avgQueueLength = (total + newQueueSize) / this.numArrivals; + this.maxQueueLength = Math.max(this.maxQueueLength, newQueueSize); + if (updateBusy) + this.busyTime += time - this.lastEventTime; + + this.lastEventTime = time; + } + + /** + * TODO + * + * @param time + * @param response + */ + public void updateDeparture(double time, double response) { + this.numDepartures++; + this.responseTime += response; + this.busyTime += time - this.lastEventTime; + this.lastEventTime = time; + + this.avgResponse = this.responseTime / this.numDepartures; + this.troughput = this.numDepartures / this.lastEventTime; + this.utilization = this.busyTime / this.lastEventTime; + } + + /** + * Resets the statistics to 0. */ public void reset() { - this.applyToAll(_ -> 0.0d); + this.apply(_ -> 0.0d); } /** * Apply a function to ALL the stats in this class. + * The only stats that are not updated with this function are the one that + * starts with max, min (since they are special) * The input of the function is the current value of the stat. * * @param func a function to apply */ - public void applyToAll(Function func) { - this.numArrivals = func.apply(this.numArrivals); - this.numDepartures = func.apply(this.numDepartures); - this.maxQueueLength = func.apply(this.maxQueueLength); - this.averageQueueLength = func.apply(this.averageQueueLength); - this.busyTime = func.apply(this.busyTime); - this.responseTime = func.apply(this.responseTime); - this.lastEventTime = func.apply(this.lastEventTime); + public void apply(Function func) { + Statistics.apply(this, this, this, (val1, _) -> func.apply(val1)); } /** - * A function used to merge two stats. + * A function used to merge tree stats. + * The only stats that are not updated with this function are the one that + * starts with max, min (since they are special) + * * @param other * @param func */ - public void mergeWith(Statistics other, BiFunction func) { - this.numArrivals = func.apply(other.numArrivals, this.numArrivals); - this.numDepartures = func.apply(other.numDepartures, this.numDepartures); - this.maxQueueLength = func.apply(other.maxQueueLength, this.maxQueueLength); - this.averageQueueLength = func.apply(other.averageQueueLength, this.averageQueueLength); - this.busyTime = func.apply(other.busyTime, this.busyTime); - this.responseTime = func.apply(other.responseTime, this.responseTime); - this.lastEventTime = func.apply(other.lastEventTime, this.lastEventTime); + public void merge(Statistics other, BiFunction func) { + Statistics.apply(this, this, other, func); + } + + /** + * TODO + * + * @param save + * @param val1 + * @param val2 + * @param func + */ + public static void apply(Statistics save, Statistics val1, Statistics val2, + BiFunction func) { + save.numArrivals = func.apply(val1.numArrivals, val2.numArrivals); + save.numDepartures = func.apply(val1.numDepartures, val2.numDepartures); + save.avgQueueLength = func.apply(val1.avgQueueLength, val2.avgQueueLength); + save.busyTime = func.apply(val1.busyTime, val2.busyTime); + save.responseTime = func.apply(val1.responseTime, val2.responseTime); + save.lastEventTime = func.apply(val1.lastEventTime, val2.lastEventTime); + // derived stats + save.troughput = func.apply(val1.troughput, val2.troughput); + save.utilization = func.apply(val1.utilization, val2.utilization); + save.avgResponse = func.apply(val1.avgResponse, val2.avgResponse); } } \ No newline at end of file