Merge "Add a timeout to aos_dump"
diff --git a/aos/network/timestamp_filter.cc b/aos/network/timestamp_filter.cc
index 8d84adf..d5f1139 100644
--- a/aos/network/timestamp_filter.cc
+++ b/aos/network/timestamp_filter.cc
@@ -1207,7 +1207,7 @@
   bool removed = false;
   while (true) {
     CHECK_LT(pop_filter_, filters_.size());
-    BootFilter *boot_filter = &filters_[pop_filter_];
+    BootFilter *boot_filter = filters_[pop_filter_].get();
     CHECK(boot_filter != nullptr);
     size_t timestamps_size = 0;
     while ((timestamps_size = boot_filter->filter.timestamps_size()) > 2) {
@@ -1229,13 +1229,13 @@
 
       // There is 1 more filter, see if there is enough data in it to switch
       // over to it.
-      if (filters_[pop_filter_ + 1].filter.timestamps_size() < 2u) {
+      if (filters_[pop_filter_ + 1]->filter.timestamps_size() < 2u) {
         return removed;
       }
       if (time <
           BootTimestamp{.boot = static_cast<size_t>(boot_filter->boot.first),
                         .time = std::get<0>(
-                            filters_[pop_filter_ + 1].filter.timestamp(1))}) {
+                            filters_[pop_filter_ + 1]->filter.timestamp(1))}) {
         return removed;
       }
     }
