diff --git a/.gitignore b/.gitignore index 04a0844..9122c96 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ replay_pid* # mine -pdf/* \ No newline at end of file +pdf/* +*.csv \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d2fb8bc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,36 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Run1k Rand", + "request": "launch", + "mainClass": "net.berack.upo.valpre.Main", + "args": "-runs 1000 -p -seed 0" + }, + { + "type": "java", + "name": "Run1k save", + "request": "launch", + "mainClass": "net.berack.upo.valpre.Main", + "args": "-runs 1000 -p -csv result.csv" + }, + { + "type": "java", + "name": "Run1k", + "request": "launch", + "mainClass": "net.berack.upo.valpre.Main", + "args": "-runs 1000 -p" + }, + { + "type": "java", + "name": "Run10", + "request": "launch", + "mainClass": "net.berack.upo.valpre.Main", + "args": "-runs 10" + }, + ] +} \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index 90969e6..b41dc01 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -1,5 +1,8 @@ package net.berack.upo.valpre; +import java.util.HashMap; +import java.util.Map; + import net.berack.upo.valpre.rand.Distribution; import net.berack.upo.valpre.sim.SimulationMultiple; import net.berack.upo.valpre.sim.Net; @@ -8,12 +11,24 @@ import net.berack.upo.valpre.sim.ServerNode; public class Main { public static void main(String[] args) throws Exception { // Parameters for the simulation - var seed = 2007539552; + String csv = null; + var runs = 100; + var seed = 2007539552L; var total = 10000; var lambda = 1.0 / 4.5; var mu = 3.2; var sigma = 0.6; + // Evantually change the parameters + var arguments = parseParameters(args); + if (arguments.containsKey("seed")) + seed = Long.parseLong(arguments.get("seed")); + if (arguments.containsKey("runs")) + runs = Integer.parseInt(arguments.get("runs")); + if (arguments.containsKey("csv")) + csv = arguments.get("csv"); + var parallel = arguments.containsKey("p"); + // Build the network var net = new Net(); var node1 = ServerNode.createLimitedSource("Source", new Distribution.Exponential(lambda), total); @@ -28,11 +43,39 @@ public class Main { // var maxTime = new EndSimulationCriteria.MaxTime(1000.0); var nano = System.nanoTime(); var sim = new SimulationMultiple(net); - var results = sim.runParallel(seed, 1000); + var results = parallel ? sim.runParallel(seed, runs) : sim.run(seed, runs); nano = System.nanoTime() - nano; System.out.print(results.average.getHeader()); System.out.print(results.average.getSummary()); System.out.println("Final time " + nano / 1e6 + "ms"); + + if (csv != null) { + results.saveCSV(csv); + System.out.println("Data saved to " + csv); + } + } + + public static Map parseParameters(String[] args) { + var arguments = new HashMap(); + arguments.put("p", false); + arguments.put("seed", true); + arguments.put("runs", true); + arguments.put("csv", true); + + var param = new Parameters("-", arguments); + try { + return param.parse(args); + } catch (IllegalArgumentException e) { + var descriptions = new HashMap(); + descriptions.put("p", "Add this if you want the simulation to use threads (one each run)"); + descriptions.put("seed", "The seed of the simulation"); + descriptions.put("runs", "How many runs the simulator should run"); + descriptions.put("csv", "The filename for saving every run statistics"); + + System.out.println(e.getMessage()); + System.out.println(param.helper(descriptions)); + return null; + } } } \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/Parameters.java b/src/main/java/net/berack/upo/valpre/Parameters.java new file mode 100644 index 0000000..04e18cf --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/Parameters.java @@ -0,0 +1,115 @@ +package net.berack.upo.valpre; + +import java.util.HashMap; +import java.util.Map; + +/** + * TODO + */ +public class Parameters { + private final Map arguments; + private final String prefix; + + /** + * TODO + * + * @param arguments + */ + public Parameters(String prefix, Map arguments) { + if (arguments == null || arguments.size() == 0) + throw new IllegalArgumentException(); + this.arguments = arguments; + this.prefix = prefix; + } + + /** + * TODO + * + * @param eventualDescription + * @return + */ + public String helper(Map eventualDescription) { + var size = 0; + var parameters = new HashMap(); + + for (var param : this.arguments.entrySet()) { + var string = this.prefix + param.getKey(); + if (param.getValue()) + string += " "; + + parameters.put(param.getKey(), string); + size = Math.max(size, string.length()); + } + size += 2; // spacing + + var builder = new StringBuilder(); + for (var param : parameters.entrySet()) { + var key = param.getKey(); + var args = param.getValue(); + + builder.append(" "); + builder.append(args); + + var desc = eventualDescription.get(key); + if (desc != null) { + builder.append(" ".repeat(size - args.length())); + builder.append(desc); + } + builder.append("\n"); + } + + return builder.toString(); + } + + /** + * TODO + * + * @param args + * @return + */ + public Map parse(String[] args) { + var result = new HashMap(); + if (args == null || args.length == 0) + return result; + + for (var i = 0; i < args.length; i += 1) { + var current = args[i]; + var next = i + 1 < args.length ? args[i + 1] : null; + + var updateI = this.parseSingle(current, next, result); + if (updateI) + i += 1; + } + + return result; + } + + /** + * TODO + * + * @param current + * @param next + * @param result + * @return + */ + private boolean parseSingle(String current, String next, Map result) { + if (!current.startsWith(this.prefix)) + throw new IllegalArgumentException("Missing prefix [" + current + "]"); + current = current.substring(this.prefix.length()); + + var value = this.arguments.get(current); + if (value != null) { + result.put(current, value ? next : ""); + return value; + } + + var finalSize = result.size() + current.length(); + for (var letter : current.split("")) + if (this.arguments.get(letter) != null) + result.put(current, ""); + + if (finalSize != result.size()) + throw new IllegalArgumentException("Argument unknown"); + return false; + } +} 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 3a61569..711e154 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 @@ -13,6 +13,9 @@ public class Result { public final long seed; public final double simulationTime; public final double timeElapsedMS; + private int size; + private String iFormat; + private String fFormat; /** * Creates a new result object for the given parameters obtained by the @@ -28,6 +31,9 @@ public class Result { this.simulationTime = time; this.timeElapsedMS = elapsed; this.nodes = nodes; + this.size = (int) Math.ceil(Math.log10(this.simulationTime)); + this.iFormat = "%" + this.size + ".0f"; + this.fFormat = "%" + (this.size + 4) + ".3f"; } /** @@ -36,13 +42,11 @@ public class Result { * real time */ public String getHeader() { - var size = (int) Math.ceil(Math.log10(this.simulationTime)); - var format = "%" + (size + 4) + ".3f"; var builder = new StringBuilder(); builder.append("===== Net Stats =====\n"); builder.append(String.format("Seed: \t%d\n", this.seed)); - builder.append(String.format("Simulation: \t" + format + "\n", this.simulationTime)); - builder.append(String.format("Elapsed: \t" + format + "ms\n", this.timeElapsedMS / 1e6)); + builder.append(String.format("Simulation: \t" + fFormat + "\n", this.simulationTime)); + builder.append(String.format("Elapsed: \t" + fFormat + "ms\n", this.timeElapsedMS / 1e6)); return builder.toString(); } @@ -52,24 +56,68 @@ public class Result { * the statistics for each node in the network. */ public String getSummary() { - var size = (int) Math.ceil(Math.log10(this.simulationTime)); - var iFormat = "%" + size + ".0f"; - var fFormat = "%" + (size + 4) + ".3f"; - - String[] h = { "Node", "Departures", "Avg Queue", "Avg Response", "Throughput", "Utilization %", "Last Event" }; + String[] h = { "Node", "Departures", "Avg Queue", "Avg Wait", "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.numDepartures), - 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)); + iFormat.formatted(stats.numDepartures), + fFormat.formatted(stats.avgQueueLength), + fFormat.formatted(stats.avgWaitTime), + fFormat.formatted(stats.avgResponse), + fFormat.formatted(stats.troughput), + fFormat.formatted(stats.utilization * 100), + fFormat.formatted(stats.lastEventTime)); } return table.toString(); } + + /** + * TODO + * + * @param tableHeader + * @return + */ + public String getSummaryCSV(boolean tableHeader) { + var builder = new StringBuilder(); + + if (tableHeader) + builder.append( + "Seed,Node,Arrivals,Departures,MaxQueue,AvgQueue,AvgWait,AvgResponse,BusyTime,WaitTime,ResponseTime,LastEventTime,Throughput,Utilization\n"); + for (var entry : this.nodes.entrySet()) { + var stats = entry.getValue(); + builder.append(this.seed); + builder.append(','); + builder.append(entry.getKey().replace(',', ';').replace('"', '\'')); + builder.append(','); + builder.append(stats.numArrivals); + builder.append(','); + builder.append(stats.numDepartures); + builder.append(','); + builder.append(stats.maxQueueLength); + builder.append(','); + builder.append(stats.avgQueueLength); + builder.append(','); + builder.append(stats.avgWaitTime); + builder.append(','); + builder.append(stats.avgResponse); + builder.append(','); + builder.append(stats.busyTime); + builder.append(','); + builder.append(stats.waitTime); + builder.append(','); + builder.append(stats.responseTime); + builder.append(','); + builder.append(stats.lastEventTime); + builder.append(','); + builder.append(stats.troughput); + builder.append(','); + builder.append(stats.utilization); + builder.append('\n'); + } + return builder.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 6f5daa6..6dda5e2 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 @@ -1,5 +1,7 @@ package net.berack.upo.valpre.sim.stats; +import java.io.FileWriter; +import java.io.IOException; import java.util.HashMap; /** @@ -9,8 +11,7 @@ public class ResultMultiple { public final Result[] runs; public final Result average; public final Result variance; - public final Result lowerBound; - public final Result upperBound; + public final Result error95; /** * TODO @@ -18,13 +19,31 @@ public class ResultMultiple { * @param runs */ public ResultMultiple(Result... runs) { + if (runs == null || runs.length <= 1) + throw new IllegalArgumentException("Sample size must be > 1"); + this.runs = runs; this.average = ResultMultiple.calcAvg(runs); this.variance = ResultMultiple.calcVar(this.average, runs); + this.error95 = calcError(this.average, this.variance, runs.length, 0.95); + } - var temp = calcInterval(this.average, this.variance, runs.length, 0.95); - this.lowerBound = temp[0]; - this.upperBound = temp[1]; + /** + * TODO + * + * @param filename + * @throws IOException + */ + public void saveCSV(String filename) throws IOException { + try (var file = new FileWriter(filename)) { + var first = true; + var builder = new StringBuilder(); + for (var run : this.runs) { + builder.append(run.getSummaryCSV(first)); + first = false; + } + file.write(builder.toString()); + } } /** @@ -98,10 +117,7 @@ public class ResultMultiple { * @param alpha * @return */ - public static Result[] calcInterval(Result avg, Result stdDev, int sampleSize, double alpha) { - if (sampleSize <= 1) - throw new IllegalArgumentException("Il numero di campioni deve essere maggiore di 1."); - + public static Result calcError(Result avg, Result stdDev, int sampleSize, double alpha) { // Getting the correct values for the percentile var distr = new org.apache.commons.math3.distribution.TDistribution(sampleSize - 1); var percentile = distr.inverseCumulativeProbability(alpha); @@ -117,31 +133,6 @@ public class ResultMultiple { stat.merge(entry.getValue(), (_, val) -> percentile * (val / sqrtSample)); error.nodes.put(entry.getKey(), stat); } - - // Calculating the lower and the upper bound - var lowerBound = new Result(avg.seed, - avg.simulationTime - error.simulationTime, - avg.timeElapsedMS - error.timeElapsedMS, - new HashMap<>()); - var upperBound = new Result(avg.seed, - avg.simulationTime + error.simulationTime, - avg.timeElapsedMS + error.timeElapsedMS, - new HashMap<>()); - error.nodes.entrySet().forEach(entry -> { - var key = entry.getKey(); - var errStat = entry.getValue(); - - var avgStat = avg.nodes.get(key); - var lower = new Statistics(); - var upper = new Statistics(); - - Statistics.apply(lower, avgStat, errStat, (a, e) -> a - e); - Statistics.apply(upper, avgStat, errStat, (a, e) -> a + e); - - lowerBound.nodes.put(key, lower); - upperBound.nodes.put(key, lower); - }); - - return new Result[] { lowerBound, upperBound }; + return error; } } \ No newline at end of file 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 34bde62..a84286b 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 @@ -12,10 +12,12 @@ public class Statistics { public double maxQueueLength = 0.0d; public double avgQueueLength = 0.0d; public double busyTime = 0.0d; + public double waitTime = 0.0d; public double responseTime = 0.0d; public double lastEventTime = 0.0d; // derived stats, you can calculate them even at the end + public double avgWaitTime = 0.0d; public double avgResponse = 0.0d; public double troughput = 0.0d; public double utilization = 0.0d; @@ -50,7 +52,9 @@ public class Statistics { this.responseTime += response; this.busyTime += time - this.lastEventTime; this.lastEventTime = time; + this.waitTime = this.responseTime - this.busyTime; + this.avgWaitTime = this.waitTime / this.numDepartures; this.avgResponse = this.responseTime / this.numDepartures; this.troughput = this.numDepartures / this.lastEventTime; this.utilization = this.busyTime / this.lastEventTime; @@ -102,10 +106,12 @@ public class Statistics { 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.waitTime = func.apply(val1.waitTime, val2.waitTime); save.lastEventTime = func.apply(val1.lastEventTime, val2.lastEventTime); // derived stats + save.avgWaitTime = func.apply(val1.avgWaitTime, val2.avgWaitTime); + save.avgResponse = func.apply(val1.avgResponse, val2.avgResponse); 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