Rewrite
- rewritten all the sim code in a way to make easier to read and collect stats
This commit is contained in:
34
src/main/java/net/berack/upo/valpre/Event.java
Normal file
34
src/main/java/net/berack/upo/valpre/Event.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package net.berack.upo.valpre;
|
||||||
|
|
||||||
|
public class Event implements Comparable<Event> {
|
||||||
|
public final double time;
|
||||||
|
public final Type type;
|
||||||
|
public final ServerNode node;
|
||||||
|
|
||||||
|
private Event(Type type, ServerNode node, double time) {
|
||||||
|
this.type = type;
|
||||||
|
this.time = time;
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareTo(Event other) {
|
||||||
|
if (this.time < other.time)
|
||||||
|
return -1;
|
||||||
|
if (this.time == other.time)
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Event newArrival(ServerNode node, double time) {
|
||||||
|
return new Event(Type.ARRIVAL, node, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Event newDeparture(ServerNode node, double time) {
|
||||||
|
return new Event(Type.DEPARTURE, node, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum Type {
|
||||||
|
ARRIVAL,
|
||||||
|
DEPARTURE,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,34 @@
|
|||||||
package net.berack.upo.valpre;
|
package net.berack.upo.valpre;
|
||||||
|
|
||||||
|
import net.berack.upo.valpre.rand.Distribution;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("Hello world!");
|
// Build the network
|
||||||
|
var node1 = ServerNode.createSource("Source", new Distribution.Exponential(1.0 / 4.5));
|
||||||
|
var node2 = ServerNode.createQueue("Queue", 1, new Distribution.NormalBoxMuller(3.2, 0.6));
|
||||||
|
node1.addChild(node2, 1.0);
|
||||||
|
|
||||||
|
/// Run the simulation
|
||||||
|
var sim = new NetSimulation(System.nanoTime());
|
||||||
|
sim.addNode(node1);
|
||||||
|
sim.addNode(node2);
|
||||||
|
var results = sim.run(1000, "Queue");
|
||||||
|
|
||||||
|
// Display the results
|
||||||
|
for (var entry : results.entrySet()) {
|
||||||
|
var stats = entry.getValue();
|
||||||
|
var size = (int) Math.ceil(Math.max(Math.log10(stats.numArrivals), Math.log10(stats.lastEventTime)));
|
||||||
|
var iFormat = "%" + size + "d";
|
||||||
|
var fFormat = "%" + (size + 4) + ".3f";
|
||||||
|
|
||||||
|
System.out.println("===== " + entry.getKey() + " =====");
|
||||||
|
System.out.printf(" Arrivals: \t" + iFormat + "\n", stats.numArrivals);
|
||||||
|
System.out.printf(" Departures:\t" + iFormat + "\n", stats.numDepartures);
|
||||||
|
System.out.printf(" Max Queue: \t" + iFormat + "\n", stats.maxQueueLength);
|
||||||
|
System.out.printf(" Response: \t" + fFormat + "\n", stats.responseTime / stats.numDepartures);
|
||||||
|
System.out.printf(" Busy %%: \t" + fFormat + "\n", stats.busyTime * 100 / stats.lastEventTime);
|
||||||
|
System.out.printf(" Last Event:\t" + fFormat + "\n", stats.lastEventTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
123
src/main/java/net/berack/upo/valpre/NetSimulation.java
Normal file
123
src/main/java/net/berack/upo/valpre/NetSimulation.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package net.berack.upo.valpre;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import net.berack.upo.valpre.rand.Rng;
|
||||||
|
|
||||||
|
public class NetSimulation {
|
||||||
|
public final long seed;
|
||||||
|
private final Rng rng;
|
||||||
|
private final Map<String, ServerNode> servers = new HashMap<>();
|
||||||
|
|
||||||
|
public NetSimulation(long seed) {
|
||||||
|
this.seed = seed;
|
||||||
|
this.rng = new Rng(seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNode(ServerNode node) {
|
||||||
|
this.servers.put(node.name, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Statistics> run(long total, String untilDepartureNode) {
|
||||||
|
// Initialization
|
||||||
|
var timeNow = 0.0d;
|
||||||
|
var stats = new HashMap<String, Statistics>();
|
||||||
|
var fel = new PriorityQueue<Event>();
|
||||||
|
for (var node : this.servers.values()) {
|
||||||
|
var s = new Statistics(this.rng);
|
||||||
|
s.addArrivalIf(node.isSource, node, timeNow, fel);
|
||||||
|
stats.put(node.name, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main Simulation Loop
|
||||||
|
var nodeStop = stats.get(untilDepartureNode);
|
||||||
|
while (nodeStop.numDepartures < total) {
|
||||||
|
var event = fel.poll();
|
||||||
|
if (event == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeNow = event.time;
|
||||||
|
var statsNode = stats.get(event.node.name);
|
||||||
|
switch (event.type) {
|
||||||
|
case ARRIVAL -> statsNode.processArrival(event, timeNow, fel);
|
||||||
|
case DEPARTURE -> statsNode.processDeparture(event, timeNow, fel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Statistics {
|
||||||
|
public int numArrivals = 0;
|
||||||
|
public int numDepartures = 0;
|
||||||
|
public int maxQueueLength = 0;
|
||||||
|
public double busyTime = 0.0;
|
||||||
|
public double responseTime = 0.0;
|
||||||
|
public double lastEventTime = 0.0;
|
||||||
|
|
||||||
|
private int numServerBusy = 0;
|
||||||
|
private ArrayDeque<Double> queue = new ArrayDeque<>();
|
||||||
|
private final Rng rng;
|
||||||
|
|
||||||
|
public Statistics(Rng rng) {
|
||||||
|
this.rng = rng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.numArrivals = 0;
|
||||||
|
this.numDepartures = 0;
|
||||||
|
this.numServerBusy = 0;
|
||||||
|
this.busyTime = 0.0;
|
||||||
|
this.responseTime = 0.0;
|
||||||
|
this.queue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processArrival(Event event, double timeNow, PriorityQueue<Event> fel) {
|
||||||
|
this.numArrivals++;
|
||||||
|
this.queue.add(event.time);
|
||||||
|
this.maxQueueLength = Math.max(this.maxQueueLength, this.queue.size());
|
||||||
|
|
||||||
|
if (event.node.maxServers > this.numServerBusy) {
|
||||||
|
this.numServerBusy++;
|
||||||
|
var time = event.node.distribution.sample(this.rng);
|
||||||
|
var departure = Event.newDeparture(event.node, timeNow + time);
|
||||||
|
fel.add(departure);
|
||||||
|
} else {
|
||||||
|
this.busyTime += timeNow - this.lastEventTime;
|
||||||
|
}
|
||||||
|
this.lastEventTime = timeNow;
|
||||||
|
|
||||||
|
this.addArrivalIf(event.node.isSource, event.node, timeNow, fel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processDeparture(Event event, double timeNow, PriorityQueue<Event> fel) {
|
||||||
|
var startService = this.queue.poll();
|
||||||
|
var response = timeNow - startService;
|
||||||
|
|
||||||
|
if (this.queue.size() < this.numServerBusy) {
|
||||||
|
this.numServerBusy--;
|
||||||
|
} else {
|
||||||
|
var time = event.node.distribution.sample(this.rng);
|
||||||
|
var departure = Event.newDeparture(event.node, timeNow + time);
|
||||||
|
fel.add(departure);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.numDepartures++;
|
||||||
|
this.responseTime += response;
|
||||||
|
this.busyTime += timeNow - this.lastEventTime;
|
||||||
|
this.lastEventTime = timeNow;
|
||||||
|
|
||||||
|
var next = event.node.getChild(rng);
|
||||||
|
this.addArrivalIf(!event.node.isSink, next, timeNow, fel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addArrivalIf(boolean condition, ServerNode node, double timeNow, PriorityQueue<Event> fel) {
|
||||||
|
if (condition && node != null) {
|
||||||
|
var delay = node.distribution.sample(this.rng);
|
||||||
|
fel.add(Event.newArrival(node, timeNow + delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/main/java/net/berack/upo/valpre/ServerNode.java
Normal file
58
src/main/java/net/berack/upo/valpre/ServerNode.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package net.berack.upo.valpre;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import net.berack.upo.valpre.rand.Distribution;
|
||||||
|
import net.berack.upo.valpre.rand.Rng;
|
||||||
|
|
||||||
|
public class ServerNode {
|
||||||
|
public final String name;
|
||||||
|
public final int maxServers;
|
||||||
|
public final Distribution distribution;
|
||||||
|
public final boolean isSink;
|
||||||
|
public final boolean isSource;
|
||||||
|
private final List<NodeChild> children = new ArrayList<>();
|
||||||
|
private double sumProbabilities = 0.0;
|
||||||
|
|
||||||
|
public static ServerNode createSource(String name, Distribution distribution) {
|
||||||
|
return new ServerNode(name, Integer.MAX_VALUE, distribution, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerNode createQueue(String name, int maxServers, Distribution distribution) {
|
||||||
|
return new ServerNode(name, maxServers, distribution, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerNode(String name, int maxServers, Distribution distribution, boolean isSink, boolean isSource) {
|
||||||
|
this.name = name;
|
||||||
|
this.maxServers = maxServers;
|
||||||
|
this.distribution = distribution;
|
||||||
|
this.isSink = isSink;
|
||||||
|
this.isSource = isSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChild(ServerNode node, double probability) {
|
||||||
|
this.children.add(new NodeChild(node, probability));
|
||||||
|
this.sumProbabilities += probability;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerNode getChild(Rng rng) {
|
||||||
|
var random = rng.random();
|
||||||
|
for (var child : this.children) {
|
||||||
|
random -= child.probability / this.sumProbabilities;
|
||||||
|
if (random <= 0) {
|
||||||
|
return child.node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NodeChild {
|
||||||
|
public final ServerNode node;
|
||||||
|
public final double probability;
|
||||||
|
|
||||||
|
public NodeChild(ServerNode node, double probability) {
|
||||||
|
this.node = node;
|
||||||
|
this.probability = probability;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/main/java/net/berack/upo/valpre/rand/Distribution.java
Normal file
59
src/main/java/net/berack/upo/valpre/rand/Distribution.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package net.berack.upo.valpre.rand;
|
||||||
|
|
||||||
|
public interface Distribution {
|
||||||
|
public double sample(Rng rng);
|
||||||
|
|
||||||
|
public static class Exponential implements Distribution {
|
||||||
|
private final double lambda;
|
||||||
|
|
||||||
|
public Exponential(double lambda) {
|
||||||
|
this.lambda = lambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double sample(Rng rng) {
|
||||||
|
return -Math.log(rng.random()) / lambda;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Normal implements Distribution {
|
||||||
|
private final double mean;
|
||||||
|
private final double sigma;
|
||||||
|
|
||||||
|
public Normal(double mean, double sigma) {
|
||||||
|
this.mean = mean;
|
||||||
|
this.sigma = sigma;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double sample(Rng rng) {
|
||||||
|
var sample = rng.random();
|
||||||
|
return mean + sigma * Math.sqrt(-2 * Math.log(sample)) * Math.cos(2 * Math.PI * sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NormalBoxMuller implements Distribution {
|
||||||
|
private final double mean;
|
||||||
|
private final double sigma;
|
||||||
|
private double next = Double.NaN;
|
||||||
|
|
||||||
|
public NormalBoxMuller(double mean, double sigma) {
|
||||||
|
this.mean = mean;
|
||||||
|
this.sigma = sigma;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double sample(Rng rng) {
|
||||||
|
if (!Double.isNaN(next)) {
|
||||||
|
var sample = next;
|
||||||
|
next = Double.NaN;
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sample1 = rng.random();
|
||||||
|
var sample2 = rng.random();
|
||||||
|
next = mean + sigma * Math.sqrt(-2 * Math.log(sample1)) * Math.sin(2 * Math.PI * sample2);
|
||||||
|
return mean + sigma * Math.sqrt(-2 * Math.log(sample1)) * Math.cos(2 * Math.PI * sample2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user