S4U Examples

SimGrid comes with an extensive set of examples, documented on this page. Most of them only demonstrate one single feature, with some larger exemplars listed below.

The C++ examples can be found under examples/cpp while python examples are in examples/python. Each such directory contains the source code (also listed from this page), and the so-called tesh file containing how to call the binary obtained by compiling this example and also the expected output. Tesh files are used to turn each of our examples into an integration test. Some examples also contain other files, on need.

A good way to bootstrap your own project is to copy and combine some of the provided examples to constitute the skeleton of what you plan to simulate.

Actors: the Active Entities

Starting and Stopping Actors

Creating actors

Most actors are started from the deployment XML file because this is a better scientific habit, but you can also create them directly from your code.

You create actors either:

View examples/cpp/actor-create/s4u-actor-create.cpp

Download s4u-actor-create.cpp

/* Copyright (c) 2006-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example shows how to declare and start your actors.
 *
 * The first step is to declare the code of your actors (what they do exactly does not matter to this example) and then
 * you ask SimGrid to start your actors. There is three ways of doing so:
 *  - Directly, by instantiating your actor as parameter to Actor::create()
 *  - By first registering your actors before instantiating it
 *  - Through the deployment file.
 *
 * This example shows all these solutions, even if you obviously should use only one of these solutions to start your
 * actors. The most advised solution is to use a deployment file, as it creates a clear separation between your
 * application and the settings to test it. This is a better scientific methodology. Actually, starting an actor with
 * Actor::create() is mostly useful to start an actor from another actor.
 */

#include <simgrid/s4u.hpp>
#include <string>
namespace sg4 = simgrid::s4u;

// This declares a logging channel so that XBT_INFO can be used later
XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_actor_create, "The logging channel used in this example");

/* Our first class of actors is simply implemented with a function, that takes a single string as parameter.
 *
 * Later, this actor class is instantiated within the simulation.
 */
static void receiver(const std::string& mailbox_name)
{
  sg4::Mailbox* mailbox = sg4::Mailbox::by_name(mailbox_name);

  XBT_INFO("Hello s4u, I'm ready to get any message you'd want on %s", mailbox->get_cname());

  auto msg1 = mailbox->get_unique<std::string>();
  auto msg2 = mailbox->get_unique<std::string>();
  auto msg3 = mailbox->get_unique<std::string>();
  XBT_INFO("I received '%s', '%s' and '%s'", msg1->c_str(), msg2->c_str(), msg3->c_str());
  XBT_INFO("I'm done. See you.");
}

/* Our second class of actors is also a function */
static void forwarder(int argc, char** argv)
{
  xbt_assert(argc >= 3, "Actor forwarder requires 2 parameters, but got only %d", argc - 1);
  sg4::Mailbox* in             = sg4::Mailbox::by_name(argv[1]);
  sg4::Mailbox* out            = sg4::Mailbox::by_name(argv[2]);
  auto* msg                    = in->get<std::string>();
  XBT_INFO("Forward '%s'.", msg->c_str());
  out->put(msg, msg->size());
}

/* Declares a third class of actors which sends a message to the mailbox 'mb42'.
 * The sent message is what was passed as parameter on creation (or 'GaBuZoMeu' by default)
 *
 * Later, this actor class is instantiated twice in the simulation.
 */
class Sender {
public:
  std::string mbox  = "mb42";
  std::string msg = "GaBuZoMeu";
  explicit Sender() = default; /* Sending the default message */
  explicit Sender(const std::string& arg) : msg(arg) { /* Sending the specified message */}
  explicit Sender(std::vector<std::string> args)
  {
    /* This constructor is used when we start the actor from the deployment file */
    /* args[0] is the actor's name, so the first parameter is args[1] */

    xbt_assert(args.size() >= 3, "The sender is expecting 2 parameters from the deployment file but got %zu",
               args.size() - 1);
    msg  = args[1];
    mbox = args[2];
  }
  void operator()() const /* This is the main code of the actor */
  {
    XBT_INFO("Hello s4u, I have something to send");
    sg4::Mailbox* mailbox = sg4::Mailbox::by_name(mbox);

    mailbox->put(new std::string(msg), msg.size());
    XBT_INFO("I'm done. See you.");
  }
};

/* Here comes the main function of your program */
int main(int argc, char** argv)
{
  /* When your program starts, you have to first start a new simulation engine, as follows */
  sg4::Engine e(&argc, argv);

  /* Then you should load a platform file, describing your simulated platform */
  e.load_platform(argc > 1 ? argv[1] : "../../platforms/small_platform.xml");

  /* And now you have to ask SimGrid to actually start your actors.
   *
   * The easiest way to do so is to implement the behavior of your actor in a single function,
   * as we do here for the receiver actors. This function can take any kind of parameters, as
   * long as the last parameters of Actor::create() match what your function expects.
   */
  sg4::Actor::create("receiver", e.host_by_name("Fafard"), &receiver, "mb42");

  /* If your actor is getting more complex, you probably want to implement it as a class instead,
   * as we do here for the sender actors. The main behavior goes into operator()() of the class.
   *
   * You can then directly start your actor, as follows: */
  sg4::Actor::create("sender1", e.host_by_name("Tremblay"), Sender());
  /* If you want to pass parameters to your class, that's very easy: just use your constructors */
  sg4::Actor::create("sender2", e.host_by_name("Jupiter"), Sender("GloubiBoulga"));

  /* But starting actors directly is considered as a bad experimental habit, since it ties the code
   * you want to test with the experimental scenario. Starting your actors from an external deployment
   * file in XML ensures that you can test your code in several scenarios without changing the code itself.
   *
   * For that, you first need to register your function or your actor as follows.
   * Actor classes must have a (std::vector<std::string>) constructor,
   * and actor functions must be of type int(*)(int argc, char**argv). */
  e.register_actor<Sender>("sender"); // The sender class is passed as a template parameter here
  e.register_function("forwarder", &forwarder);
  /* Once actors and functions are registered, just load the deployment file */
  e.load_deployment(argc > 2 ? argv[2] : "s4u-actor-create_d.xml");

  /* Once every actors are started in the engine, the simulation can start */
  e.run();

  /* Once the simulation is done, the program is ended */
  return 0;
}

Reacting to actors’ end

You can attach callbacks to the end of actors. There are several ways of doing so, depending on whether you want to attach your callback to a given actor and on how you define the end of a given actor. User code probably wants to react to the termination of an actor while some plugins want to react to the destruction (memory collection) of actors.

This example shows how to attach a callback to:

View examples/cpp/actor-exiting/s4u-actor-exiting.cpp

Download s4u-actor-exiting.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* There is two very different ways of being informed when an actor exits.
 *
 * The this_actor::on_exit() function allows one to register a function to be
 * executed when this very actor exits. The registered function will run
 * when this actor terminates (either because its main function returns, or
 * because it's killed in any way). No simcall are allowed here: your actor
 * is dead already, so it cannot interact with its environment in any way
 * (network, executions, disks, etc).
 *
 * Usually, the functions registered in this_actor::on_exit() are in charge
 * of releasing any memory allocated by the actor during its execution.
 *
 * The other way of getting informed when an actor terminates is to connect a
 * function in the Actor::on_termination signal, that is shared between
 * all actors. Callbacks to this signal are executed for each terminating
 * actors, no matter what. This is useful in many cases, in particular
 * when developing SimGrid plugins.
 *
 * Finally, you can attach callbacks to the Actor::on_destruction signal.
 * It is also shared between all actors, and gets fired when the actors
 * are destroyed. A delay is possible between the termination of an actor
 * (ie, when it terminates executing its code) and its destruction (ie,
 * when it is not referenced anywhere in the simulation and can be collected).
 *
 * In both cases, you can stack more than one callback in the signal.
 * They will all be executed in the registration order.
 */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_actor_exiting, "Messages specific for this s4u example");

static void actor_a()
{
  // Register a lambda function to be executed once it stops
  sg4::this_actor::on_exit([](bool /*failed*/) { XBT_INFO("I stop now"); });

  sg4::this_actor::sleep_for(1);
}

static void actor_b()
{
  sg4::this_actor::sleep_for(2);
}

static void actor_c()
{
  // Register a lambda function to be executed once it stops
  sg4::this_actor::on_exit([](bool failed) {
    if (failed) {
      XBT_INFO("I was killed!");
      if (xbt_log_no_loc)
        XBT_INFO("The backtrace would be displayed here if --log=no_loc would not have been passed");
      else
        xbt_backtrace_display_current();
    } else
      XBT_INFO("Exiting gracefully.");
  });

  sg4::this_actor::sleep_for(3);
  XBT_INFO("And now, induce a deadlock by waiting for a message that will never come\n\n");
  sg4::Mailbox::by_name("nobody")->get<void>();
  xbt_die("Receiving is not supposed to succeed when nobody is sending");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/small_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]); /* - Load the platform description */

  /* Register a callback in the Actor::on_termination signal. It will be called for every terminated actors */
  sg4::Actor::on_termination_cb(
      [](sg4::Actor const& actor) { XBT_INFO("Actor %s terminates now", actor.get_cname()); });
  /* Register a callback in the Actor::on_destruction signal. It will be called for every destructed actors */
  sg4::Actor::on_destruction_cb(
      [](sg4::Actor const& actor) { XBT_INFO("Actor %s gets destroyed now", actor.get_cname()); });

  /* Create some actors */
  sg4::Actor::create("A", e.host_by_name("Tremblay"), actor_a);
  sg4::Actor::create("B", e.host_by_name("Fafard"), actor_b);
  sg4::Actor::create("C", e.host_by_name("Ginette"), actor_c);

  e.run(); /* - Run the simulation */

  return 0;
}

Killing actors

Actors can forcefully stop other actors.

See also void simgrid::s4u::Actor::kill(void), void simgrid::s4u::Actor::kill_all(), simgrid::s4u::this_actor::exit(), simgrid::s4u::Actor::on_exit().

View examples/cpp/actor-kill/s4u-actor-kill.cpp

Download s4u-actor-kill.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_actor_kill, "Messages specific for this s4u example");

static void victimA_fun()
{
  sg4::this_actor::on_exit([](bool /*failed*/) { XBT_INFO("I have been killed!"); });
  XBT_INFO("Hello!");
  XBT_INFO("Suspending myself");
  sg4::this_actor::suspend();          /* - Start by suspending itself */
  XBT_INFO("OK, OK. Let's work");      /* - Then is resumed and start to execute some flops */
  sg4::this_actor::execute(1e9);
  XBT_INFO("Bye!"); /* - But will never reach the end of it */
}

static void victimB_fun()
{
  XBT_INFO("Terminate before being killed");
}

static void killer()
{
  XBT_INFO("Hello!"); /* - First start a victim actor */
  sg4::ActorPtr victimA = sg4::Actor::create("victim A", sg4::Host::by_name("Fafard"), victimA_fun);
  sg4::ActorPtr victimB = sg4::Actor::create("victim B", sg4::Host::by_name("Jupiter"), victimB_fun);
  sg4::this_actor::sleep_for(10); /* - Wait for 10 seconds */

  XBT_INFO("Resume the victim A"); /* - Resume it from its suspended state */
  victimA->resume();
  sg4::this_actor::sleep_for(2);

  XBT_INFO("Kill the victim A"); /* - and then kill it */
  sg4::Actor::by_pid(victimA->get_pid())->kill(); // You can retrieve an actor from its PID (and then kill it)

  sg4::this_actor::sleep_for(1);

  XBT_INFO("Kill victimB, even if it's already dead"); /* that's a no-op, there is no zombies in SimGrid */
  victimB->kill(); // the actor is automatically garbage-collected after this last reference

  sg4::this_actor::sleep_for(1);

  XBT_INFO("Start a new actor, and kill it right away");
  sg4::ActorPtr victimC = sg4::Actor::create("victim C", sg4::Host::by_name("Jupiter"), victimA_fun);
  victimC->kill();

  sg4::this_actor::sleep_for(1);

  XBT_INFO("Killing everybody but myself");
  sg4::Actor::kill_all();

  XBT_INFO("OK, goodbye now. I commit a suicide.");
  sg4::this_actor::exit();

  XBT_INFO("This line never gets displayed: I'm already dead since the previous line.");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/small_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]); /* - Load the platform description */
  /* - Create and deploy killer actor, that will create the victim actors  */
  sg4::Actor::create("killer", e.host_by_name("Tremblay"), killer);

  e.run(); /* - Run the simulation */

  return 0;
}

Actors’ life cycle from XML_reference

You can specify a start time and a kill time in the deployment file.

This file is not really interesting: the important matter is in the XML file.

View examples/cpp/actor-lifetime/s4u-actor-lifetime.cpp

Download s4u-actor-lifetime.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This C++ file acts as the foil to the corresponding XML file, where the
   action takes place: Actors are started and stopped at predefined time.   */

#include "simgrid/s4u.hpp"
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(test, "Messages specific for this s4u example");

/* This actor just sleeps until termination */
class sleeper {
public:
  explicit sleeper(std::vector<std::string> /*args*/)
  {
    sg4::this_actor::on_exit([](bool /*failed*/) {
      /* Executed on actor termination, to display a message helping to understand the output */
      XBT_INFO("Exiting now (done sleeping or got killed).");
    });
  }
  void operator()() const
  {
    XBT_INFO("Hello! I go to sleep.");
    sg4::this_actor::sleep_for(10);
    XBT_INFO("Done sleeping.");
  }
};

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  xbt_assert(argc > 2,
             "Usage: %s platform_file deployment_file\n"
             "\tExample: %s ../platforms/cluster_backbone.xml ./s4u_actor_lifetime_d.xml\n",
             argv[0], argv[0]);

  e.load_platform(argv[1]); /* Load the platform description */
  e.register_actor<sleeper>("sleeper");
  e.load_deployment(argv[2]); /*  Deploy the sleeper actors with explicit start/kill times */

  e.run(); /* - Run the simulation */

  return 0;
}

Daemon actors

Some actors may be intended to simulate daemons that run in the background. This example shows how to transform a regular actor into a daemon that will be automatically killed once the simulation is over.

See also simgrid::s4u::Actor::daemonize() and simgrid::s4u::Actor::is_daemon().

View examples/cpp/actor-daemon/s4u-actor-daemon.cpp

Download s4u-actor-daemon.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_actor_daemon, "Messages specific for this s4u example");

/* The worker actor, working for a while before leaving */
static void worker()
{
  XBT_INFO("Let's do some work (for 10 sec on Boivin).");
  sg4::this_actor::execute(980.95e6);

  XBT_INFO("I'm done now. I leave even if it makes the daemon die.");
}

/* The daemon, displaying a message every 3 seconds until all other actors stop */
static void my_daemon()
{
  sg4::Actor::self()->daemonize();

  while (sg4::this_actor::get_host()->is_on()) {
    XBT_INFO("Hello from the infinite loop");
    sg4::this_actor::sleep_for(3.0);
  }

  XBT_INFO("I will never reach that point: daemons are killed when regular actors are done");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);
  sg4::Actor::create("worker", e.host_by_name("Boivin"), worker);
  sg4::Actor::create("daemon", e.host_by_name("Tremblay"), my_daemon);

  e.run();
  return 0;
}

Specifying the stack size

The stack size can be specified by default on the command line, globally by changing the configuration with simgrid::s4u::Engine::set_config(), or for a specific actor using simgrid::s4u::Actor::set_stacksize() before its start.

View examples/cpp/actor-stacksize/s4u-actor-stacksize.cpp

Download s4u-actor-stacksize.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This code tests that we can change the stack-size between the actors creation. */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");

static void actor()
{
  XBT_INFO("Hello");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  // If you don't specify anything, you get the default size (8Mb) or the one passed on the command line
  sg4::Actor::create("actor", e.host_by_name("Tremblay"), actor);

  // You can use set_config(string) to pass a size that will be parsed. That value will be used for any subsequent
  // actors
  sg4::Engine::set_config("contexts/stack-size:16384");
  sg4::Actor::create("actor", e.host_by_name("Tremblay"), actor);
  sg4::Actor::create("actor", e.host_by_name("Tremblay"), actor);

  // You can use set_config(key, value) for the same effect.
  sg4::Engine::set_config("contexts/stack-size", 32 * 1024);
  sg4::Actor::create("actor", e.host_by_name("Tremblay"), actor);
  sg4::Actor::create("actor", e.host_by_name("Tremblay"), actor);

  // Or you can use set_stacksize() before starting the actor to modify only this one
  sg4::Actor::init("actor", e.host_by_name("Tremblay"))->set_stacksize(64 * 1024)->start(actor);
  sg4::Actor::create("actor", e.host_by_name("Tremblay"), actor);

  e.run();
  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Inter-Actors Interactions

See also the examples on inter-actors communications and the ones on classical synchronization objects.

Suspending/resuming Actors

Actors can be suspended and resumed during their executions.

See also simgrid::s4u::this_actor::suspend(), simgrid::s4u::Actor::suspend(), simgrid::s4u::Actor::resume(), and simgrid::s4u::Actor::is_suspended().

View examples/cpp/actor-suspend/s4u-actor-suspend.cpp

Download s4u-actor-suspend.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_actor_suspend, "Messages specific for this s4u example");

/* The Lazy guy only wants to sleep, but can be awaken by the dream_master actor. */
static void lazy_guy()
{
  XBT_INFO("Nobody's watching me ? Let's go to sleep.");
  sg4::this_actor::suspend(); /* - Start by suspending itself */
  XBT_INFO("Uuuh ? Did somebody call me ?");

  XBT_INFO("Going to sleep..."); /* - Then repetitively go to sleep, but got awaken */
  sg4::this_actor::sleep_for(10);
  XBT_INFO("Mmm... waking up.");

  XBT_INFO("Going to sleep one more time (for 10 sec)...");
  sg4::this_actor::sleep_for(10);
  XBT_INFO("Waking up once for all!");

  XBT_INFO("Ok, let's do some work, then (for 10 sec on Boivin).");
  sg4::this_actor::execute(980.95e6);

  XBT_INFO("Mmmh, I'm done now. Goodbye.");
}

/* The Dream master: */
static void dream_master()
{
  XBT_INFO("Let's create a lazy guy."); /* - Create a lazy_guy actor */
  sg4::ActorPtr lazy = sg4::Actor::create("Lazy", sg4::this_actor::get_host(), lazy_guy);
  XBT_INFO("Let's wait a little bit...");
  sg4::this_actor::sleep_for(10); /* - Wait for 10 seconds */
  XBT_INFO("Let's wake the lazy guy up! >:) BOOOOOUUUHHH!!!!");
  if (lazy->is_suspended())
    lazy->resume(); /* - Then wake up the lazy_guy */
  else
    XBT_ERROR("I was thinking that the lazy guy would be suspended now");

  sg4::this_actor::sleep_for(5); /* Repeat two times: */
  XBT_INFO("Suspend the lazy guy while he's sleeping...");
  lazy->suspend(); /* - Suspend the lazy_guy while he's asleep */
  XBT_INFO("Let him finish his siesta.");
  sg4::this_actor::sleep_for(10); /* - Wait for 10 seconds */
  XBT_INFO("Wake up, lazy guy!");
  lazy->resume(); /* - Then wake up the lazy_guy again */

  sg4::this_actor::sleep_for(5);
  XBT_INFO("Suspend again the lazy guy while he's sleeping...");
  lazy->suspend();
  XBT_INFO("This time, don't let him finish his siesta.");
  sg4::this_actor::sleep_for(2);
  XBT_INFO("Wake up, lazy guy!");
  lazy->resume();

  sg4::this_actor::sleep_for(5);
  XBT_INFO("Give a 2 seconds break to the lazy guy while he's working...");
  lazy->suspend();
  sg4::this_actor::sleep_for(2);
  XBT_INFO("Back to work, lazy guy!");
  lazy->resume();

  XBT_INFO("OK, I'm done here.");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/small_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]); /* - Load the platform description */
  std::vector<sg4::Host*> list = e.get_all_hosts();
  sg4::Actor::create("dream_master", list.front(), dream_master);

  e.run(); /* - Run the simulation */

  return 0;
}

Migrating Actors

Actors can move or be moved from a host to another very easily. It amounts to setting them on a new host.

See also simgrid::s4u::this_actor::set_host() and simgrid::s4u::Actor::set_host().

View examples/cpp/actor-migrate/s4u-actor-migrate.cpp

Download s4u-actor-migrate.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example demonstrate the actor migrations.
 *
 * The worker actor first move by itself, and then start an execution.
 * During that execution, the monitor migrates the worker, that wakes up on another host.
 * The execution was of the right amount of flops to take exactly 5 seconds on the first host
 * and 5 other seconds on the second one, so it stops after 10 seconds.
 *
 * Then another migration is done by the monitor while the worker is suspended.
 *
 * Note that worker() takes an uncommon set of parameters,
 * and that this is perfectly accepted by create().
 */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_actor_migration, "Messages specific for this s4u example");

static void worker(sg4::Host* first, const sg4::Host* second)
{
  double flopAmount = first->get_speed() * 5 + second->get_speed() * 5;

  XBT_INFO("Let's move to %s to execute %.2f Mflops (5sec on %s and 5sec on %s)", first->get_cname(), flopAmount / 1e6,
           first->get_cname(), second->get_cname());

  sg4::this_actor::set_host(first);
  sg4::this_actor::execute(flopAmount);

  XBT_INFO("I wake up on %s. Let's suspend a bit", sg4::this_actor::get_host()->get_cname());

  sg4::this_actor::suspend();

  XBT_INFO("I wake up on %s", sg4::this_actor::get_host()->get_cname());
  XBT_INFO("Done");
}

static void monitor()
{
  sg4::Host* boivin    = sg4::Host::by_name("Boivin");
  sg4::Host* jacquelin = sg4::Host::by_name("Jacquelin");
  sg4::Host* fafard    = sg4::Host::by_name("Fafard");

  sg4::ActorPtr actor = sg4::Actor::create("worker", fafard, worker, boivin, jacquelin);

  sg4::this_actor::sleep_for(5);

  XBT_INFO("After 5 seconds, move the actor to %s", jacquelin->get_cname());
  actor->set_host(jacquelin);

  sg4::this_actor::sleep_until(15);
  XBT_INFO("At t=15, move the actor to %s and resume it.", fafard->get_cname());
  actor->set_host(fafard);
  actor->resume();
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/small_platform.xml\n", argv[0], argv[0]);
  e.load_platform(argv[1]);

  sg4::Actor::create("monitor", e.host_by_name("Boivin"), monitor);
  e.run();

  return 0;
}

Waiting for the termination of an actor (joining on it)

You can block the current actor until the end of another actor.

See also simgrid::s4u::Actor::join().

View examples/cpp/actor-join/s4u-actor-join.cpp

Download s4u-actor-join.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");

static void sleeper()
{
  XBT_INFO("Sleeper started");
  sg4::this_actor::sleep_for(3);
  XBT_INFO("I'm done. See you!");
}

static void master()
{
  sg4::ActorPtr actor;

  XBT_INFO("Start sleeper");
  actor = sg4::Actor::create("sleeper from master", sg4::Host::current(), sleeper);
  XBT_INFO("Join the sleeper (timeout 2)");
  actor->join(2);

  XBT_INFO("Start sleeper");
  actor = sg4::Actor::create("sleeper from master", sg4::Host::current(), sleeper);
  XBT_INFO("Join the sleeper (timeout 4)");
  actor->join(4);

  XBT_INFO("Start sleeper");
  actor = sg4::Actor::create("sleeper from master", sg4::Host::current(), sleeper);
  XBT_INFO("Join the sleeper (timeout 2)");
  actor->join(2);

  XBT_INFO("Start sleeper");
  actor = sg4::Actor::create("sleeper from master", sg4::Host::current(), sleeper);
  XBT_INFO("Waiting 4");
  sg4::this_actor::sleep_for(4);
  XBT_INFO("Join the sleeper after its end (timeout 1)");
  actor->join(1);

  XBT_INFO("Goodbye now!");

  sg4::this_actor::sleep_for(1);

  XBT_INFO("Goodbye now!");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/small_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  sg4::Actor::create("master", e.host_by_name("Tremblay"), master);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Yielding to other actors

The `yield()` function interrupts the execution of the current actor, leaving a chance to the other actors that are ready to run at this timestamp.

See also simgrid::s4u::this_actor::yield().

View examples/cpp/actor-yield/s4u-actor-yield.cpp

Download s4u-actor-yield.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

/* This example does not much: It just spans over-polite actor that yield a large amount
 * of time before ending.
 *
 * This serves as an example for the sg4::this_actor::yield() function, with which an actor can request
 * to be rescheduled after the other actor that are ready at the current timestamp.
 *
 * It can also be used to benchmark our context-switching mechanism.
 */
XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_actor_yield, "Messages specific for this s4u example");

static void yielder(long number_of_yields)
{
  for (int i = 0; i < number_of_yields; i++)
    sg4::this_actor::yield();
  XBT_INFO("I yielded %ld times. Goodbye now!", number_of_yields);
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);             /* Load the platform description */

  sg4::Actor::create("yielder", e.host_by_name("Tremblay"), yielder, 10);
  sg4::Actor::create("yielder", e.host_by_name("Ruby"), yielder, 15);

  e.run(); /* - Run the simulation */

  return 0;
}

Traces Replay as a Workload

This section details how to run trace-driven simulations. It is very handy when you want to test an algorithm or protocol that only reacts to external events. For example, many P2P protocols react to user requests, but do nothing if there is no such event.

In such situations, you should write your protocol in C++, and separate the workload that you want to play onto your protocol in a separate text file. Declare a function handling each type of the events in your trace, register them using xbt_replay_action_register() in your main, and then run the simulation.

Then, you can either have one trace file containing all your events, or a file per simulated process: the former may be easier to work with, but the second is more efficient on very large traces. Check also the tesh files in the example directories for details.

Communication replay

Presents a set of event handlers reproducing classical communication primitives (asynchronous send/receive at the moment).

View examples/cpp/replay-comm/s4u-replay-comm.cpp

Download s4u-replay-comm.cpp

/* Copyright (c) 2009-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include "xbt/replay.hpp"
#include "xbt/str.h"
#include <boost/algorithm/string/join.hpp>
#include <cinttypes>
#include <string>

XBT_LOG_NEW_DEFAULT_CATEGORY(replay_comm, "Messages specific for this example");
namespace sg4 = simgrid::s4u;

#define ACT_DEBUG(...)                                                                                                 \
  if (XBT_LOG_ISENABLED(replay_comm, xbt_log_priority_verbose)) {                                                   \
    std::string NAME = boost::algorithm::join(action, " ");                                                            \
    XBT_DEBUG(__VA_ARGS__);                                                                                            \
  } else                                                                                                               \
  ((void)0)

static void log_action(const simgrid::xbt::ReplayAction& action, double date)
{
  if (XBT_LOG_ISENABLED(replay_comm, xbt_log_priority_verbose)) {
    std::string s = boost::algorithm::join(action, " ");
    XBT_VERB("%s %f", s.c_str(), date);
  }
}

class Replayer {
public:
  explicit Replayer(std::vector<std::string> args)
  {
    const char* actor_name     = args.at(0).c_str();
    if (args.size() > 1) { // split mode, the trace file was provided in the deployment file
      const char* trace_filename = args[1].c_str();
      simgrid::xbt::replay_runner(actor_name, trace_filename);
    } else { // Merged mode
      simgrid::xbt::replay_runner(actor_name);
    }
  }

  void operator()() const
  {
    // Nothing to do here
  }

  /* My actions */
  static void compute(simgrid::xbt::ReplayAction& action)
  {
    double amount = std::stod(action[2]);
    double clock  = sg4::Engine::get_clock();
    ACT_DEBUG("Entering %s", NAME.c_str());
    sg4::this_actor::execute(amount);
    log_action(action, sg4::Engine::get_clock() - clock);
  }

  static void send(simgrid::xbt::ReplayAction& action)
  {
    auto size                 = static_cast<uint64_t>(std::stod(action[3]));
    auto* payload             = new std::string(action[3]);
    double clock              = sg4::Engine::get_clock();
    sg4::Mailbox* to          = sg4::Mailbox::by_name(sg4::this_actor::get_name() + "_" + action[2]);
    ACT_DEBUG("Entering Send: %s (size: %" PRIu64 ") -- Actor %s on mailbox %s", NAME.c_str(), size,
              sg4::this_actor::get_cname(), to->get_cname());
    to->put(payload, size);
    log_action(action, sg4::Engine::get_clock() - clock);
  }

  static void recv(simgrid::xbt::ReplayAction& action)
  {
    double clock       = sg4::Engine::get_clock();
    sg4::Mailbox* from = sg4::Mailbox::by_name(action[2] + "_" + sg4::this_actor::get_name());

    ACT_DEBUG("Receiving: %s -- Actor %s on mailbox %s", NAME.c_str(), sg4::this_actor::get_cname(), from->get_cname());
    from->get_unique<std::string>();
    log_action(action, sg4::Engine::get_clock() - clock);
  }
};

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  xbt_assert(argc > 2,
             "Usage: %s platform_file deployment_file [action_files]\n"
             "\t# if all actions are in the same file\n"
             "\tExample: %s platform.xml deployment.xml actions\n"
             "\t# if actions are in separate files, specified in deployment\n"
             "\tExample: %s platform.xml deployment.xml ",
             argv[0], argv[0], argv[0]);

  e.load_platform(argv[1]);
  e.register_actor<Replayer>("p0");
  e.register_actor<Replayer>("p1");
  e.load_deployment(argv[2]);
  if (argv[3] != nullptr)
    xbt_replay_set_tracefile(argv[3]);

  /*   Action registration */
  xbt_replay_action_register("compute", Replayer::compute);
  xbt_replay_action_register("send", Replayer::send);
  xbt_replay_action_register("recv", Replayer::recv);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

I/O replay

Presents a set of event handlers reproducing classical I/O primitives (open, read, close).

View examples/cpp/replay-io/s4u-replay-io.cpp

Download s4u-replay-io.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/plugins/file_system.h>
#include <simgrid/s4u.hpp>
#include <xbt/replay.hpp>
#include <xbt/str.h>

#include <boost/algorithm/string/join.hpp>

XBT_LOG_NEW_DEFAULT_CATEGORY(replay_io, "Messages specific for this example");
namespace sg4 = simgrid::s4u;

#define ACT_DEBUG(...)                                                                                                 \
  if (XBT_LOG_ISENABLED(replay_io, xbt_log_priority_verbose)) {                                                        \
    std::string NAME = boost::algorithm::join(action, " ");                                                            \
    XBT_DEBUG(__VA_ARGS__);                                                                                            \
  } else                                                                                                               \
    ((void)0)

class Replayer {
  static std::unordered_map<std::string, sg4::File*> opened_files;

  static void log_action(const simgrid::xbt::ReplayAction& action, double date)
  {
    if (XBT_LOG_ISENABLED(replay_io, xbt_log_priority_verbose)) {
      std::string s = boost::algorithm::join(action, " ");
      XBT_VERB("%s %f", s.c_str(), date);
    }
  }

  static sg4::File* get_file_descriptor(const std::string& file_name)
  {
    std::string full_name = sg4::this_actor::get_name() + ":" + file_name;
    return opened_files.at(full_name);
  }

public:
  explicit Replayer(std::vector<std::string> args)
  {
    const char* actor_name = args[0].c_str();
    if (args.size() > 1) { // split mode, the trace file was provided in the deployment file
      const char* trace_filename = args[1].c_str();
      simgrid::xbt::replay_runner(actor_name, trace_filename);
    } else { // Merged mode
      simgrid::xbt::replay_runner(actor_name);
    }
  }

  void operator()() const
  {
    // Nothing to do here
  }

  /* My actions */
  static void open(simgrid::xbt::ReplayAction& action)
  {
    std::string file_name = action[2];
    double clock          = sg4::Engine::get_clock();
    std::string full_name = sg4::this_actor::get_name() + ":" + file_name;

    ACT_DEBUG("Entering Open: %s (filename: %s)", NAME.c_str(), file_name.c_str());
    auto* file = sg4::File::open(file_name, nullptr);
    opened_files.try_emplace(full_name, file);

    log_action(action, sg4::Engine::get_clock() - clock);
  }

  static void read(simgrid::xbt::ReplayAction& action)
  {
    std::string file_name = action[2];
    sg_size_t size        = std::stoul(action[3]);
    double clock          = sg4::Engine::get_clock();

    sg4::File* file = get_file_descriptor(file_name);

    ACT_DEBUG("Entering Read: %s (size: %llu)", NAME.c_str(), size);
    file->read(size);

    log_action(action, sg4::Engine::get_clock() - clock);
  }

  static void close(simgrid::xbt::ReplayAction& action)
  {
    std::string file_name = action[2];
    std::string full_name = sg4::this_actor::get_name() + ":" + file_name;
    double clock          = sg4::Engine::get_clock();

    ACT_DEBUG("Entering Close: %s (filename: %s)", NAME.c_str(), file_name.c_str());
    auto entry = opened_files.find(full_name);
    xbt_assert(entry != opened_files.end(), "File not found in opened files: %s", full_name.c_str());
    entry->second->close();
    opened_files.erase(entry);
    log_action(action, sg4::Engine::get_clock() - clock);
  }
};

std::unordered_map<std::string, sg4::File*> Replayer::opened_files;

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  sg_storage_file_system_init();

  xbt_assert(argc > 3,
             "Usage: %s platform_file deployment_file [action_files]\n"
             "\texample: %s platform.xml deployment.xml actions # if all actions are in the same file\n"
             "\t# if actions are in separate files, specified in deployment\n"
             "\texample: %s platform.xml deployment.xml",
             argv[0], argv[0], argv[0]);

  e.load_platform(argv[1]);
  e.register_actor<Replayer>("p0");
  e.load_deployment(argv[2]);

  if (argv[3] != nullptr)
    xbt_replay_set_tracefile(argv[3]);

  /*   Action registration */
  xbt_replay_action_register("open", Replayer::open);
  xbt_replay_action_register("read", Replayer::read);
  xbt_replay_action_register("close", Replayer::close);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Activities: what Actors do

Communications on the Network

Basic communications

This simple example just sends one message back and forth. The tesh file laying in the directory shows how to start the simulator binary, highlighting how to pass options to the simulators (as detailed in Section Configuring SimGrid).

View examples/cpp/comm-pingpong/s4u-comm-pingpong.cpp

Download s4u-comm-pingpong.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_app_pingpong, "Messages specific for this s4u example");

static void pinger(sg4::Mailbox* mailbox_in, sg4::Mailbox* mailbox_out)
{
  XBT_INFO("Ping from mailbox %s to mailbox %s", mailbox_in->get_name().c_str(), mailbox_out->get_name().c_str());

  /* - Do the ping with a 1-Byte payload (latency bound) ... */
  auto* payload = new double(sg4::Engine::get_clock());

  mailbox_out->put(payload, 1);
  /* - ... then wait for the (large) pong */
  auto sender_time = mailbox_in->get_unique<double>();

  double communication_time = sg4::Engine::get_clock() - *sender_time;
  XBT_INFO("Payload received : large communication (bandwidth bound)");
  XBT_INFO("Pong time (bandwidth bound): %.3f", communication_time);
}

static void ponger(sg4::Mailbox* mailbox_in, sg4::Mailbox* mailbox_out)
{
  XBT_INFO("Pong from mailbox %s to mailbox %s", mailbox_in->get_name().c_str(), mailbox_out->get_name().c_str());

  /* - Receive the (small) ping first ....*/
  auto sender_time          = mailbox_in->get_unique<double>();
  double communication_time = sg4::Engine::get_clock() - *sender_time;
  XBT_INFO("Payload received : small communication (latency bound)");
  XBT_INFO("Ping time (latency bound) %f", communication_time);

  /*  - ... Then send a 1GB pong back (bandwidth bound) */
  auto* payload = new double(sg4::Engine::get_clock());
  XBT_INFO("payload = %.3f", *payload);

  mailbox_out->put(payload, 1e9);
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  sg4::Mailbox* mb1 = e.mailbox_by_name_or_create("Mailbox 1");
  sg4::Mailbox* mb2 = e.mailbox_by_name_or_create("Mailbox 2");

  sg4::Actor::create("pinger", e.host_by_name("Tremblay"), pinger, mb1, mb2);
  sg4::Actor::create("ponger", e.host_by_name("Jupiter"), ponger, mb2, mb1);

  e.run();

  XBT_INFO("Total simulation time: %.3f", sg4::Engine::get_clock());

  return 0;
}

Basic asynchronous communications

Illustrates how to have non-blocking communications, that are communications running in the background leaving the process free to do something else during their completion.

See also simgrid::s4u::Mailbox::put_async() and simgrid::s4u::Comm::wait().

View examples/cpp/comm-wait/s4u-comm-wait.cpp

Download s4u-comm-wait.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example shows how to use simgrid::s4u::this_actor::wait() to wait for a given communication.
 *
 * As for the other asynchronous examples, the sender initiate all the messages it wants to send and
 * pack the resulting simgrid::s4u::CommPtr objects in a vector. All messages thus occurs concurrently.
 *
 * The sender then loops until there is no ongoing communication.
 */

#include "simgrid/s4u.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_comm_wait, "Messages specific for this s4u example");

static void sender(int messages_count, size_t payload_size)
{
  double sleep_start_time = 5.0;
  double sleep_test_time  = 0;

  sg4::Mailbox* mbox = sg4::Mailbox::by_name("receiver");

  XBT_INFO("sleep_start_time : %f , sleep_test_time : %f", sleep_start_time, sleep_test_time);
  sg4::this_actor::sleep_for(sleep_start_time);

  for (int i = 0; i < messages_count; i++) {
    std::string msg_content = "Message " + std::to_string(i);
    // Copy the data we send: the 'msg_content' variable is not a stable storage location.
    // It will be destroyed when this actor leaves the loop, ie before the receiver gets the data
    auto* payload = new std::string(msg_content);

    /* Create a communication representing the ongoing communication and then */
    sg4::CommPtr comm = mbox->put_async(payload, payload_size);
    XBT_INFO("Send '%s' to '%s'", msg_content.c_str(), mbox->get_cname());

    if (sleep_test_time > 0) {   /* - "test_time" is set to 0, wait */
      while (not comm->test()) { /* - Call test() every "sleep_test_time" otherwise */
        sg4::this_actor::sleep_for(sleep_test_time);
      }
    } else {
      comm->wait();
    }
  }

  /* Send message to let the receiver know that it should stop */
  XBT_INFO("Send 'finalize' to 'receiver'");
  mbox->put(new std::string("finalize"), 0);
}

/* Receiver actor expects 1 argument: its ID */
static void receiver()
{
  double sleep_start_time = 1.0;
  double sleep_test_time  = 0.1;

  sg4::Mailbox* mbox = sg4::Mailbox::by_name("receiver");

  XBT_INFO("sleep_start_time : %f , sleep_test_time : %f", sleep_start_time, sleep_test_time);
  sg4::this_actor::sleep_for(sleep_start_time);

  XBT_INFO("Wait for my first message");
  for (bool cont = true; cont;) {
    std::string* received;
    sg4::CommPtr comm = mbox->get_async<std::string>(&received);

    if (sleep_test_time > 0) {   /* - "test_time" is set to 0, wait */
      while (not comm->test()) { /* - Call test() every "sleep_test_time" otherwise */
        sg4::this_actor::sleep_for(sleep_test_time);
      }
    } else {
      comm->wait();
    }

    XBT_INFO("I got a '%s'.", received->c_str());
    if (*received == "finalize")
      cont = false; // If it's a finalize message, we're done.
    delete received;
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);

  sg4::Actor::create("sender", e.host_by_name("Tremblay"), sender, 3, 482117300);
  sg4::Actor::create("receiver", e.host_by_name("Ruby"), receiver);

  e.run();

  return 0;
}

Waiting for communications with timeouts

There is two ways of declaring timeouts in SimGrid. waituntil let you specify the deadline until when you want to wait, while waitfor expects the maximal wait duration. This example is very similar to the previous one, simply adding how to declare timeouts when waiting on asynchronous communication.

See also simgrid::s4u::Activity::wait_until() and simgrid::s4u::Comm::wait_for().

View examples/cpp/comm-waituntil/s4u-comm-waituntil.cpp

Download s4u-comm-waituntil.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example shows how to use simgrid::s4u::Activity::wait_until() and
 * simgrid::s4u::Activity::wait_for() on a given communication.
 *
 * It is very similar to the comm-wait example, but the sender initially
 * does some waits that are too short before doing an infinite wait.
 */

#include "simgrid/s4u.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_comm_waituntil, "Messages specific for this s4u example");

static void sender(int messages_count, size_t payload_size)
{
  std::vector<sg4::CommPtr> pending_comms;
  sg4::Mailbox* mbox = sg4::Mailbox::by_name("receiver-0");

  /* Start dispatching all messages to the receiver */
  for (int i = 0; i < messages_count; i++) {
    std::string message = "Message " + std::to_string(i);
    auto* payload       = new std::string(message); // copy the data we send:

    // 'msgName' is not a stable storage location
    XBT_INFO("Send '%s' to '%s'", message.c_str(), mbox->get_cname());
    /* Create a communication representing the ongoing communication */
    sg4::CommPtr comm = mbox->put_async(payload, payload_size);
    /* Add this comm to the vector of all known comms */
    pending_comms.push_back(comm);
  }

  /* Start the finalize signal to the receiver*/
  auto* payload     = new std::string("finalize"); // Make a copy of the data we will send
  sg4::CommPtr final_comm = mbox->put_async(payload, 0);
  pending_comms.push_back(final_comm);
  XBT_INFO("Send 'finalize' to 'receiver-0'");

  XBT_INFO("Done dispatching all messages");

  /* Now that all message exchanges were initiated, wait for their completion, in order of creation. */
  while (not pending_comms.empty()) {
    sg4::CommPtr comm = pending_comms.back();
    comm->wait_for(1);
    pending_comms.pop_back(); // remove it from the list
  }

  XBT_INFO("Goodbye now!");
}

static void receiver()
{
  sg4::Mailbox* mbox = sg4::Mailbox::by_name("receiver-0");

  XBT_INFO("Wait for my first message");
  for (bool cont = true; cont;) {
    auto received = mbox->get_unique<std::string>();
    XBT_INFO("I got a '%s'.", received->c_str());
    if (*received == "finalize")
      cont = false; // If it's a finalize message, we're done.
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);

  sg4::Actor::create("sender", e.host_by_name("Tremblay"), sender, 3, 5e7);
  sg4::Actor::create("receiver", e.host_by_name("Ruby"), receiver);

  e.run();

  return 0;
}

Checking for incoming communications

This example uses Mailbox.ready() to check for completed communications. When this function returns true, then at least a message is arrived, so you know that Mailbox.get() will complete immediately. This is thus another way toward asynchronous communications.

See also simgrid::s4u::Mailbox::ready().

View examples/cpp/comm-ready/s4u-comm-ready.cpp

Download s4u-comm-ready.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example shows how to use simgrid::s4u::Mailbox::ready() to check for completed communications.
 *
 * We have a number of peers which send and receive messages in two phases:
 * -> sending phase:   each one of them sends a number of messages to the others followed
 *                     by a single "finalize" message.
 * -> receiving phase: each one of them receives all the available messages that reached
 *                     their corresponding mailbox until it has all the needed "finalize"
 *                     messages to know that no more work needs to be done.
 *
 * To avoid doing a wait() over the ongoing communications, each peer makes use of the
 * simgrid::s4u::Mailbox::ready() method. If it returns true then a following get() will fetch the
 * message immediately, if not the peer will sleep for a fixed amount of time before checking again.
 *
 */

#include "simgrid/s4u.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_async_ready, "Messages specific for this s4u example");

static void peer(int my_id, int messages_count, size_t payload_size, int peers_count)
{
  /* Set myself as the persistent receiver of my mailbox so that messages start flowing to me as soon as they are put
   * into it */
  sg4::Mailbox* my_mbox = sg4::Mailbox::by_name("peer-" + std::to_string(my_id));
  my_mbox->set_receiver(sg4::Actor::self());

  sg4::ActivitySet pending_comms;

  /* Start dispatching all messages to peers others that myself */
  for (int i = 0; i < messages_count; i++) {
    for (int peer_id = 0; peer_id < peers_count; peer_id++) {
      if (peer_id != my_id) {
        sg4::Mailbox* mbox  = sg4::Mailbox::by_name("peer-" + std::to_string(peer_id));
        std::string message = "Message " + std::to_string(i) + " from peer " + std::to_string(my_id);
        auto* payload       = new std::string(message); // copy the data we send:
        // 'message' is not a stable storage location
        XBT_INFO("Send '%s' to '%s'", message.c_str(), mbox->get_cname());
        /* Create a communication representing the ongoing communication */
        pending_comms.push(mbox->put_async(payload, payload_size));
      }
    }
  }

  /* Start sending messages to let peers know that they should stop */
  for (int peer_id = 0; peer_id < peers_count; peer_id++) {
    if (peer_id != my_id) {
      sg4::Mailbox* mbox = sg4::Mailbox::by_name("peer-" + std::to_string(peer_id));
      auto* payload      = new std::string("finalize"); // Make a copy of the data we will send
      pending_comms.push(mbox->put_async(payload, payload_size));
      XBT_INFO("Send 'finalize' to 'peer-%d'", peer_id);
    }
  }
  XBT_INFO("Done dispatching all messages");

  /* Retrieve all the messages other peers have been sending to me until I receive all the corresponding "Finalize"
   * messages */
  long pending_finalize_messages = peers_count - 1;
  while (pending_finalize_messages > 0) {
    if (my_mbox->ready()) {
      double start        = sg4::Engine::get_clock();
      auto received       = my_mbox->get_unique<std::string>();
      double waiting_time = sg4::Engine::get_clock() - start;
      xbt_assert(
          waiting_time == 0,
          "Expecting the waiting time to be 0 because the communication was supposedly ready, but got %f instead",
          waiting_time);
      XBT_INFO("I got a '%s'.", received->c_str());
      if (*received == "finalize") {
        pending_finalize_messages--;
      }
    } else {
      XBT_INFO("Nothing ready to consume yet, I better sleep for a while");
      sg4::this_actor::sleep_for(.01);
    }
  }

  XBT_INFO("I'm done, just waiting for my peers to receive the messages before exiting");
  pending_comms.wait_all();

  XBT_INFO("Goodbye now!");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  sg4::Actor::create("peer", e.host_by_name("Tremblay"), peer, 0, 2, 5e7, 3);
  sg4::Actor::create("peer", e.host_by_name("Ruby"), peer, 1, 6, 2.5e5, 3);
  sg4::Actor::create("peer", e.host_by_name("Perl"), peer, 2, 0, 5e7, 3);

  e.run();

  return 0;
}

Suspending communications

The suspend() and resume() functions block the progression of a given communication for a while and then unblock it. is_suspended() returns whether that activity is currently blocked or not.

See also simgrid::s4u::Activity::suspend() simgrid::s4u::Activity::resume() and simgrid::s4u::Activity::is_suspended().

View examples/cpp/comm-suspend/s4u-comm-suspend.cpp

Download s4u-comm-suspend.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example shows how to suspend and resume an asynchronous communication. */

#include "simgrid/s4u.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_comm_wait, "Messages specific for this s4u example");

static void sender()
{
  sg4::Mailbox* mbox = sg4::Mailbox::by_name("receiver");

  // Copy the data we send: the 'msg_content' variable is not a stable storage location.
  // It will be destroyed when this actor leaves the loop, ie before the receiver gets the data
  auto* payload = new std::string("Sent message");

  /* Create a communication representing the ongoing communication and then */
  sg4::CommPtr comm = mbox->put_init(payload, 13194230);
  XBT_INFO("Suspend the communication before it starts (remaining: %.0f bytes) and wait a second.",
           comm->get_remaining());
  sg4::this_actor::sleep_for(1);
  XBT_INFO("Now, start the communication (remaining: %.0f bytes) and wait another second.", comm->get_remaining());
  comm->start();
  sg4::this_actor::sleep_for(1);

  XBT_INFO("There is still %.0f bytes to transfer in this communication. Suspend it for one second.",
           comm->get_remaining());
  comm->suspend();
  XBT_INFO("Now there is %.0f bytes to transfer. Resume it and wait for its completion.", comm->get_remaining());
  comm->resume();
  comm->wait();
  XBT_INFO("There is %f bytes to transfer after the communication completion.", comm->get_remaining());
  XBT_INFO("Suspending a completed activity is a no-op.");
  comm->suspend();
}

static void receiver()
{
  sg4::Mailbox* mbox = sg4::Mailbox::by_name("receiver");
  XBT_INFO("Wait for the message.");
  auto received = mbox->get_unique<std::string>();

  XBT_INFO("I got '%s'.", received->c_str());
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);

  sg4::Actor::create("sender", e.host_by_name("Tremblay"), sender);
  sg4::Actor::create("receiver", e.host_by_name("Jupiter"), receiver);

  e.run();

  return 0;
}

Dealing with network failures

This examples shows how to survive to network exceptions that occurs when a link is turned off, or when the actor with whom you communicate fails because its host is turned off. In this case, any blocking operation such as put, get or wait will raise an exception that you can catch and react to. See also Modeling churn (e.g., in P2P), this example on how to attach a state profile to hosts and that example on how to react to host failures.

View examples/cpp/comm-failure/s4u-comm-failure.cpp

Download s4u-comm-failure.cpp

/* Copyright (c) 2021-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example shows how to react to a failed communication, which occurs when a link is turned off,
 * or when the actor with whom you communicate fails because its host is turned off.
 */

#include <simgrid/s4u.hpp>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_comm_failure, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

class Sender {
  std::string mailbox1_name;
  std::string mailbox2_name;

public:
  Sender(const std::string& mailbox1_name, const std::string& mailbox2_name)
      : mailbox1_name(mailbox1_name), mailbox2_name(mailbox2_name)
  {
  }

  void operator()() const
  {
    auto* mailbox1 = sg4::Mailbox::by_name(mailbox1_name);
    auto* mailbox2 = sg4::Mailbox::by_name(mailbox2_name);

    XBT_INFO("Initiating asynchronous send to %s", mailbox1->get_cname());
    auto comm1 = mailbox1->put_async((void*)666, 5);
    XBT_INFO("Initiating asynchronous send to %s", mailbox2->get_cname());
    auto comm2 = mailbox2->put_async((void*)666, 2);

    XBT_INFO("Calling wait_any..");
    sg4::ActivitySet pending_comms;
    pending_comms.push(comm1);
    pending_comms.push(comm2);
    try {
      auto* acti = pending_comms.wait_any().get();
      XBT_INFO("Wait any returned comm to %s", dynamic_cast<sg4::Comm*>(acti)->get_mailbox()->get_cname());
    } catch (const simgrid::NetworkFailureException&) {
      XBT_INFO("Sender has experienced a network failure exception, so it knows that something went wrong");
      XBT_INFO("Now it needs to figure out which of the two comms failed by looking at their state:");
      XBT_INFO("  Comm to %s has state: %s", comm1->get_mailbox()->get_cname(), comm1->get_state_str());
      XBT_INFO("  Comm to %s has state: %s", comm2->get_mailbox()->get_cname(), comm2->get_state_str());
    }

    try {
      comm1->wait();
    } catch (const simgrid::NetworkFailureException& e) {
      XBT_INFO("Waiting on a FAILED comm raises an exception: '%s'", e.what());
    }
    XBT_INFO("Wait for remaining comm, just to be nice");
    pending_comms.wait_all();
  }
};

class Receiver {
  sg4::Mailbox* mailbox;

public:
  explicit Receiver(const std::string& mailbox_name) : mailbox(sg4::Mailbox::by_name(mailbox_name)) {}

  void operator()() const
  {
    XBT_INFO("Receiver posting a receive...");
    try {
      mailbox->get<void*>();
      XBT_INFO("Receiver has received successfully!");
    } catch (const simgrid::NetworkFailureException&) {
      XBT_INFO("Receiver has experience a network failure exception");
    }
  }
};

int main(int argc, char** argv)
{
  sg4::Engine engine(&argc, argv);
  auto* zone  = sg4::create_full_zone("AS0");
  auto* host1 = zone->create_host("Host1", "1f");
  auto* host2 = zone->create_host("Host2", "1f");
  auto* host3 = zone->create_host("Host3", "1f");
  const auto* link2 = zone->create_link("linkto2", "1bps")->seal();
  const auto* link3 = zone->create_link("linkto3", "1bps")->seal();

  zone->add_route(host1, host2, {link2});
  zone->add_route(host1, host3, {link3});
  zone->seal();

  sg4::Actor::create("Sender", host1, Sender("mailbox2", "mailbox3"));
  sg4::Actor::create("Receiver", host2, Receiver("mailbox2"));
  sg4::Actor::create("Receiver", host3, Receiver("mailbox3"));

  sg4::Actor::create("LinkKiller", host1, [](){
    sg4::this_actor::sleep_for(10.0);
    XBT_INFO("Turning off link 'linkto2'");
    sg4::Link::by_name("linkto2")->turn_off();
  });

  engine.run();

  return 0;
}

Direct host-to-host communication

This example demonstrates the direct communication mechanism, that allows to send data from one host to another without relying on the mailbox mechanism.

See also simgrid::s4u::Comm::sendto_init() and simgrid::s4u::Comm::sendto_async().

View examples/cpp/comm-host2host/s4u-comm-host2host.cpp

Download s4u-comm-host2host.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This simple example demonstrates the Comm::sento_init() Comm::sento_async() functions,
   that can be used to create a direct communication from one host to another without
   relying on the mailbox mechanism.

   There is not much to say, actually: The _init variant creates the communication and
   leaves it unstarted (in case you want to modify this communication before it starts),
   while the _async variant creates and start it. In both cases, you need to wait() it.

   It is mostly useful when you want to have a centralized simulation of your settings,
   with a central actor declaring all communications occurring on your distributed system.
  */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_comm_host2host, "Messages specific for this s4u example");

static void sender(sg4::Host* h1, sg4::Host* h2, sg4::Host* h3, sg4::Host* h4)
{
  XBT_INFO("Send c12 with sendto_async(%s -> %s), and c34 with sendto_init(%s -> %s)", h1->get_cname(), h2->get_cname(),
           h3->get_cname(), h4->get_cname());

  auto c12 = sg4::Comm::sendto_async(h1, h2, 1.5e7); // Creates and start a direct communication

  auto c34 = sg4::Comm::sendto_init(h3, h4); // Creates but do not start another direct communication
  c34->set_payload_size(1e7);                // Specify the amount of bytes to exchange in this comm

  // You can also detach() communications that you never plan to test() or wait().
  // Here we create a communication that only slows down the other ones
  auto noise = sg4::Comm::sendto_init(h1, h2);
  noise->set_payload_size(10000);
  noise->detach();

  XBT_INFO("After creation,  c12 is %s (remaining: %.2e bytes); c34 is %s (remaining: %.2e bytes)",
           c12->get_state_str(), c12->get_remaining(), c34->get_state_str(), c34->get_remaining());
  sg4::this_actor::sleep_for(1);
  XBT_INFO("One sec later,   c12 is %s (remaining: %.2e bytes); c34 is %s (remaining: %.2e bytes)",
           c12->get_state_str(), c12->get_remaining(), c34->get_state_str(), c34->get_remaining());
  c34->start();
  XBT_INFO("After c34->start,c12 is %s (remaining: %.2e bytes); c34 is %s (remaining: %.2e bytes)",
           c12->get_state_str(), c12->get_remaining(), c34->get_state_str(), c34->get_remaining());
  c12->wait();
  XBT_INFO("After c12->wait, c12 is %s (remaining: %.2e bytes); c34 is %s (remaining: %.2e bytes)",
           c12->get_state_str(), c12->get_remaining(), c34->get_state_str(), c34->get_remaining());
  c34->wait();
  XBT_INFO("After c34->wait, c12 is %s (remaining: %.2e bytes); c34 is %s (remaining: %.2e bytes)",
           c12->get_state_str(), c12->get_remaining(), c34->get_state_str(), c34->get_remaining());

  /* As usual, you don't have to explicitly start communications that were just init()ed.
     The wait() will start it automatically. */
  auto c14 = sg4::Comm::sendto_init(h1, h4);
  c14->set_payload_size(100)->wait(); // Chaining 2 operations on this new communication
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  sg4::Actor::create("sender", e.host_by_name("Boivin"), sender, e.host_by_name("Tremblay"), e.host_by_name("Jupiter"),
                     e.host_by_name("Fafard"), e.host_by_name("Ginette"));

  e.run();

  XBT_INFO("Total simulation time: %.3f", sg4::Engine::get_clock());

  return 0;
}

Executions on the CPU

Basic execution

The computations done in your program are not reported to the simulated world unless you explicitly request the simulator to pause the actor until a given amount of flops gets computed on its simulated host. Some executions can be given a higher priority so that they get more resources.

See also void simgrid::s4u::this_actor::execute(double) and void simgrid::s4u::this_actor::execute(double, double).

View examples/cpp/exec-basic/s4u-exec-basic.cpp

Download s4u-exec-basic.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void executor()
{
  /* this_actor::execute() tells SimGrid to pause the calling actor
   * until its host has computed the amount of flops passed as a parameter */
  sg4::this_actor::execute(98095);
  XBT_INFO("Done.");

  /* This simple example does not do anything beyond that */
}

static void privileged()
{
  /* This version of this_actor::execute() specifies that this execution gets a larger share of the resource.
   *
   * Since the priority is 2, it computes twice as fast as a regular one.
   *
   * So instead of a half/half sharing between the two executions, we get a 1/3 vs 2/3 sharing. */
  sg4::this_actor::execute(98095, 2);
  XBT_INFO("Done.");

  /* Note that the timings printed when running this example are a bit misleading, because the uneven sharing only  last
   * until the privileged actor ends. After this point, the unprivileged one gets 100% of the CPU and finishes quite
   * quickly. */
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 1, "Usage: %s platform_file\n\tExample: %s platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  sg4::Actor::create("executor", e.host_by_name("Tremblay"), executor);
  sg4::Actor::create("privileged", e.host_by_name("Tremblay"), privileged);

  e.run();

  return 0;
}

Asynchronous execution

You can start asynchronous executions, just like you would fire background threads.

See also simgrid::s4u::this_actor::exec_init(), simgrid::s4u::Activity::start(), simgrid::s4u::Activity::wait(), simgrid::s4u::Activity::get_remaining(), simgrid::s4u::Exec::get_remaining_ratio(), simgrid::s4u::this_actor::exec_async() and simgrid::s4u::Activity::cancel().

View examples/cpp/exec-async/s4u-exec-async.cpp

Download s4u-exec-async.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

/* This actor simply waits for its activity completion after starting it.
 * That's exactly equivalent to synchronous execution. */
static void waiter()
{
  double computation_amount = sg4::this_actor::get_host()->get_speed();
  XBT_INFO("Execute %g flops, should take 1 second.", computation_amount);
  sg4::ExecPtr activity = sg4::this_actor::exec_init(computation_amount);
  activity->start();
  activity->wait();

  XBT_INFO("Goodbye now!");
}

/* This actor tests the ongoing execution until its completion, and don't wait before it's terminated. */
static void monitor()
{
  double computation_amount = sg4::this_actor::get_host()->get_speed();
  XBT_INFO("Execute %g flops, should take 1 second.", computation_amount);
  sg4::ExecPtr activity = sg4::this_actor::exec_init(computation_amount);
  activity->start();

  while (not activity->test()) {
    XBT_INFO("Remaining amount of flops: %g (%.0f%%)", activity->get_remaining(),
             100 * activity->get_remaining_ratio());
    sg4::this_actor::sleep_for(0.3);
  }

  XBT_INFO("Goodbye now!");
}

/* This actor cancels the ongoing execution after a while. */
static void canceller()
{
  double computation_amount = sg4::this_actor::get_host()->get_speed();

  XBT_INFO("Execute %g flops, should take 1 second.", computation_amount);
  sg4::ExecPtr activity = sg4::this_actor::exec_async(computation_amount);
  sg4::this_actor::sleep_for(0.5);
  XBT_INFO("I changed my mind, cancel!");
  activity->cancel();

  XBT_INFO("Goodbye now!");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  sg4::Host* fafard  = e.host_by_name("Fafard");
  sg4::Host* ginette = e.host_by_name("Ginette");
  sg4::Host* boivin  = e.host_by_name("Boivin");

  sg4::Actor::create("wait", fafard, waiter);
  sg4::Actor::create("monitor", ginette, monitor);
  sg4::Actor::create("cancel", boivin, canceller);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Remote execution

You can start executions on remote hosts, or even change the host on which they occur during their execution. This is naturally not very realistic, but it’s something handy to have.

See also simgrid::s4u::Exec::set_host().

View examples/cpp/exec-remote/s4u-exec-remote.cpp

Download s4u-exec-remote.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void wizard()
{
  const sg4::Host* fafard = sg4::Host::by_name("Fafard");
  sg4::Host* ginette      = sg4::Host::by_name("Ginette");
  sg4::Host* boivin       = sg4::Host::by_name("Boivin");

  XBT_INFO("I'm a wizard! I can run an activity on the Ginette host from the Fafard one! Look!");
  sg4::ExecPtr exec = sg4::this_actor::exec_init(48.492e6);
  exec->set_host(ginette);
  exec->start();
  XBT_INFO("It started. Running 48.492Mf takes exactly one second on Ginette (but not on Fafard).");

  sg4::this_actor::sleep_for(0.1);
  XBT_INFO("Loads in flops/s: Boivin=%.0f; Fafard=%.0f; Ginette=%.0f", boivin->get_load(), fafard->get_load(),
           ginette->get_load());

  exec->wait();

  XBT_INFO("Done!");
  XBT_INFO("And now, harder. Start a remote activity on Ginette and move it to Boivin after 0.5 sec");
  exec = sg4::this_actor::exec_init(73293500)->set_host(ginette);
  exec->start();

  sg4::this_actor::sleep_for(0.5);
  XBT_INFO("Loads before the move: Boivin=%.0f; Fafard=%.0f; Ginette=%.0f", boivin->get_load(), fafard->get_load(),
           ginette->get_load());

  exec->set_host(boivin);

  sg4::this_actor::sleep_for(0.1);
  XBT_INFO("Loads after the move: Boivin=%.0f; Fafard=%.0f; Ginette=%.0f", boivin->get_load(), fafard->get_load(),
           ginette->get_load());

  exec->wait();
  XBT_INFO("Done!");

  XBT_INFO("And now, even harder. Start a remote activity on Ginette and turn off the host after 0.5 sec");
  exec = sg4::this_actor::exec_init(48.492e6)->set_host(ginette);
  exec->start();

  sg4::this_actor::sleep_for(0.5);
  ginette->turn_off();
  try {
    exec->wait();
  } catch (const simgrid::HostFailureException&) {
    XBT_INFO("Execution failed on %s", ginette->get_cname());
  }
  XBT_INFO("Done!");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);
  sg4::Actor::create("test", e.host_by_name("Fafard"), wizard);

  e.run();

  return 0;
}

Parallel executions

These objects are convenient abstractions of parallel computational kernels that span over several machines, such as a PDGEM and the other ScaLAPACK routines. Note that this only works with the “ptask_L07” host model (--cfg=host/model:ptask_L07).

This example demonstrates several kinds of parallel tasks: regular ones, communication-only (without computation), computation-only (without communication), synchronization-only (neither communication nor computation). It also shows how to reconfigure a task after its start, to change the number of hosts it runs onto. This allows simulating malleable tasks.

See also simgrid::s4u::this_actor::parallel_execute().

View examples/cpp/exec-ptask/s4u-exec-ptask.cpp

Download s4u-exec-ptask.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* Parallel activities are convenient abstractions of parallel computational kernels that span over several machines.
 * To create a new one, you have to provide several things:
 *   - a vector of hosts on which the activity will execute
 *   - a vector of values, the amount of computation for each of the hosts (in flops)
 *   - a matrix of values, the amount of communication between each pair of hosts (in bytes)
 *
 * Each of these operation will be processed at the same relative speed.
 * This means that at some point in time, all sub-executions and all sub-communications will be at 20% of completion.
 * Also, they will all complete at the exact same time.
 *
 * This is obviously a simplistic abstraction, but this is very handful in a large amount of situations.
 *
 * Please note that you must have the LV07 platform model enabled to use such constructs.
 */

#include <simgrid/s4u.hpp>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_ptask, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void runner()
{
  /* Retrieve the list of all hosts as an array of hosts */
  auto hosts         = sg4::Engine::get_instance()->get_all_hosts();
  size_t hosts_count = hosts.size();

  std::vector<double> computation_amounts;
  std::vector<double> communication_amounts;

  /* ------[ test 1 ]----------------- */
  XBT_INFO("First, build a classical parallel activity, with 1 Gflop to execute on each node, "
           "and 10MB to exchange between each pair");

  computation_amounts.assign(hosts_count, 1e9 /*1Gflop*/);
  communication_amounts.assign(hosts_count * hosts_count, 0);
  for (size_t i = 0; i < hosts_count; i++)
    for (size_t j = i + 1; j < hosts_count; j++)
      communication_amounts[i * hosts_count + j] = 1e7; // 10 MB

  sg4::this_actor::parallel_execute(hosts, computation_amounts, communication_amounts);

  /* ------[ test 2 ]----------------- */
  XBT_INFO("We can do the same with a timeout of 10 seconds enabled.");
  computation_amounts.assign(hosts_count, 1e9 /*1Gflop*/);
  communication_amounts.assign(hosts_count * hosts_count, 0);
  for (size_t i = 0; i < hosts_count; i++)
    for (size_t j = i + 1; j < hosts_count; j++)
      communication_amounts[i * hosts_count + j] = 1e7; // 10 MB

  sg4::ExecPtr activity = sg4::this_actor::exec_init(hosts, computation_amounts, communication_amounts);
  try {
    activity->wait_for(10.0 /* timeout (in seconds)*/);
    xbt_die("Woops, this did not timeout as expected... Please report that bug.");
  } catch (const simgrid::TimeoutException&) {
    XBT_INFO("Caught the expected timeout exception.");
    activity->cancel();
  }

  /* ------[ test 3 ]----------------- */
  XBT_INFO("Then, build a parallel activity involving only computations (of different amounts) and no communication");
  computation_amounts = {3e8, 6e8, 1e9}; // 300Mflop, 600Mflop, 1Gflop
  communication_amounts.clear();         // no comm
  sg4::this_actor::parallel_execute(hosts, computation_amounts, communication_amounts);

  /* ------[ test 4 ]----------------- */
  XBT_INFO("Then, build a parallel activity with no computation nor communication (synchro only)");
  computation_amounts.clear();
  communication_amounts.clear();
  sg4::this_actor::parallel_execute(hosts, computation_amounts, communication_amounts);

  /* ------[ test 5 ]----------------- */
  XBT_INFO("Then, Monitor the execution of a parallel activity");
  computation_amounts.assign(hosts_count, 1e6 /*1Mflop*/);
  communication_amounts = {0, 1e6, 0, 0, 0, 1e6, 1e6, 0, 0};
  activity              = sg4::this_actor::exec_init(hosts, computation_amounts, communication_amounts);
  activity->start();

  while (not activity->test()) {
    XBT_INFO("Remaining flop ratio: %.0f%%", 100 * activity->get_remaining_ratio());
    sg4::this_actor::sleep_for(5);
  }
  activity->wait();

  /* ------[ test 6 ]----------------- */
  XBT_INFO("Finally, simulate a malleable task (a parallel execution that gets reconfigured after its start).");
  XBT_INFO("  - Start a regular parallel execution, with both comm and computation");
  computation_amounts.assign(hosts_count, 1e6 /*1Mflop*/);
  communication_amounts = {0, 1e6, 0, 0, 1e6, 0, 1e6, 0, 0};
  activity              = sg4::this_actor::exec_init(hosts, computation_amounts, communication_amounts);
  activity->start();

  sg4::this_actor::sleep_for(10);
  double remaining_ratio = activity->get_remaining_ratio();
  XBT_INFO("  - After 10 seconds, %.2f%% remains to be done. Change it from 3 hosts to 2 hosts only.",
           remaining_ratio * 100);
  XBT_INFO("    Let's first suspend the task.");
  activity->suspend();

  XBT_INFO("  - Now, simulate the reconfiguration (modeled as a comm from the removed host to the remaining ones).");
  std::vector<double> rescheduling_comp{0, 0, 0};
  std::vector<double> rescheduling_comm{0, 0, 0, 0, 0, 0, 25000, 25000, 0};
  sg4::this_actor::parallel_execute(hosts, rescheduling_comp, rescheduling_comm);

  XBT_INFO("  - Now, let's cancel the old task and create a new task with modified comm and computation vectors:");
  XBT_INFO("    What was already done is removed, and the load of the removed host is shared between remaining ones.");
  for (int i = 0; i < 2; i++) {
    // remove what we've done so far, for both comm and compute load
    computation_amounts[i]   *= remaining_ratio;
    communication_amounts[i] *= remaining_ratio;
    // The work from 1 must be shared between 2 remaining ones. 1/2=50% of extra work for each
    computation_amounts[i]   *= 1.5;
    communication_amounts[i] *= 1.5;
  }
  hosts.resize(2);
  computation_amounts.resize(2);
  double remaining_comm = communication_amounts[1];
  communication_amounts = {0, remaining_comm, remaining_comm, 0}; // Resizing a linearized matrix is hairly

  activity->cancel();
  activity = sg4::this_actor::exec_init(hosts, computation_amounts, communication_amounts);

  XBT_INFO("  - Done, let's wait for the task completion");
  activity->wait();

  XBT_INFO("Goodbye now!");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  xbt_assert(argc == 2, "Usage: %s <platform file>", argv[0]);

  e.load_platform(argv[1]);
  sg4::Actor::create("test", e.host_by_name("MyHost1"), runner);

  e.run();
  XBT_INFO("Simulation done.");
  return 0;
}

Ptasks play well with the host energy plugin, as shown in this example. There is not much new compared to the above ptask example or the examples about energy. It just works.

View examples/cpp/energy-exec-ptask/s4u-energy-exec-ptask.cpp

Download s4u-energy-exec-ptask.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include "simgrid/plugins/energy.h"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void runner()
{
  sg4::Host* host1 = sg4::Host::by_name("MyHost1");
  sg4::Host* host2 = sg4::Host::by_name("MyHost2");
  std::vector<sg4::Host*> hosts{host1, host2};

  double old_energy_host1 = sg_host_get_consumed_energy(host1);
  double old_energy_host2 = sg_host_get_consumed_energy(host2);

  XBT_INFO("[%s] Energetic profile: %s", host1->get_cname(), host1->get_property("wattage_per_state"));
  XBT_INFO("[%s] Initial peak speed=%.0E flop/s; Total energy dissipated =%.0E J", host1->get_cname(), host1->get_speed(),
           old_energy_host1);
  XBT_INFO("[%s] Energetic profile: %s", host2->get_cname(), host2->get_property("wattage_per_state"));
  XBT_INFO("[%s] Initial peak speed=%.0E flop/s; Total energy dissipated =%.0E J", host2->get_cname(), host2->get_speed(),
           old_energy_host2);

  double start = sg4::Engine::get_clock();
  XBT_INFO("Sleep for 10 seconds");
  sg4::this_actor::sleep_for(10);

  double new_energy_host1 = sg_host_get_consumed_energy(host1);
  double new_energy_host2 = sg_host_get_consumed_energy(host2);
  XBT_INFO("Done sleeping (duration: %.2f s).\n"
           "[%s] Current peak speed=%.0E; Energy dissipated during this step=%.2f J; Total energy dissipated=%.2f J\n"
           "[%s] Current peak speed=%.0E; Energy dissipated during this step=%.2f J; Total energy dissipated=%.2f J\n",
           sg4::Engine::get_clock() - start, host1->get_cname(), host1->get_speed(),
           (new_energy_host1 - old_energy_host1), sg_host_get_consumed_energy(host1), host2->get_cname(),
           host2->get_speed(), (new_energy_host2 - old_energy_host2), sg_host_get_consumed_energy(host2));

  old_energy_host1 = new_energy_host1;
  old_energy_host2 = new_energy_host2;


  // ========= Execute something =========
  start             = sg4::Engine::get_clock();
  double flopAmount = 1E9;
  std::vector<double> cpu_amounts{flopAmount, flopAmount};
  std::vector<double> com_amounts{0, 0, 0, 0};
  XBT_INFO("Run a task of %.0E flops on two hosts", flopAmount);
  sg4::this_actor::parallel_execute(hosts, cpu_amounts, com_amounts);

  new_energy_host1 = sg_host_get_consumed_energy(host1);
  new_energy_host2 = sg_host_get_consumed_energy(host2);
  XBT_INFO(
      "Task done (duration: %.2f s).\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f J\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f "
      "J\n",
      sg4::Engine::get_clock() - start, host1->get_cname(), host1->get_speed(), (new_energy_host1 - old_energy_host1),
      sg_host_get_consumed_energy(host1), host2->get_cname(), host2->get_speed(), (new_energy_host2 - old_energy_host2),
      sg_host_get_consumed_energy(host2));

  old_energy_host1 = new_energy_host1;
  old_energy_host2 = new_energy_host2;


  // ========= Change power peak =========
  int pstate = 2;
  host1->set_pstate(pstate);
  host2->set_pstate(pstate);
  XBT_INFO("========= Requesting pstate %d for both hosts (speed should be of %.0E flop/s and is of %.0E flop/s)", pstate,
           host1->get_pstate_speed(pstate), host1->get_speed());


  // ========= Run another ptask =========
  start = sg4::Engine::get_clock();
  std::vector<double> cpu_amounts2{flopAmount, flopAmount};
  std::vector<double> com_amounts2{0, 0, 0, 0};
  XBT_INFO("Run a task of %.0E flops on %s and %.0E flops on %s.", flopAmount, host1->get_cname(), flopAmount, host2->get_cname());
  sg4::this_actor::parallel_execute(hosts, cpu_amounts2, com_amounts2);

  new_energy_host1 = sg_host_get_consumed_energy(host1);
  new_energy_host2 = sg_host_get_consumed_energy(host2);
  XBT_INFO(
      "Task done (duration: %.2f s).\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f J\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f "
      "J\n",
      sg4::Engine::get_clock() - start, host1->get_cname(), host1->get_speed(), (new_energy_host1 - old_energy_host1),
      sg_host_get_consumed_energy(host1), host2->get_cname(), host2->get_speed(), (new_energy_host2 - old_energy_host2),
      sg_host_get_consumed_energy(host2));

  old_energy_host1 = new_energy_host1;
  old_energy_host2 = new_energy_host2;


  // ========= A new ptask with computation and communication =========
  start            = sg4::Engine::get_clock();
  double comAmount = 1E7;
  std::vector<double> cpu_amounts3{flopAmount, flopAmount};
  std::vector<double> com_amounts3{0, comAmount, comAmount, 0};
  XBT_INFO("Run a task with computation and communication on two hosts.");
  sg4::this_actor::parallel_execute(hosts, cpu_amounts3, com_amounts3);

  new_energy_host1 = sg_host_get_consumed_energy(host1);
  new_energy_host2 = sg_host_get_consumed_energy(host2);
  XBT_INFO(
      "Task done (duration: %.2f s).\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f J\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f "
      "J\n",
      sg4::Engine::get_clock() - start, host1->get_cname(), host1->get_speed(), (new_energy_host1 - old_energy_host1),
      sg_host_get_consumed_energy(host1), host2->get_cname(), host2->get_speed(), (new_energy_host2 - old_energy_host2),
      sg_host_get_consumed_energy(host2));

  old_energy_host1 = new_energy_host1;
  old_energy_host2 = new_energy_host2;


  // ========= A new ptask with communication only =========
  start = sg4::Engine::get_clock();
  std::vector<double> cpu_amounts4{0, 0};
  std::vector<double> com_amounts4{0, comAmount, comAmount, 0};
  XBT_INFO("Run a task with only communication on two hosts.");
  sg4::this_actor::parallel_execute(hosts, cpu_amounts4, com_amounts4);

  new_energy_host1 = sg_host_get_consumed_energy(host1);
  new_energy_host2 = sg_host_get_consumed_energy(host2);
  XBT_INFO(
      "Task done (duration: %.2f s).\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f J\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f "
      "J\n",
      sg4::Engine::get_clock() - start, host1->get_cname(), host1->get_speed(), (new_energy_host1 - old_energy_host1),
      sg_host_get_consumed_energy(host1), host2->get_cname(), host2->get_speed(), (new_energy_host2 - old_energy_host2),
      sg_host_get_consumed_energy(host2));

  old_energy_host1 = new_energy_host1;
  old_energy_host2 = new_energy_host2;

  // ========= A new ptask with computation and a timeout =========
  start = sg4::Engine::get_clock();
  XBT_INFO("Run a task with computation on two hosts and a timeout of 20s.");
  try {
    std::vector<double> cpu_amounts5{flopAmount, flopAmount};
    std::vector<double> com_amounts5{0, 0, 0, 0};
    sg4::this_actor::exec_init(hosts, cpu_amounts5, com_amounts5)->wait_for(20);
  } catch (const simgrid::TimeoutException &){
    XBT_INFO("Finished WITH timeout");
  }

  new_energy_host1 = sg_host_get_consumed_energy(host1);
  new_energy_host2 = sg_host_get_consumed_energy(host2);
  XBT_INFO(
      "Task ended (duration: %.2f s).\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f J\n"
      "[%s] Current peak speed=%.0E flop/s; Energy dissipated during this step=%.2f J; Total energy dissipated=%.0f "
      "J\n",
      sg4::Engine::get_clock() - start, host1->get_cname(), host1->get_speed(), (new_energy_host1 - old_energy_host1),
      sg_host_get_consumed_energy(host1), host2->get_cname(), host2->get_speed(), (new_energy_host2 - old_energy_host2),
      sg_host_get_consumed_energy(host2));

  XBT_INFO("Now is time to quit!");
}

int main(int argc, char* argv[])
{
  sg_host_energy_plugin_init();
  sg4::Engine e(&argc, argv);
  sg4::Engine::set_config("host/model:ptask_L07");

  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/energy_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);
  sg4::Actor::create("energy_ptask_test", e.host_by_name("MyHost1"), runner);

  e.run();
  XBT_INFO("End of simulation.");
  return 0;
}

Dealing with host failures

This examples shows how to survive to host failure exceptions that occur when an host is turned off. The actors do not get notified when the host on which they run is turned off: they are just terminated in this case, and their on_exit() callback gets executed. For remote executions on failing hosts however, any blocking operation such as exec or wait will raise an exception that you can catch and react to. See also Modeling churn (e.g., in P2P), this example on how to attach a state profile to hosts, and that example on how to react to network failures.

View examples/cpp/exec-failure/s4u-exec-failure.cpp

Download s4u-exec-failure.cpp

/* Copyright (c) 2021-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This examples shows how to survive to host failure exceptions that occur when an host is turned off.
 *
 * The actors do not get notified when the host on which they run is turned off: they are just terminated
 * in this case, and their ``on_exit()`` callback gets executed.
 *
 * For remote executions on failing hosts however, any blocking operation such as ``exec`` or ``wait`` will
 * raise an exception that you can catch and react to, as illustrated in this example.
 */

#include <simgrid/s4u.hpp>
#include "simgrid/kernel/ProfileBuilder.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_exec_failure, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void dispatcher(std::vector<sg4::Host*> const& hosts)
{
  std::vector<sg4::ExecPtr> pending_execs;
  for (auto* host: hosts) {
    XBT_INFO("Initiating asynchronous exec on %s", host->get_cname());
    // Computing 20 flops on an host which speed is 1f takes 20 seconds (when it does not fail)
    auto exec = sg4::this_actor::exec_init(20)->set_host(host);
    pending_execs.push_back(exec);
    exec->start();
  }

  XBT_INFO("---------------------------------");
  XBT_INFO("Wait on the first exec, which host is turned off at t=10 by the another actor.");
  try {
    pending_execs[0]->wait();
    xbt_assert("This wait was not supposed to succeed.");
  } catch (const simgrid::HostFailureException&) {
    XBT_INFO("Dispatcher has experienced a host failure exception, so it knows that something went wrong.");
  }

  XBT_INFO("State of each exec:");
  for (auto const& exec : pending_execs) 
    XBT_INFO("  Exec on %s has state: %s", exec->get_host()->get_cname(), exec->get_state_str());

  XBT_INFO("---------------------------------");
  XBT_INFO("Wait on the second exec, which host is turned off at t=12 by the state profile.");
  try {
    pending_execs[1]->wait();
    xbt_assert("This wait was not supposed to succeed.");
  } catch (const simgrid::HostFailureException&) {
    XBT_INFO("Dispatcher has experienced a host failure exception, so it knows that something went wrong.");
  }
  XBT_INFO("State of each exec:");
  for (auto const& exec : pending_execs) 
    XBT_INFO("  Exec on %s has state: %s", exec->get_host()->get_cname(), exec->get_state_str());

  XBT_INFO("---------------------------------");
  XBT_INFO("Wait on the third exec, which should succeed.");
  try {
    pending_execs[2]->wait();
    XBT_INFO("No exception occured.");
  } catch (const simgrid::HostFailureException&) {
    xbt_assert("This wait was not supposed to fail.");
  }
  XBT_INFO("State of each exec:");
  for (auto const& exec : pending_execs) 
    XBT_INFO("  Exec on %s has state: %s", exec->get_host()->get_cname(), exec->get_state_str());
}

static void host_killer(sg4::Host* to_kill)
{
  sg4::this_actor::sleep_for(10.0);
  XBT_INFO("HostKiller turns off the host '%s'.", to_kill->get_cname());
  to_kill->turn_off();
}

int main(int argc, char** argv)
{
  sg4::Engine engine(&argc, argv);

  auto* zone  = sg4::create_full_zone("world");
  std::vector<sg4::Host*> hosts;
  for (const auto* name : {"Host1", "Host2", "Host3"}) {
    auto* host = zone->create_host(name, "1f");
    hosts.push_back(host);
  }
  /* Attaching a state profile (ie a list of events changing the on/off state of the resource) to host3.
   * The syntax of the profile (second parameter) is a list of: "date state\n"
   *   The R"(   )" thing is the C++ way of writing multiline strings, including literals \n.
   *     You'd have the same behavior by using "12 0\n20 1\n" instead.
   *   So here, the link is turned off at t=12 and back on at t=20.
   * The last parameter is the period of that profile, meaning that it loops after 30 seconds.
   */
  hosts[1]->set_state_profile(simgrid::kernel::profile::ProfileBuilder::from_string("profile name", R"(
12 0
20 1
)",                                                                               30));

  zone->seal();

  sg4::Actor::create("Dispatcher", hosts[2], dispatcher, hosts);
  sg4::Actor::create("HostKiller", hosts[2], host_killer, hosts[0]);

  engine.run();

  return 0;
}

DVFS and pstates

This example shows how to define a set of pstates in the XML. The current pstate of a host can then be accessed and changed from the program.

See also simgrid::s4u::Host::get_pstate_speed() and simgrid::s4u::Host::set_pstate().

View examples/cpp/exec-dvfs/s4u-exec-dvfs.cpp

Download s4u-exec-dvfs.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(test, "Pstate properties test");
namespace sg4 = simgrid::s4u;

static int dvfs()
{
  double workload = 100E6;
  sg4::Host* host = sg4::this_actor::get_host();

  unsigned long nb = host->get_pstate_count();
  XBT_INFO("Count of Processor states=%lu", nb);

  XBT_INFO("Current power peak=%f", host->get_speed());

  // Run a Computation
  sg4::this_actor::execute(workload);

  double exec_time = sg4::Engine::get_clock();
  XBT_INFO("Computation1 duration: %.2f", exec_time);

  // Change power peak
  unsigned long new_pstate = 2;

  XBT_INFO("Changing power peak value to %f (at index %lu)", host->get_pstate_speed(new_pstate), new_pstate);

  host->set_pstate(new_pstate);

  XBT_INFO("Current power peak=%f", host->get_speed());

  // Run a second Computation
  sg4::this_actor::execute(workload);

  exec_time = sg4::Engine::get_clock() - exec_time;
  XBT_INFO("Computation2 duration: %.2f", exec_time);

  // Verify that the default pstate is set to 0
  host = sg4::Host::by_name_or_null("MyHost2");
  XBT_INFO("Count of Processor states=%lu", host->get_pstate_count());

  XBT_INFO("Current power peak=%f", host->get_speed());
  return 0;
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/energy_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  sg4::Actor::create("dvfs_test", e.host_by_name("MyHost1"), dvfs);
  sg4::Actor::create("dvfs_test", e.host_by_name("MyHost2"), dvfs);

  e.run();

  XBT_INFO("Total simulation time: %e", sg4::Engine::get_clock());

  return 0;
}

I/O on Disks and Files

SimGrid provides two levels of abstraction to interact with the simulated disks. At the simplest level, you simply create read and write actions on the disk resources.

Access to raw disk devices

This example illustrates how to simply read and write data on a simulated disk resource.

View examples/cpp/io-disk-raw/s4u-io-disk-raw.cpp

Download s4u-io-disk-raw.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <string>
#include <unordered_map>

XBT_LOG_NEW_DEFAULT_CATEGORY(disk_test, "Messages specific for this simulation");
namespace sg4 = simgrid::s4u;

static void host()
{
  /* -Add an extra disk in a programmatic way */
  sg4::Host::current()->create_disk("Disk3", /*read bandwidth*/ 9.6e7, /*write bandwidth*/ 6.4e7)->seal();

  /* - Display information on the disks mounted by the current host */
  XBT_INFO("*** Storage info on %s ***", sg4::Host::current()->get_cname());

  /* - Retrieve all disks from current host */
  std::vector<sg4::Disk*> const& disk_list = sg4::Host::current()->get_disks();

  /* - For each disk mounted on host, display disk name and mount point */
  for (auto const& disk : disk_list)
    XBT_INFO("Disk name: %s (read: %.0f B/s -- write: %.0f B/s", disk->get_cname(), disk->get_read_bandwidth(),
             disk->get_write_bandwidth());

  /* - Write 400,000 bytes on Disk1 */
  sg4::Disk* disk          = disk_list.front();
  sg_size_t write          = disk->write(400000);
  XBT_INFO("Wrote %llu bytes on '%s'", write, disk->get_cname());

  /*  - Now read 200,000 bytes */
  sg_size_t read = disk->read(200000);
  XBT_INFO("Read %llu bytes on '%s'", read, disk->get_cname());

  /* - Write 800,000 bytes on Disk3 */
  const sg4::Disk* disk3          = disk_list.back();
  sg_size_t write_on_disk3        = disk3->write(800000);
  XBT_INFO("Wrote %llu bytes on '%s'", write_on_disk3, disk3->get_cname());

  /* - Attach some user data to disk1 */
  XBT_INFO("*** Get/set data for storage element: Disk1 ***");

  auto data = disk->get_unique_data<std::string>();

  XBT_INFO("Get storage data: '%s'", data ? data->c_str() : "No user data");

  disk->set_data(new std::string("Some user data"));
  data = disk->get_unique_data<std::string>();
  XBT_INFO("Set and get data: '%s'", data->c_str());
}

int main(int argc, char** argv)
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  /* - Display Host properties */
  for (auto const* h : e.get_all_hosts()) {
    XBT_INFO("*** %s properties ****", h->get_cname());
    for (auto const& [key, value] : *h->get_properties())
      XBT_INFO("  %s -> %s", key.c_str(), value.c_str());
  }

  sg4::Actor::create("", e.host_by_name("bob"), host);

  e.run();
  XBT_INFO("Simulated time: %g", sg4::Engine::get_clock());

  return 0;
}

Asynchronous raw accesses

As most other activities, raw IO accesses can be used asynchronously, as illustrated in this example.

View examples/cpp/io-async/s4u-io-async.cpp

Download s4u-io-async.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void test(sg_size_t size)
{
  const sg4::Disk* disk = sg4::Host::current()->get_disks().front();
  XBT_INFO("Hello! read %llu bytes from %s", size, disk->get_cname());

  sg4::IoPtr activity = disk->io_init(size, sg4::Io::OpType::READ);
  activity->start();
  activity->wait();

  XBT_INFO("Goodbye now!");
}

static void test_waitfor(sg_size_t size)
{
  const sg4::Disk* disk = sg4::Host::current()->get_disks().front();
  XBT_INFO("Hello! write %llu bytes from %s", size, disk->get_cname());

  sg4::IoPtr activity = disk->write_async(size);
  try {
    activity->wait_for(0.5);
  } catch (const simgrid::TimeoutException&) {
    XBT_INFO("Asynchronous write: Timeout!");
  }

  XBT_INFO("Goodbye now!");
}

static void test_cancel(sg_size_t size)
{
  const sg4::Disk* disk = sg4::Host::current()->get_disks().front();
  sg4::this_actor::sleep_for(0.5);
  XBT_INFO("Hello! write %llu bytes from %s", size, disk->get_cname());

  sg4::IoPtr activity = disk->write_async(size);
  sg4::this_actor::sleep_for(0.5);
  XBT_INFO("I changed my mind, cancel!");
  activity->cancel();

  XBT_INFO("Goodbye now!");
}

static void test_monitor(sg_size_t size)
{
  const sg4::Disk* disk = sg4::Host::current()->get_disks().front();
  sg4::this_actor::sleep_for(1);
  sg4::IoPtr activity = disk->write_async(size);

  while (not activity->test()) {
    XBT_INFO("Remaining amount of bytes to write: %g", activity->get_remaining());
    sg4::this_actor::sleep_for(0.2);
  }
  activity->wait();

  XBT_INFO("Goodbye now!");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);
  sg4::Actor::create("test", e.host_by_name("bob"), test, 2e7);
  sg4::Actor::create("test_waitfor", e.host_by_name("alice"), test_waitfor, 5e7);
  sg4::Actor::create("test_cancel", e.host_by_name("alice"), test_cancel, 5e7);
  sg4::Actor::create("test_monitor", e.host_by_name("alice"), test_monitor, 5e7);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Filesystem plugin

The FileSystem plugin provides a more detailed view, with the classical operations over files: open, move, unlink, and of course, read and write. The file and disk sizes are also dealt with and can result in short reads and short writes, as in reality.

  • File Management: This example illustrates the use of operations on files (read, write, seek, tell, unlink, etc).

    View examples/cpp/io-file-system/s4u-io-file-system.cpp

    Download s4u-io-file-system.cpp

    /* Copyright (c) 2006-2024. The SimGrid Team. All rights reserved.          */
    
    /* This program is free software; you can redistribute it and/or modify it
     * under the terms of the license (GNU LGPL) which comes with this package. */
    
    #include <string>
    #include <vector>
    
    #include "simgrid/plugins/file_system.h"
    #include "simgrid/s4u.hpp"
    
    XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "a sample log category");
    namespace sg4 = simgrid::s4u;
    
    class MyHost {
    public:
      void show_info(std::vector<sg4::Disk*> const& disks) const
      {
        XBT_INFO("Storage info on %s:", sg4::Host::current()->get_cname());
    
        for (auto const& d : disks) {
          // Retrieve disk's information
          XBT_INFO("    %s (%s) Used: %llu; Free: %llu; Total: %llu.", d->get_cname(), sg_disk_get_mount_point(d),
                   sg_disk_get_size_used(d), sg_disk_get_size_free(d), sg_disk_get_size(d));
        }
      }
    
      void operator()() const
      {
        std::vector<sg4::Disk*> const& disks = sg4::Host::current()->get_disks();
    
        show_info(disks);
    
        // Open a non-existing file to create it
        std::string filename     = "/scratch/tmp/data.txt";
        auto* file               = sg4::File::open(filename, nullptr);
    
        sg_size_t write = file->write(200000); // Write 200,000 bytes
        XBT_INFO("Create a %llu bytes file named '%s' on /scratch", write, filename.c_str());
    
        // check that sizes have changed
        show_info(disks);
    
        // Now retrieve the size of created file and read it completely
        const sg_size_t file_size = file->size();
        file->seek(0);
        const sg_size_t read = file->read(file_size);
        XBT_INFO("Read %llu bytes on %s", read, filename.c_str());
    
        // Now write 100,000 bytes in tmp/data.txt
        write = file->write(100000); // Write 100,000 bytes
        XBT_INFO("Write %llu bytes on %s", write, filename.c_str());
    
        // Now rename file from ./tmp/data.txt to ./tmp/simgrid.readme
        std::string newpath = "/scratch/tmp/simgrid.readme";
        XBT_INFO("Move '%s' to '%s'", file->get_path(), newpath.c_str());
        file->move(newpath);
    
        // Test attaching some user data to the file
        file->set_data(new std::string("777"));
        auto file_data = file->get_unique_data<std::string>();
        XBT_INFO("User data attached to the file: %s", file_data->c_str());
    
        // Close the file
        file->close();
    
        show_info(disks);
    
        // Reopen the file and then unlink it
        file = sg4::File::open("/scratch/tmp/simgrid.readme", nullptr);
        XBT_INFO("Unlink file: '%s'", file->get_path());
        file->unlink();
        file->close(); // Unlinking the file on "disk" does not close the file and free the object
    
        show_info(disks);
      }
    };
    
    int main(int argc, char** argv)
    {
      sg4::Engine e(&argc, argv);
      sg_storage_file_system_init();
      e.load_platform(argv[1]);
      sg4::Actor::create("host", e.host_by_name("bob"), MyHost());
      e.run();
    
      return 0;
    }
    
  • Remote I/O: I/O operations on files can also be done remotely, i.e. when the accessed disk is not mounted on the caller’s host.

    View examples/cpp/io-file-remote/s4u-io-file-remote.cpp

    Download s4u-io-file-remote.cpp

    /* Copyright (c) 2014-2024. The SimGrid Team. All rights reserved.          */
    
    /* This program is free software; you can redistribute it and/or modify it
     * under the terms of the license (GNU LGPL) which comes with this package. */
    
    #include <simgrid/plugins/file_system.h>
    #include <simgrid/s4u.hpp>
    #include <string>
    
    #define INMEGA (1024 * 1024)
    
    XBT_LOG_NEW_DEFAULT_CATEGORY(remote_io, "Messages specific for this io example");
    namespace sg4 = simgrid::s4u;
    
    static void host(std::vector<std::string> args)
    {
      sg4::File* file          = sg4::File::open(args[1], nullptr);
      const char* filename     = file->get_path();
      XBT_INFO("Opened file '%s'", filename);
      file->dump();
      XBT_INFO("Try to write %llu MiB to '%s'", file->size() / 1024, filename);
      sg_size_t write = file->write(file->size() * 1024);
      XBT_INFO("Have written %llu MiB to '%s'.", write / (1024 * 1024), filename);
    
      if (args.size() > 4) {
        if (std::stoi(args[4]) != 0) {
          XBT_INFO("Move '%s' (of size %llu) from '%s' to '%s'", filename, file->size(), sg4::Host::current()->get_cname(),
                   args[2].c_str());
          file->remote_move(sg4::Host::by_name(args[2]), args[3]);
        } else {
          XBT_INFO("Copy '%s' (of size %llu) from '%s' to '%s'", filename, file->size(), sg4::Host::current()->get_cname(),
                   args[2].c_str());
          file->remote_copy(sg4::Host::by_name(args[2]), args[3]);
        }
      }
      file->close();
    }
    
    int main(int argc, char** argv)
    {
      sg4::Engine e(&argc, argv);
      sg_storage_file_system_init();
      e.load_platform(argv[1]);
      e.register_function("host", host);
      e.load_deployment(argv[2]);
      std::vector<sg4::Host*> all_hosts = e.get_all_hosts();
    
      for (auto const& h : all_hosts) {
        for (auto const& d : h->get_disks())
          XBT_INFO("Init: %s: %llu/%llu MiB used/free on '%s@%s'", h->get_cname(), sg_disk_get_size_used(d) / INMEGA,
                   sg_disk_get_size_free(d) / INMEGA, d->get_cname(), d->get_host()->get_cname());
      }
    
      e.run();
    
      for (auto const& h : all_hosts) {
        for (auto const& d : h->get_disks())
          XBT_INFO("End: %llu/%llu MiB used/free on '%s@%s'", sg_disk_get_size_used(d) / INMEGA,
                   sg_disk_get_size_free(d) / INMEGA, d->get_cname(), h->get_cname());
      }
    
      XBT_INFO("Simulation time %g", sg4::Engine::get_clock());
      return 0;
    }
    

Bags of activities

Sometimes, you want to block on a set of activities, getting unblocked when any activity of the set unblocks, or waiting for the completion of all activities in the set. This is where the ActivitySet become useful.

Waiting for all activities in a set

The wait_all() function is useful when you want to block until all activities in a given set have been completed.

See also simgrid::s4u::ActivitySet::wait_all().

View examples/cpp/activityset-waitall/s4u-activityset-waitall.cpp

Download s4u-activityset-waitall.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_activity_waitall, "Messages specific for this s4u example");

static void bob()
{
  sg4::Mailbox* mbox    = sg4::Mailbox::by_name("mbox");
  sg4::MessageQueue* mqueue = sg4::MessageQueue::by_name("mqueue");
  const sg4::Disk* disk = sg4::Host::current()->get_disks().front();
  std::string* payload;
  std::string* message;

  XBT_INFO("Create my asynchronous activities");
  auto exec = sg4::this_actor::exec_async(5e9);
  auto comm = mbox->get_async(&payload);
  auto io   = disk->read_async(3e8);
  auto mess = mqueue->get_async(&message);

  sg4::ActivitySet pending_activities({exec, comm, io, mess});

  XBT_INFO("Wait for asynchronous activities to complete, all in one shot.");
  pending_activities.wait_all();

  XBT_INFO("All activities are completed.");
  delete payload;
  delete message;
}

static void alice()
{
  auto* payload = new std::string("Message");
  XBT_INFO("Send '%s'", payload->c_str());
  sg4::Mailbox::by_name("mbox")->put(payload, 6e8);
}

static void carl()
{
  auto* payload = new std::string("Control Message");
  sg4::MessageQueue::by_name("mqueue")->put(payload);
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);

  sg4::Actor::create("bob", e.host_by_name("bob"), bob);
  sg4::Actor::create("alice", e.host_by_name("alice"), alice);
  sg4::Actor::create("carl", e.host_by_name("carl"), carl);

  e.run();

  return 0;
}

Waiting for all activities in a set (with timeout)

The wait_all_for() function is very similar to wait_all() but allows to specify a timeout.

See also simgrid::s4u::ActivitySet::wait_all_for().

View examples/cpp/activityset-waitallfor/s4u-activityset-waitallfor.cpp

Download s4u-activityset-waitallfor.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_activity_waitallfor, "Messages specific for this s4u example");

static void bob()
{
  sg4::Mailbox* mbox    = sg4::Mailbox::by_name("mbox");
  sg4::MessageQueue* mqueue = sg4::MessageQueue::by_name("mqueue");
  const sg4::Disk* disk = sg4::Host::current()->get_disks().front();
  std::string* payload;
  std::string* message;

  XBT_INFO("Create my asynchronous activities");
  auto exec = sg4::this_actor::exec_async(5e9);
  auto comm = mbox->get_async(&payload);
  auto io   = disk->read_async(3e8);
  auto mess = mqueue->get_async(&message);

  sg4::ActivitySet pending_activities({exec, comm, io, mess});

  XBT_INFO("Wait for asynchronous activities to complete");
  while (not pending_activities.empty()) {
    try {
      pending_activities.wait_all_for(1);
    } catch (simgrid::TimeoutException&) {
      XBT_INFO("Not all activities are terminated yet.");
    }
    while (auto completed_one = pending_activities.test_any()) {
      if (boost::dynamic_pointer_cast<sg4::Comm>(completed_one))
        XBT_INFO("Completed a Comm");
      if (boost::dynamic_pointer_cast<sg4::Mess>(completed_one))
        XBT_INFO("Completed a Mess");
      if (boost::dynamic_pointer_cast<sg4::Exec>(completed_one))
        XBT_INFO("Completed an Exec");
      if (boost::dynamic_pointer_cast<sg4::Io>(completed_one))
        XBT_INFO("Completed an I/O");
    }
  }
  XBT_INFO("Last activity is complete");
  delete payload;
  delete message;
}

static void alice()
{
  auto* payload = new std::string("Message");
  XBT_INFO("Send '%s'", payload->c_str());
  sg4::Mailbox::by_name("mbox")->put(payload, 6e8);
}

static void carl()
{
  sg4::this_actor::sleep_for(1.99);
  auto* payload = new std::string("Control Message");
  XBT_INFO("Send '%s'", payload->c_str());
  sg4::MessageQueue::by_name("mqueue")->put(payload);
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);

  sg4::Actor::create("bob", e.host_by_name("bob"), bob);
  sg4::Actor::create("alice", e.host_by_name("alice"), alice);
  sg4::Actor::create("carl", e.host_by_name("carl"), carl);

  e.run();

  return 0;
}

Waiting for the first completed activity in a set

The wait_any() blocks until one activity of the set completes, no matter which terminates first.

See also simgrid::s4u::ActivitySet::wait_any().

View examples/cpp/activityset-waitany/s4u-activityset-waitany.cpp

Download s4u-activityset-waitany.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_activity_waitany, "Messages specific for this s4u example");

static void bob()
{
  sg4::Mailbox* mbox    = sg4::Mailbox::by_name("mbox");
  sg4::MessageQueue* mqueue = sg4::MessageQueue::by_name("mqueue");
  const sg4::Disk* disk = sg4::Host::current()->get_disks().front();
  std::string* payload;
  std::string* message;

  XBT_INFO("Create my asynchronous activities");
  auto exec = sg4::this_actor::exec_async(5e9);
  auto comm = mbox->get_async(&payload);
  auto io   = disk->read_async(3e8);
  auto mess = mqueue->get_async(&message);

  sg4::ActivitySet pending_activities({exec, comm, io, mess});

  XBT_INFO("Wait for asynchronous activities to complete");
  while (not pending_activities.empty()) {
    auto completed_one = pending_activities.wait_any();
    if (completed_one != nullptr) {
      if (boost::dynamic_pointer_cast<sg4::Comm>(completed_one))
        XBT_INFO("Completed a Comm");
      if (boost::dynamic_pointer_cast<sg4::Mess>(completed_one))
        XBT_INFO("Completed a Mess");
      if (boost::dynamic_pointer_cast<sg4::Exec>(completed_one))
        XBT_INFO("Completed an Exec");
      if (boost::dynamic_pointer_cast<sg4::Io>(completed_one))
        XBT_INFO("Completed an I/O");
    }
  }
  XBT_INFO("Last activity is complete");
  delete payload;
  delete message;
}

static void alice()
{
  auto* payload = new std::string("Message");
  XBT_INFO("Send '%s'", payload->c_str());
  sg4::Mailbox::by_name("mbox")->put(payload, 6e8);
}

static void carl()
{
  sg4::this_actor::sleep_for(2);
  auto* payload = new std::string("Control Message");
  XBT_INFO("Send '%s'", payload->c_str());
  sg4::MessageQueue::by_name("mqueue")->put(payload);
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);

  sg4::Actor::create("bob", e.host_by_name("bob"), bob);
  sg4::Actor::create("alice", e.host_by_name("alice"), alice);
  sg4::Actor::create("carl", e.host_by_name("carl"), carl);

  e.run();

  return 0;
}

Testing whether at least one activity completed

The test_any() returns whether at least one activity of the set has completed.

See also simgrid::s4u::ActivitySet::test_any().

View examples/cpp/activityset-testany/s4u-activityset-testany.cpp

Download s4u-activityset-testany.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <cstdlib>
#include <iostream>
#include <string>

namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_activity_testany, "Messages specific for this s4u example");

static void bob()
{
  sg4::Mailbox* mbox        = sg4::Mailbox::by_name("mbox");
  sg4::MessageQueue* mqueue = sg4::MessageQueue::by_name("mqueue");
  const sg4::Disk* disk     = sg4::Host::current()->get_disks().front();
  std::string* payload;
  std::string* message;

  XBT_INFO("Create my asynchronous activities");
  auto exec = sg4::this_actor::exec_async(5e9);
  auto comm = mbox->get_async(&payload);
  auto mess = mqueue->get_async(&message);
  auto io   = disk->read_async(3e8);

  sg4::ActivitySet pending_activities({exec, comm, mess, io});
  XBT_INFO("Sleep_for a while");
  sg4::this_actor::sleep_for(1);

  XBT_INFO("Test for completed activities");
  while (not pending_activities.empty()) {
    auto completed_one = pending_activities.test_any();
    if (completed_one != nullptr) {
      if (boost::dynamic_pointer_cast<sg4::Comm>(completed_one))
        XBT_INFO("Completed a Comm");
      if (boost::dynamic_pointer_cast<sg4::Mess>(completed_one))
        XBT_INFO("Completed a Mess");
      if (boost::dynamic_pointer_cast<sg4::Exec>(completed_one))
        XBT_INFO("Completed an Exec");
      if (boost::dynamic_pointer_cast<sg4::Io>(completed_one))
        XBT_INFO("Completed an I/O");
    } else {
      XBT_INFO("Nothing matches, test again in 0.5s");
      sg4::this_actor::sleep_for(.5);
    }
  }
  XBT_INFO("Last activity is complete");
  delete payload;
  delete message;
}

static void alice()
{
  auto* payload = new std::string("Message");
  XBT_INFO("Send '%s'", payload->c_str());
  sg4::Mailbox::by_name("mbox")->put(payload, 6e8);
}

static void carl()
{
  sg4::this_actor::sleep_for(1.99);
  auto* payload = new std::string("Control Message");
  XBT_INFO("Send '%s'", payload->c_str());
  sg4::MessageQueue::by_name("mqueue")->put(payload);
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);

  sg4::Actor::create("bob", e.host_by_name("bob"), bob);
  sg4::Actor::create("alice", e.host_by_name("alice"), alice);
  sg4::Actor::create("carl", e.host_by_name("carl"), carl);

  e.run();

  return 0;
}

Dependencies between activities

SimGrid makes it easy to express dependencies between activities, where a given activity cannot start until the completion of all its predecessors. You can even have simulation not involving any actors, where the main thread (called maestro) creates and schedules activities itself.

Simple dependencies

When you declare dependencies between two activities, the dependent will not actually start until all its dependencies complete, as shown in the following examples. The first one declare dependencies between executions while the second one declare dependencies between communications. You could declare such dependencies between arbitrary activities.

View examples/cpp/exec-dependent/s4u-exec-dependent.cpp

Download s4u-exec-dependent.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <vector>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void worker()
{
  // Define an amount of work that should take 1 second to execute.
  double computation_amount = sg4::this_actor::get_host()->get_speed();

  // Create a small DAG
  // + Two parents and a child
  // + First parent ends after 1 second and the Second parent after 2 seconds.
  sg4::ExecPtr first_parent = sg4::this_actor::exec_init(computation_amount);
  sg4::ExecPtr second_parent = sg4::this_actor::exec_init(2 * computation_amount);
  sg4::ExecPtr child = sg4::Exec::init()->set_flops_amount(computation_amount);

  sg4::ActivitySet pending_execs ({first_parent, second_parent, child});

  // Name the activities (for logging purposes only)
  first_parent->set_name("parent 1");
  second_parent->set_name("parent 2");
  child->set_name("child");

  // Create the dependencies by declaring 'child' as a successor of first_parent and second_parent
  first_parent->add_successor(child);
  second_parent->add_successor(child);

  // Start the activities.
  first_parent->start();
  second_parent->start();
  child->start();

  // wait for the completion of all activities
  while (not pending_execs.empty()) {
    auto completed_one = pending_execs.wait_any();
    if (completed_one != nullptr)
      XBT_INFO("Exec '%s' is complete", completed_one->get_cname());
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  sg4::Actor::create("worker", e.host_by_name("Fafard"), worker);

  sg4::Exec::on_veto_cb([&e](sg4::Exec& exec) {
    // First display the situation
    XBT_INFO("Activity '%s' vetoed. Dependencies: %s; Ressources: %s", exec.get_cname(),
             (exec.dependencies_solved() ? "solved" : "NOT solved"),
             (exec.is_assigned() ? "assigned" : "NOT assigned"));

    // In this simple case, we just assign the child task to a resource when its dependencies are solved
    if (exec.dependencies_solved() && not exec.is_assigned()) {
      XBT_INFO("Activity %s's dependencies are resolved. Let's assign it to Fafard.", exec.get_cname());
      exec.set_host(e.host_by_name("Fafard"));
    }
  });

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

View examples/cpp/comm-dependent/s4u-comm-dependent.cpp

Download s4u-comm-dependent.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_comm_dependent, "Messages specific for this s4u example");

static void sender(sg4::Mailbox* mailbox)
{
  auto* computation_amount = new double(sg4::this_actor::get_host()->get_speed());
  sg4::ExecPtr exec        = sg4::this_actor::exec_init(2 * (*computation_amount));
  sg4::CommPtr comm        = mailbox->put_init(computation_amount, 7e6);

  exec->set_name("exec on sender")->add_successor(comm)->start();
  comm->set_name("comm to receiver")->start();
  exec->wait();
  comm->wait();
}

static void receiver(sg4::Mailbox* mailbox)
{
  double* received           = nullptr;
  double computation_amount  = sg4::this_actor::get_host()->get_speed();
  sg4::ExecPtr exec          = sg4::this_actor::exec_init(2 * computation_amount);
  sg4::CommPtr comm          = mailbox->get_init()->set_dst_data((void**)&received, sizeof(double));

  comm->set_name("comm from sender")->add_successor(exec)->start();
  exec->set_name("exec on receiver")->start();

  comm->wait();
  exec->wait();
  XBT_INFO("Received: %.0f flops were computed on sender", *received);
  delete received;
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  sg4::Mailbox* mbox = e.mailbox_by_name_or_create("Mailbox");

  sg4::Actor::create("sender", e.host_by_name("Tremblay"), sender, mbox);
  sg4::Actor::create("receiver", e.host_by_name("Jupiter"), receiver, mbox);

  e.run();

  XBT_INFO("Simulation time: %.3f", sg4::Engine::get_clock());

  return 0;
}

Assigning activities

To actually start, an activity needs to be assigned to a given resource. This examples illustrates how an execution that is not assigned will not actually start until being assigned. In some sense, activities’ assignment can be seen as a specific dependency that can withdraw their execution.

View examples/cpp/exec-unassigned/s4u-exec-unassigned.cpp

Download s4u-exec-unassigned.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <vector>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void worker()
{
  // Define an amount of work that should take 1 second to execute.
  double computation_amount = sg4::this_actor::get_host()->get_speed();

  // Create an unassigned activity and start it. It will not actually start, because it's not assigned to any host yet
  sg4::ExecPtr exec = sg4::Exec::init()->set_flops_amount(computation_amount)->set_name("exec")->start();

  // Wait for a while
  sg4::this_actor::sleep_for(10);

  // Assign the activity to the current host. This triggers its start, then waits for it completion.
  exec->set_host(sg4::Host::current())->wait();
  XBT_INFO("Exec '%s' is complete", exec->get_cname());
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  sg4::Actor::create("worker", e.host_by_name("Fafard"), worker);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Simple DAG of activities

This example shows how to create activities from the maestro directly without relying on an actor, organize the dependencies of activities as a DAG (direct acyclic graph), and start them. Each activity will start as soon as its dependencies are fulfilled.

View examples/cpp/dag-simple/s4u-dag-simple.cpp

Download s4u-dag-simple.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <vector>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);
  std::set<sg4::Activity*> vetoed;
  e.track_vetoed_activities(&vetoed);

  auto* fafard = e.host_by_name("Fafard");

  // Display the details on vetoed activities
  sg4::Exec::on_veto_cb([](sg4::Exec const& exec) {
    XBT_INFO("Execution '%s' vetoed. Dependencies: %s; Ressources: %s", exec.get_cname(),
             (exec.dependencies_solved() ? "solved" : "NOT solved"),
             (exec.is_assigned() ? "assigned" : "NOT assigned"));
  });

  sg4::Exec::on_completion_cb([](sg4::Exec const& exec) {
    XBT_INFO("Execution '%s' is complete (start time: %f, finish time: %f)", exec.get_cname(), exec.get_start_time(),
             exec.get_finish_time());
  });

  // Define an amount of work that should take 1 second to execute.
  double computation_amount = fafard->get_speed();

  // Create a small DAG: Two parents and a child
  sg4::ExecPtr first_parent  = sg4::Exec::init();
  sg4::ExecPtr second_parent = sg4::Exec::init();
  sg4::ExecPtr child         = sg4::Exec::init();
  first_parent->add_successor(child);
  second_parent->add_successor(child);

  // Set the parameters (the name is for logging purposes only)
  // + First parent ends after 1 second and the Second parent after 2 seconds.
  first_parent->set_name("parent 1")->set_flops_amount(computation_amount);
  second_parent->set_name("parent 2")->set_flops_amount(2 * computation_amount);
  child->set_name("child")->set_flops_amount(computation_amount);

  // Only the parents are scheduled so far
  first_parent->set_host(fafard);
  second_parent->set_host(fafard);

  // Start all activities that can actually start.
  first_parent->start();
  second_parent->start();
  child->start();

  while (child->get_state() != sg4::Activity::State::FINISHED) {
    e.run();
    for (auto* a : vetoed) {
      auto* exec = static_cast<sg4::Exec*>(a);

      // In this simple case, we just assign the child task to a resource when its dependencies are solved
      if (exec->dependencies_solved() && not exec->is_assigned()) {
        XBT_INFO("Activity %s's dependencies are resolved. Let's assign it to Fafard.", exec->get_cname());
        exec->set_host(fafard);
      } else {
        XBT_INFO("Activity %s not ready.", exec->get_cname());
      }
    }
    vetoed.clear(); // DON'T FORGET TO CLEAR this set between two calls to run
  }

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

DAG with communication

This is a little example showing how add communication activities to your DAG, representing inter-task data exchanges.

View examples/cpp/dag-comm/s4u-dag-comm.cpp

Download s4u-dag-comm.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/plugins/file_system.h>
#include <simgrid/s4u.hpp>
#include <vector>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  auto* tremblay = e.host_by_name("Tremblay");
  auto* jupiter  = e.host_by_name("Jupiter");

  // Display the details on vetoed activities
  sg4::Exec::on_veto_cb([](sg4::Exec const& exec) {
    XBT_INFO("Execution '%s' vetoed. Dependencies: %s; Ressources: %s", exec.get_cname(),
             (exec.dependencies_solved() ? "solved" : "NOT solved"), (exec.is_assigned() ? "assigned" : "NOT assigned"));
  });
  sg4::Comm::on_veto_cb([](sg4::Comm const& comm) {
    XBT_INFO("Communication '%s' vetoed. Dependencies: %s; Ressources: %s", comm.get_cname(),
             (comm.dependencies_solved() ? "solved" : "NOT solved"), (comm.is_assigned() ? "assigned" : "NOT assigned"));
  });

  sg4::Exec::on_completion_cb([](sg4::Exec const& exec) {
    XBT_INFO("Exec '%s' is complete (start time: %f, finish time: %f)", exec.get_cname(), exec.get_start_time(),
             exec.get_finish_time());
  });
  sg4::Comm::on_completion_cb([](sg4::Comm const& comm) {
    XBT_INFO("Comm '%s' is complete", comm.get_cname());
  });

  // Create a small DAG: parent->transfer->child
  sg4::ExecPtr parent   = sg4::Exec::init();
  sg4::CommPtr transfer = sg4::Comm::sendto_init();
  sg4::ExecPtr child    = sg4::Exec::init();
  parent->add_successor(transfer);
  transfer->add_successor(child);

  // Set the parameters (the name is for logging purposes only)
  // + parent and child end after 1 second
  parent->set_name("parent")->set_flops_amount(tremblay->get_speed())->start();
  transfer->set_name("transfer")->set_payload_size(125e6)->start();
  child->set_name("child")->set_flops_amount(jupiter->get_speed())->start();

  // Schedule the different activities
  parent->set_host(tremblay);
  transfer->set_source(tremblay);
  child->set_host(jupiter);
  transfer->set_destination(jupiter);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

DAG with I/O

This is a little example showing how add I/O activities to your DAG, representing disk buffers.

View examples/cpp/dag-io/s4u-dag-io.cpp

Download s4u-dag-io.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/plugins/file_system.h>
#include <simgrid/s4u.hpp>
#include <vector>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  auto* bob  = e.host_by_name("bob");
  auto* carl = e.host_by_name("carl");

  // Display the details on vetoed activities
  sg4::Exec::on_veto_cb([](sg4::Exec const& exec) {
    XBT_INFO("Exec '%s' vetoed. Dependencies: %s; Ressources: %s", exec.get_cname(),
             (exec.dependencies_solved() ? "solved" : "NOT solved"), (exec.is_assigned() ? "assigned" : "NOT assigned"));
  });
  sg4::Io::on_veto_cb([](sg4::Io const& io) {
    XBT_INFO("Io '%s' vetoed. Dependencies: %s; Ressources: %s", io.get_cname(),
             (io.dependencies_solved() ? "solved" : "NOT solved"), (io.is_assigned() ? "assigned" : "NOT assigned"));
  });

  sg4::Exec::on_completion_cb([](sg4::Exec const& exec) {
    XBT_INFO("Exec '%s' is complete (start time: %f, finish time: %f)", exec.get_cname(), exec.get_start_time(),
             exec.get_finish_time());
  });

  // Create a small DAG: parent->write_output->read_input->child
  sg4::ExecPtr parent     = sg4::Exec::init();
  sg4::IoPtr write_output = sg4::Io::init();
  sg4::IoPtr read_input   = sg4::Io::init();
  sg4::ExecPtr child      = sg4::Exec::init();
  parent->add_successor(write_output);
  write_output->add_successor(read_input);
  read_input->add_successor(child);

  // Set the parameters (the name is for logging purposes only)
  // + parent and chile end after 1 second
  parent->set_name("parent")->set_flops_amount(bob->get_speed());
  write_output->set_name("write")->set_size(1e9)->set_op_type(sg4::Io::OpType::WRITE);
  read_input->set_name("read")->set_size(1e9)->set_op_type(sg4::Io::OpType::READ);
  child->set_name("child")->set_flops_amount(carl->get_speed());

  // Schedule and try to start the different activities
  parent->set_host(bob)->start();
  write_output->set_disk(bob->get_disks().front())->start();
  read_input->set_disk(carl->get_disks().front())->start();
  child->set_host(carl)->start();

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Scheduling activities

This example illustrates a simple scheduling algorithm, where the activities are placed on the “most adapted” host. Of course, there is many way to determine which host is the better fit for a given activity, and this example just uses a simple algorithm.

View examples/cpp/dag-scheduling/s4u-dag-scheduling.cpp

Download s4u-dag-scheduling.cpp

/* Copyright (c) 2009-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* simple test to schedule a DAX file with the Min-Min algorithm.           */
#include <algorithm>
#include <simgrid/host.h>
#include <simgrid/s4u.hpp>
#include <string.h>

XBT_LOG_NEW_DEFAULT_CATEGORY(dag_scheduling, "Logging specific to this example");
namespace sg4 = simgrid::s4u;

static std::vector<sg4::Exec*> get_ready_tasks(const std::vector<sg4::ActivityPtr>& dax)
{
  std::vector<sg4::Exec*> ready_tasks;
  std::map<sg4::Exec*, unsigned int> candidate_execs;

  for (const auto& a : dax) {
    // Only look at activity that have their dependencies solved but are not assigned
    if (a->dependencies_solved() && not a->is_assigned()) {
      // if it is an exec, it's ready
      if (auto* exec = dynamic_cast<sg4::Exec*>(a.get()))
        ready_tasks.push_back(exec);
      // if it a comm, we consider its successor as a candidate. If a candidate solves all its dependencies,
      // i.e., get all its input data, it's ready
      if (const auto* comm = dynamic_cast<sg4::Comm*>(a.get())) {
        auto* next_exec = static_cast<sg4::Exec*>(comm->get_successors().front().get());
        candidate_execs[next_exec]++;
        if (next_exec->get_dependencies().size() == candidate_execs[next_exec])
          ready_tasks.push_back(next_exec);
      }
    }
  }
  XBT_DEBUG("There are %zu ready tasks", ready_tasks.size());
  return ready_tasks;
}

static sg4::Host* get_best_host(const sg4::ExecPtr exec, double* min_finish_time)
{
  sg4::Host* best_host = nullptr;
  *min_finish_time = std::numeric_limits<double>::max();

  for (const auto& host : sg4::Engine::get_instance()->get_all_hosts()) {
    double data_available      = 0.;
    double last_data_available = -1.0;
    /* compute last_data_available */
    for (const auto& parent : exec->get_dependencies()) {
      /* normal case */
      if (const auto* comm = dynamic_cast<sg4::Comm*>(parent.get())) {
        const auto* source = comm->get_source();
        XBT_DEBUG("transfer from %s to %s", source->get_cname(), host->get_cname());
        /* Estimate the redistribution time from this parent */
        double redist_time;
        if (comm->get_remaining() <= 1e-6) {
          redist_time = 0;
        } else {
          double bandwidth      = std::numeric_limits<double>::max();
          auto [links, latency] = source->route_to(host);
          for (auto const& link : links)
            bandwidth = std::min(bandwidth, link->get_bandwidth());

          redist_time = latency + comm->get_remaining() / bandwidth;
        }
        // We use the user data field to store the finish time of the predecessor of the comm, i.e., its potential
        // start time
        data_available = *comm->get_data<double>() + redist_time;
      }

      /* no transfer, control dependency */
      if (const auto* parent_exec = dynamic_cast<sg4::Exec*>(parent.get()))
        data_available = parent_exec->get_finish_time();

      if (last_data_available < data_available)
        last_data_available = data_available;
    }

    double finish_time = std::max(*host->get_data<double>(), last_data_available) +
                         exec->get_remaining() / host->get_speed();

    XBT_DEBUG("%s finishes on %s at %f", exec->get_cname(), host->get_cname(), finish_time);

    if (finish_time < *min_finish_time) {
      *min_finish_time = finish_time;
      best_host        = host;
    }
  }

  return best_host;
}

static void schedule_on(sg4::ExecPtr exec, sg4::Host* host, double busy_until = 0.0)
{
  exec->set_host(host);
  // We use the user data field to store up to when the host is busy
  delete host->get_data<double>(); // In case we're erasing a previous value
  host->set_data(new double(busy_until));
  // we can also set the destination of all the input comms of this exec
  for (const auto& pred : exec->get_dependencies()) {
    auto* comm = dynamic_cast<sg4::Comm*>(pred.get());
    if (comm != nullptr) {
      comm->set_destination(host);
      delete comm->get_data<double>();
    }
  }
  // we can also set the source of all the output comms of this exec
  for (const auto& succ : exec->get_successors()) {
    auto* comm = dynamic_cast<sg4::Comm*>(succ.get());
    if (comm != nullptr)
      comm->set_source(host);
  }
}

int main(int argc, char** argv)
{
  sg4::Engine e(&argc, argv);
  std::set<sg4::Activity*> vetoed;
  e.track_vetoed_activities(&vetoed);

  sg4::Exec::on_completion_cb([](sg4::Exec const& exec) {
    // when an Exec completes, we need to set the potential start time of all its ouput comms
    for (const auto& succ : exec.get_successors()) {
      auto* comm = dynamic_cast<sg4::Comm*>(succ.get());
      if (comm != nullptr) {
        auto* finish_time = new double(exec.get_finish_time());
        // We use the user data field to store the finish time of the predecessor of the comm, i.e., its potential start
        // time
        comm->set_data(finish_time);
      }
    }
  });

  e.load_platform(argv[1]);

  /* Mark all hosts as sequential, as it ought to be in such a scheduling example.
   *
   * It means that the hosts can only compute one thing at a given time. If an execution already takes place on a given
   * host, any subsequently started execution will be queued until after the first execution terminates */
  for (auto const& host : e.get_all_hosts()) {
    host->set_concurrency_limit(1);
    host->set_data(new double(0.0));
  }
  /* load the DAX file */
  auto dax = sg4::create_DAG_from_DAX(argv[2]);

  /* Schedule the root first */
  double root_finish_time;
  auto* root = static_cast<sg4::Exec*>(dax.front().get());
  auto* host = get_best_host(root, &root_finish_time);
  schedule_on(root, host);

  e.run();

  while (not vetoed.empty()) {
    XBT_DEBUG("Start new scheduling round");
    /* Get the set of ready tasks */
    auto ready_tasks = get_ready_tasks(dax);
    vetoed.clear();

    if (ready_tasks.empty()) {
      /* there is no ready exec, let advance the simulation */
      e.run();
      continue;
    }
    /* For each ready exec:
     * get the host that minimizes the completion time.
     * select the exec that has the minimum completion time on its best host.
     */
    double min_finish_time   = std::numeric_limits<double>::max();
    sg4::Exec* selected_task = nullptr;
    sg4::Host* selected_host = nullptr;

    for (auto* exec : ready_tasks) {
      XBT_DEBUG("%s is ready", exec->get_cname());
      double finish_time;
      host = get_best_host(exec, &finish_time);
      if (finish_time < min_finish_time) {
        min_finish_time = finish_time;
        selected_task   = exec;
        selected_host   = host;
      }
    }

    XBT_INFO("Schedule %s on %s", selected_task->get_cname(), selected_host->get_cname());
    schedule_on(selected_task, selected_host, min_finish_time);

    ready_tasks.clear();
    e.run();
  }

  /* Cleanup memory */
  for (auto const* h : e.get_all_hosts())
    delete h->get_data<double>();

  XBT_INFO("Simulation Time: %f", simgrid_get_clock());

  return 0;
}

Loading DAGs from file

There is currently two file formats that you can load directly in SimGrid, but writing another loader for your beloved format should not be difficult.

View examples/cpp/dag-from-dax/s4u-dag-from-dax.cpp

Download s4u-dag-from-dax.cpp

/* simple test trying to load a DAX file.                                   */

/* Copyright (c) 2009-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"

#include <stdio.h>
#include <string.h>

XBT_LOG_NEW_DEFAULT_CATEGORY(dag_from_dax, "Logging specific to this example");
namespace sg4 = simgrid::s4u;

int main(int argc, char** argv)
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  std::vector<sg4::ActivityPtr> dag = sg4::create_DAG_from_DAX(argv[2]);

  if (dag.empty()) {
    XBT_ERROR("A problem occurred during DAX parsing (cycle or syntax). Do not continue this test");
    exit(2);
  }

  XBT_INFO("--------- Display all activities of the loaded DAG -----------");
  for (const auto& a : dag) {
    std::string type = "an Exec";
    std::string task = "flops to execute";
    if (dynamic_cast<sg4::Comm*>(a.get()) != nullptr) {
      type = "a Comm";
      task = "bytes to transfer";
    }
    XBT_INFO("'%s' is %s: %.0f %s. Dependencies: %s; Ressources: %s", a->get_cname(), type.c_str(), a->get_remaining(),
             task.c_str(), (a->dependencies_solved() ? "solved" : "NOT solved"),
             (a->is_assigned() ? "assigned" : "NOT assigned"));
  }

  XBT_INFO("------------------- Schedule tasks ---------------------------");
  auto hosts = e.get_all_hosts();
  auto count = e.get_host_count();
  int cursor = 0;
  // Schedule end first
  static_cast<sg4::Exec*>(dag.back().get())->set_host(hosts[0]);

  for (const auto& a : dag) {
    if (auto* exec = dynamic_cast<sg4::Exec*>(a.get()); exec != nullptr && exec->get_name() != "end") {
      exec->set_host(hosts[cursor % count]);
      cursor++;
    }
    if (auto* comm = dynamic_cast<sg4::Comm*>(a.get())) {
      const auto* pred = dynamic_cast<sg4::Exec*>((*comm->get_dependencies().begin()).get());
      const auto* succ = dynamic_cast<sg4::Exec*>(comm->get_successors().front().get());
      comm->set_source(pred->get_host())->set_destination(succ->get_host());
    }
  }

  XBT_INFO("------------------- Run the schedule -------------------------");
  e.run();

  XBT_INFO("-------------- Summary of executed schedule ------------------");
  for (const auto& a : dag) {
    if (const auto* exec = dynamic_cast<sg4::Exec*>(a.get())) {
      XBT_INFO("[%f->%f] '%s' executed on %s", exec->get_start_time(), exec->get_finish_time(), exec->get_cname(),
               exec->get_host()->get_cname());
    }
    if (const auto* comm = dynamic_cast<sg4::Comm*>(a.get())) {
      XBT_INFO("[%f->%f] '%s' transferred from %s to %s", comm->get_start_time(), comm->get_finish_time(),
               comm->get_cname(), comm->get_source()->get_cname(), comm->get_destination()->get_cname());
    }
  }
  return 0;
}

View examples/cpp/dag-from-dot/s4u-dag-from-dot.cpp

Download s4u-dag-from-dot.cpp

/* simple test trying to load a DOT file.                                   */

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <stdio.h>

XBT_LOG_NEW_DEFAULT_CATEGORY(dag_from_dot, "Logging specific to this example");
namespace sg4 = simgrid::s4u;

int main(int argc, char** argv)
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  std::vector<sg4::ActivityPtr> dag = sg4::create_DAG_from_dot(argv[2]);

  if (dag.empty()) {
    XBT_CRITICAL("No dot loaded. Do you have a cycle in your graph?");
    exit(2);
  }

  XBT_INFO("--------- Display all activities of the loaded DAG -----------");
  for (const auto& a : dag) {
    std::string type = "an Exec";
    std::string task = "flops to execute";
    if (dynamic_cast<sg4::Comm*>(a.get()) != nullptr) {
      type = "a Comm";
      task = "bytes to transfer";
    }
    XBT_INFO("'%s' is %s: %.0f %s. Dependencies: %s; Ressources: %s", a->get_cname(), type.c_str(), a->get_remaining(),
             task.c_str(), (a->dependencies_solved() ? "solved" : "NOT solved"),
             (a->is_assigned() ? "assigned" : "NOT assigned"));
  }

  XBT_INFO("------------------- Schedule tasks ---------------------------");
  auto hosts = e.get_all_hosts();
  auto count = e.get_host_count();
  int cursor = 0;
  // Schedule end first
  static_cast<sg4::Exec*>(dag.back().get())->set_host(hosts[0]);

  for (const auto& a : dag) {
    if (auto* exec = dynamic_cast<sg4::Exec*>(a.get()); exec != nullptr && exec->get_name() != "end") {
      exec->set_host(hosts[cursor % count]);
      cursor++;
    }
    if (auto* comm = dynamic_cast<sg4::Comm*>(a.get())) {
      const auto* pred = dynamic_cast<sg4::Exec*>((*comm->get_dependencies().begin()).get());
      const auto* succ = dynamic_cast<sg4::Exec*>(comm->get_successors().front().get());
      comm->set_source(pred->get_host())->set_destination(succ->get_host());
    }
  }

  XBT_INFO("------------------- Run the schedule -------------------------");
  e.run();

  XBT_INFO("-------------- Summary of executed schedule ------------------");
  for (const auto& a : dag) {
    if (const auto* exec = dynamic_cast<sg4::Exec*>(a.get())) {
      XBT_INFO("[%f->%f] '%s' executed on %s", exec->get_start_time(), exec->get_finish_time(), exec->get_cname(),
               exec->get_host()->get_cname());
    }
    if (const auto* comm = dynamic_cast<sg4::Comm*>(a.get())) {
      XBT_INFO("[%f->%f] '%s' transferred from %s to %s", comm->get_start_time(), comm->get_finish_time(),
               comm->get_cname(), comm->get_source()->get_cname(), comm->get_destination()->get_cname());
    }
  }
  return 0;
}

Simulating a time slice

When you declare activities, simgrid::s4u::Engine::run() runs up to the point of time where an activity completes. Sometimes, you want to give a maximal duration to simulate up to a given date at most, for example to inject a new activity at that time. This example shows how to do it.

View examples/cpp/engine-run-partial/s4u-engine-run-partial.cpp

Download s4u-engine-run-partial.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example is to show how to use Engine::run(date) to simulate only up to that point in time */

#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

/* This actor simply executes and waits the activity it got as a parameter. */
static void runner(sg4::ExecPtr activity)
{
  activity->start();
  activity->wait();

  XBT_INFO("Goodbye now!");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  sg4::Host* fafard = e.host_by_name("Fafard");

  sg4::ExecPtr activity = sg4::this_actor::exec_init(fafard->get_speed() * 10.)->set_host(fafard);
  sg4::Actor::create("runner", fafard, runner, activity);

  while (activity->get_remaining() > 0) {
    XBT_INFO("Remaining amount of flops: %g (%.0f%%)", activity->get_remaining(),
             100 * activity->get_remaining_ratio());
    e.run_until(sg4::Engine::get_clock() + 1);
  }

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

DAG and failures

This example shows how to deal with host or network failures while scheduling DAGs of activities.

View examples/cpp/dag-failure/s4u-dag-failure.cpp

Download s4u-dag-failure.cpp

/* Copyright (c) 2006-2024. The SimGrid Team.
 * All rights reserved.                                                     */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(dag_failure, "Logging specific to this example");

namespace sg4 = simgrid::s4u;

int main(int argc, char** argv)
{
  sg4::Engine e(&argc, argv);
  sg4::Engine::set_config("host/model:ptask_L07");
  e.load_platform(argv[1]);

  auto* faulty = e.host_by_name("Faulty Host");
  auto* safe   = e.host_by_name("Safe Host");
  sg4::Exec::on_completion_cb([](sg4::Exec const& exec) {
    if (exec.get_state() == sg4::Activity::State::FINISHED)
      XBT_INFO("Activity '%s' is complete (start time: %f, finish time: %f)", exec.get_cname(), exec.get_start_time(),
               exec.get_finish_time());
    if (exec.get_state() == sg4::Activity::State::FAILED) {
      if (exec.is_parallel())
        XBT_INFO("Activity '%s' has failed. %.f %% remain to be done", exec.get_cname(),
                 100 * exec.get_remaining_ratio());
      else
        XBT_INFO("Activity '%s' has failed. %.f flops remain to be done", exec.get_cname(), exec.get_remaining());
    }
  });

  /* creation of a single Exec that will poorly fail when the workstation will stop */
  XBT_INFO("First test: sequential Exec activity");
  sg4::ExecPtr exec = sg4::Exec::init()->set_name("Poor task")->set_flops_amount(2e10)->start();

  XBT_INFO("Schedule Activity '%s' on 'Faulty Host'", exec->get_cname());
  exec->set_host(faulty);

  /* Add a child Exec that depends on the Poor task' */
  sg4::ExecPtr child = sg4::Exec::init()->set_name("Child")->set_flops_amount(2e10)->set_host(safe);
  exec->add_successor(child);
  child->start();

  XBT_INFO("Run the simulation");
  e.run();

  XBT_INFO("let's unschedule Activity '%s' and reschedule it on the 'Safe Host'", exec->get_cname());
  exec->unset_host();
  exec->set_host(safe);

  XBT_INFO("Run the simulation again");
  e.run();

  XBT_INFO("Second test: parallel Exec activity");
  exec = sg4::Exec::init()->set_name("Poor parallel task")->set_flops_amounts({2e10, 2e10})->start();

  XBT_INFO("Schedule Activity '%s' on 'Safe Host' and 'Faulty Host'", exec->get_cname());
  exec->set_hosts({safe, faulty});

  /* Add a child Exec that depends on the Poor parallel task' */
  child = sg4::Exec::init()->set_name("Child")->set_flops_amount(2e10)->set_host(safe);
  exec->add_successor(child);
  child->start();

  XBT_INFO("Run the simulation");
  e.run();

  XBT_INFO("let's unschedule Activity '%s' and reschedule it only on the 'Safe Host'", exec->get_cname());
  exec->unset_hosts();
  exec->set_flops_amount(4e10)->set_host(safe);

  XBT_INFO("Run the simulation again");
  e.run();

  return 0;
}

Classical synchronization objects

Barrier

Shows how to use simgrid::s4u::Barrier synchronization objects.

View examples/cpp/synchro-barrier/s4u-synchro-barrier.cpp

Download s4u-synchro-barrier.cpp

/* Copyright (c) 2006-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "a sample log category");
namespace sg4 = simgrid::s4u;

/// Wait on the barrier then leave
static void worker(sg4::BarrierPtr barrier)
{
  XBT_INFO("Waiting on the barrier");
  barrier->wait();

  XBT_INFO("Bye");
}

/// Spawn actor_count-1 workers and do a barrier with them
static void master(int actor_count)
{
  sg4::BarrierPtr barrier = sg4::Barrier::create(actor_count);

  XBT_INFO("Spawning %d workers", actor_count - 1);
  for (int i = 0; i < actor_count - 1; i++) {
    sg4::Actor::create("worker", sg4::Host::by_name("Jupiter"), worker, barrier);
  }

  XBT_INFO("Waiting on the barrier");
  if (barrier->wait())
    XBT_INFO("Bye from the last to enter");
  else
    XBT_INFO("Bye");
}

int main(int argc, char **argv)
{
  sg4::Engine e(&argc, argv);

  // Parameter: Number of actores in the barrier
  xbt_assert(argc >= 2, "Usage: %s <actor-count>\n", argv[0]);
  int actor_count = std::stoi(argv[1]);
  xbt_assert(actor_count > 0, "<actor-count> must be greater than 0");

  e.load_platform(argc > 2 ? argv[2] : "../../platforms/two_hosts.xml");
  sg4::Actor::create("master", e.host_by_name("Tremblay"), master, actor_count);
  e.run();

  return 0;
}

Condition variable: basic usage

Shows how to use simgrid::s4u::ConditionVariable synchronization objects.

View examples/cpp/synchro-condition-variable/s4u-synchro-condition-variable.cpp

Download s4u-synchro-condition-variable.cpp

/* Copyright (c) 2006-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <mutex>           /* std::mutex and std::scoped_lock */
#include <simgrid/s4u.hpp> /* All of S4U */

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "a sample log category");
namespace sg4 = simgrid::s4u;

static void worker_fun(sg4::ConditionVariablePtr cv, sg4::MutexPtr mutex, std::string& data, bool& done)
{
  const std::scoped_lock lock(*mutex);

  XBT_INFO("Start processing data which is '%s'.", data.c_str());
  data += " after processing";

  // Send data back to main()
  XBT_INFO("Signal to master that the data processing is completed, and exit.");

  done = true;
  cv->notify_one();
}

static void master_fun()
{
  auto mutex  = sg4::Mutex::create();
  auto cv     = sg4::ConditionVariable::create();
  std::string data = "Example data";
  bool done        = false;

  auto worker = sg4::Actor::create("worker", sg4::Host::by_name("Jupiter"), worker_fun, cv, mutex, std::ref(data),
                                   std::ref(done));

  // wait for the worker
  cv->wait(std::unique_lock(*mutex), [&done]() { return done; });
  XBT_INFO("data is now '%s'.", data.c_str());

  worker->join();
}

int main(int argc, char** argv)
{
  sg4::Engine e(&argc, argv);
  e.load_platform("../../platforms/two_hosts.xml");
  sg4::Actor::create("main", e.host_by_name("Tremblay"), master_fun);
  e.run();

  return 0;
}

Condition variable: timeouts

Shows how to specify timeouts when blocking on condition variables.

View examples/cpp/synchro-condition-variable-waituntil/s4u-synchro-condition-variable-waituntil.cpp

Download s4u-synchro-condition-variable-waituntil.cpp

/* Copyright (c) 2006-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <mutex>           /* std::mutex and std::scoped_lock */
#include <simgrid/s4u.hpp> /* All of S4U */

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "a sample log category");
namespace sg4 = simgrid::s4u;

static void competitor(int id, sg4::ConditionVariablePtr cv, sg4::MutexPtr mtx, std::shared_ptr<bool> ready)
{
  XBT_INFO("Entering the race...");
  std::unique_lock lock(*mtx);
  while (not *ready) {
    auto now = sg4::Engine::get_clock();
    if (cv->wait_until(lock, now + (id + 1) * 0.25) == std::cv_status::timeout) {
      XBT_INFO("Out of wait_until (timeout)");
    } else {
      XBT_INFO("Out of wait_until (YAY!)");
    }
  }
  XBT_INFO("Running!");
}

static void go(sg4::ConditionVariablePtr cv, sg4::MutexPtr mtx, std::shared_ptr<bool> ready)
{
  XBT_INFO("Are you ready? ...");
  sg4::this_actor::sleep_for(3);
  const std::scoped_lock lock(*mtx);
  XBT_INFO("Go go go!");
  *ready = true;
  cv->notify_all();
}

static void main_actor()
{
  auto mtx   = sg4::Mutex::create();
  auto cv    = sg4::ConditionVariable::create();
  auto ready = std::make_shared<bool>(false);

  auto* host = sg4::this_actor::get_host();
  for (int i = 0; i < 10; ++i)
    sg4::Actor::create("competitor", host, competitor, i, cv, mtx, ready);
  sg4::Actor::create("go", host, go, cv, mtx, ready);
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform("../../platforms/small_platform.xml");

  sg4::Actor::create("main", e.host_by_name("Tremblay"), main_actor);

  e.run();
  return 0;
}

Mutex

Shows how to use simgrid::s4u::Mutex synchronization objects.

View examples/cpp/synchro-mutex/s4u-synchro-mutex.cpp

Download s4u-synchro-mutex.cpp

/* Copyright (c) 2006-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp" /* All of S4U */
#include "xbt/config.hpp"
#include <mutex> /* std::mutex and std::scoped_lock */

namespace sg4 = simgrid::s4u;

static simgrid::config::Flag<int> cfg_actor_count("actors", "How many pairs of actors should be started?", 6);

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "a sample log category");

/* This worker uses a classical mutex */
static void worker(sg4::MutexPtr mutex, int& result)
{
  // lock the mutex before enter in the critical section
  mutex->lock();

  XBT_INFO("Hello s4u, I'm ready to compute after a regular lock");
  // And finally add it to the results
  result += 1;
  XBT_INFO("I'm done, good bye");

  // You have to unlock the mutex if you locked it manually.
  // Beware of exceptions preventing your unlock() from being executed!
  mutex->unlock();
}

static void workerScopedLock(sg4::MutexPtr mutex, int& result)
{
  // Simply use the std::scoped_lock like this
  // It's like a lock() that would do the unlock() automatically when getting out of scope
  const std::scoped_lock lock(*mutex);

  // then you are in a safe zone
  XBT_INFO("Hello s4u, I'm ready to compute after a scoped_lock");
  // update the results
  result += 1;
  XBT_INFO("I'm done, good bye");

  // Nothing specific here: the unlock will be automatic
}

int main(int argc, char** argv)
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argc > 1 ? argv[1] : "../../platforms/two_hosts.xml");

  /* Create the requested amount of actors pairs. Each pair has a specific mutex and cell in `result`. */
  std::vector<int> result(cfg_actor_count.get());

  for (int i = 0; i < cfg_actor_count; i++) {
    sg4::MutexPtr mutex = sg4::Mutex::create();
    sg4::Actor::create("worker", sg4::Host::by_name("Jupiter"), workerScopedLock, mutex, std::ref(result[i]));
    sg4::Actor::create("worker", sg4::Host::by_name("Tremblay"), worker, mutex, std::ref(result[i]));
  }

  e.run();

  for (int i = 0; i < cfg_actor_count; i++)
    XBT_INFO("Result[%d] -> %d", i, result[i]);

  return 0;
}

Semaphore

Shows how to use simgrid::s4u::Semaphore synchronization objects.

View examples/cpp/synchro-semaphore/s4u-synchro-semaphore.cpp

Download s4u-synchro-semaphore.cpp

/* Copyright (c) 2006-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

// This example implements a simple producer/consumer schema,
// passing a bunch of items from one to the other

#include "simgrid/s4u.hpp"

#include <memory>

namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(sem_test, "Simple test of the semaphore");

static void producer(std::string* buffer, sg4::SemaphorePtr sem_empty, sg4::SemaphorePtr sem_full,
                     const std::vector<std::string>& args)
{
  for (auto const& str : args) {
    sem_empty->acquire();
    XBT_INFO("Pushing '%s'", str.c_str());
    *buffer = str;
    sem_full->release();
  }

  XBT_INFO("Bye!");
}
static void consumer(const std::string* buffer, sg4::SemaphorePtr sem_empty, sg4::SemaphorePtr sem_full)
{
  std::string str;
  do {
    sem_full->acquire();
    str = *buffer;
    XBT_INFO("Receiving '%s'", str.c_str());
    sem_empty->release();
  } while (str != "");

  XBT_INFO("Bye!");
}

int main(int argc, char **argv)
{
  std::vector<std::string> args({"one", "two", "three", ""});
  sg4::Engine e(&argc, argv);
  e.load_platform(argc > 1 ? argv[1] : "../../platforms/two_hosts.xml");

  std::string buffer;                         /* Where the data is exchanged */
  auto sem_empty = sg4::Semaphore::create(1); /* indicates whether the buffer is empty */
  auto sem_full  = sg4::Semaphore::create(0); /* indicates whether the buffer is full */

  sg4::Actor::create("producer", e.host_by_name("Tremblay"), producer, &buffer, sem_empty, sem_full, std::cref(args));
  sg4::Actor::create("consumer", e.host_by_name("Jupiter"), consumer, &buffer, sem_empty, sem_full);
  e.run();

  return 0;
}

Interacting with the Platform

User-defined properties

You can attach arbitrary information to most platform elements from the XML file, and then interact with these values from your program. Note that the changes are not written permanently on disk, in the XML file nor anywhere else. They only last until the end of your simulation.

View examples/cpp/platform-properties/s4u-platform-properties.cpp

Download s4u-platform-properties.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

// TODO: also test the properties attached to links

#include <algorithm>
#include <simgrid/s4u.hpp>
#include <string>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Property test");
namespace sg4 = simgrid::s4u;

static void test_host(const std::string& hostname)
{
  sg4::Host* thehost                                            = sg4::Host::by_name(hostname);
  const std::unordered_map<std::string, std::string>* hostprops = thehost->get_properties();
  const char* noexist = "Unknown";
  const char* exist   = "Hdd";
  const char* value;

  XBT_INFO("== Print the properties of the host '%s'", hostname.c_str());
  // Sort the properties before displaying them, so that the tests are perfectly reproducible
  std::vector<std::string> keys;
  for (auto const& [key, _] : *hostprops)
    keys.push_back(key);
  std::sort(keys.begin(), keys.end());
  for (const std::string& key : keys)
    XBT_INFO("  Host property: '%s' -> '%s'", key.c_str(), hostprops->at(key).c_str());

  XBT_INFO("== Try to get a host property that does not exist");
  value = thehost->get_property(noexist);
  xbt_assert(not value, "The key exists (it's not supposed to)");

  XBT_INFO("== Try to get a host property that does exist");
  value = thehost->get_property(exist);
  xbt_assert(value, "\tProperty %s is undefined (where it should)", exist);
  xbt_assert(strcmp(value, "180") == 0, "\tValue of property %s is defined to %s (where it should be 180)", exist,
             value);
  XBT_INFO("   Property: %s old value: %s", exist, value);

  XBT_INFO("== Trying to modify a host property");
  thehost->set_property(exist, "250");

  /* Test if we have changed the value */
  value = thehost->get_property(exist);
  xbt_assert(value, "Property %s is undefined (where it should)", exist);
  xbt_assert(strcmp(value, "250") == 0, "Value of property %s is defined to %s (where it should be 250)", exist, value);
  XBT_INFO("   Property: %s old value: %s", exist, value);

  /* Restore the value for the next test */
  thehost->set_property(exist, "180");

  const auto* thezone = thehost->get_englobing_zone();
  XBT_INFO("== Print the properties of the zone '%s' that contains '%s'", thezone->get_cname(), hostname.c_str());
  const std::unordered_map<std::string, std::string>* zoneprops = thezone->get_properties();
  keys.clear();
  for (auto const& [key, _] : *zoneprops)
    keys.push_back(key);
  std::sort(keys.begin(), keys.end());
  for (const std::string& key : keys)
    XBT_INFO("  Zone property: '%s' -> '%s'", key.c_str(), zoneprops->at(key).c_str());
}

static void alice()
{
  /* Dump what we have on the current host */
  test_host("host1");
}

static void carole()
{
  /* Dump what we have on a remote host */
  sg4::this_actor::sleep_for(1); // Wait for alice to be done with its experiment
  test_host("host1");
}

static void david()
{
  /* Dump what we have on a remote host */
  sg4::this_actor::sleep_for(2); // Wait for alice and carole to be done with its experiment
  test_host("node-0.simgrid.org");
}

static void bob()
{
  /* this host also tests the properties of the AS*/
  const sg4::NetZone* root = sg4::Engine::get_instance()->get_netzone_root();
  XBT_INFO("== Print the properties of the root zone");
  XBT_INFO("   Zone property: filename -> %s", root->get_property("filename"));
  XBT_INFO("   Zone property: date -> %s", root->get_property("date"));
  XBT_INFO("   Zone property: author -> %s", root->get_property("author"));

  /* Get the property list of current bob actor */
  const std::unordered_map<std::string, std::string>* props = sg4::Actor::self()->get_properties();
  const char* noexist = "UnknownProcessProp";

  XBT_INFO("== Print the properties of the actor");
  for (const auto& [key, value] : *props)
    XBT_INFO("   Actor property: %s -> %s", key.c_str(), value.c_str());

  XBT_INFO("== Try to get an actor property that does not exist");

  const char* value = sg4::Actor::self()->get_property(noexist);
  xbt_assert(not value, "The property is defined (it should not)");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);
  auto* host1 = e.host_by_name("host1");
  auto* host2 = e.host_by_name("host2");

  size_t totalHosts = e.get_host_count();

  XBT_INFO("There are %zu hosts in the environment", totalHosts);
  std::vector<sg4::Host*> hosts = e.get_all_hosts();
  for (sg4::Host const* host : hosts)
    XBT_INFO("Host '%s' runs at %.0f flops/s", host->get_cname(), host->get_speed());

  sg4::Actor::create("alice", host1, alice);
  sg4::Actor::create("bob", host1, bob)->set_property("SomeProp", "SomeValue");
  sg4::Actor::create("carole", host2, carole);
  sg4::Actor::create("david", host2, david);

  e.run();

  return 0;
}

Element filtering

Retrieving the netzones matching given criteria

Shows how to filter the cluster netzones.

View examples/cpp/routing-get-clusters/s4u-routing-get-clusters.cpp

Download s4u-routing-get-clusters.cpp

/* Copyright (c) 2009-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/kernel/routing/ClusterZone.hpp"
#include "simgrid/kernel/routing/DragonflyZone.hpp"
#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  std::vector<simgrid::kernel::routing::ClusterZone*> clusters =
      e.get_filtered_netzones<simgrid::kernel::routing::ClusterZone>();

  for (auto const* c : clusters) {
    XBT_INFO("%s", c->get_cname());
    std::vector<sg4::Host*> hosts = c->get_all_hosts();
    for (auto const* h : hosts)
      XBT_INFO("   %s", h->get_cname());
  }

  std::vector<simgrid::kernel::routing::DragonflyZone*> dragonfly_clusters =
      e.get_filtered_netzones<simgrid::kernel::routing::DragonflyZone>();

  for (auto const* d : dragonfly_clusters) {
    XBT_INFO("%s' dragonfly topology:", d->get_cname());
    for (size_t i = 0; i < d->get_host_count(); i++) {
      const simgrid::kernel::routing::DragonflyZone::Coords coords = d->rankId_to_coords(i);
      XBT_INFO("   %zu: (%lu, %lu, %lu, %lu)", i, coords.group, coords.chassis, coords.blade, coords.node);
    }
  }

  return 0;
}

Retrieving the list of hosts matching given criteria

Shows how to filter the actors that match given criteria.

View examples/cpp/engine-filtering/s4u-engine-filtering.cpp

Download s4u-engine-filtering.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/s4u.hpp>

/* This example shows how to use sg4::Engine::get_filtered_hosts() to retrieve
 * all hosts that match a given criteria. This criteria can be specified either with:
 *   - an inlined callback
 *   - a boolean function, such as filter_speed_more_than_50Mf() below
 *   - a functor (= function object), that can either be stateless such as filter::SingleCore below, or that can save
 *     state such as filter::FrequencyChanged below
 *
 * This file provides examples for each of these categories. You should implement your own filters in your code.
 */

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_engine_filtering, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

namespace filter {
/* First example of thing that we can use as a filtering criteria: a simple boolean function */
static bool filter_speed_more_than_50Mf(const sg4::Host* host)
{
  return host->get_speed() > 50E6;
}

/* Second kind of thing that we can use as a filtering criteria: a functor (=function object).
 * This one is a bit stupid: it's a lot of boilerplate over a dummy boolean function.
 */
class SingleCore {
public:
  bool operator()(const sg4::Host* host) const { return host->get_core_count() == 1; }
};

/* This functor is a bit more complex, as it saves the current state when created.
 * Then, it allows one to easily retrieve the hosts which frequency changed since the functor creation.
 */
class FrequencyChanged {
  std::map<sg4::Host*, unsigned long> host_list;

public:
  explicit FrequencyChanged(const sg4::Engine& e)
  {
    std::vector<sg4::Host*> list = e.get_all_hosts();
    for (auto& host : list) {
      host_list.insert({host, host->get_pstate()});
    }
  }

  bool operator()(sg4::Host* host) { return host->get_pstate() != host_list.at(host); }

  unsigned long get_old_speed_state(sg4::Host* host) { return host_list.at(host); }
};
}
int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  e.load_platform(argv[1]);

  /* Use a lambda function to filter hosts: We only want multicore hosts */
  XBT_INFO("Hosts currently registered with this engine: %zu", e.get_host_count());
  std::vector<sg4::Host*> list = e.get_filtered_hosts([](const sg4::Host* host) { return host->get_core_count() > 1; });

  for (auto& host : list)
    XBT_INFO("The following hosts have more than one core: %s", host->get_cname());

  xbt_assert(list.size() == 1);

  /* Use a function object (functor) without memory */
  list = e.get_filtered_hosts(filter::SingleCore());

  for (auto& host : list)
    XBT_INFO("The following hosts are SingleCore: %s", host->get_cname());

  /* Use a function object that uses memory to filter */
  XBT_INFO("A simple example: Let's retrieve all hosts that changed their frequency");
  filter::FrequencyChanged filter(e);
  e.host_by_name("MyHost2")->set_pstate(2);
  list = e.get_filtered_hosts(filter);

  for (auto& host : list)
    XBT_INFO("The following hosts changed their frequency: %s (from %.1ff to %.1ff)", host->get_cname(),
             host->get_pstate_speed(filter.get_old_speed_state(host)), host->get_speed());

  /* You can also just use any regular function (namespaced on need) to filter  */
  list = e.get_filtered_hosts(filter::filter_speed_more_than_50Mf);

  for (auto& host : list)
    XBT_INFO("The following hosts have a frequency > 50Mf: %s", host->get_cname());

  return 0;
}

Profiles

Specifying state profiles

Shows how to specify when the resources must be turned off and on again, and how to react to such failures in your code. See also Modeling churn (e.g., in P2P), this example on how to react to communication failures, and that example on how to react to host failures.

View examples/cpp/platform-failures/s4u-platform-failures.cpp

Download s4u-platform-failures.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example shows how to work with the state profile of a host or a link,
 * specifying when the resource must be turned on or off.
 *
 * To set such a profile, the first way is to use a file in the XML, while the second is to use the programmatic
 * interface, as exemplified in the main() below. Once this profile is in place, the resource will automatically
 * be turned on and off.
 *
 * The actors running on a host that is turned off are forcefully killed
 * once their on_exit callbacks are executed. They cannot avoid this fate.
 * Since we specified on_failure="RESTART" for each actors in the XML file,
 * they will be automatically restarted when the host starts again.
 *
 * Communications using failed links will .. fail.
 */

#include "simgrid/kernel/ProfileBuilder.hpp"
#include "simgrid/s4u.hpp"

namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");

static void master(std::vector<std::string> args)
{
  xbt_assert(args.size() == 5, "Expecting one parameter");

  sg4::Mailbox* mailbox;
  long number_of_tasks = std::stol(args[1]);
  double comp_size     = std::stod(args[2]);
  long comm_size       = std::stol(args[3]);
  long workers_count   = std::stol(args[4]);

  XBT_INFO("Got %ld workers and %ld tasks to process", workers_count, number_of_tasks);

  for (int i = 0; i < number_of_tasks; i++) {
    mailbox         = sg4::Mailbox::by_name("worker-" + std::to_string(i % workers_count));
    auto* payload   = new double(comp_size);
    try {
      XBT_INFO("Send a message to %s", mailbox->get_cname());
      mailbox->put(payload, comm_size, 10.0);
      XBT_INFO("Send to %s completed", mailbox->get_cname());
    } catch (const simgrid::TimeoutException&) {
      delete payload;
      XBT_INFO("Mmh. Got timeouted while speaking to '%s'. Nevermind. Let's keep going!", mailbox->get_cname());
    } catch (const simgrid::NetworkFailureException&) {
      delete payload;
      XBT_INFO("Mmh. The communication with '%s' failed. Nevermind. Let's keep going!", mailbox->get_cname());
    }
  }

  XBT_INFO("All tasks have been dispatched. Let's tell everybody the computation is over.");
  for (int i = 0; i < workers_count; i++) {
    /* - Eventually tell all the workers to stop by sending a "finalize" task */
    mailbox         = sg4::Mailbox::by_name("worker-" + std::to_string(i));
    auto* payload   = new double(-1.0);
    try {
      mailbox->put(payload, 0, 1.0);
    } catch (const simgrid::TimeoutException&) {
      delete payload;
      XBT_INFO("Mmh. Got timeouted while speaking to '%s'. Nevermind. Let's keep going!", mailbox->get_cname());
    } catch (const simgrid::NetworkFailureException&) {
      delete payload;
      XBT_INFO("Mmh. Something went wrong with '%s'. Nevermind. Let's keep going!", mailbox->get_cname());
    }
  }

  XBT_INFO("Goodbye now!");
}

static void worker(std::vector<std::string> args)
{
  xbt_assert(args.size() == 2, "Expecting one parameter");
  long id               = std::stol(args[1]);
  sg4::Mailbox* mailbox = sg4::Mailbox::by_name("worker-" + std::to_string(id));
  while (true) {
    try {
      XBT_INFO("Waiting a message on %s", mailbox->get_cname());
      auto payload = mailbox->get_unique<double>();
      xbt_assert(payload != nullptr, "mailbox->get() failed");
      double comp_size = *payload;
      if (comp_size < 0) { /* - Exit when -1.0 is received */
        XBT_INFO("I'm done. See you!");
        break;
      }
      /*  - Otherwise, process the task */
      XBT_INFO("Start execution...");
      sg4::this_actor::execute(comp_size);
      XBT_INFO("Execution complete.");
    } catch (const simgrid::NetworkFailureException&) {
      XBT_INFO("Mmh. Something went wrong. Nevermind. Let's keep going!");
    }
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  // This is how to attach a profile to an host that is created from the XML file.
  // This should be done before calling load_platform(), as the on_creation() event is fired when loading the platform.
  // You can never set a new profile to a resource that already have one.
  sg4::Host::on_creation_cb([](sg4::Host& h) {
    if (h.get_name() == "Bourrassa") {
      h.set_state_profile(simgrid::kernel::profile::ProfileBuilder::from_string("bourassa_profile", "67 0\n70 1\n", 0));
    }
  });
  e.load_platform(argv[1]);

  e.register_function("master", master);
  e.register_function("worker", worker);
  e.load_deployment(argv[2]);

  // Add a new host programatically, and attach a state profile to it
  auto* root     = e.get_netzone_root();
  auto* lilibeth = root->create_host("Lilibeth", 1e15);
  auto link      = e.link_by_name("10");
  root->add_route(e.host_by_name("Tremblay"), lilibeth, {link});
  lilibeth->set_state_profile(simgrid::kernel::profile::ProfileBuilder::from_string("lilibeth_profile", R"(
4 0
5 1
)",
                                                                                    10));
  lilibeth->seal();

  // Create an actor on that new host, to monitor its own state
  auto actor = sg4::Actor::create("sleeper", lilibeth, []() {
    XBT_INFO("Start sleeping...");
    sg4::this_actor::sleep_for(1);
    XBT_INFO("done sleeping.");
  });
  actor->set_auto_restart(true);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());
  return 0;
}

Specifying speed profiles

Shows how to specify an external load to resources, variating their peak speed over time.

View examples/cpp/platform-profile/s4u-platform-profile.cpp

Download s4u-platform-profile.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/kernel/ProfileBuilder.hpp"
#include "simgrid/s4u.hpp"

/* This example demonstrates how to attach a profile to a host or a link, to specify external changes to the resource
 * speed. The first way to do so is to use a file in the XML, while the second is to use the programmatic interface. */

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_platform_profile, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void watcher()
{
  const auto* jupiter  = sg4::Host::by_name("Jupiter");
  const auto* fafard   = sg4::Host::by_name("Fafard");
  const auto* lilibeth = sg4::Host::by_name("Lilibeth");
  const auto* link1    = sg4::Link::by_name("1");
  const auto* link2    = sg4::Link::by_name("2");

  std::vector<sg4::Link*> links;
  double lat = 0;
  jupiter->route_to(fafard, links, &lat);

  std::string path;
  for (const auto* l : links)
    path += std::string(path.empty() ? "" : ", ") + "link '" + l->get_name() + "'";
  XBT_INFO("Path from Jupiter to Fafard: %s (latency: %fs).", path.c_str(), lat);

  for (int i = 0; i < 10; i++) {
    XBT_INFO("Fafard: %.0fMflops, Jupiter: %4.0fMflops, Lilibeth: %3.1fMflops, Link1: (%.2fMB/s %.0fms), Link2: "
             "(%.2fMB/s %.0fms)",
             fafard->get_speed() * fafard->get_available_speed() / 1000000,
             jupiter->get_speed() * jupiter->get_available_speed() / 1000000,
             lilibeth->get_speed() * lilibeth->get_available_speed() / 1000000, link1->get_bandwidth() / 1000,
             link1->get_latency() * 1000, link2->get_bandwidth() / 1000, link2->get_latency() * 1000);
    sg4::this_actor::sleep_for(1);
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  xbt_assert(argc > 1, "Usage: %s platform_file\n\tExample: %s platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  // Add a new host programmatically, and attach a simple speed profile to it (alternate between full and half speed
  // every two seconds
  e.get_netzone_root()
      ->create_host("Lilibeth", 25e6)
      ->set_speed_profile(simgrid::kernel::profile::ProfileBuilder::from_string("lilibeth_profile", R"(
0 1.0
2 0.5
)",
                                                                                4))
      ->seal();

  // Add a watcher of the changes
  sg4::Actor::create("watcher", e.host_by_name("Fafard"), watcher);

  e.run();

  return 0;
}

Modifying the platform

Serializing communications

This example shows how to limit the amount of communications going through a given link. It is very similar to the other asynchronous communication examples, but messages get serialized by the platform. Without this call to Link::set_concurrency_limit(2), all messages would be received at the exact same timestamp since they are initiated at the same instant and are of the same size. But with this extra configuration to the link, at most 2 messages can travel through the link at the same time.

See also simgrid::s4u::Link::set_concurrency_limit().

View examples/cpp/platform-comm-serialize/s4u-platform-comm-serialize.cpp

Download s4u-platform-comm-serialize.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This example shows how to serialize a set of communications going through a link using Link::set_concurrency_limit()
 *
 * This example is very similar to the other asynchronous communication examples, but messages get serialized by the platform.
 * Without this call to Link::set_concurrency_limit(2) in main, all messages would be received at the exact same timestamp since
 * they are initiated at the same instant and are of the same size. But with this extra configuration to the link, at most 2
 * messages can travel through the link at the same time.
 */

#include "simgrid/s4u.hpp"
#include <string>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_async_serialize, "Messages specific for this s4u example");

class Sender {
  int messages_count; /* - number of messages */
  int msg_size;       /* - message size in bytes */

public:
  explicit Sender(int size, int count) : messages_count(count), msg_size(size) {}
  void operator()() const
  {
    // sphinx-doc: init-begin (this line helps the doc to build; ignore it)
    /* ActivitySet in which we store all ongoing communications */
    sg4::ActivitySet pending_comms;

    /* Mailbox to use */
    sg4::Mailbox* mbox = sg4::Mailbox::by_name("receiver");
    // sphinx-doc: init-end

    /* Start dispatching all messages to receiver */
    for (int i = 0; i < messages_count; i++) {
      std::string msg_content = "Message " + std::to_string(i);
      // Copy the data we send: the 'msg_content' variable is not a stable storage location.
      // It will be destroyed when this actor leaves the loop, ie before the receiver gets it
      auto* payload = new std::string(msg_content);

      XBT_INFO("Send '%s' to '%s'", msg_content.c_str(), mbox->get_cname());

      /* Create a communication representing the ongoing communication, and store it in pending_comms */
      sg4::CommPtr comm = mbox->put_async(payload, msg_size);
      pending_comms.push(comm);
    }

    XBT_INFO("Done dispatching all messages");

    /* Now that all message exchanges were initiated, wait for their completion in one single call */
    pending_comms.wait_all();
    // sphinx-doc: put-end

    XBT_INFO("Goodbye now!");
  }
};

/* Receiver actor expects 1 argument: number of messages to be received */
class Receiver {
  sg4::Mailbox* mbox;
  int messages_count = 10; /* - number of messages */

public:
  explicit Receiver(int count) : messages_count(count) { mbox = sg4::Mailbox::by_name("receiver"); }
  void operator()()
  {
    /* Where we store all incoming msgs */
    std::unordered_map<sg4::CommPtr, std::shared_ptr<std::string*>> pending_msgs;
    sg4::ActivitySet pending_comms;

    XBT_INFO("Wait for %d messages asynchronously", messages_count);
    for (int i = 0; i < messages_count; i++) {
      auto msg  = std::make_shared<std::string*>();
      auto comm = mbox->get_async<std::string>(msg.get());
      pending_comms.push(comm);
      pending_msgs.insert({comm, msg});
    }

    while (not pending_comms.empty()) {
      auto completed_one = pending_comms.wait_any();
      if (completed_one != nullptr){
        auto comm = boost::dynamic_pointer_cast<sg4::Comm>(completed_one);
        auto msg = *pending_msgs[comm];
        XBT_INFO("I got '%s'.", msg->c_str());
        /* cleanup memory and remove from map */
        delete msg;
        pending_msgs.erase(comm);
      }
    }
  }
};

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  /* Creates the platform
   *  ________                 __________
   * | Sender |===============| Receiver |
   * |________|    Link1      |__________|
   */
  auto* zone     = sg4::create_full_zone("Zone1");
  auto* sender   = zone->create_host("sender", 1)->seal();
  auto* receiver = zone->create_host("receiver", 1)->seal();

  /* create split-duplex link1 (UP/DOWN), limiting the number of concurrent flows in it for 2 */
  const auto* link =
      zone->create_split_duplex_link("link1", 10e9)->set_latency(10e-6)->set_concurrency_limit(2)->seal();

  /* create routes between nodes */
  zone->add_route(sender, receiver, {link});
  zone->seal();

  /* create actors Sender/Receiver */
  sg4::Actor::create("receiver", receiver, Receiver(10));
  sg4::Actor::create("sender", sender, Sender(1e6, 10));

  e.run();

  return 0;
}

Energy Simulation

Setup

Describing the energy profiles in the platform

The first platform file contains the energy profile of each link and host for a wired network, which is necessary to get energy consumption predictions. The second platform file is the equivalent for a wireless network. As usual, you should not trust our example, and you should strive to double-check that your instantiation matches your target platform.

View examples/platforms/energy_platform.xml

Download energy_platform.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE platform SYSTEM "https://simgrid.org/simgrid.dtd">
<platform version="4.1">
  <zone id="AS0" routing="Full">
    <!-- Multiple pstate processor capacities can be defined as a list of powers specified for a given host -->
    <!-- Attribute 'pstate' specifies the initially selected pstate (here, the lowest pstate corresponds to the highest
         processor speed) -->
    <host core="4" id="MyHost1" pstate="0" speed="100.0Mf,50.0Mf,20.0Mf">
      <!--  List of Idle:Epsilon:AllCores (in Watts) corresponding to the speed consumed when the processor is idle,
            when all cores have a tiny epsilon load, and when all cores are fully loaded -->
      <!--  The list must contain one energetic profile for each previously defined pstate-->
      <prop id="wattage_per_state" value="100.0:93.33333333333333:200.0, 93.0:90.0:170.0, 90.0:90.0:150.0" />
      <prop id="wattage_off" value="10" />
    </host>

    <host core="1" id="MyHost2" pstate="0" speed="100.0Mf,50.0Mf,20.0Mf">
      <!-- This host is mono-core and its consumption is either idle or full load (Epsilon=AllCores) -->
      <prop id="wattage_per_state" value="100.0:200.0:200.0, 93.0:170.0:170.0, 90.0:150.0:150.0" />
      <prop id="wattage_off" value="10" />
    </host>

    <host core="1" id="MyHost3" pstate="0" speed="100.0Mf,50.0Mf,20.0Mf">
      <!-- This host is mono-core and its consumption is either idle or full load (Epsilon=AllCores) -->
      <prop id="wattage_per_state" value="100.0:200.0:200.0, 93.0:170.0:170.0, 90.0:150.0:150.0" />
      <prop id="wattage_off" value="10" />
    </host>

    <link bandwidth="100kBps" id="bus" latency="0" sharing_policy="SHARED">
<!--   REALISTIC VALUES                    <prop id="wattage_range" value="10.3581:10.7479" /> -->
<!--  IREALISTIC VALUES FOR THE TEST -->   <prop id="wattage_range" value="1:3" />
    </link>
    <route dst="MyHost2" src="MyHost1">
      <link_ctn id="bus" />
    </route>
    <route dst="MyHost3" src="MyHost1">
      <link_ctn id="bus" />
    </route>
    <route dst="MyHost3" src="MyHost2">
      <link_ctn id="bus" />
    </route>
  </zone>
</platform>

View examples/platforms/wifi_energy.xml

Download wifi_energy.xml

<?xml version = '1.0'?>
<!DOCTYPE platform SYSTEM "https://simgrid.org/simgrid.dtd">
<platform version = "4.1">
  <zone id="WIFI zone" routing = "Wifi">
    <prop id = "access_point" value = "router" />

    <host id = "Station 1" speed = "100.0Mf,50.0Mf,20.0Mf" />
    <host id = "Station 2" speed = "100.0Mf,50.0Mf,20.0Mf" />

    <router id = "router"/>

    <link id = "AP1" sharing_policy = "WIFI" bandwidth = "54Mbps" latency="0ms">
      <prop id = "wifi_watt_values" value = "0:1:1:0"/>
      <prop id = "control_duration" value = "0"/>
    </link>
  </zone>
</platform>

Usage

CPU energy consumption

This example shows how to retrieve the amount of energy consumed by the CPU during computations, and the impact of the pstate.

View examples/cpp/energy-exec/s4u-energy-exec.cpp

Download s4u-energy-exec.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include "simgrid/plugins/energy.h"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void dvfs()
{
  sg4::Host* host1 = sg4::Host::by_name("MyHost1");
  sg4::Host* host2 = sg4::Host::by_name("MyHost2");

  XBT_INFO("Energetic profile: %s", host1->get_property("wattage_per_state"));
  XBT_INFO("Initial peak speed=%.0E flop/s; Energy dissipated =%.0E J", host1->get_speed(),
           sg_host_get_consumed_energy(host1));

  double start = sg4::Engine::get_clock();
  XBT_INFO("Sleep for 10 seconds");
  sg4::this_actor::sleep_for(10);
  XBT_INFO("Done sleeping (duration: %.2f s). Current peak speed=%.0E; Energy dissipated=%.2f J",
           sg4::Engine::get_clock() - start, host1->get_speed(), sg_host_get_consumed_energy(host1));

  // Execute something
  start             = sg4::Engine::get_clock();
  double flopAmount = 100E6;
  XBT_INFO("Run a computation of %.0E flops", flopAmount);
  sg4::this_actor::execute(flopAmount);
  XBT_INFO(
      "Computation done (duration: %.2f s). Current peak speed=%.0E flop/s; Current consumption: from %.0fW to %.0fW"
      " depending on load; Energy dissipated=%.0f J",
      sg4::Engine::get_clock() - start, host1->get_speed(), sg_host_get_wattmin_at(host1, host1->get_pstate()),
      sg_host_get_wattmax_at(host1, host1->get_pstate()), sg_host_get_consumed_energy(host1));

  // ========= Change power peak =========
  int pstate = 2;
  host1->set_pstate(pstate);
  XBT_INFO("========= Requesting pstate %d (speed should be of %.0E flop/s and is of %.0E flop/s)", pstate,
           host1->get_pstate_speed(pstate), host1->get_speed());

  // Run another computation
  start = sg4::Engine::get_clock();
  XBT_INFO("Run a computation of %.0E flops", flopAmount);
  sg4::this_actor::execute(flopAmount);
  XBT_INFO("Computation done (duration: %.2f s). Current peak speed=%.0E flop/s; Energy dissipated=%.0f J",
           sg4::Engine::get_clock() - start, host1->get_speed(), sg_host_get_consumed_energy(host1));

  start = sg4::Engine::get_clock();
  XBT_INFO("Sleep for 4 seconds");
  sg4::this_actor::sleep_for(4);
  XBT_INFO("Done sleeping (duration: %.2f s). Current peak speed=%.0E flop/s; Energy dissipated=%.0f J",
           sg4::Engine::get_clock() - start, host1->get_speed(), sg_host_get_consumed_energy(host1));

  // =========== Turn the other host off ==========
  XBT_INFO("Turning MyHost2 off, and sleeping another 10 seconds. MyHost2 dissipated %.0f J so far.",
           sg_host_get_consumed_energy(host2));
  host2->turn_off();
  start = sg4::Engine::get_clock();
  sg4::this_actor::sleep_for(10);
  XBT_INFO("Done sleeping (duration: %.2f s). Current peak speed=%.0E flop/s; Energy dissipated=%.0f J",
           sg4::Engine::get_clock() - start, host1->get_speed(), sg_host_get_consumed_energy(host1));
}

int main(int argc, char* argv[])
{
  sg_host_energy_plugin_init();
  sg4::Engine e(&argc, argv);

  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/energy_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);
  sg4::Actor::create("dvfs_test", e.host_by_name("MyHost1"), dvfs);

  e.run();

  XBT_INFO("End of simulation.");

  return 0;
}

Virtual machines consumption

This example is very similar to the previous one, adding VMs to the picture.

View examples/cpp/energy-vm/s4u-energy-vm.cpp

Download s4u-energy-vm.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include "simgrid/plugins/energy.h"
#include "simgrid/s4u/VirtualMachine.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(energy_vm, "Messages of this example");
namespace sg4 = simgrid::s4u;

static void executor()
{
  sg4::this_actor::execute(300E6);
  XBT_INFO("This worker is done.");
}

static void dvfs()
{
  sg4::Host* host1 = sg4::Host::by_name("MyHost1");
  sg4::Host* host2 = sg4::Host::by_name("MyHost2");
  sg4::Host* host3 = sg4::Host::by_name("MyHost3");

  /* Host 1 */
  XBT_INFO("Creating and starting two VMs");
  auto* vm_host1 = host1->create_vm("vm_host1", 1);
  vm_host1->start();
  auto* vm_host2 = host2->create_vm("vm_host2", 1);
  vm_host2->start();

  XBT_INFO("Create two activities on Host1: both inside a VM");
  sg4::Actor::create("p11", vm_host1, executor);
  sg4::Actor::create("p12", vm_host1, executor);

  XBT_INFO("Create two activities on Host2: one inside a VM, the other directly on the host");
  sg4::Actor::create("p21", vm_host2, executor);
  sg4::Actor::create("p22", host2, executor);

  XBT_INFO("Create two activities on Host3: both directly on the host");
  sg4::Actor::create("p31", host3, executor);
  sg4::Actor::create("p32", host3, executor);

  XBT_INFO("Wait 5 seconds. The activities are still running (they run for 3 seconds, but 2 activities are co-located, "
           "so they run for 6 seconds)");
  sg4::this_actor::sleep_for(5);
  XBT_INFO("Wait another 5 seconds. The activities stop at some point in between");
  sg4::this_actor::sleep_for(5);

  vm_host1->destroy();
  vm_host2->destroy();
}

int main(int argc, char* argv[])
{
  sg_host_energy_plugin_init();
  sg4::Engine e(&argc, argv);

  xbt_assert(argc > 1, "Usage: %s platform_file\n\tExample: %s ../platforms/energy_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  sg4::Actor::create("dvfs", e.host_by_name("MyHost1"), dvfs);

  e.run();

  XBT_INFO("Total simulation time: %.2f; Host2 and Host3 must have the exact same energy consumption; Host1 is "
           "multi-core and will differ.",
           sg4::Engine::get_clock());

  return 0;
}

Wired network energy consumption

This example shows how to retrieve and display the energy consumed by the wired network during communications.

View examples/cpp/energy-link/s4u-energy-link.cpp

Download s4u-energy-link.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/plugins/energy.h"
#include "xbt/log.h"
#include "xbt/random.hpp"
#include <simgrid/s4u.hpp>

/* Parameters of the random generation of the flow size */
static const int min_size = 1e6;
static const int max_size = 1e9;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_app_energyconsumption, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void sender(std::vector<std::string> args)
{
  xbt_assert(args.size() == 2, "The master function expects 2 arguments.");
  int flow_amount = std::stoi(args.at(0));
  long comm_size  = std::stol(args.at(1));
  XBT_INFO("Send %ld bytes, in %d flows", comm_size, flow_amount);

  sg4::Mailbox* mailbox = sg4::Mailbox::by_name("message");

  /* Sleep a while before starting the example */
  sg4::this_actor::sleep_for(10);

  if (flow_amount == 1) {
    /* - Send the task to the @ref worker */
    char* payload = bprintf("%ld", comm_size);
    mailbox->put(payload, comm_size);
  } else {
    // Start all comms in parallel, and wait for all completions in one shot
    sg4::ActivitySet comms;
    for (int i = 0; i < flow_amount; i++)
      comms.push(mailbox->put_async(bprintf("%d", i), comm_size));
    comms.wait_all();
  }
  XBT_INFO("sender done.");
}

static void receiver(std::vector<std::string> args)
{
  int flow_amount = std::stoi(args.at(0));

  XBT_INFO("Receiving %d flows ...", flow_amount);

  sg4::Mailbox* mailbox = sg4::Mailbox::by_name("message");

  if (flow_amount == 1) {
    char* res = mailbox->get<char>();
    xbt_free(res);
  } else {
    std::vector<char*> data(flow_amount);

    // Start all comms in parallel, and wait for their completion in one shot
    sg4::ActivitySet comms;
    for (int i = 0; i < flow_amount; i++)
      comms.push(mailbox->get_async<char>(&data[i]));

    comms.wait_all();
    for (int i = 0; i < flow_amount; i++)
      xbt_free(data[i]);
  }
  XBT_INFO("receiver done.");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  XBT_INFO("Activating the SimGrid link energy plugin");
  sg_link_energy_plugin_init();

  xbt_assert(argc > 1, "\nUsage: %s platform_file [flowCount [datasize]]\n"
                       "\tExample: %s s4uplatform.xml \n",
             argv[0], argv[0]);
  e.load_platform(argv[1]);

  /* prepare to launch the actors */
  std::vector<std::string> argSender;
  std::vector<std::string> argReceiver;
  if (argc > 2) {
    argSender.emplace_back(argv[2]); // Take the amount of flows from the command line
    argReceiver.emplace_back(argv[2]);
  } else {
    argSender.emplace_back("1"); // Default value
    argReceiver.emplace_back("1");
  }

  if (argc > 3) {
    if (strcmp(argv[3], "random") == 0) { // We're asked to get a random size
      std::string size = std::to_string(simgrid::xbt::random::uniform_int(min_size, max_size));
      argSender.push_back(size);
    } else {                        // Not "random" ? Then it should be the size to use
      argSender.emplace_back(argv[3]); // Take the datasize from the command line
    }
  } else { // No parameter at all? Then use the default value
    argSender.emplace_back("25000");
  }
  sg4::Actor::create("sender", e.host_by_name("MyHost1"), sender, argSender);
  sg4::Actor::create("receiver", e.host_by_name("MyHost2"), receiver, argReceiver);

  /* And now, launch the simulation */
  e.run();

  return 0;
}

WiFi network energy consumption

This example shows how to retrieve and display the energy consumed by the wireless network during communications.

View examples/cpp/energy-wifi/s4u-energy-wifi.cpp

Download s4u-energy-wifi.cpp

/* Copyright (c) 2020-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/**
 * Test the wifi energy plugin
 * Desactivate cross-factor to get round values
 * Launch with: ./test test_platform_2STA.xml --cfg=plugin:link_energy_wifi --cfg=network/crosstraffic:0
 */

#include "simgrid/plugins/energy.h"
#include "simgrid/s4u/Activity.hpp"
#include "simgrid/s4u/Actor.hpp"
#include "simgrid/s4u/Engine.hpp"
#include "simgrid/s4u/Host.hpp"
#include "simgrid/s4u/Link.hpp"
#include "simgrid/s4u/Mailbox.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(test_wifi, "Wifi energy demo");
namespace sg4 = simgrid::s4u;

static void sender()
{
  // start sending after 5 seconds
  sg4::this_actor::sleep_until(5);

  std::string mbName = "MailBoxRCV";
  sg4::Mailbox* dst  = sg4::Mailbox::by_name(mbName);

  int size = 6750000;

  XBT_INFO("SENDING 1 msg of size %d to %s", size, mbName.c_str());
  static std::string message = "message";
  dst->put(&message, size);
  XBT_INFO("finished sending");
}

static void receiver()
{
  std::string mbName = "MailBoxRCV";
  XBT_INFO("RECEIVING on mb %s", mbName.c_str());
  sg4::Mailbox* myBox = sg4::Mailbox::by_name(mbName);
  myBox->get<std::string>();

  XBT_INFO("received all messages");
}

int main(int argc, char** argv)
{
  sg4::Engine engine(&argc, argv);
  sg_wifi_energy_plugin_init();
  engine.load_platform(argv[1]);

  // setup WiFi bandwidths
  const auto* l = engine.link_by_name("AP1");
  l->set_host_wifi_rate(engine.host_by_name("Station 1"), 0);
  l->set_host_wifi_rate(engine.host_by_name("Station 2"), 0);

  // create the two actors for the test
  sg4::Actor::create("act0", engine.host_by_name("Station 1"), sender);
  sg4::Actor::create("act1", engine.host_by_name("Station 2"), receiver);

  engine.run();

  return 0;
}

Modeling the shutdown and boot of hosts

Simple example of a model for the energy consumption during the host boot and shutdown periods.

View examples/platforms/energy_boot.xml

Download energy_boot.xml

<?xml version='1.0'?>
<!DOCTYPE platform SYSTEM "https://simgrid.org/simgrid.dtd">
<platform version="4.1">
  <zone  id="AS0"  routing="Full">

    <!-- Use the pstate mechanism to encode the boot/shutdown time and energy.
      -
      -  See the C++ file in the same directory for more information.
      -->

    <!-- pstate values:
      * 0-2: real pstates
         0: p1 100 MFlops/s, [idle: 95W -> full burning: 200W]
         1: p2  50 MFlops/s, [idle: 93W -> full burning: 170W]
         2: p3  20 MFlops/s, [idel: 90W -> full burning: 150W]

      * 3: booting up was measured to take 150s and 18000J.
           So we create a pstate 3 consuming 18000J/150s=120W, and a boot remains at this pstate for 150s.
           Speed is set at 0 flop/s so that nothing progresses during the boot.
      * 4: shutting down was measured to take 7 s and 770 J
           So we create a pstate 4 consuming 770J/7s=110W, and a shutdown remains at this pstate for 7s.

      Please note that even if these values are educated guesses, you should still challenge them.
      If you want a realistic simulation, you must use values coming from a real benchmark of your platform.
      -->

    <host id="MyHost1"             speed="100.0Mf,50.0Mf,20.0Mf,                    0f,0f" pstate="0" >
      <prop id="wattage_per_state" value="95.0:200.0,93.0:170.0,90.0:150.0,         120:120,110:110" />
      <prop id="wattage_off"       value="10" />
    </host>
    <host id="MyHost2"             speed="100.0Mf" >
      <prop id="wattage_per_state" value="100.0:200.0" />
      <prop id="wattage_off"       value="10" />
    </host>

    <link id="link1" bandwidth="100kBps" latency="0"/>
    <route src="MyHost1" dst="MyHost2">
      <link_ctn id="link1"/>
    </route>
  </zone>
</platform>

Tracing and Visualizing

Tracing can be activated by various configuration options which are illustrated in these examples. See also the full list of options related to tracing. The following introduces some option sets of interest that you may want to pass to your simulators.

Todo

These tracing examples should be integrated in the examples to not duplicate the C++ files. A full command line to see the result in the right tool (vite/FrameSoc) should be given along with some screenshots.

Platform Tracing

Basic example

This program is a toy example just loading the platform so that you can play with the platform visualization. Recommended options: --cfg=tracing:yes --cfg=tracing/categorized:yes

View examples/cpp/trace-platform/s4u-trace-platform.cpp

Download s4u-trace-platform.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This source code simply loads the platform. This is only useful to play
 * with the tracing module. See the tesh file to see how to generate the
 * traces.
 */

#include "simgrid/s4u.hpp"

int main(int argc, char* argv[])
{
  simgrid::s4u::Engine e(&argc, argv);
  e.load_platform(argv[1]);
  e.run();
  return 0;
}

Setting Categories

This example declares several tracing categories that are used to classify its tasks. When the program is executed, the tracing mechanism registers the resource utilization of hosts and links according to these categories. Recommended options: --cfg=tracing:yes --cfg=tracing/categorized:yes --cfg=tracing/uncategorized:yes

View examples/cpp/trace-categories/s4u-trace-categories.cpp

Download s4u-trace-categories.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This source code simply loads the platform. This is only useful to play
 * with the tracing module. See the tesh file to see how to generate the
 * traces.
 */

#include "simgrid/instr.h"
#include "simgrid/s4u.hpp"

namespace sg4 = simgrid::s4u;

struct Task {
  std::string name;
  std::string category;
  double flops;
  uint64_t bytes;
};

static void master()
{
  auto* mbox = sg4::Mailbox::by_name("master_mailbox");
  for (int i = 0; i < 10; i++) {
    Task task;
    if (i % 2)
      task = {"task_compute", "compute", 10000000, 0};
    else if (i % 3)
      task = {"task_request", "request", 10, 10};
    else
      task = {"task_data", "data", 10, 10000000};
    mbox->put(new Task(task), task.bytes);
  }
  Task finalize = {"finalize", "finalize", 0, 1000};
  mbox->put(new Task(finalize), finalize.bytes);
}

static void worker()
{
  auto* mbox = sg4::Mailbox::by_name("master_mailbox");
  while (true) {
    auto task = mbox->get_unique<Task>();
    if (task->name == "finalize") {
      break;
    }
    // creating task and setting its category
    sg4::this_actor::exec_init(task->flops)->set_name(task->name)->set_tracing_category(task->category)->wait();
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 1, "Usage: %s platform_file\n \tExample: %s small_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  // declaring user categories with RGB colors
  simgrid::instr::declare_tracing_category("compute", "1 0 0");  // red
  simgrid::instr::declare_tracing_category("request", "0 1 0");  // green
  simgrid::instr::declare_tracing_category("data", "0 0 1");     // blue
  simgrid::instr::declare_tracing_category("finalize", "0 0 0"); // black

  sg4::Actor::create("master", e.host_by_name("Tremblay"), master);
  sg4::Actor::create("worker", e.host_by_name("Fafard"), worker);

  e.run();
  return 0;
}

Master Workers tracing

This is an augmented version of our basic master/worker example using several tracing features. It traces resource usage, sorted out in several categories; Trace marks and user variables are also used. Recommended options: --cfg=tracing/categorized:yes --cfg=tracing/uncategorized:yes

View examples/cpp/trace-masterworkers/s4u-trace-masterworkers.cpp

Download s4u-trace-masterworkers.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/instr.h>
#include <simgrid/s4u.hpp>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_trace_masterworker, "Messages specific for this example");
namespace sg4 = simgrid::s4u;

struct Task {
  std::string name;
  std::string category;
  double flops;
};

static void master(std::vector<std::string> args)
{
  xbt_assert(args.size() > 4, "The master function expects at least 3 arguments");

  long tasks_count        = std::stol(args[1]);
  double compute_cost     = std::stod(args[2]);
  long communication_cost = std::stol(args[3]);
  size_t workers_count    = args.size() - 4;
  const auto& my_host     = sg4::this_actor::get_host()->get_name();
  auto* mailbox           = sg4::Mailbox::by_name("master_mailbox");

  XBT_DEBUG("Got %zu workers and %ld tasks to process", workers_count, tasks_count);

  // setting the variable "is_master" (previously declared) to value 1
  simgrid::instr::set_host_variable(my_host, "is_master", 1);

  simgrid::instr::mark("msmark", "start_send_tasks");
  for (int i = 0; i < tasks_count; i++) {
    // setting the variable "task_creation" to value i
    simgrid::instr::set_host_variable(my_host, "task_creation", i);

    // setting the category of task to "compute"
    Task task = {"task", "compute", compute_cost};
    mailbox->put(new Task(task), communication_cost);
  }
  simgrid::instr::mark("msmark", "finish_send_tasks");

  XBT_DEBUG("All tasks have been dispatched. Request all workers to stop.");
  for (unsigned int i = 0; i < workers_count; i++) {
    Task finalize = {"finalize", "finalize", 0};
    mailbox->put(new Task(finalize), 0);
  }
}

static void worker(std::vector<std::string> args)
{
  xbt_assert(args.size() == 1, "The worker expects no argument");

  const auto& my_host = sg4::this_actor::get_host()->get_name();
  auto* mailbox       = sg4::Mailbox::by_name("master_mailbox");

  simgrid::instr::set_host_variable(my_host, "is_worker", 1);
  simgrid::instr::set_host_variable(my_host, "task_computation", 0);

  while (true) {
    auto task = mailbox->get_unique<Task>();
    if (task->name == "finalize") {
      break;
    }
    // adding the task's cost to the variable "task_computation"
    simgrid::instr::add_host_variable(my_host, "task_computation", task->flops);
    sg4::this_actor::exec_init(task->flops)->set_name(task->name)->set_tracing_category(task->category)->wait();
  }

  XBT_DEBUG("Exiting now.");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 2, "Usage: %s platform_file deployment_file\n", argv[0]);

  e.load_platform(argv[1]);

  // declaring user variables
  simgrid::instr::declare_host_variable("is_worker");
  simgrid::instr::declare_host_variable("is_master");
  simgrid::instr::declare_host_variable("task_creation");
  simgrid::instr::declare_host_variable("task_computation");

  // declaring user markers and values
  simgrid::instr::declare_mark("msmark");
  simgrid::instr::declare_mark_value("msmark", "start_send_tasks");
  simgrid::instr::declare_mark_value("msmark", "finish_send_tasks");

  // declaring user categories with RGB colors (values from 0 to 1)
  simgrid::instr::declare_tracing_category("compute", "1 0 0");  // compute is red
  simgrid::instr::declare_tracing_category("finalize", "0 1 0"); // finalize is green
  // categories without user-defined colors receive random colors generated by the tracing system
  simgrid::instr::declare_tracing_category("request");
  simgrid::instr::declare_tracing_category("report");

  e.register_function("master", &master);
  e.register_function("worker", &worker);
  e.load_deployment(argv[2]);

  e.run();

  XBT_DEBUG("Simulation is over");

  if (const auto& categories = simgrid::instr::get_tracing_categories(); not categories.empty()) {
    XBT_INFO("Declared tracing categories:");
    for (const auto& category : categories)
      XBT_INFO("%s", category.c_str());
  }

  if (const auto& marks = simgrid::instr::get_marks(); not marks.empty()) {
    XBT_INFO("Declared marks:");
    for (const auto& mark : marks)
      XBT_INFO("%s", mark.c_str());
  }

  return 0;
}

Process migration tracing

This version is enhanced so that the process migrations can be displayed as arrows in a Gantt-chart visualization. Recommended options to that extend: --cfg=tracing:yes --cfg=tracing/actor:yes

View examples/cpp/trace-process-migration/s4u-trace-process-migration.cpp

Download s4u-trace-process-migration.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This source code simply loads the platform. This is only useful to play
 * with the tracing module. See the tesh file to see how to generate the
 * traces.
 */

#include "simgrid/instr.h"
#include "simgrid/s4u.hpp"
#include <memory>

namespace sg4 = simgrid::s4u;

/* The guy we will move from host to host. It move alone and then is moved by policeman back  */
static void emigrant()
{
  auto* mailbox = sg4::Mailbox::by_name("master_mailbox");

  sg4::this_actor::sleep_for(2);

  while (true) { // I am an eternal emigrant
    auto destination = mailbox->get_unique<std::string>();
    if (destination->empty())
      break; // there is no destination, die
    sg4::this_actor::set_host(sg4::Host::by_name(*destination));
    sg4::this_actor::sleep_for(2); // I am tired, have to sleep for 2 seconds
  }
}

static void policeman()
{
  // I am the master of emigrant actor,
  // I tell it where it must emigrate to.
  auto destinations = {"Tremblay", "Jupiter", "Fafard", "Ginette", "Bourassa", "Fafard", "Tremblay", "Ginette", ""};
  auto* mailbox     = sg4::Mailbox::by_name("master_mailbox");

  for (auto const& destination : destinations) {
    mailbox->put_init(new std::string(destination), 0)->set_tracing_category("migration_order")->wait();
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 1, "Usage: %s platform_file\n \tExample: %s small_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  simgrid::instr::declare_tracing_category("migration_order");

  sg4::Actor::create("emigrant", e.host_by_name("Fafard"), emigrant);
  sg4::Actor::create("policeman", e.host_by_name("Tremblay"), policeman);

  e.run();
  return 0;
}

Tracing user variables

You can also attach your own variables to any resource described in the platform file. The following examples illustrate this feature. They have to be run with the following options: --cfg=tracing:yes --cfg=tracing/platform:yes

Attaching variables to Hosts

View examples/cpp/trace-host-user-variables/s4u-trace-host-user-variables.cpp

Download s4u-trace-host-user-variables.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This source code simply loads the platform. This is only useful to play
 * with the tracing module. See the tesh file to see how to generate the
 * traces.
 */

#include "simgrid/instr.h"
#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void trace_fun()
{
  const auto host = sg4::this_actor::get_host()->get_name();

  // the hostname has an empty HDD with a capacity of 100000 (bytes)
  simgrid::instr::set_host_variable(host, "HDD_capacity", 100000);
  simgrid::instr::set_host_variable(host, "HDD_utilization", 0);

  for (int i = 0; i < 10; i++) {
    // create and execute a task just to make the simulated time advance
    sg4::this_actor::execute(1e4);

    // ADD: after the execution of this task, the HDD utilization increases by 100 (bytes)
    simgrid::instr::add_host_variable(host, "HDD_utilization", 100);
  }

  for (int i = 0; i < 10; i++) {
    // create and execute a task just to make the simulated time advance
    sg4::this_actor::execute(1e4);

    // SUB: after the execution of this task, the HDD utilization decreases by 100 (bytes)
    simgrid::instr::sub_host_variable(host, "HDD_utilization", 100);
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 1, "Usage: %s platform_file\n \tExample: %s small_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  // declaring user variables
  simgrid::instr::declare_host_variable("HDD_capacity");
  simgrid::instr::declare_host_variable("HDD_utilization", "1 0 0"); // red color

  sg4::Actor::create("master", e.host_by_name("Tremblay"), trace_fun);

  e.run();

  // get user declared variables
  if (const auto& host_variables = simgrid::instr::get_host_variables(); not host_variables.empty()) {
    XBT_INFO("Declared host variables:");
    for (const auto& var : host_variables)
      XBT_INFO("%s", var.c_str());
  }
  const auto& link_variables = simgrid::instr::get_link_variables();
  xbt_assert(link_variables.empty(), "Should not have any declared link variable!");

  return 0;
}

Attaching variables to network routes

It is often easier to update a given variable for all links of a given network path (identified by its source and destination hosts) instead of knowing the name of each specific link.

View examples/cpp/trace-route-user-variables/s4u-trace-route-user-variables.cpp

Download s4u-trace-route-user-variables.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* This source code simply loads the platform. This is only useful to play
 * with the tracing module. See the tesh file to see how to generate the
 * traces.
 */

#include "simgrid/instr.h"
#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void trace_fun()
{
  // Set initial values for the link user variables
  // This example uses source and destination where source and destination are the name of hosts in the platform file.
  // The functions will set/change the value of the variable for all links in the route between source and destination.

  // Set the Link_Capacity variable
  simgrid::instr::set_link_variable("Tremblay", "Bourassa", "Link_Capacity", 12.34);
  simgrid::instr::set_link_variable("Fafard", "Ginette", "Link_Capacity", 56.78);

  // Set the Link_Utilization variable
  simgrid::instr::set_link_variable("Tremblay", "Bourassa", "Link_Utilization", 1.2);
  simgrid::instr::set_link_variable("Fafard", "Ginette", "Link_Utilization", 3.4);

  // run the simulation, update my variables accordingly
  for (int i = 0; i < 10; i++) {
    sg4::this_actor::execute(1e6);

    // Add to link user variables
    simgrid::instr::add_link_variable("Tremblay", "Bourassa", "Link_Utilization", 5.6);
    simgrid::instr::add_link_variable("Fafard", "Ginette", "Link_Utilization", 7.8);
  }

  for (int i = 0; i < 10; i++) {
    sg4::this_actor::execute(1e6);

    // Subtract from link user variables
    simgrid::instr::sub_link_variable("Tremblay", "Bourassa", "Link_Utilization", 3.4);
    simgrid::instr::sub_link_variable("Fafard", "Ginette", "Link_Utilization", 5.6);
  }
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 1, "Usage: %s platform_file\n \tExample: %s small_platform.xml\n", argv[0], argv[0]);

  e.load_platform(argv[1]);

  // declaring link user variables (one without, another with an RGB color)
  simgrid::instr::declare_link_variable("Link_Capacity");
  simgrid::instr::declare_link_variable("Link_Utilization", "0.9 0.1 0.1");

  sg4::Actor::create("master", e.host_by_name("Tremblay"), trace_fun);
  sg4::Actor::create("worker", e.host_by_name("Tremblay"), trace_fun);
  sg4::Actor::create("worker", e.host_by_name("Jupiter"), trace_fun);
  sg4::Actor::create("worker", e.host_by_name("Fafard"), trace_fun);
  sg4::Actor::create("worker", e.host_by_name("Ginette"), trace_fun);
  sg4::Actor::create("worker", e.host_by_name("Bourassa"), trace_fun);

  e.run();
  return 0;
}

Larger SimGrid Exemplars

This section contains application examples that are somewhat larger than the previous examples.

Classical examples

Token ring

Shows how to implement a classical communication pattern, where a token is exchanged along a ring to reach every participant.

View examples/cpp/app-token-ring/s4u-app-token-ring.cpp

Download s4u-app-token-ring.cpp

/* Copyright (c) 2017-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <simgrid/s4u.hpp>
#include <algorithm>
#include <string>
#include <map>
#include <vector>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_app_token_ring, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

class RelayRunner {
public:
  explicit RelayRunner() = default;

  void operator()() const
  {
    size_t token_size = 1000000; /* The token is 1MB long*/
    sg4::Mailbox* my_mailbox;
    sg4::Mailbox* neighbor_mailbox;
    unsigned int rank = 0;

    try {
      rank = std::stoi(sg4::this_actor::get_name());
    } catch (const std::invalid_argument& ia) {
      throw std::invalid_argument(std::string("Actors of this example must have a numerical name, not ") + ia.what());
    }
    my_mailbox = sg4::Mailbox::by_name(std::to_string(rank));
    if (rank + 1 == sg4::Engine::get_instance()->get_host_count())
      /* The last actor sends the token back to rank 0 */
      neighbor_mailbox = sg4::Mailbox::by_name("0");
    else
      /* The others actors send to their right neighbor (rank+1) */
      neighbor_mailbox = sg4::Mailbox::by_name(std::to_string(rank + 1));

    if (rank == 0) {
      /* The root actor (rank 0) first sends the token then waits to receive it back */
      XBT_INFO("Host \"%u\" send 'Token' to Host \"%s\"", rank, neighbor_mailbox->get_cname());
      std::string msg = "Token";
      neighbor_mailbox->put(&msg, token_size);
      const auto* res = my_mailbox->get<std::string>();
      XBT_INFO("Host \"%u\" received \"%s\"", rank, res->c_str());
    } else {
      auto* res = my_mailbox->get<std::string>();
      XBT_INFO("Host \"%u\" received \"%s\"", rank, res->c_str());
      XBT_INFO("Host \"%u\" send 'Token' to Host \"%s\"", rank, neighbor_mailbox->get_cname());
      neighbor_mailbox->put(res, token_size);
    }
  }
};

int main(int argc, char** argv)
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 1, "Usage: %s platform.xml\n", argv[0]);
  e.load_platform(argv[1]);

  XBT_INFO("Number of hosts '%zu'", e.get_host_count());
  int id = 0;
  std::vector<sg4::Host*> list = e.get_all_hosts();
  for (auto const& host : list) {
    /* - Give a unique rank to each host and create a @ref relay_runner actor on each */
    sg4::Actor::create((std::to_string(id)).c_str(), host, RelayRunner());
    id++;
  }
  e.run();
  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Master Workers

Another good old example, where one Master actor has a bunch of tasks to dispatch to a set of several Worker actors. This example is used in the SimGrid tutorial.

This example comes in two equivalent variants, one where the actors are specified as simple functions (which is easier to understand for newcomers) and one where the actors are specified as classes (which is more powerful for the users wanting to build their own projects upon the example).

View examples/cpp/app-masterworkers/s4u-app-masterworkers-class.cpp

Download s4u-app-masterworkers-class.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* ************************************************************************* */
/* Take this tutorial online: https://simgrid.org/doc/latest/Tutorial_Algorithms.html */
/* ************************************************************************* */

#include <simgrid/s4u.hpp>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_app_masterworker, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

class Master {
  long tasks_count      = 0;
  double compute_cost   = 0;
  long communicate_cost = 0;
  std::vector<sg4::Mailbox*> workers;

public:
  explicit Master(std::vector<std::string> args)
  {
    xbt_assert(args.size() > 4, "The master function expects 3 arguments plus the workers' names");

    tasks_count      = std::stol(args[1]);
    compute_cost     = std::stod(args[2]);
    communicate_cost = std::stol(args[3]);
    for (unsigned int i = 4; i < args.size(); i++)
      workers.push_back(sg4::Mailbox::by_name(args[i]));

    XBT_INFO("Got %zu workers and %ld tasks to process", workers.size(), tasks_count);
  }

  void operator()()
  {
    for (int i = 0; i < tasks_count; i++) { /* For each task to be executed: */
      /* - Select a worker in a round-robin way */
      sg4::Mailbox* mailbox = workers[i % workers.size()];

      /* - Send the computation amount to the worker */
      if (tasks_count < 10000 || (tasks_count < 100000 && i % 10000 == 0) || i % 100000 == 0)
        XBT_INFO("Sending task %d of %ld to mailbox '%s'", i, tasks_count, mailbox->get_cname());
      mailbox->put(new double(compute_cost), communicate_cost);
    }

    XBT_INFO("All tasks have been dispatched. Request all workers to stop.");
    for (unsigned int i = 0; i < workers.size(); i++) {
      /* The workers stop when receiving a negative compute_cost */
      sg4::Mailbox* mailbox = workers[i % workers.size()];
      mailbox->put(new double(-1.0), 0);
    }
  }
};

class Worker {
  sg4::Mailbox* mailbox = nullptr;

public:
  explicit Worker(std::vector<std::string> args)
  {
    xbt_assert(args.size() == 1, "The worker expects to not get any argument");

    mailbox = sg4::Mailbox::by_name(sg4::this_actor::get_host()->get_name());
  }

  void operator()()
  {
    double compute_cost;
    do {
      auto msg     = mailbox->get_unique<double>();
      compute_cost = *msg;

      if (compute_cost > 0) /* If compute_cost is valid, execute a computation of that cost */
        sg4::this_actor::execute(compute_cost);
    } while (compute_cost > 0); /* Stop when receiving an invalid compute_cost */

    XBT_INFO("Exiting now.");
  }
};

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 2, "Usage: %s platform_file deployment_file\n", argv[0]);

  /* Register the classes representing the actors */
  e.register_actor<Master>("master");
  e.register_actor<Worker>("worker");

  /* Load the platform description and then deploy the application */
  e.load_platform(argv[1]);
  e.load_deployment(argv[2]);

  /* Run the simulation */
  e.run();

  XBT_INFO("Simulation is over");

  return 0;
}

View examples/cpp/app-masterworkers/s4u-app-masterworkers-fun.cpp

Download s4u-app-masterworkers-fun.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/* ************************************************************************* */
/* Take this tutorial online: https://simgrid.org/doc/latest/Tutorial_Algorithms.html */
/* ************************************************************************* */

#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_app_masterworker, "Messages specific for this example");

// master-begin
static void master(std::vector<std::string> args)
{
  xbt_assert(args.size() > 4, "The master function expects at least 3 arguments");

  long tasks_count        = std::stol(args[1]);
  double compute_cost     = std::stod(args[2]);
  long communication_cost = std::stol(args[3]);
  std::vector<sg4::Mailbox*> workers;
  for (unsigned int i = 4; i < args.size(); i++)
    workers.push_back(sg4::Mailbox::by_name(args[i]));

  XBT_INFO("Got %zu workers and %ld tasks to process", workers.size(), tasks_count);

  for (int i = 0; i < tasks_count; i++) { /* For each task to be executed: */
    /* - Select a worker in a round-robin way */
    sg4::Mailbox* mailbox = workers[i % workers.size()];

    /* - Send the computation cost to that worker */
    XBT_INFO("Sending task %d of %ld to mailbox '%s'", i, tasks_count, mailbox->get_cname());
    mailbox->put(new double(compute_cost), communication_cost);
  }

  XBT_INFO("All tasks have been dispatched. Request all workers to stop.");
  for (unsigned int i = 0; i < workers.size(); i++) {
    /* The workers stop when receiving a negative compute_cost */
    sg4::Mailbox* mailbox = workers[i % workers.size()];

    mailbox->put(new double(-1.0), 0);
  }
}
// master-end

// worker-begin
static void worker(std::vector<std::string> args)
{
  xbt_assert(args.size() == 1, "The worker expects no argument");

  const sg4::Host* my_host = sg4::this_actor::get_host();
  sg4::Mailbox* mailbox    = sg4::Mailbox::by_name(my_host->get_name());

  double compute_cost;
  do {
    auto msg     = mailbox->get_unique<double>();
    compute_cost = *msg;

    if (compute_cost > 0) /* If compute_cost is valid, execute a computation of that cost */
      sg4::this_actor::execute(compute_cost);
  } while (compute_cost > 0); /* Stop when receiving an invalid compute_cost */

  XBT_INFO("Exiting now.");
}
// worker-end

// main-begin
int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 2, "Usage: %s platform_file deployment_file\n", argv[0]);

  /* Register the functions representing the actors */
  e.register_function("master", &master);
  e.register_function("worker", &worker);

  /* Load the platform description and then deploy the application */
  e.load_platform(argv[1]);
  e.load_deployment(argv[2]);

  /* Run the simulation */
  e.run();

  XBT_INFO("Simulation is over");

  return 0;
}
// main-end

Data diffusion

Bit Torrent

Classical protocol for Peer-to-Peer data diffusion.

View examples/cpp/app-bittorrent/s4u-bittorrent.cpp

Download s4u-bittorrent.cpp

/* Copyright (c) 2012-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "s4u-bittorrent.hpp"
#include "s4u-peer.hpp"
#include "s4u-tracker.hpp"

int main(int argc, char* argv[])
{
  simgrid::s4u::Engine e(&argc, argv);

  /* Check the arguments */
  xbt_assert(argc > 2, "Usage: %s platform_file deployment_file", argv[0]);

  e.load_platform(argv[1]);

  e.register_actor<Tracker>("tracker");
  e.register_actor<Peer>("peer");
  e.load_deployment(argv[2]);

  e.run();

  return 0;
}

View examples/cpp/app-bittorrent/s4u-peer.cpp

Download s4u-peer.cpp

/* Copyright (c) 2012-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include <algorithm>
#include <array>
#include <climits>

#include "s4u-peer.hpp"
#include "s4u-tracker.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_bt_peer, "Messages specific for the peers");
namespace sg4 = simgrid::s4u;

/*
 * User parameters for transferred file data. For the test, the default values are :
 * File size: 10 pieces * 5 blocks/piece * 16384 bytes/block = 819200 bytes
 */
constexpr unsigned long FILE_PIECES   = 10UL;
constexpr unsigned long PIECES_BLOCKS = 5UL;
constexpr int BLOCK_SIZE              = 16384;

/** Number of blocks asked by each request */
constexpr unsigned long BLOCKS_REQUESTED = 2UL;

constexpr double SLEEP_DURATION     = 1.0;
#define BITS_TO_BYTES(x) (((x) / 8 + (x) % 8) ? 1 : 0)

/** Message sizes
 * Sizes based on report by A. Legout et al, Understanding BitTorrent: An Experimental Perspective
 * http://hal.inria.fr/inria-00000156/en
 */
constexpr unsigned message_size(MessageType type)
{
  constexpr std::array<unsigned, 10> sizes{{/* HANDSHAKE     */ 68,
                                            /* CHOKE         */ 5,
                                            /* UNCHOKE       */ 5,
                                            /* INTERESTED    */ 5,
                                            /* NOTINTERESTED */ 5,
                                            /* HAVE          */ 9,
                                            /* BITFIELD      */ 5,
                                            /* REQUEST       */ 17,
                                            /* PIECE         */ 13,
                                            /* CANCEL        */ 17}};
  return sizes.at(static_cast<int>(type));
}

constexpr const char* message_name(MessageType type)
{
  constexpr std::array<const char*, 10> names{{"HANDSHAKE", "CHOKE", "UNCHOKE", "INTERESTED", "NOTINTERESTED", "HAVE",
                                               "BITFIELD", "REQUEST", "PIECE", "CANCEL"}};
  return names.at(static_cast<int>(type));
}

Peer::Peer(std::vector<std::string> args)
{
  // Check arguments
  xbt_assert(args.size() == 3 || args.size() == 4, "Wrong number of arguments");
  try {
    id       = std::stoi(args[1]);
    mailbox_ = sg4::Mailbox::by_name(std::to_string(id));
  } catch (const std::invalid_argument&) {
    throw std::invalid_argument("Invalid ID:" + args[1]);
  }
  random.set_seed(id);

  try {
    deadline = std::stod(args[2]);
  } catch (const std::invalid_argument&) {
    throw std::invalid_argument("Invalid deadline:" + args[2]);
  }
  xbt_assert(deadline > 0, "Wrong deadline supplied");

  if (args.size() == 4 && args[3] == "1") {
    bitfield_       = (1U << FILE_PIECES) - 1U;
    bitfield_blocks = (1ULL << (FILE_PIECES * PIECES_BLOCKS)) - 1ULL;
  }
  pieces_count.resize(FILE_PIECES);

  XBT_INFO("Hi, I'm joining the network with id %d", id);
}

/** Peer main function */
void Peer::operator()()
{
  // Getting peer data from the tracker.
  if (getPeersFromTracker()) {
    XBT_DEBUG("Got %zu peers from the tracker. Current status is: %s", connected_peers.size(), getStatus().c_str());
    begin_receive_time = sg4::Engine::get_clock();
    mailbox_->set_receiver(sg4::Actor::self());
    if (hasFinished()) {
      sendHandshakeToAllPeers();
    } else {
      leech();
    }
    seed();
  } else {
    XBT_INFO("Couldn't contact the tracker.");
  }

  XBT_INFO("Here is my current status: %s", getStatus().c_str());
}

bool Peer::getPeersFromTracker()
{
  sg4::Mailbox* tracker_mailbox = sg4::Mailbox::by_name(TRACKER_MAILBOX);
  // Build the task to send to the tracker
  auto* peer_request = new TrackerQuery(id, mailbox_);
  try {
    XBT_DEBUG("Sending a peer request to the tracker.");
    tracker_mailbox->put(peer_request, TRACKER_COMM_SIZE, GET_PEERS_TIMEOUT);
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Timeout expired when requesting peers to tracker");
    delete peer_request;
    return false;
  }

  try {
    auto answer = mailbox_->get_unique<TrackerAnswer>(GET_PEERS_TIMEOUT);
    // Add the peers the tracker gave us to our peer list.
    for (auto const& peer_id : answer->getPeers())
      if (id != peer_id)
        connected_peers.try_emplace(peer_id, peer_id);
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Timeout expired when requesting peers to tracker");
    return false;
  }
  return true;
}

void Peer::sendHandshakeToAllPeers()
{
  for (auto const& [_, remote_peer] : connected_peers) {
    auto* handshake               = new Message(MessageType::HANDSHAKE, id, mailbox_);
    remote_peer.mailbox_->put_init(handshake, message_size(MessageType::HANDSHAKE))->detach();
    XBT_DEBUG("Sending a HANDSHAKE to %d", remote_peer.id);
  }
}

void Peer::sendMessage(sg4::Mailbox* mailbox, MessageType type, uint64_t size)
{
  XBT_DEBUG("Sending %s to %s", message_name(type), mailbox->get_cname());
  mailbox->put_init(new Message(type, id, bitfield_, mailbox_), size)->detach();
}

void Peer::sendBitfield(sg4::Mailbox* mailbox)
{
  XBT_DEBUG("Sending a BITFIELD to %s", mailbox->get_cname());
  mailbox
      ->put_init(new Message(MessageType::BITFIELD, id, bitfield_, mailbox_),
                 message_size(MessageType::BITFIELD) + BITS_TO_BYTES(FILE_PIECES))
      ->detach();
}

void Peer::sendPiece(sg4::Mailbox* mailbox, unsigned int piece, int block_index, int block_length)
{
  xbt_assert(not hasNotPiece(piece), "Tried to send a unavailable piece.");
  XBT_DEBUG("Sending the PIECE %u (%d,%d) to %s", piece, block_index, block_length, mailbox->get_cname());
  mailbox->put_init(new Message(MessageType::PIECE, id, mailbox_, piece, block_index, block_length), BLOCK_SIZE)
      ->detach();
}

void Peer::sendHaveToAllPeers(unsigned int piece)
{
  XBT_DEBUG("Sending HAVE message to all my peers");
  for (auto const& [_, remote_peer] : connected_peers) {
    remote_peer.mailbox_->put_init(new Message(MessageType::HAVE, id, mailbox_, piece), message_size(MessageType::HAVE))
        ->detach();
  }
}

void Peer::sendRequestTo(Connection* remote_peer, unsigned int piece)
{
  remote_peer->current_piece = piece;
  xbt_assert(remote_peer->hasPiece(piece));
  int block_index = getFirstMissingBlockFrom(piece);
  if (block_index != -1) {
    int block_length = static_cast<int>(std::min(BLOCKS_REQUESTED, PIECES_BLOCKS - block_index));
    XBT_DEBUG("Sending a REQUEST to %s for piece %u (%d,%d)", remote_peer->mailbox_->get_cname(), piece, block_index,
              block_length);
    remote_peer->mailbox_
        ->put_init(new Message(MessageType::REQUEST, id, mailbox_, piece, block_index, block_length),
                   message_size(MessageType::REQUEST))
        ->detach();
  }
}

std::string Peer::getStatus() const
{
  std::string res;
  for (unsigned i = 0; i < FILE_PIECES; i++)
    res += (bitfield_ & (1U << i)) ? '1' : '0';
  return res;
}

bool Peer::hasFinished() const
{
  return bitfield_ == (1U << FILE_PIECES) - 1U;
}

/** Indicates if the remote peer has a piece not stored by the local peer */
bool Peer::isInterestedBy(const Connection* remote_peer) const
{
  return remote_peer->bitfield & (bitfield_ ^ ((1 << FILE_PIECES) - 1));
}

bool Peer::isInterestedByFree(const Connection* remote_peer) const
{
  for (unsigned int i = 0; i < FILE_PIECES; i++)
    if (hasNotPiece(i) && remote_peer->hasPiece(i) && isNotDownloadingPiece(i))
      return true;
  return false;
}

void Peer::updatePiecesCountFromBitfield(unsigned int bitfield)
{
  for (unsigned int i = 0; i < FILE_PIECES; i++)
    if (bitfield & (1U << i))
      pieces_count[i]++;
}

unsigned int Peer::countPieces(unsigned int bitfield) const
{
  unsigned int count = 0U;
  unsigned int n     = bitfield;
  while (n) {
    count += n & 1U;
    n >>= 1U;
  }
  return count;
}

int Peer::nbInterestedPeers() const
{
  return static_cast<int>(std::count_if(connected_peers.begin(), connected_peers.end(),
                                        [](const auto& kv) { return kv.second.interested; }));
}

void Peer::leech()
{
  double next_choked_update = sg4::Engine::get_clock() + UPDATE_CHOKED_INTERVAL;
  XBT_DEBUG("Start downloading.");

  /* Send a "handshake" message to all the peers it got (since it couldn't have gotten more than 50 peers) */
  sendHandshakeToAllPeers();
  XBT_DEBUG("Starting main leech loop listening on mailbox: %s", mailbox_->get_cname());

  while (sg4::Engine::get_clock() < deadline && countPieces(bitfield_) < FILE_PIECES) {
    if (comm_received == nullptr) {
      comm_received = mailbox_->get_async<Message>(&message);
    }
    if (comm_received->test()) {
      handleMessage();
      delete message;
      comm_received = nullptr;
    } else {
      // We don't execute the choke algorithm if we don't already have a piece
      if (sg4::Engine::get_clock() >= next_choked_update && countPieces(bitfield_) > 0) {
        updateChokedPeers();
        next_choked_update += UPDATE_CHOKED_INTERVAL;
      } else {
        sg4::this_actor::sleep_for(SLEEP_DURATION);
      }
    }
  }
  if (hasFinished())
    XBT_DEBUG("%d becomes a seeder", id);
}

void Peer::seed()
{
  double next_choked_update = sg4::Engine::get_clock() + UPDATE_CHOKED_INTERVAL;
  XBT_DEBUG("Start seeding.");
  // start the main seed loop
  while (sg4::Engine::get_clock() < deadline) {
    if (comm_received == nullptr) {
      comm_received = mailbox_->get_async<Message>(&message);
    }
    if (comm_received->test()) {
      handleMessage();
      delete message;
      comm_received = nullptr;
    } else {
      if (sg4::Engine::get_clock() >= next_choked_update) {
        updateChokedPeers();
        // TODO: Change the choked peer algorithm when seeding.
        next_choked_update += UPDATE_CHOKED_INTERVAL;
      } else {
        sg4::this_actor::sleep_for(SLEEP_DURATION);
      }
    }
  }
}

void Peer::updateActivePeersSet(Connection* remote_peer)
{
  if (remote_peer->interested && not remote_peer->choked_upload)
    active_peers.insert(remote_peer);
  else
    active_peers.erase(remote_peer);
}

void Peer::handleMessage()
{
  XBT_DEBUG("Received a %s message from %s", message_name(message->type), message->return_mailbox->get_cname());

  auto known_peer         = connected_peers.find(message->peer_id);
  Connection* remote_peer = (known_peer == connected_peers.end()) ? nullptr : &known_peer->second;
  xbt_assert(remote_peer != nullptr || message->type == MessageType::HANDSHAKE,
             "The impossible did happened: A not-in-our-list peer sent us a message.");

  switch (message->type) {
    case MessageType::HANDSHAKE:
      // Check if the peer is in our connection list.
      if (remote_peer == nullptr) {
        XBT_DEBUG("This peer %d was unknown, answer to its handshake", message->peer_id);
        connected_peers.try_emplace(message->peer_id, message->peer_id);
        sendMessage(message->return_mailbox, MessageType::HANDSHAKE, message_size(MessageType::HANDSHAKE));
      }
      // Send our bitfield to the peer
      sendBitfield(message->return_mailbox);
      break;
    case MessageType::BITFIELD:
      // Update the pieces list
      updatePiecesCountFromBitfield(message->bitfield);
      // Store the bitfield
      remote_peer->bitfield = message->bitfield;
      xbt_assert(not remote_peer->am_interested, "Should not be interested at first");
      if (isInterestedBy(remote_peer)) {
        remote_peer->am_interested = true;
        sendMessage(message->return_mailbox, MessageType::INTERESTED, message_size(MessageType::INTERESTED));
      }
      break;
    case MessageType::INTERESTED:
      // Update the interested state of the peer.
      remote_peer->interested = true;
      updateActivePeersSet(remote_peer);
      break;
    case MessageType::NOTINTERESTED:
      remote_peer->interested = false;
      updateActivePeersSet(remote_peer);
      break;
    case MessageType::UNCHOKE:
      xbt_assert(remote_peer->choked_download);
      remote_peer->choked_download = false;
      // Send requests to the peer, since it has unchoked us
      if (remote_peer->am_interested)
        requestNewPieceTo(remote_peer);
      break;
    case MessageType::CHOKE:
      xbt_assert(not remote_peer->choked_download);
      remote_peer->choked_download = true;
      if (remote_peer->current_piece != -1)
        removeCurrentPiece(remote_peer, remote_peer->current_piece);
      break;
    case MessageType::HAVE:
      XBT_DEBUG("\t for piece %d", message->piece);
      xbt_assert((message->piece >= 0 && static_cast<unsigned int>(message->piece) < FILE_PIECES),
                 "Wrong HAVE message received");
      remote_peer->bitfield = remote_peer->bitfield | (1U << static_cast<unsigned int>(message->piece));
      pieces_count[message->piece]++;
      // If the piece is in our pieces, we tell the peer that we are interested.
      if (not remote_peer->am_interested && hasNotPiece(message->piece)) {
        remote_peer->am_interested = true;
        sendMessage(message->return_mailbox, MessageType::INTERESTED, message_size(MessageType::INTERESTED));
        if (not remote_peer->choked_download)
          requestNewPieceTo(remote_peer);
      }
      break;
    case MessageType::REQUEST:
      xbt_assert(remote_peer->interested);
      xbt_assert((message->piece >= 0 && static_cast<unsigned int>(message->piece) < FILE_PIECES),
                 "Wrong HAVE message received");
      if (not remote_peer->choked_upload) {
        XBT_DEBUG("\t for piece %d (%d,%d)", message->piece, message->block_index,
                  message->block_index + message->block_length);
        if (not hasNotPiece(message->piece)) {
          sendPiece(message->return_mailbox, message->piece, message->block_index, message->block_length);
        }
      } else {
        XBT_DEBUG("\t for piece %d but he is choked.", message->peer_id);
      }
      break;
    case MessageType::PIECE:
      XBT_DEBUG(" \t for piece %d (%d,%d)", message->piece, message->block_index,
                message->block_index + message->block_length);
      xbt_assert(not remote_peer->choked_download);
      xbt_assert(not remote_peer->choked_download, "Can't received a piece if I'm choked !");
      xbt_assert((message->piece >= 0 && static_cast<unsigned int>(message->piece) < FILE_PIECES),
                 "Wrong piece received");
      // TODO: Execute a computation.
      if (hasNotPiece(static_cast<unsigned int>(message->piece))) {
        updateBitfieldBlocks(message->piece, message->block_index, message->block_length);
        if (hasCompletedPiece(static_cast<unsigned int>(message->piece))) {
          // Removing the piece from our piece list
          removeCurrentPiece(remote_peer, message->piece);
          // Setting the fact that we have the piece
          bitfield_ = bitfield_ | (1U << static_cast<unsigned int>(message->piece));
          XBT_DEBUG("My status is now %s", getStatus().c_str());
          // Sending the information to all the peers we are connected to
          sendHaveToAllPeers(message->piece);
          // sending UNINTERESTED to peers that do not have what we want.
          updateInterestedAfterReceive();
        } else {                                      // piece not completed
          sendRequestTo(remote_peer, message->piece); // ask for the next block
        }
      } else {
        XBT_DEBUG("However, we already have it");
        requestNewPieceTo(remote_peer);
      }
      break;
    case MessageType::CANCEL:
      break;
    default:
      THROW_IMPOSSIBLE;
  }
  // Update the peer speed.
  if (remote_peer) {
    remote_peer->addSpeedValue(1.0 / (sg4::Engine::get_clock() - begin_receive_time));
  }
  begin_receive_time = sg4::Engine::get_clock();
}

/** Selects the appropriate piece to download and requests it to the remote_peer */
void Peer::requestNewPieceTo(Connection* remote_peer)
{
  int piece = selectPieceToDownload(remote_peer);
  if (piece != -1) {
    current_pieces |= (1U << (unsigned int)piece);
    sendRequestTo(remote_peer, piece);
  }
}

void Peer::removeCurrentPiece(Connection* remote_peer, unsigned int current_piece)
{
  current_pieces &= ~(1U << current_piece);
  remote_peer->current_piece = -1;
}

/** @brief Return the piece to be downloaded
 * There are two cases (as described in "Bittorrent Architecture Protocol", Ryan Toole :
 * If a piece is partially downloaded, this piece will be selected prioritarily
 * If the peer has strictly less than 4 pieces, he chooses a piece at random.
 * If the peer has more than pieces, he downloads the pieces that are the less replicated (rarest policy).
 * If all pieces have been downloaded or requested, we select a random requested piece (endgame mode).
 * @param remote_peer: information about the connection
 * @return the piece to download if possible. -1 otherwise
 */
int Peer::selectPieceToDownload(const Connection* remote_peer)
{
  int piece = partiallyDownloadedPiece(remote_peer);
  // strict priority policy
  if (piece != -1)
    return piece;

  // end game mode
  if (countPieces(current_pieces) >= (FILE_PIECES - countPieces(bitfield_)) && isInterestedBy(remote_peer)) {
    int nb_interesting_pieces = 0;
    // compute the number of interesting pieces
    for (unsigned int i = 0; i < FILE_PIECES; i++)
      if (remotePeerHasMissingPiece(remote_peer, i))
        nb_interesting_pieces++;

    xbt_assert(nb_interesting_pieces != 0);
    // get a random interesting piece
    int random_piece_index = random.uniform_int(0, nb_interesting_pieces - 1);
    int current_index      = 0;
    for (unsigned int i = 0; i < FILE_PIECES; i++) {
      if (remotePeerHasMissingPiece(remote_peer, i)) {
        if (random_piece_index == current_index) {
          piece = i;
          break;
        }
        current_index++;
      }
    }
    xbt_assert(piece != -1);
    return piece;
  }
  // Random first policy
  if (countPieces(bitfield_) < 4 && isInterestedByFree(remote_peer)) {
    int nb_interesting_pieces = 0;
    // compute the number of interesting pieces
    for (unsigned int i = 0; i < FILE_PIECES; i++)
      if (remotePeerHasMissingPiece(remote_peer, i) && isNotDownloadingPiece(i))
        nb_interesting_pieces++;
    xbt_assert(nb_interesting_pieces != 0);
    // get a random interesting piece
    int random_piece_index = random.uniform_int(0, nb_interesting_pieces - 1);
    int current_index      = 0;
    for (unsigned int i = 0; i < FILE_PIECES; i++) {
      if (remotePeerHasMissingPiece(remote_peer, i) && isNotDownloadingPiece(i)) {
        if (random_piece_index == current_index) {
          piece = i;
          break;
        }
        current_index++;
      }
    }
    xbt_assert(piece != -1);
    return piece;
  } else { // Rarest first policy
    short min         = SHRT_MAX;
    int nb_min_pieces = 0;
    int current_index = 0;
    // compute the smallest number of copies of available pieces
    for (unsigned int i = 0; i < FILE_PIECES; i++) {
      if (pieces_count[i] < min && remotePeerHasMissingPiece(remote_peer, i) && isNotDownloadingPiece(i))
        min = pieces_count[i];
    }

    xbt_assert(min != SHRT_MAX || not isInterestedByFree(remote_peer));
    // compute the number of rarest pieces
    for (unsigned int i = 0; i < FILE_PIECES; i++)
      if (pieces_count[i] == min && remotePeerHasMissingPiece(remote_peer, i) && isNotDownloadingPiece(i))
        nb_min_pieces++;

    xbt_assert(nb_min_pieces != 0 || not isInterestedByFree(remote_peer));
    // get a random rarest piece
    int random_rarest_index = 0;
    if (nb_min_pieces > 0) {
      random_rarest_index = random.uniform_int(0, nb_min_pieces - 1);
    }
    for (unsigned int i = 0; i < FILE_PIECES; i++)
      if (pieces_count[i] == min && remotePeerHasMissingPiece(remote_peer, i) && isNotDownloadingPiece(i)) {
        if (random_rarest_index == current_index) {
          piece = i;
          break;
        }
        current_index++;
      }

    xbt_assert(piece != -1 || not isInterestedByFree(remote_peer));
    return piece;
  }
}

void Peer::updateChokedPeers()
{
  if (nbInterestedPeers() == 0)
    return;
  XBT_DEBUG("(%d) update_choked peers %zu active peers", id, active_peers.size());
  // update the current round
  round_                  = (round_ + 1) % 3;
  Connection* chosen_peer = nullptr;
  // select first active peer and remove it from the set
  Connection* choked_peer;
  if (active_peers.empty()) {
    choked_peer = nullptr;
  } else {
    choked_peer = *active_peers.begin();
    active_peers.erase(choked_peer);
  }

  /**If we are currently seeding, we unchoke the peer which has been unchoked the last time.*/
  if (hasFinished()) {
    double unchoke_time = sg4::Engine::get_clock() + 1;
    for (auto& [_, remote_peer] : connected_peers) {
      if (remote_peer.last_unchoke < unchoke_time && remote_peer.interested && remote_peer.choked_upload) {
        unchoke_time = remote_peer.last_unchoke;
        chosen_peer  = &remote_peer;
      }
    }
  } else {
    // Random optimistic unchoking
    if (round_ == 0) {
      int j = 0;
      do {
        // We choose a random peer to unchoke.
        auto chosen_peer_it = connected_peers.begin();
        std::advance(chosen_peer_it, random.uniform_int(0, static_cast<int>(connected_peers.size() - 1)));
        chosen_peer = &chosen_peer_it->second;
        if (not chosen_peer->interested || not chosen_peer->choked_upload)
          chosen_peer = nullptr;
        else
          XBT_DEBUG("Nothing to do, keep going");
        j++;
      } while (chosen_peer == nullptr && j < MAXIMUM_PEERS);
    } else {
      // Use the "fastest download" policy.
      double fastest_speed = 0.0;
      for (auto& [_, remote_peer] : connected_peers) {
        if (remote_peer.peer_speed > fastest_speed && remote_peer.choked_upload && remote_peer.interested) {
          fastest_speed = remote_peer.peer_speed;
          chosen_peer   = &remote_peer;
        }
      }
    }
  }

  if (chosen_peer != nullptr)
    XBT_DEBUG("(%d) update_choked peers unchoked (%d) ; int (%d) ; choked (%d) ", id, chosen_peer->id,
              chosen_peer->interested, chosen_peer->choked_upload);

  if (choked_peer != chosen_peer) {
    if (choked_peer != nullptr) {
      xbt_assert(not choked_peer->choked_upload, "Tries to choked a choked peer");
      choked_peer->choked_upload = true;
      updateActivePeersSet(choked_peer);
      XBT_DEBUG("(%d) Sending a CHOKE to %d", id, choked_peer->id);
      sendMessage(choked_peer->mailbox_, MessageType::CHOKE, message_size(MessageType::CHOKE));
    }
    if (chosen_peer != nullptr) {
      xbt_assert((chosen_peer->choked_upload), "Tries to unchoked an unchoked peer");
      chosen_peer->choked_upload = false;
      active_peers.insert(chosen_peer);
      chosen_peer->last_unchoke = sg4::Engine::get_clock();
      XBT_DEBUG("(%d) Sending a UNCHOKE to %d", id, chosen_peer->id);
      updateActivePeersSet(chosen_peer);
      sendMessage(chosen_peer->mailbox_, MessageType::UNCHOKE, message_size(MessageType::UNCHOKE));
    }
  }
}

/** @brief Update "interested" state of peers: send "not interested" to peers that don't have any more pieces we want.*/
void Peer::updateInterestedAfterReceive()
{
  for (auto& [_, remote_peer] : connected_peers) {
    if (remote_peer.am_interested) {
      bool interested = false;
      // Check if the peer still has a piece we want.
      for (unsigned int i = 0; i < FILE_PIECES; i++)
        if (remotePeerHasMissingPiece(&remote_peer, i)) {
          interested = true;
          break;
        }

      if (not interested) { // no more piece to download from connection
        remote_peer.am_interested = false;
        sendMessage(remote_peer.mailbox_, MessageType::NOTINTERESTED, message_size(MessageType::NOTINTERESTED));
      }
    }
  }
}

void Peer::updateBitfieldBlocks(int piece, int block_index, int block_length)
{
  xbt_assert((piece >= 0 && static_cast<unsigned int>(piece) <= FILE_PIECES), "Wrong piece.");
  xbt_assert((block_index >= 0 && static_cast<unsigned int>(block_index) <= PIECES_BLOCKS), "Wrong block : %d.",
             block_index);
  for (int i = block_index; i < (block_index + block_length); i++)
    bitfield_blocks |= (1ULL << static_cast<unsigned int>(piece * PIECES_BLOCKS + i));
}

bool Peer::hasCompletedPiece(unsigned int piece) const
{
  for (unsigned int i = 0; i < PIECES_BLOCKS; i++)
    if (not(bitfield_blocks & 1ULL << (piece * PIECES_BLOCKS + i)))
      return false;
  return true;
}

int Peer::getFirstMissingBlockFrom(int piece) const
{
  for (unsigned int i = 0; i < PIECES_BLOCKS; i++)
    if (not(bitfield_blocks & 1ULL << (piece * PIECES_BLOCKS + i)))
      return i;
  return -1;
}

/** Returns a piece that is partially downloaded and stored by the remote peer if any -1 otherwise. */
int Peer::partiallyDownloadedPiece(const Connection* remote_peer) const
{
  for (unsigned int i = 0; i < FILE_PIECES; i++)
    if (remotePeerHasMissingPiece(remote_peer, i) && isNotDownloadingPiece(i) && getFirstMissingBlockFrom(i) > 0)
      return i;
  return -1;
}

View examples/cpp/app-bittorrent/s4u-tracker.cpp

Download s4u-tracker.cpp

/* Copyright (c) 2012-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "s4u-tracker.hpp"
#include <algorithm>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_bt_tracker, "Messages specific for the tracker");
namespace sg4 = simgrid::s4u;

Tracker::Tracker(std::vector<std::string> args)
{
  // Checking arguments
  xbt_assert(args.size() == 2, "Wrong number of arguments for the tracker.");
  // Retrieving end time
  try {
    deadline = std::stod(args[1]);
  } catch (const std::invalid_argument&) {
    throw std::invalid_argument("Invalid deadline:" + args[1]);
  }
  xbt_assert(deadline > 0, "Wrong deadline supplied");

  mailbox = sg4::Mailbox::by_name(TRACKER_MAILBOX);

  XBT_INFO("Tracker launched.");
}

void Tracker::operator()()
{
  sg4::CommPtr comm          = nullptr;
  TrackerQuery* query        = nullptr;
  while (sg4::Engine::get_clock() < deadline) {
    if (comm == nullptr)
      comm = mailbox->get_async<TrackerQuery>(&query);
    if (comm->test()) {
      // Retrieve the data sent by the peer.
      xbt_assert(query != nullptr);

      // Add the peer to our peer list, if not already known.
      known_peers.emplace(query->getPeerId());

      // Sending back peers to the requesting peer
      auto* answer = new TrackerAnswer(TRACKER_QUERY_INTERVAL);
      std::set<int>::iterator next_peer;
      int nb_known_peers = static_cast<int>(known_peers.size());
      int max_tries      = std::min(MAXIMUM_PEERS, nb_known_peers);
      int tried          = 0;
      while (tried < max_tries) {
        do {
          next_peer = known_peers.begin();
          std::advance(next_peer, random.uniform_int(0, nb_known_peers - 1));
        } while (answer->getPeers().find(*next_peer) != answer->getPeers().end());
        answer->addPeer(*next_peer);
        tried++;
      }
      query->getReturnMailbox()->put_init(answer, TRACKER_COMM_SIZE)->detach();

      delete query;
      comm = nullptr;
    } else {
      sg4::this_actor::sleep_for(1);
    }
  }
  XBT_INFO("Tracker is leaving");
}

Chained Send

Data broadcast over a ring of processes.

View examples/cpp/app-chainsend/s4u-app-chainsend.cpp

Download s4u-app-chainsend.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include <vector>

constexpr unsigned PIECE_SIZE                    = 65536;
constexpr unsigned MESSAGE_BUILD_CHAIN_SIZE      = 40;
constexpr unsigned MESSAGE_SEND_DATA_HEADER_SIZE = 1;

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_chainsend, "Messages specific for chainsend");
namespace sg4 = simgrid::s4u;

class ChainMessage {
public:
  sg4::Mailbox* prev_            = nullptr;
  sg4::Mailbox* next_            = nullptr;
  unsigned int num_pieces        = 0;
  explicit ChainMessage(sg4::Mailbox* prev, sg4::Mailbox* next, const unsigned int num_pieces)
      : prev_(prev), next_(next), num_pieces(num_pieces)
  {
  }
};

class FilePiece {
public:
  FilePiece()  = default;
};

class Peer {
public:
  sg4::Mailbox* prev = nullptr;
  sg4::Mailbox* next = nullptr;
  sg4::Mailbox* me   = nullptr;
  sg4::ActivitySet pending_recvs;
  sg4::ActivitySet pending_sends;

  unsigned long long received_bytes = 0;
  unsigned int received_pieces      = 0;
  unsigned int total_pieces         = 0;

  Peer() { me = sg4::Mailbox::by_name(sg4::Host::current()->get_cname()); }

  void joinChain()
  {
    auto msg     = me->get_unique<ChainMessage>();
    prev         = msg->prev_;
    next         = msg->next_;
    total_pieces = msg->num_pieces;
    XBT_DEBUG("Peer %s got a 'BUILD_CHAIN' message (prev: %s / next: %s)", me->get_cname(),
              prev ? prev->get_cname() : nullptr, next ? next->get_cname() : nullptr);
  }

  void forwardFile()
  {
    FilePiece* received;
    bool done = false;

    while (not done) {
      sg4::CommPtr comm = me->get_async<FilePiece>(&received);
      pending_recvs.push(comm);

      auto completed_one = pending_recvs.wait_any();
      if (completed_one != nullptr) {
        comm = boost::dynamic_pointer_cast<sg4::Comm>(completed_one);
        XBT_DEBUG("Peer %s got a 'SEND_DATA' message", me->get_cname());
        if (next != nullptr) {
          XBT_DEBUG("Sending (asynchronously) from %s to %s", me->get_cname(), next->get_cname());
          sg4::CommPtr send = next->put_async(received, MESSAGE_SEND_DATA_HEADER_SIZE + PIECE_SIZE);
          pending_sends.push(send);
        } else
          delete received;

        received_pieces++;
        received_bytes += PIECE_SIZE;
        XBT_DEBUG("%u pieces received, %llu bytes received", received_pieces, received_bytes);
        if (received_pieces >= total_pieces) {
          done = true;
        }
      }
    }
  }
};

class Broadcaster {
public:
  sg4::Mailbox* first = nullptr;
  std::vector<sg4::Mailbox*> mailboxes;
  unsigned int piece_count;

  void buildChain()
  {
    /* Build the chain if there's at least one peer */
    if (not mailboxes.empty())
      first = mailboxes.front();

    for (unsigned i = 0; i < mailboxes.size(); i++) {
      sg4::Mailbox* prev = i > 0 ? mailboxes[i - 1] : nullptr;
      sg4::Mailbox* next = i < mailboxes.size() - 1 ? mailboxes[i + 1] : nullptr;
      XBT_DEBUG("Building chain--broadcaster:\"%s\" dest:\"%s\" prev:\"%s\" next:\"%s\"",
                sg4::Host::current()->get_cname(), mailboxes[i]->get_cname(), prev ? prev->get_cname() : nullptr,
                next ? next->get_cname() : nullptr);
      /* Send message to current peer */
      mailboxes[i]->put(new ChainMessage(prev, next, piece_count), MESSAGE_BUILD_CHAIN_SIZE);
    }
  }

  void sendFile()
  {
    sg4::ActivitySet pending_sends;
    for (unsigned int current_piece = 0; current_piece < piece_count; current_piece++) {
      XBT_DEBUG("Sending (send) piece %u from %s into mailbox %s", current_piece, sg4::Host::current()->get_cname(),
                first->get_cname());
      sg4::CommPtr comm = first->put_async(new FilePiece(), MESSAGE_SEND_DATA_HEADER_SIZE + PIECE_SIZE);
      pending_sends.push(comm);
    }
    pending_sends.wait_all();
  }

  Broadcaster(int hostcount, unsigned int piece_count) : piece_count(piece_count)
  {
    for (int i = 1; i <= hostcount; i++) {
      std::string name = "node-" + std::to_string(i) + ".simgrid.org";
      XBT_DEBUG("%s", name.c_str());
      mailboxes.push_back(sg4::Mailbox::by_name(name));
    }
  }
};

static void peer()
{
  XBT_DEBUG("peer");

  Peer p;

  double start_time = sg4::Engine::get_clock();
  p.joinChain();
  p.forwardFile();

  p.pending_sends.wait_all();
  double end_time = sg4::Engine::get_clock();

  XBT_INFO("### %f %llu bytes (Avg %f MB/s); copy finished (simulated).", end_time - start_time, p.received_bytes,
           p.received_bytes / 1024.0 / 1024.0 / (end_time - start_time));
}

static void broadcaster(int hostcount, unsigned int piece_count)
{
  XBT_DEBUG("broadcaster");

  Broadcaster bc(hostcount, piece_count);
  bc.buildChain();
  bc.sendFile();
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  e.load_platform(argv[1]);

  sg4::Actor::create("broadcaster", e.host_by_name("node-0.simgrid.org"), broadcaster, 8, 256);

  sg4::Actor::create("peer", e.host_by_name("node-1.simgrid.org"), peer);
  sg4::Actor::create("peer", e.host_by_name("node-2.simgrid.org"), peer);
  sg4::Actor::create("peer", e.host_by_name("node-3.simgrid.org"), peer);
  sg4::Actor::create("peer", e.host_by_name("node-4.simgrid.org"), peer);
  sg4::Actor::create("peer", e.host_by_name("node-5.simgrid.org"), peer);
  sg4::Actor::create("peer", e.host_by_name("node-6.simgrid.org"), peer);
  sg4::Actor::create("peer", e.host_by_name("node-7.simgrid.org"), peer);
  sg4::Actor::create("peer", e.host_by_name("node-8.simgrid.org"), peer);

  e.run();
  XBT_INFO("Total simulation time: %e", sg4::Engine::get_clock());

  return 0;
}

Distributed Hash Tables (DHT)

Chord Protocol

One of the most famous DHT protocol.

View examples/cpp/dht-chord/s4u-dht-chord.cpp

Download s4u-dht-chord.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "s4u-dht-chord.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_chord, "Messages specific for this s4u example");

int main(int argc, char* argv[])
{
  simgrid::s4u::Engine e(&argc, argv);
  xbt_assert(argc > 2,
             "Usage: %s [-nb_bits=n] [-timeout=t] platform_file deployment_file\n"
             "\tExample: %s ../platforms/cluster_backbone.xml ./s4u-dht-chord_d.xml\n",
             argv[0], argv[0]);
  std::string platform_file(argv[argc - 2]);
  std::string deployment_file(argv[argc - 1]);
  int nb_bits = 24;
  int timeout = 50;
  for (const auto& option : std::vector<std::string>(argv + 1, argv + argc - 2)) {
    if (option.rfind("-nb_bits=", 0) == 0) {
      nb_bits = std::stoi(option.substr(option.find('=') + 1));
      XBT_DEBUG("Set nb_bits to %d", nb_bits);
    } else if (option.rfind("-timeout=", 0) == 0) {
      timeout = std::stoi(option.substr(option.find('=') + 1));
      XBT_DEBUG("Set timeout to %d", timeout);
    } else {
      xbt_die("Invalid chord option '%s'", option.c_str());
    }
  }
  int nb_keys = 1U << nb_bits;
  XBT_DEBUG("Sets nb_keys to %d", nb_keys);

  e.load_platform(platform_file);

  /* Global initialization of the Chord simulation. */
  Node::set_parameters(nb_bits, nb_keys, timeout);

  e.register_actor<Node>("node");
  e.load_deployment(deployment_file);

  e.run();

  XBT_INFO("Simulated time: %g", simgrid::s4u::Engine::get_clock());
  return 0;
}

View examples/cpp/dht-chord/s4u-dht-chord-node.cpp

Download s4u-dht-chord-node.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "s4u-dht-chord.hpp"

XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(s4u_chord);
namespace sg4 = simgrid::s4u;

void ChordMessage::destroy(void* message)
{
  delete static_cast<ChordMessage*>(message);
}

/* Returns whether an id belongs to the interval [start, end].
 *
 * The parameters are normalized to make sure they are between 0 and nb_keys_ - 1).
 * 1 belongs to [62, 3]
 * 1 does not belong to [3, 62]
 * 63 belongs to [62, 3]
 * 63 does not belong to [3, 62]
 * 24 belongs to [21, 29]
 * 24 does not belong to [29, 21]
 *
 * @param id id to check
 * @param start lower bound
 * @param end upper bound
 * @return true if id in in [start, end]
 */
bool Node::is_in_interval(int id, int start, int end)
{
  int i = id % nb_keys_;
  int s = start % nb_keys_;
  int e = end % nb_keys_;

  // make sure end >= start and id >= start
  if (e < s) {
    e += nb_keys_;
  }

  if (i < s) {
    i += nb_keys_;
  }

  return i <= e;
}

void Node::set_parameters(int nb_bits, int nb_keys, int timeout)
{
  nb_bits_ = nb_bits;
  nb_keys_ = nb_keys;
  timeout_ = timeout;
}

/* Initializes the current node as the first one of the system */
Node::Node(std::vector<std::string> args)
{
  xbt_assert(args.size() == 3 || args.size() == 5, "Wrong number of arguments for this node");

  // initialize my node
  id_                = std::stoi(args[1]);
  XBT_DEBUG("Initialize node with id: %d", id_);
  random_.set_seed(id_);
  mailbox_           = sg4::Mailbox::by_name(std::to_string(id_));
  next_finger_to_fix_ = 0;
  fingers_.resize(nb_bits_, id_);

  if (args.size() == 3) { // first ring
    deadline_   = std::stod(args[2]);
    start_time_ = sg4::Engine::get_clock();
    XBT_DEBUG("Create a new Chord ring...");
  } else {
    known_id_   = std::stoi(args[2]);
    start_time_ = std::stod(args[3]);
    deadline_   = std::stod(args[4]);
    XBT_DEBUG("Hey! Let's join the system in %f seconds (shall leave at time %f)", start_time_,
              start_time_ + deadline_);
  }
}

/* Makes the current node join the ring, knowing the id of a node already in the ring
 *
 * @param known_id id of a node already in the ring
 * @return true if the join operation succeeded
 *  */

void Node::join(int known_id)
{
  XBT_INFO("Joining the ring with id %d, knowing node %d", id_, known_id);
  setPredecessor(-1); // no predecessor (yet)

  int successor_id = remoteFindSuccessor(known_id, id_);
  if (successor_id == -1) {
    XBT_INFO("Cannot join the ring.");
  } else {
    setFinger(0, successor_id);
    printFingerTable();
    joined_ = true;
  }
}

/* Makes the current node quit the system */
void Node::leave()
{
  XBT_INFO("Well Guys! I Think it's time for me to leave ;)");
  notifyAndQuit();
  joined_ = false;
}

/* Notifies the successor and the predecessor of the current node before leaving */
void Node::notifyAndQuit()
{
  // send the PREDECESSOR_LEAVING to our successor
  auto* pred_msg         = new ChordMessage(MessageType::PREDECESSOR_LEAVING);
  pred_msg->request_id   = pred_id_;
  pred_msg->answer_to    = mailbox_;

  XBT_DEBUG("Sending a 'PREDECESSOR_LEAVING' to my successor %d", fingers_[0]);
  try {
    sg4::Mailbox::by_name(std::to_string(fingers_[0]))->put(pred_msg, 10, timeout_);
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Timeout expired when sending a 'PREDECESSOR_LEAVING' to my successor %d", fingers_[0]);
    delete pred_msg;
  }

  if (pred_id_ != -1 && pred_id_ != id_) {
    // send the SUCCESSOR_LEAVING to our predecessor (only if I have one that is not me)
    auto* succ_msg         = new ChordMessage(MessageType::SUCCESSOR_LEAVING);
    succ_msg->request_id   = fingers_[0];
    succ_msg->answer_to    = mailbox_;
    XBT_DEBUG("Sending a 'SUCCESSOR_LEAVING' to my predecessor %d", pred_id_);

    try {
      sg4::Mailbox::by_name(std::to_string(pred_id_))->put(succ_msg, 10, timeout_);
    } catch (const simgrid::TimeoutException&) {
      XBT_DEBUG("Timeout expired when sending a 'SUCCESSOR_LEAVING' to my predecessor %d", pred_id_);
      delete succ_msg;
    }
  }
}

/* Performs a find successor request to a random id */
void Node::randomLookup()
{
  int res          = id_;
  int random_index = random_.uniform_int(0, nb_bits_ - 1);
  int random_id    = fingers_[random_index];
  XBT_DEBUG("Making a lookup request for id %d", random_id);
  if (random_id != id_)
    res = findSuccessor(random_id);
  XBT_DEBUG("The successor of node %d is %d", random_id, res);
}

/* Sets a finger of the current node.
 *
 * @param node the current node
 * @param finger_index index of the finger to set (0 to nb_bits_ - 1)
 * @param id the id to set for this finger
 */
void Node::setFinger(int finger_index, int id)
{
  if (id != fingers_[finger_index]) {
    fingers_[finger_index] = id;
    XBT_VERB("My new finger #%d is %d", finger_index, id);
  }
}

/* Sets the predecessor of the current node.
 * @param id the id to predecessor, or -1 to unset the predecessor
 */
void Node::setPredecessor(int predecessor_id)
{
  if (predecessor_id != pred_id_) {
    pred_id_ = predecessor_id;
    XBT_VERB("My new predecessor is %d", predecessor_id);
  }
}

/** refreshes the finger table of the current node (called periodically) */
void Node::fixFingers()
{
  XBT_DEBUG("Fixing fingers");
  int id = findSuccessor(id_ + (1U << next_finger_to_fix_));
  if (id != -1) {
    if (id != fingers_[next_finger_to_fix_]) {
      setFinger(next_finger_to_fix_, id);
      printFingerTable();
    }
    next_finger_to_fix_ = (next_finger_to_fix_ + 1) % nb_bits_;
  }
}

/** Displays the finger table of a node. */
void Node::printFingerTable()
{
  if (XBT_LOG_ISENABLED(s4u_chord, xbt_log_priority_verbose)) {
    XBT_VERB("My finger table:");
    XBT_VERB("Start | Succ");
    for (int i = 0; i < nb_bits_; i++) {
      XBT_VERB(" %3u  | %3d", (id_ + (1U << i)) % nb_keys_, fingers_[i]);
    }

    XBT_VERB("Predecessor: %d", pred_id_);
  }
}

/* checks whether the predecessor has failed (called periodically) */
void Node::checkPredecessor()
{
  XBT_DEBUG("Checking whether my predecessor is alive");
  if (pred_id_ == -1)
    return;

  sg4::Mailbox* mailbox        = sg4::Mailbox::by_name(std::to_string(pred_id_));
  sg4::Mailbox* return_mailbox = sg4::Mailbox::by_name(std::to_string(id_) + "_is_alive");

  auto* message         = new ChordMessage(MessageType::PREDECESSOR_ALIVE);
  message->request_id   = pred_id_;
  message->answer_to    = return_mailbox;

  XBT_DEBUG("Sending a 'Predecessor Alive' request to my predecessor %d", pred_id_);
  try {
    mailbox->put(message, 10, timeout_);
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Failed to send the 'Predecessor Alive' request to %d", pred_id_);
    delete message;
    return;
  }

  // receive the answer
  XBT_DEBUG("Sent 'Predecessor Alive' request to %d, waiting for the answer on my mailbox '%s'", pred_id_,
            message->answer_to->get_cname());
  ChordMessage* answer       = nullptr;
  sg4::CommPtr comm          = return_mailbox->get_async<ChordMessage>(&answer);

  try {
    comm->wait_for_or_cancel(timeout_);
    XBT_DEBUG("Received the answer to my 'Predecessor Alive': my predecessor %d is alive", pred_id_);
    delete answer;
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Failed to receive the answer to my 'Predecessor Alive' request");
    pred_id_ = -1;
  }
}

/* Asks its predecessor to a remote node
 *
 * @param ask_to the node to ask to
 * @return the id of its predecessor node, or -1 if the request failed (or if the node does not know its predecessor)
 */
int Node::remoteGetPredecessor(int ask_to)
{
  int predecessor_id                      = -1;
  sg4::Mailbox* mailbox                   = sg4::Mailbox::by_name(std::to_string(ask_to));
  sg4::Mailbox* return_mailbox            = sg4::Mailbox::by_name(std::to_string(id_) + "_pred");

  auto* message         = new ChordMessage(MessageType::GET_PREDECESSOR);
  message->request_id   = id_;
  message->answer_to    = return_mailbox;

  // send a "Get Predecessor" request to ask_to_id
  XBT_DEBUG("Sending a 'Get Predecessor' request to %d", ask_to);
  try {
    mailbox->put(message, 10, timeout_);
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Failed to send the 'Get Predecessor' request to %d", ask_to);
    delete message;
    return predecessor_id;
  }

  // receive the answer
  XBT_DEBUG("Sent 'Get Predecessor' request to %d, waiting for the answer on my mailbox '%s'", ask_to,
            message->answer_to->get_cname());
  ChordMessage* answer       = nullptr;
  sg4::CommPtr comm          = return_mailbox->get_async<ChordMessage>(&answer);

  try {
    comm->wait_for_or_cancel(timeout_);
    XBT_DEBUG("Received the answer to my 'Get Predecessor' request: the predecessor of node %d is %d", ask_to,
              answer->answer_id);
    predecessor_id = answer->answer_id;
    delete answer;
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Failed to receive the answer to my 'Get Predecessor' request");
    delete answer;
  }

  return predecessor_id;
}

/* Returns the closest preceding finger of an id with respect to the finger table of the current node.
 *
 * @param id the id to find
 * @return the closest preceding finger of that id
 */
int Node::closestPrecedingFinger(int id)
{
  for (int i = nb_bits_ - 1; i >= 0; i--) {
    if (is_in_interval(fingers_[i], id_ + 1, id - 1)) {
      return fingers_[i];
    }
  }
  return id_;
}

/* Makes the current node find the successor node of an id.
 *
 * @param id the id to find
 * @return the id of the successor node, or -1 if the request failed
 */
int Node::findSuccessor(int id)
{
  // is my successor the successor?
  if (is_in_interval(id, id_ + 1, fingers_[0])) {
    return fingers_[0];
  }

  // otherwise, ask the closest preceding finger in my table
  return remoteFindSuccessor(closestPrecedingFinger(id), id);
}

int Node::remoteFindSuccessor(int ask_to, int id)
{
  int successor                           = -1;
  sg4::Mailbox* mailbox                   = sg4::Mailbox::by_name(std::to_string(ask_to));
  sg4::Mailbox* return_mailbox            = sg4::Mailbox::by_name(std::to_string(id_) + "_succ");

  auto* message         = new ChordMessage(MessageType::FIND_SUCCESSOR);
  message->request_id   = id_;
  message->answer_to    = return_mailbox;

  // send a "Find Successor" request to ask_to_id
  XBT_DEBUG("Sending a 'Find Successor' request to %d for id %d", ask_to, id);
  try {
    mailbox->put(message, 10, timeout_);
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Failed to send the 'Find Successor' request to %d for id %d", ask_to, id_);
    delete message;
    return successor;
  }
  // receive the answer
  XBT_DEBUG("Sent a 'Find Successor' request to %d for key %d, waiting for the answer", ask_to, id);
  ChordMessage* answer       = nullptr;
  sg4::CommPtr comm          = return_mailbox->get_async<ChordMessage>(&answer);

  try {
    comm->wait_for_or_cancel(timeout_);
    XBT_DEBUG("Received the answer to my 'Find Successor' request for id %d: the successor of key %d is %d",
              answer->request_id, id_, answer->answer_id);
    successor = answer->answer_id;
    delete answer;
  } catch (const simgrid::TimeoutException&) {
    XBT_DEBUG("Failed to receive the answer to my 'Find Successor' request");
    delete answer;
  }

  return successor;
}

/* Notifies the current node that its predecessor may have changed. */
void Node::notify(int predecessor_candidate_id)
{
  if (pred_id_ == -1 || is_in_interval(predecessor_candidate_id, pred_id_ + 1, id_ - 1)) {
    setPredecessor(predecessor_candidate_id);
    printFingerTable();
  } else {
    XBT_DEBUG("I don't have to change my predecessor to %d", predecessor_candidate_id);
  }
}

/* Notifies a remote node that its predecessor may have changed. */
void Node::remoteNotify(int notify_id, int predecessor_candidate_id) const
{
  auto* message         = new ChordMessage(MessageType::NOTIFY);
  message->request_id   = predecessor_candidate_id;
  message->answer_to    = nullptr;

  // send a "Notify" request to notify_id
  XBT_DEBUG("Sending a 'Notify' request to %d", notify_id);
  sg4::Mailbox* mailbox = sg4::Mailbox::by_name(std::to_string(notify_id));
  mailbox->put_init(message, 10)->detach(ChordMessage::destroy);
}

/* This function is called periodically. It checks the immediate successor of the current node. */
void Node::stabilize()
{
  XBT_DEBUG("Stabilizing node");

  // get the predecessor of my immediate successor
  int candidate_id = pred_id_;
  int successor_id = fingers_[0];
  if (successor_id != id_)
    candidate_id = remoteGetPredecessor(successor_id);

  // this node is a candidate to become my new successor
  if (candidate_id != -1 && is_in_interval(candidate_id, id_ + 1, successor_id - 1)) {
    setFinger(0, candidate_id);
  }
  if (successor_id != id_) {
    remoteNotify(successor_id, id_);
  }
}

/* This function is called when a node receives a message.
 *
 * @param message the message to handle (don't touch it afterward: it will be destroyed, reused or forwarded)
 */
void Node::handleMessage(ChordMessage* message)
{
  switch (message->type) {
    case MessageType::FIND_SUCCESSOR:
      XBT_DEBUG("Received a 'Find Successor' request from %s for id %d", message->issuer_host_name.c_str(),
                message->request_id);
      // is my successor the successor?
      if (is_in_interval(message->request_id, id_ + 1, fingers_[0])) {
        message->type      = MessageType::FIND_SUCCESSOR_ANSWER;
        message->answer_id = fingers_[0];
        XBT_DEBUG("Sending back a 'Find Successor Answer' to %s (mailbox %s): the successor of %d is %d",
                  message->issuer_host_name.c_str(), message->answer_to->get_cname(), message->request_id,
                  message->answer_id);
        message->answer_to->put_init(message, 10)->detach(ChordMessage::destroy);
      } else {
        // otherwise, forward the request to the closest preceding finger in my table
        int closest = closestPrecedingFinger(message->request_id);
        XBT_DEBUG("Forwarding the 'Find Successor' request for id %d to my closest preceding finger %d",
                  message->request_id, closest);
        sg4::Mailbox* mailbox = sg4::Mailbox::by_name(std::to_string(closest));
        mailbox->put_init(message, 10)->detach(ChordMessage::destroy);
      }
      break;

    case MessageType::GET_PREDECESSOR:
      XBT_DEBUG("Receiving a 'Get Predecessor' request from %s", message->issuer_host_name.c_str());
      message->type      = MessageType::GET_PREDECESSOR_ANSWER;
      message->answer_id = pred_id_;
      XBT_DEBUG("Sending back a 'Get Predecessor Answer' to %s via mailbox '%s': my predecessor is %d",
                message->issuer_host_name.c_str(), message->answer_to->get_cname(), message->answer_id);
      message->answer_to->put_init(message, 10)->detach(ChordMessage::destroy);
      break;

    case MessageType::NOTIFY:
      // someone is telling me that he may be my new predecessor
      XBT_DEBUG("Receiving a 'Notify' request from %s", message->issuer_host_name.c_str());
      notify(message->request_id);
      delete message;
      break;

    case MessageType::PREDECESSOR_LEAVING:
      // my predecessor is about to quit
      XBT_DEBUG("Receiving a 'Predecessor Leaving' message from %s", message->issuer_host_name.c_str());
      // modify my predecessor
      setPredecessor(message->request_id);
      delete message;
      /*TODO :
        >> notify my new predecessor
        >> send a notify_predecessors !!
       */
      break;

    case MessageType::SUCCESSOR_LEAVING:
      // my successor is about to quit
      XBT_DEBUG("Receiving a 'Successor Leaving' message from %s", message->issuer_host_name.c_str());
      // modify my successor FIXME : this should be implicit ?
      setFinger(0, message->request_id);
      delete message;
      /* TODO
         >> notify my new successor
         >> update my table & predecessors table */
      break;

    case MessageType::PREDECESSOR_ALIVE:
      XBT_DEBUG("Receiving a 'Predecessor Alive' request from %s", message->issuer_host_name.c_str());
      message->type = MessageType::PREDECESSOR_ALIVE_ANSWER;
      XBT_DEBUG("Sending back a 'Predecessor Alive Answer' to %s (mailbox %s)", message->issuer_host_name.c_str(),
                message->answer_to->get_cname());
      message->answer_to->put_init(message, 10)->detach(ChordMessage::destroy);
      break;

    default:
      XBT_DEBUG("Ignoring unexpected message: %d from %s", static_cast<int>(message->type),
                message->issuer_host_name.c_str());
      delete message;
  }
}

void Node::operator()()
{
  sg4::this_actor::sleep_for(start_time_);
  if (known_id_ == -1) {
    setPredecessor(-1); // -1 means that I have no predecessor
    printFingerTable();
    joined_ = true;
  } else {
    join(known_id_);
  }

  if (not joined_)
    return;
  ChordMessage* message              = nullptr;
  double now                         = sg4::Engine::get_clock();
  double next_stabilize_date         = start_time_ + PERIODIC_STABILIZE_DELAY;
  double next_fix_fingers_date       = start_time_ + PERIODIC_FIX_FINGERS_DELAY;
  double next_check_predecessor_date = start_time_ + PERIODIC_CHECK_PREDECESSOR_DELAY;
  double next_lookup_date            = start_time_ + PERIODIC_LOOKUP_DELAY;
  sg4::CommPtr comm_receive          = nullptr;
  while (now < std::min(start_time_ + deadline_, MAX_SIMULATION_TIME)) {
    if (comm_receive == nullptr)
      comm_receive = mailbox_->get_async<ChordMessage>(&message);
    bool comm_completed = true;
    try {
      if (not comm_receive->test())
        comm_completed = false;
    } catch (const simgrid::TimeoutException&) {
      XBT_DEBUG("Caught a timeout, go ahead.");
    }

    if (comm_completed) {
      if (message != nullptr) {
        handleMessage(message);
        message = nullptr;
      }
      comm_receive = nullptr;
    } else {
      // no task was received: make some periodic calls
      if (now >= next_stabilize_date) {
        stabilize();
        next_stabilize_date = sg4::Engine::get_clock() + PERIODIC_STABILIZE_DELAY;
      } else if (now >= next_fix_fingers_date) {
        fixFingers();
        next_fix_fingers_date = sg4::Engine::get_clock() + PERIODIC_FIX_FINGERS_DELAY;
      } else if (now >= next_check_predecessor_date) {
        checkPredecessor();
        next_check_predecessor_date = sg4::Engine::get_clock() + PERIODIC_CHECK_PREDECESSOR_DELAY;
      } else if (now >= next_lookup_date) {
        randomLookup();
        next_lookup_date = sg4::Engine::get_clock() + PERIODIC_LOOKUP_DELAY;
      } else {
        // nothing to do: sleep for a while
        sg4::this_actor::sleep_for(SLEEP_DELAY);
      }
    }

    now = sg4::Engine::get_clock();
  }
  if (comm_receive != nullptr) {
    try {
      if (comm_receive->test())
        delete message;
      else
        comm_receive->cancel();
    } catch (const simgrid::TimeoutException&) {
      XBT_DEBUG("Caught a timeout for last message, nevermind.");
    }
  }
  // leave the ring
  leave();
}

Kademlia

Another well-known DHT protocol.

View examples/cpp/dht-kademlia/s4u-dht-kademlia.cpp

Download s4u-dht-kademlia.cpp

/* Copyright (c) 2012-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "s4u-dht-kademlia.hpp"

#include "message.hpp"
#include "node.hpp"
#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(kademlia, "Messages specific for this example");
namespace sg4 = simgrid::s4u;

/** @brief Node function
  * @param my node ID
  * @param the ID of the person I know in the system (or not)
  * @param Time before I leave the system because I'm bored
  */
static void node(std::vector<std::string> args)
{
  bool join_success = true;
  double deadline;
  xbt_assert(args.size() == 3 || args.size() == 4, "Wrong number of arguments");
  /* Node initialization */
  auto node_id = static_cast<unsigned int>(std::stoul(args[1], nullptr, 0));
  kademlia::Node node(node_id);

  if (args.size() == 4) {
    XBT_INFO("Hi, I'm going to join the network with id %u", node.getId());
    auto known_id = static_cast<unsigned int>(std::stoul(args[2], nullptr, 0));
    join_success  = node.join(known_id);
    deadline      = std::stod(args[3]);
  } else {
    deadline = std::stod(args[2]);
    XBT_INFO("Hi, I'm going to create the network with id %u", node.getId());
    node.routingTableUpdate(node.getId());
  }

  if (join_success) {
    XBT_VERB("Ok, I'm joining the network with id %u", node.getId());
    // We start the main loop
    double next_lookup_time = sg4::Engine::get_clock() + RANDOM_LOOKUP_INTERVAL;

    XBT_VERB("Main loop start");

    sg4::Mailbox* mailbox = sg4::Mailbox::by_name(std::to_string(node.getId()));

    while (sg4::Engine::get_clock() < deadline) {
      if (kademlia::Message* msg = node.receive(mailbox)) {
        // There has been a message, we need to handle it !
        node.handleFindNode(msg);
        delete msg;
      } else {
        /* We search for a pseudo random node */
        if (sg4::Engine::get_clock() >= next_lookup_time) {
          node.randomLookup();
          next_lookup_time += RANDOM_LOOKUP_INTERVAL;
        } else {
          // Didn't get a message: sleep for a while...
          sg4::this_actor::sleep_for(1);
        }
      }
    }
  } else {
    XBT_INFO("I couldn't join the network :(");
  }
  XBT_DEBUG("I'm leaving the network");
  node.displaySuccessRate();
}

/** @brief Main function */
int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);

  /* Check the arguments */
  xbt_assert(argc > 2,
             "Usage: %s platform_file deployment_file\n\tExample: %s cluster_backbone.xml dht-kademlia_d.xml\n",
             argv[0], argv[0]);

  e.load_platform(argv[1]);
  e.register_function("node", node);
  e.load_deployment(argv[2]);

  e.run();

  XBT_INFO("Simulated time: %g", sg4::Engine::get_clock());

  return 0;
}

View examples/cpp/dht-kademlia/routing_table.cpp

Download routing_table.cpp

/* Copyright (c) 2012-2024. The SimGrid Team.
 * All rights reserved.                                                     */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "routing_table.hpp"
#include "node.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(kademlia_routing_table, "Messages specific for this example");

namespace kademlia {

/** @brief Initialization of a node routing table.  */
RoutingTable::RoutingTable(unsigned int node_id) : id_(node_id)
{
  buckets_.reserve(IDENTIFIER_SIZE + 1);
  for (unsigned int i = 0; i < IDENTIFIER_SIZE + 1; i++)
    buckets_.emplace_back(i);
}

void RoutingTable::print() const
{
  XBT_INFO("Routing table of %08x:", id_);

  for (unsigned int i = 0; i <= IDENTIFIER_SIZE; i++) {
    if (not buckets_[i].nodes_.empty()) {
      XBT_INFO("Bucket number %u: ", i);
      int j = 0;
      for (auto value : buckets_[i].nodes_) {
        XBT_INFO("Element %d: %08x", j, value);
        j++;
      }
    }
  }
}

/** @brief Finds the corresponding bucket in a routing table for a given identifier
  * @param id the identifier
  * @return the bucket in which the the identifier would be.
  */
Bucket* RoutingTable::findBucket(unsigned int id)
{
  unsigned int xor_number = id_ ^ id;
  unsigned int prefix     = get_node_prefix(xor_number, IDENTIFIER_SIZE);
  xbt_assert(prefix <= IDENTIFIER_SIZE, "Tried to return a  bucket that doesn't exist.");
  return &buckets_[prefix];
}

/** Returns if the routing table contains the id. */
bool RoutingTable::contains(unsigned int node_id)
{
  const Bucket* bucket = findBucket(node_id);
  return std::find(bucket->nodes_.begin(), bucket->nodes_.end(), node_id) != bucket->nodes_.end();
}
} // namespace kademlia

View examples/cpp/dht-kademlia/answer.cpp

Download answer.cpp

/* Copyright (c) 2012-2024. The SimGrid Team.
 * All rights reserved.                                                     */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "answer.hpp"

XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(kademlia_node);

namespace kademlia {

/** @brief Prints an Answer, for debugging purposes */
void Answer::print() const
{
  XBT_INFO("Searching %08x, size %zu", destination_id_, nodes_.size());
  unsigned int i = 0;
  for (auto const& [contact, distance] : nodes_)
    XBT_INFO("Node %08x: %08x is at distance %u", i++, contact, distance);
}

/** @brief Merge two answers together, only keeping the best nodes
  * @param source the source of the nodes to add
  */
unsigned int Answer::merge(const Answer* source)
{
  if (this == source)
    return 0;

  unsigned int nb_added = 0;
  for (auto const& contact : source->nodes_) {
    if (std::find(nodes_.begin(), nodes_.end(), contact) == nodes_.end()) {
      nodes_.push_back(contact);
      nb_added++;
    }
  }
  trim();
  return nb_added;
}

/** @brief Trims an Answer, in order for it to have a size of less or equal to "bucket_size" */
void Answer::trim()
{
  // sort by distance
  std::sort(nodes_.begin(), nodes_.end(),
            [](const std::pair<unsigned int, unsigned int>& a, const std::pair<unsigned int, unsigned int>& b) {
              return (a.second < b.second);
            });
  if (nodes_.size() > BUCKET_SIZE)
    nodes_.resize(BUCKET_SIZE);
}

/** @brief Returns if the destination we are trying to find is found
  * @return if the destination is found.
  */
bool Answer::destinationFound() const
{
  return not nodes_.empty() && nodes_.begin()->second == 0;
}

/** @brief Adds the content of a bucket unsigned into an answer object.
 * @param bucket the bucket we have to had unsigned into
 */
void Answer::addBucket(const Bucket* bucket)
{
  xbt_assert((bucket != nullptr), "Provided a NULL bucket");

  for (auto const& id : bucket->nodes_) {
    unsigned int distance = id ^ destination_id_;
    nodes_.emplace_back(id, distance);
  }
}
} // namespace kademlia

View examples/cpp/dht-kademlia/node.cpp

Download node.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "node.hpp"
#include "routing_table.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(kademlia_node, "Messages specific for this example");
namespace sg4 = simgrid::s4u;

namespace kademlia {
static void destroy(void* message)
{
  const auto* msg = static_cast<Message*>(message);
  delete msg;
}

/**
  * Try to asynchronously get a new message from given mailbox. Return null if none available.
  */
Message* Node::receive(sg4::Mailbox* mailbox)
{
  if (receive_comm == nullptr)
    receive_comm = mailbox->get_async<kademlia::Message>(&received_msg);
  if (not receive_comm->test())
    return nullptr;
  receive_comm = nullptr;
  return received_msg;
}

/**
  * @brief Tries to join the network
  * @param known_id id of the node I know in the network.
  */
bool Node::join(unsigned int known_id)
{
  bool got_answer = false;

  /* Add the guy we know to our routing table and ourselves. */
  routingTableUpdate(id_);
  routingTableUpdate(known_id);

  /* First step: Send a "FIND_NODE" request to the node we know */
  sendFindNode(known_id, id_);

  sg4::Mailbox* mailbox = sg4::Mailbox::by_name(std::to_string(id_));
  do {
    if (Message* msg = receive(mailbox)) {
      XBT_DEBUG("Received an answer from the node I know.");
      got_answer = true;
      // retrieve the node list and ping them.
      if (const Answer* node_list = msg->answer_.get()) {
        for (auto const& [contact, _] : node_list->getNodes())
          routingTableUpdate(contact);
      } else {
        handleFindNode(msg);
      }
      delete msg;
    } else
      sg4::this_actor::sleep_for(1);
  } while (not got_answer);

  /* Second step: Send a FIND_NODE to a random node in buckets */
  unsigned int bucket_id = table.findBucket(known_id)->getId();
  xbt_assert(bucket_id <= IDENTIFIER_SIZE);
  for (unsigned int i = 0; ((bucket_id > i) || (bucket_id + i) <= IDENTIFIER_SIZE) && i < JOIN_BUCKETS_QUERIES; i++) {
    if (bucket_id > i) {
      unsigned int id_in_bucket = get_id_in_prefix(id_, bucket_id - i);
      findNode(id_in_bucket, false);
    }
    if (bucket_id + i <= IDENTIFIER_SIZE) {
      unsigned int id_in_bucket = get_id_in_prefix(id_, bucket_id + i);
      findNode(id_in_bucket, false);
    }
  }
  return got_answer;
}

/** @brief Send a "FIND_NODE" to a node
  * @param id node we are querying
  * @param destination node we are trying to find.
  */
void Node::sendFindNode(unsigned int id, unsigned int destination) const
{
  /* Gets the mailbox to send to */
  sg4::Mailbox* mailbox = sg4::Mailbox::by_name(std::to_string(id));
  /* Build the task */

  auto* msg =
      new Message(id_, destination, sg4::Mailbox::by_name(std::to_string(id_)), sg4::Host::current()->get_cname());

  /* Send the task */
  mailbox->put_init(msg, 1)->detach(kademlia::destroy);
  XBT_VERB("Asking %u for its closest nodes", id);
}

/**
  * Sends to the best "KADEMLIA_ALPHA" nodes in the "node_list" array a "FIND_NODE" request, to ask them for their best
  * nodes
  */
unsigned int Node::sendFindNodeToBest(const Answer* node_list) const
{
  unsigned int i           = 0;
  unsigned int j           = 0;
  unsigned int destination = node_list->getDestinationId();
  for (auto const& [node_to_query, _] : node_list->getNodes()) {
    /* We need to have at most "KADEMLIA_ALPHA" requests each time, according to the protocol */
    /* Gets the node we want to send the query to */
    if (node_to_query != id_) { /* No need to query ourselves */
      sendFindNode(node_to_query, destination);
      j++;
    }
    i++;
    if (j == KADEMLIA_ALPHA)
      break;
  }
  return i;
}

/** @brief Updates/Puts the node id unsigned into our routing table
  * @param id The id of the node we need to add unsigned into our routing table
  */
void Node::routingTableUpdate(unsigned int id)
{
  // retrieval of the bucket in which the should be
  Bucket* bucket = table.findBucket(id);

  // check if the id is already in the bucket.
  auto id_pos = std::find(bucket->nodes_.begin(), bucket->nodes_.end(), id);

  if (id_pos == bucket->nodes_.end()) {
    /* We check if the bucket is full or not. If it is, we evict an old element */
    if (bucket->nodes_.size() >= BUCKET_SIZE) {
      bucket->nodes_.pop_back();
    }
    bucket->nodes_.push_front(id);
    XBT_VERB("I'm adding to my routing table %08x", id);
  } else {
    // We push the element to the front
    bucket->nodes_.erase(id_pos);
    bucket->nodes_.push_front(id);
    XBT_VERB("I'm updating %08x", id);
  }
}

/** @brief Finds the closest nodes to the node given.
  * @param node : our node
  * @param destination_id : the id of the guy we are trying to find
  */
std::unique_ptr<Answer> Node::findClosest(unsigned int destination_id)
{
  auto answer = std::make_unique<Answer>(destination_id);
  /* We find the corresponding bucket for the id */
  const Bucket* bucket = table.findBucket(destination_id);
  int bucket_id  = bucket->getId();
  xbt_assert((bucket_id <= IDENTIFIER_SIZE), "Bucket found has a wrong identifier");
  /* So, we copy the contents of the bucket unsigned into our answer */
  answer->addBucket(bucket);

  /* However, if we don't have enough elements in our bucket, we NEED to include at least "BUCKET_SIZE" elements
   * (if, of course, we know at least "BUCKET_SIZE" elements. So we're going to look unsigned into the other buckets.
   */
  for (int i = 1; answer->getSize() < BUCKET_SIZE && ((bucket_id - i > 0) || (bucket_id + i < IDENTIFIER_SIZE)); i++) {
    /* We check the previous buckets */
    if (bucket_id - i >= 0) {
      const Bucket* bucket_p = &table.getBucketAt(bucket_id - i);
      answer->addBucket(bucket_p);
    }
    /* We check the next buckets */
    if (bucket_id + i <= IDENTIFIER_SIZE) {
      const Bucket* bucket_n = &table.getBucketAt(bucket_id + i);
      answer->addBucket(bucket_n);
    }
  }
  /* We trim the array to have only BUCKET_SIZE or less elements */
  answer->trim();

  return answer;
}

/** @brief Send a request to find a node in the node routing table.
  * @param id_to_find the id of the node we are trying to find
  */
bool Node::findNode(unsigned int id_to_find, bool count_in_stats)
{
  unsigned int queries;
  unsigned int answers;
  bool destination_found   = false;
  unsigned int nodes_added = 0;
  double global_timeout    = sg4::Engine::get_clock() + FIND_NODE_GLOBAL_TIMEOUT;
  unsigned int steps       = 0;

  /* First we build a list of who we already know */
  std::unique_ptr<Answer> node_list = findClosest(id_to_find);
  xbt_assert((node_list != nullptr), "node_list incorrect");
  XBT_DEBUG("Doing a FIND_NODE on %08x", id_to_find);

  /* Ask the nodes on our list if they have information about the node we are trying to find */
  do {
    answers        = 0;
    queries        = sendFindNodeToBest(node_list.get());
    nodes_added    = 0;
    double timeout = sg4::Engine::get_clock() + FIND_NODE_TIMEOUT;
    steps++;
    double time_beginreceive = sg4::Engine::get_clock();

    sg4::Mailbox* mailbox = sg4::Mailbox::by_name(std::to_string(id_));
    do {
      if (Message* msg = receive(mailbox)) {
        // Check if what we have received is what we are looking for.
        if (msg->answer_ && msg->answer_->getDestinationId() == id_to_find) {
          routingTableUpdate(msg->sender_id_);
          // Handle the answer
          for (auto const& [contact, _] : node_list->getNodes())
            routingTableUpdate(contact);
          answers++;

          nodes_added = node_list->merge(msg->answer_.get());
          XBT_DEBUG("Received an answer from %s (%s) with %zu nodes on it", msg->answer_to_->get_cname(),
                    msg->issuer_host_name_.c_str(), msg->answer_->getSize());
        } else {
          if (msg->answer_) {
            routingTableUpdate(msg->sender_id_);
            XBT_DEBUG("Received a wrong answer for a FIND_NODE");
          } else {
            handleFindNode(msg);
          }
          // Update the timeout if we didn't have our answer
          timeout += sg4::Engine::get_clock() - time_beginreceive;
          time_beginreceive = sg4::Engine::get_clock();
        }
        delete msg;
      } else {
        sg4::this_actor::sleep_for(1);
      }
    } while (sg4::Engine::get_clock() < timeout && answers < queries);
    destination_found = node_list->destinationFound();
  } while (not destination_found && (nodes_added > 0 || answers == 0) && sg4::Engine::get_clock() < global_timeout &&
           steps < MAX_STEPS);

  if (destination_found) {
    if (count_in_stats)
      find_node_success++;
    if (queries > 4)
      XBT_VERB("FIND_NODE on %08x success in %u steps", id_to_find, steps);
    routingTableUpdate(id_to_find);
  } else {
    if (count_in_stats) {
      find_node_failed++;
      XBT_VERB("%08x not found in %u steps", id_to_find, steps);
    }
  }
  return destination_found;
}

/** @brief Does a pseudo-random lookup for someone in the system
  * @param node caller node data
  */
void Node::randomLookup()
{
  unsigned int id_to_look = RANDOM_LOOKUP_NODE; // Totally random.
  /* TODO: Use some pseudo-random generator. */
  XBT_DEBUG("I'm doing a random lookup");
  findNode(id_to_look, true);
}

/** @brief Handles the answer to an incoming "find_node" task */
void Node::handleFindNode(const Message* msg)
{
  routingTableUpdate(msg->sender_id_);
  XBT_VERB("Received a FIND_NODE from %s (%s), he's trying to find %08x", msg->answer_to_->get_cname(),
           msg->issuer_host_name_.c_str(), msg->destination_id_);
  // Building the answer to the request
  auto* answer = new Message(id_, msg->destination_id_, findClosest(msg->destination_id_),
                             sg4::Mailbox::by_name(std::to_string(id_)), sg4::Host::current()->get_cname());
  // Sending the answer
  msg->answer_to_->put_init(answer, 1)->detach(kademlia::destroy);
}

void Node::displaySuccessRate() const
{
  XBT_INFO("%u/%u FIND_NODE have succeeded", find_node_success, find_node_success + find_node_failed);
}
} // namespace kademlia
/**@brief Returns an identifier which is in a specific bucket of a routing table
 * @param id id of the routing table owner
 * @param prefix id of the bucket where we want that identifier to be
 */
unsigned int get_id_in_prefix(unsigned int id, unsigned int prefix)
{
  if (prefix == 0) {
    return 0;
  } else {
    return (1U << (prefix - 1)) ^ id;
  }
}

/** @brief Returns the prefix of an identifier.
  * The prefix is the id of the bucket in which the remote identifier xor our identifier should be stored.
  * @param id : big unsigned int id to test
  * @param nb_bits : key size
  */
unsigned int get_node_prefix(unsigned int id, unsigned int nb_bits)
{
  unsigned int size = sizeof(unsigned int) * 8;
  for (unsigned int j = 0; j < size; j++) {
    if (((id >> (size - 1 - j)) & 0x1) != 0) {
      return nb_bits - j;
    }
  }
  return 0;
}

Pastry

Yet another well-known DHT protocol.

View examples/c/dht-pastry/dht-pastry.c

Download dht-pastry.c

/* Copyright (c) 2013-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/actor.h"
#include "simgrid/comm.h"
#include "simgrid/engine.h"
#include "simgrid/mailbox.h"

#include "xbt/dynar.h"
#include "xbt/ex.h"
#include "xbt/log.h"
#include "xbt/str.h"
#include "xbt/sysdep.h"

#include <stdio.h>

XBT_LOG_NEW_DEFAULT_CATEGORY(pastry, "Messages specific for this example");

#define COMM_SIZE 10
#define COMP_SIZE 0
#define MAILBOX_NAME_SIZE 10

#define DOMAIN_SIZE 4
#define LEVELS_COUNT 8 // sizeof(int)*8/DOMAIN_SIZE
#define LEVEL_SIZE 16  // 2^DOMAIN_SIZE
#define NEIGHBORHOOD_SIZE 6
#define NAMESPACE_SIZE 6
#define MAILBOX_NAME_SIZE 10

static int nb_bits             = 16;
static int timeout             = 50;
static int max_simulation_time = 1000;

typedef struct s_node {
  int id; // 128bits generated random(2^128 -1)
  int known_id;
  sg_mailbox_t mailbox;
  int namespace_set[NAMESPACE_SIZE];
  int neighborhood_set[NEIGHBORHOOD_SIZE];
  int routing_table[LEVELS_COUNT][LEVEL_SIZE];
  int ready;
  sg_comm_t comm_receive; // current communication to receive
  xbt_dynar_t pending_messages;
} s_node_t;
typedef s_node_t* node_t;
typedef const s_node_t* const_node_t;

typedef struct s_state {
  int id;
  int namespace_set[NAMESPACE_SIZE];
  int neighborhood_set[NEIGHBORHOOD_SIZE];
  int routing_table[LEVELS_COUNT][LEVEL_SIZE];
} s_state_t;
typedef s_state_t* state_t;

/** Types of tasks exchanged between nodes. */
typedef enum { JOIN, JOIN_REPLY, JOIN_LAST_REPLY, UPDATE } e_message_type_t;

typedef struct s_pastry_message {
  e_message_type_t type; // type of task
  int sender_id;         // id parameter (used by some types of tasks)
  // int request_finger;           // finger parameter (used by some types of tasks)
  int answer_id;          // answer (used by some types of tasks)
  sg_mailbox_t answer_to; // mailbox to send an answer to (if any)
  int steps;
  state_t state;
} s_pastry_message_t;
typedef s_pastry_message_t* pastry_message_t;
typedef const s_pastry_message_t* const_pastry_message_t;

/** Get the mailbox of a host given its pastry id. */
static sg_mailbox_t get_mailbox(int node_id)
{
  char mailbox_name[MAILBOX_NAME_SIZE];
  snprintf(mailbox_name, MAILBOX_NAME_SIZE - 1, "%d", node_id);
  return sg_mailbox_by_name(mailbox_name);
}

/** Get the specific level of a node id */
unsigned int domain_mask = 0;
static int domain(unsigned int a, unsigned int level)
{
  if (domain_mask == 0)
    domain_mask = (1U << DOMAIN_SIZE) - 1;
  unsigned int shift = (LEVELS_COUNT - level - 1) * DOMAIN_SIZE;
  return (a >> shift) & domain_mask;
}

/* Get the shared domains between the two givens ids */
static int shl(int a, int b)
{
  int l = 0;
  while (l < LEVELS_COUNT && domain(a, l) == domain(b, l))
    l++;
  return l;
}

/* Frees the memory used by a task and destroy it */
static void message_free(pastry_message_t message)
{
  if (message != NULL) {
    xbt_free(message->state);
    xbt_free(message);
  }
}

/* Get the closest id to the dest in the node namespace_set */
static int closest_in_namespace_set(const_node_t node, int dest)
{
  int res = -1;
  if ((node->namespace_set[NAMESPACE_SIZE - 1] <= dest) && (dest <= node->namespace_set[0])) {
    int best_dist = abs(node->id - dest);
    res           = node->id;
    for (int i = 0; i < NAMESPACE_SIZE; i++) {
      if (node->namespace_set[i] != -1) {
        int dist = abs(node->namespace_set[i] - dest);
        if (dist < best_dist) {
          best_dist = dist;
          res       = node->namespace_set[i];
        }
      }
    }
  }
  return res;
}

/* Find the next node to forward a message to */
static int routing_next(const_node_t node, int dest)
{
  int closest = closest_in_namespace_set(node, dest);
  if (closest != -1)
    return closest;

  int l   = shl(node->id, dest);
  int res = node->routing_table[l][domain(dest, l)];
  if (res != -1)
    return res;

  // rare case
  int dist = abs(node->id - dest);
  for (int i = l; i < LEVELS_COUNT; i++) {
    for (int j = 0; j < LEVEL_SIZE; j++) {
      res = node->routing_table[i][j];
      if (res != -1 && abs(res - dest) < dist)
        return res;
    }
  }

  for (int i = 0; i < NEIGHBORHOOD_SIZE; i++) {
    res = node->neighborhood_set[i];
    if (res != -1 && shl(res, dest) >= l && abs(res - dest) < dist)
      return res;
  }

  for (int i = 0; i < NAMESPACE_SIZE; i++) {
    res = node->namespace_set[i];
    if (res != -1 && shl(res, dest) >= l && abs(res - dest) < dist)
      return res;
  }

  return node->id;
}

/* Get the corresponding state of a node */
static state_t node_get_state(const_node_t node)
{
  state_t state = xbt_new0(s_state_t, 1);
  state->id     = node->id;
  for (int i = 0; i < NEIGHBORHOOD_SIZE; i++)
    state->neighborhood_set[i] = node->neighborhood_set[i];

  for (int i = 0; i < LEVELS_COUNT; i++)
    for (int j = 0; j < LEVEL_SIZE; j++)
      state->routing_table[i][j] = node->routing_table[i][j];

  for (int i = 0; i < NAMESPACE_SIZE; i++)
    state->namespace_set[i] = node->namespace_set[i];

  return state;
}

static void print_node_id(const_node_t node)
{
  XBT_INFO(" Id: %i '%08x'", node->id, (unsigned)node->id);
}

/* Print the node namespace set */
static void print_node_namespace_set(const_node_t node)
{
  XBT_INFO(" Namespace:");
  for (int i = 0; i < NAMESPACE_SIZE; i++)
    XBT_INFO("  %08x", (unsigned)node->namespace_set[i]);
}

/** Handle a given task */
static void handle_message(node_t node, pastry_message_t message)
{
  XBT_DEBUG("Handling task %p", message);
  int i;
  int j;
  int min;
  int max;
  int next;
  sg_mailbox_t mailbox;
  sg_comm_t comm = NULL;
  sg_error_t err = SG_OK;
  pastry_message_t request;
  e_message_type_t type = message->type;

  // If the node is not ready keep the task for later
  if (node->ready != 0 && !(type == JOIN_LAST_REPLY || type == JOIN_REPLY)) {
    XBT_DEBUG("Task pending %u", type);
    xbt_dynar_push(node->pending_messages, &message);
    return;
  }

  switch (type) {
    /* Try to join the ring */
    case JOIN:
      next = routing_next(node, message->answer_id);
      XBT_DEBUG("Join request from %08x forwarding to %08x", (unsigned)message->answer_id, (unsigned)next);
      type = JOIN_LAST_REPLY;

      request            = xbt_new0(s_pastry_message_t, 1);
      request->answer_id = message->sender_id;
      request->steps     = message->steps + 1;

      // if next different from current node forward the join
      if (next != node->id) {
        mailbox            = get_mailbox(next);
        message->sender_id = node->id;
        message->steps++;
        comm = sg_mailbox_put_async(mailbox, message, COMM_SIZE);
        err  = sg_comm_wait_for(comm, timeout);
        if (err == SG_ERROR_TIMEOUT) {
          XBT_DEBUG("Timeout expired when forwarding join to next %d", next);
          xbt_free(request);
          break;
        }
        type = JOIN_REPLY;
      }

      // send back the current node state to the joining node
      request->type      = type;
      request->sender_id = node->id;
      request->answer_to = get_mailbox(node->id);
      request->state     = node_get_state(node);
      comm               = sg_mailbox_put_async(message->answer_to, request, COMM_SIZE);
      err                = sg_comm_wait_for(comm, timeout);
      if (err == SG_ERROR_TIMEOUT) {
        XBT_DEBUG("Timeout expired when sending back the current node state to the joining node to %d", node->id);
        message_free(request);
      }
      break;
    /* Join reply from all the node touched by the join  */
    case JOIN_LAST_REPLY:
      // if last node touched reply, copy its namespace set
      // TODO: it works only if the two nodes are side to side (is it really the case ?)
      j = (message->sender_id < node->id) ? -1 : 0;
      for (i = 0; i < NAMESPACE_SIZE / 2; i++) {
        node->namespace_set[i]                      = message->state->namespace_set[i - j];
        node->namespace_set[NAMESPACE_SIZE - 1 - i] = message->state->namespace_set[NAMESPACE_SIZE - 1 - i - j - 1];
      }
      node->namespace_set[NAMESPACE_SIZE / 2 + j] = message->sender_id;
      node->ready += message->steps + 1;
      /* fallthrough */
    case JOIN_REPLY:
      XBT_DEBUG("Joining Reply");

      // if first node touched reply, copy its neighborhood set
      if (message->sender_id == node->known_id) {
        node->neighborhood_set[0] = message->sender_id;
        for (i = 1; i < NEIGHBORHOOD_SIZE; i++)
          node->neighborhood_set[i] = message->state->neighborhood_set[i - 1];
      }

      // copy the corresponding routing table levels
      min = (node->id == message->answer_id) ? 0 : shl(node->id, message->answer_id);
      max = shl(node->id, message->sender_id) + 1;
      for (i = min; i < max; i++) {
        int d = domain(node->id, i);
        for (j = 0; j < LEVEL_SIZE; j++)
          if (d != j)
            node->routing_table[i][j] = message->state->routing_table[i][j];
      }

      node->ready--;
      // if the node is ready, do all the pending tasks and send update to known nodes
      if (node->ready == 0) {
        XBT_DEBUG("Node %i is ready!!!", node->id);
        while (!xbt_dynar_is_empty(node->pending_messages)) {
          pastry_message_t m;
          xbt_dynar_shift(node->pending_messages, &m);
          handle_message(node, m);
        }

        for (i = 0; i < NAMESPACE_SIZE; i++) {
          j = node->namespace_set[i];
          if (j != -1) {
            XBT_DEBUG("Send update to %i", j);
            mailbox = get_mailbox(j);

            request            = xbt_new0(s_pastry_message_t, 1);
            request->answer_id = node->id;
            request->steps     = 0;
            request->type      = UPDATE;
            request->sender_id = node->id;
            request->answer_to = get_mailbox(node->id);
            request->state     = node_get_state(node);
            comm               = sg_mailbox_put_async(mailbox, request, COMM_SIZE);
            err                = sg_comm_wait_for(comm, timeout);
            if (err == SG_ERROR_TIMEOUT) {
              XBT_DEBUG("Timeout expired when sending update to %d", j);
              message_free(request);
              break;
            }
          }
        }
      }
      break;
    /* Received an update of state */
    case UPDATE:
      XBT_DEBUG("Task update %i !!!", node->id);

      /* Update namespace ses */
      XBT_INFO("Task update from %i !!!", message->sender_id);
      XBT_INFO("Node:");
      print_node_id(node);
      print_node_namespace_set(node);
      int curr_namespace_set[NAMESPACE_SIZE];
      int task_namespace_set[NAMESPACE_SIZE + 1];

      // Copy the current namespace and the task state namespace with state->id in the middle
      i = 0;
      for (; i < NAMESPACE_SIZE / 2; i++) {
        curr_namespace_set[i] = node->namespace_set[i];
        task_namespace_set[i] = message->state->namespace_set[i];
      }
      task_namespace_set[i] = message->state->id;
      for (; i < NAMESPACE_SIZE; i++) {
        curr_namespace_set[i]     = node->namespace_set[i];
        task_namespace_set[i + 1] = message->state->namespace_set[i];
      }

      // get the index of values before and after node->id in task_namespace
      min = -1;
      max = -1;
      for (i = 0; i <= NAMESPACE_SIZE; i++) {
        j = task_namespace_set[i];
        if (j != -1 && j < node->id)
          min = i;
        if (j != -1 && max == -1 && j > node->id)
          max = i;
      }

      // add lower elements
      j = NAMESPACE_SIZE / 2 - 1;
      for (i = NAMESPACE_SIZE / 2 - 1; i >= 0; i--) {
        if (min < 0 || curr_namespace_set[j] > task_namespace_set[min]) {
          node->namespace_set[i] = curr_namespace_set[j];
          j--;
        } else if (curr_namespace_set[j] == task_namespace_set[min]) {
          node->namespace_set[i] = curr_namespace_set[j];
          j--;
          min--;
        } else {
          node->namespace_set[i] = task_namespace_set[min];
          min--;
        }
      }

      // add greater elements
      j = NAMESPACE_SIZE / 2;
      for (i = NAMESPACE_SIZE / 2; i < NAMESPACE_SIZE; i++) {
        if (min < 0 || max >= NAMESPACE_SIZE) {
          node->namespace_set[i] = curr_namespace_set[j];
          j++;
        } else if (max >= 0) {
          if (curr_namespace_set[j] == -1 || curr_namespace_set[j] > task_namespace_set[max]) {
            node->namespace_set[i] = task_namespace_set[max];
            max++;
          } else if (curr_namespace_set[j] == task_namespace_set[max]) {
            node->namespace_set[i] = curr_namespace_set[j];
            j++;
            max++;
          } else {
            node->namespace_set[i] = curr_namespace_set[j];
            j++;
          }
        }
      }

      /* Update routing table */
      for (i = shl(node->id, message->state->id); i < LEVELS_COUNT; i++) {
        for (j = 0; j < LEVEL_SIZE; j++) {
          // FIXME: this is a no-op!
          if (node->routing_table[i][j] == -1 && message->state->routing_table[i][j] == -1)
            node->routing_table[i][j] = message->state->routing_table[i][j];
        }
      }
      break;
    default:
      THROW_IMPOSSIBLE;
  }
  message_free(message);
}

/* Join the ring */
static int join(const_node_t node)
{
  pastry_message_t request = xbt_new0(s_pastry_message_t, 1);
  request->type            = JOIN;
  request->sender_id       = node->id;
  request->answer_id       = node->id;
  request->steps           = 0;
  request->answer_to       = get_mailbox(node->id);

  sg_mailbox_t mailbox = get_mailbox(node->known_id);

  XBT_DEBUG("Trying to join Pastry ring... (with node %s)", sg_mailbox_get_name(mailbox));
  sg_comm_t comm = sg_mailbox_put_async(mailbox, request, COMM_SIZE);
  sg_error_t err = sg_comm_wait_for(comm, timeout);
  if (err == SG_ERROR_TIMEOUT) {
    XBT_DEBUG("Timeout expired when joining ring with node %d", node->known_id);
    message_free(request);
    return 0;
  }

  return 1;
}

/**
 * @brief Node Function
 * Arguments:
 * - my id
 * - the id of a guy I know in the system (except for the first node)
 * - the time to sleep before I join (except for the first node)
 * - the deadline time
 */
static void node(int argc, char* argv[])
{
  double init_time = simgrid_get_clock();
  void* received   = NULL;
  int join_success = 0;
  double deadline;
  xbt_assert(argc == 3 || argc == 5, "Wrong number of arguments for this node");
  s_node_t node         = {0};
  node.id               = (int)xbt_str_parse_int(argv[1], "Invalid ID");
  node.known_id         = -1;
  node.ready            = -1;
  node.pending_messages = xbt_dynar_new(sizeof(pastry_message_t), NULL);
  node.mailbox          = get_mailbox(node.id);

  XBT_DEBUG("New node with id %s (%08x)", sg_mailbox_get_name(node.mailbox), (unsigned)node.id);

  for (int i = 0; i < LEVELS_COUNT; i++) {
    int d = domain(node.id, i);
    for (int j = 0; j < LEVEL_SIZE; j++)
      node.routing_table[i][j] = (d == j) ? node.id : -1;
  }

  for (int i = 0; i < NEIGHBORHOOD_SIZE; i++)
    node.neighborhood_set[i] = -1;

  for (int i = 0; i < NAMESPACE_SIZE; i++)
    node.namespace_set[i] = -1;

  if (argc == 3) { // first ring
    XBT_DEBUG("Hey! Let's create the system.");
    deadline   = xbt_str_parse_double(argv[2], "Invalid deadline");
    node.ready = 0;
    XBT_DEBUG("Create a new Pastry ring...");
    join_success = 1;
  } else {
    node.known_id     = (int)xbt_str_parse_int(argv[2], "Invalid known ID");
    double sleep_time = xbt_str_parse_double(argv[3], "Invalid sleep time");
    deadline          = xbt_str_parse_double(argv[4], "Invalid deadline");

    // sleep before starting
    XBT_DEBUG("Let's sleep during %f", sleep_time);
    sg_actor_sleep_for(sleep_time);
    XBT_DEBUG("Hey! Let's join the system.");

    join_success = join(&node);
  }

  if (join_success) {
    XBT_DEBUG("Waiting ….");

    while (simgrid_get_clock() < init_time + deadline && simgrid_get_clock() < max_simulation_time) {
      if (node.comm_receive == NULL) {
        received          = NULL;
        node.comm_receive = sg_mailbox_get_async(node.mailbox, &received);
      }
      if (!sg_comm_test(node.comm_receive)) {
        sg_actor_sleep_for(5);
      } else {
        // the task was successfully received
        handle_message(&node, received);
        node.comm_receive = NULL;
      }
    }
    // Cleanup the receiving communication.
    if (node.comm_receive != NULL)
      sg_comm_unref(node.comm_receive);
  }
  xbt_dynar_free(&node.pending_messages);
}

/** @brief Main function. */
int main(int argc, char* argv[])
{
  simgrid_init(&argc, argv);
  xbt_assert(argc > 2,
             "Usage: %s [-nb_bits=n] [-timeout=t] platform_file deployment_file\n"
             "\tExample: %s ../platform.xml pastry10.xml\n",
             argv[0], argv[0]);

  char** options = &argv[1];
  while (!strncmp(options[0], "-", 1)) {
    size_t length = strlen("-nb_bits=");
    if (!strncmp(options[0], "-nb_bits=", length) && strlen(options[0]) > length) {
      nb_bits = (int)xbt_str_parse_int(options[0] + length, "Invalid nb_bits parameter");
      XBT_DEBUG("Set nb_bits to %d", nb_bits);
    } else {
      length = strlen("-timeout=");
      xbt_assert(strncmp(options[0], "-timeout=", length) == 0 && strlen(options[0]) > length,
                 "Invalid pastry option '%s'", options[0]);
      timeout = (int)xbt_str_parse_int(options[0] + length, "Invalid timeout parameter");
      XBT_DEBUG("Set timeout to %d", timeout);
    }
    options++;
  }

  simgrid_load_platform(options[0]);

  simgrid_register_function("node", node);
  simgrid_load_deployment(options[1]);

  simgrid_run();
  XBT_INFO("Simulated time: %g", simgrid_get_clock());

  return 0;
}

Simulating Clouds

Cloud basics

This example starts some computations both on PMs and VMs and migrates some VMs around.

View examples/cpp/cloud-simple/s4u-cloud-simple.cpp

Download s4u-cloud-simple.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include "simgrid/plugins/live_migration.h"
#include "simgrid/s4u/VirtualMachine.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void computation_fun()
{
  double clock_sta = sg4::Engine::get_clock();
  sg4::this_actor::execute(1000000);
  double clock_end = sg4::Engine::get_clock();

  XBT_INFO("%s:%s executed %g", sg4::this_actor::get_host()->get_cname(), sg4::this_actor::get_cname(),
           clock_end - clock_sta);
}

static void launch_computation_worker(s4u_Host* host)
{
  sg4::Actor::create("compute", host, computation_fun);
}

struct s_payload {
  s4u_Host* tx_host;
  const char* tx_actor_name;
  double clock_sta;
};

static void communication_tx_fun(std::vector<std::string> args)
{
  sg4::Mailbox* mbox            = sg4::Mailbox::by_name(args.at(0));
  auto* payload                 = new s_payload;
  payload->tx_actor_name        = sg4::Actor::self()->get_cname();
  payload->tx_host              = sg4::this_actor::get_host();
  payload->clock_sta            = sg4::Engine::get_clock();

  mbox->put(payload, 1000000);
}

static void communication_rx_fun(std::vector<std::string> args)
{
  const char* actor_name = sg4::Actor::self()->get_cname();
  const char* host_name  = sg4::this_actor::get_host()->get_cname();
  sg4::Mailbox* mbox     = sg4::Mailbox::by_name(args.at(0));

  auto payload     = mbox->get_unique<struct s_payload>();
  double clock_end = sg4::Engine::get_clock();

  XBT_INFO("%s:%s to %s:%s => %g sec", payload->tx_host->get_cname(), payload->tx_actor_name, host_name, actor_name,
           clock_end - payload->clock_sta);
}

static void launch_communication_worker(s4u_Host* tx_host, s4u_Host* rx_host)
{
  std::string mbox_name = "MBOX:" + tx_host->get_name() + "-" + rx_host->get_name();
  std::vector<std::string> args;
  args.push_back(mbox_name);

  sg4::Actor::create("comm_tx", tx_host, communication_tx_fun, args);

  sg4::Actor::create("comm_rx", rx_host, communication_rx_fun, args);
}

static void master_main()
{
  s4u_Host* pm0 = sg4::Host::by_name("Fafard");
  s4u_Host* pm1 = sg4::Host::by_name("Tremblay");
  s4u_Host* pm2 = sg4::Host::by_name("Bourassa");

  XBT_INFO("## Test 1 (started): check computation on normal PMs");

  XBT_INFO("### Put an activity on a PM");
  launch_computation_worker(pm0);
  sg4::this_actor::sleep_for(2);

  XBT_INFO("### Put two activities on a PM");
  launch_computation_worker(pm0);
  launch_computation_worker(pm0);
  sg4::this_actor::sleep_for(2);

  XBT_INFO("### Put an activity on each PM");
  launch_computation_worker(pm0);
  launch_computation_worker(pm1);
  sg4::this_actor::sleep_for(2);

  XBT_INFO("## Test 1 (ended)");

  XBT_INFO(
      "## Test 2 (started): check impact of running an activity inside a VM (there is no degradation for the moment)");

  XBT_INFO("### Put a VM on a PM, and put an activity to the VM");
  auto* vm0 = pm0->create_vm("VM0", 1);
  vm0->start();
  launch_computation_worker(vm0);
  sg4::this_actor::sleep_for(2);
  vm0->destroy();

  XBT_INFO("## Test 2 (ended)");

  XBT_INFO("## Test 3 (started): check impact of running an activity collocated with a VM (there is no VM noise for "
           "the moment)");

  XBT_INFO("### Put a VM on a PM, and put an activity to the PM");
  vm0 = pm0->create_vm("VM0", 1);
  vm0->start();
  launch_computation_worker(pm0);
  sg4::this_actor::sleep_for(2);
  vm0->destroy();
  XBT_INFO("## Test 3 (ended)");

  XBT_INFO(
      "## Test 4 (started): compare the cost of running two activities inside two different VMs collocated or not (for"
      " the moment, there is no degradation for the VMs. Hence, the time should be equals to the time of test 1");

  XBT_INFO("### Put two VMs on a PM, and put an activity to each VM");
  vm0 = pm0->create_vm("VM0", 1);
  vm0->start();
  auto* vm1 = pm0->create_vm("VM1", 1);
  launch_computation_worker(vm0);
  launch_computation_worker(vm1);
  sg4::this_actor::sleep_for(2);
  vm0->destroy();
  vm1->destroy();

  XBT_INFO("### Put a VM on each PM, and put an activity to each VM");
  vm0 = pm0->create_vm("VM0", 1);
  vm1 = pm1->create_vm("VM1", 1);
  vm0->start();
  vm1->start();
  launch_computation_worker(vm0);
  launch_computation_worker(vm1);
  sg4::this_actor::sleep_for(2);
  vm0->destroy();
  vm1->destroy();
  XBT_INFO("## Test 4 (ended)");

  XBT_INFO("## Test 5  (started): Analyse network impact");
  XBT_INFO("### Make a connection between PM0 and PM1");
  launch_communication_worker(pm0, pm1);
  sg4::this_actor::sleep_for(5);

  XBT_INFO("### Make two connection between PM0 and PM1");
  launch_communication_worker(pm0, pm1);
  launch_communication_worker(pm0, pm1);
  sg4::this_actor::sleep_for(5);

  XBT_INFO("### Make a connection between PM0 and VM0@PM0");
  vm0 = pm0->create_vm("VM0", 1);
  vm0->start();
  launch_communication_worker(pm0, vm0);
  sg4::this_actor::sleep_for(5);
  vm0->destroy();

  XBT_INFO("### Make a connection between PM0 and VM0@PM1");
  vm0 = pm1->create_vm("VM0", 1);
  launch_communication_worker(pm0, vm0);
  sg4::this_actor::sleep_for(5);
  vm0->destroy();

  XBT_INFO("### Make two connections between PM0 and VM0@PM1");
  vm0 = pm1->create_vm("VM0", 1);
  vm0->start();
  launch_communication_worker(pm0, vm0);
  launch_communication_worker(pm0, vm0);
  sg4::this_actor::sleep_for(5);
  vm0->destroy();

  XBT_INFO("### Make a connection between PM0 and VM0@PM1, and also make a connection between PM0 and PM1");
  vm0 = pm1->create_vm("VM0", 1);
  vm0->start();
  launch_communication_worker(pm0, vm0);
  launch_communication_worker(pm0, pm1);
  sg4::this_actor::sleep_for(5);
  vm0->destroy();

  XBT_INFO("### Make a connection between VM0@PM0 and PM1@PM1, and also make a connection between VM0@PM0 and VM1@PM1");
  vm0 = pm0->create_vm("VM0", 1);
  vm1 = pm1->create_vm("VM1", 1);
  vm0->start();
  vm1->start();
  launch_communication_worker(vm0, vm1);
  launch_communication_worker(vm0, vm1);
  sg4::this_actor::sleep_for(5);
  vm0->destroy();
  vm1->destroy();

  XBT_INFO("## Test 5 (ended)");
  XBT_INFO("## Test 6 (started): Check migration impact (not yet implemented neither on the CPU resource nor on the"
           " network one");
  XBT_INFO("### Relocate VM0 between PM0 and PM1");
  vm0 = pm0->create_vm("VM0", 1);
  vm0->set_ramsize(1L * 1024 * 1024 * 1024); // 1GiB

  vm0->start();
  launch_communication_worker(vm0, pm2);
  sg4::this_actor::sleep_for(0.01);
  sg_vm_migrate(vm0, pm1);
  sg4::this_actor::sleep_for(0.01);
  sg_vm_migrate(vm0, pm0);
  sg4::this_actor::sleep_for(5);
  vm0->destroy();
  XBT_INFO("## Test 6 (ended)");
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  sg_vm_live_migration_plugin_init();
  e.load_platform(argv[1]); /* - Load the platform description */

  sg4::Actor::create("master_", e.host_by_name("Fafard"), master_main);

  e.run();

  XBT_INFO("Simulation time %g", sg4::Engine::get_clock());

  return 0;
}

Migrating VMs

This example shows how to migrate VMs between PMs.

View examples/cpp/cloud-migration/s4u-cloud-migration.cpp

Download s4u-cloud-migration.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/s4u.hpp"
#include "simgrid/plugins/live_migration.h"
#include "simgrid/s4u/VirtualMachine.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_cloud_migration, "Messages specific for this example");
namespace sg4 = simgrid::s4u;

static void vm_migrate(sg4::VirtualMachine* vm, sg4::Host* dst_pm)
{
  const sg4::Host* src_pm = vm->get_pm();
  double mig_sta          = sg4::Engine::get_clock();
  sg_vm_migrate(vm, dst_pm);
  double mig_end = sg4::Engine::get_clock();

  XBT_INFO("%s migrated: %s->%s in %g s", vm->get_cname(), src_pm->get_cname(), dst_pm->get_cname(), mig_end - mig_sta);
}

static void vm_migrate_async(sg4::VirtualMachine* vm, sg4::Host* dst_pm)
{
  sg4::Actor::create("mig_wrk", sg4::Host::current(), vm_migrate, vm, dst_pm);
}

static void master_main()
{
  sg4::Host* pm0 = sg4::Host::by_name("Fafard");
  sg4::Host* pm1 = sg4::Host::by_name("Tremblay");
  sg4::Host* pm2 = sg4::Host::by_name("Bourassa");

  auto* vm0 = pm0->create_vm("VM0", 1);
  vm0->set_ramsize(1e9); // 1Gbytes
  vm0->start();

  XBT_INFO("Test: Migrate a VM with %zu Mbytes RAM", vm0->get_ramsize() / 1000 / 1000);
  vm_migrate(vm0, pm1);

  vm0->destroy();

  vm0 = pm0->create_vm("VM0", 1);
  vm0->set_ramsize(1e8); // 100Mbytes
  vm0->start();

  XBT_INFO("Test: Migrate a VM with %zu Mbytes RAM", vm0->get_ramsize() / 1000 / 1000);
  vm_migrate(vm0, pm1);

  vm0->destroy();

  vm0       = pm0->create_vm("VM0", 1);
  auto* vm1 = pm0->create_vm("VM1", 1);

  vm0->set_ramsize(1e9); // 1Gbytes
  vm1->set_ramsize(1e9); // 1Gbytes
  vm0->start();
  vm1->start();

  XBT_INFO("Test: Migrate two VMs at once from PM0 to PM1");
  vm_migrate_async(vm0, pm1);
  vm_migrate_async(vm1, pm1);
  sg4::this_actor::sleep_for(10000);

  vm0->destroy();
  vm1->destroy();

  vm0 = pm0->create_vm("VM0", 1);
  vm1 = pm0->create_vm("VM1", 1);

  vm0->set_ramsize(1e9); // 1Gbytes
  vm1->set_ramsize(1e9); // 1Gbytes
  vm0->start();
  vm1->start();

  XBT_INFO("Test: Migrate two VMs at once to different PMs");
  vm_migrate_async(vm0, pm1);
  vm_migrate_async(vm1, pm2);
  sg4::this_actor::sleep_for(10000);

  vm0->destroy();
  vm1->destroy();
}

int main(int argc, char* argv[])
{
  /* Get the arguments */
  sg4::Engine e(&argc, argv);
  sg_vm_live_migration_plugin_init();

  /* load the platform file */
  e.load_platform(argv[1]);

  sg4::Actor::create("master_", sg4::Host::by_name("Fafard"), master_main);

  e.run();

  XBT_INFO("Bye (simulation time %g)", sg4::Engine::get_clock());

  return 0;
}

Plugin Examples

It is possible to extend SimGrid without modifying its internals by attaching code to the existing signals and by adding extra data to the simulation objects through extensions. How to do that is not exactly documented yet, and you should look for examples in the src/plugins directory.

This section documents how the existing plugins can be used. Remember that you are very welcome to modify the plugins to fit your needs. It should be much easier than modifying the SimGrid kernel.

Monitoring the host load

View examples/cpp/plugin-host-load/s4u-plugin-host-load.cpp

Download s4u-plugin-host-load.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#include "simgrid/plugins/load.h"
#include "simgrid/s4u.hpp"

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

static void execute_load_test()
{
  s4u_Host* host = sg4::Host::by_name("MyHost1");

  XBT_INFO("Initial peak speed: %.0E flop/s; number of flops computed so far: %.0E (should be 0) and current average "
           "load: %.5f (should be 0)",
           host->get_speed(), sg_host_get_computed_flops(host), sg_host_get_avg_load(host));

  double start = sg4::Engine::get_clock();
  XBT_INFO("Sleep for 10 seconds");
  sg4::this_actor::sleep_for(10);

  double speed = host->get_speed();
  XBT_INFO("Done sleeping %.2fs; peak speed: %.0E flop/s; number of flops computed so far: %.0E (nothing should have "
           "changed)",
           sg4::Engine::get_clock() - start, host->get_speed(), sg_host_get_computed_flops(host));

  // Run an activity
  start = sg4::Engine::get_clock();
  XBT_INFO("Run an activity of %.0E flops at current speed of %.0E flop/s", 200E6, host->get_speed());
  sg4::this_actor::execute(200E6);

  XBT_INFO(
      "Done working on my activity; this took %.2fs; current peak speed: %.0E flop/s (when I started the computation, "
      "the speed was set to %.0E flop/s); number of flops computed so "
      "far: %.2E, average load as reported by the HostLoad plugin: %.5f (should be %.5f)",
      sg4::Engine::get_clock() - start, host->get_speed(), speed, sg_host_get_computed_flops(host),
      sg_host_get_avg_load(host),
      200E6 / (10.5 * speed * host->get_core_count() +
               (sg4::Engine::get_clock() - start - 0.5) * host->get_speed() * host->get_core_count()));

  // ========= Change power peak =========
  int pstate = 1;
  host->set_pstate(pstate);
  XBT_INFO(
      "========= Requesting pstate %d (speed should be of %.0E flop/s and is of %.0E flop/s, average load is %.5f)",
      pstate, host->get_pstate_speed(pstate), host->get_speed(), sg_host_get_avg_load(host));

  // Run a second activity
  start = sg4::Engine::get_clock();
  XBT_INFO("Run an activity of %.0E flops", 100E6);
  sg4::this_actor::execute(100E6);
  XBT_INFO("Done working on my activity; this took %.2fs; current peak speed: %.0E flop/s; number of flops computed so "
           "far: %.2E",
           sg4::Engine::get_clock() - start, host->get_speed(), sg_host_get_computed_flops(host));

  start = sg4::Engine::get_clock();
  XBT_INFO("========= Requesting a reset of the computation and load counters");
  sg_host_load_reset(host);
  XBT_INFO("After reset: %.0E flops computed; load is %.5f", sg_host_get_computed_flops(host),
           sg_host_get_avg_load(host));
  XBT_INFO("Sleep for 4 seconds");
  sg4::this_actor::sleep_for(4);
  XBT_INFO("Done sleeping %.2f s; peak speed: %.0E flop/s; number of flops computed so far: %.0E",
           sg4::Engine::get_clock() - start, host->get_speed(), sg_host_get_computed_flops(host));

  // =========== Turn the other host off ==========
  s4u_Host* host2 = sg4::Host::by_name("MyHost2");
  XBT_INFO("Turning MyHost2 off, and sleeping another 10 seconds. MyHost2 computed %.0f flops so far and has an "
           "average load of %.5f.",
           sg_host_get_computed_flops(host2), sg_host_get_avg_load(host2));
  host2->turn_off();
  start = sg4::Engine::get_clock();
  sg4::this_actor::sleep_for(10);
  XBT_INFO("Done sleeping %.2f s; peak speed: %.0E flop/s; number of flops computed so far: %.0E",
           sg4::Engine::get_clock() - start, host->get_speed(), sg_host_get_computed_flops(host));
}

static void change_speed()
{
  s4u_Host* host = sg4::Host::by_name("MyHost1");
  sg4::this_actor::sleep_for(10.5);
  XBT_INFO("I slept until now, but now I'll change the speed of this host "
           "while the other actor is still computing! This should slow the computation down.");
  host->set_pstate(2);
}

int main(int argc, char* argv[])
{
  sg_host_load_plugin_init();
  sg4::Engine e(&argc, argv);

  xbt_assert(argc == 2, "Usage: %s platform_file\n\tExample: %s ../platforms/energy_platform.xml\n", argv[0], argv[0]);
  e.load_platform(argv[1]);

  sg4::Actor::create("load_test", e.host_by_name("MyHost1"), execute_load_test);
  sg4::Actor::create("change_speed", e.host_by_name("MyHost1"), change_speed);

  e.run();

  XBT_INFO("Total simulation time: %.2f", sg4::Engine::get_clock());

  return 0;
}

Model-Checking examples

The model-checker can be used to exhaustively search for issues in the tested application. It must be activated at compile-time, but this mode is rather experimental in SimGrid (as of v3.25). We are working on it :)

Failing assert

In this example, two actors send some data to a central server, which asserts that the messages are always received in the same order. This is wrong, and the model-checker correctly finds a counter-example to that assertion.

View examples/cpp/mc-failing-assert/s4u-mc-failing-assert.cpp

Download s4u-mc-failing-assert.cpp

/* Copyright (c) 2010-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/******************** Non-deterministic message ordering  *********************/
/* Server assumes a fixed order in the reception of messages from its clients */
/* which is incorrect because the message ordering is non-deterministic       */
/******************************************************************************/

#include <simgrid/modelchecker.h>
#include <simgrid/s4u.hpp>

XBT_LOG_NEW_DEFAULT_CATEGORY(mc_assert_example, "Logging channel used in this example");
namespace sg4 = simgrid::s4u;

static int server(int worker_amount)
{
  int value_got             = -1;
  sg4::Mailbox* mb          = sg4::Mailbox::by_name("server");
  for (int count = 0; count < worker_amount; count++) {
    auto msg  = mb->get_unique<int>();
    value_got = *msg;
  }
  /*
   * We assert here that the last message we got (which overwrite any previously received message) is the one from the
   * last worker This will obviously fail when the messages are received out of order.
   */
  MC_assert(value_got == 2);

  XBT_INFO("OK");
  return 0;
}

static int client(int rank)
{
  /* I just send my rank onto the mailbox. It must be passed as a stable memory block (thus the new) so that that
   * memory survives even after the end of the client */

  sg4::Mailbox* mailbox = sg4::Mailbox::by_name("server");
  mailbox->put(new int(rank), 1 /* communication cost is not really relevant in MC mode */);

  XBT_INFO("Sent!");
  return 0;
}

int main(int argc, char* argv[])
{
  sg4::Engine e(&argc, argv);
  xbt_assert(argc > 1, "Usage: %s platform_file\n", argv[0]);

  e.load_platform(argv[1]);
  auto hosts = e.get_all_hosts();
  xbt_assert(hosts.size() >= 3, "This example requires at least 3 hosts");

  sg4::Actor::create("server", hosts[0], &server, 2);
  sg4::Actor::create("client1", hosts[1], &client, 1);
  sg4::Actor::create("client2", hosts[2], &client, 2);

  e.run();
  return 0;
}

Advanced examples

Changing maestro’s thread

Usually, SimGrid’s maestro executes in the main thread of your application, meaning that the main thread is in charge of initializing the simulation and then scheduling the activities. If you really need it, it is possible to move away maestro in another system thread, for example because another library absolutely wants to run as the system main thread. The following example shows how to do that, using sg_actor_attach() at the begining and sg_actor_detach() on termination.

View examples/cpp/maestro-set/s4u-maestro-set.cpp

Download s4u-maestro-set.cpp

/* Copyright (c) 2007-2024. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

/** Switch the system thread hosting our maestro.
 *
 *  That's a very advanced example in which we move the maestro context to another system thread.
 *  Not many users need it (maybe only one, actually), but this example is also a regression test.
 *
 *  This example is in C++ because we use C++11 threads to ensure that the feature is working as
 *  expected. You can still use that feature from a C code.
 */

#include "simgrid/Exception.hpp"
#include "simgrid/actor.h"
#include "simgrid/s4u.hpp"

#include <string>
#include <thread>

XBT_LOG_NEW_DEFAULT_CATEGORY(s4u_test, "Messages specific for this s4u example");
namespace sg4 = simgrid::s4u;

const std::thread::id root_id = std::this_thread::get_id();

static void ensure_root_tid()
{
  std::thread::id this_id = std::this_thread::get_id();
  xbt_assert(root_id == this_id, "I was supposed to be the main thread");
  XBT_INFO("I am the main thread, as expected");
}
static void ensure_other_tid()
{
  std::thread::id this_id = std::this_thread::get_id();
  xbt_assert(this_id != root_id, "I was NOT supposed to be the main thread");
  XBT_INFO("I am not the main thread, as expected");
}

static void sender()
{
  ensure_root_tid();
  auto* payload = new std::string("some message");
  sg4::Mailbox::by_name("some mailbox")->put(payload, 10e8);
}

static void receiver()
{
  ensure_other_tid();

  sg4::Mailbox::by_name("some mailbox")->get_unique<std::string>();
  XBT_INFO("Task received");
}

static void maestro(void* /* data */)
{
  ensure_other_tid();
  sg4::Actor::create("receiver", sg4::Host::by_name("Jupiter"), receiver);
  sg4::Engine::get_instance()->run();
}

/** Main function */
int main(int argc, char* argv[])
{
  /* Specify which code should be executed by maestro on another thread, once this current thread is affected to an
   * actor by the subsequent sg_actor_attach(). This must be done before the creation of the engine. */
  simgrid_set_maestro(maestro, nullptr);

  sg4::Engine e(&argc, argv);

  xbt_assert(argc == 2, "Usage: %s platform_file\n"
                        "example: %s ../platforms/small_platform.xml\n",
             argv[0], argv[0]);

  e.load_platform(argv[1]);

  /* Become one of the simulated actors (must be done after the platform creation, or the host won't exist). */
  sg_actor_attach("sender", nullptr, e.host_by_name("Tremblay"), nullptr);

  ensure_root_tid(); // Only useful in this test: we ensure that SimGrid is not broken and that this code is executed in
                     // the correct system thread

  // Execute the sender code. The root thread was actually turned into a regular actor
  sender();

  sg_actor_detach(); // The root thread becomes maestro again (as proved by the output)
  XBT_INFO("Detached");
  ensure_root_tid();

  return 0;
}