Skip to content

Commit 5bd0828

Browse files
committed
Implement edmtest::GenericCloner
This EDProducer will clone all the event products declared by its configuration, using their ROOT dictionaries.
1 parent a72baa8 commit 5bd0828

File tree

4 files changed

+297
-0
lines changed

4 files changed

+297
-0
lines changed

FWCore/TestModules/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,14 @@ product read from the `Event`.
3737
Together `edmtest::EventIDProducer` and `edmtest::EventIDValidator` can be used
3838
to validate that an object produced in a given event is being read back in the
3939
same event.
40+
41+
42+
## `edmtest::GenericCloner`
43+
44+
This module will clone all the event products declared by its configuration,
45+
using their ROOT dictionaries.
46+
The products can be specified either as module labels (_e.g._ `<module label>`)
47+
or as branch names (_e.g._ `<product type>_<module label>_<instance name>_<process name>`).
48+
Glob expressions (`?` and `*`) are supported in module labels and within the
49+
individual fields of branch names, similar to an `OutputModule`'s `keep`
50+
statements.
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* This EDProducer will clone all the event products declared by its configuration, using their ROOT dictionaries.
3+
*
4+
* The products can be specified either as module labels (e.g. "<module label>") or as branch names (e.g.
5+
* "<product type>_<module label>_<instance name>_<process name>").
6+
*
7+
* If a module label is used, no underscore ("_") must be present; this module will clone all the products produced by
8+
* that module, including those produced by the Transformer functionality (such as the implicitly copied-to-host
9+
* products in case of Alpaka-based modules).
10+
* If a branch name is used, all four fields must be present, separated by underscores; this module will clone only on
11+
* the matching product(s).
12+
*
13+
* Glob expressions ("?" and "*") are supported in module labels and within the individual fields of branch names,
14+
* similar to an OutputModule's "keep" statements.
15+
* Use "*" to clone all products.
16+
*
17+
* For example, in the case of Alpaka-based modules running on a device, using
18+
*
19+
* eventProducts = cms.untracked.vstring( "module" )
20+
*
21+
* will cause "module" to run, along with automatic copy of its device products to the host, and will attempt to clone
22+
* all device and host products.
23+
* To clone only the host product, the branch can be specified explicitly with
24+
*
25+
* eventProducts = cms.untracked.vstring( "HostProductType_module_*_*" )
26+
*
27+
* .
28+
*/
29+
30+
#include <cstring>
31+
#include <iostream>
32+
#include <memory>
33+
#include <string>
34+
#include <string_view>
35+
#include <utility>
36+
#include <vector>
37+
38+
#include <TBufferFile.h>
39+
40+
#include "DataFormats/Provenance/interface/ProductDescription.h"
41+
#include "DataFormats/Provenance/interface/ProductNamePattern.h"
42+
#include "FWCore/Framework/interface/Event.h"
43+
#include "FWCore/Framework/interface/GenericHandle.h"
44+
#include "FWCore/Framework/interface/GenericProduct.h"
45+
#include "FWCore/Framework/interface/global/EDProducer.h"
46+
#include "FWCore/MessageLogger/interface/MessageLogger.h"
47+
#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h"
48+
#include "FWCore/ParameterSet/interface/ParameterDescriptionNode.h"
49+
#include "FWCore/ParameterSet/interface/ParameterSet.h"
50+
#include "FWCore/ParameterSet/interface/ParameterSetDescription.h"
51+
#include "FWCore/Reflection/interface/ObjectWithDict.h"
52+
#include "FWCore/Utilities/interface/EDMException.h"
53+
54+
namespace edmtest {
55+
56+
class GenericCloner : public edm::global::EDProducer<> {
57+
public:
58+
explicit GenericCloner(edm::ParameterSet const&);
59+
~GenericCloner() override = default;
60+
61+
void produce(edm::StreamID, edm::Event&, edm::EventSetup const&) const override;
62+
63+
static void fillDescriptions(edm::ConfigurationDescriptions& descriptions);
64+
65+
private:
66+
struct Entry {
67+
edm::TypeWithDict objectType_;
68+
edm::TypeWithDict wrappedType_;
69+
edm::EDGetToken getToken_;
70+
edm::EDPutToken putToken_;
71+
};
72+
73+
std::vector<edm::ProductNamePattern> eventPatterns_;
74+
std::vector<Entry> eventProducts_;
75+
std::string label_;
76+
bool verbose_;
77+
};
78+
79+
GenericCloner::GenericCloner(edm::ParameterSet const& config)
80+
: eventPatterns_(edm::productPatterns(config.getParameter<std::vector<std::string>>("eventProducts"))),
81+
label_(config.getParameter<std::string>("@module_label")),
82+
verbose_(config.getUntrackedParameter<bool>("verbose")) {
83+
eventProducts_.reserve(eventPatterns_.size());
84+
85+
callWhenNewProductsRegistered([this](edm::ProductDescription const& branch) {
86+
static const std::string_view kPathStatus("edm::PathStatus");
87+
static const std::string_view kEndPathStatus("edm::EndPathStatus");
88+
89+
switch (branch.branchType()) {
90+
case edm::InEvent:
91+
if (branch.className() == kPathStatus or branch.className() == kEndPathStatus) {
92+
return;
93+
}
94+
for (auto& pattern : eventPatterns_) {
95+
if (pattern.match(branch)) {
96+
Entry product;
97+
product.objectType_ = branch.unwrappedType();
98+
product.wrappedType_ = branch.wrappedType();
99+
// TODO move this to EDConsumerBase::consumes() ?
100+
product.getToken_ = this->consumes(
101+
edm::TypeToGet{branch.unwrappedTypeID(), edm::PRODUCT_TYPE},
102+
edm::InputTag{branch.moduleLabel(), branch.productInstanceName(), branch.processName()});
103+
product.putToken_ = this->produces(branch.unwrappedTypeID(), branch.productInstanceName());
104+
eventProducts_.push_back(product);
105+
106+
if (verbose_) {
107+
edm::LogInfo("GenericCloner")
108+
<< label_ << " will clone Event product " << branch.friendlyClassName() << '_'
109+
<< branch.moduleLabel() << '_' << branch.productInstanceName() << '_' << branch.processName();
110+
}
111+
break;
112+
}
113+
}
114+
break;
115+
116+
case edm::InLumi:
117+
case edm::InRun:
118+
case edm::InProcess:
119+
// lumi, run and process products are not supported
120+
break;
121+
122+
default:
123+
throw edm::Exception(edm::errors::LogicError)
124+
<< "Unexpected branch type " << branch.branchType() << "\nPlease contact a Framework developer.";
125+
}
126+
});
127+
}
128+
129+
void GenericCloner::produce(edm::StreamID /*unused*/, edm::Event& event, edm::EventSetup const& /*unused*/) const {
130+
for (auto& product : eventProducts_) {
131+
edm::GenericHandle handle(product.objectType_);
132+
event.getByToken(product.getToken_, handle);
133+
edm::ObjectWithDict const* object = handle.product();
134+
135+
// An ObjectWithDict can hold
136+
// - a fundamental type,
137+
// - a class type (class, struct or enum),
138+
// - a pointer,
139+
// - a C array.
140+
//
141+
// Pointers can be treated as fundamental types and shallow-copied.
142+
// C arrays are not supported as event products, so can be skipped.
143+
if (product.objectType_.isFundamental() or product.objectType_.isPointer()) {
144+
// fundamental types can simply be copied, and the copy is done implicitly by the Event::put() specialisation for GenericProduct
145+
event.put(product.putToken_, std::make_unique<edm::GenericProduct>(*object, product.wrappedType_));
146+
} else if (product.objectType_.isClass()) {
147+
// objects are cloned using their ROOT dictionary
148+
if (product.wrappedType_.dataMemberByName("obj").isTransient()) {
149+
throw edm::Exception(edm::errors::LogicError)
150+
<< "Type " << product.objectType_.name() << " is transient, and cannot be cloned.";
151+
}
152+
153+
// write the product into a TBuffer
154+
TBufferFile buffer(TBuffer::kWrite);
155+
product.objectType_.getClass()->Streamer(object->address(), buffer);
156+
157+
// read back a copy of the product form the TBuffer
158+
buffer.SetReadMode();
159+
buffer.SetBufferOffset(0);
160+
edm::ObjectWithDict clone = product.objectType_.construct();
161+
product.objectType_.getClass()->Streamer(clone.address(), buffer);
162+
163+
// wrap the copy in a GenericProduct, and call the Event::put() method specialisation for GenericProduct
164+
event.put(product.putToken_, std::make_unique<edm::GenericProduct>(clone, product.wrappedType_));
165+
166+
// the cloned object has been moved into the Event; now destroy the temporary object
167+
clone.destruct(true);
168+
} else {
169+
// other types are not supported
170+
throw edm::Exception(edm::errors::LogicError)
171+
<< "Unsupported type " << product.objectType_.name() << ".\nPlease contact a framework developer.";
172+
}
173+
}
174+
}
175+
176+
void GenericCloner::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {
177+
descriptions.setComment(
178+
R"(This EDProducer will clone all the event products declared by its configuration, using their ROOT dictionaries.
179+
180+
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>").
181+
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).
182+
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).
183+
184+
Glob expressions ("?" and "*") are supported in module labels and within the individual fields of branch names, similar to an OutputModule's "keep" statements.
185+
Use "*" to clone all products.
186+
187+
For example, in the case of Alpaka-based modules running on a device, using
188+
189+
eventProducts = cms.untracked.vstring( "module" )
190+
191+
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.
192+
To clone only the host product, the branch can be specified explicitly with
193+
194+
eventProducts = cms.untracked.vstring( "HostProductType_module_*_*" )
195+
196+
.)");
197+
198+
edm::ParameterSetDescription desc;
199+
desc.add<std::vector<std::string>>("eventProducts", {})
200+
->setComment("List of modules or branches whose event products will be cloned.");
201+
desc.addUntracked<bool>("verbose", false)
202+
->setComment("Print the branch names of the products that will be cloned.");
203+
descriptions.addWithDefaultLabel(desc);
204+
}
205+
206+
} // namespace edmtest
207+
208+
#include "FWCore/Framework/interface/MakerMacros.h"
209+
DEFINE_FWK_MODULE(edmtest::GenericCloner);

