Merge "Tee output of assemble_cvd and run_cvd to a log file"
diff --git a/common/libs/fs/Android.bp b/common/libs/fs/Android.bp
index 9f5d55e..0e49e5a 100644
--- a/common/libs/fs/Android.bp
+++ b/common/libs/fs/Android.bp
@@ -18,6 +18,7 @@
srcs: [
"shared_buf.cc",
"shared_fd.cpp",
+ "tee.cpp",
],
shared: {
shared_libs: [
@@ -46,6 +47,7 @@
srcs: [
"shared_buf.cc",
"shared_fd.cpp",
+ "tee.cpp",
],
shared_libs: [
"libbase",
diff --git a/common/libs/fs/shared_buf.cc b/common/libs/fs/shared_buf.cc
index 84397a0..7400f03 100644
--- a/common/libs/fs/shared_buf.cc
+++ b/common/libs/fs/shared_buf.cc
@@ -28,6 +28,22 @@
const size_t BUFF_SIZE = 1 << 14;
+static ssize_t WriteAll(SharedFD fd, const char* buf, size_t size) {
+ size_t total_written = 0;
+ ssize_t written = 0;
+ while ((written = fd->Write((void*)&(buf[total_written]), size - total_written)) > 0) {
+ if (written < 0) {
+ errno = fd->GetErrno();
+ return written;
+ }
+ total_written += written;
+ if (total_written == size) {
+ break;
+ }
+ }
+ return total_written;
+}
+
} // namespace
ssize_t ReadAll(SharedFD fd, std::string* buf) {
@@ -63,19 +79,11 @@
}
ssize_t WriteAll(SharedFD fd, const std::string& buf) {
- size_t total_written = 0;
- ssize_t written = 0;
- while ((written = fd->Write((void*)&(buf[total_written]), buf.size() - total_written)) > 0) {
- if (written < 0) {
- errno = fd->GetErrno();
- return written;
- }
- total_written += written;
- if (total_written == buf.size()) {
- break;
- }
- }
- return total_written;
+ return WriteAll(fd, buf.data(), buf.size());
+}
+
+ssize_t WriteAll(SharedFD fd, const std::vector<char>& buf) {
+ return WriteAll(fd, buf.data(), buf.size());
}
} // namespace cvd
diff --git a/common/libs/fs/shared_buf.h b/common/libs/fs/shared_buf.h
index 3577fa1..bebb5bc 100644
--- a/common/libs/fs/shared_buf.h
+++ b/common/libs/fs/shared_buf.h
@@ -52,4 +52,14 @@
*/
ssize_t WriteAll(SharedFD fd, const std::string& buf);
+/**
+ * Writes to fd until writing all bytes in buf.
+ *
+ * On a successful write, returns buf.size().
+ *
+ * If a write error is encountered, returns -1. Some data may have already been
+ * written to fd at that point.
+ */
+ssize_t WriteAll(SharedFD fd, const std::vector<char>& buf);
+
} // namespace cvd
diff --git a/common/libs/fs/tee.cpp b/common/libs/fs/tee.cpp
new file mode 100644
index 0000000..87ad432
--- /dev/null
+++ b/common/libs/fs/tee.cpp
@@ -0,0 +1,122 @@
+//
+// Copyright (C) 2019 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.
+
+#include <algorithm>
+#include <iostream>
+
+#include "common/libs/glog/logging.h"
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/tee.h"
+
+static const std::size_t READ_SIZE = 512;
+
+namespace cvd {
+
+TeeSubscriber* Tee::AddSubscriber(TeeSubscriber subscriber) {
+ if (reader_.joinable()) {
+ return nullptr;
+ }
+ return &targets_.emplace_back(std::move(subscriber)).handler;
+}
+
+void Tee::Start(SharedFD source) {
+ reader_ = std::thread([this, source]() {
+ while (true) {
+ // TODO(schfufelen): Use multiple buffers at once for readv
+ // TODO(schuffelen): Reuse buffers
+ TeeBufferPtr buffer = std::make_shared<std::vector<char>>(READ_SIZE);
+ ssize_t read = source->Read(buffer->data(), buffer->size());
+ if (read <= 0) {
+ for (auto& target : targets_) {
+ target.content_queue.Push(nullptr);
+ }
+ break;
+ }
+ buffer->resize(read);
+ for (auto& target : targets_) {
+ target.content_queue.Push(buffer);
+ }
+ }
+ });
+ for (auto& target : targets_) {
+ target.runner = std::thread([&target]() {
+ while (true) {
+ auto queue_chunk = target.content_queue.PopAll();
+ // TODO(schuffelen): Pass multiple buffers to support writev
+ for (auto& buffer : queue_chunk) {
+ if (!buffer) {
+ return;
+ }
+ target.handler(buffer);
+ }
+ }
+ });
+ }
+}
+
+Tee::~Tee() {
+ Join();
+}
+
+void Tee::Join() {
+ if (reader_.joinable()) {
+ reader_.join();
+ }
+ auto it = targets_.begin();
+ while (it != targets_.end()) {
+ if (it->runner.joinable()) {
+ it->runner.join();
+ }
+ it = targets_.erase(it);
+ }
+}
+
+TeeSubscriber SharedFDWriter(SharedFD fd) {
+ return [fd](const TeeBufferPtr buffer) { WriteAll(fd, *buffer); };
+}
+
+// An alternative to this would have been to modify the logger, but that would
+// not capture logs from subprocesses.
+TeeStderrToFile::TeeStderrToFile() {
+ original_stderr_ = SharedFD::Dup(2);
+
+ SharedFD stderr_read, stderr_write;
+ SharedFD::Pipe(&stderr_read, &stderr_write);
+ stderr_write->UNMANAGED_Dup2(2);
+ stderr_write->Close();
+
+ tee_.AddSubscriber(SharedFDWriter(original_stderr_));
+ tee_.AddSubscriber(
+ [this](cvd::TeeBufferPtr data) {
+ std::unique_lock lock(mutex_);
+ while (!log_file_->IsOpen()) {
+ notifier_.wait(lock);
+ }
+ cvd::WriteAll(log_file_, *data);
+ });
+ tee_.Start(std::move(stderr_read));
+}
+
+TeeStderrToFile::~TeeStderrToFile() {
+ original_stderr_->UNMANAGED_Dup2(2);
+}
+
+void TeeStderrToFile::SetFile(SharedFD file) {
+ std::lock_guard lock(mutex_);
+ log_file_ = file;
+ notifier_.notify_all();
+}
+
+} // namespace
diff --git a/common/libs/fs/tee.h b/common/libs/fs/tee.h
new file mode 100644
index 0000000..23bc3d1
--- /dev/null
+++ b/common/libs/fs/tee.h
@@ -0,0 +1,70 @@
+//
+// Copyright (C) 2019 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.
+
+#pragma once
+
+#include <condition_variable>
+#include <functional>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <string>
+#include <vector>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/thread_safe_queue/thread_safe_queue.h"
+
+namespace cvd {
+
+using TeeBufferPtr = std::shared_ptr<std::vector<char>>;
+using TeeSubscriber = std::function<void(const TeeBufferPtr)>;
+
+struct TeeTarget {
+ std::thread runner;
+ ThreadSafeQueue<TeeBufferPtr> content_queue;
+ TeeSubscriber handler;
+
+ TeeTarget(TeeSubscriber handler) : handler(handler) {}
+};
+
+class Tee {
+ std::thread reader_;
+ std::list<TeeTarget> targets_;
+public:
+ ~Tee();
+
+ TeeSubscriber* AddSubscriber(TeeSubscriber);
+
+ void Start(SharedFD source);
+ void Join();
+};
+
+TeeSubscriber SharedFDWriter(SharedFD fd);
+
+class TeeStderrToFile {
+ cvd::SharedFD log_file_;
+ cvd::SharedFD original_stderr_;
+ std::condition_variable notifier_;
+ std::mutex mutex_;
+ Tee tee_; // This should be destroyed first, so placed last.
+public:
+ TeeStderrToFile();
+ ~TeeStderrToFile();
+
+ void SetFile(SharedFD file);
+};
+
+} // namespace cvd
diff --git a/common/libs/thread_safe_queue/thread_safe_queue.h b/common/libs/thread_safe_queue/thread_safe_queue.h
index 8662928..9970d31 100644
--- a/common/libs/thread_safe_queue/thread_safe_queue.h
+++ b/common/libs/thread_safe_queue/thread_safe_queue.h
@@ -50,6 +50,14 @@
return t;
}
+ QueueImpl PopAll() {
+ std::unique_lock<std::mutex> guard(m_);
+ while (items_.empty()) {
+ new_item_.wait(guard);
+ }
+ return std::move(items_);
+ }
+
void Push(T&& t) {
std::lock_guard<std::mutex> guard(m_);
DropItemsIfAtCapacity();
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index c829395..247aff1 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -20,6 +20,7 @@
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/tee.h"
#include "host/commands/assemble_cvd/assembler_defs.h"
#include "host/commands/assemble_cvd/flags.h"
#include "host/libs/config/fetcher_config.h"
@@ -55,11 +56,13 @@
int error_num = errno;
if (error_num == EBADF) {
LOG(FATAL) << "stdin was not a valid file descriptor, expected to be passed the output "
- << "of assemble_cvd. Did you mean to run launch_cvd?";
+ << "of launch_cvd. Did you mean to run launch_cvd?";
return cvd::AssemblerExitCodes::kInvalidHostConfiguration;
}
}
+ cvd::TeeStderrToFile stderr_tee;
+
std::string input_files_str;
{
auto input_fd = cvd::SharedFD::Dup(0);
@@ -72,6 +75,9 @@
auto config = InitFilesystemAndCreateConfig(&argc, &argv, FindFetcherConfig(input_files));
+ auto assembler_log_path = config->PerInstancePath("assemble_cvd.log");
+ stderr_tee.SetFile(cvd::SharedFD::Creat(assembler_log_path.c_str(), 0755));
+
std::cout << GetConfigFilePath(*config) << "\n";
std::cout << std::flush;
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index e352393..c77e924 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -42,6 +42,7 @@
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/fs/shared_select.h"
+#include "common/libs/fs/tee.h"
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/subprocess.h"
@@ -291,6 +292,8 @@
}
}
+ cvd::TeeStderrToFile stderr_tee;
+
std::string input_files_str;
{
auto input_fd = cvd::SharedFD::Dup(0);
@@ -313,6 +316,9 @@
auto config = vsoc::CuttlefishConfig::Get();
+ auto runner_log_path = config->PerInstancePath("run_cvd.log");
+ stderr_tee.SetFile(cvd::SharedFD::Creat(runner_log_path.c_str(), 0755));
+
// Change working directory to the instance directory as early as possible to
// ensure all host processes have the same working dir. This helps stop_cvd
// find the running processes when it can't establish a communication with the