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

Commit e3330792 authored by Gavin Corkery's avatar Gavin Corkery Committed by Gerrit Code Review
Browse files

Merge changes from topic "bugreporting-aosp-sync"

* changes:
  Fixes dumpstate_smoke_test failed #2
  Deletes temporary files from thread pool when report is cancel
  Remove a redundant call of starting DumpstateService
  Faster bugreports (6/n)
  Faster bugreports (5/n)
  Faster bugreports (4/n)
  Faster bugreports (3/n)
  Faster bugreports (2/n)
  Faster bugreports (1/n)
  Add BugreportManagerTestCases to dumpstate presubmit
  Fixes dumpstate_smoke_test failed
  More pre/postsubmit tests to dumpstate
parents 13f4350a 8be82ea1
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -105,6 +105,8 @@ cc_binary {
    name: "dumpstate",
    defaults: ["dumpstate_defaults"],
    srcs: [
        "DumpPool.cpp",
        "TaskQueue.cpp",
        "dumpstate.cpp",
        "main.cpp",
    ],
@@ -132,6 +134,8 @@ cc_test {
    name: "dumpstate_test",
    defaults: ["dumpstate_defaults"],
    srcs: [
        "DumpPool.cpp",
        "TaskQueue.cpp",
        "dumpstate.cpp",
        "tests/dumpstate_test.cpp",
    ],
@@ -148,10 +152,14 @@ cc_test {
    name: "dumpstate_smoke_test",
    defaults: ["dumpstate_defaults"],
    srcs: [
        "DumpPool.cpp",
        "TaskQueue.cpp",
        "dumpstate.cpp",
        "tests/dumpstate_smoke_test.cpp",
    ],
    static_libs: ["libgmock"],
    test_config: "dumpstate_smoke_test.xml",
    test_suites: ["device-tests"],
}


+196 −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.
 */

#define LOG_TAG "dumpstate"

#include "DumpPool.h"

#include <array>
#include <thread>

#include <log/log.h>

#include "dumpstate.h"
#include "DumpstateInternal.h"
#include "DumpstateUtil.h"

namespace android {
namespace os {
namespace dumpstate {

const std::string DumpPool::PREFIX_TMPFILE_NAME = "dump-tmp.";

DumpPool::DumpPool(const std::string& tmp_root) : tmp_root_(tmp_root), shutdown_(false),
        log_duration_(true) {
    assert(!tmp_root.empty());
    deleteTempFiles(tmp_root_);
}

DumpPool::~DumpPool() {
    shutdown();
}

void DumpPool::start(int thread_counts) {
    assert(thread_counts > 0);
    assert(threads_.empty());
    if (thread_counts > MAX_THREAD_COUNT) {
        thread_counts = MAX_THREAD_COUNT;
    }
    MYLOGI("Start thread pool:%d", thread_counts);
    shutdown_ = false;
    for (int i = 0; i < thread_counts; i++) {
        threads_.emplace_back(std::thread([=]() {
            setThreadName(pthread_self(), i + 1);
            loop();
        }));
    }
}

void DumpPool::shutdown() {
    std::unique_lock lock(lock_);
    if (shutdown_ || threads_.empty()) {
        return;
    }
    while (!tasks_.empty()) tasks_.pop();
    futures_map_.clear();

    shutdown_ = true;
    condition_variable_.notify_all();
    lock.unlock();

    for (auto& thread : threads_) {
        thread.join();
    }
    threads_.clear();
    deleteTempFiles(tmp_root_);
    MYLOGI("shutdown thread pool");
}

void DumpPool::waitForTask(const std::string& task_name, const std::string& title,
        int out_fd) {
    DurationReporter duration_reporter("Wait for " + task_name, true);
    auto iterator = futures_map_.find(task_name);
    if (iterator == futures_map_.end()) {
        MYLOGW("Task %s does not exist", task_name.c_str());
        return;
    }
    Future future = iterator->second;
    futures_map_.erase(iterator);

    std::string result = future.get();
    if (result.empty()) {
        return;
    }
    DumpFileToFd(out_fd, title, result);
    if (unlink(result.c_str())) {
        MYLOGE("Failed to unlink (%s): %s\n", result.c_str(), strerror(errno));
    }
}

void DumpPool::deleteTempFiles() {
    deleteTempFiles(tmp_root_);
}

void DumpPool::setLogDuration(bool log_duration) {
    log_duration_ = log_duration;
}

template <>
void DumpPool::invokeTask<std::function<void()>>(std::function<void()> dump_func,
        const std::string& duration_title, int out_fd) {
    DurationReporter duration_reporter(duration_title, /*logcat_only =*/!log_duration_,
            /*verbose =*/false, out_fd);
    std::invoke(dump_func);
}

template <>
void DumpPool::invokeTask<std::function<void(int)>>(std::function<void(int)> dump_func,
        const std::string& duration_title, int out_fd) {
    DurationReporter duration_reporter(duration_title, /*logcat_only =*/!log_duration_,
            /*verbose =*/false, out_fd);
    std::invoke(dump_func, out_fd);
}

std::unique_ptr<DumpPool::TmpFile> DumpPool::createTempFile() {
    auto tmp_file_ptr = std::make_unique<TmpFile>();
    std::string file_name_format = "%s/" + PREFIX_TMPFILE_NAME + "XXXXXX";
    snprintf(tmp_file_ptr->path, sizeof(tmp_file_ptr->path), file_name_format.c_str(),
             tmp_root_.c_str());
    tmp_file_ptr->fd.reset(TEMP_FAILURE_RETRY(
            mkostemp(tmp_file_ptr->path, O_CLOEXEC)));
    if (tmp_file_ptr->fd.get() == -1) {
        MYLOGE("open(%s, %s)\n", tmp_file_ptr->path, strerror(errno));
        tmp_file_ptr = nullptr;
        return tmp_file_ptr;
    }
    return tmp_file_ptr;
}

void DumpPool::deleteTempFiles(const std::string& folder) {
    std::unique_ptr<DIR, decltype(&closedir)> dir_ptr(opendir(folder.c_str()),
            &closedir);
    if (!dir_ptr) {
        MYLOGE("Failed to opendir (%s): %s\n", folder.c_str(), strerror(errno));
        return;
    }
    int dir_fd = dirfd(dir_ptr.get());
    if (dir_fd < 0) {
        MYLOGE("Failed to get fd of dir (%s): %s\n", folder.c_str(),
               strerror(errno));
        return;
    }

    struct dirent* de;
    while ((de = readdir(dir_ptr.get()))) {
        if (de->d_type != DT_REG) {
            continue;
        }
        std::string file_name(de->d_name);
        if (file_name.find(PREFIX_TMPFILE_NAME) != 0) {
            continue;
        }
        if (unlinkat(dir_fd, file_name.c_str(), 0)) {
            MYLOGE("Failed to unlink (%s): %s\n", file_name.c_str(),
                   strerror(errno));
        }
    }
}

void DumpPool::setThreadName(const pthread_t thread, int id) {
    std::array<char, 15> name;
    snprintf(name.data(), name.size(), "dumpstate_%d", id);
    pthread_setname_np(thread, name.data());
}

void DumpPool::loop() {
    std::unique_lock lock(lock_);
    while (!shutdown_) {
        if (tasks_.empty()) {
            condition_variable_.wait(lock);
            continue;
        } else {
            std::packaged_task<std::string()> task = std::move(tasks_.front());
            tasks_.pop();
            lock.unlock();
            std::invoke(task);
            lock.lock();
        }
    }
}

}  // namespace dumpstate
}  // namespace os
}  // namespace android
+206 −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.
 */

#ifndef FRAMEWORK_NATIVE_CMD_DUMPPOOL_H_
#define FRAMEWORK_NATIVE_CMD_DUMPPOOL_H_

#include <future>
#include <map>
#include <queue>
#include <string>

#include <android-base/file.h>
#include <android-base/macros.h>

namespace android {
namespace os {
namespace dumpstate {

class DumpPoolTest;

/*
 * A thread pool with the fixed number of threads to execute multiple dump tasks
 * simultaneously for the dumpstate. The dump task is a callable function. It
 * could include a file descriptor as a parameter to redirect dump results, if
 * it needs to output results to the bugreport. This can avoid messing up
 * bugreport's results when multiple dump tasks are running at the same time.
 * Takes an example below for the usage of the DumpPool:
 *
 * void DumpFoo(int out_fd) {
 *     dprintf(out_fd, "Dump result to out_fd ...");
 * }
 * ...
 * DumpPool pool(tmp_root);
 * pool.enqueueTaskWithFd("TaskName", &DumpFoo, std::placeholders::_1);
 * ...
 * pool.waitForTask("TaskName");
 *
 * DumpFoo is a callable function included a out_fd parameter. Using the
 * enqueueTaskWithFd method in DumpPool to enqueue the task to the pool. The
 * std::placeholders::_1 is a placeholder for DumpPool to pass a fd argument.
 */
class DumpPool {
  friend class android::os::dumpstate::DumpPoolTest;

  public:
    /*
     * Creates a thread pool.
     *
     * |tmp_root| A path to a temporary folder for threads to create temporary
     * files.
     */
    explicit DumpPool(const std::string& tmp_root);
    ~DumpPool();

    /*
     * Starts the threads in the pool.
     *
     * |thread_counts| the number of threads to start.
     */
    void start(int thread_counts = MAX_THREAD_COUNT);

    /*
     * Requests to shutdown the pool and waits until all threads exit the loop.
     */
    void shutdown();

    /*
     * Adds a task into the queue of the thread pool.
     *
     * |task_name| The name of the task. It's also the title of the
     * DurationReporter log.
     * |f| Callable function to execute the task.
     * |args| A list of arguments.
     *
     * TODO(b/164369078): remove this api to have just one enqueueTask for consistency.
     */
    template<class F, class... Args> void enqueueTask(const std::string& task_name, F&& f,
            Args&&... args) {
        std::function<void(void)> func = std::bind(std::forward<F>(f),
                std::forward<Args>(args)...);
        futures_map_[task_name] = post(task_name, func);
        if (threads_.empty()) {
            start();
        }
    }

    /*
     * Adds a task into the queue of the thread pool. The task takes a file
     * descriptor as a parameter to redirect dump results to a temporary file.
     *
     * |task_name| The name of the task. It's also the title of the
     * DurationReporter log.
     * |f| Callable function to execute the task.
     * |args| A list of arguments. A placeholder std::placeholders::_1 as a fd
     * argument needs to be included here.
     */
    template<class F, class... Args> void enqueueTaskWithFd(const std::string& task_name, F&& f,
            Args&&... args) {
        std::function<void(int)> func = std::bind(std::forward<F>(f),
                std::forward<Args>(args)...);
        futures_map_[task_name] = post(task_name, func);
        if (threads_.empty()) {
            start();
        }
    }

    /*
     * Waits until the task is finished. Dumps the task results to the STDOUT_FILENO.
     */
    void waitForTask(const std::string& task_name) {
        waitForTask(task_name, "", STDOUT_FILENO);
    }

    /*
     * Waits until the task is finished. Dumps the task results to the specified
     * out_fd.
     *
     * |task_name| The name of the task.
     * |title| Dump title string to the out_fd, an empty string for nothing.
     * |out_fd| The target file to dump the result from the task.
     */
    void waitForTask(const std::string& task_name, const std::string& title, int out_fd);

    /*
     * Deletes temporary files created by DumpPool.
     */
    void deleteTempFiles();

    static const std::string PREFIX_TMPFILE_NAME;

  private:
    using Task = std::packaged_task<std::string()>;
    using Future = std::shared_future<std::string>;

    template<class T> void invokeTask(T dump_func, const std::string& duration_title, int out_fd);

    template<class T> Future post(const std::string& task_name, T dump_func) {
        Task packaged_task([=]() {
            std::unique_ptr<TmpFile> tmp_file_ptr = createTempFile();
            if (!tmp_file_ptr) {
                return std::string("");
            }
            invokeTask(dump_func, task_name, tmp_file_ptr->fd.get());
            fsync(tmp_file_ptr->fd.get());
            return std::string(tmp_file_ptr->path);
        });
        std::unique_lock lock(lock_);
        auto future = packaged_task.get_future().share();
        tasks_.push(std::move(packaged_task));
        condition_variable_.notify_one();
        return future;
    }

    typedef struct {
      android::base::unique_fd fd;
      char path[1024];
    } TmpFile;

    std::unique_ptr<TmpFile> createTempFile();
    void deleteTempFiles(const std::string& folder);
    void setThreadName(const pthread_t thread, int id);
    void loop();

    /*
     * For test purpose only. Enables or disables logging duration of the task.
     *
     * |log_duration| if true, DurationReporter is initiated to log duration of
     * the task.
     */
    void setLogDuration(bool log_duration);

  private:
    static const int MAX_THREAD_COUNT = 4;

    /* A path to a temporary folder for threads to create temporary files. */
    std::string tmp_root_;
    bool shutdown_;
    bool log_duration_; // For test purpose only, the default value is true.
    std::mutex lock_;  // A lock for the tasks_.
    std::condition_variable condition_variable_;

    std::vector<std::thread> threads_;
    std::queue<Task> tasks_;
    std::map<std::string, Future> futures_map_;

    DISALLOW_COPY_AND_ASSIGN(DumpPool);
};

}  // namespace dumpstate
}  // namespace os
}  // namespace android

#endif //FRAMEWORK_NATIVE_CMD_DUMPPOOL_H_
+9 −0
Original line number Diff line number Diff line
@@ -180,6 +180,7 @@ CommandOptions::CommandOptionsBuilder CommandOptions::WithTimeoutInMs(int64_t ti
std::string PropertiesHelper::build_type_ = "";
int PropertiesHelper::dry_run_ = -1;
int PropertiesHelper::unroot_ = -1;
int PropertiesHelper::parallel_run_ = -1;

bool PropertiesHelper::IsUserBuild() {
    if (build_type_.empty()) {
@@ -202,6 +203,14 @@ bool PropertiesHelper::IsUnroot() {
    return unroot_ == 1;
}

bool PropertiesHelper::IsParallelRun() {
    if (parallel_run_ == -1) {
        parallel_run_ = android::base::GetBoolProperty("dumpstate.parallel_run",
                /* default_value = */true) ? 1 : 0;
    }
    return parallel_run_ == 1;
}

int DumpFileToFd(int out_fd, const std::string& title, const std::string& path) {
    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
    if (fd.get() < 0) {
+8 −0
Original line number Diff line number Diff line
@@ -176,10 +176,18 @@ class PropertiesHelper {
     */
    static bool IsUnroot();

    /*
     * Whether or not the parallel run is enabled. Setting the system property
     * 'dumpstate.parallel_run' to false to disable it, otherwise it returns
     * true by default.
     */
    static bool IsParallelRun();

  private:
    static std::string build_type_;
    static int dry_run_;
    static int unroot_;
    static int parallel_run_;
};

/*
Loading