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 StartNodeEventStartNode 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 TAddressSimilar 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
The source code for this page can be found here.