FWCore/TestModules/test/BuildFile.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
<test name="TestFWCoreModulesEventIDValidator" command="cmsRun ${LOCALTOP}/src/FWCore/TestModules/test/testEventIDValidator_cfg.py"/>
2+
3+
<test name="TestFWCoreModulesGenericCloner" command="cmsRun ${LOCALTOP}/src/FWCore/TestModules/test/testGenericCloner_cfg.py"/>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import FWCore.ParameterSet.Config as cms
2+
3+
process = cms.Process("TEST")
4+
5+
process.load("FWCore.MessageService.MessageLogger_cfi")
6+
process.MessageLogger.cerr.INFO.limit = 10000000
7+
8+
process.options.numberOfThreads = 1
9+
process.options.numberOfStreams = 1
10+
11+
process.source = cms.Source("EmptySource")
12+
process.maxEvents.input = 10
13+
14+
# produce, clone and validate products of type int
15+
process.produceInt = cms.EDProducer("edmtest::GlobalIntProducer",
16+
value = cms.int32(42)
17+
)
18+
19+
process.cloneInt = cms.EDProducer("edmtest::GenericCloner",
20+
eventProducts = cms.vstring("produceInt"),
21+
verbose = cms.untracked.bool(True)
22+
)
23+
24+
process.validateInt = cms.EDAnalyzer("BuiltinIntTestAnalyzer",
25+
moduleLabel = cms.untracked.InputTag("cloneInt"),
26+
valueMustMatch = cms.untracked.int32(42)
27+
)
28+
29+
process.taskInt = cms.Task(process.produceInt, process.cloneInt)
30+
31+
process.pathInt = cms.Path(process.validateInt, process.taskInt)
32+
33+
# produce, clone and validate products of type std::string
34+
process.produceString = cms.EDProducer("edmtest::GlobalStringProducer",
35+
value = cms.string("Hello world")
36+
)
37+
38+
process.cloneString = cms.EDProducer("edmtest::GenericCloner",
39+
eventProducts = cms.vstring("produceString"),
40+
verbose = cms.untracked.bool(True)
41+
)
42+
43+
process.validateString = cms.EDAnalyzer("edmtest::GlobalStringAnalyzer",
44+
source = cms.InputTag("cloneString"),
45+
expected = cms.string("Hello world")
46+
)
47+
48+
process.taskString = cms.Task(process.produceString, process.cloneString)
49+
50+
process.pathString = cms.Path(process.validateString, process.taskString)
51+
52+
# produce, clone and validate products of type edm::EventID
53+
process.eventIds = cms.EDProducer("edmtest::EventIDProducer")
54+
55+
process.cloneIdsByLabel = cms.EDProducer("edmtest::GenericCloner",
56+
eventProducts = cms.vstring("eventIds"),
57+
verbose = cms.untracked.bool(True)
58+
)
59+
60+
process.cloneIdsByBranch = cms.EDProducer("edmtest::GenericCloner",
61+
eventProducts = cms.vstring("*_eventIds__TEST"),
62+
verbose = cms.untracked.bool(True)
63+
)
64+
65+
process.validateIdsByLabel = cms.EDAnalyzer("edmtest::EventIDValidator",
66+
source = cms.untracked.InputTag('cloneIdsByLabel')
67+
)
68+
69+
process.validateIdsByBranch = cms.EDAnalyzer("edmtest::EventIDValidator",
70+
source = cms.untracked.InputTag('cloneIdsByBranch')
71+
)
72+
73+
process.taskIds = cms.Task(process.eventIds, process.cloneIdsByLabel, process.cloneIdsByBranch)
74+
75+
process.pathIds = cms.Path(process.validateIdsByLabel + process.validateIdsByBranch, process.taskIds)

0 commit comments

Comments
 (0)