otsdaq  v2_04_01
ARTDAQBoardReaderTable_table.cc
1 #include "otsdaq-core/ConfigurationInterface/ConfigurationManager.h"
2 #include "otsdaq-core/Macros/TablePluginMacros.h"
3 #include "otsdaq-core/TablePlugins/ARTDAQBoardReaderTable.h"
4 #include "otsdaq-core/TablePlugins/XDAQContextTable.h"
5 
6 #include <stdio.h>
7 #include <sys/stat.h> //for mkdir
8 #include <fstream> // std::fstream
9 #include <iostream>
10 
11 using namespace ots;
12 
13 #define ARTDAQ_FCL_PATH std::string(__ENV__("USER_DATA")) + "/" + "ARTDAQConfigurations/"
14 #define ARTDAQ_FILE_PREAMBLE "boardReader"
15 
16 // helpers
17 #define OUT out << tabStr << commentStr
18 #define PUSHTAB tabStr += "\t"
19 #define POPTAB tabStr.resize(tabStr.size() - 1)
20 #define PUSHCOMMENT commentStr += "# "
21 #define POPCOMMENT commentStr.resize(commentStr.size() - 2)
22 
23 //========================================================================================================================
24 ARTDAQBoardReaderTable::ARTDAQBoardReaderTable(void) : TableBase("ARTDAQBoardReaderTable")
25 {
27  // WARNING: the names used in C++ MUST match the Table INFO //
29 }
30 
31 //========================================================================================================================
32 ARTDAQBoardReaderTable::~ARTDAQBoardReaderTable(void) {}
33 
34 //========================================================================================================================
35 void ARTDAQBoardReaderTable::init(ConfigurationManager* configManager)
36 {
37  // use isFirstAppInContext to only run once per context, for example to avoid
38  // generating files on local disk multiple times.
39  bool isFirstAppInContext = configManager->isOwnerFirstAppInContext();
40 
41  //__COUTV__(isFirstAppInContext);
42  if(!isFirstAppInContext)
43  return;
44 
45  // make directory just in case
46  mkdir((ARTDAQ_FCL_PATH).c_str(), 0755);
47 
48  __COUT__ << "*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*" << __E__;
49  __COUT__ << configManager->__SELF_NODE__ << __E__;
50 
51  const XDAQContextTable* contextConfig =
52  configManager->__GET_CONFIG__(XDAQContextTable);
53 
54  std::vector<const XDAQContextTable::XDAQContext*> readerContexts =
55  contextConfig->getBoardReaderContexts();
56 
57  // for each reader context
58  // do NOT output associated fcl config file
59  // but do check the link is to a DataManager-esque thing
60  for(auto& readerContext : readerContexts)
61  {
62  try
63  {
64  ConfigurationTree readerConfigNode = contextConfig->getSupervisorConfigNode(
65  configManager,
66  readerContext->contextUID_,
67  readerContext->applications_[0].applicationUID_);
68 
69  __COUT__ << "Path for this reader config is " << readerContext->contextUID_
70  << "/" << readerContext->applications_[0].applicationUID_ << "/"
71  << readerConfigNode.getValueAsString() << __E__;
72 
73  __COUT__ << "Checking that this reader supervisor node is DataManager-like."
74  << __E__;
75 
76  readerConfigNode.getNode("LinkToDataBufferTable").getChildren();
77  }
78  catch(const std::runtime_error& e)
79  {
80  __SS__ << "artdaq Board Readers must be instantiated as a Consumer within a "
81  "DataManager table. "
82  << "Error found while checking for LinkToDataBufferTable: " << e.what()
83  << __E__;
84  __COUT_ERR__ << ss.str();
85  __COUT__ << "Path for this reader config is " << readerContext->contextUID_
86  << "/" << readerContext->applications_[0].applicationUID_ << "/X"
87  << __E__;
88  __COUT_ERR__ << "This board reader will likely not get instantiated "
89  "properly! Proceeding anyway with fcl generation."
90  << __E__;
91 
92  // proceed anyway, because it was really annoying to not be able to activate
93  // the table group when the context is being developed also.
94  //__SS_THROW__;
95  }
96 
97  // artdaq Reader is not at Supervisor level like other apps
98  // it is at Consumer level!
99  // outputFHICL(readerConfigNode,
100  // contextConfig);
101  }
102 
103  // handle fcl file generation, wherever the level of this table
104 
105  auto childrenMap = configManager->__SELF_NODE__.getChildren();
106  std::string appUID, buffUID, consumerUID;
107  for(auto& child : childrenMap)
108  {
109  const XDAQContextTable::XDAQContext* thisContext = nullptr;
110  for(auto& readerContext : readerContexts)
111  {
112  ConfigurationTree readerConfigNode = contextConfig->getSupervisorConfigNode(
113  configManager,
114  readerContext->contextUID_,
115  readerContext->applications_[0].applicationUID_);
116  auto dataManagerConfMap =
117  readerConfigNode.getNode("LinkToDataBufferTable").getChildren();
118  for(auto dmc : dataManagerConfMap)
119  {
120  auto dataBufferConfMap =
121  dmc.second.getNode("LinkToDataProcessorTable").getChildren();
122  for(auto dbc : dataBufferConfMap)
123  {
124  auto processorConfUID =
125  dbc.second.getNode("LinkToProcessorUID").getUIDAsString();
126  if(processorConfUID == child.second.getValue())
127  {
128  __COUT__ << "Found match for context UID: "
129  << readerContext->contextUID_ << __E__;
130  thisContext = readerContext;
131  }
132  }
133  }
134  }
135 
136  if(thisContext == nullptr)
137  {
138  __COUT_ERR__ << "Could not find matching context for this table!" << __E__;
139  }
140  else
141  {
142  if(child.second.getNode(TableViewColumnInfo::COL_NAME_STATUS)
143  .getValue<bool>())
144  {
145  outputFHICL(configManager,
146  child.second,
147  contextConfig->getARTDAQAppRank(thisContext->contextUID_),
148  contextConfig->getContextAddress(thisContext->contextUID_),
149  contextConfig->getARTDAQDataPort(configManager,
150  thisContext->contextUID_),
151  contextConfig);
152  }
153  }
154  }
155 }
156 
158 // void ARTDAQBoardReaderTable::getBoardReaderParents(const ConfigurationTree
159 // &readerNode, const ConfigurationTree &contextNode, std::string &applicationUID,
160 // std::string &bufferUID, std::string &consumerUID)
161 //{
162 // //search through all board reader contexts
163 // contextNode.getNode()
164 //}
165 
166 //========================================================================================================================
167 std::string ARTDAQBoardReaderTable::getFHICLFilename(
168  const ConfigurationTree& boardReaderNode)
169 {
170  __COUT__ << "ARTDAQ BoardReader UID: " << boardReaderNode.getValue() << __E__;
171  std::string filename = ARTDAQ_FCL_PATH + ARTDAQ_FILE_PREAMBLE + "-";
172  std::string uid = boardReaderNode.getValue();
173  for(unsigned int i = 0; i < uid.size(); ++i)
174  if((uid[i] >= 'a' && uid[i] <= 'z') || (uid[i] >= 'A' && uid[i] <= 'Z') ||
175  (uid[i] >= '0' && uid[i] <= '9')) // only allow alpha numeric in file name
176  filename += uid[i];
177 
178  filename += ".fcl";
179 
180  __COUT__ << "fcl: " << filename << __E__;
181 
182  return filename;
183 }
184 
185 //========================================================================================================================
186 void ARTDAQBoardReaderTable::outputFHICL(ConfigurationManager* configManager,
187  const ConfigurationTree& boardReaderNode,
188  unsigned int selfRank,
189  std::string selfHost,
190  unsigned int selfPort,
191  const XDAQContextTable* contextConfig)
192 {
193  /*
194  the file will look something like this:
195 
196  daq: {
197  fragment_receiver: {
198  mpi_sync_interval: 50
199 
200  # CommandableFragmentGenerator Table:
201  fragment_ids: []
202  fragment_id: -99 # Please define only one of these
203 
204  sleep_on_stop_us: 0
205 
206  requests_enabled: false # Whether to set up the socket for listening for
207  trigger messages request_mode: "Ignored" # Possible values are: Ignored, Single,
208  Buffer, Window
209 
210  data_buffer_depth_fragments: 1000
211  data_buffer_depth_mb: 1000
212 
213  request_port: 3001
214  request_address: "227.128.12.26" # Multicast request address
215 
216  request_window_offset: 0 # Request message contains tzero. Window will be from
217  tzero - offset to tzero + width request_window_width: 0 stale_request_timeout:
218  "0xFFFFFFFF" # How long to wait before discarding request messages that are outside
219  the available data request_windows_are_unique: true # If request windows are
220  unique, avoids a copy operation, but the same data point cannot be used for two
221  requests. If this is not anticipated, leave set to "true"
222 
223  separate_data_thread: false # MUST be true for triggers to be applied! If
224  triggering is not desired, but a separate readout thread is, set this to true,
225  triggers_enabled to false and trigger_mode to ignored. separate_monitoring_thread:
226  false # Whether a thread should be started which periodically calls checkHWStatus_,
227  a user-defined function which should be used to check hardware status registers and
228  report to MetricMan. poll_hardware_status: false # Whether checkHWStatus_ will be
229  called, either through the thread or at the start of getNext
230  hardware_poll_interval_us: 1000000 # If hardware monitoring thread is enabled,
231  how often should it call checkHWStatus_
232 
233 
234  # Generated Parameters:
235  generator: ToySimulator
236  fragment_type: TOY1
237  fragment_id: 0
238  board_id: 0
239  starting_fragment_id: 0
240  random_seed: 5780
241  sleep_on_stop_us: 500000
242 
243  # Generator-Specific Table:
244 
245  nADCcounts: 40
246 
247  throttle_usecs: 100000
248 
249  distribution_type: 1
250 
251  timestamp_scale_factor: 1
252 
253 
254  destinations: {
255  d2: { transferPluginType: MPI
256  destination_rank: 2
257  max_fragment_size_bytes: 2097152
258  host_map: [
259  {
260  host: "mu2edaq01.fnal.gov"
261  rank: 0
262  },
263  {
264  host: "mu2edaq01.fnal.gov"
265  rank: 1
266  }]
267  }
268  d3: { transferPluginType: MPI
269  destination_rank: 3
270  max_fragment_size_bytes: 2097152
271  host_map: [
272  {
273  host: "mu2edaq01.fnal.gov"
274  rank: 0
275  },
276  {
277  host: "mu2edaq01.fnal.gov"
278  rank: 1
279  }]
280  }
281 
282  }
283  }
284 
285  metrics: {
286  brFile: {
287  metricPluginType: "file"
288  level: 3
289  fileName: "/tmp/boardreader/br_%UID%_metrics.log"
290  uniquify: true
291  }
292  # ganglia: {
293  # metricPluginType: "ganglia"
294  # level: %{ganglia_level}
295  # reporting_interval: 15.0
296  #
297  # configFile: "/etc/ganglia/gmond.conf"
298  # group: "ARTDAQ"
299  # }
300  # msgfac: {
301  # level: %{mf_level}
302  # metricPluginType: "msgFacility"
303  # output_message_application_name: "ARTDAQ Metric"
304  # output_message_severity: 0
305  # }
306  # graphite: {
307  # level: %{graphite_level}
308  # metricPluginType: "graphite"
309  # host: "localhost"
310  # port: 20030
311  # namespace: "artdaq."
312  # }
313  }
314  }
315 
316  */
317 
318  std::string filename = getFHICLFilename(boardReaderNode);
319 
321  // generate xdaq run parameter file
322  std::fstream out;
323 
324  std::string tabStr = "";
325  std::string commentStr = "";
326 
327  out.open(filename, std::fstream::out | std::fstream::trunc);
328  if(out.fail())
329  {
330  __SS__ << "Failed to open ARTDAQ Builder fcl file: " << filename << __E__;
331  __SS_THROW__;
332  }
333 
334  //--------------------------------------
335  // header
336  OUT << "###########################################################" << __E__;
337  OUT << "#" << __E__;
338  OUT << "# artdaq reader fcl configuration file produced by otsdaq." << __E__;
339  OUT << "# Creation timestamp: " << StringMacros::getTimestampString() << __E__;
340  OUT << "# Original filename: " << filename << __E__;
341  OUT << "# otsdaq-ARTDAQ Reader UID: " << boardReaderNode.getValue() << __E__;
342  OUT << "#" << __E__;
343  OUT << "###########################################################" << __E__;
344  OUT << "\n\n";
345 
346  // no primary link to table tree for reader node!
347  try
348  {
349  if(boardReaderNode.isDisconnected())
350  {
351  // create empty fcl
352  OUT << "{}\n\n";
353  out.close();
354  return;
355  }
356  }
357  catch(const std::runtime_error&)
358  {
359  __COUT__ << "Ignoring error, assume this a valid UID node." << __E__;
360  // error is expected here for UIDs.. so just ignore
361  // this check is valuable if source node is a unique-Link node, rather than UID
362  }
363 
364  //--------------------------------------
365  // handle daq
366  OUT << "daq: {\n";
367 
368  // fragment_receiver
369  PUSHTAB;
370  OUT << "fragment_receiver: {\n";
371 
372  PUSHTAB;
373  {
374  // plugin type and fragment data-type
375  OUT << "generator"
376  << ": " << boardReaderNode.getNode("daqGeneratorPluginType").getValue()
377  << ("\t #daq generator plug-in type") << "\n";
378  OUT << "fragment_type"
379  << ": " << boardReaderNode.getNode("daqGeneratorFragmentType").getValue()
380  << ("\t #generator data fragment type") << "\n\n";
381 
382  // shared and unique parameters
383  auto parametersLink = boardReaderNode.getNode("daqParametersLink");
384  if(!parametersLink.isDisconnected())
385  {
386  auto parameters = parametersLink.getChildren();
387  for(auto& parameter : parameters)
388  {
389  if(!parameter.second.getNode(TableViewColumnInfo::COL_NAME_STATUS)
390  .getValue<bool>())
391  PUSHCOMMENT;
392 
393  // __COUT__ <<
394  // parameter.second.getNode("daqParameterKey").getValue() <<
395  // ": " <<
396  // parameter.second.getNode("daqParameterValue").getValue()
397  // <<
398  // "\n";
399 
400  auto comment =
401  parameter.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT);
402  OUT << parameter.second.getNode("daqParameterKey").getValue() << ": "
403  << parameter.second.getNode("daqParameterValue").getValue()
404  << (comment.isDefaultValue() ? "" : ("\t # " + comment.getValue()))
405  << "\n";
406 
407  if(!parameter.second.getNode(TableViewColumnInfo::COL_NAME_STATUS)
408  .getValue<bool>())
409  POPCOMMENT;
410  }
411  }
412  OUT << "\n"; // end daq board reader parameters
413  }
414  // { //unique parameters
415  // auto parametersLink = boardReaderNode.getNode("daqUniqueParametersLink");
416  // if(!parametersLink.isDisconnected())
417  // {
418  //
419  // auto parameters = parametersLink.getChildren();
420  // for(auto &parameter:parameters)
421  // {
422  // if(!parameter.second.getNode(TableViewColumnInfo::COL_NAME_STATUS).getValue<bool>())
423  // PUSHCOMMENT;
424  //
425  // auto comment =
426  //parameter.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT); OUT <<
427  //parameter.second.getNode("daqParameterKey").getValue() <<
428  // ": " <<
429  // parameter.second.getNode("daqParameterValue").getValue()
430  // <<
431  // (comment.isDefaultValue()?"":("\t # " + comment.getValue()))
432  //<<
433  // "\n";
434  //
435  // if(!parameter.second.getNode(TableViewColumnInfo::COL_NAME_STATUS).getValue<bool>())
436  // POPCOMMENT;
437  // }
438  // }
439  // OUT << "\n"; //end shared daq board reader parameters
440  // }
441 
442  OUT << "destinations: {\n";
443 
444  PUSHTAB;
445  auto destinationsGroup = boardReaderNode.getNode("daqDestinationsLink");
446  if(!destinationsGroup.isDisconnected())
447  {
448  try
449  {
450  auto destinations = destinationsGroup.getChildren();
451  for(auto& destination : destinations)
452  {
453  auto destinationContextUID =
454  destination.second.getNode("destinationARTDAQContextLink")
455  .getValueAsString();
456 
457  unsigned int destinationRank = -1;
458  try
459  {
460  destinationRank =
461  contextConfig->getARTDAQAppRank(destinationContextUID);
462  }
463  catch(const std::runtime_error& e)
464  {
465  __MCOUT_WARN__(
466  "Are the DAQ destinations valid? "
467  << "Perhaps a Context has been turned off? "
468  << "Skipping destination due to an error looking for Board "
469  << "Reader DAQ source context '" << destinationContextUID
470  << "' for UID '" << boardReaderNode.getValue()
471  << "': " << e.what() << __E__);
472  continue;
473  }
474 
475  std::string host =
476  contextConfig->getContextAddress(destinationContextUID);
477  unsigned int port = contextConfig->getARTDAQDataPort(
478  configManager, destinationContextUID);
479 
480  // open destination object
481  OUT << destination.second.getNode("destinationKey").getValue() << ": {\n";
482  PUSHTAB;
483 
484  OUT << "transferPluginType: "
485  << destination.second.getNode("transferPluginType").getValue()
486  << __E__;
487 
488  OUT << "destination_rank: " << destinationRank << __E__;
489 
490  try
491  {
492  auto mfsb = destination.second
493  .getNode("ARTDAQGlobalTableLink/maxFragmentSizeBytes")
494  .getValue<unsigned long long>();
495  OUT << "max_fragment_size_bytes: " << mfsb << __E__;
496  OUT << "max_fragment_size_words: " << (mfsb / 8) << __E__;
497  }
498  catch(...)
499  {
500  __SS__ << "The field ARTDAQGlobalTableLink/maxFragmentSizeBytes "
501  "could not be accessed. Make sure the link is valid."
502  << __E__;
503  __SS_THROW__;
504  }
505 
506  OUT << "host_map: [\n";
507  PUSHTAB;
508  OUT << "{\n";
509  PUSHTAB;
510  OUT << "rank: " << destinationRank << __E__;
511  OUT << "host: \"" << host << "\"" << __E__;
512  OUT << "portOffset: " << std::to_string(port) << __E__;
513  POPTAB;
514  OUT << "},\n";
515  OUT << "{\n";
516  PUSHTAB;
517  OUT << "rank: " << selfRank << __E__;
518  OUT << "host: \"" << selfHost << "\"" << __E__;
519  OUT << "portOffset: " << std::to_string(selfPort) << __E__;
520  POPTAB;
521  OUT << "}" << __E__;
522  POPTAB;
523  OUT << "]" << __E__; // close host_map
524 
525  POPTAB;
526  OUT << "}" << __E__; // close destination object
527  }
528  }
529  catch(const std::runtime_error& e)
530  {
531  __SS__ << "Are the DAQ destinations valid? Error occurred looking for Board "
532  "Reader DAQ sources for UID '"
533  << boardReaderNode.getValue() << "': " << e.what() << __E__;
534  __SS_THROW__;
535  }
536  }
537  POPTAB;
538  OUT << "}\n\n"; // end destinations
539 
540  POPTAB;
541  OUT << "}\n\n"; // end fragment_receiver
542 
543  OUT << "metrics: {\n";
544 
545  PUSHTAB;
546  auto metricsGroup = boardReaderNode.getNode("daqMetricsLink");
547  if(!metricsGroup.isDisconnected())
548  {
549  auto metrics = metricsGroup.getChildren();
550 
551  for(auto& metric : metrics)
552  {
553  if(!metric.second.getNode(TableViewColumnInfo::COL_NAME_STATUS)
554  .getValue<bool>())
555  PUSHCOMMENT;
556 
557  OUT << metric.second.getNode("metricKey").getValue() << ": {\n";
558  PUSHTAB;
559 
560  OUT << "metricPluginType: "
561  << metric.second.getNode("metricPluginType").getValue() << "\n";
562  OUT << "level: " << metric.second.getNode("metricLevel").getValue() << "\n";
563 
564  auto metricParametersGroup = metric.second.getNode("metricParametersLink");
565  if(!metricParametersGroup.isDisconnected())
566  {
567  auto metricParameters = metricParametersGroup.getChildren();
568  for(auto& metricParameter : metricParameters)
569  {
570  if(!metricParameter.second
571  .getNode(TableViewColumnInfo::COL_NAME_STATUS)
572  .getValue<bool>())
573  PUSHCOMMENT;
574 
575  OUT << metricParameter.second.getNode("metricParameterKey").getValue()
576  << ": "
577  << metricParameter.second.getNode("metricParameterValue")
578  .getValue()
579  << "\n";
580 
581  if(!metricParameter.second
582  .getNode(TableViewColumnInfo::COL_NAME_STATUS)
583  .getValue<bool>())
584  POPCOMMENT;
585  }
586  }
587  POPTAB;
588  OUT << "}\n\n"; // end metric
589 
590  if(!metric.second.getNode(TableViewColumnInfo::COL_NAME_STATUS)
591  .getValue<bool>())
592  POPCOMMENT;
593  }
594  }
595  POPTAB;
596  OUT << "}\n\n"; // end metrics
597 
598  POPTAB;
599  OUT << "}\n\n"; // end daq
600 
601  out.close();
602 }
603 
604 DEFINE_OTS_TABLE(ARTDAQBoardReaderTable)