Refactoring
- better args input - possibility to simulate or display the results - modified CSV logic
This commit is contained in:
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@@ -9,21 +9,21 @@
|
||||
"name": "Run1k Simple",
|
||||
"request": "launch",
|
||||
"mainClass": "net.berack.upo.valpre.Main",
|
||||
"args": "-net example1.net -runs 1000 -p -seed 0"
|
||||
"args": "simulation -net example1.net -runs 1000 -p -seed 0"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Run1k Complex",
|
||||
"request": "launch",
|
||||
"mainClass": "net.berack.upo.valpre.Main",
|
||||
"args": "-net example2.net -runs 1000 -p -seed 0"
|
||||
"args": "simulation -net example2.net -runs 1000 -p -seed 0"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Run10",
|
||||
"request": "launch",
|
||||
"mainClass": "net.berack.upo.valpre.Main",
|
||||
"args": "-net example1.net -runs 10"
|
||||
"args": "simulation -net example1.net -runs 10"
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -1,77 +1,33 @@
|
||||
package net.berack.upo.valpre;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.berack.upo.valpre.sim.Net;
|
||||
import net.berack.upo.valpre.sim.SimulationMultiple;
|
||||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Parameters for the simulation
|
||||
String csv = null;
|
||||
var runs = 100;
|
||||
var seed = 2007539552L;
|
||||
if (args.length == 0)
|
||||
exit();
|
||||
|
||||
// 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");
|
||||
|
||||
var file = arguments.get("net");
|
||||
if (file == null)
|
||||
throw new IllegalArgumentException("Net file needed! Use -net <file>");
|
||||
if (file.startsWith("example"))
|
||||
file = Main.class.getClassLoader().getResource(file).getPath();
|
||||
|
||||
// var maxDepartures = new EndSimulationCriteria.MaxDepartures("Queue", total);
|
||||
// var maxTime = new EndSimulationCriteria.MaxTime(1000.0);
|
||||
|
||||
/// Run multiple simulations
|
||||
var net = Net.load(file);
|
||||
var nano = System.nanoTime();
|
||||
var sim = new SimulationMultiple(net);
|
||||
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);
|
||||
var subArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
switch (args[0]) {
|
||||
case "simulation":
|
||||
var sim = new Simulation(subArgs);
|
||||
sim.run();
|
||||
break;
|
||||
case "plot":
|
||||
var plot = new Plot(subArgs);
|
||||
plot.show();
|
||||
break;
|
||||
default:
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> parseParameters(String[] args) {
|
||||
var arguments = new HashMap<String, Boolean>();
|
||||
arguments.put("p", false);
|
||||
arguments.put("seed", true);
|
||||
arguments.put("runs", true);
|
||||
arguments.put("net", true);
|
||||
arguments.put("csv", true);
|
||||
|
||||
var param = new Parameters("-", arguments);
|
||||
try {
|
||||
return param.parse(args);
|
||||
} catch (IllegalArgumentException e) {
|
||||
var descriptions = new HashMap<String, String>();
|
||||
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("net",
|
||||
"The net to use. It should be a file. Use example1.net or example2.net for the provided ones.");
|
||||
descriptions.put("csv", "The filename for saving every run statistics.");
|
||||
|
||||
System.out.println(e.getMessage());
|
||||
System.out.println(param.helper(descriptions));
|
||||
return null;
|
||||
}
|
||||
public static void exit() throws URISyntaxException {
|
||||
var uri = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI();
|
||||
var name = new File(uri).getName();
|
||||
System.out.println("Usage: java -jar " + name + ".jar [simulation|plot] [args]");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -124,4 +124,31 @@ public class Parameters {
|
||||
throw new IllegalArgumentException("Argument unknown");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the arguments passed and returns a map of Argument --> Value that can
|
||||
* be used to retrieve the information. In the case that the arguments are not
|
||||
* in the correct format then an exception is thrown and the helper is printed.
|
||||
*
|
||||
* @param args the arguments in input
|
||||
* @param prefix the prefix to be used
|
||||
* @param arguments a map of arguments where the key is a string and if the
|
||||
* boolean is true then the argument expect a value
|
||||
* @param descriptions a map of descriptions for the arguments
|
||||
* @throws IllegalArgumentException if the arguments are not formatted correctly
|
||||
* or if there is an unknown argument
|
||||
* @return a map of the values
|
||||
*/
|
||||
public static Map<String, String> getArgsOrHelper(String[] args, String prefix, Map<String, Boolean> arguments,
|
||||
Map<String, String> descriptions) {
|
||||
|
||||
var param = new Parameters(prefix, arguments);
|
||||
try {
|
||||
return param.parse(args);
|
||||
} catch (IllegalArgumentException e) {
|
||||
System.out.println(e.getMessage());
|
||||
System.out.println(param.helper(descriptions));
|
||||
throw new IllegalArgumentException("Invalid arguments");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
55
src/main/java/net/berack/upo/valpre/Plot.java
Normal file
55
src/main/java/net/berack/upo/valpre/Plot.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package net.berack.upo.valpre;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.berack.upo.valpre.sim.stats.CsvResult;
|
||||
import net.berack.upo.valpre.sim.stats.ResultMultiple;
|
||||
|
||||
/**
|
||||
* This class is used to plot the results of the simulation.
|
||||
* The results are saved in a CSV file and then loaded to be plotted.
|
||||
*/
|
||||
public class Plot {
|
||||
public final ResultMultiple results;
|
||||
|
||||
/**
|
||||
* Create a new plot object.
|
||||
*
|
||||
* @param args the arguments to create the plot
|
||||
* @throws IOException if anything happens while reading the file
|
||||
*/
|
||||
public Plot(String[] args) throws IOException {
|
||||
var arguments = Plot.parseParameters(args);
|
||||
var csv = arguments.get("csv");
|
||||
if (csv == null)
|
||||
throw new IllegalArgumentException("CSV file needed! Use -csv <file>");
|
||||
|
||||
var results = new CsvResult(csv).loadResults();
|
||||
this.results = new ResultMultiple(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the plot of the results.
|
||||
*/
|
||||
public void show() {
|
||||
// TODO: Use JavaFX to show the plot
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the arguments to get the CSV file.
|
||||
*
|
||||
* @param args the arguments to parse
|
||||
* @return a map with the arguments
|
||||
*/
|
||||
private static Map<String, String> parseParameters(String[] args) {
|
||||
var arguments = new HashMap<String, Boolean>();
|
||||
arguments.put("csv", true);
|
||||
|
||||
var descriptions = new HashMap<String, String>();
|
||||
descriptions.put("csv", "The filename that contains the previous saved runs.");
|
||||
|
||||
return Parameters.getArgsOrHelper(args, "-", arguments, descriptions);
|
||||
}
|
||||
}
|
||||
113
src/main/java/net/berack/upo/valpre/Simulation.java
Normal file
113
src/main/java/net/berack/upo/valpre/Simulation.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package net.berack.upo.valpre;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
|
||||
import net.berack.upo.valpre.sim.Net;
|
||||
import net.berack.upo.valpre.sim.SimulationMultiple;
|
||||
import net.berack.upo.valpre.sim.stats.CsvResult;
|
||||
|
||||
/**
|
||||
* This class is responsible for running the simulation. It parses the arguments
|
||||
* and runs the simulation with the given parameters.
|
||||
*/
|
||||
public class Simulation {
|
||||
public final String csv;
|
||||
public final int runs;
|
||||
public final long seed;
|
||||
public final boolean parallel;
|
||||
public final String file;
|
||||
|
||||
/**
|
||||
* Create a new simulation with the given arguments.
|
||||
*
|
||||
* @param args The arguments for the simulation.
|
||||
*/
|
||||
public Simulation(String[] args) {
|
||||
// Evantually change the parameters
|
||||
var arguments = Simulation.parseParameters(args);
|
||||
this.runs = Simulation.getFromArguments(arguments, "runs", Integer::parseInt, 100);
|
||||
this.seed = Simulation.getFromArguments(arguments, "seed", Long::parseLong, 2007539552L);
|
||||
this.csv = arguments.getOrDefault("csv", null);
|
||||
this.parallel = arguments.containsKey("p");
|
||||
|
||||
var file = arguments.get("net");
|
||||
if (file == null)
|
||||
throw new IllegalArgumentException("Net file needed! Use -net <file>");
|
||||
if (file.startsWith("example"))
|
||||
file = Main.class.getClassLoader().getResource(file).getPath();
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the simulation with the given parameters.
|
||||
* At the end it prints the results and saves them to a CSV file if requested.
|
||||
*
|
||||
* @throws InterruptedException If the simulation is interrupted.
|
||||
* @throws ExecutionException If the simulation fails.
|
||||
* @throws KryoException If the simulation fails.
|
||||
* @throws IOException If the simulation fails.
|
||||
*/
|
||||
public void run() throws InterruptedException, ExecutionException, KryoException, IOException {
|
||||
var net = Net.load(this.file);
|
||||
var nano = System.nanoTime();
|
||||
var sim = new SimulationMultiple(net);
|
||||
var results = this.parallel ? sim.runParallel(this.seed, this.runs) : sim.run(this.seed, this.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) {
|
||||
new CsvResult(this.csv).saveResults(results.runs);
|
||||
System.out.println("Data saved to " + this.csv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value from the arguments or the default value if it is not present.
|
||||
*
|
||||
* @param args The arguments for the simulation.
|
||||
* @param key The key to get the value from.
|
||||
* @param parse The function to parse the value.
|
||||
* @param value The default value if the key is not present.
|
||||
* @return The value from the arguments or the default value if it is not
|
||||
* present.
|
||||
*/
|
||||
private static <T> T getFromArguments(Map<String, String> args, String key, Function<String, T> parse, T value) {
|
||||
if (args.containsKey(key))
|
||||
return parse.apply(args.get(key));
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the arguments for the simulation.
|
||||
*
|
||||
* @param args The arguments for the simulation.
|
||||
* @return The parsed arguments for the simulation.
|
||||
*/
|
||||
private static Map<String, String> parseParameters(String[] args) {
|
||||
var arguments = new HashMap<String, Boolean>();
|
||||
arguments.put("p", false);
|
||||
arguments.put("seed", true);
|
||||
arguments.put("runs", true);
|
||||
arguments.put("net", true);
|
||||
arguments.put("csv", true);
|
||||
|
||||
var descriptions = new HashMap<String, String>();
|
||||
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("net",
|
||||
"The net to use. It should be a file. Use example1.net or example2.net for the provided ones.");
|
||||
descriptions.put("csv", "The filename for saving every run statistics.");
|
||||
|
||||
return Parameters.getArgsOrHelper(args, "-", arguments, descriptions);
|
||||
}
|
||||
}
|
||||
116
src/main/java/net/berack/upo/valpre/sim/stats/CsvResult.java
Normal file
116
src/main/java/net/berack/upo/valpre/sim/stats/CsvResult.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user