dumpsys: dump multiple types of info in one run

I was looking at which processes were using too many threads, since
almost all services in dumpsys report 0/16 threads out of use, and this
looks like a lot of (potentially wasted threads). However, almost all of
these come from system_server, so they are actually shared among all of
the services. Though, from the existing output of dumpsys, it was a bit
hard to actually understand this without comparing two outputs.

This still isn't quite like lshal (which has columns), but at least now
I can run `dumpsys --thread --pid` and I can see all the services and
see their processes to make this analysis easier.

Bug: N/A
Test: dumpsys_test
Change-Id: I88a6c80f4c9aed82b3f5a050b29c13ba86284562
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 8f466ca..1db2867 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1236,7 +1236,7 @@
         std::string path(title);
         path.append(" - ").append(String8(service).c_str());
         size_t bytes_written = 0;
-        status_t status = dumpsys.startDumpThread(Dumpsys::Type::DUMP, service, args);
+        status_t status = dumpsys.startDumpThread(Dumpsys::TYPE_DUMP, service, args);
         if (status == OK) {
             dumpsys.writeDumpHeader(STDOUT_FILENO, service, priority);
             std::chrono::duration<double> elapsed_seconds;
@@ -1315,7 +1315,7 @@
             path.append("_HIGH");
         }
         path.append(kProtoExt);
-        status_t status = dumpsys.startDumpThread(Dumpsys::Type::DUMP, service, args);
+        status_t status = dumpsys.startDumpThread(Dumpsys::TYPE_DUMP, service, args);
         if (status == OK) {
             status = ds.AddZipEntryFromFd(path, dumpsys.getDumpFd(), service_timeout);
             bool dumpTerminated = (status == OK);
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
index 83a52b8..f8fdaa0 100644
--- a/cmds/dumpsys/dumpsys.cpp
+++ b/cmds/dumpsys/dumpsys.cpp
@@ -62,13 +62,14 @@
             "usage: dumpsys\n"
             "         To dump all services.\n"
             "or:\n"
-            "       dumpsys [-t TIMEOUT] [--priority LEVEL] [--pid] [--thread] [--help | -l | "
-            "--skip SERVICES "
+            "       dumpsys [-t TIMEOUT] [--priority LEVEL] [--dump] [--pid] [--thread] [--help | "
+            "-l | --skip SERVICES "
             "| SERVICE [ARGS]]\n"
             "         --help: shows this help\n"
             "         -l: only list services, do not dump them\n"
             "         -t TIMEOUT_SEC: TIMEOUT to use in seconds instead of default 10 seconds\n"
             "         -T TIMEOUT_MS: TIMEOUT to use in milliseconds instead of default 10 seconds\n"
+            "         --dump: ask the service to dump itself (this is the default)\n"
             "         --pid: dump PID instead of usual dump\n"
             "         --proto: filter services that support dumping data in proto format. Dumps\n"
             "               will be in proto format.\n"
@@ -127,10 +128,11 @@
     bool showListOnly = false;
     bool skipServices = false;
     bool asProto = false;
-    Type type = Type::DUMP;
+    int dumpTypeFlags = 0;
     int timeoutArgMs = 10000;
     int priorityFlags = IServiceManager::DUMP_FLAG_PRIORITY_ALL;
     static struct option longOptions[] = {{"help", no_argument, 0, 0},
+                                          {"dump", no_argument, 0, 0},
                                           {"pid", no_argument, 0, 0},
                                           {"priority", required_argument, 0, 0},
                                           {"proto", no_argument, 0, 0},
@@ -168,12 +170,14 @@
                     usage();
                     return -1;
                 }
+            } else if (!strcmp(longOptions[optionIndex].name, "dump")) {
+                dumpTypeFlags |= TYPE_DUMP;
             } else if (!strcmp(longOptions[optionIndex].name, "pid")) {
-                type = Type::PID;
+                dumpTypeFlags |= TYPE_PID;
             } else if (!strcmp(longOptions[optionIndex].name, "stability")) {
-                type = Type::STABILITY;
+                dumpTypeFlags |= TYPE_STABILITY;
             } else if (!strcmp(longOptions[optionIndex].name, "thread")) {
-                type = Type::THREAD;
+                dumpTypeFlags |= TYPE_THREAD;
             }
             break;
 
@@ -211,6 +215,10 @@
         }
     }
 
