PingPong Simulation
Starting from the Distributed PingPong example, in this section we will see how we can run the pinger/ponger configuration as a simulation.
Parent vs. Host
The first change we want to do in order to be able to use the deploy code in simulation (with minimal changes), is use a Parent
and Host
division. The Parent
will connect all subcomponents of the system between themselves and to a Network
and Timer
port.
In deployment, the Host
will create the Timer
and Network
instances and connect them to the Parent
, while in simulation, the simulator will provide the Timer
and Network
, instead, and will connect them to the Parent
.
PingerParent
- Java
-
package jexamples.simulation.pingpong; import se.sics.kompics.Channel; import se.sics.kompics.Component; import se.sics.kompics.ComponentDefinition; import se.sics.kompics.Init; import se.sics.kompics.Positive; import se.sics.kompics.network.Network; import se.sics.kompics.network.netty.NettyInit; import se.sics.kompics.network.netty.NettyNetwork; import se.sics.kompics.timer.Timer; import se.sics.kompics.timer.java.JavaTimer; public class PingerParent extends ComponentDefinition { Positive<Network> network = requires(Network.class); Positive<Timer> timer = requires(Timer.class); public PingerParent() { // create all components except timer and network Component pinger = create(Pinger.class, Init.NONE); // connect required internal components to network and timer connect(pinger.getNegative(Timer.class), timer, Channel.TWO_WAY); connect(pinger.getNegative(Network.class), network, Channel.TWO_WAY); } }
- Scala
-
class PingerParent extends ComponentDefinition { val timer = requires[Timer]; val network = requires[Network]; val pinger = create[Pinger]; connect[Timer](timer -> pinger); connect[Network](network -> pinger); }
PingerHost
- Java
-
package jexamples.simulation.pingpong; import se.sics.kompics.Channel; import se.sics.kompics.Component; import se.sics.kompics.ComponentDefinition; import se.sics.kompics.Init; import se.sics.kompics.network.Network; import se.sics.kompics.network.netty.NettyInit; import se.sics.kompics.network.netty.NettyNetwork; import se.sics.kompics.timer.Timer; import se.sics.kompics.timer.java.JavaTimer; public class PingerHost extends ComponentDefinition { public PingerHost() { TAddress self = config().getValue("pingpong.pinger.addr", TAddress.class); Component network = create(NettyNetwork.class, new NettyInit(self)); Component timer = create(JavaTimer.class, Init.NONE); Component pingerParent = create(PingerParent.class, Init.NONE); connect( pingerParent.getNegative(Network.class), network.getPositive(Network.class), Channel.TWO_WAY); connect(pingerParent.getNegative(Timer.class), timer.getPositive(Timer.class), Channel.TWO_WAY); } }
- Scala
-
class PingerHost extends ComponentDefinition { val self = cfg.getValue[TAddress]("pingpong.pinger.addr"); val timer = create[JavaTimer]; val network = create[NettyNetwork](new NettyInit(self)); val pingerParent = create[PingerParent]; connect[Timer](timer -> pingerParent); connect[Network](network -> pingerParent); }
PongerParent
- Java
-
package jexamples.simulation.pingpong; import se.sics.kompics.Channel; import se.sics.kompics.Component; import se.sics.kompics.ComponentDefinition; import se.sics.kompics.Init; import se.sics.kompics.Positive; import se.sics.kompics.network.Network; import se.sics.kompics.timer.Timer; public class PongerParent extends ComponentDefinition { Positive<Network> network = requires(Network.class); Positive<Timer> timer = requires(Timer.class); public PongerParent() { // create all components except timer and network Component ponger = create(Ponger.class, Init.NONE); // connect required internal components to network and timer connect(ponger.getNegative(Network.class), network, Channel.TWO_WAY); } }
- Scala
-
class PongerParent extends ComponentDefinition { val timer = requires[Timer]; val network = requires[Network]; val ponger = create[Ponger]; connect[Network](network -> ponger); }
PongerHost
- Java
-
package jexamples.simulation.pingpong; import se.sics.kompics.Channel; import se.sics.kompics.Component; import se.sics.kompics.ComponentDefinition; import se.sics.kompics.Init; import se.sics.kompics.network.Network; import se.sics.kompics.network.netty.NettyInit; import se.sics.kompics.network.netty.NettyNetwork; import se.sics.kompics.timer.Timer; import se.sics.kompics.timer.java.JavaTimer; public class PongerHost extends ComponentDefinition { public PongerHost() { TAddress self = config().getValue("pingpong.ponger.addr", TAddress.class); Component network = create(NettyNetwork.class, new NettyInit(self)); Component timer = create(JavaTimer.class, Init.NONE); Component pongerParent = create(PongerParent.class, Init.NONE); connect( pongerParent.getNegative(Network.class), network.getPositive(Network.class), Channel.TWO_WAY); connect(pongerParent.getNegative(Timer.class), timer.getPositive(Timer.class), Channel.TWO_WAY); } }
- Scala
-
class PongerHost extends ComponentDefinition { val self = cfg.getValue[TAddress]("pingpong.ponger.addr"); val timer = create[JavaTimer]; val network = create[NettyNetwork](new NettyInit(self)); val pongerParent = create[PongerParent]; connect[Timer](timer -> pongerParent); connect[Network](network -> pongerParent); }
Simulation Scenario
In the simulation scenario we define two types of StartNodeEvent
StartNode
events for PongerParent
and PingerParent
. The methods that are required to be overridden are very simple, as they are getter methods for the class of the respective parent, an Init
event for it, and the node’s TAddress
Similar to direct component creation, we must specify the type of the component to be created, its Init
event, and also an address for it. We are going to pass addresses in the config as a simple map, which the simulator will convert into a config update instance, when creating the components.
The scenario is set to start 5 ponger nodes and 5 pinger nodes. The sequential distributions will give IPs ending in x={1,2,3,4,5} to the pongers and IPs ending in y={6,7,8,9,10} to pingers and the <x,y> relation is: {<1,6>, <2, 7>, <3,8>, <4,9>, <5,10>} (or simply y = x+5).
SimulationGen
- Java
-
package jexamples.simulation.pingpong; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import se.sics.kompics.Init; import se.sics.kompics.network.Address; import se.sics.kompics.simulator.SimulationScenario; import se.sics.kompics.simulator.adaptor.Operation1; import se.sics.kompics.simulator.adaptor.Operation2; import se.sics.kompics.simulator.adaptor.distributions.extra.BasicIntSequentialDistribution; import se.sics.kompics.simulator.events.system.StartNodeEvent; public class ScenarioGen { public static final int PORT = 12345; public static final String IP_PREFIX = "192.193.0."; static Operation1<StartNodeEvent, Integer> startPongerOp = new Operation1<StartNodeEvent, Integer>() { @Override public StartNodeEvent generate(final Integer self) { return new StartNodeEvent() { TAddress selfAdr; { try { selfAdr = new TAddress(InetAddress.getByName(IP_PREFIX + self), PORT); } catch (UnknownHostException ex) { throw new RuntimeException(ex); } } @Override public Map<String, Object> initConfigUpdate() { HashMap<String, Object> config = new HashMap<>(); config.put("pingpong.ponger.addr", selfAdr); return config; } @Override public Address getNodeAddress() { return selfAdr; } @Override public Class<PongerParent> getComponentDefinition() { return PongerParent.class; } @Override public Init getComponentInit() { return Init.NONE; } @Override public String toString() { return "StartPonger<" + selfAdr.toString() + ">"; } }; } }; static Operation2<StartNodeEvent, Integer, Integer> startPingerOp = new Operation2<StartNodeEvent, Integer, Integer>() { @Override public StartNodeEvent generate(final Integer self, final Integer ponger) { return new StartNodeEvent() { TAddress selfAdr; TAddress pongerAdr; { try { selfAdr = new TAddress(InetAddress.getByName(IP_PREFIX + self), PORT); pongerAdr = new TAddress(InetAddress.getByName(IP_PREFIX + ponger), PORT); } catch (UnknownHostException ex) { throw new RuntimeException(ex); } } @Override public Map<String, Object> initConfigUpdate() { HashMap<String, Object> config = new HashMap<>(); config.put("pingpong.pinger.addr", selfAdr); config.put("pingpong.pinger.pongeraddr", pongerAdr); return config; } @Override public Address getNodeAddress() { return selfAdr; } @Override public Class<PingerParent> getComponentDefinition() { return PingerParent.class; } @Override public Init getComponentInit() { return Init.NONE; } @Override public String toString() { return "StartPinger<" + selfAdr.toString() + ">"; } }; } }; public static SimulationScenario simplePing() { SimulationScenario scen = new SimulationScenario() { { SimulationScenario.StochasticProcess ponger = new SimulationScenario.StochasticProcess() { { eventInterarrivalTime(constant(1000)); raise(5, startPongerOp, new BasicIntSequentialDistribution(1)); } }; SimulationScenario.StochasticProcess pinger = new SimulationScenario.StochasticProcess() { { eventInterarrivalTime(constant(1000)); raise( 5, startPingerOp, new BasicIntSequentialDistribution(6), new BasicIntSequentialDistribution(1)); } }; ponger.start(); pinger.startAfterTerminationOf(1000, ponger); terminateAfterTerminationOf(10000, pinger); } }; return scen; } }
- Scala
-
package sexamples.simulation.pingpong import se.sics.kompics.sl._ import se.sics.kompics.sl.simulator._ import se.sics.kompics.simulator.{SimulationScenario => JSimulationScenario} import scala.concurrent.duration._ import java.net.InetAddress; object ScenarioGen { import Distributions._ // needed for the distributions, but needs to be initialised after setting the seed implicit val random = JSimulationScenario.getRandom(); val PORT = 12345; val IP_PREFIX = "192.193.0."; def makeAddress(num: Int): TAddress = { TAddress(InetAddress.getByName(s"${IP_PREFIX}${num}"), PORT); } val startPongerOp = Op { (self: java.lang.Integer) => StartNode[PongerParent, TAddress](makeAddress(self), Init.none[PongerParent], Map("pingpong.ponger.addr" -> makeAddress(self))) }; val startPingerOp = Op { (self: java.lang.Integer, ponger: java.lang.Integer) => StartNode[PingerParent, TAddress](makeAddress(self), Init.none[PingerParent], Map( "pingpong.pinger.addr" -> makeAddress(self), "pingpong.pinger.pongeraddr" -> makeAddress(ponger) )) }; val scenario = raise(5, startPongerOp, 1.toN) .arrival(constant(1.second)) .andThen(1.second) .afterTermination( raise(5, startPingerOp, 6.toN, 1.toN).arrival(constant(1.second)) ) .andThen(10.seconds) .afterTermination(Terminate); }
Main
In order to start our simulation, we are going to add another branch to our Main
classobject, allowing us to specify simulation
as a command line parameter, as well.
- Java
-
} else if (args[0].equalsIgnoreCase("simulation")) { long seed = 123; SimulationScenario.setSeed(seed); SimulationScenario simpleBootScenario = ScenarioGen.simplePing(); System.out.println("Starting a Simulation"); simpleBootScenario.simulate(LauncherComp.class); } else {
- Scala
-
} else if (args(0).equalsIgnoreCase("simulation")) { val seed = 123; SimulationScenario.setSeed(seed); System.out.println("Starting a Simulation"); ScenarioGen.scenario.simulate(classOf[LauncherComp]); } else {
Execution
Now we can simply run out simulation from within sbt as before with:
runMain jexamples.simulation.pingpong.Main simulation
runMain sexamples.simulation.pingpong.Main simulation