blob: c2c8a72f52fce90ab3667091cea07940890eefa4 [file] [log] [blame]
/*
* 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;
}
futures_map_.clear();
while (!tasks_.empty()) tasks_.pop();
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