+    if (dumpTypeFlags == 0) {
+        dumpTypeFlags = TYPE_DUMP;
+    }
+
     for (int i = optind; i < argc; i++) {
         if (skipServices) {
             skippedServices.add(String16(argv[i]));
@@ -263,7 +271,7 @@
         const String16& serviceName = services[i];
         if (IsSkipped(skippedServices, serviceName)) continue;
 
-        if (startDumpThread(type, serviceName, args) == OK) {
+        if (startDumpThread(dumpTypeFlags, serviceName, args) == OK) {
             bool addSeparator = (N > 1);
             if (addSeparator) {
                 writeDumpHeader(STDOUT_FILENO, serviceName, priorityFlags);
@@ -330,18 +338,21 @@
     }
 }
 
-static status_t dumpPidToFd(const sp<IBinder>& service, const unique_fd& fd) {
+static status_t dumpPidToFd(const sp<IBinder>& service, const unique_fd& fd, bool exclusive) {
      pid_t pid;
      status_t status = service->getDebugPid(&pid);
      if (status != OK) {
          return status;
      }
+     if (!exclusive) {
+        WriteStringToFd("Service host process PID: ", fd.get());
+     }
      WriteStringToFd(std::to_string(pid) + "\n", fd.get());
      return OK;
 }
 
 static status_t dumpStabilityToFd(const sp<IBinder>& service, const unique_fd& fd) {
-     WriteStringToFd(internal::Stability::debugToString(service) + "\n", fd);
+     WriteStringToFd("Stability: " + internal::Stability::debugToString(service) + "\n", fd);
      return OK;
 }
 
@@ -362,7 +373,14 @@
     return OK;
 }
 
-status_t Dumpsys::startDumpThread(Type type, const String16& serviceName,
+static void reportDumpError(const String16& serviceName, status_t error, const char* context) {
+    if (error == OK) return;
+
+    std::cerr << "Error with service '" << serviceName << "' while " << context << ": "
+              << statusToString(error) << std::endl;
+}
+
+status_t Dumpsys::startDumpThread(int dumpTypeFlags, const String16& serviceName,
                                   const Vector<String16>& args) {
     sp<IBinder> service = sm_->checkService(serviceName);
     if (service == nullptr) {
@@ -383,29 +401,23 @@
 
     // dump blocks until completion, so spawn a thread..
     activeThread_ = std::thread([=, remote_end{std::move(remote_end)}]() mutable {
-        status_t err = 0;
-
-        switch (type) {
-        case Type::DUMP:
-            err = service->dump(remote_end.get(), args);
-            break;
-        case Type::PID:
-            err = dumpPidToFd(service, remote_end);
-            break;
-        case Type::STABILITY:
-            err = dumpStabilityToFd(service, remote_end);
-            break;
-        case Type::THREAD:
-            err = dumpThreadsToFd(service, remote_end);
-            break;
-        default:
-            std::cerr << "Unknown dump type" << static_cast<int>(type) << std::endl;
-            return;
+        if (dumpTypeFlags & TYPE_PID) {
+            status_t err = dumpPidToFd(service, remote_end, dumpTypeFlags == TYPE_PID);
+            reportDumpError(serviceName, err, "dumping PID");
+        }
+        if (dumpTypeFlags & TYPE_STABILITY) {
+            status_t err = dumpStabilityToFd(service, remote_end);
+            reportDumpError(serviceName, err, "dumping stability");
+        }
+        if (dumpTypeFlags & TYPE_THREAD) {
+            status_t err = dumpThreadsToFd(service, remote_end);
+            reportDumpError(serviceName, err, "dumping thread info");
         }
 
-        if (err != OK) {
-            std::cerr << "Error dumping service info status_t: " << statusToString(err) << " "
-                 << serviceName << std::endl;
+        // other types always act as a header, this is usually longer
+        if (dumpTypeFlags & TYPE_DUMP) {
+            status_t err = service->dump(remote_end.get(), args);
+            reportDumpError(serviceName, err, "dumping");
         }
     });
     return OK;
diff --git a/cmds/dumpsys/dumpsys.h b/cmds/dumpsys/dumpsys.h
index 1b3ae6a..05c5d5e 100644
--- a/cmds/dumpsys/dumpsys.h
+++ b/cmds/dumpsys/dumpsys.h
@@ -51,24 +51,25 @@
      */
     static void setServiceArgs(Vector<String16>& args, bool asProto, int priorityFlags);
 
-    enum class Type {
-        DUMP,      // dump using `dump` function
-        PID,       // dump pid of server only
-        STABILITY, // dump stability information of server
-        THREAD,    // dump thread usage of server only
+    enum Type {
+        TYPE_DUMP = 0x1,      // dump using `dump` function
+        TYPE_PID = 0x2,       // dump pid of server only
+        TYPE_STABILITY = 0x4, // dump stability information of server
+        TYPE_THREAD = 0x8,    // dump thread usage of server only
     };
 
     /**
      * Starts a thread to connect to a service and get its dump output. The thread redirects
      * the output to a pipe. Thread must be stopped by a subsequent call to {@code
      * stopDumpThread}.
+     * @param dumpTypeFlags operations to perform
      * @param serviceName
      * @param args list of arguments to pass to service dump method.
      * @return {@code OK} thread is started successfully.
      *         {@code NAME_NOT_FOUND} service could not be found.
      *         {@code != OK} error
      */
-    status_t startDumpThread(Type type, const String16& serviceName,
+    status_t startDumpThread(int dumpTypeFlags, const String16& serviceName,
                              const Vector<String16>& args);
 
     /**
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index 277f445..312f4d7 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -200,7 +200,7 @@
         CaptureStdout();
         CaptureStderr();
         dump_.setServiceArgs(args, supportsProto, priorityFlags);
-        status_t status = dump_.startDumpThread(Dumpsys::Type::DUMP, serviceName, args);
+        status_t status = dump_.startDumpThread(Dumpsys::TYPE_DUMP, serviceName, args);
         EXPECT_THAT(status, Eq(0));
         status = dump_.writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(500), false,
                                  elapsedDuration, bytesWritten);
@@ -627,6 +627,28 @@
     AssertOutputFormat(format);
 }
 
+// Tests 'dumpsys --thread --stability'
+TEST_F(DumpsysTest, ListAllServicesWithMultipleOptions) {
+    ExpectListServices({"Locksmith", "Valet"});
+    ExpectCheckService("Locksmith");
+    ExpectCheckService("Valet");
+
+    CallMain({"--pid", "--stability"});
+    AssertRunningServices({"Locksmith", "Valet"});
+
+    AssertOutputContains(std::to_string(getpid()));
+    AssertOutputContains("stability");
+}
+
+// Tests 'dumpsys --pid --stability service_name'
+TEST_F(DumpsysTest, ListServiceWithMultipleOptions) {
+    ExpectCheckService("Locksmith");
+    CallMain({"--pid", "--stability", "Locksmith"});
+
+    AssertOutputContains(std::to_string(getpid()));
+    AssertOutputContains("stability");
+}
+
 TEST_F(DumpsysTest, GetBytesWritten) {
     const char* serviceName = "service2";
     const char* dumpContents = "dump1";