Merge "Remove autos for the at home challanges"
diff --git a/aos/starter/starter_cmd.cc b/aos/starter/starter_cmd.cc
index 5f67c4b..8257e3b 100644
--- a/aos/starter/starter_cmd.cc
+++ b/aos/starter/starter_cmd.cc
@@ -23,6 +23,32 @@
                         {"stop", aos::starter::Command::STOP},
                         {"restart", aos::starter::Command::RESTART}};
 
+const aos::Node *MaybeMyNode(const aos::Configuration *configuration) {
+  if (!configuration->has_nodes()) {
+    return nullptr;
+  }
+
+  return aos::configuration::GetMyNode(configuration);
+}
+
+bool ValidApplication(const aos::Configuration *config,
+                      std::string_view application_name) {
+  const aos::Node *node = MaybeMyNode(config);
+  const aos::Application *application =
+      aos::configuration::GetApplication(config, node, application_name);
+  if (application == nullptr) {
+    if (node) {
+      std::cout << "Unknown application '" << application_name << "' on node '"
+                << node->name()->string_view() << "'" << std::endl;
+    } else {
+      std::cout << "Unknown application '" << application_name << "'"
+                << std::endl;
+    }
+    return false;
+  }
+  return true;
+}
+
 void PrintKey() {
   absl::PrintF("%-30s %-8s %-6s %-9s\n", "Name", "State", "PID", "Uptime");
 }
@@ -43,8 +69,8 @@
   }
 }
 
