Loading cmds/dumpsys/dumpsys.cpp +208 −135 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -96,6 +98,19 @@ static bool ConvertPriorityTypeToBitmask(const String16& type, int& bitmask) { 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; Loading @@ -104,9 +119,9 @@ int Dumpsys::main(int argc, char* const argv[]) { 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}, Loading @@ -131,13 +146,13 @@ int Dumpsys::main(int argc, char* const argv[]) { 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; Loading Loading @@ -198,28 +213,11 @@ int Dumpsys::main(int argc, char* const argv[]) { } 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; Loading @@ -239,35 +237,87 @@ int Dumpsys::main(int argc, char* const argv[]) { } 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 (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); if (pipe(sfd) != 0) { aerr << "Failed to create pipe to dump service info for " << service_name << ": " << strerror(errno) << endl; continue; if (status == TIMED_OUT) { aout << endl << "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs << "ms) EXPIRED ***" << endl << endl; } unique_fd local_end(sfd[0]); unique_fd remote_end(sfd[1]); sfd[0] = sfd[1] = -1; if (addSeparator) { writeDumpFooter(STDOUT_FILENO, serviceName, elapsedDuration); } bool dumpComplete = (status == OK); stopDumpThread(dumpComplete); } } 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; 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.. std::thread dump_thread([=, remote_end { std::move(remote_end) }]() mutable { 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 Loading @@ -277,22 +327,53 @@ int Dumpsys::main(int argc, char* const argv[]) { remote_end.reset(); if (err != 0) { aerr << "Error dumping service info: (" << strerror(err) << ") " << service_name << endl; 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); } auto timeout = std::chrono::milliseconds(timeoutArgMs); 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; struct pollfd pfd = { .fd = local_end.get(), .events = POLLIN }; int serviceDumpFd = redirectFd_.get(); if (serviceDumpFd == -1) { return INVALID_OPERATION; } struct pollfd pfd = {.fd = serviceDumpFd, .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]() { Loading @@ -303,65 +384,57 @@ int Dumpsys::main(int argc, char* const argv[]) { int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms())); if (rc < 0) { aerr << "Error in poll while dumping service " << service_name << " : " aerr << "Error in poll while dumping service " << serviceName << " : " << strerror(errno) << endl; error = true; status = -errno; break; } else if (rc == 0) { timed_out = true; status = TIMED_OUT; break; } char buf[4096]; rc = TEMP_FAILURE_RETRY(read(local_end.get(), buf, sizeof(buf))); rc = TEMP_FAILURE_RETRY(read(redirectFd_.get(), buf, sizeof(buf))); if (rc < 0) { aerr << "Failed to read while dumping service " << service_name << ": " aerr << "Failed to read while dumping service " << serviceName << ": " << strerror(errno) << endl; error = true; status = -errno; break; } else if (rc == 0) { // EOF. break; } if (!WriteFully(STDOUT_FILENO, buf, rc)) { aerr << "Failed to write while dumping service " << service_name << ": " if (!WriteFully(fd, buf, rc)) { aerr << "Failed to write while dumping service " << serviceName << ": " << strerror(errno) << endl; error = true; status = -errno; break; } totalBytes += rc; } if (timed_out) { aout << endl << "*** SERVICE '" << service_name << "' DUMP TIMEOUT (" << timeoutArgMs << "ms) EXPIRED ***" << endl << endl; 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); } if (timed_out || error) { dump_thread.detach(); } else { dump_thread.join(); elapsedDuration = std::chrono::steady_clock::now() - start; bytesWritten = totalBytes; return status; } 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; 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); aout << ", ending at: " << std::put_time(&finish_tm, "%Y-%m-%d %H:%M:%S") << endl; } } else { aerr << "Can't find service: " << service_name << endl; } } return 0; 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); } cmds/dumpsys/dumpsys.h +90 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -25,10 +28,97 @@ class Dumpsys { 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_; }; } Loading cmds/dumpsys/tests/dumpsys_test.cpp +46 −2 Original line number Diff line number Diff line Loading @@ -188,6 +188,22 @@ class DumpsysTest : public Test { 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) { Loading @@ -209,6 +225,7 @@ class DumpsysTest : public Test { 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, Loading @@ -216,6 +233,7 @@ class DumpsysTest : public Test { 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) { Loading Loading @@ -425,8 +443,8 @@ TEST_F(DumpsysTest, DumpWithPriorityNormal) { 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' Loading Loading @@ -461,3 +479,29 @@ TEST_F(DumpsysTest, DumpWithPriorityHighAndProto) { 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 Loading
cmds/dumpsys/dumpsys.cpp +208 −135 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -96,6 +98,19 @@ static bool ConvertPriorityTypeToBitmask(const String16& type, int& bitmask) { 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; Loading @@ -104,9 +119,9 @@ int Dumpsys::main(int argc, char* const argv[]) { 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}, Loading @@ -131,13 +146,13 @@ int Dumpsys::main(int argc, char* const argv[]) { 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; Loading Loading @@ -198,28 +213,11 @@ int Dumpsys::main(int argc, char* const argv[]) { } 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; Loading @@ -239,35 +237,87 @@ int Dumpsys::main(int argc, char* const argv[]) { } 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 (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); if (pipe(sfd) != 0) { aerr << "Failed to create pipe to dump service info for " << service_name << ": " << strerror(errno) << endl; continue; if (status == TIMED_OUT) { aout << endl << "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs << "ms) EXPIRED ***" << endl << endl; } unique_fd local_end(sfd[0]); unique_fd remote_end(sfd[1]); sfd[0] = sfd[1] = -1; if (addSeparator) { writeDumpFooter(STDOUT_FILENO, serviceName, elapsedDuration); } bool dumpComplete = (status == OK); stopDumpThread(dumpComplete); } } 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; 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.. std::thread dump_thread([=, remote_end { std::move(remote_end) }]() mutable { 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 Loading @@ -277,22 +327,53 @@ int Dumpsys::main(int argc, char* const argv[]) { remote_end.reset(); if (err != 0) { aerr << "Error dumping service info: (" << strerror(err) << ") " << service_name << endl; 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); } auto timeout = std::chrono::milliseconds(timeoutArgMs); 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; struct pollfd pfd = { .fd = local_end.get(), .events = POLLIN }; int serviceDumpFd = redirectFd_.get(); if (serviceDumpFd == -1) { return INVALID_OPERATION; } struct pollfd pfd = {.fd = serviceDumpFd, .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]() { Loading @@ -303,65 +384,57 @@ int Dumpsys::main(int argc, char* const argv[]) { int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms())); if (rc < 0) { aerr << "Error in poll while dumping service " << service_name << " : " aerr << "Error in poll while dumping service " << serviceName << " : " << strerror(errno) << endl; error = true; status = -errno; break; } else if (rc == 0) { timed_out = true; status = TIMED_OUT; break; } char buf[4096]; rc = TEMP_FAILURE_RETRY(read(local_end.get(), buf, sizeof(buf))); rc = TEMP_FAILURE_RETRY(read(redirectFd_.get(), buf, sizeof(buf))); if (rc < 0) { aerr << "Failed to read while dumping service " << service_name << ": " aerr << "Failed to read while dumping service " << serviceName << ": " << strerror(errno) << endl; error = true; status = -errno; break; } else if (rc == 0) { // EOF. break; } if (!WriteFully(STDOUT_FILENO, buf, rc)) { aerr << "Failed to write while dumping service " << service_name << ": " if (!WriteFully(fd, buf, rc)) { aerr << "Failed to write while dumping service " << serviceName << ": " << strerror(errno) << endl; error = true; status = -errno; break; } totalBytes += rc; } if (timed_out) { aout << endl << "*** SERVICE '" << service_name << "' DUMP TIMEOUT (" << timeoutArgMs << "ms) EXPIRED ***" << endl << endl; 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); } if (timed_out || error) { dump_thread.detach(); } else { dump_thread.join(); elapsedDuration = std::chrono::steady_clock::now() - start; bytesWritten = totalBytes; return status; } 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; 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); aout << ", ending at: " << std::put_time(&finish_tm, "%Y-%m-%d %H:%M:%S") << endl; } } else { aerr << "Can't find service: " << service_name << endl; } } return 0; 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); }
cmds/dumpsys/dumpsys.h +90 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -25,10 +28,97 @@ class Dumpsys { 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_; }; } Loading
cmds/dumpsys/tests/dumpsys_test.cpp +46 −2 Original line number Diff line number Diff line Loading @@ -188,6 +188,22 @@ class DumpsysTest : public Test { 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) { Loading @@ -209,6 +225,7 @@ class DumpsysTest : public Test { 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, Loading @@ -216,6 +233,7 @@ class DumpsysTest : public Test { 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) { Loading Loading @@ -425,8 +443,8 @@ TEST_F(DumpsysTest, DumpWithPriorityNormal) { 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' Loading Loading @@ -461,3 +479,29 @@ TEST_F(DumpsysTest, DumpWithPriorityHighAndProto) { 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