Add text dumpsys section to incidentd
Enable Incidentd to dump any existing dumpsys section in plain text
(as dumpsys.proto), only in eng or userdebug build. This is for a
few dumpsys services that are prohibitively expensive to migrate to
protobuf dumpsys or will undergo a major rewrite (thus render the
previously defined proto completely useless).
Bug: 149816498
Bug: 146085372
Bug: 146086519
Test: $ incident -p EXPLICIT 4000 4001
Change-Id: I0693d9bace0055cfeb63d7c8d48995d57dc0b733
(cherry picked from commit 95ba73f9c9815da08cdb7015195939a3c1b250bd)
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 2229e1c..d79123b 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -20,9 +20,9 @@
#include <dirent.h>
#include <errno.h>
-
#include <mutex>
#include <set>
+#include <thread>
#include <android-base/file.h>
#include <android-base/properties.h>
@@ -42,6 +42,7 @@
#include "frameworks/base/core/proto/android/os/backtrace.proto.h"
#include "frameworks/base/core/proto/android/os/data.proto.h"
#include "frameworks/base/core/proto/android/util/log.proto.h"
+#include "frameworks/base/core/proto/android/util/textdump.proto.h"
#include "incidentd_util.h"
namespace android {
@@ -135,7 +136,7 @@
status_t ihStatus = wait_child(pid);
if (ihStatus != NO_ERROR) {
ALOGW("[%s] abnormal child process: %s", this->name.string(), strerror(-ihStatus));
- return ihStatus;
+ return OK; // Not a fatal error.
}
return writer->writeSection(buffer);
@@ -234,7 +235,7 @@
Fpipe pipe;
// Lock protects these fields
- mutex lock;
+ std::mutex lock;
bool workerDone;
status_t workerError;
@@ -261,83 +262,47 @@
}
}
-static void* worker_thread_func(void* cookie) {
- // Don't crash the service if we write to a closed pipe (which can happen if
- // dumping times out).
- signal(SIGPIPE, sigpipe_handler);
-
- WorkerThreadData* data = (WorkerThreadData*)cookie;
- status_t err = data->section->BlockingCall(data->pipe.writeFd());
-
- {
- unique_lock<mutex> lock(data->lock);
- data->workerDone = true;
- data->workerError = err;
- }
-
- data->pipe.writeFd().reset();
- data->decStrong(data->section);
- // data might be gone now. don't use it after this point in this thread.
- return NULL;
-}
-
status_t WorkerThreadSection::Execute(ReportWriter* writer) const {
status_t err = NO_ERROR;
- pthread_t thread;
- pthread_attr_t attr;
bool workerDone = false;
FdBuffer buffer;
- // Data shared between this thread and the worker thread.
- sp<WorkerThreadData> data = new WorkerThreadData(this);
-
- // Create the pipe
- if (!data->pipe.init()) {
+ // Create shared data and pipe
+ WorkerThreadData data(this);
+ if (!data.pipe.init()) {
return -errno;
}
- // Create the thread
- err = pthread_attr_init(&attr);
- if (err != 0) {
- return -err;
- }
- // TODO: Do we need to tweak thread priority?
- err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- if (err != 0) {
- pthread_attr_destroy(&attr);
- return -err;
- }
-
- // The worker thread needs a reference and we can't let the count go to zero
- // if that thread is slow to start.
- data->incStrong(this);
-
- err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get());
- pthread_attr_destroy(&attr);
- if (err != 0) {
- data->decStrong(this);
- return -err;
- }
+ std::thread([&]() {
+ // Don't crash the service if writing to a closed pipe (may happen if dumping times out)
+ signal(SIGPIPE, sigpipe_handler);
+ status_t err = data.section->BlockingCall(data.pipe.writeFd());
+ {
+ std::unique_lock<std::mutex> lock(data.lock);
+ data.workerDone = true;
+ data.workerError = err;
+ // unique_fd is not thread safe. If we don't lock it, reset() may pause half way while
+ // the other thread executes to the end, calling ~Fpipe, which is a race condition.
+ data.pipe.writeFd().reset();
+ }
+ }).detach();
// Loop reading until either the timeout or the worker side is done (i.e. eof).
- err = buffer.read(data->pipe.readFd().get(), this->timeoutMs);
+ err = buffer.read(data.pipe.readFd().get(), this->timeoutMs);
if (err != NO_ERROR) {
ALOGE("[%s] reader failed with error '%s'", this->name.string(), strerror(-err));
}
- // Done with the read fd. The worker thread closes the write one so
- // we never race and get here first.
- data->pipe.readFd().reset();
-
// If the worker side is finished, then return its error (which may overwrite
// our possible error -- but it's more interesting anyway). If not, then we timed out.
{
- unique_lock<mutex> lock(data->lock);
- if (data->workerError != NO_ERROR) {
- err = data->workerError;
+ std::unique_lock<std::mutex> lock(data.lock);
+ data.pipe.close();
+ if (data.workerError != NO_ERROR) {
+ err = data.workerError;
ALOGE("[%s] worker failed with error '%s'", this->name.string(), strerror(-err));
}
- workerDone = data->workerDone;
+ workerDone = data.workerDone;
}
writer->setSectionStats(buffer);
@@ -473,6 +438,77 @@
}
// ================================================================================
+TextDumpsysSection::TextDumpsysSection(int id, const char* service, ...)
+ : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), mService(service) {
+ name = "dumpsys ";
+ name += service;
+
+ va_list args;
+ va_start(args, service);
+ while (true) {
+ const char* arg = va_arg(args, const char*);
+ if (arg == NULL) {
+ break;
+ }
+ mArgs.add(String16(arg));
+ name += " ";
+ name += arg;
+ }
+ va_end(args);
+}
+
+TextDumpsysSection::~TextDumpsysSection() {}
+
+status_t TextDumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
+ // checkService won't wait for the service to show up like getService will.
+ sp<IBinder> service = defaultServiceManager()->checkService(mService);
+ if (service == NULL) {
+ ALOGW("TextDumpsysSection: Can't lookup service: %s", String8(mService).string());
+ return NAME_NOT_FOUND;
+ }
+
+ // Create pipe
+ Fpipe dumpPipe;
+ if (!dumpPipe.init()) {
+ ALOGW("[%s] failed to setup pipe", this->name.string());
+ return -errno;
+ }
+
+ // Run dumping thread
+ const uint64_t start = Nanotime();
+ std::thread worker([&]() {
+ // Don't crash the service if writing to a closed pipe (may happen if dumping times out)
+ signal(SIGPIPE, sigpipe_handler);
+ status_t err = service->dump(dumpPipe.writeFd().get(), mArgs);
+ if (err != OK) {
+ ALOGW("[%s] dump thread failed. Error: %s", this->name.string(), strerror(-err));
+ }
+ dumpPipe.writeFd().reset();
+ });
+
+ // Collect dump content
+ std::string content;
+ bool success = ReadFdToString(dumpPipe.readFd(), &content);
+ worker.join(); // Wait for worker to finish
+ dumpPipe.readFd().reset();
+ if (!success) {
+ ALOGW("[%s] failed to read data from pipe", this->name.string());
+ return -1;
+ }
+
+ ProtoOutputStream proto;
+ proto.write(util::TextDumpProto::COMMAND, std::string(name.string()));
+ proto.write(util::TextDumpProto::CONTENT, content);
+ proto.write(util::TextDumpProto::DUMP_DURATION_NS, int64_t(Nanotime() - start));
+
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
+ ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
+ return EPIPE;
+ }
+ return OK;
+}
+
+// ================================================================================
// initialization only once in Section.cpp.
map<log_id_t, log_time> LogSection::gLastLogsRetrieved;
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 0bb9da9..6162b3a 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -112,7 +112,8 @@
};
/**
- * Section that calls dumpsys on a system service.
+ * Section that calls protobuf dumpsys on a system service, usually
+ * "dumpsys [service_name] --proto".
*/
class DumpsysSection : public WorkerThreadSection {
public:
@@ -127,6 +128,21 @@
};
/**
+ * Section that calls text dumpsys on a system service, usually "dumpsys [service_name]".
+ */
+class TextDumpsysSection : public WorkerThreadSection {
+public:
+ TextDumpsysSection(int id, const char* service, ...);
+ virtual ~TextDumpsysSection();
+
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+ String16 mService;
+ Vector<String16> mArgs;
+};
+
+/**
* Section that calls dumpsys on a system service.
*/
class SystemPropertyDumpsysSection : public WorkerThreadSection {