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

Commit f80d2686 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Refactor dumpsys to expose helper apis"

parents c58237ee e4f61748
Loading
Loading
Loading
Loading
+208 −135
Original line number Diff line number Diff line
@@ -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 @@ 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;
@@ -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},
@@ -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;
@@ -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;
@@ -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
@@ -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]() {
@@ -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);
}
+90 −0
Original line number Diff line number Diff line
@@ -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 @@ 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_;
};
}

+46 −2
Original line number Diff line number Diff line
@@ -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) {
@@ -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,
@@ -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) {
@@ -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'
@@ -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