diff --git a/src/main/java/net/berack/upo/valpre/NetBuilderInteractive.java b/src/main/java/net/berack/upo/valpre/NetBuilderInteractive.java index d63e753..c510695 100644 --- a/src/main/java/net/berack/upo/valpre/NetBuilderInteractive.java +++ b/src/main/java/net/berack/upo/valpre/NetBuilderInteractive.java @@ -74,12 +74,12 @@ public class NetBuilderInteractive { var limit = ask("Arrivals limit (0 for Int.Max): ", Integer::parseInt, 1); if (limit <= 0) limit = Integer.MAX_VALUE; - yield ServerNode.createLimitedSource(name, distribution, limit); + yield ServerNode.Builder.sourceLimited(name, limit, distribution); } case 2 -> { var servers = ask("Number of servers: ", Integer::parseInt, 1); var unavailable = askDistribution("Unavailable distribution"); - yield ServerNode.createQueue(name, servers, distribution, unavailable); + yield ServerNode.Builder.queue(name, servers, distribution, unavailable); } default -> null; }; 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 9475b65..caa82c4 100644 --- a/src/main/java/net/berack/upo/valpre/sim/ServerNode.java +++ b/src/main/java/net/berack/upo/valpre/sim/ServerNode.java @@ -15,84 +15,36 @@ public class ServerNode { public final Distribution service; public final Distribution unavailable; - /** - * Creates a source node with the given name and distribution. - * It swpawns infinite arrivals (Integer.MAX_VALUE) that are served by infinite - * servers (Integer.MAX_VALUE). - * - * @param name The name of the node. - * @param distribution The distribution of the inter-arrival times. - * @return The created source node. - */ - public static ServerNode createSource(String name, Distribution distribution) { - return new ServerNode(name, Integer.MAX_VALUE, distribution, null, Integer.MAX_VALUE); - } - - /** - * Creates a source node with the given name, distribution, and number of - * arrivals to spawn that are served by infinite servers (Integer.MAX_VALUE). - * - * @param name The name of the node. - * @param service The distribution of the inter-arrival times. - * @param spawnArrivals The number of arrivals to spawn. - * @return The created source node. - */ - public static ServerNode createLimitedSource(String name, Distribution service, int spawnArrivals) { - return new ServerNode(name, Integer.MAX_VALUE, service, null, spawnArrivals); - } - - /** - * Creates a queue node with the given name, maximum number of servers, and - * distribution. - * - * @param name The name of the node. - * @param maxServers The maximum number of servers in the queue. - * @param service The distribution of the service times. - * @return The created queue node. - */ - public static ServerNode createQueue(String name, int maxServers, Distribution service) { - return new ServerNode(name, maxServers, service, null, 0); - } - - /** - * Creates a queue node with the given name, maximum number of servers, and - * distribution. - * - * @param name The name of the node. - * @param maxServers The maximum number of servers in the queue. - * @param service The distribution of the service times. - * @param unavailable The distribution of the unavailable times after service. - * @return The created queue node. - */ - public static ServerNode createQueue(String name, int maxServers, Distribution service, Distribution unavailable) { - return new ServerNode(name, maxServers, service, unavailable, 0); - } - /** * Creates a generic node with the given name and distribution. * The servers number must be 1 or higher; if lower will be put to 1. * The spawn number must be 0 or higher; if lower will be put to 0. - * The distribution can't be null, otherwise an exception is thrown. + * The queue number must be equal or higher than the servers number; if lower + * will be put to the servers number. + * The service distribution can't be null, otherwise an exception is thrown. * - * @param name The name of the node. - * @param maxServers The maximum number of servers in the queue. - * @param service The distribution of the service times. - * @param unavailable The distribution of the unavailable times after service. - * @param spawnArrivals The number of arrivals to spawn. + * @param name The name of the node. + * @param servers The maximum number of servers in the queue. + * @param spawn The number of arrivals to spawn. + * @param queue The maximum number of requests in the queue. + * @param service The distribution of the service times. + * @param unavailable The distribution of the unavailable times after service. * @throws NullPointerException if the distribution is null */ - private ServerNode(String name, int maxServers, Distribution service, Distribution unavailable, int spawnArrivals) { + private ServerNode(String name, int servers, int spawn, int queue, Distribution service, Distribution unavailable) { if (service == null) throw new NullPointerException("Service distribution can't be null"); - if (maxServers <= 0) - maxServers = 1; - if (spawnArrivals < 0) - spawnArrivals = 0; + if (servers <= 0) + servers = 1; + if (spawn < 0) + spawn = 0; + if (queue < servers) + queue = servers; this.name = name; - this.maxQueue = 100; // TODO change to runtime - this.maxServers = maxServers; - this.spawnArrivals = spawnArrivals; + this.maxQueue = queue; + this.maxServers = servers; + this.spawnArrivals = spawn; this.service = service; this.unavailable = unavailable; } @@ -131,4 +83,114 @@ public class ServerNode { public int hashCode() { return this.name.hashCode(); } + + /** + * Creates a new builder for the node. + * It is useful to create a node with a more readable syntax and a in a more + * flexible way. + */ + public static class Builder { + private String name; + private int maxQueue; + private int maxServers; + private int spawnArrivals; + private Distribution service; + private Distribution unavailable; + + /** + * Creates a new builder for the node with the given name and distribution. + * The maximum number of servers is set to 1, the maximum number of requests in + * the queue is set to 100, the number of arrivals to spawn is set to 0, and + * the unavailable time is set to null. + * + * @param name The name of the node. + * @param service The distribution of the service times. + * @return The created sink node. + */ + public Builder(String name, Distribution service) { + this.name = name; + this.service = service; + this.maxQueue = 100; // default value + this.maxServers = 1; + this.spawnArrivals = 0; + this.unavailable = null; + } + + public Builder queue(int maxQueue) { + this.maxQueue = maxQueue; + return this; + } + + public Builder servers(int maxServers) { + this.maxServers = maxServers; + return this; + } + + public Builder spawn(int spawnArrivals) { + this.spawnArrivals = spawnArrivals; + return this; + } + + public Builder unavailable(Distribution unavailable) { + this.unavailable = unavailable; + return this; + } + + public ServerNode build() { + return new ServerNode(name, maxServers, spawnArrivals, maxQueue, service, unavailable); + } + + /** + * Creates a source node with the given name and distribution. + * It swpawns infinite arrivals (Integer.MAX_VALUE) that are served by infinite + * servers (Integer.MAX_VALUE). + * + * @param name The name of the node. + * @param distribution The distribution of the inter-arrival times. + * @return The created source node. + */ + public static ServerNode source(String name, Distribution distribution) { + return new Builder(name, distribution).spawn(Integer.MAX_VALUE).build(); + } + + /** + * Creates a source node with the given name, distribution, and number of + * arrivals to spawn that are served by infinite servers (Integer.MAX_VALUE). + * + * @param name The name of the node. + * @param service The distribution of the inter-arrival times. + * @param spawnArrivals The number of arrivals to spawn. + * @return The created source node. + */ + public static ServerNode sourceLimited(String name, int spawnArrivals, Distribution service) { + return new Builder(name, service).spawn(spawnArrivals).build(); + } + + /** + * Creates a queue node with the given name, maximum number of servers, and + * distribution. + * + * @param name The name of the node. + * @param maxServers The maximum number of servers in the queue. + * @param service The distribution of the service times. + * @return The created queue node. + */ + public static ServerNode queue(String name, int maxServers, Distribution service) { + return new Builder(name, service).servers(maxServers).build(); + } + + /** + * Creates a queue node with the given name, maximum number of servers, and + * distribution. + * + * @param name The name of the node. + * @param maxServers The maximum number of servers in the queue. + * @param service The distribution of the service times. + * @param unavailable The distribution of the unavailable times after service. + * @return The created queue node. + */ + public static ServerNode queue(String name, int maxServers, Distribution service, Distribution unavailable) { + return new Builder(name, service).unavailable(unavailable).servers(maxServers).build(); + } + } } 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 b1b3a61..40b2e50 100644 --- a/src/main/java/net/berack/upo/valpre/sim/Simulation.java +++ b/src/main/java/net/berack/upo/valpre/sim/Simulation.java @@ -97,8 +97,12 @@ public final class Simulation { this.addToFel(state.spawnUnavailableIfPossible(time, this.rng)); this.addToFel(state.spawnDepartureIfPossible(time, this.rng)); - this.addToFel(state.spawnArrivalToChild(time, this.rng)); this.addToFel(state.spawnArrivalIfPossilbe(time)); + + // Spawn arrival to child node if queue is not full otherwise drop + var ev = state.spawnArrivalToChild(time, this.rng); + if (ev != null && !this.states[ev.nodeIndex].isQueueFull()) + this.addToFel(ev); } } } diff --git a/src/main/resources/example1.net b/src/main/resources/example1.net index bd6b8bc..52d954e 100644 Binary files a/src/main/resources/example1.net and b/src/main/resources/example1.net differ diff --git a/src/main/resources/example2.net b/src/main/resources/example2.net index e213984..b7dc9d3 100644 Binary files a/src/main/resources/example2.net and b/src/main/resources/example2.net differ diff --git a/src/main/resources/example3.net b/src/main/resources/example3.net index cb933b0..59f7664 100644 Binary files a/src/main/resources/example3.net and b/src/main/resources/example3.net differ diff --git a/src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java b/src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java index 4f6d31d..5141031 100644 --- a/src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java +++ b/src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java @@ -1,5 +1,7 @@ package net.berack.upo.valpre.sim; +import static org.junit.Assert.assertEquals; + import java.io.IOException; import org.junit.Test; @@ -7,6 +9,8 @@ import org.junit.Test; import com.esotericsoftware.kryo.KryoException; import net.berack.upo.valpre.rand.Distribution; +import net.berack.upo.valpre.rand.Rng; +import net.berack.upo.valpre.sim.stats.NodeStats; public class TestSaveExamplesNet { @@ -22,40 +26,103 @@ public class TestSaveExamplesNet { private static final Distribution unNorm = new Distribution.UnavailableTime(0.2, norm4_2); private static final Distribution unExp = new Distribution.UnavailableTime(0.1, exp10); + private static final int spawn = 10000; + private static final String path = "src/main/resources/example%d.%s"; + private static final String net1 = path.formatted(1, "net"); + private static final String net2 = path.formatted(2, "net"); + private static final String net3 = path.formatted(3, "net"); + @Test public void testSaveExample1() throws KryoException, IOException { var net = new Net(); - net.addNode(ServerNode.createLimitedSource("Source", exp0_22, 10000)); - net.addNode(ServerNode.createQueue("Queue", 1, norm3_2)); + net.addNode(ServerNode.Builder.sourceLimited("Source", spawn, exp0_22)); + net.addNode(ServerNode.Builder.queue("Queue", 1, norm3_2)); net.addConnection(0, 1, 1.0); - net.save("src/main/resources/example1.net"); - net = Net.load("src/main/resources/example1.net"); + net.save(net1); + net = Net.load(net1); + + var sim = new Simulation(net, new Rng()); + var res = sim.run(); + var time = 44782.0; + var maxErr = time / 1000.0; + + assertEquals(Rng.DEFAULT, res.seed); + assertEquals(time, res.simulationTime, maxErr); + testNode(res.nodes.get("Source"), 10000, time, 1.0, 4.5, 0.0, 0.0); + testNode(res.nodes.get("Queue"), 10000, time, 2.6, 7.2, 4.0, 0.0); } @Test public void testSaveExample2() throws KryoException, IOException { var net = new Net(); - net.addNode(ServerNode.createLimitedSource("Source", exp0_22, 10000)); - net.addNode(ServerNode.createQueue("Queue", 1, norm3_2)); - net.addNode(ServerNode.createQueue("Queue Wait", 1, norm3_2, unNorm)); + net.addNode(ServerNode.Builder.sourceLimited("Source", spawn, exp0_22)); + net.addNode(ServerNode.Builder.queue("Queue", 1, norm3_2)); + net.addNode(ServerNode.Builder.queue("Queue Wait", 1, norm3_2, unNorm)); net.addConnection(0, 1, 1.0); net.addConnection(1, 2, 1.0); - net.save("src/main/resources/example2.net"); - net = Net.load("src/main/resources/example2.net"); + net.save(net2); + net = Net.load(net2); + + var sim = new Simulation(net, new Rng()); + var res = sim.run(); + var time = 45417.0; + var maxErr = time / 1000.0; + + assertEquals(Rng.DEFAULT, res.seed); + assertEquals(time, res.simulationTime, maxErr); + testNode(res.nodes.get("Source"), 10000, time, 1.0, 4.5, 0.0, 0.0); + testNode(res.nodes.get("Queue"), 10000, time, 2.6, 7.2, 4.0, 0.0); + testNode(res.nodes.get("Queue Wait"), 10000, time, 5.8, 22.3, 19.1, 8497.7); } @Test public void testSaveExample3() throws KryoException, IOException { var net = new Net(); - net.addNode(ServerNode.createLimitedSource("Source", exp1_5, 10000)); - net.addNode(ServerNode.createQueue("Service1", 1, exp2)); - net.addNode(ServerNode.createQueue("Service2", 1, exp3_5, unExp)); + net.addNode(ServerNode.Builder.sourceLimited("Source", spawn, exp1_5)); + net.addNode(ServerNode.Builder.queue("Service1", 1, exp2)); + net.addNode(ServerNode.Builder.queue("Service2", 1, exp3_5, unExp)); net.addConnection(0, 1, 1.0); net.addConnection(1, 2, 1.0); - net.save("src/main/resources/example3.net"); - net = Net.load("src/main/resources/example3.net"); + net.save(net3); + net = Net.load(net3); + + var sim = new Simulation(net, new Rng()); + var res = sim.run(); + var time = 6736.0; + var maxErr = time / 1000.0; + + assertEquals(Rng.DEFAULT, res.seed); + assertEquals(time, res.simulationTime, maxErr); + testNode(res.nodes.get("Source"), 10000, time, 1.0, 0.6, 0.0, 0.0); + testNode(res.nodes.get("Service1"), 10000, time, 3.5, 1.7, 1.2, 0.0); + testNode(res.nodes.get("Service2"), 10000, time, 1.7, 0.5, 0.22, 102.2); + } + + private void testNode(NodeStats stat, double numClients, double time, double avgQueue, + double avgResponse, double avgWait, double totalUnavailable) { + assertEquals("Num Arrivals", numClients, stat.numArrivals, 0.1); + assertEquals("Num Departures", numClients, stat.numDepartures, 0.1); + + var maxErr = time / 1000.0; + assertEquals(time, stat.lastEventTime, maxErr); + assertEquals("Avg Queue", avgQueue, stat.avgQueueLength, 0.1); + assertEquals("Avg Wait", avgWait, stat.avgWaitTime, 0.1); + assertEquals("Avg Response", avgResponse, stat.avgResponse, 0.1); + + var totalWait = numClients * stat.avgWaitTime; + var totalResponse = numClients * stat.avgResponse; + var totalBusy = totalResponse - totalWait; + + assertEquals("Tot Wait", totalWait, stat.waitTime, maxErr); + assertEquals("Tot Response", totalResponse, stat.responseTime, maxErr); + assertEquals("Tot Busy", totalBusy, stat.busyTime, maxErr); + assertEquals("Tot Unavailable", totalUnavailable, stat.unavailableTime, maxErr); + + assertEquals("Throughput", stat.numDepartures / stat.lastEventTime, stat.throughput, 0.001); + assertEquals("% Busy", stat.busyTime / stat.lastEventTime, stat.utilization, 0.001); + assertEquals("% Unavailable", stat.unavailableTime / stat.lastEventTime, stat.unavailable, 0.001); } } diff --git a/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java b/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java index a814618..4b97ae2 100644 --- a/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java +++ b/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java @@ -23,8 +23,8 @@ public class TestSimulation { private static final ServerNode node0; private static final ServerNode node1; static { - node0 = ServerNode.createLimitedSource("First", const1, 0); - node1 = ServerNode.createQueue("Second", 1, const1); + node0 = ServerNode.Builder.sourceLimited("First", 0, const1); + node1 = ServerNode.Builder.queue("Second", 1, const1); simpleNet = new Net(); simpleNet.addNode(node0); @@ -41,27 +41,27 @@ public class TestSimulation { @Test public void serverNode() { - var node = ServerNode.createQueue("Nodo", 0, const1); + var node = ServerNode.Builder.queue("Nodo", 0, const1); assertEquals("Nodo", node.name); assertEquals(1, node.maxServers); assertEquals(0, node.spawnArrivals); assertEquals(1.0, node.getServiceTime(null), DELTA); - node = ServerNode.createQueue("Queue", 50, const1); + node = ServerNode.Builder.queue("Queue", 50, const1); assertEquals("Queue", node.name); assertEquals(50, node.maxServers); assertEquals(0, node.spawnArrivals); assertEquals(1.0, node.getServiceTime(null), DELTA); - node = ServerNode.createSource("Source", const1); + node = ServerNode.Builder.source("Source", const1); assertEquals("Source", node.name); - assertEquals(Integer.MAX_VALUE, node.maxServers); + assertEquals(1, node.maxServers); assertEquals(Integer.MAX_VALUE, node.spawnArrivals); assertEquals(1.0, node.getServiceTime(null), DELTA); - node = ServerNode.createLimitedSource("Source", const1, 50); + node = ServerNode.Builder.sourceLimited("Source", 50, const1); assertEquals("Source", node.name); - assertEquals(Integer.MAX_VALUE, node.maxServers); + assertEquals(1, node.maxServers); assertEquals(50, node.spawnArrivals); assertEquals(1.0, node.getServiceTime(null), DELTA); } @@ -93,7 +93,7 @@ public class TestSimulation { var net = new Net(); assertEquals(0, net.size()); - var node = ServerNode.createSource("First", const0); + var node = ServerNode.Builder.source("First", const0); var index = net.addNode(node); assertEquals(1, net.size()); assertEquals(0, index); @@ -101,7 +101,7 @@ public class TestSimulation { assertEquals(node, net.getNode("First")); assertEquals(index, net.getNodeIndex("First")); - var node1 = ServerNode.createQueue("Second", 1, const0); + var node1 = ServerNode.Builder.queue("Second", 1, const0); var index1 = net.addNode(node1); assertEquals(2, net.size()); assertEquals(0, index); @@ -126,7 +126,7 @@ public class TestSimulation { conn = net.getChildren(1); assertEquals(0, conn.size()); - var node2 = ServerNode.createQueue("Third", 1, const0); + var node2 = ServerNode.Builder.queue("Third", 1, const0); net.addNode(node2); net.addConnection(0, 2, 1.0); conn = net.getChildren(0); @@ -172,7 +172,104 @@ public class TestSimulation { assertFalse(state.hasRequests()); assertFalse(state.shouldSpawnArrival()); - // TODO better test + state.numServerBusy = 1; + assertEquals(1, state.numServerBusy); + assertFalse(state.canServe()); + assertFalse(state.hasRequests()); + + state.numServerBusy = 0; + state.numServerUnavailable = 1; + assertEquals(1, state.numServerUnavailable); + assertFalse(state.canServe()); + assertFalse(state.hasRequests()); + + state.queue.add(1.0); + assertEquals(1, state.queue.size()); + assertTrue(state.hasRequests()); + assertFalse(state.isQueueFull()); + + state.numServerUnavailable = 0; + state.numServerBusy = 0; + assertTrue(state.canServe()); + assertTrue(state.hasRequests()); + state.numServerBusy = 1; + state.queue.poll(); + assertEquals(0, state.queue.size()); + assertFalse(state.hasRequests()); + } + + @Test + public void nodeStatsUpdates() { + var net = new Net(); + net.addNode(ServerNode.Builder.sourceLimited("Source", 50, const1)); + net.addNode(node1); + net.addConnection(0, 1, 1.0); + + var state = new ServerNodeState(0, net); + + var event = state.spawnArrivalIfPossilbe(0); + assertNotNull(event); + assertEquals(0, state.stats.numArrivals, DELTA); + assertEquals(0, state.stats.numDepartures, DELTA); + assertEquals(0, state.numServerBusy); + assertEquals(0, state.numServerUnavailable); + assertEquals(Event.Type.ARRIVAL, event.type); + assertEquals(0, event.nodeIndex); + state.updateArrival(event.time); + assertEquals(1, state.stats.numArrivals, DELTA); + assertEquals(0, state.numServerBusy); + + event = state.spawnDepartureIfPossible(event.time, rigged); + assertNotNull(event); + assertEquals(1, state.stats.numArrivals, DELTA); + assertEquals(0, state.stats.numDepartures, DELTA); + assertEquals(1, state.numServerBusy); + assertEquals(0, state.numServerUnavailable); + assertEquals(Event.Type.DEPARTURE, event.type); + assertEquals(0, event.nodeIndex); + state.updateDeparture(event.time); + assertEquals(1, state.stats.numArrivals, DELTA); + assertEquals(1, state.stats.numDepartures, DELTA); + assertEquals(0, state.numServerBusy); + assertEquals(0, state.numServerUnavailable); + + state = new ServerNodeState(1, net); + event = state.spawnArrivalIfPossilbe(0); + assertNull(event); + assertEquals(0, state.stats.numArrivals, DELTA); + assertEquals(0, state.stats.numDepartures, DELTA); + assertEquals(0, state.numServerBusy); + assertEquals(0, state.numServerUnavailable); + state.updateArrival(0); + assertEquals(1, state.stats.numArrivals, DELTA); + assertEquals(0, state.numServerBusy); + + event = state.spawnDepartureIfPossible(0, rigged); + assertNotNull(event); + assertEquals(1, state.stats.numArrivals, DELTA); + assertEquals(0, state.stats.numDepartures, DELTA); + assertEquals(1, state.numServerBusy); + assertEquals(0, state.numServerUnavailable); + assertEquals(Event.Type.DEPARTURE, event.type); + assertEquals(1, event.nodeIndex); + state.updateDeparture(event.time); + assertEquals(1, state.stats.numArrivals, DELTA); + assertEquals(1, state.stats.numDepartures, DELTA); + assertEquals(0, state.numServerBusy); + assertEquals(0, state.numServerUnavailable); + + event = state.spawnUnavailableIfPossible(0, rigged); + assertNull(event); + + state = new ServerNodeState(0, net); + event = state.spawnArrivalToChild(0, rigged); + assertNotNull(event); + assertEquals(0, state.stats.numArrivals, DELTA); + assertEquals(0, state.stats.numDepartures, DELTA); + assertEquals(0, state.numServerBusy); + assertEquals(0, state.numServerUnavailable); + assertEquals(Event.Type.ARRIVAL, event.type); + assertEquals(1, event.nodeIndex); } @Test @@ -431,13 +528,13 @@ public class TestSimulation { assertEquals(6, res.nodes.get(node0.name).numArrivals, DELTA); assertEquals(5, res.nodes.get(node0.name).numDepartures, DELTA); assertEquals(4, res.nodes.get(node1.name).numArrivals, DELTA); - assertEquals(0, res.nodes.get(node1.name).numDepartures, DELTA); + assertEquals(3, res.nodes.get(node1.name).numDepartures, DELTA); } @Test public void simulationStats() { var net = new Net(); - net.addNode(ServerNode.createLimitedSource("Source", const1, 50)); + net.addNode(ServerNode.Builder.sourceLimited("Source", 50, const1)); var sim = new Simulation(net, rigged); var result = sim.run(); @@ -454,7 +551,7 @@ public class TestSimulation { assertEquals(1.0, nodeStat.utilization, DELTA); assertEquals(0.0, nodeStat.unavailable, DELTA); - net.addNode(ServerNode.createQueue("Queue", 1, const1)); + net.addNode(ServerNode.Builder.queue("Queue", 1, const1)); net.addConnection(0, 1, 1.0); sim = new Simulation(net, rigged);