Skip to content

Commit 13cd2ac

Browse files
committed
DPL: record the workflow plugin of each data processor
DataProcessorInfo and DeviceExecution get the plugin which defines the data processor, as <library>:<plugin>, and the workflow JSON gets a "plugin" key in the metadata of the data processor. This is what a driver needs to load the plugins of a workflow itself. Plugins embedded in the executable, i.e. with an empty library, are not recorded: their plugin stays empty and the key is not written, so the dumps of all existing executables are unchanged. Internal data processors added by DPL have no plugin either. Data processors imported from an upstream workflow keep the plugin they had there. Tested by TestDataProcessorInfoPlugin, a round trip of a workflow with and without plugins, and test_Framework_test_WorkflowPluginsDump, which dumps the workflow of the two test plugins.
1 parent 91a803b commit 13cd2ac

9 files changed

Lines changed: 85 additions & 2 deletions

‎Framework/Core/CMakeLists.txt‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,11 @@ if(workflowPluginsTest)
472472
DPL_WORKFLOW_PLUGIN_LIBRARY=O2FrameworkTestWorkflowPlugin${p})
473473
add_dependencies(${workflowPluginsTest} O2FrameworkTestWorkflowPlugin${p})
474474
endforeach()
475+
add_test(NAME test_Framework_test_WorkflowPluginsDump
476+
COMMAND ${CMAKE_CURRENT_LIST_DIR}/test/test_WorkflowPluginsDump.sh $<TARGET_FILE:${workflowPluginsTest}>)
477+
set_property(TEST test_Framework_test_WorkflowPluginsDump PROPERTY LABELS framework)
478+
set_property(TEST test_Framework_test_WorkflowPluginsDump PROPERTY ENVIRONMENT
479+
"DYLD_LIBRARY_PATH=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}:$ENV{DYLD_LIBRARY_PATH};LD_LIBRARY_PATH=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}:$ENV{LD_LIBRARY_PATH}")
475480
endif()
476481

477482
# the test is compiled from the ExternalFairMQDeviceWorkflow test and run with

‎Framework/Core/include/Framework/DataProcessorInfo.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ struct DataProcessorInfo {
3232
std::vector<ConfigParamSpec> workflowOptions = {};
3333
/// The channels for a given dataprocessor
3434
std::vector<std::string> channels = {};
35+
/// The workflow plugin which defines the DataProcessorSpec, as <library>:<plugin>.
36+
/// Empty when it is the one embedded in the executable.
37+
std::string plugin = "";
3538
};
3639

3740
} // namespace o2

‎Framework/Core/include/Framework/DeviceExecution.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#ifndef FRAMEWORK_DEVICEEXECUTION_H
1212
#define FRAMEWORK_DEVICEEXECUTION_H
1313

14+
#include <string>
1415
#include <vector>
1516

1617
namespace o2
@@ -26,6 +27,8 @@ struct DeviceExecution {
2627
std::vector<char*> args;
2728
/// The environment to be passed to a given device
2829
std::vector<char*> environ;
30+
/// The workflow plugin which defines the device, see DataProcessorInfo::plugin
31+
std::string plugin;
2932
};
3033

3134
} // namespace framework

‎Framework/Core/include/Framework/WorkflowDefinitionContext.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "Framework/ChannelConfigurationPolicy.h"
2424
#include "Framework/ServiceRegistry.h"
2525
#include <functional>
26+
#include <map>
2627
#include <memory>
2728
#include <vector>
2829

@@ -47,6 +48,8 @@ struct WorkflowDefinitionContext {
4748
std::unique_ptr<ConfigParamRegistry> workflowOptionsRegistry{nullptr};
4849

4950
o2::framework::WorkflowSpec specs;
51+
/// The plugin which defines each data processor, if not embedded in the executable.
52+
std::map<std::string, std::string> processorPlugins;
5053
};
5154

5255
struct WorkflowDefinition {

‎Framework/Core/src/DeviceSpecHelpers.cxx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,6 +1749,7 @@ void DeviceSpecHelpers::prepareArguments(bool defaultQuiet, bool defaultStopped,
17491749
}
17501750
O2_SIGNPOST_END(device_spec_helpers, poid, "prepareArguments", "The following options are being forwarded to %{public}s: %{public}s",
17511751
spec.id.c_str(), str.str().c_str());
1752+
execution.plugin = pi->plugin;
17521753
}
17531754
}
17541755