diff --git a/aos/network/timestamp_filter.h b/aos/network/timestamp_filter.h
index 2ed98da..88031a1 100644
--- a/aos/network/timestamp_filter.h
+++ b/aos/network/timestamp_filter.h
@@ -301,8 +301,8 @@
 
   size_t timestamps_size() const {
     size_t result = 0u;
-    for (const BootFilter &filter : filters_) {
-      result += filter.filter.timestamps_size();
+    for (const std::unique_ptr<BootFilter> &filter : filters_) {
+      result += filter->filter.timestamps_size();
     }
     return result;
   }
@@ -320,10 +320,10 @@
 
   // For testing only:
   void Debug() const {
-    for (const BootFilter &filter : filters_) {
-      LOG(INFO) << NodeNames() << " boota: " << filter.boot.first << ", "
-                << filter.boot.second;
-      filter.filter.Debug();
+    for (const std::unique_ptr<BootFilter> &filter : filters_) {
+      LOG(INFO) << NodeNames() << " boota: " << filter->boot.first << ", "
+                << filter->boot.second;
+      filter->filter.Debug();
     }
   }
 
@@ -340,18 +340,18 @@
 
   // Returns true if there is a full line which hasn't been observed.
   bool has_unobserved_line() const {
-    return filters_.back().filter.has_unobserved_line();
+    return filters_.back()->filter.has_unobserved_line();
   }
   // Returns the time of the second point in the unobserved line, or min_time if
   // there is no line.
   logger::BootTimestamp unobserved_line_end() const {
-    auto &f = filters_.back();
+    auto &f = *filters_.back();
     return {static_cast<size_t>(f.boot.first), f.filter.unobserved_line_end()};
   }
   // Returns the time of the second point in the unobserved line on the remote
   // node, or min_time if there is no line.
   logger::BootTimestamp unobserved_line_remote_end() const {
-    auto &f = filters_.back();
+    auto &f = *filters_.back();
     return {static_cast<size_t>(f.boot.second),
             f.filter.unobserved_line_remote_end()};
   }
@@ -367,7 +367,7 @@
 
     size_t current_filter = std::max(static_cast<ssize_t>(0), current_filter_);
     while (true) {
-      const BootFilter &filter = filters_[current_filter];
+      const BootFilter &filter = *filters_[current_filter];
       std::optional<
           std::tuple<monotonic_clock::time_point, std::chrono::nanoseconds>>
           result = filter.filter.Observe();
@@ -400,7 +400,7 @@
           std::tuple<monotonic_clock::time_point, std::chrono::nanoseconds>>
           result =
               current_filter_ < 0 ? std::nullopt
-                                  : filters_[current_filter_].filter.Consume();
+                                  : filters_[current_filter_]->filter.Consume();
       if (!result) {
         if (static_cast<size_t>(current_filter_ + 1) == filters_.size()) {
           return std::nullopt;
@@ -409,7 +409,7 @@
           continue;
         }
       }
-      BootFilter &filter = filters_[current_filter_];
+      BootFilter &filter = *filters_[current_filter_];
       return std::make_tuple(
           logger::BootTimestamp{static_cast<size_t>(filter.boot.first),
                                 std::get<0>(*result)},
@@ -643,12 +643,12 @@
   };
 
   static bool FilterLessThanUpper(const std::pair<int, int> &l,
-                                  const BootFilter &r) {
-    return l < r.boot;
+                                  const std::unique_ptr<BootFilter> &r) {
+    return l < r->boot;
   }
-  static bool FilterLessThanLower(const BootFilter &l,
+  static bool FilterLessThanLower(const std::unique_ptr<BootFilter> &l,
                                   const std::pair<int, int> &r) {
-    return l.boot < r;
+    return l->boot < r;
   }
 
  protected:
@@ -656,21 +656,23 @@
     auto it =
         std::lower_bound(filters_.begin(), filters_.end(),
                          std::make_pair(boota, bootb), FilterLessThanLower);
-    if (it != filters_.end() && it->boot == std::make_pair(boota, bootb)) {
-      return &it->filter;
+    if (it != filters_.end() && (*it)->boot == std::make_pair(boota, bootb)) {
+      return &(*it)->filter;
     }
 
     if (!filters_.empty() && current_filter_ >= 0) {
       CHECK_LT(static_cast<size_t>(current_filter_), filters_.size());
-      CHECK_GE(boota, filters_[current_filter_].boot.first);
-      CHECK_GE(bootb, filters_[current_filter_].boot.second) << NodeNames();
+      CHECK_GE(boota, filters_[current_filter_]->boot.first);
+      CHECK_GE(bootb, filters_[current_filter_]->boot.second) << NodeNames();
     }
     SingleFilter *result =
         &filters_
              .emplace(std::upper_bound(filters_.begin(), filters_.end(),
                                        std::make_pair(boota, bootb),
                                        FilterLessThanUpper),
-                      std::make_pair(boota, bootb), NodeNames())
+                      std::make_unique<BootFilter>(std::make_pair(boota, bootb),
+                                                   NodeNames()))
+             ->get()
              ->filter;
 
     {
@@ -679,14 +681,14 @@
       // means that both boots on both devices talked to both other boots.
       int last_boota = -1;
       int last_bootb = -1;
-      for (const BootFilter &filter : filters_) {
-        CHECK(filter.boot.first != last_boota ||
-              filter.boot.second != last_bootb)
+      for (const std::unique_ptr<BootFilter> &filter : filters_) {
+        CHECK(filter->boot.first != last_boota ||
+              filter->boot.second != last_bootb)
             << ": Boots didn't increase.";
-        CHECK_GE(filter.boot.first, last_boota);
-        CHECK_GE(filter.boot.second, last_bootb);
-        last_boota = filter.boot.first;
-        last_bootb = filter.boot.second;
+        CHECK_GE(filter->boot.first, last_boota);
+        CHECK_GE(filter->boot.second, last_bootb);
+        last_boota = filter->boot.first;
+        last_bootb = filter->boot.second;
       }
     }
     return result;
@@ -699,8 +701,8 @@
     if (it == filters_.end()) {
       return nullptr;
     }
-    if (it->boot == std::make_pair(boota, bootb)) {
-      return &it->filter;
+    if (it->get()->boot == std::make_pair(boota, bootb)) {
+      return &it->get()->filter;
     } else {
       return nullptr;
     }
@@ -714,7 +716,7 @@
   }
 
  private:
-  std::vector<BootFilter> filters_;
+  std::vector<std::unique_ptr<BootFilter>> filters_;
 
   ssize_t current_filter_ = -1;
 
diff --git a/aos/starter/starter_cmd.cc b/aos/starter/starter_cmd.cc
index b4d80d8..a076a9d 100644
--- a/aos/starter/starter_cmd.cc
+++ b/aos/starter/starter_cmd.cc
@@ -1,3 +1,4 @@
+#include <algorithm>
 #include <chrono>
 #include <functional>
 #include <iostream>
@@ -25,6 +26,9 @@
             "autocomplete script.");
 DEFINE_string(_bash_autocomplete_word, "",
               "Internal use: Current word being autocompleted");
+DEFINE_string(sort, "name",
+              "The name of the column to sort processes by.  "
+              "Can be \"name\", \"state\", \"pid\", or \"uptime\".");
 
 namespace {
 
@@ -85,6 +89,105 @@
                "Uptime");
 }
 
+std::vector<const aos::starter::ApplicationStatus *> SortApplications(
+    const aos::FlatbufferVector<aos::starter::Status> &status) {
+  std::vector<const aos::starter::ApplicationStatus *> sorted_statuses;
+  for (const aos::starter::ApplicationStatus *app_status :
+       *status.message().statuses()) {
+    sorted_statuses.push_back(app_status);
+  }
+  // If --sort flag not set, then return this unsorted vector as is.
+  if (FLAGS_sort.empty()) {
+    return sorted_statuses;
+  }
+
+  // Convert --sort flag to lowercase for testing below.
+  std::transform(FLAGS_sort.begin(), FLAGS_sort.end(), FLAGS_sort.begin(),
+                 tolower);
+
+  // This function is called once for each node being reported upon, so there is
+  // no need to sort on node, it happens implicitly.
+
+  if (FLAGS_sort == "name") {
+    // Sort on name using std::string_view::operator< for lexicographic order.
+    std::sort(sorted_statuses.begin(), sorted_statuses.end(),
+              [](const aos::starter::ApplicationStatus *lhs,
+                 const aos::starter::ApplicationStatus *rhs) {
+                return lhs->name()->string_view() < rhs->name()->string_view();
+              });
+  } else if (FLAGS_sort == "state") {
+    // Sort on state first, and then name for apps in same state.
+    // ApplicationStatus::state is an enum, so need to call EnumNameState()
+    // convenience wrapper to convert enum to char*, and then wrap in
+    // std::string_view for lexicographic ordering.
+    std::sort(sorted_statuses.begin(), sorted_statuses.end(),
+              [](const aos::starter::ApplicationStatus *lhs,
+                 const aos::starter::ApplicationStatus *rhs) {
+                return (lhs->state() != rhs->state())
+                           ? (std::string_view(
+                                  aos::starter::EnumNameState(lhs->state())) <
+                              std::string_view(
+                                  aos::starter::EnumNameState(rhs->state())))
+                           : (lhs->name()->string_view() <
+                              rhs->name()->string_view());
+              });
+  } else if (FLAGS_sort == "pid") {
+    // Sort on pid first, and then name for when both apps are not running.
+    // If the app state is STOPPED, then it will not have a pid, so need to test
+    // that first. If only one app is STOPPED, then return Boolean state to put
+    // running apps before stopped.
+    std::sort(sorted_statuses.begin(), sorted_statuses.end(),
+              [](const aos::starter::ApplicationStatus *lhs,
+                 const aos::starter::ApplicationStatus *rhs) {
+                if (lhs->state() == aos::starter::State::STOPPED) {
+                  if (rhs->state() == aos::starter::State::STOPPED) {
+                    return lhs->name()->string_view() <
+                           rhs->name()->string_view();
+                  } else {
+                    return false;
+                  }
+                } else {
+                  if (rhs->state() == aos::starter::State::STOPPED) {
+                    return true;
+                  } else {
+                    return lhs->pid() < rhs->pid();
+                  }
+                }
+              });
+  } else if (FLAGS_sort == "uptime") {
+    // Sort on last_start_time first, and then name for when both apps are not
+    // running, or have exact same start time. Only use last_start_time when app
+    // is not STOPPED. If only one app is STOPPED, then return Boolean state to
+    // put running apps before stopped.
+    std::sort(
+        sorted_statuses.begin(), sorted_statuses.end(),
+        [](const aos::starter::ApplicationStatus *lhs,
+           const aos::starter::ApplicationStatus *rhs) {
+          if (lhs->state() == aos::starter::State::STOPPED) {
+            if (rhs->state() == aos::starter::State::STOPPED) {
+              return lhs->name()->string_view() < rhs->name()->string_view();
+            } else {
+              return false;
+            }
+          } else {
+            if (rhs->state() == aos::starter::State::STOPPED) {
+              return true;
+            } else {
+              return (lhs->last_start_time() == rhs->last_start_time())
+                         ? (lhs->name()->string_view() <
+                            rhs->name()->string_view())
+                         : (lhs->last_start_time() < rhs->last_start_time());
+            }
+          }
+        });
+  } else {
+    std::cerr << "Unknown sort criteria \"" << FLAGS_sort << "\"" << std::endl;
+    exit(1);
+  }
+
+  return sorted_statuses;
+}
+
 void PrintApplicationStatus(const aos::starter::ApplicationStatus *app_status,
                             const aos::monotonic_clock::time_point &time,
                             const aos::Node *node) {
@@ -116,8 +219,9 @@
       const aos::FlatbufferVector<aos::starter::Status> &status =
           optional_status->second;
       const aos::monotonic_clock::time_point time = optional_status->first;
+      const auto &sorted_statuses = SortApplications(status);
       for (const aos::starter::ApplicationStatus *app_status :
-           *status.message().statuses()) {
+           sorted_statuses) {
         PrintApplicationStatus(app_status, time, node);
       }
     } else {