All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SaveConfigurationIntoTFile_module.cc
Go to the documentation of this file.
1 /**
2  * @file SaveConfigurationIntoTFile_module.cc
3  * @brief Writes the configuration of this job into `TFileService` file.
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date August 3, 2018
6  *
7  *
8  *
9  *
10  *
11  */
12 
13 // framework libraries
14 #include "art_root_io/TFileService.h"
15 #include "art/Framework/Principal/Event.h"
16 #include "art/Framework/Core/EDAnalyzer.h"
17 #include "art/Framework/Core/ModuleMacros.h"
18 #include "canvas/Persistency/Provenance/ProcessHistory.h"
19 
20 #include "messagefacility/MessageLogger/MessageLogger.h"
21 #include "fhiclcpp/types/Atom.h"
22 #include "fhiclcpp/ParameterSetRegistry.h"
23 
24 // ROOT libraries
25 #include "TNamed.h"
26 
27 // C/C++ standard libraries
28 #include <set>
29 #include <string>
30 
31 
32 //------------------------------------------------------------------------------
33 /**
34  * @brief Writes the _art_ configuration into the `TFileService` file.
35  *
36  * A `TStringObj` object is written into the directory assigned by `TFileService`
37  * to this analyzer for every selected process configuration. The `TObjString`
38  * will have the same ROOT name as the process itself. In addition, the
39  * following are saved:
40  * * `current_process` (`TNamed`): the name of this process
41  *
42  * The selection of processes is controlled by the module configuration.
43  * The current process is always selected.
44  *
45  * This module takes a simplistic approach, and prints only the first occurrence
46  * of each process name. When saving configuration of processes from the input
47  * files, and they contain different configurations for the same process name,
48  * only one of them (the first one encountered) will be saved.
49  *
50  * @todo Currently the author does not know how to extract the configuration of the current process, which is therefore a dummy
51  *
52  *
53  * Configuration parameters
54  * =========================
55  *
56  * * **includePreviousProcesses** (boolean, default: `false`): saves also the
57  * configuration of all the processes the input file has seen; this has
58  * limitations when there are multiple input files (see above).
59  *
60  */
61 class SaveConfigurationIntoTFile: public art::EDAnalyzer {
62 
63  public:
64 
65  struct Config {
66 
67  using Name = fhicl::Name;
68  using Comment = fhicl::Comment;
69 
70  fhicl::Atom<bool> includePreviousProcesses{
71  Name("includePreviousProcesses"),
72  Comment("Also save configuration of all previous processes in the input file"),
73  false
74  };
75 
76  }; // struct Config
77 
78  using Parameters = art::EDAnalyzer::Table<Config>;
79 
80  /// Standard _art_ analyzer moduel constructor.
82 
83  /// Writes the configuration information of the current process.
84  virtual void beginJob() override;
85 
86  /// Writes the configuration information of the input processes.
87  virtual void analyze(art::Event const& event) override;
88 
89 
90  private:
91 
92  /// Whether to save the configuration of the input file.
94 
95  // we might go by configuration IDs to store all possible configurations
96  std::set<std::string> fProcessedProcesses; ///< Process names already saved.
97 
98  /// Writes the specified configuration into a ROOT directory.
100  (std::string const& procName, fhicl::ParameterSet const& config);
101 
102  /// Returns whether we have already saved configuration for `procName`.
103  bool isProcessConfigurationSaved(std::string const& procName) const
104  { return fProcessedProcesses.count(procName) > 0; }
105 
106 
107  /// Extracts the configuration of the specified process from `event`.
108  static fhicl::ParameterSet processConfiguration
109  (art::Event const& event, std::string const& procName);
110 
111  /// Saves a string via `TFileService` (as `TNamed`).
112  static void storeString(std::string const& key, std::string const& value);
113 
114  /// Returns the configuration of the current _art_ process.
115  static fhicl::ParameterSet currentProcessConfiguration
116  (std::string const& procName);
117 
118  /// Returns whether the specified one is a process configuration.
119  static bool isProcessConfiguration(fhicl::ParameterSet const& pset);
120 
121  /// Returns whether `pset` is the configuration of `procName` process.
122  static bool isProcessConfiguration
123  (fhicl::ParameterSet const& pset, std::string const& procName);
124 
125 }; // class SaveConfigurationIntoTFile
126 
127 
128 DEFINE_ART_MODULE(SaveConfigurationIntoTFile)
129 
130 
131 //------------------------------------------------------------------------------
132 namespace {
133 
134  bool has_table(fhicl::ParameterSet const& pset, std::string const& name)
135  { return pset.has_key(name) && pset.is_key_to_table(name); }
136 
137  bool has_sequence(fhicl::ParameterSet const& pset, std::string const& name)
138  { return pset.has_key(name) && pset.is_key_to_sequence(name); }
139 
140  bool has_atom(fhicl::ParameterSet const& pset, std::string const& name)
141  { return pset.has_key(name) && pset.is_key_to_atom(name); }
142 
143 
144 } // local namespace
145 
146 //------------------------------------------------------------------------------
148  : art::EDAnalyzer(config)
149  , fIncludePreviousProcesses(config().includePreviousProcesses())
150  {}
151 
152 
153 //------------------------------------------------------------------------------
155 
156  std::string const procName = processName();
157  //
158  // current process
159  //
160  fhicl::ParameterSet pset = currentProcessConfiguration(procName);
161 
162  MF_LOG_DEBUG("SaveConfigurationIntoTFile")
163  << "This process: '" << procName << "':\n"
164  << std::string(80, '=') << '\n'
165  << pset.to_indented_string()
166  << '\n' << std::string(80, '=');
167  saveProcessConfiguration(procName, pset);
168  storeString("current_process", procName);
169 
170  mf::LogInfo("SaveConfigurationIntoTFile")
171  << "Configuration of current process ('" << procName
172  << "') stored via TFileService.";
173 
174 } // SaveConfigurationIntoTFile::beginJob()
175 
176 
177 //------------------------------------------------------------------------------
178 void SaveConfigurationIntoTFile::analyze(art::Event const& event) {
179 
180  //
181  // previous processes for input file
182  //
184  for (auto const& procConfigInfo: event.processHistory()) {
185  std::string const& procName = procConfigInfo.processName();
186  if (isProcessConfigurationSaved(procName)) continue;
187 
189  (procName, processConfiguration(event, procName));
190  mf::LogInfo("SaveConfigurationIntoTFile")
191  << "Configuration of process '" << procName
192  << "' from input file stored via TFileService.";
193 
194  } // for process config
195  } // if include history
196 
197 } // SaveConfigurationIntoTFile::analyze()
198 
199 
200 //------------------------------------------------------------------------------
202  std::string const& procName, fhicl::ParameterSet const& config
203 ) {
204 
205  if (fProcessedProcesses.count(procName) > 0) return; // already saved
206 
207  storeString(procName, config.to_indented_string());
208 
209  fProcessedProcesses.insert(procName);
210 
211 } // SaveConfigurationIntoTFile::saveProcessConfiguration()
212 
213 
214 //------------------------------------------------------------------------------
216  (art::Event const& event, std::string const& procName)
217 {
218  fhicl::ParameterSet pset;
219  event.getProcessParameterSet(procName, pset);
220  return pset;
221 } // SaveConfigurationIntoTFile::processConfiguration()
222 
223 
224 //------------------------------------------------------------------------------
226  (std::string const& key, std::string const& value)
227 {
228  /*
229  * The following `TFileService::makeAndRegister()` call does two things:
230  * 1) constructs a `TNamed` object with the _default constructor_
231  * 2) assigns it the specified name and title
232  * 3) adds the new object to the ROOT directory we are given.
233  *
234  * The first two operations might be combined by calling the proper
235  * constructor, but the generic `makeAndRegister()` will always explicitly
236  * perform action (2) anyway, while `make()` will not perform action (3)
237  * at all, which appears to be required for successfully writing `TNamed`.
238  *
239  */
240  auto const& fs = *(art::ServiceHandle<art::TFileService>());
241  fs.makeAndRegister<TNamed>(key.c_str(), value.c_str());
242 
243 } // SaveConfigurationIntoTFile::storeString()
244 
245 
246 //------------------------------------------------------------------------------
248  (std::string const& procName)
249 {
250 
251  /*
252  * The strategy is to parse the global FHiCL registry (yes, there is such a
253  * thing), looking for the table which contains 'process_name'.
254  * This heuristic is not fool proof, since there might be other tables with
255  * the same atom. So:
256  * TODO find a way to ask art which is the root parameter set
257  * The registry contains all root-level tables, including the main
258  * configuration and other tables that are used to resolve references
259  * (internally, references are not resolved to save space).
260  * It turns out that when a fhicl::ParameterSet is created, all references
261  * are resolved, so by the time we get the parameter sets we don't have to
262  * worry to manually resolve the references. Which would not be fun.
263  *
264  */
265 
266  static std::string const rootKey { "process_name" };
267 
268  fhicl::ParameterSet const* rootConfig = nullptr;
269  for (auto const& idAndSet: fhicl::ParameterSetRegistry::get()) {
270  auto const& pset = idAndSet.second;
271  if (isProcessConfiguration(pset, procName)) {
272  if (rootConfig) {
273  throw art::Exception(art::errors::LogicError)
274  << "Found two candidate process parameter sets: with " << rootKey
275  << " '" << rootConfig->get<std::string>(rootKey) << "' and '"
276  << pset.get<std::string>(rootKey) << "'!\n";
277  }
278  rootConfig = &pset;
279  } // if found
280  }
281  if (!rootConfig) {
282  throw art::Exception(art::errors::LogicError)
283  << "No parameter set with 'process_name' atom found!\n";
284  }
285 
286  return *rootConfig;
287 } // SaveConfigurationIntoTFile::currentProcessConfiguration()
288 
289 
290 //------------------------------------------------------------------------------
292  (fhicl::ParameterSet const& pset)
293 {
294  /*
295  * We are forced to parse the parameter set heuristically.
296  * If a configuration is something like:
297  *
298  * full_configuration: { ... }
299  *
300  * @table::full_configuration
301  *
302  * the heuristic will match both the actual root configuration *and*
303  * `full_configuration` parameter set (hint: put `full_configuration` within a
304  * prolog). This may cause trouble downstream.
305  * Barred that case, the more detailed the logic here is, the less likely
306  * a false positive is to happen.
307  *
308  */
309  if (!::has_table (pset, "services" )) return false;
310  if (!::has_table (pset, "physics" )) return false;
311  if (!::has_table (pset, "source" )) return false;
312  if (!::has_atom (pset, "process_name")) return false;
313  if (!::has_sequence(pset, "physics.trigger_paths")) return false;
314  if (!::has_sequence(pset, "physics.end_paths")) return false;
315 
316  return true;
317 } // SaveConfigurationIntoTFile::isProcessConfiguration()
318 
319 
320 //------------------------------------------------------------------------------
322  (fhicl::ParameterSet const& pset, std::string const& procName)
323 {
324  if (!isProcessConfiguration(pset)) return false;
325  return pset.get<std::string>("process_name") == procName;
326 } // SaveConfigurationIntoTFile::isProcessConfiguration(std::string)
327 
328 
329 //------------------------------------------------------------------------------
330 
art::EDAnalyzer::Table< Config > Parameters
std::set< std::string > fProcessedProcesses
Process names already saved.
bool isProcessConfigurationSaved(std::string const &procName) const
Returns whether we have already saved configuration for procName.
static fhicl::ParameterSet currentProcessConfiguration(std::string const &procName)
Returns the configuration of the current art process.
virtual void beginJob() override
Writes the configuration information of the current process.
static void storeString(std::string const &key, std::string const &value)
Saves a string via TFileService (as TNamed).
void saveProcessConfiguration(std::string const &procName, fhicl::ParameterSet const &config)
Writes the specified configuration into a ROOT directory.
BEGIN_PROLOG vertical distance to the surface Name
SaveConfigurationIntoTFile(Parameters const &config)
Standard art analyzer moduel constructor.
static bool isProcessConfiguration(fhicl::ParameterSet const &pset)
Returns whether the specified one is a process configuration.
virtual void analyze(art::Event const &event) override
Writes the configuration information of the input processes.
then echo fcl name
Writes the art configuration into the TFileService file.
temporary value
static fhicl::ParameterSet processConfiguration(art::Event const &event, std::string const &procName)
Extracts the configuration of the specified process from event.
bool fIncludePreviousProcesses
Whether to save the configuration of the input file.