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

Commit e4f61748 authored by Vishnu Nair's avatar Vishnu Nair
Browse files

Refactor dumpsys to expose helper apis

- allow dumpstate to interact with services without executing dumpsys binary
- Remove "NORMAL" priority from section name for backwards compatibility when switching to version 2.0

Bug: 67716082

Test: mmm -j56 frameworks/native/cmds/dumpsys && \
      adb sync data && \
      adb shell /data/nativetest/dumpsys_test/dumpsys_test && \
      adb shell /data/nativetest64/dumpsys_test/dumpsys_test && \
      printf "\n\n#### ALL TESTS PASSED ####\n"

Test: manual tests with "adb bugreport"
Change-Id: I4198333a58ffe6cb521b5cb7066520c7a3ef0675
parent 94563fc8
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