Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 95ba73f9 authored by Mike Ma's avatar Mike Ma
Browse files

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
parent 0bdf785b
Loading
Loading
Loading
Loading
+97 −61
Original line number Diff line number Diff line
@@ -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;
+17 −1
Original line number Diff line number Diff line
@@ -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.
 */
+12 −0
Original line number Diff line number Diff line
@@ -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;
}
+4 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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;
}
Loading