Skip to content

Commit dcae84b

Browse files
committed
DPL Analysis: do not build the tasks a device does not run
Every device used to construct all the analysis tasks of its executable only to describe them, although the driver already passes their specs on stdin. Their constructors create histograms, which start the ROOT interpreter in the reader, the writer and the other internal devices. A device now builds only its own task and takes the specs of the others from the driver. Without a workflow on stdin, or with --clone, everything is still built. Reader of o2-testworkflows-analysis-histograms on macOS: physical footprint 120.5 MB -> 74.7 MB, libCling and the .pcm modules no longer mapped. Output identical.
1 parent 86084e0 commit dcae84b

7 files changed

Lines changed: 120 additions & 0 deletions

File tree

‎Framework/Core/include/Framework/AnalysisTask.h‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "Framework/ConfigParamsHelper.h"
2020
#include "Framework/ControlService.h"
2121
#include "Framework/DataProcessorSpec.h"
22+
#include "Framework/DataProcessorSpecHelpers.h"
2223
#include "Framework/Expressions.h"
2324
#include "Framework/EndOfStreamContext.h"
2425
#include "Framework/GroupSlicer.h"
@@ -450,6 +451,24 @@ struct TaskName {
450451

451452
namespace
452453
{
454+
template <typename T, typename... A>
455+
std::string getTaskName(TaskName const& first, A const&...)
456+
{
457+
return first.value;
458+
}
459+
460+
template <typename T, typename... A>
461+
std::string getTaskName(SetDefaultProcesses const&, TaskName const& second, A const&...)
462+
{
463+
return second.value;
464+
}
465+
466+
template <typename T, typename... A>
467+
std::string getTaskName(A const&...)
468+
{
469+
return type_to_task_name(type_name<T>());
470+
}
471+
453472
template <typename T, typename... A>
454473
auto getTaskNameSetProcesses(std::string& outputName, TaskName first, SetDefaultProcesses second, A... args)
455474
{
@@ -519,6 +538,10 @@ auto getTaskNameSetProcesses(std::string& outputName, A... args)
519538
template <typename T, typename... Args>
520539
DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args)
521540
{
541+
if (auto name = getTaskName<T>(args...) + ctx.options().get<std::string>("workflow-suffix"); !ctx.mayRun(name)) {
542+
return DataProcessorSpecHelpers::placeholder(name);
543+
}
544+
522545
TH1::AddDirectory(false);
523546

524547
std::string name_str;

‎Framework/Core/include/Framework/ConfigContext.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class ConfigContext
2929
[[nodiscard]] ServiceRegistryRef services() const { return mServices; }
3030

3131
[[nodiscard]] bool helpOnCommandLine() const;
32+
/// Whether this process may run @a processor. A device only runs its own;
33+
/// it gets the specs of the others from the driver, so they need not be built.
34+
[[nodiscard]] bool mayRun(std::string_view processor) const;
3235

3336
[[nodiscard]] int argc() const { return mArgc; }
3437
[[nodiscard]] char* const* argv() const { return mArgv; }

‎Framework/Core/include/Framework/DataProcessorSpecHelpers.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ namespace o2::framework
1818
{
1919
struct DataProcessorSpecHelpers {
2020
static bool hasLabel(DataProcessorSpec const& spec, char const* name);
21+
/// Stands for a processor this process does not run: everything but the name
22+
/// is filled from the workflow the driver passes to the device.
23+
static DataProcessorSpec placeholder(std::string name);
24+
static bool isPlaceholder(DataProcessorSpec const& spec);
2125
};
2226
} // namespace o2::framework
2327
#endif

‎Framework/Core/src/ConfigContext.cxx‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
// or submit itself to any jurisdiction.
1111

1212
#include "Framework/ConfigContext.h"
13+
#include <regex>
14+
#include <sys/stat.h>
15+
#include <unistd.h>
1316

1417
namespace o2::framework
1518
{
@@ -29,4 +32,23 @@ bool ConfigContext::helpOnCommandLine() const
2932
return helpasked;
3033
}
3134

35+
bool ConfigContext::mayRun(std::string_view processor) const
36+
{
37+
std::string_view id;
38+
for (int argi = 0; argi + 1 < mArgc; ++argi) {
39+
if (strcmp(mArgv[argi], "--id") == 0) {
40+
id = mArgv[argi + 1];
41+
}
42+
}
43+
// The driver, a device without a workflow on stdin and clones, which keep the
44+
// name of their template, build everything.
45+
struct stat s;
46+
if (id.empty() || fstat(STDIN_FILENO, &s) != 0 || !(S_ISFIFO(s.st_mode) || S_ISREG(s.st_mode)) || !mOptions.get<std::string>("clone").empty()) {
47+
return true;
48+
}
49+
// Pipelined devices get a _t<N> suffix, DDS ones a _dds<...> one.
50+
auto name = std::regex_replace(std::regex_replace(std::string{id}, std::regex{"_dds.*"}, ""), std::regex{"_t[0-9]+$"}, "");
51+
return id == processor || name == processor;
52+
}
53+
3254
} // namespace o2::framework

‎Framework/Core/src/DataProcessorSpecHelpers.cxx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// or submit itself to any jurisdiction.
1111

1212
#include "Framework/DataProcessorSpecHelpers.h"
13+
#include "Framework/Logger.h"
1314
#include <string>
1415
#include <algorithm>
1516

@@ -20,4 +21,20 @@ bool DataProcessorSpecHelpers::hasLabel(DataProcessorSpec const& spec, char cons
2021
auto sameLabel = [other = DataProcessorLabel{{label}}](DataProcessorLabel const& label) { return label == other; };
2122
return std::find_if(spec.labels.begin(), spec.labels.end(), sameLabel) != spec.labels.end();
2223
}
24+
25+
DataProcessorSpec DataProcessorSpecHelpers::placeholder(std::string name)
26+
{
27+
return DataProcessorSpec{
28+
.name = name,
29+
.algorithm = AlgorithmSpec::InitCallback{[name](InitContext&) -> AlgorithmSpec::ProcessCallback {
30+
LOGP(fatal, "{} was not built in this process, which therefore cannot run it.", name);
31+
return {};
32+
}},
33+
.metadata = {{"dpl-placeholder", "true"}}};
34+
}
35+
36+
bool DataProcessorSpecHelpers::isPlaceholder(DataProcessorSpec const& spec)
37+
{
38+
return std::ranges::any_of(spec.metadata, [](auto const& m) { return m.key == "dpl-placeholder"; });
39+
}
2340
} // namespace o2::framework

‎Framework/Core/src/runDataProcessing.cxx‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "Framework/DataProcessingDevice.h"
2727
#include "Framework/DataProcessingContext.h"
2828
#include "Framework/DataProcessorSpec.h"
29+
#include "Framework/DataProcessorSpecHelpers.h"
2930
#include "Framework/PluginManager.h"
3031
#include "Framework/DeviceControl.h"
3132
#include "Framework/DeviceExecution.h"
@@ -3037,9 +3038,18 @@ int doMain(int argc, char** argv, o2::framework::WorkflowSpec const& workflow,
30373038
if (found == physicalWorkflow.end()) {
30383039
physicalWorkflow.push_back(dp);
30393040
rankIndex.insert(std::make_pair(dp.name, workflowHashB));
3041+
} else if (DataProcessorSpecHelpers::isPlaceholder(*found)) {
3042+
auto algorithm = std::move(found->algorithm);
3043+
*found = dp;
3044+
found->algorithm = std::move(algorithm);
30403045
}
30413046
}
30423047
}
3048+
for (auto& dp : physicalWorkflow) {
3049+
if (DataProcessorSpecHelpers::isPlaceholder(dp)) {
3050+
LOGP(fatal, "{} was not built, but the driver did not pass its spec.", dp.name);
3051+
}
3052+
}
30433053