‎Framework/Core/src/WorkflowSerializationHelpers.cxx‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ struct WorkflowImporter : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>,
9292
IN_DATAPROCESSOR_INFO,
9393
IN_DATAPROCESSOR_INFO_NAME,
9494
IN_DATAPROCESSOR_INFO_EXECUTABLE,
95+
IN_DATAPROCESSOR_INFO_PLUGIN,
9596
IN_DATAPROCESSOR_INFO_ARGS,
9697
IN_DATAPROCESSOR_INFO_ARG,
9798
IN_DATAPROCESSOR_INFO_CHANNELS,
@@ -264,6 +265,9 @@ struct WorkflowImporter : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>,
264265
case State::IN_DATAPROCESSOR_INFO_EXECUTABLE:
265266
s << "IN_DATAPROCESSOR_INFO_EXECUTABLE";
266267
break;
268+
case State::IN_DATAPROCESSOR_INFO_PLUGIN:
269+
s << "IN_DATAPROCESSOR_INFO_PLUGIN";
270+
break;
267271
case State::IN_DATAPROCESSOR_INFO_ARGS:
268272
s << "IN_DATAPROCESSOR_INFO_ARGS";
269273
break;
@@ -708,6 +712,8 @@ struct WorkflowImporter : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>,
708712
push(State::IN_DATAPROCESSOR_INFO_NAME);
709713
} else if (in(State::IN_DATAPROCESSOR_INFO) && strncmp(str, "executable", length) == 0) {
710714
push(State::IN_DATAPROCESSOR_INFO_EXECUTABLE);
715+
} else if (in(State::IN_DATAPROCESSOR_INFO) && strncmp(str, "plugin", length) == 0) {
716+
push(State::IN_DATAPROCESSOR_INFO_PLUGIN);
711717
} else if (in(State::IN_DATAPROCESSOR_INFO) && strncmp(str, "cmdLineArgs", length) == 0) {
712718
push(State::IN_DATAPROCESSOR_INFO_ARGS);
713719
} else if (in(State::IN_DATAPROCESSOR_INFO) && strncmp(str, "workflowOptions", length) == 0) {
@@ -734,6 +740,9 @@ struct WorkflowImporter : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>,
734740
} else if (in(State::IN_DATAPROCESSOR_INFO_EXECUTABLE)) {
735741
assert(metadata.size());
736742
metadata.back().executable = s;
743+
} else if (in(State::IN_DATAPROCESSOR_INFO_PLUGIN)) {
744+
assert(metadata.size());
745+
metadata.back().plugin = s;
737746
} else if (in(State::IN_INPUT_BINDING)) {
738747
binding = s;
739748
} else if (in(State::IN_INPUT_ORIGIN)) {
@@ -1270,6 +1279,10 @@ void WorkflowSerializationHelpers::dump(std::ostream& out,
12701279
w.String(info.name.c_str());
12711280
w.Key("executable");
12721281
w.String(info.executable.c_str());
1282+
if (!info.plugin.empty()) {
1283+
w.Key("plugin");
1284+
w.String(info.plugin.c_str());
1285+
}
12731286
w.Key("cmdLineArgs");
12741287
w.StartArray();
12751288
for (auto& arg : info.cmdLineArgs) {

‎Framework/Core/src/runDataProcessing.cxx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,7 @@ struct WorkflowInfo {
11831183
std::string executable;
11841184
std::vector<std::string> args;
11851185
std::vector<ConfigParamSpec> options;
1186+
std::map<std::string, std::string> plugins;
11861187
};
11871188

11881189
void gui_callback(uv_timer_s* ctx)
@@ -1696,7 +1697,8 @@ int runStateMachine(DataProcessorSpecs const& workflow,
16961697
workflowInfo.executable,
16971698
workflowInfo.args,
16981699
workflowInfo.options,
1699-
channels});
1700+
channels,
1701+
workflowInfo.plugins.contains(device.name) ? workflowInfo.plugins.at(device.name) : ""});
17001702
}
17011703
break;
17021704
case DriverState::MATERIALISE_WORKFLOW:
@@ -2989,6 +2991,9 @@ o2::framework::WorkflowDefinitionContext o2::framework::defineWorkflow(std::vect
29892991
if (!inserted && it->second != di) {
29902992
throw runtime_error_f("Data processor %s is defined by both %s and %s.", spec.name.c_str(), names[it->second].c_str(), names[di].c_str());
29912993
}
2994+
if (!names[di].starts_with(":")) {
2995+
result.processorPlugins[spec.name] = names[di];
2996+
}
29922997
}
29932998
append(result.specs, context.specs);
29942999
append(result.channelPolicies, context.channelPolicies);
@@ -3035,7 +3040,8 @@ int doMain(int argc, char** argv, o2::framework::WorkflowDefinitionContext& work
30353040
WorkflowInfo currentWorkflow{
30363041
argv[0],
30373042
currentArgs,
3038-
currentWorkflowOptions};
3043+
currentWorkflowOptions,
3044+
workflowContext.processorPlugins};
30393045

