tdaq-develop-2025-02-12
ConsoleSupervisor.cc
1 #include "otsdaq-utilities/Console/ConsoleSupervisor.h"
2 #include <xdaq/NamespaceURI.h>
3 #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
4 #include "otsdaq/Macros/CoutMacros.h"
5 #include "otsdaq/MessageFacility/MessageFacility.h"
6 #include "otsdaq/NetworkUtilities/ReceiverSocket.h"
7 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
8 
9 #include <dirent.h> //for DIR
10 #include <sys/stat.h> //for mkdir
11 #include <fstream>
12 #include <iostream>
13 #include <string>
14 #include <thread> //for std::thread
15 
16 using namespace ots;
17 
22 XDAQ_INSTANTIATOR_IMPL(ConsoleSupervisor)
23 
24 #define USER_CONSOLE_PREF_PATH \
25  std::string(__ENV__("SERVICE_DATA_PATH")) + "/ConsolePreferences/"
26 #define USER_CONSOLE_SNAPSHOT_PATH \
27  std::string(__ENV__("SERVICE_DATA_PATH")) + "/ConsoleSnapshots/"
28 #define USERS_PREFERENCES_FILETYPE "pref"
29 #define CUSTOM_COUNT_LIST_FILENAME std::string("CustomCountList.dat")
30 
31 #define QUIET_CFG_FILE \
32  std::string(__ENV__("USER_DATA")) + \
33  "/MessageFacilityConfigurations/" \
34  "QuietForwarder.cfg"
35 
36 #define CONSOLE_SPECIAL_ERROR \
37  std::string("|30-Aug-2019 15:30:17 CDT|0|||Error|Console||-1||ConsoleSupervisor|") + \
38  std::string(__FILE__) + std::string("|") + std::to_string(__LINE__) + \
39  std::string("|")
40 #define CONSOLE_SPECIAL_WARNING \
41  std::string( \
42  "|30-Aug-2019 15:30:17 CDT|0|||Warning|Console||-1||ConsoleSupervisor|") + \
43  std::string(__FILE__) + std::string("|") + std::to_string(__LINE__) + \
44  std::string("|")
45 
46 #undef __MF_SUBJECT__
47 #define __MF_SUBJECT__ "Console"
48 
49 #define CONSOLE_MISSED_NEEDLE "Console missed * packet(s)"
50 
52 const std::set<std::string> ConsoleSupervisor::CUSTOM_TRIGGER_ACTIONS({"Count Only",
53  "System Message",
54  "Halt",
55  "Stop",
56  "Pause",
57  "Soft Error",
58  "Hard Error"});
59 
60 const std::string ConsoleSupervisor::ConsoleMessageStruct::LABEL_TRACE = "TRACE";
61 const std::string ConsoleSupervisor::ConsoleMessageStruct::LABEL_TRACE_PLUS = "TRACE+";
63  std::string /* fieldNames */>
64  ConsoleSupervisor::ConsoleMessageStruct::fieldNames({
65  {FieldType::TIMESTAMP, "Timestamp"},
66  {FieldType::SEQID, "SequenceID"},
67  {FieldType::LEVEL, "Level"},
68  {FieldType::LABEL, "Label"},
69  {FieldType::SOURCEID, "SourceID"},
70  {FieldType::HOSTNAME, "Hostname"},
71  {FieldType::SOURCE, "Source"},
72  {FieldType::FILE, "File"},
73  {FieldType::LINE, "Line"},
74  {FieldType::MSG, "Msg"},
75  });
76 
77 //==============================================================================
78 ConsoleSupervisor::ConsoleSupervisor(xdaq::ApplicationStub* stub)
79  : CoreSupervisorBase(stub)
80  , messageCount_(0)
81  , maxMessageCount_(100000)
82  , maxClientMessageRequest_(500)
83 {
84  __SUP_COUT__ << "Constructor started." << __E__;
85 
86  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
87 
88  // attempt to make directory structure (just in case)
89  mkdir(((std::string)USER_CONSOLE_PREF_PATH).c_str(), 0755);
90  mkdir(((std::string)USER_CONSOLE_SNAPSHOT_PATH).c_str(), 0755);
91 
92  xoap::bind(
93  this, &ConsoleSupervisor::resetConsoleCounts, "ResetConsoleCounts", XDAQ_NS_URI);
94 
95  init();
96 
97  //test custom count list and always force the first one to be the "Console missed" !
98  //Note: to avoid recursive triggers, Label='Console' can not trigger, so this 1st one is the only Console source trigger!
99  // Custom Trigger Philosophy
100  // - only one global custom count priority list (too complicated to manage by user, would have to timeout logins, etc..)
101  // - the admin users can modify priority of items in prority list
102  // - actions can infer others ('Count' is always inferred, in addition 'System Message' is inferred for FSM actions)
103  // - the admin users can modify actions
104  // -- including to the 1st Console-missing-message custom count
105  loadCustomCountList();
106  if(priorityCustomTriggerList_.size() ==
107  0) //then create default starting list example for user
108  {
109  addCustomTriggeredAction(CONSOLE_MISSED_NEEDLE, "System Message");
110  // addCustomTriggeredAction("runtime*4",
111  // "Halt");
112  // addCustomTriggeredAction("runtime",
113  // "System Message");
114  } //end test and force custom count list
115 
116  __SUP_COUT__ << "Constructor complete." << __E__;
117 
118 } // end constructor()
119 
120 //==============================================================================
121 ConsoleSupervisor::~ConsoleSupervisor(void) { destroy(); }
122 //==============================================================================
123 void ConsoleSupervisor::init(void)
124 {
125  // start mf msg listener
126  std::thread(
127  [](ConsoleSupervisor* cs) {
128  ConsoleSupervisor::messageFacilityReceiverWorkLoop(cs);
129  },
130  this)
131  .detach();
132 } // end init()
133 
134 //==============================================================================
135 void ConsoleSupervisor::destroy(void)
136 {
137  // called by destructor
138 } // end destroy()
139 
140 //==============================================================================
141 xoap::MessageReference ConsoleSupervisor::resetConsoleCounts(
142  xoap::MessageReference /*message*/)
143 {
144  __COUT_INFO__ << "Resetting Console Error/Warn/Info counts and preparing for new "
145  "first messages."
146  << __E__;
147 
148  // lockout the messages array for the remainder of the scope
149  // this guarantees the reading thread can safely access the messages
150  std::lock_guard<std::mutex> lock(messageMutex_);
151  errorCount_ = 0;
152  firstErrorMessageTime_ = 0;
153  lastErrorMessageTime_ = 0;
154  warnCount_ = 0;
155  firstWarnMessageTime_ = 0;
156  lastWarnMessageTime_ = 0;
157  infoCount_ = 0;
158  firstInfoMessageTime_ = 0;
159  lastInfoMessageTime_ = 0;
160 
161  return SOAPUtilities::makeSOAPMessageReference("Done");
162 } // end resetConsoleCounts()
163 
164 //==============================================================================
168 void ConsoleSupervisor::messageFacilityReceiverWorkLoop(ConsoleSupervisor* cs)
169 try
170 {
171  __COUT__ << "Starting workloop based on config file: " << QUIET_CFG_FILE << __E__;
172 
173  std::string configFile = QUIET_CFG_FILE;
174  FILE* fp = fopen(configFile.c_str(), "r");
175  if(!fp)
176  {
177  __SS__ << "File with port info could not be loaded: " << QUIET_CFG_FILE << __E__;
178  __COUT__ << "\n" << ss.str();
179  __SS_THROW__;
180  }
181  char tmp[100];
182  fgets(tmp, 100, fp); // receive port (ignore)
183  fgets(tmp, 100, fp); // destination port *** used here ***
184  int myport;
185  sscanf(tmp, "%*s %d", &myport);
186 
187  fgets(tmp, 100, fp); // destination ip *** used here ***
188  char myip[100];
189  sscanf(tmp, "%*s %s", myip);
190  fclose(fp);
191 
192  ReceiverSocket rsock(myip, myport); // Take Port from Configuration
193  try
194  {
195  rsock.initialize(0x1400000 /*socketReceiveBufferSize*/);
196  }
197  catch(...)
198  {
199  // lockout the messages array for the remainder of the scope
200  // this guarantees the reading thread can safely access the messages
201  std::lock_guard<std::mutex> lock(cs->messageMutex_);
202 
203  // NOTE: if we do not want this to be fatal, do not throw here, just print out
204 
205  if(1) // generate special message and throw for failed socket
206  {
207  __SS__ << "FATAL Console error. Could not initialize socket on port "
208  << myport
209  << ". Perhaps the port is already in use? Check for multiple stale "
210  "instances of otsdaq processes, or notify admins."
211  << " Multiple instances of otsdaq on the same node should be "
212  "possible, but port numbers must be unique."
213  << __E__;
214  __SS_THROW__;
215  }
216 
217  // generate special message to indicate failed socket
218  __SS__ << "FATAL Console error. Could not initialize socket on port " << myport
219  << ". Perhaps it is already in use? Exiting Console receive loop."
220  << __E__;
221  __COUT__ << ss.str();
222 
223  cs->messages_.emplace_back(CONSOLE_SPECIAL_ERROR + ss.str(),
224  cs->messageCount_++,
225  cs->priorityCustomTriggerList_);
226  //force time to now for auto-generated message
227  cs->messages_.back().setTime(time(0));
228  if(cs->messages_.size() > cs->maxMessageCount_)
229  {
230  cs->messages_.erase(cs->messages_.begin());
231  }
232 
233  return;
234  }
235 
236  std::string buffer;
237  int i = 0;
238  int heartbeatCount = 0;
239  int selfGeneratedMessageCount = 0;
240 
241  std::map<unsigned int, unsigned int> sourceLastSequenceID; // map from sourceID to
242  // lastSequenceID to
243  // identify missed messages
244  long long newSourceId;
245  uint32_t newSequenceId;
246  unsigned int c;
247 
248  // force a starting message
249  __COUT__ << "DEBUG messages look like this." << __E__;
250 
251  while(1)
252  {
253  // if receive succeeds display message
254 
255  //__COUTV__(i);
256 
257  if(rsock.receive(
258  buffer, 1 /*timeoutSeconds*/, 0 /*timeoutUSeconds*/, false /*verbose*/) !=
259  -1)
260  {
261  // use 1-byte "ping" to keep socket alive
262  if(buffer.size() == 1)
263  {
264  // std::cout << "Ping!" << __E__;
265  continue;
266  }
267 
268  if(i != 200)
269  {
270  __COUT__ << "Console has first message." << __E__;
271  i = 200; // mark so things are good for all time. (this indicates things
272  // are configured to be sent here)
273 
274  __COUT_INFO__ << "INFO messages look like this." << __E__;
275  __COUT_WARN__ << "WARNING messages look like this." << __E__;
276  __COUT_ERR__ << "ERROR messages look like this." << __E__;
277 
278  // //to debug special packets
279  // __SS__ << "???";
280  // cs->messages_[cs->writePointer_].set(CONSOLE_SPECIAL_ERROR
281  //+ ss.str(),
282  // cs->messageCount_++);
283  //
284  // if(++cs->writePointer_ == cs->messages_.size()) //handle
285  // wrap-around cs->writePointer_ = 0;
286  }
287 
288  if(selfGeneratedMessageCount)
289  --selfGeneratedMessageCount; // decrement internal message count
290  else // reset heartbeat if external messages are coming through
291  heartbeatCount = 0;
292 
293  //__COUT__ << buffer << __E__;
294 
295  // lockout the messages array for the remainder of the scope
296  // this guarantees the reading thread can safely access the messages
297  std::lock_guard<std::mutex> lock(cs->messageMutex_);
298 
299  // handle message stacking in packet
300  c = 0;
301  while(c < buffer.size())
302  {
303  // std::cout << "CONSOLE " << c << " sz=" << buffer.size() << " len=" <<
304  // strlen(&(buffer.c_str()[c])) << __E__;
305  cs->messages_.emplace_back(&(buffer.c_str()[c]),
306  cs->messageCount_++,
307  cs->priorityCustomTriggerList_);
308  if(cs->messages_.back().hasCustomTriggerMatchAction())
309  cs->customTriggerActionQueue_.push(
310  cs->messages_.back().getCustomTriggerMatch());
311 
312  //update system status
313  if(cs->messages_.back().getLevel() == "Error")
314  {
315  if(cs->errorCount_ == 0)
316  {
317  cs->firstErrorMessageTime_ = time(0);
318  cs->firstErrorMessage_ = cs->messages_.back().getMsg();
319  }
320 
321  cs->lastErrorMessageTime_ = time(0);
322  ++cs->errorCount_;
323  cs->lastErrorMessage_ = cs->messages_.back().getMsg();
324  }
325  else if(cs->messages_.back().getLevel() == "Warning")
326  {
327  if(cs->warnCount_ == 0)
328  {
329  cs->firstWarnMessageTime_ = time(0);
330  cs->firstWarnMessage_ = cs->messages_.back().getMsg();
331  }
332  cs->lastWarnMessageTime_ = time(0);
333  ++cs->warnCount_;
334  cs->lastWarnMessage_ = cs->messages_.back().getMsg();
335  }
336  else if(cs->messages_.back().getLevel() == "Info")
337  {
338  if(cs->infoCount_ == 0)
339  {
340  cs->firstInfoMessageTime_ = time(0);
341  cs->firstInfoMessage_ = cs->messages_.back().getMsg();
342  }
343  cs->lastInfoMessageTime_ = time(0);
344  ++cs->infoCount_;
345  cs->lastInfoMessage_ = cs->messages_.back().getMsg();
346  }
347 
348  // check if sequence ID is out of order
349  newSourceId = cs->messages_.back().getSourceIDAsNumber();
350  newSequenceId = cs->messages_.back().getSequenceIDAsNumber();
351 
352  // std::cout << rsock.getLastIncomingIPAddress() << ":" <<
353  // rsock.getLastIncomingPort() << ":newSourceId: " << newSourceId <<
354  // ":" << newSequenceId << __E__;
355 
356  if( // newSequenceId%1000 == 0 || //for debugging missed!
357  (newSourceId != -1 &&
358  sourceLastSequenceID.find(newSourceId) !=
359  sourceLastSequenceID
360  .end() && // ensure not first packet received
361  ((newSequenceId == 0 && sourceLastSequenceID[newSourceId] !=
362  (uint32_t)-1) || // wrap around case
363  newSequenceId !=
364  sourceLastSequenceID[newSourceId] + 1)) // normal sequence case
365  )
366  {
367  // missed some messages!
368  std::stringstream missedSs;
369  missedSs << "Console missed "
370  << (newSequenceId - 1) -
371  (sourceLastSequenceID[newSourceId] + 1) + 1
372  << " packet(s) from " << cs->messages_.back().getSource()
373  << "!" << __E__;
374  __SS__ << missedSs.str();
375  std::cout << ss.str();
376 
377  // generate special message to indicate missed packets
378  cs->messages_.emplace_back(CONSOLE_SPECIAL_WARNING + missedSs.str(),
379  cs->messageCount_++,
380  cs->priorityCustomTriggerList_);
381  //force time to now for auto-generated message
382  cs->messages_.back().setTime(time(0));
383  //Force a custom count because Console Label are ignored! if(cs->messages_.back().hasCustomTriggerMatchAction())
384  if(cs->priorityCustomTriggerList_.size())
385  {
386  cs->customTriggerActionQueue_.push(
387  cs->priorityCustomTriggerList_
388  [0]); //push newest action to back
389  cs->priorityCustomTriggerList_[0]
390  .occurrences++; //increment occurrences
391  cs->customTriggerActionQueue_.back().triggeredMessageCountIndex =
392  cs->messages_.back().getCount();
393  cs->messages_.back().setCustomTriggerMatch(
394  cs->customTriggerActionQueue_.back());
395  }
396  }
397 
398  // save the new last sequence ID
399  sourceLastSequenceID[newSourceId] = newSequenceId;
400 
401  while(cs->messages_.size() > 0 &&
402  cs->messages_.size() > cs->maxMessageCount_)
403  {
404  cs->messages_.erase(cs->messages_.begin());
405  }
406 
407  c += strlen(&(buffer.c_str()[c])) + 1;
408  } // end handle message stacking in packet
409  } // end received packet handling
410  else // idle network handling
411  {
412  if(i < 120) // if nothing received for 120 seconds, then something is wrong
413  // with Console configuration
414  ++i;
415 
416  sleep(1); // sleep one second, if timeout
417 
418  // every 60 heartbeatCount (2 seconds each = 1 sleep and 1 timeout) print a
419  // heartbeat message
420  if(i != 200 || // show first message, if not already a message
421  (heartbeatCount < 60 * 5 &&
422  heartbeatCount % 60 == 59)) // every ~2 min for first 5 messages
423  {
424  ++selfGeneratedMessageCount; // increment internal message count
425  __COUT__ << "Console is alive and waiting... (if no messages, next "
426  "heartbeat is in two minutes)"
427  << __E__;
428  }
429  else if(heartbeatCount % (60 * 30) == 59) // approx every hour
430  {
431  ++selfGeneratedMessageCount; // increment internal message count
432  __COUT__ << "Console is alive and waiting a long time... (if no "
433  "messages, next heartbeat is in one hour)"
434  << __E__;
435  }
436 
437  ++heartbeatCount;
438  } // end idle network handling
439 
440  // if nothing received for 2 minutes seconds, then something is wrong with Console
441  // configuration after 5 seconds there is a self-send. Which will at least
442  // confirm configuration. OR if 5 generated messages and never cleared.. then
443  // the forwarding is not working.
444  if(i == 120 || selfGeneratedMessageCount == 5)
445  {
446  __COUTV__(i);
447  __COUTV__(selfGeneratedMessageCount);
448  __COUT__ << "No messages received at Console Supervisor. Exiting Console "
449  "messageFacilityReceiverWorkLoop"
450  << __E__;
451  break; // assume something wrong, and break loop
452  }
453 
454  if(cs->customTriggerActionQueue_.size() && !cs->customTriggerActionThreadExists_)
455  {
456  cs->customTriggerActionThreadExists_ = true;
457 
458  std::thread(
459  [](ConsoleSupervisor* c) {
460  ConsoleSupervisor::customTriggerActionThread(c);
461  },
462  cs)
463  .detach();
464  }
465 
466  } // end infinite loop
467 
468 } // end messageFacilityReceiverWorkLoop()
469 catch(const std::runtime_error& e)
470 {
471  __COUT_ERR__ << "Error caught at Console Supervisor thread: " << e.what() << __E__;
472 }
473 catch(...)
474 {
475  __COUT_ERR__ << "Unknown error caught at Console Supervisor thread." << __E__;
476 } // end messageFacilityReceiverWorkLoop() exception handling
477 
478 //==============================================================================
481 void ConsoleSupervisor::customTriggerActionThread(ConsoleSupervisor* cs)
482 try
483 {
484  __COUT__ << "Starting customTriggerActionThread" << __E__;
485  CustomTriggeredAction_t triggeredAction;
486  while(1) //infinite workloop
487  {
488  { //mutex scope:
489  // lockout the messages array for the remainder of the scope
490  // this guarantees the reading thread can safely access the action queue
491  std::lock_guard<std::mutex> lock(cs->messageMutex_);
492  if(cs->customTriggerActionQueue_.size())
493  {
494  triggeredAction = cs->customTriggerActionQueue_.front();
495  cs->customTriggerActionQueue_.pop(); //pop first/oldest element
496  }
497  } //end mutex scope
498 
499  if(triggeredAction.action.size())
500  {
501  __COUT_TYPE__(TLVL_DEBUG + 2)
502  << __COUT_HDR__ << "Handling action '" << triggeredAction.action
503  << "' on custom count search string: "
504  << StringMacros::vectorToString(triggeredAction.needleSubstrings, {'*'})
505  << __E__;
506  cs->doTriggeredAction(triggeredAction);
507  }
508 
509  triggeredAction.action = ""; //clear action for next in queue
510  triggeredAction.triggeredMessageCountIndex =
511  -1; //clear triggered message ID for next in queue
512  sleep(2); //mostly sleep
513 
514  } //end infinite workloop
515 
516 } // end customTriggerActionThread()
517 catch(const std::runtime_error& e)
518 {
519  __COUT_ERR__ << "Error caught at Console Supervisor Action thread: " << e.what()
520  << __E__;
521 }
522 catch(...)
523 {
524  __COUT_ERR__ << "Unknown error caught at Console Supervisor Action thread." << __E__;
525 } // end customTriggerActionThread() exception handling
526 
527 //==============================================================================
528 void ConsoleSupervisor::doTriggeredAction(const CustomTriggeredAction_t& triggeredAction)
529 {
530  __SUP_COUT_INFO__ << "Launching Triggered Action '" << triggeredAction.action
531  << "' fired on custom count search string: "
532  << StringMacros::vectorToString(triggeredAction.needleSubstrings,
533  {'*'})
534  << __E__;
535 
536  //valid actions:
537  // Halt
538  // Stop
539  // Pause
540  // Soft Error
541  // Hard Error
542  // System Message
543 
544  if(CUSTOM_TRIGGER_ACTIONS.find(triggeredAction.action) ==
546  {
547  __SUP_SS__ << "Unrecognized triggered action '" << triggeredAction.action
548  << ",' valid actions are "
549  << StringMacros::setToString(CUSTOM_TRIGGER_ACTIONS) << __E__;
550  __SUP_SS_THROW__;
551  }
552 
553  //all FSM commands include a system message
554  if(triggeredAction.action != "Count Only")
555  theRemoteWebUsers_.sendSystemMessage(
556  "*" /* to all users*/,
557  "In the Console Supervisor, a custom count fired the action '" +
558  triggeredAction.action + "' on the search string '" +
559  StringMacros::vectorToString(triggeredAction.needleSubstrings, {'*'}) +
560  "'");
561 
562  if(triggeredAction.action == "Halt")
563  {
564  //TODO
565  }
566  else if(triggeredAction.action == "Stop")
567  {
568  //TODO
569  }
570  else if(triggeredAction.action == "Pause")
571  {
572  //TODO
573  }
574 
575 } // end doTriggeredAction()
576 
577 //==============================================================================
579 void ConsoleSupervisor::addCustomTriggeredAction(const std::string& triggerNeedle,
580  const std::string& triggerAction,
581  uint32_t priority /* = -1 */)
582 {
583  __SUP_COUTV__(triggerNeedle);
584 
585  bool allAsterisks = true;
586  for(const auto& c : triggerNeedle)
587  if(c != '*')
588  {
589  allAsterisks = false;
590  break;
591  }
592  if(allAsterisks)
593  {
594  __SUP_SS__ << "Illegal empty Search String value for the new Custom Count and "
595  "Action! Please enter a valid Search String (* wildcards are "
596  "allowed, e.g. \"value = * seconds\")."
597  << __E__;
598  __SUP_SS_THROW__;
599  }
600 
601  //check if triggerNeedle already exists
602  uint32_t currentPriority = -1;
603  for(const auto& customTrigger : priorityCustomTriggerList_)
604  {
605  ++currentPriority; //inc first to get to 0
606  if(StringMacros::vectorToString(customTrigger.needleSubstrings, {'*'}) ==
607  triggerNeedle)
608  {
609  __SUP_SS__ << "Failure! Can not add Custom Count Search String that already "
610  "exists. Found '"
611  << triggerNeedle
612  << "' already existing at priority = " << currentPriority << __E__;
613  __SUP_SS_THROW__;
614  }
615  } //end check if already exists
616 
617  __SUP_COUTV__(triggerAction);
618  __SUP_COUTV__(priority);
619  if(priority >= priorityCustomTriggerList_.size())
620  priority = priorityCustomTriggerList_.size(); //place at end
621  if(priority == 0 && triggerNeedle != CONSOLE_MISSED_NEEDLE)
622  {
623  __SUP_SS__ << "Illegal priority position of '" << priority
624  << "' requested. Please enter a priority value greater than 0. "
625  "Position 0 is reserved for identifying missing messages at the "
626  "Console Supervisor. Note: the action for missing messages, at "
627  "priority 0, may be customized by the user."
628  << __E__;
629  __SUP_SS_THROW__;
630  }
631 
632  //valid actions:
633  // Halt
634  // Stop
635  // Pause
636  // Soft Error
637  // Hard Error
638  // System Message
639 
640  if(CUSTOM_TRIGGER_ACTIONS.find(triggerAction) == CUSTOM_TRIGGER_ACTIONS.end())
641  {
642  __SUP_SS__ << "Unrecognized triggered action '" << triggerAction
643  << ",' valid actions are "
644  << StringMacros::setToString(CUSTOM_TRIGGER_ACTIONS) << __E__;
645  __SUP_SS_THROW__;
646  }
647 
648  //insert new custom count at priority position
649  priorityCustomTriggerList_.insert(priorityCustomTriggerList_.begin() + priority,
650  CustomTriggeredAction_t());
651  // priorityCustomTriggerList_.push_back(CustomTriggeredAction_t());
652  // priorityCustomTriggerList_.back()
653 
654  //break up on substring
655  StringMacros::getVectorFromString(
656  triggerNeedle,
657  priorityCustomTriggerList_[priority].needleSubstrings,
658  {'*'} /* delimiter */,
659  {} /* do not ignore whitespace */);
660  priorityCustomTriggerList_[priority].action = triggerAction;
661 
662  __SUP_COUT__ << "Added custom count: "
663  << (StringMacros::vectorToString(
664  priorityCustomTriggerList_[priority].needleSubstrings))
665  << " at priority: " << priority << __E__;
666 
667 } // end addCustomTriggeredAction()
668 
669 //==============================================================================
673 uint32_t ConsoleSupervisor::modifyCustomTriggeredAction(const std::string& currentNeedle,
674  const std::string& modifyType,
675  const std::string& setNeedle,
676  const std::string& setAction,
677  uint32_t setPriority)
678 {
679  __SUP_COUTV__(currentNeedle);
680  __SUP_COUTV__(modifyType);
681  __SUP_COUTV__(setNeedle);
682  __SUP_COUTV__(setAction);
683  __SUP_COUTV__(setPriority);
684 
685  //find current priority position of currentNeedle
686  uint32_t currentPriority = -1;
687  bool found = false;
688  for(const auto& customTrigger : priorityCustomTriggerList_)
689  {
690  ++currentPriority; //inc first to get to 0, -1 indicates not found
691  if(StringMacros::vectorToString(customTrigger.needleSubstrings, {'*'}) ==
692  currentNeedle)
693  {
694  found = true;
695  break; //found
696  }
697  }
698 
699  __SUP_COUTV__(currentPriority);
700  if(!found)
701  {
702  __SUP_SS__ << "Attempt to modify Custom Count Search String failed. Could not "
703  "find specified Search String '"
704  << currentNeedle << "' in prioritized list." << __E__;
705  __SUP_SS_THROW__;
706  }
707 
708  if(modifyType == "Deletion")
709  {
710  if(currentPriority == 0)
711  {
712  __SUP_SS__ << "Illegal deletion requested of priority position 0. Position 0 "
713  "is reserved for identifying missing messages at the Console "
714  "Supervisor. Note: the action of priority 0 may be customized "
715  "by the user, but it can not be deleted."
716  << __E__;
717  __SUP_SS_THROW__;
718  }
719 
720  __SUP_COUT__ << "Deleting custom count: "
721  << StringMacros::vectorToString(
722  priorityCustomTriggerList_[currentPriority].needleSubstrings,
723  {'*'})
724  << " w/action: "
725  << priorityCustomTriggerList_[currentPriority].action
726  << " and priority: " << currentPriority << __E__;
727  priorityCustomTriggerList_.erase(priorityCustomTriggerList_.begin() +
728  currentPriority);
729  return -1;
730  }
731 
732  if(modifyType == "Priority" || modifyType == "All")
733  {
734  if(setPriority >= priorityCustomTriggerList_.size())
735  setPriority = priorityCustomTriggerList_.size(); //place at end
736  if(setPriority == 0 && setNeedle != CONSOLE_MISSED_NEEDLE)
737  {
738  __SUP_SS__ << "Illegal priority position of '" << setPriority
739  << "' requested. Position 0 is reserved for identifying missing "
740  "messages at the Console Supervisor. Note: the action of "
741  "priority 0 may be customized by the user."
742  << __E__;
743  __SUP_SS_THROW__;
744  }
745  }
746  else //keep existing
747  setPriority = currentPriority;
748 
749  if(modifyType == "Action" || modifyType == "All")
750  {
751  //valid actions:
752  // Halt
753  // Stop
754  // Pause
755  // Soft Error
756  // Hard Error
757  // System Message
758 
759  if(CUSTOM_TRIGGER_ACTIONS.find(setAction) == CUSTOM_TRIGGER_ACTIONS.end())
760  {
761  __SUP_SS__ << "Unrecognized custom count action '" << setAction
762  << ",' valid actions are "
763  << StringMacros::setToString(CUSTOM_TRIGGER_ACTIONS) << __E__;
764  __SUP_SS_THROW__;
765  }
766  //modify existing action
767  priorityCustomTriggerList_[currentPriority].action = setAction;
768  }
769 
770  if(modifyType == "Search String" || modifyType == "All")
771  {
772  //modify existing needle
773  priorityCustomTriggerList_[currentPriority].needleSubstrings.clear();
774  StringMacros::getVectorFromString(
775  setNeedle,
776  priorityCustomTriggerList_[currentPriority].needleSubstrings,
777  {'*'} /* delimiter */,
778  {} /* do not ignore whitespace */);
779  }
780 
781  if(currentPriority != setPriority) //then need to copy
782  {
783  //insert new custom count at priority position
784  priorityCustomTriggerList_.insert(
785  priorityCustomTriggerList_.begin() + setPriority,
786  priorityCustomTriggerList_[currentPriority]);
787 
788  //delete from old position
789  if(currentPriority >= setPriority) //then increment after insert
790  ++currentPriority;
791 
792  priorityCustomTriggerList_.erase(priorityCustomTriggerList_.begin() +
793  currentPriority);
794 
795  if(currentPriority < setPriority) //then decrement after delete
796  --setPriority;
797  }
798 
799  __SUP_COUT__ << "Modified '" << modifyType << "' custom count: "
800  << StringMacros::vectorToString(
801  priorityCustomTriggerList_[setPriority].needleSubstrings, {'*'})
802  << " now w/action: " << priorityCustomTriggerList_[setPriority].action
803  << " and at priority: " << setPriority << __E__;
804 
805  return setPriority;
806 } // end modifyCustomTriggeredAction()
807 
808 //==============================================================================
809 void ConsoleSupervisor::loadCustomCountList()
810 {
811  __SUP_COUT__ << "loadCustomCountList() from "
812  << USER_CONSOLE_PREF_PATH + CUSTOM_COUNT_LIST_FILENAME << __E__;
813 
814  FILE* fp = fopen((USER_CONSOLE_PREF_PATH + CUSTOM_COUNT_LIST_FILENAME).c_str(), "r");
815  if(!fp)
816  {
817  __SUP_COUT__ << "Ignoring missing Custom Count list file at path: "
818  << (USER_CONSOLE_PREF_PATH + CUSTOM_COUNT_LIST_FILENAME) << __E__;
819  return;
820  }
821  priorityCustomTriggerList_.clear();
822 
823  char line[1000]; //do not allow larger than 1000 chars!
824  uint32_t i = 0;
825  std::string needle;
826  while(fgets(line, 1000, fp))
827  {
828  //ignore new line
829  if(strlen(line))
830  line[strlen(line) - 1] = '\0';
831 
832  if(i % 2 == 0) //needle
833  needle = line;
834  else //action (so have all info)
835  {
836  __SUP_COUTTV__(needle);
837  __SUP_COUTTV__(line);
838  if(i == 1 &&
839  needle !=
840  CONSOLE_MISSED_NEEDLE) //then force missed Console message as priority 0
841  addCustomTriggeredAction(CONSOLE_MISSED_NEEDLE, "System Message");
842  addCustomTriggeredAction(needle, line);
843  }
844 
845  ++i;
846  }
847  fclose(fp);
848 
849 } // end loadCustomCountList()
850 
851 //==============================================================================
852 void ConsoleSupervisor::saveCustomCountList()
853 {
854  __SUP_COUT__ << "saveCustomCountList()" << __E__;
855 
856  FILE* fp = fopen((USER_CONSOLE_PREF_PATH + CUSTOM_COUNT_LIST_FILENAME).c_str(), "w");
857  if(!fp)
858  {
859  __SUP_SS__ << "Failed to create Custom Count list file at path: "
860  << (USER_CONSOLE_PREF_PATH + CUSTOM_COUNT_LIST_FILENAME) << __E__;
861  __SUP_SS_THROW__;
862  }
863  for(auto& customCount : priorityCustomTriggerList_)
864  {
865  fprintf(fp,
866  (StringMacros::vectorToString(customCount.needleSubstrings, {'*'}) + "\n")
867  .c_str());
868  fprintf(fp, (customCount.action + "\n").c_str());
869  }
870  fclose(fp);
871 } // end saveCustomCountList()
872 
873 //==============================================================================
874 void ConsoleSupervisor::defaultPage(xgi::Input* /*in*/, xgi::Output* out)
875 {
876  // __SUP_COUT__ << "ApplicationDescriptor LID="
877  // << getApplicationDescriptor()->getLocalId() << __E__;
878  *out << "<!DOCTYPE HTML><html lang='en'><frameset col='100%' row='100%'><frame "
879  "src='/WebPath/html/Console.html?urn="
880  << getApplicationDescriptor()->getLocalId() << "'></frameset></html>";
881 } // end defaultPage()
882 
883 //==============================================================================
887 {
888  CorePropertySupervisorBase::setSupervisorProperty(
889  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AutomatedRequestTypes,
890  "GetConsoleMsgs");
891 } // end forceSupervisorPropertyValues()
892 
893 //==============================================================================
897 void ConsoleSupervisor::request(const std::string& requestType,
898  cgicc::Cgicc& cgiIn,
899  HttpXmlDocument& xmlOut,
900  const WebUsers::RequestUserInfo& userInfo)
901 {
902  //__SUP_COUT__ << "requestType " << requestType << __E__;
903 
904  // Commands:
905  // GetConsoleMsgs
906  // PrependHistoricMessages
907  // SaveUserPreferences
908  // LoadUserPreferences
909  // GetTraceLevels
910  // SetTraceLevels
911  // GetTriggerStatus
912  // SetTriggerEnable
913  // ResetTRACE
914  // EnableTRACE
915  // GetTraceSnapshot
916  // GetCustomCountsAndActions
917  // AddCustomCountsAndAction
918  // ModifyCustomCountsAndAction
919 
920  // Note: to report to logbook admin status use
921  // xmlOut.addTextElementToData(XML_ADMIN_STATUS,refreshTempStr_);
922 
923  if(requestType == "GetConsoleMsgs")
924  {
925  // lindex of -1 means first time and user just gets update lcount and lindex
926  std::string lastUpdateCountStr = CgiDataUtilities::postData(cgiIn, "lcount");
927 
928  if(lastUpdateCountStr == "")
929  {
930  __SUP_COUT_ERR__ << "Invalid Parameters! lastUpdateCount="
931  << lastUpdateCountStr << __E__;
932  xmlOut.addTextElementToData("Error",
933  "Error - Invalid parameters for GetConsoleMsgs.");
934  return;
935  }
936 
937  size_t lastUpdateCount = std::stoull(lastUpdateCountStr);
938 
939  // __SUP_COUT__ << "lastUpdateCount=" << lastUpdateCount << __E__;
940 
941  insertMessageRefresh(&xmlOut, lastUpdateCount);
942  }
943  else if(requestType == "PrependHistoricMessages")
944  {
945  size_t earliestOnhandMessageCount =
946  CgiDataUtilities::postDataAsInt(cgiIn, "earlyCount");
947  __SUP_COUTV__(earliestOnhandMessageCount);
948  prependHistoricMessages(&xmlOut, earliestOnhandMessageCount);
949  }
950  else if(requestType == "SaveUserPreferences")
951  {
952  int colorIndex = CgiDataUtilities::postDataAsInt(cgiIn, "colorIndex");
953  int showSideBar = CgiDataUtilities::postDataAsInt(cgiIn, "showSideBar");
954  int noWrap = CgiDataUtilities::postDataAsInt(cgiIn, "noWrap");
955  int messageOnly = CgiDataUtilities::postDataAsInt(cgiIn, "messageOnly");
956  int hideLineNumers = CgiDataUtilities::postDataAsInt(cgiIn, "hideLineNumers");
957 
958  // __SUP_COUT__ << "requestType " << requestType << __E__;
959  // __SUP_COUT__ << "colorIndex: " << colorIndex << __E__;
960  // __SUP_COUT__ << "showSideBar: " << showSideBar << __E__;
961  // __SUP_COUT__ << "noWrap: " << noWrap << __E__;
962  // __SUP_COUT__ << "messageOnly: " << messageOnly << __E__;
963  // __SUP_COUT__ << "hideLineNumers: " << hideLineNumers << __E__;
964 
965  if(userInfo.username_ == "") // should never happen?
966  {
967  __SUP_COUT_ERR__ << "Invalid user found! user=" << userInfo.username_
968  << __E__;
969  xmlOut.addTextElementToData("Error",
970  "Error - InvauserInfo.username_user found.");
971  return;
972  }
973 
974  std::string fn = (std::string)USER_CONSOLE_PREF_PATH + userInfo.username_ + "." +
975  (std::string)USERS_PREFERENCES_FILETYPE;
976 
977  // __SUP_COUT__ << "Save preferences: " << fn << __E__;
978  FILE* fp = fopen(fn.c_str(), "w");
979  if(!fp)
980  {
981  __SS__;
982  __THROW__(ss.str() + "Could not open file: " + fn);
983  }
984  fprintf(fp, "colorIndex %d\n", colorIndex);
985  fprintf(fp, "showSideBar %d\n", showSideBar);
986  fprintf(fp, "noWrap %d\n", noWrap);
987  fprintf(fp, "messageOnly %d\n", messageOnly);
988  fprintf(fp, "hideLineNumers %d\n", hideLineNumers);
989  fclose(fp);
990  }
991  else if(requestType == "LoadUserPreferences")
992  {
993  // __SUP_COUT__ << "requestType " << requestType << __E__;
994 
995  unsigned int colorIndex, showSideBar, noWrap, messageOnly, hideLineNumers;
996 
997  if(userInfo.username_ == "") // should never happen?
998  {
999  __SUP_COUT_ERR__ << "Invalid user found! user=" << userInfo.username_
1000  << __E__;
1001  xmlOut.addTextElementToData("Error", "Error - Invalid user found.");
1002  return;
1003  }
1004 
1005  std::string fn = (std::string)USER_CONSOLE_PREF_PATH + userInfo.username_ + "." +
1006  (std::string)USERS_PREFERENCES_FILETYPE;
1007 
1008  // __SUP_COUT__ << "Load preferences: " << fn << __E__;
1009 
1010  FILE* fp = fopen(fn.c_str(), "r");
1011  if(!fp)
1012  {
1013  // return defaults
1014  __SUP_COUT__ << "Returning defaults." << __E__;
1015  xmlOut.addTextElementToData("colorIndex", "0");
1016  xmlOut.addTextElementToData("showSideBar", "0");
1017  xmlOut.addTextElementToData("noWrap", "1");
1018  xmlOut.addTextElementToData("messageOnly", "0");
1019  xmlOut.addTextElementToData("hideLineNumers", "1");
1020  return;
1021  }
1022  fscanf(fp, "%*s %u", &colorIndex);
1023  fscanf(fp, "%*s %u", &showSideBar);
1024  fscanf(fp, "%*s %u", &noWrap);
1025  fscanf(fp, "%*s %u", &messageOnly);
1026  fscanf(fp, "%*s %u", &hideLineNumers);
1027  fclose(fp);
1028  // __SUP_COUT__ << "colorIndex: " << colorIndex << __E__;
1029  // __SUP_COUT__ << "showSideBar: " << showSideBar << __E__;
1030  // __SUP_COUT__ << "noWrap: " << noWrap << __E__;
1031  // __SUP_COUT__ << "messageOnly: " << messageOnly << __E__;
1032  // __SUP_COUT__ << "hideLineNumers: " << hideLineNumers << __E__;
1033 
1034  char tmpStr[20];
1035  sprintf(tmpStr, "%u", colorIndex);
1036  xmlOut.addTextElementToData("colorIndex", tmpStr);
1037  sprintf(tmpStr, "%u", showSideBar);
1038  xmlOut.addTextElementToData("showSideBar", tmpStr);
1039  sprintf(tmpStr, "%u", noWrap);
1040  xmlOut.addTextElementToData("noWrap", tmpStr);
1041  sprintf(tmpStr, "%u", messageOnly);
1042  xmlOut.addTextElementToData("messageOnly", tmpStr);
1043  sprintf(tmpStr, "%u", hideLineNumers);
1044  xmlOut.addTextElementToData("hideLineNumers", tmpStr);
1045  }
1046  else if(requestType == "GetTraceLevels")
1047  {
1048  __SUP_COUT__ << "requestType " << requestType << __E__;
1049 
1050  SOAPParameters txParameters; // params for xoap to send
1051  txParameters.addParameter("Request", "GetTraceLevels");
1052 
1053  SOAPParameters rxParameters; // params for xoap to recv
1054  rxParameters.addParameter("Command");
1055  rxParameters.addParameter("Error");
1056  rxParameters.addParameter("TRACEHostnameList");
1057  rxParameters.addParameter("TRACEList");
1058 
1059  traceMapToXDAQHostname_.clear(); // reset
1060 
1061  std::string traceList = "";
1062  auto& allTraceApps = allSupervisorInfo_.getAllTraceControllerSupervisorInfo();
1063  for(const auto& appInfo : allTraceApps)
1064  {
1065  __SUP_COUT__ << "Supervisor hostname = " << appInfo.first << "/"
1066  << appInfo.second.getId()
1067  << " name = " << appInfo.second.getName()
1068  << " class = " << appInfo.second.getClass()
1069  << " hostname = " << appInfo.second.getHostname() << __E__;
1070  try
1071  {
1072  xoap::MessageReference retMsg =
1073  SOAPMessenger::sendWithSOAPReply(appInfo.second.getDescriptor(),
1074  "TRACESupervisorRequest",
1075  txParameters);
1076  SOAPUtilities::receive(retMsg, rxParameters);
1077  __SUP_COUT__ << "Received TRACE response: "
1078  << SOAPUtilities::translate(retMsg).getCommand() << " ==> "
1079  << SOAPUtilities::translate(retMsg) << __E__;
1080 
1081  if(SOAPUtilities::translate(retMsg).getCommand() == "Fault")
1082  {
1083  __SUP_SS__ << "Unrecognized command at destination TRACE Supervisor "
1084  "hostname = "
1085  << appInfo.first << "/" << appInfo.second.getId()
1086  << " name = " << appInfo.second.getName()
1087  << " class = " << appInfo.second.getClass()
1088  << " hostname = " << appInfo.second.getHostname() << __E__;
1089  __SUP_SS_THROW__;
1090  }
1091  else if(SOAPUtilities::translate(retMsg).getCommand() == "TRACEFault")
1092  {
1093  __SUP_SS__ << "Error received: " << rxParameters.getValue("Error")
1094  << __E__;
1095  __SUP_SS_THROW__;
1096  }
1097  }
1098  catch(const xdaq::exception::Exception& e)
1099  {
1100  __SUP_SS__ << "Error transmitting request to TRACE Supervisor LID = "
1101  << appInfo.second.getId()
1102  << " name = " << appInfo.second.getName() << ". \n\n"
1103  << e.what() << __E__;
1104  //do not throw exception, because unable to set levels when some Supervisors are down
1105  //__SUP_SS_THROW__;
1106  __SUP_COUT_ERR__ << ss.str();
1107  continue; //skip bad Supervisor
1108  }
1109 
1110  std::vector<std::string> traceHostnameArr;
1111  __COUTTV__(rxParameters.getValue("TRACEHostnameList"));
1112  StringMacros::getVectorFromString(
1113  rxParameters.getValue("TRACEHostnameList"), traceHostnameArr, {';'});
1114  for(const auto& traceHostname : traceHostnameArr)
1115  {
1116  if(traceHostname == "")
1117  continue; //skip blanks
1118  traceMapToXDAQHostname_[traceHostname] = appInfo.first;
1119  }
1120 
1121  // traceList += ";" + appInfo.first; //insert xdaq context version of
1122  // name
1123  // //FIXME and create mapp from user's typed in xdaq
1124  // context name to TRACE hostname resolution
1125 
1126  __COUTTV__(rxParameters.getValue("TRACEList"));
1127  traceList += rxParameters.getValue("TRACEList");
1128 
1129  } // end app get TRACE loop
1130  __SUP_COUT__ << "TRACE hostname map received: \n"
1131  << StringMacros::mapToString(traceMapToXDAQHostname_) << __E__;
1132  __SUP_COUT__ << "TRACE List received: \n" << traceList << __E__;
1133  xmlOut.addTextElementToData("traceList", traceList);
1134  } // end GetTraceLevels
1135  else if(requestType == "SetTraceLevels")
1136  {
1137  __SUP_COUT__ << "requestType " << requestType << __E__;
1138 
1139  std::string individualValues =
1140  CgiDataUtilities::postData(cgiIn, "individualValues");
1141  std::string hostLabelMap = CgiDataUtilities::postData(cgiIn, "hostLabelMap");
1142  std::string setMode = CgiDataUtilities::postData(cgiIn, "setMode");
1143  std::string setValueMSB = CgiDataUtilities::postData(cgiIn, "setValueMSB");
1144  std::string setValueLSB = CgiDataUtilities::postData(cgiIn, "setValueLSB");
1145 
1146  __SUP_COUTV__(individualValues);
1147  __SUP_COUTV__(setMode);
1148  // set modes: SLOW, FAST, TRIGGER
1149  __SUP_COUTV__(setValueMSB);
1150  __SUP_COUTV__(setValueLSB);
1151 
1152  std::map<std::string /*host*/, std::string /*labelArr*/> hostToLabelMap;
1153 
1154  auto& allTraceApps = allSupervisorInfo_.getAllTraceControllerSupervisorInfo();
1155 
1156  SOAPParameters rxParameters; // params for xoap to recv
1157  rxParameters.addParameter("Command");
1158  rxParameters.addParameter("Error");
1159  rxParameters.addParameter("TRACEList");
1160 
1161  std::string modifiedTraceList = "";
1162  std::string xdaqHostname;
1163  StringMacros::getMapFromString(hostLabelMap, hostToLabelMap, {';'}, {':'});
1164  for(auto& hostLabelsPair : hostToLabelMap)
1165  {
1166  // identify artdaq hosts to go through ARTDAQ supervisor
1167  // by adding "artdaq.." to hostname artdaq..correlator2.fnal.gov
1168  __SUP_COUTV__(hostLabelsPair.first);
1169  __SUP_COUTV__(hostLabelsPair.second);
1170 
1171  // use map to convert to xdaq host
1172  try
1173  {
1174  xdaqHostname = traceMapToXDAQHostname_.at(hostLabelsPair.first);
1175  }
1176  catch(...)
1177  {
1178  __SUP_SS__ << "Could not find the translation from TRACE hostname '"
1179  << hostLabelsPair.first << "' to xdaq Context hostname."
1180  << __E__;
1181  ss << "Here is the existing map (size=" << traceMapToXDAQHostname_.size()
1182  << "): " << StringMacros::mapToString(traceMapToXDAQHostname_)
1183  << __E__;
1184  __SUP_SS_THROW__;
1185  }
1186 
1187  __SUP_COUTV__(xdaqHostname);
1188 
1189  auto& appInfo = allTraceApps.at(xdaqHostname);
1190  __SUP_COUT__ << "Supervisor hostname = " << hostLabelsPair.first << "/"
1191  << xdaqHostname << ":" << appInfo.getId()
1192  << " name = " << appInfo.getName()
1193  << " class = " << appInfo.getClass()
1194  << " hostname = " << appInfo.getHostname() << __E__;
1195  try
1196  {
1197  SOAPParameters txParameters; // params for xoap to send
1198  txParameters.addParameter("Request", "SetTraceLevels");
1199  txParameters.addParameter("IndividualValues", individualValues);
1200  txParameters.addParameter("Host", hostLabelsPair.first);
1201  txParameters.addParameter("SetMode", setMode);
1202  txParameters.addParameter("Labels", hostLabelsPair.second);
1203  txParameters.addParameter("SetValueMSB", setValueMSB);
1204  txParameters.addParameter("SetValueLSB", setValueLSB);
1205 
1206  xoap::MessageReference retMsg = SOAPMessenger::sendWithSOAPReply(
1207  appInfo.getDescriptor(), "TRACESupervisorRequest", txParameters);
1208  SOAPUtilities::receive(retMsg, rxParameters);
1209  __SUP_COUT__ << "Received TRACE response: "
1210  << SOAPUtilities::translate(retMsg).getCommand() << " ==> "
1211  << SOAPUtilities::translate(retMsg) << __E__;
1212 
1213  if(SOAPUtilities::translate(retMsg).getCommand() == "Fault")
1214  {
1215  __SUP_SS__ << "Unrecognized command at destination TRACE Supervisor "
1216  "hostname = "
1217  << hostLabelsPair.first << "/" << appInfo.getId()
1218  << " name = " << appInfo.getName()
1219  << " class = " << appInfo.getClass()
1220  << " hostname = " << appInfo.getHostname() << __E__;
1221  __SUP_SS_THROW__;
1222  }
1223  else if(SOAPUtilities::translate(retMsg).getCommand() == "TRACEFault")
1224  {
1225  __SUP_SS__ << "Error received: " << rxParameters.getValue("Error")
1226  << __E__;
1227  __SUP_SS_THROW__;
1228  }
1229  }
1230  catch(const xdaq::exception::Exception& e)
1231  {
1232  __SUP_SS__ << "Error transmitting request to TRACE Supervisor LID = "
1233  << appInfo.getId() << " name = " << appInfo.getName()
1234  << ". \n\n"
1235  << e.what() << __E__;
1236  __SUP_SS_THROW__;
1237  }
1238 
1239  modifiedTraceList +=
1240  ";" + hostLabelsPair.first; // insert xdaq context version of name
1241  // FIXME and create mapp from user's typed in xdaq
1242  // context name to TRACE hostname resolution
1243 
1244  modifiedTraceList += rxParameters.getValue("TRACEList");
1245 
1246  } // end host set TRACE loop
1247 
1248  __SUP_COUT__ << "mod'd TRACE List received: \n" << modifiedTraceList << __E__;
1249  xmlOut.addTextElementToData("modTraceList", modifiedTraceList);
1250  } // end SetTraceLevels
1251  else if(requestType == "GetTriggerStatus")
1252  {
1253  __SUP_COUT__ << "requestType " << requestType << __E__;
1254  SOAPParameters txParameters; // params for xoap to send
1255  txParameters.addParameter("Request", "GetTriggerStatus");
1256 
1257  SOAPParameters rxParameters; // params for xoap to recv
1258  rxParameters.addParameter("Command");
1259  rxParameters.addParameter("Error");
1260  rxParameters.addParameter("TRACETriggerStatus");
1261 
1262  std::string traceTriggerStatus = "";
1263  auto& allTraceApps = allSupervisorInfo_.getAllTraceControllerSupervisorInfo();
1264  for(const auto& appInfo : allTraceApps)
1265  {
1266  __SUP_COUT__ << "Supervisor hostname = " << appInfo.first << "/"
1267  << appInfo.second.getId()
1268  << " name = " << appInfo.second.getName()
1269  << " class = " << appInfo.second.getClass()
1270  << " hostname = " << appInfo.second.getHostname() << __E__;
1271  try
1272  {
1273  xoap::MessageReference retMsg =
1274  SOAPMessenger::sendWithSOAPReply(appInfo.second.getDescriptor(),
1275  "TRACESupervisorRequest",
1276  txParameters);
1277  SOAPUtilities::receive(retMsg, rxParameters);
1278  __SUP_COUT__ << "Received TRACE response: "
1279  << SOAPUtilities::translate(retMsg).getCommand() << " ==> "
1280  << SOAPUtilities::translate(retMsg) << __E__;
1281 
1282  if(SOAPUtilities::translate(retMsg).getCommand() == "Fault")
1283  {
1284  __SUP_SS__ << "Unrecognized command at destination TRACE Supervisor "
1285  "hostname = "
1286  << appInfo.first << "/" << appInfo.second.getId()
1287  << " name = " << appInfo.second.getName()
1288  << " class = " << appInfo.second.getClass()
1289  << " hostname = " << appInfo.second.getHostname() << __E__;
1290  __SUP_SS_THROW__;
1291  }
1292  else if(SOAPUtilities::translate(retMsg).getCommand() == "TRACEFault")
1293  {
1294  __SUP_SS__ << "Error received: " << rxParameters.getValue("Error")
1295  << __E__;
1296  __SUP_SS_THROW__;
1297  }
1298  }
1299  catch(const xdaq::exception::Exception& e)
1300  {
1301  __SUP_SS__ << "Error transmitting request to TRACE Supervisor LID = "
1302  << appInfo.second.getId()
1303  << " name = " << appInfo.second.getName() << ". \n\n"
1304  << e.what() << __E__;
1305  __SUP_SS_THROW__;
1306  }
1307 
1308  traceTriggerStatus += rxParameters.getValue("TRACETriggerStatus");
1309 
1310  } // end app get TRACE loop
1311  __SUP_COUT__ << "TRACE Trigger Status received: \n"
1312  << traceTriggerStatus << __E__;
1313  xmlOut.addTextElementToData("traceTriggerStatus", traceTriggerStatus);
1314  } // end GetTriggerStatus
1315  else if(requestType == "SetTriggerEnable")
1316  {
1317  __SUP_COUT__ << "requestType " << requestType << __E__;
1318 
1319  std::string hostList = CgiDataUtilities::postData(cgiIn, "hostList");
1320 
1321  __SUP_COUTV__(hostList);
1322 
1323  std::vector<std::string /*host*/> hosts;
1324 
1325  auto& allTraceApps = allSupervisorInfo_.getAllTraceControllerSupervisorInfo();
1326 
1327  SOAPParameters rxParameters; // params for xoap to recv
1328  rxParameters.addParameter("Command");
1329  rxParameters.addParameter("Error");
1330  rxParameters.addParameter("TRACETriggerStatus");
1331 
1332  std::string modifiedTriggerStatus = "";
1333  std::string xdaqHostname;
1334  StringMacros::getVectorFromString(hostList, hosts, {';'});
1335  for(auto& host : hosts)
1336  {
1337  // identify artdaq hosts to go through ARTDAQ supervisor
1338  // by adding "artdaq.." to hostname artdaq..correlator2.fnal.gov
1339  __SUP_COUTV__(host);
1340  if(host.size() < 3)
1341  continue; // skip bad hostnames
1342 
1343  // use map to convert to xdaq host
1344  try
1345  {
1346  xdaqHostname = traceMapToXDAQHostname_.at(host);
1347  }
1348  catch(...)
1349  {
1350  __SUP_SS__ << "Could not find the translation from TRACE hostname '"
1351  << host << "' to xdaq Context hostname." << __E__;
1352  ss << "Here is the existing map (size=" << traceMapToXDAQHostname_.size()
1353  << "): " << StringMacros::mapToString(traceMapToXDAQHostname_)
1354  << __E__;
1355  __SUP_SS_THROW__;
1356  }
1357 
1358  __SUP_COUTV__(xdaqHostname);
1359 
1360  auto& appInfo = allTraceApps.at(xdaqHostname);
1361  __SUP_COUT__ << "Supervisor hostname = " << host << "/" << xdaqHostname << ":"
1362  << appInfo.getId() << " name = " << appInfo.getName()
1363  << " class = " << appInfo.getClass()
1364  << " hostname = " << appInfo.getHostname() << __E__;
1365  try
1366  {
1367  SOAPParameters txParameters; // params for xoap to send
1368  txParameters.addParameter("Request", "SetTriggerEnable");
1369  txParameters.addParameter("Host", host);
1370 
1371  xoap::MessageReference retMsg = SOAPMessenger::sendWithSOAPReply(
1372  appInfo.getDescriptor(), "TRACESupervisorRequest", txParameters);
1373  SOAPUtilities::receive(retMsg, rxParameters);
1374  __SUP_COUT__ << "Received TRACE response: "
1375  << SOAPUtilities::translate(retMsg).getCommand() << " ==> "
1376  << SOAPUtilities::translate(retMsg) << __E__;
1377 
1378  if(SOAPUtilities::translate(retMsg).getCommand() == "Fault")
1379  {
1380  __SUP_SS__ << "Unrecognized command at destination TRACE Supervisor "
1381  "hostname = "
1382  << host << "/" << appInfo.getId()
1383  << " name = " << appInfo.getName()
1384  << " class = " << appInfo.getClass()
1385  << " hostname = " << appInfo.getHostname() << __E__;
1386  __SUP_SS_THROW__;
1387  }
1388  else if(SOAPUtilities::translate(retMsg).getCommand() == "TRACEFault")
1389  {
1390  __SUP_SS__ << "Error received: " << rxParameters.getValue("Error")
1391  << __E__;
1392  __SUP_SS_THROW__;
1393  }
1394  }
1395  catch(const xdaq::exception::Exception& e)
1396  {
1397  __SUP_SS__ << "Error transmitting request to TRACE Supervisor LID = "
1398  << appInfo.getId() << " name = " << appInfo.getName()
1399  << ". \n\n"
1400  << e.what() << __E__;
1401  __SUP_SS_THROW__;
1402  }
1403 
1404  modifiedTriggerStatus += rxParameters.getValue("TRACETriggerStatus");
1405  } // end host set TRACE loop
1406 
1407  __SUP_COUT__ << "mod'd TRACE Trigger Status received: \n"
1408  << modifiedTriggerStatus << __E__;
1409  xmlOut.addTextElementToData("modTriggerStatus", modifiedTriggerStatus);
1410  } // end SetTriggerEnable
1411  else if(requestType == "ResetTRACE")
1412  {
1413  __SUP_COUT__ << "requestType " << requestType << __E__;
1414 
1415  std::string hostList = CgiDataUtilities::postData(cgiIn, "hostList");
1416 
1417  __SUP_COUTV__(hostList);
1418 
1419  std::vector<std::string /*host*/> hosts;
1420 
1421  auto& allTraceApps = allSupervisorInfo_.getAllTraceControllerSupervisorInfo();
1422 
1423  SOAPParameters rxParameters; // params for xoap to recv
1424  rxParameters.addParameter("Command");
1425  rxParameters.addParameter("Error");
1426  rxParameters.addParameter("TRACETriggerStatus");
1427 
1428  std::string modifiedTriggerStatus = "";
1429  std::string xdaqHostname;
1430  StringMacros::getVectorFromString(hostList, hosts, {';'});
1431  for(auto& host : hosts)
1432  {
1433  // identify artdaq hosts to go through ARTDAQ supervisor
1434  // by adding "artdaq.." to hostname artdaq..correlator2.fnal.gov
1435  __SUP_COUTV__(host);
1436  if(host.size() < 3)
1437  continue; // skip bad hostnames
1438 
1439  // use map to convert to xdaq host
1440  try
1441  {
1442  xdaqHostname = traceMapToXDAQHostname_.at(host);
1443  }
1444  catch(...)
1445  {
1446  __SUP_SS__ << "Could not find the translation from TRACE hostname '"
1447  << host << "' to xdaq Context hostname." << __E__;
1448  ss << "Here is the existing map (size=" << traceMapToXDAQHostname_.size()
1449  << "): " << StringMacros::mapToString(traceMapToXDAQHostname_)
1450  << __E__;
1451  __SUP_SS_THROW__;
1452  }
1453 
1454  __SUP_COUTV__(xdaqHostname);
1455 
1456  auto& appInfo = allTraceApps.at(xdaqHostname);
1457  __SUP_COUT__ << "Supervisor hostname = " << host << "/" << xdaqHostname << ":"
1458  << appInfo.getId() << " name = " << appInfo.getName()
1459  << " class = " << appInfo.getClass()
1460  << " hostname = " << appInfo.getHostname() << __E__;
1461  try
1462  {
1463  SOAPParameters txParameters; // params for xoap to send
1464  txParameters.addParameter("Request", "ResetTRACE");
1465  txParameters.addParameter("Host", host);
1466 
1467  xoap::MessageReference retMsg = SOAPMessenger::sendWithSOAPReply(
1468  appInfo.getDescriptor(), "TRACESupervisorRequest", txParameters);
1469  SOAPUtilities::receive(retMsg, rxParameters);
1470  __SUP_COUT__ << "Received TRACE response: "
1471  << SOAPUtilities::translate(retMsg).getCommand() << " ==> "
1472  << SOAPUtilities::translate(retMsg) << __E__;
1473 
1474  if(SOAPUtilities::translate(retMsg).getCommand() == "Fault")
1475  {
1476  __SUP_SS__ << "Unrecognized command at destination TRACE Supervisor "
1477  "hostname = "
1478  << host << "/" << appInfo.getId()
1479  << " name = " << appInfo.getName()
1480  << " class = " << appInfo.getClass()
1481  << " hostname = " << appInfo.getHostname() << __E__;
1482  __SUP_SS_THROW__;
1483  }
1484  else if(SOAPUtilities::translate(retMsg).getCommand() == "TRACEFault")
1485  {
1486  __SUP_SS__ << "Error received: " << rxParameters.getValue("Error")
1487  << __E__;
1488  __SUP_SS_THROW__;
1489  }
1490  }
1491  catch(const xdaq::exception::Exception& e)
1492  {
1493  __SUP_SS__ << "Error transmitting request to TRACE Supervisor LID = "
1494  << appInfo.getId() << " name = " << appInfo.getName()
1495  << ". \n\n"
1496  << e.what() << __E__;
1497  __SUP_SS_THROW__;
1498  }
1499 
1500  modifiedTriggerStatus += rxParameters.getValue("TRACETriggerStatus");
1501  } // end host set TRACE loop
1502 
1503  __SUP_COUT__ << "mod'd TRACE Trigger Status received: \n"
1504  << modifiedTriggerStatus << __E__;
1505  xmlOut.addTextElementToData("modTriggerStatus", modifiedTriggerStatus);
1506  } // end ResetTRACE
1507  else if(requestType == "EnableTRACE")
1508  {
1509  __SUP_COUT__ << "requestType " << requestType << __E__;
1510 
1511  std::string hostList = CgiDataUtilities::postData(cgiIn, "hostList");
1512  std::string enable = CgiDataUtilities::postData(cgiIn, "enable");
1513 
1514  __SUP_COUTV__(hostList);
1515  __SUP_COUTV__(enable);
1516 
1517  std::vector<std::string /*host*/> hosts;
1518 
1519  auto& allTraceApps = allSupervisorInfo_.getAllTraceControllerSupervisorInfo();
1520 
1521  SOAPParameters rxParameters; // params for xoap to recv
1522  rxParameters.addParameter("Command");
1523  rxParameters.addParameter("Error");
1524  rxParameters.addParameter("TRACETriggerStatus");
1525 
1526  std::string modifiedTriggerStatus = "";
1527  std::string xdaqHostname;
1528  StringMacros::getVectorFromString(hostList, hosts, {';'});
1529  for(auto& host : hosts)
1530  {
1531  // identify artdaq hosts to go through ARTDAQ supervisor
1532  // by adding "artdaq.." to hostname artdaq..correlator2.fnal.gov
1533  __SUP_COUTV__(host);
1534  if(host.size() < 3)
1535  continue; // skip bad hostnames
1536 
1537  // use map to convert to xdaq host
1538  try
1539  {
1540  xdaqHostname = traceMapToXDAQHostname_.at(host);
1541  }
1542  catch(...)
1543  {
1544  __SUP_SS__ << "Could not find the translation from TRACE hostname '"
1545  << host << "' to xdaq Context hostname." << __E__;
1546  ss << "Here is the existing map (size=" << traceMapToXDAQHostname_.size()
1547  << "): " << StringMacros::mapToString(traceMapToXDAQHostname_)
1548  << __E__;
1549  __SUP_SS_THROW__;
1550  }
1551 
1552  __SUP_COUTV__(xdaqHostname);
1553 
1554  auto& appInfo = allTraceApps.at(xdaqHostname);
1555  __SUP_COUT__ << "Supervisor hostname = " << host << "/" << xdaqHostname << ":"
1556  << appInfo.getId() << " name = " << appInfo.getName()
1557  << " class = " << appInfo.getClass()
1558  << " hostname = " << appInfo.getHostname() << __E__;
1559  try
1560  {
1561  SOAPParameters txParameters; // params for xoap to send
1562  txParameters.addParameter("Request", "EnableTRACE");
1563  txParameters.addParameter("Host", host);
1564  txParameters.addParameter("SetEnable", enable);
1565 
1566  xoap::MessageReference retMsg = SOAPMessenger::sendWithSOAPReply(
1567  appInfo.getDescriptor(), "TRACESupervisorRequest", txParameters);
1568  SOAPUtilities::receive(retMsg, rxParameters);
1569  __SUP_COUT__ << "Received TRACE response: "
1570  << SOAPUtilities::translate(retMsg).getCommand() << " ==> "
1571  << SOAPUtilities::translate(retMsg) << __E__;
1572 
1573  if(SOAPUtilities::translate(retMsg).getCommand() == "Fault")
1574  {
1575  __SUP_SS__ << "Unrecognized command at destination TRACE Supervisor "
1576  "hostname = "
1577  << host << "/" << appInfo.getId()
1578  << " name = " << appInfo.getName()
1579  << " class = " << appInfo.getClass()
1580  << " hostname = " << appInfo.getHostname() << __E__;
1581  __SUP_SS_THROW__;
1582  }
1583  else if(SOAPUtilities::translate(retMsg).getCommand() == "TRACEFault")
1584  {
1585  __SUP_SS__ << "Error received: " << rxParameters.getValue("Error")
1586  << __E__;
1587  __SUP_SS_THROW__;
1588  }
1589  }
1590  catch(const xdaq::exception::Exception& e)
1591  {
1592  __SUP_SS__ << "Error transmitting request to TRACE Supervisor LID = "
1593  << appInfo.getId() << " name = " << appInfo.getName()
1594  << ". \n\n"
1595  << e.what() << __E__;
1596  __SUP_SS_THROW__;
1597  }
1598 
1599  modifiedTriggerStatus += rxParameters.getValue("TRACETriggerStatus");
1600  } // end host set TRACE loop
1601 
1602  __SUP_COUT__ << "mod'd TRACE Trigger Status received: \n"
1603  << modifiedTriggerStatus << __E__;
1604  xmlOut.addTextElementToData("modTriggerStatus", modifiedTriggerStatus);
1605  } // end EnableTRACE
1606  else if(requestType == "GetTraceSnapshot")
1607  {
1608  __SUP_COUT__ << "requestType " << requestType << __E__;
1609 
1610  std::string hostList = CgiDataUtilities::postData(cgiIn, "hostList");
1611  std::string filterFor = CgiDataUtilities::postData(cgiIn, "filterFor");
1612  std::string filterOut = CgiDataUtilities::postData(cgiIn, "filterOut");
1613 
1614  __SUP_COUTV__(hostList);
1615  __SUP_COUTV__(filterFor);
1616  __SUP_COUTV__(filterOut);
1617 
1618  std::vector<std::string /*host*/> hosts;
1619 
1620  auto& allTraceApps = allSupervisorInfo_.getAllTraceControllerSupervisorInfo();
1621 
1622  SOAPParameters rxParameters; // params for xoap to recv
1623  rxParameters.addParameter("Command");
1624  rxParameters.addParameter("Error");
1625  rxParameters.addParameter("TRACETriggerStatus");
1626  rxParameters.addParameter("TRACESnapshot");
1627 
1628  std::string modifiedTriggerStatus = "";
1629  std::string xdaqHostname;
1630  StringMacros::getVectorFromString(hostList, hosts, {';'});
1631  for(auto& host : hosts)
1632  {
1633  // identify artdaq hosts to go through ARTDAQ supervisor
1634  // by adding "artdaq.." to hostname artdaq..correlator2.fnal.gov
1635  __SUP_COUTV__(host);
1636  if(host.size() < 3)
1637  continue; // skip bad hostnames
1638 
1639  // use map to convert to xdaq host
1640  try
1641  {
1642  xdaqHostname = traceMapToXDAQHostname_.at(host);
1643  }
1644  catch(...)
1645  {
1646  __SUP_SS__ << "Could not find the translation from TRACE hostname '"
1647  << host << "' to xdaq Context hostname." << __E__;
1648  ss << "Here is the existing map (size=" << traceMapToXDAQHostname_.size()
1649  << "): " << StringMacros::mapToString(traceMapToXDAQHostname_)
1650  << __E__;
1651  __SUP_SS_THROW__;
1652  }
1653 
1654  __SUP_COUTV__(xdaqHostname);
1655 
1656  auto& appInfo = allTraceApps.at(xdaqHostname);
1657  __SUP_COUT__ << "Supervisor hostname = " << host << "/" << xdaqHostname << ":"
1658  << appInfo.getId() << " name = " << appInfo.getName()
1659  << " class = " << appInfo.getClass()
1660  << " hostname = " << appInfo.getHostname() << __E__;
1661  try
1662  {
1663  SOAPParameters txParameters; // params for xoap to send
1664  txParameters.addParameter("Request", "GetSnapshot");
1665  txParameters.addParameter("Host", host);
1666  txParameters.addParameter("FilterForCSV", filterFor);
1667  txParameters.addParameter("FilterOutCSV", filterOut);
1668 
1669  xoap::MessageReference retMsg = SOAPMessenger::sendWithSOAPReply(
1670  appInfo.getDescriptor(), "TRACESupervisorRequest", txParameters);
1671  SOAPUtilities::receive(retMsg, rxParameters);
1672  __SUP_COUT__ << "Received TRACE response: "
1673  << SOAPUtilities::translate(retMsg).getCommand() << __E__;
1674  //<< " ==> Bytes " << SOAPUtilities::translate(retMsg) << __E__;
1675 
1676  if(SOAPUtilities::translate(retMsg).getCommand() == "Fault")
1677  {
1678  __SUP_SS__ << "Unrecognized command at destination TRACE Supervisor "
1679  "hostname = "
1680  << host << "/" << appInfo.getId()
1681  << " name = " << appInfo.getName()
1682  << " class = " << appInfo.getClass()
1683  << " hostname = " << appInfo.getHostname() << __E__;
1684  __SUP_SS_THROW__;
1685  }
1686  else if(SOAPUtilities::translate(retMsg).getCommand() == "TRACEFault")
1687  {
1688  __SUP_SS__ << "Error received: " << rxParameters.getValue("Error")
1689  << __E__;
1690  __SUP_SS_THROW__;
1691  }
1692  }
1693  catch(const xdaq::exception::Exception& e)
1694  {
1695  __SUP_SS__ << "Error transmitting request to TRACE Supervisor LID = "
1696  << appInfo.getId() << " name = " << appInfo.getName()
1697  << ". \n\n"
1698  << e.what() << __E__;
1699  __SUP_SS_THROW__;
1700  }
1701 
1702  modifiedTriggerStatus += rxParameters.getValue("TRACETriggerStatus");
1703  xmlOut.addTextElementToData("host", host);
1704  std::string snapshot = rxParameters.getValue("TRACESnapshot");
1705  // if(snapshot.size() > 100000)
1706  // {
1707  // __SUP_COUT__ << "Truncating snapshot" << __E__;
1708  // snapshot.resize(100000);
1709  // }
1710  // xmlOut.addTextElementToData("hostSnapshot", snapshot);
1711 
1712  {
1713  std::string filename =
1714  USER_CONSOLE_SNAPSHOT_PATH + "snapshot_" + host + ".txt";
1715  __SUP_COUTV__(filename);
1716  FILE* fp = fopen(filename.c_str(), "w");
1717  if(!fp)
1718  {
1719  __SUP_SS__ << "Failed to create snapshot file: " << filename << __E__;
1720  __SUP_SS_THROW__;
1721  }
1722  fprintf(fp,
1723  "TRACE Snapshot taken at %s\n",
1724  StringMacros::getTimestampString().c_str());
1725 
1726  if(snapshot.size() > 5 && snapshot[2] != 'i')
1727  {
1728  // add header lines
1729  fprintf(
1730  fp,
1731  " idx us_tod delta pid tid cpu "
1732  " trcname lvl r msg \n");
1733  fprintf(fp,
1734  "----- ---------------- ----------- ------ ------ --- "
1735  "-------------------------------------- --- - "
1736  "--------------------------\n");
1737  }
1738  fprintf(fp, "%s", snapshot.c_str());
1739  fclose(fp);
1740  }
1741  } // end host set TRACE loop
1742 
1743  __SUP_COUT__ << "mod'd TRACE Trigger Status received: \n"
1744  << modifiedTriggerStatus << __E__;
1745  xmlOut.addTextElementToData("modTriggerStatus", modifiedTriggerStatus);
1746  } // end getTraceSnapshot
1747  else if(requestType == "GetCustomCountsAndActions" ||
1748  requestType == "AddCustomCountsAndAction" ||
1749  requestType == "ModifyCustomCountsAndAction")
1750  {
1751  __SUP_COUT__ << "requestType " << requestType
1752  << " size=" << priorityCustomTriggerList_.size() << __E__;
1753 
1754  //mutex scope:
1755  // lockout the messages array for the remainder of the scope
1756  // this guarantees can safely access the action queue
1757  std::lock_guard<std::mutex> lock(messageMutex_);
1758 
1759  if(requestType == "AddCustomCountsAndAction" ||
1760  requestType == "ModifyCustomCountsAndAction")
1761  {
1762  std::string needle = StringMacros::decodeURIComponent(
1763  CgiDataUtilities::postData(cgiIn, "needle"));
1764  uint32_t priority = CgiDataUtilities::postDataAsInt(cgiIn, "priority");
1765  std::string action = StringMacros::decodeURIComponent(
1766  CgiDataUtilities::postData(cgiIn, "action"));
1767 
1768  __SUP_COUTV__(needle);
1769  __SUP_COUTV__(priority);
1770  __SUP_COUTV__(action);
1771 
1772  if(requestType == "ModifyCustomCountsAndAction")
1773  {
1774  std::string buttonDo = StringMacros::decodeURIComponent(
1775  CgiDataUtilities::postData(cgiIn, "buttonDo"));
1776  std::string currentNeedle =
1777  CgiDataUtilities::postData(cgiIn, "currentNeedle");
1778 
1779  //treat needle as CSV list and do in reverse order to maintain priority of group
1780  std::vector<std::string> csvNeedles =
1781  StringMacros::getVectorFromString(currentNeedle, {','});
1782  for(size_t i = csvNeedles.size() - 1; i < csvNeedles.size(); --i)
1783  {
1784  if(csvNeedles[i].size() == 0)
1785  continue; //skip empty entries
1786  //change the priority to the last placed priority entry to keep group order
1787  priority = modifyCustomTriggeredAction(
1788  StringMacros::decodeURIComponent(csvNeedles[i]),
1789  buttonDo,
1790  needle,
1791  action,
1792  priority);
1793  } //end csv needle list handling
1794  }
1795  else
1796  addCustomTriggeredAction(needle, action, priority);
1797 
1798  saveCustomCountList();
1799  } // end AddCustomCountsAndAction
1800 
1801  //always calculate untriggered count
1802  size_t untriggeredCount =
1803  messageCount_; //copy and then decrement "unique" incrementing ID for messages
1804 
1805  for(const auto& customCount : priorityCustomTriggerList_)
1806  {
1807  xercesc::DOMElement* customCountParent =
1808  xmlOut.addTextElementToData("customCount", "");
1809 
1810  if(customCount.occurrences < untriggeredCount)
1811  untriggeredCount -= customCount.occurrences;
1812  else
1813  {
1814  __SUP_SS__ << "Impossible custom count; notify admins! "
1815  << customCount.occurrences << " > " << untriggeredCount
1816  << " for "
1817  << StringMacros::vectorToString(customCount.needleSubstrings,
1818  {'*'})
1819  << __E__;
1820  __SUP_SS_THROW__;
1821  }
1822  xmlOut.addTextElementToParent(
1823  "needle",
1824  StringMacros::vectorToString(customCount.needleSubstrings, {'*'}),
1825  customCountParent);
1826  xmlOut.addTextElementToParent(
1827  "count", std::to_string(customCount.occurrences), customCountParent);
1828  xmlOut.addTextElementToParent(
1829  "action", customCount.action, customCountParent);
1830  } //end adding custom counts to response xml loop
1831 
1832  //add untriggered always last
1833  xercesc::DOMElement* customCountParent =
1834  xmlOut.addTextElementToData("customCount", "");
1835  xmlOut.addTextElementToParent("needle", "< Untriggered >", customCountParent);
1836  xmlOut.addTextElementToParent(
1837  "count", std::to_string(untriggeredCount), customCountParent);
1838  xmlOut.addTextElementToParent("action", "Count Only", customCountParent);
1839 
1840  } // end GetCustomCountsAndActions or AddCustomCountsAndAction
1841  else
1842  {
1843  __SUP_SS__ << "requestType Request, " << requestType << ", not recognized."
1844  << __E__;
1845  __SUP_SS_THROW__;
1846  }
1847 } // end request()
1848 
1849 //==============================================================================
1853 {
1854  //Console Supervisor status detatil format is:
1855  // uptime, Err count, Warn count, Last Error msg, Last Warn msg
1856 
1857  //return uptime detail
1858  std::stringstream ss;
1859  ss << "Uptime: "
1860  << StringMacros::encodeURIComponent(StringMacros::getTimeDurationString(
1861  CorePropertySupervisorBase::getSupervisorUptime()));
1862 
1863  //return Err count, Warn count, Last Error msg, Last Warn msg, Last Info msg, Info count
1864 
1865  // size_t errorCount_ = 0, warnCount_ = 0;
1866  // std::string lastErrorMessage_, lastWarnMessage_;
1867  // time_t lastErrorMessageTime_ = 0, lastWarnMessageTime_ = 0;
1868 
1869  ss << ", Error #: " << errorCount_;
1870  ss << ", Warn #: " << warnCount_;
1871  ss << ", Last Error ("
1872  << (lastErrorMessageTime_ ? StringMacros::getTimestampString(lastErrorMessageTime_)
1873  : "0")
1874  << "): "
1875  << (lastErrorMessageTime_ ? StringMacros::encodeURIComponent(lastErrorMessage_)
1876  : "");
1877  ss << ", Last Warn ("
1878  << (lastWarnMessageTime_ ? StringMacros::getTimestampString(lastWarnMessageTime_)
1879  : "0")
1880  << "): "
1881  << (lastWarnMessageTime_ ? StringMacros::encodeURIComponent(lastWarnMessage_)
1882  : "");
1883  ss << ", Last Info ("
1884  << (lastInfoMessageTime_ ? StringMacros::getTimestampString(lastInfoMessageTime_)
1885  : "0")
1886  << "): "
1887  << (lastInfoMessageTime_ ? StringMacros::encodeURIComponent(lastInfoMessage_)
1888  : "");
1889  ss << ", Info #: " << infoCount_;
1890  ss << ", First Error ("
1891  << (firstErrorMessageTime_
1892  ? StringMacros::getTimestampString(firstErrorMessageTime_)
1893  : "0")
1894  << "): "
1895  << (firstErrorMessageTime_ ? StringMacros::encodeURIComponent(firstErrorMessage_)
1896  : "");
1897  ss << ", First Warn ("
1898  << (firstWarnMessageTime_ ? StringMacros::getTimestampString(firstWarnMessageTime_)
1899  : "0")
1900  << "): "
1901  << (firstWarnMessageTime_ ? StringMacros::encodeURIComponent(firstWarnMessage_)
1902  : "");
1903  ss << ", First Info ("
1904  << (firstInfoMessageTime_ ? StringMacros::getTimestampString(firstInfoMessageTime_)
1905  : "0")
1906  << "): "
1907  << (firstInfoMessageTime_ ? StringMacros::encodeURIComponent(firstInfoMessage_)
1908  : "");
1909 
1910  return ss.str();
1911 } // end getStatusProgressDetail()
1912 
1913 //==============================================================================
1936 void ConsoleSupervisor::insertMessageRefresh(HttpXmlDocument* xmlOut,
1937  const size_t lastUpdateCount)
1938 {
1939  //__SUP_COUT__ << __E__;
1940 
1941  if(messages_.size() == 0)
1942  return;
1943 
1944  // validate lastUpdateCount
1945  if(lastUpdateCount > messages_.back().getCount() && lastUpdateCount != (size_t)-1)
1946  {
1947  __SS__ << "Invalid lastUpdateCount: " << lastUpdateCount
1948  << " messagesArray size = " << messages_.back().getCount() << __E__;
1949  __SS_THROW__;
1950  }
1951 
1952  // lockout the messages array for the remainder of the scope
1953  // this guarantees the reading thread can safely access the messages
1954  std::lock_guard<std::mutex> lock(messageMutex_);
1955 
1956  xmlOut->addTextElementToData("last_update_count",
1957  std::to_string(messages_.back().getCount()));
1958 
1959  refreshParent_ = xmlOut->addTextElementToData("messages", "");
1960 
1961  bool requestOutOfSync = false;
1962  std::string requestOutOfSyncMsg;
1963 
1964  size_t refreshReadPointer = 0;
1965  if(lastUpdateCount != (size_t)-1)
1966  {
1967  while(refreshReadPointer < messages_.size() &&
1968  messages_[refreshReadPointer].getCount() <= lastUpdateCount)
1969  {
1970  ++refreshReadPointer;
1971  }
1972  }
1973 
1974  if(refreshReadPointer >= messages_.size())
1975  return;
1976 
1977  // limit number of catch-up messages
1978  if(messages_.size() - refreshReadPointer > maxClientMessageRequest_)
1979  {
1980  // __SUP_COUT__ << "Only sending latest " << maxClientMessageRequest_ << "
1981  // messages!";
1982 
1983  // auto oldrrp = refreshReadPointer;
1984  refreshReadPointer = messages_.size() - maxClientMessageRequest_;
1985 
1986  // __SS__ << "Skipping " << (refreshReadPointer - oldrrp)
1987  // << " messages because the web console has fallen behind!" << __E__;
1988  // __COUT__ << ss.str();
1989  // ConsoleMessageStruct msg(CONSOLE_SPECIAL_WARNING + ss.str(), lastUpdateCount);
1990  // auto it = messages_.begin();
1991  // std::advance(it, refreshReadPointer + 1);
1992  // messages_.insert(it, msg);
1993  }
1994 
1995  //return first_update_count, so that older messages could be retrieved later if desired by user
1996  xmlOut->addTextElementToData(
1997  "earliest_update_count",
1998  std::to_string(messages_[refreshReadPointer].getCount()));
1999 
2000  // output oldest to new
2001  for(; refreshReadPointer < messages_.size(); ++refreshReadPointer)
2002  {
2003  auto msg = messages_[refreshReadPointer];
2004  if(msg.getCount() < lastUpdateCount)
2005  {
2006  if(!requestOutOfSync) // record out of sync message once only
2007  {
2008  requestOutOfSync = true;
2009  __SS__ << "Request is out of sync! Message count should be more recent! "
2010  << msg.getCount() << " < " << lastUpdateCount << __E__;
2011  requestOutOfSyncMsg = ss.str();
2012  }
2013  // assume these messages are new (due to a system restart)
2014  // continue;
2015  }
2016 
2017  addMessageToResponse(xmlOut, msg);
2018 
2019  } //end main message add loop
2020 
2021  if(requestOutOfSync) // if request was out of sync, show message
2022  __SUP_COUT__ << requestOutOfSyncMsg;
2023 } // end insertMessageRefresh()
2024 
2025 //==============================================================================
2046 void ConsoleSupervisor::prependHistoricMessages(HttpXmlDocument* xmlOut,
2047  const size_t earliestOnhandMessageCount)
2048 {
2049  //__SUP_COUT__ << __E__;
2050 
2051  if(messages_.size() == 0)
2052  return;
2053 
2054  // validate earliestOnhandMessageCount
2055  if(earliestOnhandMessageCount >= messages_.back().getCount())
2056  {
2057  __SS__
2058  << "Invalid claim from user request of earliest onhand message sequence ID = "
2059  << earliestOnhandMessageCount
2060  << ". Latest existing sequence ID = " << messages_.back().getCount()
2061  << ". Was the Console Supervisor restarted?" << __E__;
2062  __SS_THROW__;
2063  }
2064 
2065  // lockout the messages array for the remainder of the scope
2066  // this guarantees the reading thread can safely access the messages
2067  std::lock_guard<std::mutex> lock(messageMutex_);
2068 
2069  refreshParent_ = xmlOut->addTextElementToData("messages", "");
2070 
2071  size_t refreshReadPointer = 0;
2072  size_t readCountStart = earliestOnhandMessageCount - maxClientMessageRequest_;
2073  if(readCountStart >= messages_.back().getCount()) //then wrapped around, so set to 0
2074  readCountStart = 0;
2075 
2076  //find starting read pointer
2077  while(refreshReadPointer < messages_.size() &&
2078  messages_[refreshReadPointer].getCount() < readCountStart)
2079  {
2080  ++refreshReadPointer;
2081  }
2082 
2083  if(refreshReadPointer >= messages_.size())
2084  return;
2085 
2086  xmlOut->addTextElementToData("earliest_update_count", //return new early onhand count
2087  std::to_string(readCountStart));
2088 
2089  //messages returned will be from readCountStart to earliestOnhandMessageCount-1
2090  // output oldest to new
2091  for(; refreshReadPointer < messages_.size(); ++refreshReadPointer)
2092  {
2093  auto msg = messages_[refreshReadPointer];
2094  if(messages_[refreshReadPointer].getCount() >= earliestOnhandMessageCount)
2095  break; //found last message
2096 
2097  addMessageToResponse(xmlOut, msg);
2098 
2099  } //end main message add loop
2100 
2101 } // end prependHistoricMessages()
2102 
2103 //==============================================================================
2104 void ConsoleSupervisor::addMessageToResponse(HttpXmlDocument* xmlOut,
2106 {
2107  // for all fields, give value
2108  for(auto& field : msg.fields)
2109  {
2110  if(field.first == ConsoleMessageStruct::FieldType::SOURCE)
2111  continue; // skip, not userful
2112  if(field.first == ConsoleMessageStruct::FieldType::SOURCEID)
2113  continue; // skip, not userful
2114  if(field.first == ConsoleMessageStruct::FieldType::SEQID)
2115  continue; // skip, not userful
2116  if(field.first == ConsoleMessageStruct::FieldType::TIMESTAMP) //use Time instead
2117  continue; // skip, not userful
2118  if(field.first ==
2119  ConsoleMessageStruct::FieldType::LEVEL) //use modified getLevel instead
2120  continue; // skip, not userful
2121 
2122  xmlOut->addTextElementToParent(
2123  "message_" + ConsoleMessageStruct::fieldNames.at(field.first),
2124  field.second,
2125  refreshParent_);
2126  } //end msg field loop
2127 
2128  // give modified level also
2129  xmlOut->addTextElementToParent("message_Level", msg.getLevel(), refreshParent_);
2130 
2131  // give timestamp also
2132  xmlOut->addTextElementToParent("message_Time", msg.getTime(), refreshParent_);
2133 
2134  // give global count index also
2135  xmlOut->addTextElementToParent(
2136  "message_Count", std::to_string(msg.getCount()), refreshParent_);
2137 
2138  //give Custom count label also (i.e., which search string this message matches, or blank "" for no match)
2139  xmlOut->addTextElementToParent(
2140  "message_Custom",
2141  StringMacros::vectorToString(msg.getCustomTriggerMatch().needleSubstrings, {'*'}),
2142  refreshParent_);
2143 
2144 } // end addMessageToResponse()
virtual void forceSupervisorPropertyValues(void) override
virtual void request(const std::string &requestType, cgicc::Cgicc &cgiIn, HttpXmlDocument &xmlOut, const WebUsers::RequestUserInfo &userInfo) override
static const std::set< std::string > CUSTOM_TRIGGER_ACTIONS
Count always happens, and System Message always happens for FSM commands.
virtual std::string getStatusProgressDetail(void) override
@ SEQID
sequence ID is incrementing number independent from each source