-bool GetStarterStatus(int argc, char **argv, const aos::Configuration *config) {
-  if (argc == 1) {
+// Prints the status for all applications.
+void GetAllStarterStatus(const aos::Configuration *config) {
     // Print status for all processes.
     const auto optional_status = aos::starter::GetStarterStatus(config);
     if (optional_status) {
@@ -58,10 +84,25 @@
     } else {
       LOG(WARNING) << "No status found";
     }
+}
+
+// Handles the "status" command.  Returns true if the help message should be
+// printed.
+bool GetStarterStatus(int argc, char **argv, const aos::Configuration *config) {
+  if (argc == 1) {
+    GetAllStarterStatus(config);
   } else if (argc == 2) {
     // Print status for the specified process.
     const auto application_name =
         aos::starter::FindApplication(argv[1], config);
+    if (application_name == "all") {
+      GetAllStarterStatus(config);
+      return false;
+    }
+
+    if (!ValidApplication(config, application_name)) {
+      return false;
+    }
     auto status = aos::starter::GetStatus(application_name, config);
     PrintKey();
     PrintApplicationStatus(&status.message(), aos::monotonic_clock::now());
@@ -72,12 +113,63 @@
   return false;
 }
 
+// Sends the provided command to all applications.  Prints the success text on
+// success, and failure text on failure.
+void InteractWithAll(const aos::Configuration *config,
+                     const aos::starter::Command command,
+                     std::string_view success_text,
+                     std::string_view failure_text) {
+  const auto optional_status = aos::starter::GetStarterStatus(config);
+  if (optional_status) {
+    auto status = *optional_status;
+    const aos::Node *my_node = MaybeMyNode(config);
+    std::vector<std::pair<aos::starter::Command, std::string_view>> commands;
+
+    for (const aos::Application *application : *config->applications()) {
+      // Ignore any applications which aren't supposed to be started on this
+      // node.
+      if (!aos::configuration::ApplicationShouldStart(config, my_node,
+                                                      application)) {
+        continue;
+      }
+
+      const std::string_view application_name =
+          application->name()->string_view();
+      if (!application->autostart()) {
+        const aos::starter::ApplicationStatus *application_status =
+            aos::starter::FindApplicationStatus(status.message(),
+                                                application_name);
+        if (application_status->state() == aos::starter::State::STOPPED) {
+          std::cout << "Skipping " << application_name
+                    << " because it is STOPPED\n";
+          continue;
+        }
+      }
+
+      commands.emplace_back(command, application_name);
+    }
+
+    // Restart each running process
+    if (aos::starter::SendCommandBlocking(commands, config,
+                                          chrono::seconds(5))) {
+      std::cout << success_text << "all \n";
+    } else {
+      std::cout << failure_text << "all \n";
+    }
+  } else {
+    LOG(WARNING) << "Starter not running";
+  }
+}
+
+// Handles the "start", "stop", and "restart" commands.  Returns true if the
+// help message should be printed.
 bool InteractWithProgram(int argc, char **argv,
                          const aos::Configuration *config) {
   const char *command_string = argv[0];
   if (argc != 2) {
-    LOG(ERROR) << "The \"" << command_string
-               << "\" command requires an application name as an argument.";
+    LOG(ERROR)
+        << "The \"" << command_string
+        << "\" command requires an application name or 'all' as an argument.";
     return true;
   }
 
@@ -85,59 +177,44 @@
   CHECK(command_search != kCommandConversions.end())
       << "Internal error: \"" << command_string
       << "\" is not in kCommandConversions.";
-
   const aos::starter::Command command = command_search->second;
-  const auto application_name = aos::starter::FindApplication(argv[1], config);
+
+  std::string_view success_text;
+  const std::string failure_text =
+      std::string("Failed to ") + std::string(command_string) + " ";
+  switch (command) {
+    case aos::starter::Command::START:
+      success_text = "Successfully started ";
+      break;
+    case aos::starter::Command::STOP:
+      success_text = "Successfully stopped ";
+      break;
+    case aos::starter::Command::RESTART:
+      success_text = "Successfully restarted ";
+      break;
+  }
+
+  const std::string_view application_name =
+      aos::starter::FindApplication(argv[1], config);
+  if (application_name == "all") {
+    InteractWithAll(config, command, success_text, failure_text);
+    return false;
+  }
+  if (!ValidApplication(config, application_name)) {
+    return false;
+  }
+
   if (aos::starter::SendCommandBlocking(command, application_name, config,
                                         chrono::seconds(5))) {
-    switch (command) {
-      case aos::starter::Command::START:
-        std::cout << "Successfully started " << application_name << '\n';
-        break;
-      case aos::starter::Command::STOP:
-        std::cout << "Successfully stopped " << application_name << '\n';
-        break;
-      case aos::starter::Command::RESTART:
-        std::cout << "Successfully restarted " << application_name << '\n';
-        break;
-    }
+    std::cout << success_text << application_name << '\n';
   } else {
-    std::cout << "Failed to " << command_string << ' ' << application_name
-              << '\n';
+    std::cout << failure_text << application_name << '\n';
   }
   return false;
 }
 
-bool RestartAll(int argc, char **, const aos::Configuration *config) {
-  if (argc == 1) {
-    const auto optional_status = aos::starter::GetStarterStatus(config);
-    if (optional_status) {
-      auto status = *optional_status;
-      for (const aos::starter::ApplicationStatus *app_status :
-           *status.message().statuses()) {
-        const auto application_name = aos::starter::FindApplication(
-            app_status->name()->string_view(), config);
-
-        // Restart each running process
-
-        if (aos::starter::SendCommandBlocking(aos::starter::Command::RESTART,
-                                              application_name, config,
-                                              chrono::seconds(5))) {
-          std::cout << "Successfully restarted " << application_name << '\n';
-        } else {
-          std::cout << "Failed to restart " << application_name << '\n';
-          return true;
-        }
-      }
-    } else {
-      LOG(WARNING) << "No processes found";
-    }
-  } else {
-    LOG(ERROR) << "The \"restart_all\" command requires only zero arguments.";
-    return true;
-  }
-  return false;
-}
+bool Help(int /*argc*/, char ** /*argv*/,
+          const aos::Configuration * /*config*/);
 
 // This is the set of subcommands we support. Each subcommand accepts argc and
 // argv from its own point of view. So argv[0] is always the name of the
@@ -145,14 +222,34 @@
 // The subcommand returns true if there was an error parsing the command line
 // arguments. It returns false when the command line arguments are parsed
 // successfully.
-static const std::unordered_map<
-    std::string, std::function<bool(int argc, char **argv,
-                                    const aos::Configuration *config)>>
-    kCommands{{"status", GetStarterStatus},
-              {"start", InteractWithProgram},
-              {"stop", InteractWithProgram},
-              {"restart", InteractWithProgram},
-              {"restart_all", RestartAll}};
+static const std::vector<
+    std::tuple<std::string,
+               std::function<bool(int argc, char **argv,
+                                  const aos::Configuration *config)>,
+               std::string_view>>
+    kCommands{
+        {"help", Help, ""},
+        {"status", GetStarterStatus,
+         " [application], Returns the status of the provided application, "
+         "or all applications by default"},
+        {"start", InteractWithProgram,
+         " application, Starts the provided application, "
+         "or all applications if all is provided"},
+        {"stop", InteractWithProgram,
+         " application, Stops the provided application, "
+         "or all applications if all is provided"},
+        {"restart", InteractWithProgram,
+         " application, Restarts the provided application, "
+         "or all applications if all is provided"}};
+
+bool Help(int /*argc*/, char ** /*argv*/,
+          const aos::Configuration * /*config*/) {
+  std::cout << "Valid commands are:" << std::endl;
+  for (auto entry : kCommands) {
+    std::cout << " - " << std::get<0>(entry) << std::get<2>(entry) << std::endl;
+  }
+  return false;
+}
 
 }  // namespace
 
@@ -168,19 +265,23 @@
     parsing_failed = true;
   } else {
     const char *command = argv[1];
-    auto it = kCommands.find(command);
+    auto it = std::find_if(
+        kCommands.begin(), kCommands.end(),
+        [command](const std::tuple<
+                  std::string,
+                  std::function<bool(int argc, char **argv,
+                                     const aos::Configuration *config)>,
+                  std::string_view> &t) { return std::get<0>(t) == command; });
+
     if (it == kCommands.end()) {
       parsing_failed = true;
     } else {
-      parsing_failed = it->second(argc - 1, argv + 1, &config.message());
+      parsing_failed = std::get<1>(*it)(argc - 1, argv + 1, &config.message());
     }
   }
 
   if (parsing_failed) {
-    LOG(ERROR) << "Parsing failed. Valid commands are:";
-    for (auto entry : kCommands) {
-      LOG(ERROR) << " - " << entry.first;
-    }
+    Help(argc - 1, argv + 1, &config.message());
     return 1;
   }
 
diff --git a/aos/starter/starterd_lib.cc b/aos/starter/starterd_lib.cc
index 9066a78..19051bb 100644
--- a/aos/starter/starterd_lib.cc
+++ b/aos/starter/starterd_lib.cc
@@ -15,7 +15,8 @@
 namespace starter {
 
 Application::Application(const aos::Application *application,
-                         aos::ShmEventLoop *event_loop)
+                         aos::ShmEventLoop *event_loop,
+                         std::function<void()> on_change)
     : name_(application->name()->string_view()),
       path_(application->has_executable_name()
                 ? application->executable_name()->string_view()
@@ -38,7 +39,8 @@
           LOG(WARNING) << "Failed to stop, sending SIGKILL to '" << name_
                        << "' pid: " << pid_;
         }
-      })) {}
+      })),
+      on_change_(on_change) {}
 
 void Application::DoStart() {
   if (status_ != aos::starter::State::WAITING) {
@@ -69,6 +71,7 @@
       start_timer_->Setup(event_loop_->monotonic_now() +
                           std::chrono::seconds(1));
     }
+    on_change_();
     return;
   }
 
