diff options
author | 2020-07-14 18:38:08 +0800 | |
---|---|---|
committer | 2020-10-19 04:07:52 +0000 | |
commit | 27077b1393e20c0e90fb7beeea7e3bba6014638e (patch) | |
tree | 01fec003a738ee23648f5151ad0314d48b19008f | |
parent | c18bfd601a8fe8acefc340c67b31e51f57e55a98 (diff) |
Faster bugreports (1/n)
Adds a thread pool to dumpstate.
Bug: 136262402
Test: atest dumpstate_test
Change-Id: I83b48a696c60fafbd4cb0dd62d3acceaa43caabb
Merged-In: I83b48a696c60fafbd4cb0dd62d3acceaa43caabb
(cherry picked from commit 3432b93ad0e423a3bdd0b87fe3f3dabb9d572dae)
-rw-r--r-- | cmds/dumpstate/Android.bp | 3 | ||||
-rw-r--r-- | cmds/dumpstate/DumpPool.cpp | 171 | ||||
-rw-r--r-- | cmds/dumpstate/DumpPool.h | 161 | ||||
-rw-r--r-- | cmds/dumpstate/tests/dumpstate_test.cpp | 76 |
4 files changed, 411 insertions, 0 deletions
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp index 40589345d2..1f055f3be7 100644 --- a/cmds/dumpstate/Android.bp +++ b/cmds/dumpstate/Android.bp @@ -105,6 +105,7 @@ cc_binary { name: "dumpstate", defaults: ["dumpstate_defaults"], srcs: [ + "DumpPool.cpp", "dumpstate.cpp", "main.cpp", ], @@ -132,6 +133,7 @@ cc_test { name: "dumpstate_test", defaults: ["dumpstate_defaults"], srcs: [ + "DumpPool.cpp", "dumpstate.cpp", "tests/dumpstate_test.cpp", ], @@ -148,6 +150,7 @@ cc_test { name: "dumpstate_smoke_test", defaults: ["dumpstate_defaults"], srcs: [ + "DumpPool.cpp", "dumpstate.cpp", "tests/dumpstate_smoke_test.cpp", ], diff --git a/cmds/dumpstate/DumpPool.cpp b/cmds/dumpstate/DumpPool.cpp new file mode 100644 index 0000000000..7324ead7c6 --- /dev/null +++ b/cmds/dumpstate/DumpPool.cpp @@ -0,0 +1,171 @@ +/* + * 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) { + 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)); + } +} + +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 diff --git a/cmds/dumpstate/DumpPool.h b/cmds/dumpstate/DumpPool.h new file mode 100644 index 0000000000..266d519b6d --- /dev/null +++ b/cmds/dumpstate/DumpPool.h @@ -0,0 +1,161 @@ +/* + * 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 { + +/* + * 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 + * included a file descriptor as a parameter, and the task could dump results to + * that fd. For example: + * + * void DumpXXXX(int out_fd) { + * dprintf(out_fd, "Dump result to out_fd ..."); + * } + * ... + * DumpPool pool(tmp_root); + * pool.enqueueTask("TaskName", &DumpXXXX, std::placeholders::_1); + * ... + * pool.waitForTask("TaskName"); + * + * DumpXXXX is a callable function included a out_fd parameter. Using the + * enqueueTask method in DumpPool to enqueue the task to the pool. The + * std::placeholders::_1 is placeholder for DumpPool to pass a fd argument. + */ +class DumpPool { + 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 with a task name into the queue of the thread pool. + * + * |task_name| The name of the task. + * |f| Callable function to execute the task. This function must + * include a parameter of file descriptor to output dump result. + * |args| A list of arguments. + */ + template<class F, class... Args> void enqueueTask(const std::string& task_name, + F&& f, Args&&... args) { + auto func = std::bind(std::forward<F>(f), std::forward<Args>(args)...); + futures_map_[task_name] = post(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); + + 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> Future post(T dump_func) { + Task packaged_task([=]() { + std::unique_ptr<TmpFile> tmp_file_ptr = createTempFile(); + if (!tmp_file_ptr) { + return std::string(""); + } + std::invoke(dump_func, 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(); + + 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_; + 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_ diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index c7df1bb6a3..d0a48266f2 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -21,6 +21,7 @@ #include "DumpstateService.h" #include "android/os/BnDumpstate.h" #include "dumpstate.h" +#include "DumpPool.h" #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -46,6 +47,7 @@ namespace dumpstate { using ::android::hardware::dumpstate::V1_1::DumpstateMode; using ::testing::EndsWith; +using ::testing::Eq; using ::testing::HasSubstr; using ::testing::IsEmpty; using ::testing::IsNull; @@ -1618,6 +1620,80 @@ TEST_F(DumpstateUtilTest, DumpFileOnDryRun) { EXPECT_THAT(out, EndsWith("skipped on dry run\n")); } +class DumpPoolTest : public DumpstateBaseTest { + public: + void SetUp() { + DumpstateBaseTest::SetUp(); + CreateOutputFile(); + } + + void CreateOutputFile() { + out_path_ = kTestDataPath + "out.txt"; + out_fd_.reset(TEMP_FAILURE_RETRY(open(out_path_.c_str(), + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))); + ASSERT_GE(out_fd_.get(), 0) << "could not create FD for path " + << out_path_; + } + + int getTempFileCounts(const std::string& folder) { + int count = 0; + std::unique_ptr<DIR, decltype(&closedir)> dir_ptr(opendir(folder.c_str()), + &closedir); + if (!dir_ptr) { + return -1; + } + int dir_fd = dirfd(dir_ptr.get()); + if (dir_fd < 0) { + return -1; + } + + 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(DumpPool::PREFIX_TMPFILE_NAME) != 0) { + continue; + } + count++; + } + return count; + } + + android::base::unique_fd out_fd_; + std::string out_path_; +}; + +TEST_F(DumpPoolTest, EnqueueTask) { + DumpPool pool(kTestDataPath); + auto dump_func_1 = [](int out_fd) { + dprintf(out_fd, "A"); + }; + auto dump_func_2 = [](int out_fd) { + dprintf(out_fd, "B"); + sleep(1); + }; + auto dump_func_3 = [](int out_fd) { + dprintf(out_fd, "C"); + }; + pool.enqueueTask(/* task_name = */"1", dump_func_1, std::placeholders::_1); + pool.enqueueTask(/* task_name = */"2", dump_func_2, std::placeholders::_1); + pool.enqueueTask(/* task_name = */"3", dump_func_3, std::placeholders::_1); + + pool.waitForTask("1", "", out_fd_.get()); + pool.waitForTask("2", "", out_fd_.get()); + pool.waitForTask("3", "", out_fd_.get()); + + std::string result; + ReadFileToString(out_path_, &result); + EXPECT_THAT(result, StrEq("A\nB\nC\n")); + EXPECT_THAT(getTempFileCounts(kTestDataPath), Eq(0)); + pool.shutdown(); +} + + } // namespace dumpstate } // namespace os } // namespace android |