node_monitor.cpp
Go to the documentation of this file.
1 // Monitors a single node process
2 // Author: Max Schwarz <max.schwarz@uni-bonn.de>
3 
4 #include "node_monitor.h"
5 
6 #include <cerrno>
7 #include <csignal>
8 #include <cstdarg>
9 #include <cstdio>
10 #include <cstring>
11 #include <sstream>
12 
13 #include <fcntl.h>
14 #include <glob.h>
15 #include <pty.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <sys/resource.h>
19 #include <sys/utsname.h>
20 #include <sys/prctl.h>
21 #include <unistd.h>
22 #include <wordexp.h>
23 
24 #include <boost/tokenizer.hpp>
25 #include <boost/range.hpp>
26 #include <boost/algorithm/string.hpp>
27 
28 #include <boost/filesystem.hpp>
29 
30 #include <fmt/format.h>
31 
32 #define TASK_COMM_LEN 16 // from linux/sched.h
33 
34 namespace fs = boost::filesystem;
35 
36 namespace
37 {
38  template<typename... Args>
39  std::runtime_error error(const char* fmt, const Args& ... args)
40  {
41  return std::runtime_error(fmt::format(fmt, args...));
42  }
43 
44  // finally() from C++ core guidelines
45  template <class F>
46  class final_act
47  {
48  public:
49  explicit final_act(F f) noexcept
50  : f_(std::move(f)), invoke_(true) {}
51 
52  final_act(final_act&& other) noexcept
53  : f_(std::move(other.f_)),
54  invoke_(other.invoke_)
55  {
56  other.invoke_ = false;
57  }
58 
59  final_act(const final_act&) = delete;
60  final_act& operator=(const final_act&) = delete;
61 
62  ~final_act() noexcept
63  {
64  if (invoke_) f_();
65  }
66 
67  private:
68  F f_;
69  bool invoke_;
70  };
71  template <class F>
72  inline final_act<F> finally(const F& f) noexcept
73  {
74  return final_act<F>(f);
75  }
76 
77  template <class F>
78  inline final_act<F> finally(F&& f) noexcept
79  {
80  return final_act<F>(std::forward<F>(f));
81  }
82 
83  static bool g_corePatternAnalyzed = false;
84  static bool g_coreIsRelative = true;
85  static bool g_coreIsSystemd = false;
86 }
87 
88 namespace rosmon
89 {
90 namespace monitor
91 {
92 
93 NodeMonitor::NodeMonitor(const launch::LaunchConfig::ConstPtr& config, const launch::Node::ConstPtr& launchNode, FDWatcher::Ptr fdWatcher, ros::NodeHandle& nh)
94  : m_launchConfig(config)
95  , m_launchNode(launchNode)
96  , m_fdWatcher(std::move(fdWatcher))
97  , m_exitCode(0)
98  , m_command(CMD_STOP) // we start in stopped state
99  , m_restarting(false)
100  , m_muted(m_launchNode->isMuted())
101 {
103  LogEvent event{fullName(), std::move(src.message)};
104  event.muted = isMuted();
105  event.type = src.severity;
106  event.channel = LogEvent::Channel::Stdout;
107  event.showStdout = m_launchNode->stdoutDisplayed();
108 
109  logMessageSignal(std::move(event));
110  });
112  LogEvent event{fullName(), std::move(src.message)};
113  event.muted = isMuted();
114  event.type = src.severity;
115  event.channel = LogEvent::Channel::Stderr;
116 
117  logMessageSignal(std::move(event));
118  });
119 
120 
121  m_restartTimer = nh.createWallTimer(ros::WallDuration(1.0), boost::bind(&NodeMonitor::start, this), false, false);
122  m_stopCheckTimer = nh.createWallTimer(ros::WallDuration(m_launchNode->stopTimeout()), boost::bind(&NodeMonitor::checkStop, this));
123 
124  m_processWorkingDirectory = m_launchNode->workingDirectory();
125 
126  if(!g_corePatternAnalyzed)
127  {
128  char core_pattern[256];
129  int core_fd = open("/proc/sys/kernel/core_pattern", O_RDONLY | O_CLOEXEC);
130  if(core_fd < 0)
131  {
132  logTyped(LogEvent::Type::Error, "could not open /proc/sys/kernel/core_pattern: {}", strerror(errno));
133  return;
134  }
135 
136  int bytes = read(core_fd, core_pattern, sizeof(core_pattern)-1);
137  close(core_fd);
138 
139  if(bytes < 1)
140  {
141  logTyped(LogEvent::Type::Error, "Could not read /proc/sys/kernel/core_pattern: {}", strerror(errno));
142  return;
143  }
144 
145  g_coreIsRelative = (core_pattern[0] != '/');
146 
147  if(std::string(core_pattern).find("systemd-coredump") != std::string::npos)
148  g_coreIsSystemd = true;
149 
150  g_corePatternAnalyzed = true;
151  }
152 }
153 
155 {
156  if(m_pid != -1)
157  {
158  kill(m_pid, SIGKILL);
159  }
160 }
161 
162 std::vector<std::string> NodeMonitor::composeCommand() const
163 {
164  if(m_launchNode->executable().empty())
165  {
166  throw error("Could not find node '{}' in package '{}'",
167  m_launchNode->type(), m_launchNode->package()
168  );
169  }
170 
171  // Start with the launch prefix...
172  std::vector<std::string> cmd = m_launchNode->launchPrefix();
173 
174  // add executable file
175  cmd.push_back(m_launchNode->executable());
176 
177  // add extra arguments from 'args'
178  auto args = m_launchNode->extraArguments();
179  std::copy(args.begin(), args.end(), std::back_inserter(cmd));
180 
181  // add parameter for node name
182  cmd.push_back("__name:=" + m_launchNode->name());
183 
184  std::string logDir = m_launchConfig->nodeLogDir();
185  if(!logDir.empty())
186  {
187  std::string logname;
188  if(m_launchNode->namespaceString().empty())
189  logname = m_launchNode->name();
190  else
191  logname = m_launchNode->namespaceString() + "/" + m_launchNode->name();
192 
193  // Strip initial / if present
194  if(!logname.empty() && logname[0] == '/')
195  logname = logname.substr(1);
196 
197  // Replace all remaining / with -
198  std::replace(logname.begin(), logname.end(), '/', '-');
199 
200  cmd.push_back(fmt::format("__log:={}/{}.log", logDir, logname));
201  }
202  else
203  {
204  // disable internal logging
205  cmd.push_back("__log:=/dev/null");
206  }
207 
208  // and finally add remappings.
209  for(auto map : m_launchNode->remappings())
210  {
211  cmd.push_back(map.first + ":=" + map.second);
212  }
213 
214  return cmd;
215 }
216 
218 {
219  if(m_firstStart && !m_restarting && m_launchNode->spawnDelay() != ros::WallDuration())
220  {
221  m_restartTimer.setPeriod(m_launchNode->spawnDelay());
223  m_restarting = true;
224  return;
225  }
226 
227  m_command = CMD_RUN;
228 
231  m_restarting = false;
232 
233  if(running())
234  return;
235 
236  if(m_launchNode->coredumpsEnabled() && g_coreIsRelative && m_processWorkingDirectory.empty())
237  {
238  char tmpfile[256];
239  strncpy(tmpfile, "/tmp/rosmon-node-XXXXXX", sizeof(tmpfile));
240  tmpfile[sizeof(tmpfile)-1] = 0;
241  m_processWorkingDirectory = mkdtemp(tmpfile);
243  }
244 
245  ROS_INFO("rosmon: starting '%s'", fullName().c_str());
246 
247  if(!m_firstStart)
248  logMessageSignal({"[rosmon]", fmt::format("Starting node {}", fullName()), LogEvent::Type::Info});
249 
250  m_firstStart = false;
251 
252  std::vector<std::string> cmd = composeCommand();
253 
254  std::vector<char*> args;
255  auto argsCleaner = finally([&args](){
256  for(char* arg : args)
257  free(arg);
258  });
259 
260  // Open pseudo-terminal
261  int master, slave;
262  std::tie(master, slave) = createPTY();
263 
264  // For stderr, we open a second PTY
265  int stderr_master, stderr_slave;
266  std::tie(stderr_master, stderr_slave) = createPTY();
267 
268  // Compose args
269  {
270  args.push_back(strdup("rosrun"));
271  args.push_back(strdup("rosmon_core"));
272  args.push_back(strdup("_shim"));
273 
274  args.push_back(strdup("--tty"));
275  args.push_back(strdup(fmt::format("{}", slave).c_str()));
276 
277  args.push_back(strdup("--stderr"));
278  args.push_back(strdup(fmt::format("{}", stderr_slave).c_str()));
279 
280  if(!m_launchNode->namespaceString().empty())
281  {
282  args.push_back(strdup("--namespace"));
283  args.push_back(strdup(m_launchNode->namespaceString().c_str()));
284  }
285 
286  for(auto& pair : m_launchNode->extraEnvironment())
287  {
288  args.push_back(strdup("--env"));
289  args.push_back(strdup(fmt::format("{}={}", pair.first, pair.second).c_str()));
290  }
291 
292  if(m_launchNode->coredumpsEnabled())
293  {
294  args.push_back(strdup("--coredump"));
295 
296  if(g_coreIsRelative)
297  {
298  args.push_back(strdup("--coredump-relative"));
299  args.push_back(strdup(m_processWorkingDirectory.c_str()));
300  }
301  }
302 
303  args.push_back(strdup("--run"));
304 
305  for(auto& c : cmd)
306  args.push_back(strdup(c.c_str()));
307 
308  args.push_back(nullptr);
309  }
310 
311  // Fork!
312  int pid = fork();
313  if(pid < 0)
314  throw error("Could not fork(): {}", strerror(errno));
315 
316  if(pid == 0)
317  {
318  close(master);
319  close(stderr_master);
320 
321  if(execvp("rosrun", args.data()) != 0)
322  {
323  std::stringstream ss;
324  for(const auto& part : cmd)
325  ss << part << " ";
326 
327  fmt::print(stderr, "Could not execute '{}': {}\n", ss.str(), strerror(errno));
328  }
329 
330  // We should not end up here...
331  std::abort();
332  }
333 
334  // Parent
335  close(slave);
336  close(stderr_slave);
337 
338  m_fd = master;
339  m_stderrFD = stderr_master;
340  m_pid = pid;
341  m_fdWatcher->registerFD(m_fd, boost::bind(&NodeMonitor::communicate, this));
342  m_fdWatcher->registerFD(m_stderrFD, boost::bind(&NodeMonitor::communicateStderr, this));
343 }
344 
346 {
347  if(restart)
349  else
351 
354 
355  if(!running())
356  return;
357 
358  logMessageSignal({"[rosmon]", fmt::format("Stopping node {}", fullName()), LogEvent::Type::Info});
359 
360  // kill(-pid) sends the signal to all processes in the process group
361  kill(-m_pid, SIGINT);
362 
364 }
365 
367 {
368  if(running())
369  {
370  logTyped(LogEvent::Type::Warning, "required SIGKILL");
371  kill(m_pid, SIGKILL);
372  }
373 
375 }
376 
378 {
381 
382  if(running())
383  stop(true);
384  else
385  start();
386 }
387 
389 {
390  if(m_pid != -1)
391  {
392  kill(-m_pid, SIGINT);
393  }
394 }
395 
397 {
398  if(m_pid != -1)
399  {
400  kill(-m_pid, SIGKILL);
401  }
402 }
403 
405 {
406  return m_pid != -1;
407 }
408 
410 {
411  if(running())
412  return STATE_RUNNING;
413 
414  if(m_restarting)
415  return STATE_WAITING;
416 
417  if(m_exitCode == 0)
418  return STATE_IDLE;
419 
420  return STATE_CRASHED;
421 }
422 
424 {
425  char buf[1024];
426  int bytes = read(m_stderrFD, buf, sizeof(buf));
427 
428  if(bytes == 0 || (bytes < 0 && errno == EIO))
429  {
430  // Flush out any remaining stderr
432 
433  m_fdWatcher->removeFD(m_stderrFD);
434  close(m_stderrFD);
435  m_stderrFD = -1;
436  return; // handled in communicate()
437  }
438 
439  if(bytes < 0)
440  throw error("{}: Could not read: {}", fullName(), strerror(errno));
441 
442  m_stderrParser.process(buf, bytes);
443 }
444 
446 {
447  char buf[1024];
448  int bytes = read(m_fd, buf, sizeof(buf));
449 
450  if(bytes == 0 || (bytes < 0 && errno == EIO))
451  {
452  int status;
453 
454  while(true)
455  {
456  if(waitpid(m_pid, &status, 0) > 0)
457  break;
458 
459  if(errno == EINTR || errno == EAGAIN)
460  continue;
461 
462  throw error("{}: Could not waitpid(): {}", fullName(), strerror(errno));
463  }
464 
465  // Flush out any remaining stdout
467 
468  if(WIFEXITED(status))
469  {
470  auto type = (WEXITSTATUS(status) == 0) ? LogEvent::Type::Info : LogEvent::Type::Error;
471  logTyped(type, "{} exited with status {}", fullName(), WEXITSTATUS(status));
472  ROS_INFO("rosmon: %s exited with status %d", fullName().c_str(), WEXITSTATUS(status));
473  m_exitCode = WEXITSTATUS(status);
474  }
475  else if(WIFSIGNALED(status))
476  {
477  logTyped(LogEvent::Type::Error, "{} died from signal {}", fullName(), WTERMSIG(status));
478  ROS_ERROR("rosmon: %s died from signal %d", fullName().c_str(), WTERMSIG(status));
479  m_exitCode = 255;
480  }
481 
482 #ifdef WCOREDUMP
483  if(WCOREDUMP(status))
484  {
485  if(!m_launchNode->launchPrefix().empty())
486  {
487  logTyped(LogEvent::Type::Info, "{} used launch-prefix, not collecting core dump as it is probably useless.", fullName());
488  }
489  else
490  {
491  // We have a chance to find the core dump...
492  logTyped(LogEvent::Type::Info, "{} left a core dump", fullName());
493  gatherCoredump(WTERMSIG(status));
494  }
495  }
496 #endif
497 
499  {
500  // Our removal strategy is two-fold: After a process exits,
501  // we immediately try to delete the temporary working directory.
502  // If that fails (e.g. because there is a core dump in there),
503  // we remember the directory in m_lastWorkingDirectory.
504 
505  // and then delete it recursively on the next process exit.
506  // That way, we always keep the last core dump around, but prevent
507  // infinite pile-up.
508  if(!m_lastWorkingDirectory.empty())
509  {
510  boost::system::error_code error;
511  boost::filesystem::remove_all(m_lastWorkingDirectory, error);
512  if(error)
513  {
515  "Could not remove old working directory '{}' after process died twice: {}",
516  m_lastWorkingDirectory, error.message()
517  );
518  }
519 
520  m_lastWorkingDirectory.clear();
521  }
522 
523  if(rmdir(m_processWorkingDirectory.c_str()) != 0)
524  {
525  logTyped(LogEvent::Type::Warning, "Could not remove process working directory '{}' after process exit: {}",
526  m_processWorkingDirectory, strerror(errno)
527  );
528 
530  }
531 
533  }
534 
535  m_pid = -1;
536  m_fdWatcher->removeFD(m_fd);
537  close(m_fd);
538  m_fd = -1;
539 
540  if(m_command == CMD_RESTART || (m_command == CMD_RUN && m_launchNode->respawn()))
541  {
542  if(m_command == CMD_RESTART)
544  else
545  m_restartTimer.setPeriod(m_launchNode->respawnDelay());
546 
547  m_restartCount++;
549  m_restarting = true;
550  }
551 
553 
554  return;
555  }
556 
557  if(bytes < 0)
558  throw error("{}: Could not read: {}", fullName(), strerror(errno));
559 
560  m_stdoutParser.process(buf, bytes);
561 }
562 
563 template<typename... Args>
564 void NodeMonitor::log(const char* format, Args&& ... args)
565 {
566  logMessageSignal({fullName(), fmt::format(format, std::forward<Args>(args)...)});
567 }
568 
569 template<typename... Args>
570 void NodeMonitor::logTyped(LogEvent::Type type, const char* format, Args&& ... args)
571 {
572  logMessageSignal({fullName(), fmt::format(format, std::forward<Args>(args)...), type});
573 }
574 
575 static boost::iterator_range<std::string::const_iterator>
576 corePatternFormatFinder(std::string::const_iterator begin, std::string::const_iterator end)
577 {
578  for(; begin != end && begin+1 != end; ++begin)
579  {
580  if(*begin == '%')
581  return {begin, begin+2};
582  }
583 
584  return {end, end};
585 }
586 
588 {
589  // If systemd-coredump is enabled, our job is easy
590  if(g_coreIsSystemd)
591  {
592  m_debuggerCommand = fmt::format("coredumpctl gdb COREDUMP_PID={}", m_pid);
593  return;
594  }
595 
596  // Otherwise we have to find the core ourselves...
597  char core_pattern[256];
598  int core_fd = open("/proc/sys/kernel/core_pattern", O_RDONLY | O_CLOEXEC);
599  if(core_fd < 0)
600  {
601  logTyped(LogEvent::Type::Error, "could not open /proc/sys/kernel/core_pattern: {}", strerror(errno));
602  return;
603  }
604 
605  int bytes = read(core_fd, core_pattern, sizeof(core_pattern)-1);
606  close(core_fd);
607 
608  if(bytes < 1)
609  {
610  log("Could not read /proc/sys/kernel/core_pattern: {}", strerror(errno));
611  return;
612  }
613 
614  core_pattern[bytes-1] = 0; // Strip off the newline at the end
615 
616  if(core_pattern[0] == '|')
617  {
618  // This may be apport, but apport still writes a "core" file if the
619  // limit is set appropriately.
620  strncpy(core_pattern, "core", sizeof(core_pattern));
621  }
622 
623  auto formatter = [&](boost::iterator_range<std::string::const_iterator> match) -> std::string {
624  char code = *(match.begin()+1);
625 
626  switch(code)
627  {
628  case '%':
629  return "%";
630  case 'p':
631  return std::to_string(m_pid);
632  case 'u':
633  return std::to_string(getuid());
634  case 'g':
635  return std::to_string(getgid());
636  case 's':
637  return std::to_string(signal);
638  case 't':
639  return "*"; // No chance
640  case 'h':
641  {
642  utsname uts;
643  memset(&uts, 0, sizeof(uts));
644  if(uname(&uts) != 0)
645  return "*";
646 
647  return uts.nodename;
648  }
649  case 'e':
650  return m_launchNode->type().substr(0, TASK_COMM_LEN-1);
651  case 'E':
652  {
653  std::string executable = m_launchNode->executable();
654  boost::replace_all(executable, "/", "!");
655  return executable;
656  }
657  case 'c':
658  {
659  rlimit limit;
660  memset(&limit, 0, sizeof(limit));
661  getrlimit(RLIMIT_CORE, &limit);
662 
663  // core limit is set to the maximum above
664  return std::to_string(limit.rlim_max);
665  }
666  default:
667  return "*";
668  }
669  };
670 
671  std::string coreGlob = boost::find_format_all_copy(std::string(core_pattern), corePatternFormatFinder, formatter);
672 
673  // If the pattern is not absolute, it is relative to our node's cwd.
674  if(coreGlob[0] != '/')
675  coreGlob = m_processWorkingDirectory + "/" + coreGlob;
676 
677  log("Determined pattern '{}'", coreGlob);
678 
679  glob_t results;
680  memset(&results, 0, sizeof(results));
681  int ret = glob(coreGlob.c_str(), GLOB_NOSORT, nullptr, &results);
682 
683  if(ret != 0 || results.gl_pathc == 0)
684  {
685  logTyped(LogEvent::Type::Warning, "Could not find a matching core file :-(");
686  globfree(&results);
687  return;
688  }
689 
690  if(results.gl_pathc > 1)
691  {
692  logTyped(LogEvent::Type::Info, "Found multiple matching core files :-(");
693  globfree(&results);
694  return;
695  }
696 
697  std::string coreFile = results.gl_pathv[0];
698  globfree(&results);
699 
700  logTyped(LogEvent::Type::Info, "Found core file '{}'", coreFile);
701 
702  std::stringstream ss;
703 
704  ss << "gdb " << m_launchNode->executable() << " " << coreFile;
705 
706  m_debuggerCommand = ss.str();
707 }
708 
709 std::pair<int,int> NodeMonitor::createPTY()
710 {
711  int master, slave;
712 
713  // NOTE: We are not using forkpty() here, as it is probably not safe in
714  // a multi-threaded process (see
715  // https://www.linuxprogrammingblog.com/threads-and-fork-think-twice-before-using-them)
716 
717  if(openpty(&master, &slave, nullptr, nullptr, nullptr) == -1)
718  throw error("Could not open pseudo terminal for child process: {}", strerror(errno));
719 
720  // On Linux, a new unix98 pty is initialized with the output flag ONLCR set,
721  // which converts \n to \r\n
722  // Disable this bahavior by clearing the flag
723  struct termios termios;
724  if(tcgetattr(slave, &termios) == -1)
725  throw error("Could not get PTY slave attributes: {}", strerror(errno));
726 
727  termios.c_oflag &= ~ONLCR;
728 
729  if(tcsetattr(slave, TCSANOW, &termios) == -1)
730  throw error("Could not set PTY slave attributes: {}", strerror(errno));
731 
732  return {master,slave};
733 }
734 
736 {
737  std::string cmd;
738 
739  if(coredumpAvailable())
740  cmd = m_debuggerCommand;
741  else
742  {
743  std::stringstream ss;
744  ss << "gdb -p " << m_pid;
745  cmd = ss.str();
746  }
747 
748  if(!getenv("DISPLAY"))
749  {
750  logTyped(LogEvent::Type::Info, "No X11 available, run gdb yourself: {}", cmd);
751  }
752  else
753  {
754  char* envTerm = getenv("ROSMON_DEBUGGER_TERMINAL");
755  std::string term = "xterm -e";
756  if(envTerm)
757  term = envTerm;
758  else if(getenv("KONSOLE_DBUS_SESSION"))
759  term = "konsole -e";
760  else if(getenv("VTE_VERSION"))
761  term = "gnome-terminal -e";
762 
763  logTyped(LogEvent::Type::Info, "Launching debugger: '{}'", cmd);
764 
765  // system() is not particularly elegant here, but we trust our cmd.
766  if(system((term + " '" + cmd + "' &").c_str()) != 0)
767  {
768  logTyped(LogEvent::Type::Error, "Could not launch debugger");
769  }
770  }
771 }
772 
773 
775 {
776  m_userTime = 0;
777  m_systemTime = 0;
778  m_memory = 0;
779 }
780 
781 void NodeMonitor::addCPUTime(uint64_t userTime, uint64_t systemTime)
782 {
783  m_userTime += userTime;
784  m_systemTime += systemTime;
785 }
786 
787 void NodeMonitor::addMemory(uint64_t memoryBytes)
788 {
789  m_memory += memoryBytes;
790 }
791 
792 void NodeMonitor::endStatUpdate(double elapsedTimeInTicks)
793 {
794  m_userLoad = m_userTime / elapsedTimeInTicks;
795  m_systemLoad = m_systemTime / elapsedTimeInTicks;
796 }
797 
798 void NodeMonitor::setMuted(bool muted)
799 {
800  m_muted = muted;
801 }
802 
803 }
804 }
void log(const char *format, Args &&... args)
std::shared_ptr< const Node > ConstPtr
Definition: node.h:24
boost::signals2::signal< void(std::string)> exitedSignal
Signalled whenever the process exits.
Definition: node_monitor.h:201
void start()
Start the node.
void forceExit()
Finish shutdown sequence.
void setPeriod(const WallDuration &period, bool reset=true)
void addMemory(uint64_t memoryBytes)
void stop(bool restart=false)
Stop the node.
std::string m_processWorkingDirectory
Definition: node_monitor.h:256
static boost::iterator_range< std::string::const_iterator > corePatternFormatFinder(std::string::const_iterator begin, std::string::const_iterator end)
void addCPUTime(uint64_t userTime, uint64_t systemTime)
State state() const
Get process state.
std::runtime_error error(const char *fmt, const Args &... args)
Definition: fd_watcher.cpp:15
bool running() const
Is the node running?
Idle (e.g. exited with code 0)
Definition: node_monitor.h:36
WallTimer createWallTimer(WallDuration period, void(T::*callback)(const WallTimerEvent &), T *obj, bool oneshot=false, bool autostart=true) const
float f(float t)
Definition: husl.c:142
void gatherCoredump(int signal)
#define ROS_INFO(...)
void logTyped(LogEvent::Type type, const char *format, Args &&... args)
void launchDebugger()
Launch gdb interactively.
void setCallback(const std::function< void(Event &&)> &cb)
Definition: log_parser.cpp:211
Waiting for automatic restart after crash.
Definition: node_monitor.h:39
std::pair< int, int > createPTY()
#define TASK_COMM_LEN
void process(const char *input, std::size_t size, const std::chrono::steady_clock::time_point &time=std::chrono::steady_clock::now())
Definition: log_parser.cpp:205
boost::signals2::signal< void(LogEvent)> logMessageSignal
Logging signal.
Definition: node_monitor.h:198
bool coredumpAvailable() const
Is a core dump available from a crash under rosmon control?
Definition: node_monitor.h:92
std::shared_ptr< const LaunchConfig > ConstPtr
ros::WallTimer m_stopCheckTimer
Definition: node_monitor.h:239
void endStatUpdate(double elapsedTimeInTicks)
std::string fullName() const
Full name including namespace.
Definition: node_monitor.h:175
launch::LaunchConfig::ConstPtr m_launchConfig
Definition: node_monitor.h:226
void shutdown()
Start shutdown sequence.
std::string arg(const std::string &name, const ParseContext &context)
int pid() const
Node PID.
Definition: node_monitor.h:181
launch::Node::ConstPtr m_launchNode
Definition: node_monitor.h:227
Crashed (i.e. exited with code != 0)
Definition: node_monitor.h:38
std::vector< std::string > composeCommand() const
void restart()
Restart the node.
#define ROS_ERROR(...)


rosmon_core
Author(s): Max Schwarz
autogenerated on Fri Jun 16 2023 02:15:06