Refactor dumpsys to expose helper apis
- allow dumpstate to interact with services without executing dumpsys binary
- Remove "NORMAL" priority from section name for backwards compatibility when switching to version 2.0
Bug: 67716082
Test: mmm -j56 frameworks/native/cmds/dumpsys && \
adb sync data && \
adb shell /data/nativetest/dumpsys_test/dumpsys_test && \
adb shell /data/nativetest64/dumpsys_test/dumpsys_test && \
printf "\n\n#### ALL TESTS PASSED ####\n"
Test: manual tests with "adb bugreport"
Change-Id: I4198333a58ffe6cb521b5cb7066520c7a3ef0675
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
index 0862a40..ae0cc01 100644
--- a/cmds/dumpsys/dumpsys.cpp
+++ b/cmds/dumpsys/dumpsys.cpp
@@ -43,9 +43,11 @@
#include "dumpsys.h"
using namespace android;
-using android::base::StringPrintf;
-using android::base::unique_fd;
-using android::base::WriteFully;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::android::base::unique_fd;
+using ::android::base::WriteFully;
+using ::android::base::WriteStringToFd;
static int sort_func(const String16* lhs, const String16* rhs)
{
@@ -96,6 +98,19 @@
return false;
}
+String16 ConvertBitmaskToPriorityType(int bitmask) {
+ if (bitmask == IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL) {
+ return String16(PriorityDumper::PRIORITY_ARG_CRITICAL);
+ }
+ if (bitmask == IServiceManager::DUMP_FLAG_PRIORITY_HIGH) {
+ return String16(PriorityDumper::PRIORITY_ARG_HIGH);
+ }
+ if (bitmask == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL) {
+ return String16(PriorityDumper::PRIORITY_ARG_NORMAL);
+ }
+ return String16("");
+}
+
int Dumpsys::main(int argc, char* const argv[]) {
Vector<String16> services;
Vector<String16> args;
@@ -104,9 +119,9 @@
Vector<String16> protoServices;
bool showListOnly = false;
bool skipServices = false;
- bool filterByProto = false;
+ bool asProto = false;
int timeoutArgMs = 10000;
- int dumpPriorityFlags = IServiceManager::DUMP_FLAG_PRIORITY_ALL;
+ int priorityFlags = IServiceManager::DUMP_FLAG_PRIORITY_ALL;
static struct option longOptions[] = {{"priority", required_argument, 0, 0},
{"proto", no_argument, 0, 0},
{"skip", no_argument, 0, 0},
@@ -131,13 +146,13 @@
if (!strcmp(longOptions[optionIndex].name, "skip")) {
skipServices = true;
} else if (!strcmp(longOptions[optionIndex].name, "proto")) {
- filterByProto = true;
+ asProto = true;
} else if (!strcmp(longOptions[optionIndex].name, "help")) {
usage();
return 0;
} else if (!strcmp(longOptions[optionIndex].name, "priority")) {
priorityType = String16(String8(optarg));
- if (!ConvertPriorityTypeToBitmask(priorityType, dumpPriorityFlags)) {
+ if (!ConvertPriorityTypeToBitmask(priorityType, priorityFlags)) {
fprintf(stderr, "\n");
usage();
return -1;
@@ -198,28 +213,11 @@
}
if (services.empty() || showListOnly) {
- // gets all services
- services = sm_->listServices(dumpPriorityFlags);
- services.sort(sort_func);
- if (filterByProto) {
- protoServices = sm_->listServices(IServiceManager::DUMP_FLAG_PROTO);
- protoServices.sort(sort_func);
- Vector<String16> intersection;
- std::set_intersection(services.begin(), services.end(), protoServices.begin(),
- protoServices.end(), std::back_inserter(intersection));
- services = std::move(intersection);
- args.insertAt(String16(PriorityDumper::PROTO_ARG), 0);
- }
- if (dumpPriorityFlags != IServiceManager::DUMP_FLAG_PRIORITY_ALL) {
- args.insertAt(String16(PriorityDumper::PRIORITY_ARG), 0);
- args.insertAt(priorityType, 1);
- } else {
- args.add(String16("-a"));
- }
+ services = listServices(priorityFlags, asProto);
+ setServiceArgs(args, asProto, priorityFlags);
}
const size_t N = services.size();
-
if (N > 1) {
// first print a list of the current services
aout << "Currently running services:" << endl;
@@ -239,129 +237,204 @@
}
for (size_t i = 0; i < N; i++) {
- const String16& service_name = std::move(services[i]);
- if (IsSkipped(skippedServices, service_name)) continue;
+ const String16& serviceName = services[i];
+ if (IsSkipped(skippedServices, serviceName)) continue;
- sp<IBinder> service = sm_->checkService(service_name);
- if (service != nullptr) {
- int sfd[2];
-
- if (pipe(sfd) != 0) {
- aerr << "Failed to create pipe to dump service info for " << service_name
- << ": " << strerror(errno) << endl;
- continue;
+ if (startDumpThread(serviceName, args) == OK) {
+ bool addSeparator = (N > 1);
+ if (addSeparator) {
+ writeDumpHeader(STDOUT_FILENO, serviceName, priorityFlags);
}
+ std::chrono::duration<double> elapsedDuration;
+ size_t bytesWritten = 0;
+ status_t status =
+ writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(timeoutArgMs),
+ asProto, elapsedDuration, bytesWritten);
- unique_fd local_end(sfd[0]);
- unique_fd remote_end(sfd[1]);
- sfd[0] = sfd[1] = -1;
-
- if (N > 1) {
- aout << "------------------------------------------------------------"
- "-------------------" << endl;
- if (dumpPriorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_ALL) {
- aout << "DUMP OF SERVICE " << service_name << ":" << endl;
- } else {
- aout << "DUMP OF SERVICE " << priorityType << " " << service_name << ":" << endl;
- }
- }
-
- // dump blocks until completion, so spawn a thread..
- std::thread dump_thread([=, remote_end { std::move(remote_end) }]() mutable {
- int err = service->dump(remote_end.get(), args);
-
- // It'd be nice to be able to close the remote end of the socketpair before the dump
- // call returns, to terminate our reads if the other end closes their copy of the
- // file descriptor, but then hangs for some reason. There doesn't seem to be a good
- // way to do this, though.
- remote_end.reset();
-
- if (err != 0) {
- aerr << "Error dumping service info: (" << strerror(err) << ") " << service_name
- << endl;
- }
- });
-
- auto timeout = std::chrono::milliseconds(timeoutArgMs);
- auto start = std::chrono::steady_clock::now();
- auto end = start + timeout;
-
- struct pollfd pfd = {
- .fd = local_end.get(),
- .events = POLLIN
- };
-
- bool timed_out = false;
- bool error = false;
- while (true) {
- // Wrap this in a lambda so that TEMP_FAILURE_RETRY recalculates the timeout.
- auto time_left_ms = [end]() {
- auto now = std::chrono::steady_clock::now();
- auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - now);
- return std::max(diff.count(), 0ll);
- };
-
- int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));
- if (rc < 0) {
- aerr << "Error in poll while dumping service " << service_name << " : "
- << strerror(errno) << endl;
- error = true;
- break;
- } else if (rc == 0) {
- timed_out = true;
- break;
- }
-
- char buf[4096];
- rc = TEMP_FAILURE_RETRY(read(local_end.get(), buf, sizeof(buf)));
- if (rc < 0) {
- aerr << "Failed to read while dumping service " << service_name << ": "
- << strerror(errno) << endl;
- error = true;
- break;
- } else if (rc == 0) {
- // EOF.
- break;
- }
-
- if (!WriteFully(STDOUT_FILENO, buf, rc)) {
- aerr << "Failed to write while dumping service " << service_name << ": "
- << strerror(errno) << endl;
- error = true;
- break;
- }
- }
-
- if (timed_out) {
+ if (status == TIMED_OUT) {
aout << endl
- << "*** SERVICE '" << service_name << "' DUMP TIMEOUT (" << timeoutArgMs
+ << "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
<< "ms) EXPIRED ***" << endl
<< endl;
}
- if (timed_out || error) {
- dump_thread.detach();
- } else {
- dump_thread.join();
+ if (addSeparator) {
+ writeDumpFooter(STDOUT_FILENO, serviceName, elapsedDuration);
}
-
- if (N > 1) {
- std::chrono::duration<double> elapsed_seconds =
- std::chrono::steady_clock::now() - start;
- aout << StringPrintf("--------- %.3fs ", elapsed_seconds.count()).c_str()
- << "was the duration of dumpsys " << service_name;
-
- using std::chrono::system_clock;
- const auto finish = system_clock::to_time_t(system_clock::now());
- std::tm finish_tm;
- localtime_r(&finish, &finish_tm);
- aout << ", ending at: " << std::put_time(&finish_tm, "%Y-%m-%d %H:%M:%S")
- << endl;
- }
- } else {
- aerr << "Can't find service: " << service_name << endl;
+ bool dumpComplete = (status == OK);
+ stopDumpThread(dumpComplete);
}
}
return 0;
}
+
+Vector<String16> Dumpsys::listServices(int priorityFilterFlags, bool filterByProto) const {
+ Vector<String16> services = sm_->listServices(priorityFilterFlags);
+ services.sort(sort_func);
+ if (filterByProto) {
+ Vector<String16> protoServices = sm_->listServices(IServiceManager::DUMP_FLAG_PROTO);
+ protoServices.sort(sort_func);
+ Vector<String16> intersection;
+ std::set_intersection(services.begin(), services.end(), protoServices.begin(),
+ protoServices.end(), std::back_inserter(intersection));
+ services = std::move(intersection);
+ }
+ return services;
+}
+
+void Dumpsys::setServiceArgs(Vector<String16>& args, bool asProto, int priorityFlags) const {
+ if ((priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_ALL) ||
+ (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL)) {
+ args.add(String16("-a"));
+ }
+ if (asProto) {
+ args.insertAt(String16(PriorityDumper::PROTO_ARG), 0);
+ }
+ if (priorityFlags != IServiceManager::DUMP_FLAG_PRIORITY_ALL) {
+ String16 priorityType = ConvertBitmaskToPriorityType(priorityFlags);
+ args.insertAt(String16(PriorityDumper::PRIORITY_ARG), 0);
+ args.insertAt(priorityType, 1);
+ }
+}
+
+status_t Dumpsys::startDumpThread(const String16& serviceName, const Vector<String16>& args) {
+ sp<IBinder> service = sm_->checkService(serviceName);
+ if (service == nullptr) {
+ aerr << "Can't find service: " << serviceName << endl;
+ return NAME_NOT_FOUND;
+ }
+
+ int sfd[2];
+ if (pipe(sfd) != 0) {
+ aerr << "Failed to create pipe to dump service info for " << serviceName << ": "
+ << strerror(errno) << endl;
+ return -errno;
+ }
+
+ redirectFd_ = unique_fd(sfd[0]);
+ unique_fd remote_end(sfd[1]);
+ sfd[0] = sfd[1] = -1;
+
+ // dump blocks until completion, so spawn a thread..
+ activeThread_ = std::thread([=, remote_end{std::move(remote_end)}]() mutable {
+ int err = service->dump(remote_end.get(), args);
+
+ // It'd be nice to be able to close the remote end of the socketpair before the dump
+ // call returns, to terminate our reads if the other end closes their copy of the
+ // file descriptor, but then hangs for some reason. There doesn't seem to be a good
+ // way to do this, though.
+ remote_end.reset();
+
+ if (err != 0) {
+ aerr << "Error dumping service info: (" << strerror(err) << ") "
+ << serviceName << endl;
+ }
+ });
+ return OK;
+}
+
+void Dumpsys::stopDumpThread(bool dumpComplete) {
+ if (dumpComplete) {
+ activeThread_.join();
+ } else {
+ activeThread_.detach();
+ }
+ /* close read end of the dump output redirection pipe */
+ redirectFd_.reset();
+}
+
+void Dumpsys::writeDumpHeader(int fd, const String16& serviceName, int priorityFlags) const {
+ std::string msg(
+ "----------------------------------------"
+ "---------------------------------------\n");
+ if (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_ALL ||
+ priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL) {
+ StringAppendF(&msg, "DUMP OF SERVICE %s:\n", String8(serviceName).c_str());
+ } else {
+ String16 priorityType = ConvertBitmaskToPriorityType(priorityFlags);
+ StringAppendF(&msg, "DUMP OF SERVICE %s %s:\n", String8(priorityType).c_str(),
+ String8(serviceName).c_str());
+ }
+ WriteStringToFd(msg, fd);
+}
+
+status_t Dumpsys::writeDump(int fd, const String16& serviceName, std::chrono::milliseconds timeout,
+ bool asProto, std::chrono::duration<double>& elapsedDuration,
+ size_t& bytesWritten) const {
+ status_t status = OK;
+ size_t totalBytes = 0;
+ auto start = std::chrono::steady_clock::now();
+ auto end = start + timeout;
+
+ int serviceDumpFd = redirectFd_.get();
+ if (serviceDumpFd == -1) {
+ return INVALID_OPERATION;
+ }
+
+ struct pollfd pfd = {.fd = serviceDumpFd, .events = POLLIN};
+
+ while (true) {
+ // Wrap this in a lambda so that TEMP_FAILURE_RETRY recalculates the timeout.
+ auto time_left_ms = [end]() {
+ auto now = std::chrono::steady_clock::now();
+ auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - now);
+ return std::max(diff.count(), 0ll);
+ };
+
+ int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));
+ if (rc < 0) {
+ aerr << "Error in poll while dumping service " << serviceName << " : "
+ << strerror(errno) << endl;
+ status = -errno;
+ break;
+ } else if (rc == 0) {
+ status = TIMED_OUT;
+ break;
+ }
+
+ char buf[4096];
+ rc = TEMP_FAILURE_RETRY(read(redirectFd_.get(), buf, sizeof(buf)));
+ if (rc < 0) {
+ aerr << "Failed to read while dumping service " << serviceName << ": "
+ << strerror(errno) << endl;
+ status = -errno;
+ break;
+ } else if (rc == 0) {
+ // EOF.
+ break;
+ }
+
+ if (!WriteFully(fd, buf, rc)) {
+ aerr << "Failed to write while dumping service " << serviceName << ": "
+ << strerror(errno) << endl;
+ status = -errno;
+ break;
+ }
+ totalBytes += rc;
+ }
+
+ if ((status == TIMED_OUT) && (!asProto)) {
+ std::string msg = StringPrintf("\n*** SERVICE '%s' DUMP TIMEOUT (%llums) EXPIRED ***\n\n",
+ String8(serviceName).string(), timeout.count());
+ WriteStringToFd(msg, fd);
+ }
+
+ elapsedDuration = std::chrono::steady_clock::now() - start;
+ bytesWritten = totalBytes;
+ return status;
+}
+
+void Dumpsys::writeDumpFooter(int fd, const String16& serviceName,
+ const std::chrono::duration<double>& elapsedDuration) const {
+ using std::chrono::system_clock;
+ const auto finish = system_clock::to_time_t(system_clock::now());
+ std::tm finish_tm;
+ localtime_r(&finish, &finish_tm);
+ std::stringstream oss;
+ oss << std::put_time(&finish_tm, "%Y-%m-%d %H:%M:%S");
+ std::string msg =
+ StringPrintf("--------- %.3fs was the duration of dumpsys %s, ending at: %s\n",
+ elapsedDuration.count(), String8(serviceName).string(), oss.str().c_str());
+ WriteStringToFd(msg, fd);
+}
diff --git a/cmds/dumpsys/dumpsys.h b/cmds/dumpsys/dumpsys.h
index 2534dde..1d78aa4 100644
--- a/cmds/dumpsys/dumpsys.h
+++ b/cmds/dumpsys/dumpsys.h
@@ -17,6 +17,9 @@
#ifndef FRAMEWORK_NATIVE_CMD_DUMPSYS_H_
#define FRAMEWORK_NATIVE_CMD_DUMPSYS_H_
+#include <thread>
+
+#include <android-base/unique_fd.h>
#include <binder/IServiceManager.h>
namespace android {
@@ -25,10 +28,97 @@
public:
Dumpsys(android::IServiceManager* sm) : sm_(sm) {
}
+ /**
+ * Main entry point into dumpsys.
+ */
int main(int argc, char* const argv[]);
+ /**
+ * Returns a list of services.
+ * @param priorityFlags filter services by specified priorities
+ * @param supportsProto filter services that support proto dumps
+ * @return list of services
+ */
+ Vector<String16> listServices(int priorityFlags, bool supportsProto) const;
+
+ /**
+ * Modifies @{code args} to add additional arguments to indicate if the service
+ * must dump as proto or dump to a certian priority bucket.
+ * @param args initial list of arguments to pass to service dump method.
+ * @param asProto dump service as proto by passing an additional --proto arg
+ * @param priorityFlags indicates priority of dump by passing additional priority args
+ * to the service
+ */
+ void setServiceArgs(Vector<String16>& args, bool asProto, int priorityFlags) const;
+
+ /**
+ * 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 callto {@code
+ * stopDumpThread}.
+ * @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(const String16& serviceName, const Vector<String16>& args);
+
+ /**
+ * Writes a section header to a file descriptor.
+ * @param fd file descriptor to write data
+ * @param serviceName
+ * @param priorityFlags dump priority specified
+ */
+ void writeDumpHeader(int fd, const String16& serviceName, int priorityFlags) const;
+
+ /**
+ * Redirects service dump to a file descriptor. This requires
+ * {@code startDumpThread} to be called successfully otherwise the function will
+ * return {@code INVALID_OPERATION}.
+ * @param fd file descriptor to write data
+ * @param serviceName
+ * @param timeout timeout to terminate the dump if not completed
+ * @param asProto used to supresses additional output to the fd such as timeout
+ * error messages
+ * @param elapsedDuration returns elapsed time in seconds
+ * @param bytesWritten returns number of bytes written
+ * @return {@code OK} if successful
+ * {@code TIMED_OUT} dump timed out
+ * {@code INVALID_OPERATION} invalid state
+ * {@code != OK} error
+ */
+ status_t writeDump(int fd, const String16& serviceName, std::chrono::milliseconds timeout,
+ bool asProto, std::chrono::duration<double>& elapsedDuration,
+ size_t& bytesWritten) const;
+
+ /**
+ * Writes a section footer to a file descriptor with duration info.
+ * @param fd file descriptor to write data
+ * @param serviceName
+ * @param elapsedDuration duration of dump
+ */
+ void writeDumpFooter(int fd, const String16& serviceName,
+ const std::chrono::duration<double>& elapsedDuration) const;
+
+ /**
+ * Terminates dump thread.
+ * @param dumpComplete If {@code true}, indicates the dump was successfully completed and
+ * tries to join the thread. Otherwise thread is detached.
+ */
+ void stopDumpThread(bool dumpComplete);
+
+ /**
+ * Returns file descriptor of the pipe used to dump service data. This assumes
+ * {@code startDumpThread} was called successfully.
+ */
+ int getDumpFd() const {
+ return redirectFd_.get();
+ }
+
private:
android::IServiceManager* sm_;
+ std::thread activeThread_;
+ mutable android::base::unique_fd redirectFd_;
};
}
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index bdb0a9a..b13f59d 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -188,6 +188,22 @@
EXPECT_THAT(status, Eq(0));
}
+ void CallSingleService(const String16& serviceName, Vector<String16>& args, int priorityFlags,
+ bool supportsProto, std::chrono::duration<double>& elapsedDuration,
+ size_t& bytesWritten) {
+ CaptureStdout();
+ CaptureStderr();
+ dump_.setServiceArgs(args, supportsProto, priorityFlags);
+ status_t status = dump_.startDumpThread(serviceName, args);
+ EXPECT_THAT(status, Eq(0));
+ status = dump_.writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(500), false,
+ elapsedDuration, bytesWritten);
+ EXPECT_THAT(status, Eq(0));
+ dump_.stopDumpThread(/* dumpCompleted = */ true);
+ stdout_ = GetCapturedStdout();
+ stderr_ = GetCapturedStderr();
+ }
+
void AssertRunningServices(const std::vector<std::string>& services) {
std::string expected;
if (services.size() > 1) {
@@ -209,6 +225,7 @@
void AssertDumped(const std::string& service, const std::string& dump) {
EXPECT_THAT(stdout_, HasSubstr("DUMP OF SERVICE " + service + ":\n" + dump));
+ EXPECT_THAT(stdout_, HasSubstr("was the duration of dumpsys " + service + ", ending at: "));
}
void AssertDumpedWithPriority(const std::string& service, const std::string& dump,
@@ -216,6 +233,7 @@
std::string priority = String8(priorityType).c_str();
EXPECT_THAT(stdout_,
HasSubstr("DUMP OF SERVICE " + priority + " " + service + ":\n" + dump));
+ EXPECT_THAT(stdout_, HasSubstr("was the duration of dumpsys " + service + ", ending at: "));
}
void AssertNotDumped(const std::string& dump) {
@@ -425,8 +443,8 @@
CallMain({"--priority", "NORMAL"});
AssertRunningServices({"runningnormal1", "runningnormal2"});
- AssertDumpedWithPriority("runningnormal1", "dump1", PriorityDumper::PRIORITY_ARG_NORMAL);
- AssertDumpedWithPriority("runningnormal2", "dump2", PriorityDumper::PRIORITY_ARG_NORMAL);
+ AssertDumped("runningnormal1", "dump1");
+ AssertDumped("runningnormal2", "dump2");
}
// Tests 'dumpsys --proto'
@@ -461,3 +479,29 @@
AssertDumpedWithPriority("runninghigh1", "dump1", PriorityDumper::PRIORITY_ARG_HIGH);
AssertDumpedWithPriority("runninghigh2", "dump2", PriorityDumper::PRIORITY_ARG_HIGH);
}
+
+TEST_F(DumpsysTest, GetBytesWritten) {
+ const char* serviceName = "service2";
+ const char* dumpContents = "dump1";
+ ExpectDump(serviceName, dumpContents);
+
+ String16 service(serviceName);
+ Vector<String16> args;
+ std::chrono::duration<double> elapsedDuration;
+ size_t bytesWritten;
+
+ CallSingleService(service, args, IServiceManager::DUMP_FLAG_PRIORITY_ALL,
+ /* as_proto = */ false, elapsedDuration, bytesWritten);
+
+ AssertOutput(dumpContents);
+ EXPECT_THAT(bytesWritten, Eq(strlen(dumpContents)));
+}
+
+TEST_F(DumpsysTest, WriteDumpWithoutThreadStart) {
+ std::chrono::duration<double> elapsedDuration;
+ size_t bytesWritten;
+ status_t status =
+ dump_.writeDump(STDOUT_FILENO, String16("service"), std::chrono::milliseconds(500),
+ /* as_proto = */ false, elapsedDuration, bytesWritten);
+ EXPECT_THAT(status, Eq(INVALID_OPERATION));
+}
\ No newline at end of file