Skip to content

Commit

Permalink
Implement edmtest::GenericCloner
Browse files Browse the repository at this point in the history
This EDProducer will clone all the event products declared by its
configuration, using their ROOT dictionaries.

Refactor common functionality with GenericConsumer.
  • Loading branch information
fwyzard committed Nov 26, 2024
1 parent 79bb53a commit 339cf8b
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 82 deletions.
92 changes: 10 additions & 82 deletions FWCore/Modules/src/GenericConsumer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,80 +40,7 @@
#include "FWCore/ParameterSet/interface/ParameterDescriptionNode.h"
#include "FWCore/ParameterSet/interface/ParameterSet.h"
#include "FWCore/ParameterSet/interface/ParameterSetDescription.h"

namespace {
struct ProductBranch {
public:
ProductBranch(std::string const& label) {
static const char kSeparator = '_';
static const char kWildcard = '*';
static const std::regex kAny{".*"};

// wildcard
if (label == kWildcard) {
type_ = kAny;
moduleLabel_ = kAny;
productInstanceName_ = kAny;
processName_ = kAny;
return;
}

int fields = std::count(label.begin(), label.end(), kSeparator) + 1;
if (fields == 1) {
// convert the module label into a regular expression
type_ = kAny;
moduleLabel_ = glob_to_regex(label);
productInstanceName_ = kAny;
processName_ = kAny;
} else if (fields == 4) {
// split the branch name into <product type>_<module label>_<instance name>_<process name>
// and convert the glob expressions into regular expressions
size_t first = 0, last = 0;
last = label.find(kSeparator, first);
type_ = glob_to_regex(label.substr(first, last - first));
first = last + 1;
last = label.find(kSeparator, first);
moduleLabel_ = glob_to_regex(label.substr(first, last - first));
first = last + 1;
last = label.find(kSeparator, first);
productInstanceName_ = glob_to_regex(label.substr(first, last - first));
first = last + 1;
last = label.find(kSeparator, first);
processName_ = glob_to_regex(label.substr(first, last - first));
} else {
// invalid input
throw edm::Exception(edm::errors::Configuration) << "Invalid module label or branch name: \"" << label << "\"";
}
}

bool match(edm::BranchDescription const& branch) const {
return (std::regex_match(branch.friendlyClassName(), type_) and
std::regex_match(branch.moduleLabel(), moduleLabel_) and
std::regex_match(branch.productInstanceName(), productInstanceName_) and
std::regex_match(branch.processName(), processName_));
}

private:
static std::regex glob_to_regex(std::string pattern) {
boost::replace_all(pattern, "*", ".*");
boost::replace_all(pattern, "?", ".");
return std::regex(pattern);
}

std::regex type_;
std::regex moduleLabel_;
std::regex productInstanceName_;
std::regex processName_;
};

std::vector<ProductBranch> make_patterns(std::vector<std::string> const& labels) {
std::vector<ProductBranch> patterns;
patterns.reserve(labels.size());
for (auto const& label : labels)
patterns.emplace_back(label);
return patterns;
}
} // namespace
#include "FWCore/Utilities/interface/BranchPattern.h"

