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 95ba73f9c9)
This commit is contained in:
@@ -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 FileSection::Execute(ReportWriter* writer) const {
|
||||
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 @@ struct WorkerThreadData : public virtual RefBase {
|
||||
Fpipe pipe;
|
||||
|
||||
// Lock protects these fields
|
||||
mutex lock;
|
||||
std::mutex lock;
|
||||
bool workerDone;
|
||||
status_t workerError;
|
||||
|
||||
@@ -261,83 +262,47 @@ void sigpipe_handler(int signum) {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -472,6 +437,77 @@ status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
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;
|
||||
|
||||
@@ -112,7 +112,8 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
* 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:
|
||||
@@ -126,6 +127,21 @@ private:
|
||||
Vector<String16> mArgs;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -58,6 +58,7 @@ import "frameworks/base/core/proto/android/service/sensor_service.proto";
|
||||
import "frameworks/base/core/proto/android/service/usb.proto";
|
||||
import "frameworks/base/core/proto/android/util/event_log_tags.proto";
|
||||
import "frameworks/base/core/proto/android/util/log.proto";
|
||||
import "frameworks/base/core/proto/android/util/textdump.proto";
|
||||
import "frameworks/base/core/proto/android/privacy.proto";
|
||||
import "frameworks/base/core/proto/android/section.proto";
|
||||
import "frameworks/base/proto/src/ipconnectivity.proto";
|
||||
@@ -510,6 +511,17 @@ message IncidentProto {
|
||||
(section).args = "sensorservice --proto"
|
||||
];
|
||||
|
||||
// Dumps in text format (on userdebug and eng builds only): 4000 ~ 4999
|
||||
optional android.util.TextDumpProto textdump_wifi = 4000 [
|
||||
(section).type = SECTION_TEXT_DUMPSYS,
|
||||
(section).args = "wifi"
|
||||
];
|
||||
|
||||
optional android.util.TextDumpProto textdump_bluetooth = 4001 [
|
||||
(section).type = SECTION_TEXT_DUMPSYS,
|
||||
(section).args = "bluetooth_manager"
|
||||
];
|
||||
|
||||
// Reserved for OEMs.
|
||||
extensions 50000 to 100000;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ enum SectionType {
|
||||
|
||||
// incidentd calls tombstoned for annotated field
|
||||
SECTION_TOMBSTONE = 6;
|
||||
|
||||
// incidentd calls legacy text dumpsys for annotated field. The section will only be generated
|
||||
// on userdebug and eng builds.
|
||||
SECTION_TEXT_DUMPSYS = 7;
|
||||
}
|
||||
|
||||
message SectionFlags {
|
||||
|
||||
33
core/proto/android/util/textdump.proto
Normal file
33
core/proto/android/util/textdump.proto
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
package android.util;
|
||||
|
||||
import "frameworks/base/core/proto/android/privacy.proto";
|
||||
|
||||
option java_multiple_files = true;
|
||||
|
||||
message TextDumpProto {
|
||||
option (android.msg_privacy).dest = DEST_EXPLICIT;
|
||||
|
||||
// The command that was executed
|
||||
optional string command = 1;
|
||||
// The content that was dumped
|
||||
optional string content = 2;
|
||||
// The duration of the dump process
|
||||
optional int64 dump_duration_ns = 3;
|
||||
}
|
||||
@@ -415,7 +415,7 @@ static bool generateSectionListCpp(Descriptor const* descriptor) {
|
||||
}
|
||||
|
||||
const SectionFlags s = getSectionFlags(field);
|
||||
if (s.userdebug_and_eng_only()) {
|
||||
if (s.userdebug_and_eng_only() || s.type() == SECTION_TEXT_DUMPSYS) {
|
||||
printf("#if ALLOW_RESTRICTED_SECTIONS\n");
|
||||
}
|
||||
|
||||
@@ -449,8 +449,13 @@ static bool generateSectionListCpp(Descriptor const* descriptor) {
|
||||
printf(" new TombstoneSection(%d, \"%s\"),\n", field->number(),
|
||||
s.args().c_str());
|
||||
break;
|
||||
case SECTION_TEXT_DUMPSYS:
|
||||
printf(" new TextDumpsysSection(%d, ", field->number());
|
||||
splitAndPrint(s.args());
|
||||
printf(" NULL),\n");
|
||||
break;
|
||||
}
|
||||
if (s.userdebug_and_eng_only()) {
|
||||
if (s.userdebug_and_eng_only() || s.type() == SECTION_TEXT_DUMPSYS) {
|
||||
printf("#endif\n");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user