30403046
ProcessingPolicies processingPolicies;
30413047
enum LogParsingHelpers::LogLevel minFailureLevel;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/sh -e
2+
# Dumps the workflow of the two test plugins and checks that it records,
3+
# for each data processor, the plugin which defines it.
4+
# usage: test_WorkflowPluginsDump.sh <o2-test-framework-WorkflowPlugins>
5+
A=O2FrameworkTestWorkflowPluginA:TestWorkflowPluginA
6+
B=O2FrameworkTestWorkflowPluginB:TestWorkflowPluginB
7+
dump=$("$1" -b --workflow-plugin $A,$B --dump-workflow < /dev/null)
8+
echo "$dump" | grep -q "\"plugin\": \"$A\""
9+
echo "$dump" | grep -q "\"plugin\": \"$B\""
10+
test "$(echo "$dump" | grep -c '"plugin"')" -eq 2

‎Framework/Core/test/test_WorkflowSerialization.cxx‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,42 @@ TEST_CASE("TestInputOutputSpecMetadata")
169169
REQUIRE(wso[0].outputs[0].metadata[0] == wsi[0].outputs[0].metadata[0]);
170170
REQUIRE(wso[0].outputs[0].metadata[1] == wsi[0].outputs[0].metadata[1]);
171171
}
172+
173+
/// The plugin of a data processor is only written when there is one,
174+
/// so that dumps of executables with an embedded plugin do not change.
175+
TEST_CASE("TestDataProcessorInfoPlugin")
176+
{
177+
using namespace o2::framework;
178+
WorkflowSpec w0{
179+
DataProcessorSpec{.name = "A", .outputs = {OutputSpec{"TST", "A"}}},
180+
DataProcessorSpec{.name = "B", .inputs = {InputSpec{"a", "TST", "A"}}}};
181+
182+
std::vector<DataProcessorInfo> dataProcessorInfoOut{
183+
{.name = "A", .executable = "o2-dpl-workflow", .plugin = "O2SomePlugins:A"},
184+
{.name = "B", .executable = "o2-dpl-workflow"},
185+
};
186+
CommandInfo commandInfoOut{"o2-dpl-workflow -b"};
187+
188+
std::ostringstream firstDump;
189+
WorkflowSerializationHelpers::dump(firstDump, w0, dataProcessorInfoOut, commandInfoOut);
190+
auto json = firstDump.str();
191+
REQUIRE(json.find(R"("plugin": "O2SomePlugins:A")") != std::string::npos);
192+
// Only A has a plugin.
193+
REQUIRE(json.find(R"("plugin")") == json.rfind(R"("plugin")"));
194+
195+
std::vector<DataProcessorInfo> dataProcessorInfoIn{};
196+
CommandInfo commandInfoIn;
197+
std::istringstream is;
198+
is.str(json);
199+
WorkflowSpec w1;
200+
WorkflowSerializationHelpers::import(is, w1, dataProcessorInfoIn, commandInfoIn);
201+
REQUIRE(dataProcessorInfoIn.size() == 2);
202+
REQUIRE(dataProcessorInfoIn[0].plugin == "O2SomePlugins:A");
203+
REQUIRE(dataProcessorInfoIn[0].executable == "o2-dpl-workflow");
204+
REQUIRE(dataProcessorInfoIn[1].plugin.empty());
205+
REQUIRE(dataProcessorInfoIn[1].executable == "o2-dpl-workflow");
206+
207+
std::ostringstream secondDump;
208+
WorkflowSerializationHelpers::dump(secondDump, w1, dataProcessorInfoIn, commandInfoIn);
209+
REQUIRE(json == secondDump.str());
210+
}

0 commit comments

Comments
 (0)