C++ Platforms

Using XML to describe the platforms is very convenient. It provides a human-readable, quick way to start your experiments. Although, XML format brings several drawbacks as your platforms get larger and more complex (see Beyond the XML: the power of C++ platforms).

In this case, it may be more interesting to write your platform directly in C++ code. It allows you to programmatically describe your platform and remove the intermediate XML parser during simulations. Take care to follow the recommendations in Modeling Hints to keep a clear separation of concerns between your platform and your application.

Describing Resources

A platform in SimGrid is composed of several resources organized in different Netzones. The different resources, such as hosts, disks and links, follow the same idiom: create()->set()->set()->seal().

NetZone* zone      = s4u::create_star_zone("zone0");
Link* l_up   = zone->create_link("link_up", "125MBps")->set_latency("24us")->seal();
Host* host   = zone->create_host("host0", "1Gf")->seal();
zone->seal();

The first NetZone created will be the root zone of your platform. You’re allowed to modified an object as long as you did not seal it.

For more details about how to describe the platforms, please give a look at examples or directly at the S4U API.

Loading the platform

The C++ interface to build the platforms give you freedom to organize your code as you wish, separating (or unifying) your application from your platform code. However, we provide a small hack if you want to keep the same structure of the old code with XML platforms. You can pass a library (.so) file to Engine::load_platform function, having a predefined function implemented. When loading the platform, the Engine will look for a function with this signature: “void load_platform(const sg4::Engine& e)”, and execute it. It could be an easy way to make the transition between XML and C++ if necessary.

For more details, please refer to the cpp and CMakeLists.txt files in examples/platform.

Example

The best way to build your C++ platform is starting from some examples. Give a look in the examples folder in examples/. For instance, the file examples/cpp/clusters-multicpu/s4u-clusters-multicpu.cpp shows how to build complex platforms composed of clusters of clusters.

Here, we present a complete example showing how to create 3 regulars clusters connected through a shared link.

/* Copyright (c) 2006-2023. 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 <numeric>
#include <simgrid/s4u.hpp>
namespace sg4 = simgrid::s4u;

/**
 * @brief Create a new cabinet
 *
 * This function creates the cabinet, adding the hosts and links properly.
 * See figure below for more details of each cabinet
 *
 * @param root Root netzone
 * @param name Cabinet name
 * @param radicals IDs of nodes inside the cabinet
 * @return netzone the created netzone
 */
static sg4::NetZone*
create_cabinet(const sg4::NetZone* root, const std::string& name, const std::vector<int>& radicals)
{
  auto* cluster      = sg4::create_star_zone(name)->set_parent(root);
  std::string prefix = "griffon-";
  std::string suffix = ".nancy.grid5000.fr";

  /* create the backbone link */
  const sg4::Link* l_bb = cluster->create_link("backbone-" + name, "1.25GBps");
  sg4::LinkInRoute backbone(l_bb);

  /* create all hosts and connect them to outside world */
  for (const auto& id : radicals) {
    std::string hostname = prefix + std::to_string(id) + suffix;
    /* create host */
    const sg4::Host* host = cluster->create_host(hostname, "286.087kf");
    /* create UP/DOWN link */
    const sg4::Link* link = cluster->create_split_duplex_link(hostname, "125MBps")->set_latency("24us");

    /* add link and backbone for communications from the host */
    cluster->add_route(host, nullptr, {{link, sg4::LinkInRoute::Direction::UP}, backbone}, true);
  }

  /* create gateway */
  cluster->set_gateway(cluster->create_router(prefix + name + "-router" + suffix));

  cluster->seal();
  return cluster;
}

