Refactoring

- better args input
- possibility to simulate or display the results
- modified CSV logic
This commit is contained in:
2025-01-30 11:11:57 +01:00
parent b7b95fad7b
commit 5e5b51e38c
9 changed files with 353 additions and 153 deletions

View File

@@ -0,0 +1,116 @@
package net.berack.upo.valpre.sim.stats;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This class is used to save the results of the simulation to a CSV file.
* The CSV file is used to save the results of the simulation in a format that
* can
* be easily read by other programs.
*/
public class CsvResult {
public final String file;
/**
* Create a new CSV result object.
*
* @param file the file to save/load the results
*/
public CsvResult(String file) {
if (!file.endsWith(".csv"))
file = file + ".csv";
this.file = file;
}
/**
* Save all the runs to a csv file.
*
* @throws IOException if anything happens wile wriiting to the file
*/
public void saveResults(Result[] results) throws IOException {
var builder = new StringBuilder();
builder.append(String.join(",", Statistics.getOrderOfApply()));
try (var writer = new FileWriter(this.file)) {
for (var result : results) {
for (var entry : result.nodes.entrySet()) {
builder.append(result.seed).append(",");
builder.append(entry.getKey()).append(",");
builder.append(CsvResult.statsToCSV(entry.getValue())).append('\n');
}
}
writer.write(builder.toString());
}
}
/**
* Load the results from the CSV file.
*
* @return the results loaded from the file
* @throws IOException if anything happens while reading the file
*/
public Result[] loadResults() throws IOException {
var results = new ArrayList<Result>();
try (var scan = new Scanner(new File(this.file))) {
var _ = scan.nextLine();
var nodes = new HashMap<String, Statistics>();
var seed = 0L;
while (scan.hasNextLine()) {
var line = scan.nextLine().split(",");
var currentSeed = Long.parseLong(line[0]);
var node = line[1];
if (currentSeed != seed && seed != 0) {
results.add(new Result(seed, 0.0, 0L, nodes));
nodes = new HashMap<>();
}
seed = currentSeed;
var copy = Arrays.copyOfRange(line, 2, line.length);
var stats = CsvResult.statsFromCSV(copy);
nodes.put(node, stats);
}
results.add(new Result(seed, 0.0, 0L, nodes));
}
return results.toArray(new Result[0]);
}
/**
* Converts the statistics object to a CSV string.
*
* @param stats the statistics to convert
* @return the CSV string
*/
public static String statsToCSV(Statistics stats) {
var builder = new StringBuilder();
stats.apply(val -> {
builder.append(val).append(",");
return val;
});
return builder.toString();
}
/**
* Converts the CSV string to a statistics object.
*
* @param values the values to convert
* @return the statistics object
*/
public static Statistics statsFromCSV(String[] values) {
var i = new AtomicInteger(0);
var stats = new Statistics();
stats.apply(_ -> Double.parseDouble(values[i.getAndIncrement()]));
return stats;
}
}

View File

@@ -80,58 +80,4 @@ public class Result {
}
return table.toString();
}
/**
* Return a summary formatted for CSV.
* This meaning that all the stats will be separated by a comma (,) and each row
* is a single statistic of the node.
* Each row it will have all the statistic from the class {@link Statistics},
* the seed used for obtaining them.
*
* @param tableHeader
* @return a string with all the stats formatted
*/
public String getSummaryCSV(boolean tableHeader) {
var builder = new StringBuilder();
if (tableHeader)
builder.append(
"Seed,Node,Arrivals,Departures,MaxQueue,AvgQueue,AvgWait,AvgResponse,BusyTime,WaitTime,UnavailableTime,ResponseTime,LastEventTime,Throughput,Utilization,Unavailable\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.unavailableTime);
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(',');
builder.append(stats.unavailable);
builder.append('\n');
}
return builder.toString();
}
}

View File

@@ -1,7 +1,5 @@
package net.berack.upo.valpre.sim.stats;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
/**
@@ -32,27 +30,6 @@ public class ResultMultiple {
this.error95 = calcError(this.average, this.variance, runs.length, 0.95);
}
/**
* Save all the runs to a csv file.
*
* @param filename the name of the file
* @throws IOException if anything happens wile wriiting to the file
*/
public void saveCSV(String filename) throws IOException {
if (!filename.endsWith(".csv"))
filename = filename + ".csv";
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());
}
}
/**
* This method calculate the average of the runs result.
* The average is calculated for each node.

View File

@@ -113,6 +113,17 @@ public class Statistics {
Statistics.apply(this, this, other, func);
}
/**
* Get the order of update of the stats in the apply function.
*
* @return the order of the stats
*/
public static String[] getOrderOfApply() {
return new String[] { "numArrivals", "numDepartures", "avgQueueLength", "avgWaitTime", "avgResponse",
"busyTime", "waitTime", "unavailableTime", "responseTime", "lastEventTime", "troughput", "utilization",
"unavailable" };
}
/**
* Applies a binary function to merge two sets of statistics into a third one.
* This method combines the statistics from two `Statistics` objects (`val1` and
@@ -134,16 +145,15 @@ public class Statistics {
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.unavailableTime = func.apply(val1.unavailableTime, val2.unavailableTime);
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.unavailable = func.apply(val1.unavailable, val2.unavailable);
save.busyTime = func.apply(val1.busyTime, val2.busyTime);
save.waitTime = func.apply(val1.waitTime, val2.waitTime);
save.unavailableTime = func.apply(val1.unavailableTime, val2.unavailableTime);
save.responseTime = func.apply(val1.responseTime, val2.responseTime);
save.lastEventTime = func.apply(val1.lastEventTime, val2.lastEventTime);
save.troughput = func.apply(val1.troughput, val2.troughput);
save.utilization = func.apply(val1.utilization, val2.utilization);
save.unavailable = func.apply(val1.unavailable, val2.unavailable);
}
}