From 32072ff764f2b4cb857585a6899938e37b26f5e7 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Thu, 6 Feb 2025 14:28:49 +0100 Subject: [PATCH] Add maxQueue to ServerNode, refactor Event to use node index, and implement buildNodeStates method in Net --- .../java/net/berack/upo/valpre/sim/Event.java | 12 +- .../java/net/berack/upo/valpre/sim/Net.java | 45 +++-- .../net/berack/upo/valpre/sim/ServerNode.java | 13 +- .../upo/valpre/sim/ServerNodeState.java | 174 ++++++++++++++++++ .../net/berack/upo/valpre/sim/Simulation.java | 122 ++++-------- src/main/resources/example1.net | Bin 388 -> 394 bytes src/main/resources/example2.net | Bin 576 -> 586 bytes src/main/resources/example3.net | Bin 475 -> 485 bytes .../upo/valpre/sim/TestSaveExamplesNet.java | 61 ++++++ .../berack/upo/valpre/sim/TestSimulation.java | 102 +++++----- 10 files changed, 360 insertions(+), 169 deletions(-) create mode 100644 src/main/java/net/berack/upo/valpre/sim/ServerNodeState.java create mode 100644 src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java diff --git a/src/main/java/net/berack/upo/valpre/sim/Event.java b/src/main/java/net/berack/upo/valpre/sim/Event.java index b73f867..91d6fb2 100644 --- a/src/main/java/net/berack/upo/valpre/sim/Event.java +++ b/src/main/java/net/berack/upo/valpre/sim/Event.java @@ -6,7 +6,7 @@ package net.berack.upo.valpre.sim; public class Event implements Comparable { public final double time; public final Type type; - public final ServerNode node; + public final int nodeIndex; /** * Create a new event. @@ -15,10 +15,10 @@ public class Event implements Comparable { * @param node The node that the event is associated with. * @param time The time at which the event occurs. */ - private Event(Type type, ServerNode node, double time) { + private Event(Type type, int node, double time) { this.type = type; this.time = time; - this.node = node; + this.nodeIndex = node; } @Override @@ -37,7 +37,7 @@ public class Event implements Comparable { * @param time The time at which the event occurs. * @return The new event. */ - public static Event newArrival(ServerNode node, double time) { + public static Event newArrival(int node, double time) { return new Event(Type.ARRIVAL, node, time); } @@ -48,7 +48,7 @@ public class Event implements Comparable { * @param time The time at which the event occurs. * @return The new event. */ - public static Event newDeparture(ServerNode node, double time) { + public static Event newDeparture(int node, double time) { return new Event(Type.DEPARTURE, node, time); } @@ -59,7 +59,7 @@ public class Event implements Comparable { * @param time The time at which the event occurs. * @return The new event. */ - public static Event newAvailable(ServerNode node, double time) { + public static Event newAvailable(int node, double time) { return new Event(Type.AVAILABLE, node, time); } diff --git a/src/main/java/net/berack/upo/valpre/sim/Net.java b/src/main/java/net/berack/upo/valpre/sim/Net.java index 40255f0..6794526 100644 --- a/src/main/java/net/berack/upo/valpre/sim/Net.java +++ b/src/main/java/net/berack/upo/valpre/sim/Net.java @@ -77,6 +77,7 @@ public final class Net implements Iterable { * @param weight The probability of the child node. * @throws IndexOutOfBoundsException if one of the two nodes are not in the net * @throws IllegalArgumentException if the weight is negative or zero + * @throws IllegalArgumentException if the child is a source node */ public void addConnection(int parent, int child, double weight) { if (weight <= 0) @@ -86,6 +87,9 @@ public final class Net implements Iterable { if (parent < 0 || child < 0 || parent > max || child > max) throw new IndexOutOfBoundsException("One of the nodes does not exist"); + if (this.servers.get(child).spawnArrivals > 0) + throw new IllegalArgumentException("Can't connect to a source node"); + var list = this.connections.get(parent); for (var conn : list) { if (conn.index == child) { @@ -136,6 +140,7 @@ public final class Net implements Iterable { * * @param index the index of the node * @return the node + * @throws IndexOutOfBoundsException if the index is not in the range */ public ServerNode getNode(int index) { return this.servers.get(index); @@ -151,28 +156,28 @@ public final class Net implements Iterable { */ public ServerNode getChildOf(ServerNode parent, Rng rng) { var index = this.indices.get(parent); - return this.getChildOf(index, rng); + index = this.getChildOf(index, rng); + return index < 0 ? null : this.servers.get(index); } /** * Get one of the child nodes from the parent specified. If the index is out of - * bounds then an exception is thrown. If the node has no child then null is - * returned; + * bounds then an exception is thrown. If the node has no child then -1 is + * returned. * * @param parent the parent node * @param rng the random number generator used for getting one of the child * @throws IndexOutOfBoundsException If the index is not in the range * @return the resultig node */ - public ServerNode getChildOf(int parent, Rng rng) { + public int getChildOf(int parent, Rng rng) { var random = rng.random(); for (var conn : this.connections.get(parent)) { random -= conn.weight; - if (random <= 0) { - return this.servers.get(conn.index); - } + if (random <= 0) + return conn.index; } - return null; + return -1; } /** @@ -221,6 +226,20 @@ public final class Net implements Iterable { } } + /** + * Build the node states for the simulation. + * This method is used to create the state of each node in the network. + * Note that each call to this method will create a new state for each node. + * + * @return the array of node states + */ + public ServerNodeState[] buildNodeStates() { + var states = new ServerNodeState[this.servers.size()]; + for (var i = 0; i < states.length; i++) + states[i] = new ServerNodeState(i, this); + return states; + } + /** * Save the current net to a file. * The resulting file is saved with Kryo. @@ -237,6 +256,11 @@ public final class Net implements Iterable { } } + @Override + public Iterator iterator() { + return this.servers.iterator(); + } + /** * Load the net from the file passed as input. * The net will be the same as the one saved. @@ -295,9 +319,4 @@ public final class Net implements Iterable { this.weight = weight; } } - - @Override - public Iterator iterator() { - return this.servers.iterator(); - } } diff --git a/src/main/java/net/berack/upo/valpre/sim/ServerNode.java b/src/main/java/net/berack/upo/valpre/sim/ServerNode.java index 3ceff3c..9475b65 100644 --- a/src/main/java/net/berack/upo/valpre/sim/ServerNode.java +++ b/src/main/java/net/berack/upo/valpre/sim/ServerNode.java @@ -9,6 +9,7 @@ import net.berack.upo.valpre.rand.Rng; */ public class ServerNode { public final String name; + public final int maxQueue; public final int maxServers; public final int spawnArrivals; public final Distribution service; @@ -89,6 +90,7 @@ public class ServerNode { spawnArrivals = 0; this.name = name; + this.maxQueue = 100; // TODO change to runtime this.maxServers = maxServers; this.spawnArrivals = spawnArrivals; this.service = service; @@ -117,17 +119,6 @@ public class ServerNode { return Distribution.getPositiveSample(this.unavailable, rng); } - /** - * Determines if the node should spawn an arrival based on the number of - * arrivals. - * - * @param numArrivals The number of arrivals to check against. - * @return True if the node should spawn an arrival, false otherwise. - */ - public boolean shouldSpawnArrival(double numArrivals) { - return this.spawnArrivals > Math.max(0, numArrivals); - } - @Override public boolean equals(Object obj) { if (!(obj instanceof ServerNode)) diff --git a/src/main/java/net/berack/upo/valpre/sim/ServerNodeState.java b/src/main/java/net/berack/upo/valpre/sim/ServerNodeState.java new file mode 100644 index 0000000..f849f28 --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/sim/ServerNodeState.java @@ -0,0 +1,174 @@ +package net.berack.upo.valpre.sim; + +import java.util.ArrayDeque; + +import net.berack.upo.valpre.rand.Rng; +import net.berack.upo.valpre.sim.stats.NodeStats; + +/** + * 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. + * It also has a connection to the node and the net where it is. + */ +public class ServerNodeState { + public int numServerBusy = 0; + public int numServerUnavailable = 0; + public final ArrayDeque queue = new ArrayDeque<>(); + + public final int index; + public final Net net; + public final ServerNode node; + public final NodeStats stats = new NodeStats(); + + /** + * Create a new node state based on the index and the net passed as input + * + * @param index the index of the node + * @param net the net where the node is + */ + ServerNodeState(int index, Net net) { + this.index = index; + this.net = net; + this.node = net.getNode(index); + } + + /** + * Check if the queue is full based on the maximum queue length of the node + * + * @return true if the queue is full + */ + public boolean isQueueFull() { + return this.queue.size() >= this.node.maxQueue; + } + + /** + * Check if the node can serve a new request based on the number of servers + * + * @return true if the node can serve + */ + public boolean canServe() { + return this.node.maxServers > this.numServerBusy + this.numServerUnavailable; + } + + /** + * Check if the node has requests to serve based on the number of busy servers + * + * @return true if the node has requests + */ + public boolean hasRequests() { + return this.queue.size() > this.numServerBusy; + } + + /** + * Determines if the node should spawn an arrival based on the number of + * arrivals. + * + * @return True if the node should spawn an arrival, false otherwise. + */ + public boolean shouldSpawnArrival() { + return this.node.spawnArrivals > this.stats.numArrivals; + } + + /** + * Update stats and queue when an unavailability event finish. The + * unavailability + * time is the time of the event. + * + * @param time the time of the event + */ + public void updateAvailable(double time) { + this.stats.updateTimes(time, this.numServerBusy, this.numServerUnavailable, this.node.maxServers); + this.numServerUnavailable--; + } + + /** + * Update stats and queue when an arrival event occurs. The arrival time is the + * time of the event. + * + * @param time the time of the event + */ + public void updateArrival(double time) { + this.queue.add(time); + this.stats.updateArrival(time, this.queue.size()); + this.stats.updateTimes(time, this.numServerBusy, this.numServerUnavailable, node.maxServers); + } + + /** + * Update stats and queue when a departure event occurs. The departure time is + * the time of the event. + * + * @param time the time of the event + */ + public void updateDeparture(double time) { + var arrivalTime = this.queue.poll(); + this.stats.updateDeparture(time, arrivalTime); + this.stats.updateTimes(time, this.numServerBusy, this.numServerUnavailable, node.maxServers); + this.numServerBusy--; + } + + /** + * Create an arrival event based on the node and the time passed as input + * + * @param time the time of the event + * @return the arrival event + */ + public Event spawnArrivalIfPossilbe(double time) { + if (this.shouldSpawnArrival()) + return Event.newArrival(this.index, time); + return null; + } + + /** + * Create a departure event if the node can serve and has requests. The event is + * created based on the node and the delay is determined by the node's service + * time distribution. + * + * @param time the time of the event + * @param rng the random number generator + * @return the departure event if the node can serve and has requests, null + * otherwise + */ + public Event spawnDepartureIfPossible(double time, Rng rng) { + if (this.canServe() && this.hasRequests()) { + this.numServerBusy++; + var delay = node.getServiceTime(rng); + return Event.newDeparture(this.index, time + delay); + } + return null; + } + + /** + * Create an unavailable event if the node is unavailable. The event is created + * based on the given node, and the delay is determined by the node's + * unavailability distribution. + * + * @param time The time of the event + * @param rng The random number generator + * @return The event if the node is unavailable, null otherwise + */ + public Event spawnUnavailableIfPossible(double time, Rng rng) { + var delay = node.getUnavailableTime(rng); + if (delay > 0) { + this.numServerUnavailable++; + return Event.newAvailable(this.index, time + delay); + } + return null; + } + + /** + * Create an arrival event to a child node based on the node and the time passed + * as input + * + * @param time the time of the event + * @param rng the random number generator + * @return the arrival event to a child node if the node has children, null + * otherwise + */ + public Event spawnArrivalToChild(double time, Rng rng) { + var childIndex = this.net.getChildOf(this.index, rng); + if (childIndex >= 0) + return Event.newArrival(childIndex, time); + return null; + } +} \ 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 dfeb9b7..b01ce0d 100644 --- a/src/main/java/net/berack/upo/valpre/sim/Simulation.java +++ b/src/main/java/net/berack/upo/valpre/sim/Simulation.java @@ -1,10 +1,8 @@ package net.berack.upo.valpre.sim; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.PriorityQueue; import net.berack.upo.valpre.rand.Rng; @@ -20,8 +18,7 @@ public final class Simulation { public final EndCriteria[] criterias; public final long seed; - private final Net net; - private final Map states; + private final ServerNodeState[] states; private final PriorityQueue fel; private double time = 0.0d; private long eventProcessed = 0; @@ -36,23 +33,22 @@ public final class Simulation { */ public Simulation(Net net, Rng rng, EndCriteria... criterias) { this.timeStartedNano = System.nanoTime(); - this.net = net; - this.states = new HashMap<>(); + this.states = net.buildNodeStates(); this.fel = new PriorityQueue<>(); this.criterias = criterias; this.seed = rng.getSeed(); this.rng = rng; boolean hasLimit = false; - for (var node : net) { + for (var state : this.states) { + var node = state.node; + // check for ending criteria in simulation if (node.spawnArrivals != Integer.MAX_VALUE) hasLimit = true; // Initial arrivals (if spawned) - this.states.put(node.name, new NodeState()); - if (node.shouldSpawnArrival(0)) - this.addArrival(node); + this.addToFel(state.spawnArrivalIfPossilbe(0.0d)); } if (!hasLimit && (criterias == null || criterias.length == 0)) @@ -83,38 +79,26 @@ public final class Simulation { if (event == null) throw new NullPointerException("No more events to process!"); - var node = event.node; - var state = this.states.get(node.name); + var state = this.states[event.nodeIndex]; this.time = event.time; this.eventProcessed += 1; switch (event.type) { case AVAILABLE -> { - state.stats.updateTimes(this.time, state.numServerBusy, state.numServerUnavailable, node.maxServers); - state.numServerUnavailable--; - this.addDepartureIfPossible(node, state); + state.updateAvailable(time); + this.addToFel(state.spawnDepartureIfPossible(time, this.rng)); } case ARRIVAL -> { - state.queue.add(this.time); - state.stats.updateArrival(this.time, state.queue.size()); - state.stats.updateTimes(this.time, state.numServerBusy, state.numServerUnavailable, node.maxServers); - this.addDepartureIfPossible(node, state); + state.updateArrival(time); + this.addToFel(state.spawnDepartureIfPossible(time, this.rng)); } case DEPARTURE -> { - var arrivalTime = state.queue.poll(); - state.stats.updateDeparture(this.time, arrivalTime); - state.stats.updateTimes(this.time, state.numServerBusy, state.numServerUnavailable, node.maxServers); - state.numServerBusy--; + state.updateDeparture(time); - this.addUnavailableIfPossible(node, state); - this.addDepartureIfPossible(node, state); - - var next = this.net.getChildOf(node, this.rng); - if (next != null) - this.addArrival(next); - - if (node.shouldSpawnArrival(state.stats.numArrivals)) - this.addArrival(node); + this.addToFel(state.spawnUnavailableIfPossible(time, this.rng)); + this.addToFel(state.spawnDepartureIfPossible(time, this.rng)); + this.addToFel(state.spawnArrivalToChild(time, rng)); + this.addToFel(state.spawnArrivalIfPossilbe(time)); } } } @@ -127,8 +111,8 @@ public final class Simulation { public Result endSimulation() { var elapsed = System.nanoTime() - this.timeStartedNano; var nodes = new HashMap(); - for (var entry : this.states.entrySet()) - nodes.put(entry.getKey(), entry.getValue().stats); + for (var state : this.states) + nodes.put(state.node.name, state.stats); return new Result(this.seed, this.time, elapsed * 1e-6, nodes); } @@ -166,9 +150,10 @@ public final class Simulation { * * @param node the name of the node * @return the node + * @throws NullPointerException if the node does not exist. */ public ServerNode getNode(String node) { - return this.net.getNode(node); + return this.getNodeState(node).node; } /** @@ -176,55 +161,26 @@ public final class Simulation { * * @param node the name of the node * @return the current state of the node + * @throws NullPointerException if the node does not exist. */ - public NodeState getNodeState(String node) { - return this.states.get(node); - } - - /** - * Adds an arrival event to the future event list. The event is created based - * on the given node, and no delay is added. - * - * @param node The node to create the event for. - */ - public void addArrival(ServerNode node) { - var event = Event.newArrival(node, this.time); - fel.add(event); - } - - /** - * Adds a departure event to the future event list. The event is created based - * on the given node, and the delay is determined by the node's service - * distribution. - * - * @param node The node to create the event for. - * @param state The current state of the node - */ - public void addDepartureIfPossible(ServerNode node, NodeState state) { - var canServe = node.maxServers > state.numServerBusy + state.numServerUnavailable; - var hasRequests = state.queue.size() > state.numServerBusy; - - if (canServe && hasRequests) { - state.numServerBusy++; - var delay = node.getServiceTime(this.rng); - var event = Event.newDeparture(node, this.time + delay); - fel.add(event); + public ServerNodeState getNodeState(String node) { + for (var state : this.states) { + if (state.node.name.equals(node)) + return state; } + + throw new NullPointerException("Node not found: " + node); } /** - * Add an AVAILABLE event in the case that the node has an unavailability time. + * Add an arrival event to the future event list if the event is not null, + * otherwise do nothing. * - * @param node The node to create the event for - * @param state The current state of the node + * @param e the event to add */ - public void addUnavailableIfPossible(ServerNode node, NodeState state) { - var delay = node.getUnavailableTime(rng); - if (delay > 0) { - state.numServerUnavailable++; - var event = Event.newAvailable(node, time + delay); - this.fel.add(event); - } + public void addToFel(Event e) { + if (e != null) + this.fel.add(e); } /** @@ -243,16 +199,4 @@ public final class Simulation { } return false; } - - /** - * 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 NodeState { - public int numServerBusy = 0; - public int numServerUnavailable = 0; - public final NodeStats stats = new NodeStats(); - public final ArrayDeque queue = new ArrayDeque<>(); - } } \ No newline at end of file diff --git a/src/main/resources/example1.net b/src/main/resources/example1.net index f8e0fd8dec078034eabde9eee2dc1c63713ffc40..7e87705d45dd1692d4c88c9d8da066b86f294d13 100644 GIT binary patch delta 46 xcmZo+?qZ&>g6Ra~#1)|+@~;Q)1%3ub21dqaMizz>jFSTyMOlF&Kvou`6aZPG4MqR} delta 55 zcmeBTZegCVV&baM$&8F1ste{YG5{fCGb0PbzyJUL^9Sdb79~GrWRp2w2t;S>K?)`p HFiHUc8}Spj diff --git a/src/main/resources/example2.net b/src/main/resources/example2.net index 0428cd6d51fd7fb1b87eda9ec974b0aee8bc6d9f..b75bba1dd163b70439acf871099d1d953c1beafa 100644 GIT binary patch delta 77 zcmX@Wa*AcbR;Ck-6SuAbl9Lk|(Dqlfwheg;McM#dIK7KRgy|Nj5~&mWv$T9o{h XkzM9^ArPIh2dV&TkpgL9W3mDOJi!|Q delta 112 zcmX@ba)4#R)`{EK$T0<$rj|ZsWSun=1UMax!Qiny0|O%yBg^DgMi2W1a~K(bkgh diff --git a/src/main/resources/example3.net b/src/main/resources/example3.net index e3a788b4cdf9da46c4c0aae01a0554e67dbfc085..3785f0231051065c948f8e62cd32320621601514 100644 GIT binary patch delta 123 zcmcc3{FHgZR^AhgOu?x|WtqvTn;2OqP86GXt(55m<796}$