diff --git a/conf/cmake/Examples.cmake b/conf/cmake/Examples.cmake index 382f06a901..e0f32a06dc 100644 --- a/conf/cmake/Examples.cmake +++ b/conf/cmake/Examples.cmake @@ -3,6 +3,7 @@ set(EXAMPLES_CMAKEFILES_TXT examples/simple-example/CMakeLists.txt examples/basic-examples/bare-metal-chain/CMakeLists.txt examples/basic-examples/bare-metal-chain-scratch/CMakeLists.txt + examples/basic-examples/bare-metal-bag-of-tasks/CMakeLists.txt ) foreach (cmakefile ${EXAMPLES_CMAKEFILES_TXT}) diff --git a/examples/README.md b/examples/README.md index 71669329ca..e7a825fff4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,36 +1,44 @@ ## WRENCH Examples -The ```examples``` directory in the WRENCH distribution contains several -example simulators that showcase the use of the WRENCH APIs. Each +The ```examples``` directory in the WRENCH distribution contains several +example simulators that showcase the use of the WRENCH APIs. Each simulator (implemented using the WRENCH user API) simulates - the execution of a workflow by a custom Workflow Management System (implemented - using the WRENCH developer API). + the execution of a workflow by a custom Workflow Management System + (implemented using the WRENCH developer API). -Below are -high-level descriptions of the example in each sub-directory. Details on -the specifics of each simulator are in extensive source code - comments. +Below are high-level descriptions of the example in each sub-directory. +Details on the specifics of each simulator are in extensive source code + comments. ### Basic simulators using a bare-metal service -These simulators showcase the simplest use cases, simulating the execution of -simple applications on small hardware platforms that run instances of the **bare-metal -compute service** and of the simple storage service. The bare-metal service -is the simplest of the compute services implemented in WRENCH, which is why it is -used in these examples. - - - ```bare-metal-chain```: A simulation of the execution of a - chain workflow by a Workflow Management System on a compute service, with all workflow data being read/written - from/to a single storage service. The compute service runs on a 10-core host, and each task - is executed as a single job that uses 10 cores - - - ```bare-metal-chain-scratch```: Similar, but the compute service now has - scratch space to hold intermediate workflow files. Since files created in the scratch - space during a job's execution are erased after that job's completion. As a result, - the workflow is executed as a single multi-task job. - - - ```bare-metal-bag-of-tasks```: A simulation of the execution of a - bag-of-task workflow by a Workflow Management System on a compute service, with all workflow data being read/written - from/to a single storage service. Up to two workflow tasks are executed concurrently on the compute service, in which case - one task is executed on 6 cores and the other on 4 cores. +These simulators showcase the simplest use cases, simulating the execution +of simple applications on small hardware platforms that run instances of +the **bare-metal compute service** and of the simple storage service. The +bare-metal service is the simplest of the compute services implemented in +WRENCH, which is why it is used in these examples. + + - ```bare-metal-chain```: A simulation of the execution of a + chain workflow by a Workflow Management System on a compute service, + with all workflow data being read/written from/to a single storage + service. The compute service runs on a 10-core host, and each task is + executed as a single job that uses 10 cores + + - ```bare-metal-chain-scratch```: Similar, but the compute service now + has scratch space to hold intermediate workflow files. Since files + created in the scratch space during a job's execution are erased after + that job's completion. As a result, the workflow is executed as a single + multi-task job. + + - ```bare-metal-bag-of-tasks```: A simulation of the execution of a + bag-of-task workflow by a Workflow Management System on a compute + service, with all workflow data being read/written from/to a single + storage service. Up to two workflow tasks are executed concurrently on + the compute service, in which case one task is executed on 6 cores and + the other on 4 cores. + + - ```bare-metal-complex-job```: A simulation of the execution of a + one-task workflow on a compute service as a job that includes not long + the task computation but also data movements. + diff --git a/examples/basic-examples/bare-metal-bag-of-tasks/BareMetalBagOfTasks.cpp b/examples/basic-examples/bare-metal-bag-of-tasks/BareMetalBagOfTasks.cpp new file mode 100644 index 0000000000..349dfa8605 --- /dev/null +++ b/examples/basic-examples/bare-metal-bag-of-tasks/BareMetalBagOfTasks.cpp @@ -0,0 +1,159 @@ +/** + * Copyright (c) 2017-2018. The WRENCH Team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +/** + ** This simulator simulates the execution of a bag-of-tasks workflow, that is, of a workflow + ** in which each task has its own input file and its own output file, and tasks can be + ** executed completely independently + ** + ** InputFile #0 -> Task #0 -> OutputFile #1 + ** ... + ** InputFile #n -> Task #n -> OutputFile #n + ** + ** The compute platform comprises two hosts, WMSHost and ComputeHost. On WMSHost runs a simple storage + ** service and a WMS (defined in class OneTaskAtATimeWMS). On ComputeHost runs a bare metal + ** compute service, that has access to the 10 cores of that host. Once the simulation is done, + ** the completion time of each workflow task is printed. + ** + ** Example invocation of the simulator for a 10-task workflow, with only WMS logging: + ** ./bare-metal-bag-of-tasks-simulator 10 ./two_hosts.xml --wrench-no-logs --log=one_task_at_a_time_wms.threshold=info + ** + ** Example invocation of the simulator for a 5-task workflow with full logging: + ** ./bare-metal-bag-of-tasks-simulator 5 ./two_hosts.xml + **/ + + +#include +#include + +#include "TwoTasksAtATimeWMS.h" // WMS implementation + +/** + * @brief The Simulator's main function + * + * @param argc: argument count + * @param argv: argument array + * @return 0 on success, non-zero otherwise + */ +int main(int argc, char **argv) { + + /* + * Declare a WRENCH simulation object + */ + wrench::Simulation simulation; + + /* Initialize the simulation, which may entail extracting WRENCH-specific and + * Simgrid-specific command-line arguments that can modify general simulation behavior. + * Two special command-line arguments are --help-wrench and --help-simgrid, which print + * details about available command-line arguments. */ + simulation.init(&argc, argv); + + /* Parsing of the command-line arguments for this WRENCH simulation */ + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " [optional logging arguments]" << std::endl; + exit(1); + } + + /* Reading and parsing the platform description file, written in XML following the SimGrid-defined DTD, + * to instantiate the simulated platform */ + std::cerr << "Instantiating simulated platform..." << std::endl; + simulation.instantiatePlatform(argv[2]); + + /* Parse the first command-line argument (number of tasks) */ + int num_tasks = 0; + try { + num_tasks = std::atoi(argv[1]); + } catch (std::invalid_argument &e) { + std::cerr << "Invalid number of tasks\n"; + exit(1); + } + + /* Declare a workflow */ + wrench::Workflow workflow; + + /* Initialize and seed a RNG */ + std::uniform_int_distribution dist(100000000.0,10000000000.0); + std::mt19937 rng(42); + + /* Add workflow tasks and files */ + for (int i=0; i < num_tasks; i++) { + /* Create a task: 10GFlop, 1 to 10 cores, 0.90 parallel efficiency, 10MB memory footprint */ + auto task = workflow.addTask("task_" + std::to_string(i), dist(rng), 1, 10, 0.90, 10000000); + task->addInputFile(workflow.addFile("input_" + std::to_string(i), 10000000)); + task->addOutputFile(workflow.addFile("output_" + std::to_string(i), 10000000)); + } + + /* Instantiate a storage service, and add it to the simulation. + * A wrench::StorageService is an abstraction of a service on + * which files can be written and read. This particular storage service, which is an instance + * of wrench::SimpleStorageService, is started on WMSHost in the + * platform , which has an attached disk mounted at "/". The SimpleStorageService + * is a basic storage service implementation provided by WRENCH. + * Throughout the simulation execution, input/output files of workflow tasks will be located + * in this storage service, and accessed remotely by the compute service. Note that the + * storage service is configured to use a buffer size of 50M when transferring data over + * the network (i.e., to pipeline disk reads/writes and network revs/sends). */ + std::cerr << "Instantiating a SimpleStorageService on WMSHost..." << std::endl; + auto storage_service = simulation.add(new wrench::SimpleStorageService( + "WMSHost", {"/"}, {{wrench::SimpleStorageServiceProperty::BUFFER_SIZE, "50000000"}}, {})); + + /* Instantiate a bare-metal compute service, and add it to the simulation. + * A wrench::BareMetalComputeService is an abstraction of a compute service that corresponds to a + * to a software infrastructure that can execute tasks on hardware resources. + * This particular service is started on ComputeHost and has no scratch storage space (mount point argument = ""). + * This means that tasks running on this service will access data only from remote storage services. */ + std::cerr << "Instantiating a BareMetalComputeService on WMSHost..." << std::endl; + auto baremetal_service = simulation.add(new wrench::BareMetalComputeService( + "ComputeHost", {"ComputeHost"}, "", {}, {})); + + /* Instantiate a WMS, to be stated on WMSHost, which is responsible + * for executing the workflow. See comments in TwoTasksAtATimeWMS.cpp + * for more details */ + + auto wms = simulation.add( + new wrench::TwoTasksAtATimeWMS({baremetal_service}, {storage_service}, "WMSHost")); + + /* Associate the workflow to the WMS */ + wms->addWorkflow(&workflow); + + /* Instantiate a file registry service to be started on WMSHost. This service is + * essentially a replica catalog that stores pairs so that + * any service, in particular a WMS, can discover where workflow files are stored. */ + std::cerr << "Instantiating a FileRegistryService on WMSHost ..." << std::endl; + auto file_registry_service = new wrench::FileRegistryService("WMSHost"); + simulation.add(file_registry_service); + + /* It is necessary to store, or "stage", input files that only input. The getInputFiles() + * method of the Workflow class returns the set of all workflow files that are not generated + * by workflow tasks, and thus are only input files. These files are then staged on the storage service. */ + std::cerr << "Staging task input files..." << std::endl; + for (auto const &f : workflow.getInputFiles()) { + simulation.stageFile(f, storage_service); + } + + /* Launch the simulation. This call only returns when the simulation is complete. */ + std::cerr << "Launching the Simulation..." << std::endl; + try { + simulation.launch(); + } catch (std::runtime_error &e) { + std::cerr << "Exception: " << e.what() << std::endl; + return 1; + } + std::cerr << "Simulation done!" << std::endl; + + /* Simulation results can be examined via simulation.output, which provides access to traces + * of events. In the code below, we print the retrieve the trace of all task completion events, print how + * many such events there are, and print some information for the first such event. */ + auto trace = simulation.getOutput().getTrace(); + for (auto const &item : trace) { + std::cerr << "Task " << item->getContent()->getTask()->getID() << " completed at time " << item->getDate() << std::endl; + } + + return 0; +} \ No newline at end of file diff --git a/examples/basic-examples/bare-metal-bag-of-tasks/CMakeLists.txt b/examples/basic-examples/bare-metal-bag-of-tasks/CMakeLists.txt new file mode 100644 index 0000000000..1e4a4c4798 --- /dev/null +++ b/examples/basic-examples/bare-metal-bag-of-tasks/CMakeLists.txt @@ -0,0 +1,13 @@ + +set(SOURCE_FILES + TwoTasksAtATimeWMS.h + TwoTasksAtATimeWMS.cpp + BareMetalBagOfTasks.cpp + ) + +add_executable(wrench-bare-metal-bag-of-tasks-simulator ${SOURCE_FILES}) + +target_link_libraries(wrench-bare-metal-bag-of-tasks-simulator wrench ${SimGrid_LIBRARY} ${PUGIXML_LIBRARY} ${LEMON_LIBRARY}) + +install(TARGETS wrench-bare-metal-bag-of-tasks-simulator DESTINATION bin) + diff --git a/examples/basic-examples/bare-metal-bag-of-tasks/TwoTasksAtATimeWMS.cpp b/examples/basic-examples/bare-metal-bag-of-tasks/TwoTasksAtATimeWMS.cpp new file mode 100644 index 0000000000..8100ac7bad --- /dev/null +++ b/examples/basic-examples/bare-metal-bag-of-tasks/TwoTasksAtATimeWMS.cpp @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2017-2018. The WRENCH Team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +/** + ** A Workflow Management System (WMS) implementation that operates as follows: + ** - While the workflow is not done, repeat: + ** - Pick up to two ready tasks + ** - Submit both of them as part of a single job to the first available BareMetalComputeService so tbat: + ** - The most expensive task uses 6 cores + ** - The least expensive task uses 4 cores + ** - Each task reads the input file from the StorageService + ** - Each task reads the output file from the StorageService + **/ + +#include + +#include "TwoTasksAtATimeWMS.h" + +XBT_LOG_NEW_DEFAULT_CATEGORY(two_tasks_at_a_time_wms, "Log category for TwoTasksAtATimeWMS"); + +namespace wrench { + + /** + * @brief Constructor, which calls the super constructor + * + * @param compute_services: a set of compute services available to run tasks + * @param storage_services: a set of storage services available to store files + * @param hostname: the name of the host on which to start the WMS + */ + TwoTasksAtATimeWMS::TwoTasksAtATimeWMS(const std::set> &compute_services, + const std::set> &storage_services, + const std::string &hostname) : WMS( + nullptr, nullptr, + compute_services, + storage_services, + {}, nullptr, + hostname, + "one-task-at-a-time") {} + + /** + * @brief main method of the TwoTasksAtATimeWMS daemon + * + * @return 0 on completion + * + * @throw std::runtime_error + */ + int TwoTasksAtATimeWMS::main() { + + /* Set the logging output to GREEN */ + TerminalOutput::setThisProcessLoggingColor(TerminalOutput::COLOR_GREEN); + + WRENCH_INFO("WMS starting on host %s", Simulation::getHostName().c_str()); + WRENCH_INFO("About to execute a workflow with %lu tasks", this->getWorkflow()->getNumberOfTasks()); + + /* Create a job manager so that we can create/submit jobs */ + auto job_manager = this->createJobManager(); + + /* Get the first available bare-metal compute service and storage servic */ + auto compute_service = *(this->getAvailableComputeServices().begin()); + auto storage_service = *(this->getAvailableStorageServices().begin()); + + /* While the workflow isn't done, repeat the main loop */ + while (not this->getWorkflow()->isDone()) { + + /* Get the ready tasks */ + auto ready_tasks = this->getWorkflow()->getReadyTasks(); + + /* Sort them by flops */ + std::sort(ready_tasks.begin(), ready_tasks.end(), + [](const WorkflowTask *t1, const WorkflowTask *t2) -> bool { + + if (t1->getFlops() == t2->getFlops()) { + return ((uintptr_t) t1 > (uintptr_t) t2); + } else { + return (t1->getFlops() < t2->getFlops()); + } + }); + + /* Pick the least and most (if any) expensive task */ + auto cheap_ready_task = ready_tasks.at(0); + auto expensive_ready_task = (ready_tasks.size() > 1 ? ready_tasks.at(ready_tasks.size() - 1) : nullptr); + + /* Create a standard job for the tasks */ + + /* First, we need to create a map of file locations, stating for each file + * where is should be read/written */ + std::map> file_locations; + file_locations[cheap_ready_task->getInputFiles().at(0)] = FileLocation::LOCATION(storage_service); + file_locations[cheap_ready_task->getOutputFiles().at(0)] = FileLocation::LOCATION(storage_service); + if (expensive_ready_task) { + file_locations[expensive_ready_task->getInputFiles().at(0)] = FileLocation::LOCATION(storage_service); + file_locations[expensive_ready_task->getOutputFiles().at(0)] = FileLocation::LOCATION(storage_service); + } + + /* Create the job */ + StandardJob *standard_job; + if (expensive_ready_task) { + standard_job = job_manager->createStandardJob( + {cheap_ready_task, expensive_ready_task}, file_locations); + } else { + standard_job = job_manager->createStandardJob( + cheap_ready_task, file_locations); + } + + /* Then, we create the "service-specific arguments" that make it possible to configure + * the way in which tasks in a job can run on the compute service */ + std::map service_specific_args; + service_specific_args[cheap_ready_task->getID()] = "4"; + if (expensive_ready_task) { + service_specific_args[expensive_ready_task->getID()] = "6"; + } + + /* Submit the job to the compute service */ + job_manager->submitJob(standard_job, compute_service, service_specific_args); + + /* Wait for a workflow execution event and process it. In this case we know that + * the event will be a StandardJobCompletionEvent, which is processed by the method + * processEventStandardJobCompletion() that this class overrides. */ + this->waitForAndProcessNextEvent(); + } + + WRENCH_INFO("Workflow execution complete"); + return 0; + } + + /** + * @brief Process a standard job completion event + * + * @param event: the event + */ + void TwoTasksAtATimeWMS::processEventStandardJobCompletion(std::shared_ptr event) { + /* Retrieve the job that this event is for */ + auto job = event->standard_job; + /* Retrieve the job's first (and in our case only) task */ + auto task = job->getTasks().at(0); + WRENCH_INFO("Notified that a standard job has completed task %s", task->getID().c_str()); + } + + /** + * @brief Process a standard job failure event + * + * @param event: the event + */ + void TwoTasksAtATimeWMS::processEventStandardJobFailure(std::shared_ptr event) { + /* Retrieve the job that this event is for */ + auto job = event->standard_job; + /* Retrieve the job's first (and in our case only) task */ + auto task = job->getTasks().at(0); + /* Print some error message */ + WRENCH_INFO("Notified that a standard job has failed for task %s with error %s", + task->getID().c_str(), + event->failure_cause->toString().c_str()); + throw std::runtime_error("ABORTING DUE TO JOB FAILURE"); + } + + +} diff --git a/examples/basic-examples/bare-metal-bag-of-tasks/TwoTasksAtATimeWMS.h b/examples/basic-examples/bare-metal-bag-of-tasks/TwoTasksAtATimeWMS.h new file mode 100644 index 0000000000..688d49ef4b --- /dev/null +++ b/examples/basic-examples/bare-metal-bag-of-tasks/TwoTasksAtATimeWMS.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2017-2018. The WRENCH Team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + + +#ifndef WRENCH_TWO_TASKS_AT_A_TIME_H +#define WRENCH_TWO_TASKS_AT_A_TIME_H + +#include + + +namespace wrench { + + class Simulation; + + /** + * @brief A Workflow Management System (WMS) implementation (inherits from WMS) + */ + class TwoTasksAtATimeWMS : public WMS { + + public: + // Constructor + TwoTasksAtATimeWMS( + const std::set> &compute_services, + const std::set> &storage_services, + const std::string &hostname); + + protected: + + // Overriden method + void processEventStandardJobCompletion(std::shared_ptr) override; + void processEventStandardJobFailure(std::shared_ptr) override; + + private: + // main() method of the WMS + int main() override; + + }; +} +#endif //WRENCH_TWO_TASKS_AT_A_TIME_H diff --git a/examples/basic-examples/bare-metal-bag-of-tasks/two_hosts.xml b/examples/basic-examples/bare-metal-bag-of-tasks/two_hosts.xml new file mode 100644 index 0000000000..8ae719b0b8 --- /dev/null +++ b/examples/basic-examples/bare-metal-bag-of-tasks/two_hosts.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/basic-examples/bare-metal-chain-scratch/BareMetalChainScratch.cpp b/examples/basic-examples/bare-metal-chain-scratch/BareMetalChainScratch.cpp index ef1ba4de1e..91d9c22dba 100644 --- a/examples/basic-examples/bare-metal-chain-scratch/BareMetalChainScratch.cpp +++ b/examples/basic-examples/bare-metal-chain-scratch/BareMetalChainScratch.cpp @@ -19,10 +19,10 @@ ** the completion time of each workflow task is printed. ** ** Example invocation of the simulator for a 10-task workflow, with only WMS logging: - ** ./bare-meta-chain-simulator 10 ./two_hosts.xml --wrench-no-logs --log=workflow_as_a_single_job_wms.threshold=info + ** ./bare-metal-chain-simulator 10 ./two_hosts.xml --wrench-no-logs --log=workflow_as_a_single_job_wms.threshold=info ** ** Example invocation of the simulator for a 5-task workflow with full logging: - ** ./bare-meta-chain-simulator 5 ./two_hosts.xml + ** ./bare-metal-chain-simulator 5 ./two_hosts.xml **/ diff --git a/examples/basic-examples/bare-metal-chain/BareMetalChain.cpp b/examples/basic-examples/bare-metal-chain/BareMetalChain.cpp index c0e348ea99..3fad246b7a 100644 --- a/examples/basic-examples/bare-metal-chain/BareMetalChain.cpp +++ b/examples/basic-examples/bare-metal-chain/BareMetalChain.cpp @@ -19,10 +19,10 @@ ** the completion time of each workflow task is printed. ** ** Example invocation of the simulator for a 10-task workflow, with only WMS logging: - ** ./bare-meta-chain-simulator 10 ./two_hosts.xml --wrench-no-logs --log=one_task_at_a_time_wms.threshold=info + ** ./bare-metal-chain-simulator 10 ./two_hosts.xml --wrench-no-logs --log=one_task_at_a_time_wms.threshold=info ** ** Example invocation of the simulator for a 5-task workflow with full logging: - ** ./bare-meta-chain-simulator 5 ./two_hosts.xml + ** ./bare-metal-chain-simulator 5 ./two_hosts.xml **/