main.cpp
Go to the documentation of this file.
1 // rosmon - versatile ROS node launcher & monitor
2 // Author: Max Schwarz <max.schwarz@uni-bonn.de>
3 
4 #include <ros/init.h>
5 #include <ros/master.h>
6 #include <ros/rate.h>
7 #include <ros/node_handle.h>
8 #include <ros/package.h>
9 #include <ros/console_backend.h>
10 #include <ros/this_node.h>
11 
12 #include <boost/filesystem.hpp>
13 #include <boost/lexical_cast.hpp>
14 
15 #include <unistd.h>
16 #include <getopt.h>
17 #include <csignal>
18 
19 #include <iostream>
20 
21 #include <fmt/format.h>
22 
23 #include "launch/launch_config.h"
24 #include "launch/bytes_parser.h"
25 #include "monitor/monitor.h"
26 #include "ui.h"
27 #include "ros_interface.h"
28 #include "package_registry.h"
29 #include "fd_watcher.h"
30 #include "logger.h"
31 
32 namespace fs = boost::filesystem;
33 
34 bool g_shouldStop = false;
35 bool g_flushStdout = false;
36 
37 static fs::path findFile(const fs::path& base, const std::string& name)
38 {
39  for(fs::directory_iterator it(base); it != fs::directory_iterator(); ++it)
40  {
41  if(fs::is_directory(*it))
42  {
43  fs::path p = findFile(*it, name);
44  if(!p.empty())
45  return p;
46  }
47  else if(it->path().leaf() == name)
48  return *it;
49  }
50 
51  return fs::path();
52 }
53 
54 void usage()
55 {
56  fprintf(stderr,
57  "Usage:\n"
58  " rosmon [actions] [options] some_package test.launch [arg1:=value1 ...]\n"
59  " rosmon [actions] [options] path/to/test.launch [arg1:=value1 ...]\n"
60  "\n"
61  "Actions (default is to launch the launch file):\n"
62  " --benchmark Exit after loading the launch file\n"
63  " --list-args List launch file arguments\n"
64  "\n"
65  "Options:\n"
66  " --disable-ui Disable fancy terminal UI\n"
67  " --flush-log Flush logfile after writing an entry\n"
68  " --flush-stdout Flush stdout after writing an entry\n"
69  " --help This help screen\n"
70  " --log=FILE Write log file to FILE\n"
71  " --name=NAME Use NAME as ROS node name. By default, an anonymous\n"
72  " name is chosen.\n"
73  " --no-start Don't automatically start the nodes in the beginning\n"
74  " --stop-timeout=SECONDS\n"
75  " Kill a process if it is still running this long\n"
76  " after the initial signal is send.\n"
77  " --disable-diagnostics\n"
78  " Disable publication of ros diagnostics message about\n"
79  " monitored nodes\n"
80  " --diagnostics-prefix=PREFIX\n"
81  " Prefix for the ros diagnostics generated by this node.\n"
82  " By default this will be the node name.\n"
83  " --cpu-limit=[0-n]\n"
84  " Default CPU Usage limit of monitored process. n is the\n"
85  " number of CPU cores. This is the sum of system and user\n"
86  " CPU usage.\n"
87  " --memory-limit=15MB\n"
88  " Default memory limit usage of monitored process.\n"
89  "\n"
90  "rosmon also obeys some environment variables:\n"
91  " ROSMON_COLOR_MODE Can be set to 'truecolor', '256colors', 'ansi'\n"
92  " to force a specific color mode\n"
93  " If unset, rosmon tries to detect the best\n"
94  " available color mode.\n"
95  );
96 }
97 
98 void handleSignal(int)
99 {
100  g_shouldStop = true;
101 }
102 
103 void logToStdout(const std::string& channel, const std::string& str)
104 {
105  std::string clean = str;
106  unsigned int len = clean.length();
107  while(len != 0 && (clean[len-1] == '\n' || clean[len-1] == '\r'))
108  len--;
109 
110  clean.resize(len);
111 
112  fmt::print("{:>20}: {}\n", channel, clean);
113 
114  if(g_flushStdout)
115  fflush(stdout);
116 }
117 
118 // Options
119 static const struct option OPTIONS[] = {
120  {"disable-ui", no_argument, nullptr, 'd'},
121  {"benchmark", no_argument, nullptr, 'b'},
122  {"flush-log", no_argument, nullptr, 'f'},
123  {"flush-stdout", no_argument, nullptr, 'F'},
124  {"help", no_argument, nullptr, 'h'},
125  {"list-args", no_argument, nullptr, 'L'},
126  {"log", required_argument, nullptr, 'l'},
127  {"name", required_argument, nullptr, 'n'},
128  {"no-start", no_argument, nullptr, 'S'},
129  {"stop-timeout", required_argument, nullptr, 's'},
130  {"disable-diagnostics", no_argument, nullptr, 'D'},
131  {"cpu-limit", required_argument, nullptr, 'c'},
132  {"memory-limit", required_argument, nullptr, 'm'},
133  {"diagnostics-prefix", required_argument, nullptr, 'p'},
134  {nullptr, 0, nullptr, 0}
135 };
136 
137 enum Action {
141 };
142 
143 int main(int argc, char** argv)
144 {
145  std::string name;
146  std::string logFile;
147  std::string launchFilePath;
148 
149  Action action = ACTION_LAUNCH;
150  bool enableUI = true;
151  bool flushLog = false;
152  bool startNodes = true;
156  bool disableDiagnostics = false;
157  std::string diagnosticsPrefix;
158 
159  // Parse options
160  while(true)
161  {
162  int option_index;
163  int c = getopt_long(argc, argv, "h", OPTIONS, &option_index);
164 
165  if(c == -1)
166  break;
167 
168  switch(c)
169  {
170  case 'h':
171  usage();
172  return 0;
173  case 'n':
174  name = optarg;
175  break;
176  case 'l':
177  logFile = optarg;
178  break;
179  case 'L':
180  action = ACTION_LIST_ARGS;
181  break;
182  case 'b':
183  action = ACTION_BENCHMARK;
184  break;
185  case 'd':
186  enableUI = false;
187  break;
188  case 'f':
189  flushLog = true;
190  break;
191  case 'F':
192  g_flushStdout = true;
193  break;
194  case 'S':
195  startNodes = false;
196  break;
197  case 's':
198  try
199  {
200  stopTimeout = boost::lexical_cast<double>(optarg);
201  }
202  catch(boost::bad_lexical_cast&)
203  {
204  fmt::print(stderr, "Bad value for --stop-timeout argument: '{}'\n", optarg);
205  return 1;
206  }
207 
208  if(stopTimeout < 0)
209  {
210  fmt::print(stderr, "Stop timeout cannot be negative\n");
211  return 1;
212  }
213  break;
214  case 'D' :
215  disableDiagnostics = true;
216  break;
217  case 'c':
218  try
219  {
220  cpuLimit = boost::lexical_cast<float>(optarg);
221  }
222  catch(boost::bad_lexical_cast&)
223  {
224  fmt::print(stderr, "Bad value for --cpu-limit argument: '{}'\n", optarg);
225  return 1;
226  }
227 
228  if(cpuLimit < 0)
229  {
230  fmt::print(stderr, "CPU Limit cannot be negative\n");
231  return 1;
232  }
233  break;
234  case 'm':
235  {
236  bool ok;
237  std::tie(memoryLimit, ok) = rosmon::launch::parseMemory(optarg);
238  if(!ok)
239  {
240  fmt::print(stderr, "Bad value for --memory-limit argument: '{}'\n", optarg);
241  return 1;
242  }
243  break;
244  }
245  case 'p':
246  fmt::print(stderr, "Prefix : {}", optarg);
247  diagnosticsPrefix = std::string(optarg);
248  break;
249  }
250  }
251 
252  // Parse the positional arguments
253  if(optind == argc)
254  {
255  usage();
256  return 1;
257  }
258 
259  // Find first launch file argument (must contain ':=')
260  int firstArg = optind + 1;
261  for(; firstArg < argc; ++firstArg)
262  {
263  if(strstr(argv[firstArg], ":="))
264  break;
265  }
266 
267  // From the position of the argument (or the end-of-options), we know
268  // if we were called with a) package + filename or b) just a path.
269 
270  if(firstArg - optind == 1)
271  {
272  launchFilePath = argv[optind];
273  }
274  else if(firstArg - optind == 2)
275  {
276  const char* packageName = argv[optind];
277  const char* fileName = argv[optind + 1];
278 
279  std::string package = rosmon::PackageRegistry::getPath(packageName);
280  if(package.empty())
281  {
282  fmt::print(stderr, "Could not find path of package '{}'\n", packageName);
283  return 1;
284  }
285 
286  fs::path path = findFile(package, fileName);
287  if(path.empty())
288  {
289  fmt::print(stderr, "Could not find launch file '{}' in package '{}'\n",
290  fileName, packageName
291  );
292  return 1;
293  }
294 
295  launchFilePath = path.string();
296  }
297  else
298  {
299  usage();
300  return 1;
301  }
302 
303  // Setup logging
304  boost::scoped_ptr<rosmon::Logger> logger;
305  {
306  // Setup a sane ROSCONSOLE_FORMAT if the user did not already
307  setenv("ROSCONSOLE_FORMAT", "[${function}]: ${message}", 0);
308 
309  // Disable direct logging to stdout
311 
312  // Open logger
313  if(logFile.empty())
314  {
315  // Log to /tmp by default
316 
317  time_t t = time(nullptr);
318  tm currentTime;
319  memset(&currentTime, 0, sizeof(currentTime));
320  localtime_r(&t, &currentTime);
321 
322  char buf[256];
323  strftime(buf, sizeof(buf), "/tmp/rosmon_%Y_%m_%d_%H_%M_%S.log", &currentTime);
324 
325  logFile = buf;
326  }
327 
328  logger.reset(new rosmon::Logger(logFile, flushLog));
329  }
330 
332 
334  config->setDefaultStopTimeout(stopTimeout);
335  config->setDefaultCPULimit(cpuLimit);
336  config->setDefaultMemoryLimit(memoryLimit);
337 
338  // Parse launch file arguments from command line
339  for(int i = firstArg; i < argc; ++i)
340  {
341  char* arg = strstr(argv[i], ":=");
342 
343  if(!arg)
344  {
345  fmt::print(stderr, "You specified a non-argument after an argument\n");
346  return 1;
347  }
348 
349  char* name = argv[i];
350 
351  *arg = 0;
352 
353  char* value = arg + 2;
354 
355  config->setArgument(name, value);
356  }
357 
358  bool onlyArguments = (action == ACTION_LIST_ARGS);
359 
360  try
361  {
362  config->parse(launchFilePath, onlyArguments);
363  config->evaluateParameters();
364  }
366  {
367  fmt::print(stderr, "Could not load launch file: {}\n", e.what());
368  return 1;
369  }
370 
371  switch(action)
372  {
373  case ACTION_BENCHMARK:
374  return 0;
375  case ACTION_LIST_ARGS:
376  for(const auto& arg : config->arguments())
377  std::cout << arg.first << std::endl;
378 
379  return 0;
380  case ACTION_LAUNCH:
381  break;
382  }
383 
384  // Initialize the ROS node.
385  {
386  uint32_t init_options = ros::init_options::NoSigintHandler;
387 
388  // If the user did not specify a node name on the command line, look in
389  // the launch file
390  if(name.empty())
391  name = config->rosmonNodeName();
392 
393  // As last fallback, choose anonymous name.
394  if(name.empty())
395  {
396  name = "rosmon";
397  init_options |= ros::init_options::AnonymousName;
398  }
399 
400  // prevent ros::init from parsing the launch file arguments as remappings
401  int dummyArgc = 1;
402  ros::init(dummyArgc, argv, name, init_options);
403  }
404 
405  // Check connectivity to ROS master
406  {
407  fmt::print("ROS_MASTER_URI: '{}'\n", ros::master::getURI());
408  if(ros::master::check())
409  {
410  fmt::print("roscore is already running.\n");
411  }
412  else
413  {
414  fmt::print("roscore is not runnning.\n");
415  fmt::print("Waiting until it is up (abort with CTRL+C)...\n");
416 
417  while(!ros::master::check())
418  ros::WallDuration(0.5).sleep();
419 
420  fmt::print("roscore is running now.\n");
421  }
422  }
423 
424  ros::NodeHandle nh;
425 
426  fmt::print("Running as '{}'\n", ros::this_node::getName());
427 
428  rosmon::monitor::Monitor monitor(config, watcher);
429  monitor.logMessageSignal.connect(boost::bind(&rosmon::Logger::log, logger.get(), _1, _2));
430 
431  fmt::print("\n\n");
432  monitor.setParameters();
433 
434  if(config->nodes().empty())
435  {
436  fmt::print("No ROS nodes to be launched. Finished...\n");
437  return 0;
438  }
439 
440  // Should we automatically start the nodes?
441  if(startNodes)
442  monitor.start();
443 
444  // Start the ncurses UI
445  boost::scoped_ptr<rosmon::UI> ui;
446  if(enableUI)
447  {
448  ui.reset(new rosmon::UI(&monitor, watcher));
449  }
450  else
451  {
452  monitor.logMessageSignal.connect(logToStdout);
453  }
454 
455  // ROS interface
456  rosmon::ROSInterface rosInterface(&monitor, !disableDiagnostics, diagnosticsPrefix);
457 
458  ros::WallDuration waitDuration(0.1);
459 
460  // On SIGINT, SIGTERM, SIGHUP we stop gracefully.
461  signal(SIGINT, handleSignal);
462  signal(SIGHUP, handleSignal);
463  signal(SIGTERM, handleSignal);
464 
465  // Main loop
466  while(ros::ok() && monitor.ok() && !g_shouldStop)
467  {
468  ros::spinOnce();
469  watcher->wait(waitDuration);
470 
471  if(ui)
472  ui->update();
473  }
474 
475  if(ui)
476  ui->log("[rosmon]", "Shutting down...");
477  monitor.shutdown();
478 
479  // Wait for graceful shutdown
481  while(!monitor.allShutdown() && ros::WallTime::now() - start < ros::WallDuration(monitor.shutdownTimeout()))
482  {
483  watcher->wait(waitDuration);
484 
485  if(ui)
486  ui->update();
487  }
488 
489  // If we timed out, force exit (kill the nodes with SIGKILL)
490  if(!monitor.allShutdown())
491  monitor.forceExit();
492 
493  rosInterface.shutdown();
494 
495  // Wait until that is finished (should always work)
496  while(!monitor.allShutdown())
497  {
498  watcher->wait(waitDuration);
499 
500  if(enableUI)
501  ui->update();
502  }
503 
504  // If coredumps are available, be helpful and display gdb commands
505  bool coredumpsAvailable = std::any_of(monitor.nodes().begin(), monitor.nodes().end(),
506  [](const rosmon::monitor::NodeMonitor::Ptr& n) { return n->coredumpAvailable(); }
507  );
508 
509  if(ui && coredumpsAvailable)
510  {
511  ui->log("[rosmon]", "\n");
512  ui->log("[rosmon]", "If you want to debug one of the crashed nodes, you can use the following commands");
513  for(auto& node : monitor.nodes())
514  {
515  if(node->coredumpAvailable())
516  {
517  ui->log(
518  "[rosmon]",
519  fmt::format("{:20}: # {}", node->name(), node->debuggerCommand())
520  );
521  }
522  }
523  }
524 
525  return 0;
526 }
virtual const char * what() const noexcept
Definition: launch_config.h:38
Definition: ui.h:19
const std::vector< NodeMonitor::Ptr > & nodes() const
Definition: monitor.h:40
ROSCPP_DECL const std::string & getURI()
ROSCPP_DECL bool check()
void(* function_print)(void *,::ros::console::Level, const char *, const char *, const char *, int)
ROSCPP_DECL void start()
string package
int main(int argc, char **argv)
Definition: main.cpp:143
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
void usage()
Definition: main.cpp:54
ROSCPP_DECL const std::string & getName()
ROSCPP_DECL std::string clean(const std::string &name)
std::shared_ptr< LaunchConfig > Ptr
boost::signals2::signal< void(std::string, std::string)> logMessageSignal
Definition: monitor.h:48
static fs::path findFile(const fs::path &base, const std::string &name)
Definition: main.cpp:37
static constexpr float DEFAULT_CPU_LIMIT
void logToStdout(const std::string &channel, const std::string &str)
Definition: main.cpp:103
Write log messages into a log file.
Definition: logger.h:15
static constexpr uint64_t DEFAULT_MEMORY_LIMIT
bool sleep() const
void log(const std::string &source, const std::string &msg)
Log message.
Definition: logger.cpp:37
ROSCPP_DECL bool ok()
bool g_flushStdout
Definition: main.cpp:35
bool ok() const
Definition: monitor.h:37
void handleSignal(int)
Definition: main.cpp:98
Action
Definition: main.cpp:137
static constexpr float DEFAULT_STOP_TIMEOUT
static const struct option OPTIONS[]
Definition: main.cpp:119
bool g_shouldStop
Definition: main.cpp:34
static WallTime now()
std::string arg(const std::string &name, const ParseContext &context)
std::tuple< uint64_t, bool > parseMemory(const std::string &memory)
ROSCPP_DECL void spinOnce()
std::shared_ptr< NodeMonitor > Ptr
Definition: node_monitor.h:27


rosmon_core
Author(s): Max Schwarz
autogenerated on Wed Jul 10 2019 03:10:12