30443054
/// Iterate over the physicalWorkflow, any DataProcessorSpec that has a
30453055
/// expendable label should have all the timeframe lifetime outputs changed

‎Framework/Core/test/test_AnalysisTask.cxx‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "Framework/AnalysisTask.h"
1515
#include "Framework/AnalysisDataModel.h"
1616
#include <iostream>
17+
#include <unistd.h>
1718

1819
#include <catch_amalgamated.hpp>
1920

@@ -197,6 +198,46 @@ struct MTask {
197198
void process(aod::McCollision const&, soa::SmallGroups<soa::Join<aod::Collisions, aod::McCollisionLabels>> const&) {}
198199
};
199200

201+
struct BuildCounter {
202+
static inline int built = 0;
203+
BuildCounter() { ++built; }
204+
};
205+
206+
struct CountedTask {
207+
BuildCounter counter;
208+
void process(aod::Tracks const&) {}
209+
};
210+
211+
TEST_CASE("AdaptorBuildsOnlyTheTaskOfTheDevice")
212+
{
213+
auto cfgc = makeEmptyConfigContext();
214+
// A device gets the workflow from the driver on stdin.
215+
int fds[2];
216+
REQUIRE(pipe(fds) == 0);
217+
int stdinCopy = dup(STDIN_FILENO);
218+
dup2(fds[0], STDIN_FILENO);
219+
char const* otherArgs[] = {"workflow", "--id", "another-task"};
220+
char const* ownArgs[] = {"workflow", "--id", "counted-task_t1"};
221+
ConfigContext otherDevice{cfgc->options(), cfgc->services(), 3, const_cast<char**>(otherArgs)};
222+
ConfigContext ownDevice{cfgc->options(), cfgc->services(), 3, const_cast<char**>(ownArgs)};
223+
auto skipped = adaptAnalysisTask<CountedTask>(otherDevice);
224+
auto own = adaptAnalysisTask<CountedTask>(ownDevice);
225+
auto renamed = adaptAnalysisTask<CountedTask>(otherDevice, SetDefaultProcesses{}, TaskName{"another-task"});
226+
dup2(stdinCopy, STDIN_FILENO);
227+
close(stdinCopy);
228+
close(fds[0]);
229+
close(fds[1]);
230+
auto driver = adaptAnalysisTask<CountedTask>(*cfgc);
231+
232+
REQUIRE(BuildCounter::built == 3);
233+
REQUIRE(DataProcessorSpecHelpers::isPlaceholder(skipped));
234+
REQUIRE(skipped.name == own.name);
235+
REQUIRE(skipped.inputs.empty());
236+
REQUIRE(!DataProcessorSpecHelpers::isPlaceholder(own));
237+
REQUIRE(own.inputs.size() == driver.inputs.size());
238+
REQUIRE(!DataProcessorSpecHelpers::isPlaceholder(renamed));
239+
}
240+
200241
TEST_CASE("AdaptorCompilation")
201242
{
202243
auto cfgc = makeEmptyConfigContext();

0 commit comments

Comments
 (0)