namespace edm {
class GenericConsumer : public edm::global::EDAnalyzer<> {
Expand All @@ -126,19 +53,20 @@ namespace edm {
static void fillDescriptions(ConfigurationDescriptions& descriptions);

private:
std::vector<ProductBranch> eventProducts_;
std::vector<ProductBranch> lumiProducts_;
std::vector<ProductBranch> runProducts_;
std::vector<ProductBranch> processProducts_;
std::vector<edm::BranchPattern> eventProducts_;
std::vector<edm::BranchPattern> lumiProducts_;
std::vector<edm::BranchPattern> runProducts_;
std::vector<edm::BranchPattern> processProducts_;
std::string label_;
bool verbose_;
};

GenericConsumer::GenericConsumer(ParameterSet const& config)
: eventProducts_(make_patterns(config.getUntrackedParameter<std::vector<std::string>>("eventProducts"))),
lumiProducts_(make_patterns(config.getUntrackedParameter<std::vector<std::string>>("lumiProducts"))),
runProducts_(make_patterns(config.getUntrackedParameter<std::vector<std::string>>("runProducts"))),
processProducts_(make_patterns(config.getUntrackedParameter<std::vector<std::string>>("processProducts"))),
: eventProducts_(edm::branchPatterns(config.getUntrackedParameter<std::vector<std::string>>("eventProducts"))),
lumiProducts_(edm::branchPatterns(config.getUntrackedParameter<std::vector<std::string>>("lumiProducts"))),
runProducts_(edm::branchPatterns(config.getUntrackedParameter<std::vector<std::string>>("runProducts"))),
processProducts_(
edm::branchPatterns(config.getUntrackedParameter<std::vector<std::string>>("processProducts"))),
label_(config.getParameter<std::string>("@module_label")),
verbose_(config.getUntrackedParameter<bool>("verbose")) {
callWhenNewProductsRegistered([this](edm::BranchDescription const& branch) {
Expand Down
11 changes: 11 additions & 0 deletions FWCore/TestModules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,14 @@ product read from the `Event`.
Together `edmtest::EventIDProducer` and `edmtest::EventIDValidator` can be used
to validate that an object produced in a given event is being read back in the
same event.


## `edmtest::GenericCloner`

This module will clone all the event products declared by its configuration,
using their ROOT dictionaries.
The products can be specified either as module labels (_e.g._ `<module label>`)
or as branch names (_e.g._ `<product type>_<module label>_<instance name>_<process name>`).
Glob expressions (`?` and `*`) are supported in module labels and within the
individual fields of branch names, similar to an `OutputModule`'s `keep`
statements.
181 changes: 181 additions & 0 deletions FWCore/TestModules/plugins/GenericCloner.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* This EDProducer will clone all the event products declared by its configuration, using their ROOT dictionaries.
*
* The products can be specified either as module labels (e.g. "<module label>") or as branch names (e.g.
* "<product type>_<module label>_<instance name>_<process name>").
*
* If a module label is used, no underscore ("_") must be present; this module will clone all the products produced by
* that module, including those produced by the Transformer functionality (such as the implicitly copied-to-host
* products in case of Alpaka-based modules).
* If a branch name is used, all four fields must be present, separated by underscores; this module will clone only on
* the matching product(s).
*
* Glob expressions ("?" and "*") are supported in module labels and within the individual fields of branch names,
* similar to an OutputModule's "keep" statements.
* Use "*" to clone all products.
*
* For example, in the case of Alpaka-based modules running on a device, using
*
* eventProducts = cms.untracked.vstring( "module" )
*
* will cause "module" to run, along with automatic copy of its device products to the host, and will attempt to clone
* all device and host products.
* To clone only the host product, the branch can be specified explicitly with
*
* eventProducts = cms.untracked.vstring( "HostProductType_module_*_*" )
*
* .
*/

#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include <TBufferFile.h>

#include "DataFormats/Provenance/interface/BranchDescription.h"
#include "FWCore/Framework/interface/Event.h"
#include "FWCore/Framework/interface/GenericHandle.h"
#include "FWCore/Framework/interface/GenericProduct.h"
#include "FWCore/Framework/interface/global/EDProducer.h"
#include "FWCore/MessageLogger/interface/MessageLogger.h"
#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h"
#include "FWCore/ParameterSet/interface/ParameterDescriptionNode.h"
#include "FWCore/ParameterSet/interface/ParameterSet.h"
#include "FWCore/ParameterSet/interface/ParameterSetDescription.h"
#include "FWCore/Reflection/interface/ObjectWithDict.h"
#include "FWCore/Utilities/interface/BranchPattern.h"

namespace edmtest {

class GenericCloner : public edm::global::EDProducer<> {
public:
explicit GenericCloner(edm::ParameterSet const&);
~GenericCloner() override = default;

void produce(edm::StreamID, edm::Event&, edm::EventSetup const&) const override;

static void fillDescriptions(edm::ConfigurationDescriptions& descriptions);

private:
struct Entry {
edm::TypeWithDict objectType_;
edm::TypeWithDict wrappedType_;
edm::EDGetToken getToken_;
edm::EDPutToken putToken_;
};

std::vector<edm::BranchPattern> eventPatterns_;
std::vector<Entry> eventProducts_;
std::string label_;
bool verbose_;
};

GenericCloner::GenericCloner(edm::ParameterSet const& config)
: eventPatterns_(edm::branchPatterns(config.getParameter<std::vector<std::string>>("eventProducts"))),
label_(config.getParameter<std::string>("@module_label")),
verbose_(config.getUntrackedParameter<bool>("verbose")) {
eventProducts_.reserve(eventPatterns_.size());

callWhenNewProductsRegistered([this](edm::BranchDescription const& branch) {
static const std::string_view kPathStatus("edm::PathStatus");
static const std::string_view kEndPathStatus("edm::EndPathStatus");

switch (branch.branchType()) {
case edm::InEvent:
if (branch.className() == kPathStatus or branch.className() == kEndPathStatus)
return;
for (auto& pattern : eventPatterns_)
if (pattern.match(branch)) {
Entry product;
product.objectType_ = branch.unwrappedType();
product.wrappedType_ = branch.wrappedType();
// TODO move this to EDConsumerBase::consumes() ?
product.getToken_ = this->consumes(
edm::TypeToGet{branch.unwrappedTypeID(), edm::PRODUCT_TYPE},
edm::InputTag{branch.moduleLabel(), branch.productInstanceName(), branch.processName()});
product.putToken_ = this->produces(branch.unwrappedTypeID(), branch.productInstanceName());
eventProducts_.push_back(product);

if (verbose_) {
edm::LogInfo("GenericCloner")
<< label_ << " will clone Event product " << branch.friendlyClassName() << '_'
<< branch.moduleLabel() << '_' << branch.productInstanceName() << '_' << branch.processName();
}
break;
}
break;

case edm::InLumi:
case edm::InRun:
case edm::InProcess:
// lumi, run and process products are not supported
break;

default:
throw edm::Exception(edm::errors::LogicError)
<< "Unexpected branch type " << branch.branchType() << "\nPlease contact a Framework developer\n";
}
});
}

void GenericCloner::produce(edm::StreamID /*unused*/, edm::Event& event, edm::EventSetup const& /*unused*/) const {
for (auto& product : eventProducts_) {
edm::GenericHandle handle(product.objectType_);
event.getByToken(product.getToken_, handle);
edm::ObjectWithDict const* object = handle.product();

TBufferFile send_buffer(TBuffer::kWrite);
send_buffer.WriteObjectAny(object->address(), product.objectType_.getClass(), false);
int size = send_buffer.Length();

TBufferFile recv_buffer(TBuffer::kRead, size);
std::memcpy(recv_buffer.Buffer(), send_buffer.Buffer(), size);

void* clone_ptr = reinterpret_cast<void*>(recv_buffer.ReadObjectAny(product.objectType_.getClass()));
auto clone = std::make_unique<edm::GenericProduct>();
clone->object_ = edm::ObjectWithDict(product.objectType_, clone_ptr);
clone->wrappedType_ = product.wrappedType_;

// specialise Event::put for GenericProduct
event.put(product.putToken_, std::move(clone));
}
}

void GenericCloner::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {
descriptions.setComment(
R"(This EDProducer will clone all the event products declared by its configuration, using their ROOT dictionaries.
The products can be specified either as module labels (e.g. "<module label>") or as branch names (e.g. "<product type>_<module label>_<instance name>_<process name>").
If a module label is used, no underscore ("_") must be present; this module will clone all the products produced by that module, including those produced by the Transformer functionality (such as the implicitly copied-to-host products in case of Alpaka-based modules).
If a branch name is used, all four fields must be present, separated by underscores; this module will clone only on the matching product(s).
Glob expressions ("?" and "*") are supported in module labels and within the individual fields of branch names, similar to an OutputModule's "keep" statements.
Use "*" to clone all products.
For example, in the case of Alpaka-based modules running on a device, using
eventProducts = cms.untracked.vstring( "module" )
will cause "module" to run, along with automatic copy of its device products to the host, and will attempt to clone all device and host products.
To clone only the host product, the branch can be specified explicitly with
eventProducts = cms.untracked.vstring( "HostProductType_module_*_*" )
.)");

edm::ParameterSetDescription desc;
desc.add<std::vector<std::string>>("eventProducts", {})
->setComment("List of modules or branches whose event products will be cloned.");
desc.addUntracked<bool>("verbose", false)
->setComment("Print the branch names of the products that will be cloned.");
descriptions.addWithDefaultLabel(desc);
}

} // namespace edmtest

#include "FWCore/Framework/interface/MakerMacros.h"
DEFINE_FWK_MODULE(edmtest::GenericCloner);
2 changes: 2 additions & 0 deletions FWCore/TestModules/test/BuildFile.xml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<test name="TestFWCoreModulesEventIDValidator" command="cmsRun ${LOCALTOP}/src/FWCore/TestModules/test/testEventIDValidator_cfg.py"/>

<test name="TestFWCoreModulesGenericCloner" command="cmsRun ${LOCALTOP}/src/FWCore/TestModules/test/testGenericCloner_cfg.py"/>
36 changes: 36 additions & 0 deletions FWCore/TestModules/test/testGenericCloner_cfg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import FWCore.ParameterSet.Config as cms

process = cms.Process("TEST")

process.load("FWCore.MessageService.MessageLogger_cfi")
process.MessageLogger.cerr.INFO.limit = 10000000

process.options.numberOfThreads = 4
process.options.numberOfStreams = 4

process.source = cms.Source("EmptySource")
process.maxEvents.input = 10

process.eventIds = cms.EDProducer("edmtest::EventIDProducer")

process.cloneByLabel = cms.EDProducer("edmtest::GenericCloner",
eventProducts = cms.vstring("eventIds"),
verbose = cms.untracked.bool(True)
)

process.cloneByBranch = cms.EDProducer("edmtest::GenericCloner",
eventProducts = cms.vstring("*_eventIds__TEST"),
verbose = cms.untracked.bool(True)
)

process.validateByLabel = cms.EDAnalyzer("edmtest::EventIDValidator",
source = cms.untracked.InputTag('cloneByLabel')
)

process.validateByBranch = cms.EDAnalyzer("edmtest::EventIDValidator",
source = cms.untracked.InputTag('cloneByBranch')
)

process.task = cms.Task(process.eventIds, process.cloneByLabel, process.cloneByBranch)

process.path = cms.Path(process.validateByLabel + process.validateByBranch, process.task)
Loading

0 comments on commit 339cf8b

Please sign in to comment.