@@ -134,6 +137,7 @@
       stop_timer_->Setup(event_loop_->monotonic_now() +
                          std::chrono::seconds(1));
       queue_restart_ = restart;
+      on_change_();
       break;
     }
     case aos::starter::State::WAITING: {
@@ -144,6 +148,7 @@
         DoStart();
       } else {
         status_ = aos::starter::State::STOPPED;
+        on_change_();
       }
       break;
     }
@@ -171,6 +176,7 @@
   restart_timer_->Setup(event_loop_->monotonic_now() + std::chrono::seconds(3));
   start_timer_->Disable();
   stop_timer_->Disable();
+  on_change_();
 }
 
 void Application::set_args(
@@ -310,6 +316,7 @@
       // Disable force stop timer since the process already died
       stop_timer_->Disable();
 
+      on_change_();
       if (terminating_) {
         return true;
       }
@@ -403,15 +410,21 @@
     : config_msg_(event_loop_config),
       event_loop_(event_loop_config),
       status_sender_(event_loop_.MakeSender<aos::starter::Status>("/aos")),
-      status_timer_(event_loop_.AddTimer([this] { SendStatus(); })),
+      status_timer_(event_loop_.AddTimer([this] {
+        SendStatus();
+        status_count_ = 0;
+      })),
       cleanup_timer_(event_loop_.AddTimer([this] { event_loop_.Exit(); })),
+      max_status_count_(
+          event_loop_.GetChannel<aos::starter::Status>("/aos")->frequency() -
+          1),
       listener_(&event_loop_,
                 [this](signalfd_siginfo signal) { OnSignal(signal); }) {
   event_loop_.SkipAosLog();
 
   event_loop_.OnRun([this] {
     status_timer_->Setup(event_loop_.monotonic_now(),
-                         std::chrono::milliseconds(500));
+                         std::chrono::milliseconds(1000));
   });
 
   event_loop_.MakeWatcher("/aos", [this](const aos::starter::StarterRpc &cmd) {
@@ -452,6 +465,15 @@
   }
 }
 
+void Starter::MaybeSendStatus() {
+  if (status_count_ < max_status_count_) {
+    SendStatus();
+    ++status_count_;
+  } else {
+    VLOG(1) << "That's enough " << status_count_ << " " << max_status_count_;
+  }
+}
+
 void Starter::Cleanup() {
   if (exiting_) {
     return;
@@ -491,8 +513,9 @@
 }
 
 Application *Starter::AddApplication(const aos::Application *application) {
-  auto [iter, success] = applications_.try_emplace(application->name()->str(),
-                                                   application, &event_loop_);
+  auto [iter, success] =
+      applications_.try_emplace(application->name()->str(), application,
+                                &event_loop_, [this]() { MaybeSendStatus(); });
   if (success) {
     if (application->has_args()) {
       iter->second.set_args(*application->args());
diff --git a/aos/starter/starterd_lib.h b/aos/starter/starterd_lib.h
index 2bbfa22..b5888e0 100644
--- a/aos/starter/starterd_lib.h
+++ b/aos/starter/starterd_lib.h
@@ -68,7 +68,7 @@
 class Application {
  public:
   Application(const aos::Application *application,
-              aos::ShmEventLoop *event_loop);
+              aos::ShmEventLoop *event_loop, std::function<void()> on_change);
 
   flatbuffers::Offset<aos::starter::ApplicationStatus> PopulateStatus(
       flatbuffers::FlatBufferBuilder *builder);
@@ -137,6 +137,8 @@
   aos::ShmEventLoop *event_loop_;
   aos::TimerHandler *start_timer_, *restart_timer_, *stop_timer_;
 
+  std::function<void()> on_change_;
+
   DISALLOW_COPY_AND_ASSIGN(Application);
 };
 
@@ -179,6 +181,9 @@
 
   void OnSignal(signalfd_siginfo signal);
 
+  // Sends the Status message if it wouldn't exceed the rate limit.
+  void MaybeSendStatus();
+
   void SendStatus();
 
   const std::string config_path_;
@@ -189,6 +194,9 @@
   aos::TimerHandler *status_timer_;
   aos::TimerHandler *cleanup_timer_;
 
+  int status_count_ = 0;
+  const int max_status_count_;
+
   std::unordered_map<std::string, Application> applications_;
 
   // Set to true on cleanup to block rpc commands and ensure cleanup only