/** @brief Programmatic version of griffon.xml */
extern "C" void load_platform(const sg4::Engine& e);
void load_platform(const sg4::Engine& /*e*/)
{
  /**
   * C++ version of griffon.xml
   * Old Grid5000 cluster (not available anymore): 3 cabinets containing homogeneous nodes connected through a backbone
   *                                  1.25GBps shared link
   *                          ___________________________________
   *          1              /                |                  \
   *                        /                 |                   \
   *                       /                  |                    \
   *     ________________ /             ______|__________           \_________________
   *     |               |              |               |            |               |
   *     | cab1 router   |              | cab2 router   |            | cab3 router   |
   *     |_______________|              |_______________|            |_______________|
   *     ++++++++++++++++               ++++++++++++++++             ++++++++++++++++++  <-- 1.25 backbone
   *     / /   | |    \ \              / /    | |    \ \             / /     | |     \ \
   *    / /    | |     \ \            / /     | |     \ \           / /      | |      \ \ <-- 125Mbps links
   *   / /     | |      \ \          / /      | |      \ \         / /       | |       \ \
   * host1     ...      hostN      host1      ...      hostM      host1      ...       hostQ
   */

  auto* root = sg4::create_star_zone("AS_griffon");
  sg4::NetZone* cab_zone;

  /* create top link */
  const sg4::Link* l_bb = root->create_link("backbone", "1.25GBps")->set_latency("24us")->seal();
  sg4::LinkInRoute backbone{l_bb};

  /* create cabinet1 */
  std::vector<int> rad(32);
  std::iota(rad.begin(), rad.end(), 1); // 1-29,58,59,60
  rad[rad.size() - 1]  = 60;
  rad[rad.size() - 2]  = 59;
  rad[rad.size() - 3]  = 58;
  cab_zone             = create_cabinet(root, "cabinet1", rad);
  root->add_route(cab_zone, nullptr, {backbone});

  /* create cabinet2 */
  rad.resize(28);
  std::iota(rad.begin(), rad.end(), 30); // 30-57
  cab_zone = create_cabinet(root, "cabinet2", rad);
  root->add_route(cab_zone, nullptr, {backbone});

  /* create cabinet3 */
  rad.resize(32);
  std::iota(rad.begin(), rad.end(), 61); // 61-92
  cab_zone = create_cabinet(root, "cabinet3", rad);
  root->add_route(cab_zone, nullptr, {backbone});

  root->seal();
}

Beyond the XML: the power of C++ platforms

This section describes one of the advantages of using C++ code to write your platforms.

Let’s see an example of the description of a Fat-Tree in XML (Fat-Tree Cluster)

<?xml version='1.0'?>
<!DOCTYPE platform SYSTEM "https://simgrid.org/simgrid.dtd">
<platform version="4.1">
  <zone id="world" routing="Full">
    <cluster id="bob_cluster"
	     prefix="node-" radical="0-15" suffix=".simgrid.org"
	     speed="1Gf" bw="125MBps" lat="50us"
             topology="FAT_TREE" topo_parameters="2;4,4;1,2;1,2"
	     loopback_bw="100MBps" loopback_lat="0" />
  </zone>
</platform>

Our cluster bob is composed of 16 hosts with the same 1Gf CPU power.

Imagine now that you want to simulate the same Fat-Tree topology with more complex hosts, composed of 1 CPU, 1 GPU and some interconnecting bus.

Unfortunately, this is not possible with the XML description since its syntax obliges that the leaves in your Fat-Tree to be single Hosts. However, with the C++ API, your leaves can be composed of other zones, creating a Fat-Tree of FullZones for example.

Consequently, you can describe the desired platform as follows:

sg4::Engine e(&argc, argv);
sg4::create_fatTree_zone("bob", e.get_netzone_root(), {2, {4, 4}, {1, 2}, {1, 2}}, {create_hostzone, create_loopback, {}}, 125e6,
                       50e-6, sg4::Link::SharingPolicy::SPLITDUPLEX)->seal();

Note that the leaves and loopback links are defined through callbacks, as follows:

/* create the loopback link for each leaf in the Fat-Tree */
static sg4::Link* create_loopback(sg4::NetZone* zone, const std::vector<unsigned int>& /*coord*/, int id)
{
    // note that you could set different loopback links for each leaf
    return zone->create_link("limiter-" + std::to_string(id), 1e6)->seal();
}

/* create each leaf in the Fat-Tree, return a pair composed of: <object (host, zone), gateway> */
static std::pair<simgrid::kernel::routing::NetPoint*, simgrid::kernel::routing::NetPoint*>
create_hostzone(const sg4::NetZone* zone, const std::vector<unsigned long>& /*coord*/, unsigned long id)
{
  /* creating zone */
  std::string hostname = "host" + std::to_string(id);
  auto* host_zone = sg4::create_full_zone(hostname);
  /* setting my parent zone */
  host_zone->set_parent(zone);

  /* creating CPU */
  std::string cpu_name  = hostname + "-cpu" + std::to_string(i);
  const sg4::Host* cpu = host_zone->create_host(cpu_name, 1e9)->seal();
  /* creating GPU */
  std::string gpu_name  = hostname + "-gpu" + std::to_string(i);
  const sg4::Host* gpu = host_zone->create_host(gpu_name, 1e12)->seal();
  /* connecting them */
  sg4::Link* link   = host_zone->create_link("link-" + cpu_name, 10e9)->set_latency(10e-9)->seal();
  host_zone->add_route(cpu->get_netpoint(), gpu->get_netpoint(), nullptr, nullptr, {sg4::LinkInRoute(link)});

  host_zone->seal();
  /* cpu is the gateway for this host */
  return std::make_pair(host_zone->get_netpoint(), cpu->get_netpoint());
}

The code is straightforward and can be easily adapted to more complex environments thanks to the flexibility provided by the C++ API.