blob: 314a3734fee62fe972aa3f3bf07fe3d2aee79a26 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/pattern.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "content/browser/tracing/tracing_controller_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/tracing_controller.h"
#include "third_party/zlib/zlib.h"
namespace content {
namespace {
class StringTraceDataEndpoint : public TracingController::TraceDataEndpoint {
public:
explicit StringTraceDataEndpoint(
TracingController::CompletionCallback callback)
: completion_callback_(std::move(callback)) {}
StringTraceDataEndpoint(const StringTraceDataEndpoint&) = delete;
StringTraceDataEndpoint& operator=(const StringTraceDataEndpoint&) = delete;
void ReceivedTraceFinalContents() override {
auto str = std::make_unique<std::string>(trace_.str());
trace_.str("");
trace_.clear();
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(completion_callback_), std::move(str)));
}
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
trace_ << *chunk;
}
private:
~StringTraceDataEndpoint() override {}
TracingController::CompletionCallback completion_callback_;
std::ostringstream trace_;
};
class FileTraceDataEndpoint : public TracingController::TraceDataEndpoint {
public:
explicit FileTraceDataEndpoint(const base::FilePath& trace_file_path,
base::OnceClosure callback,
base::TaskPriority write_priority)
: file_path_(trace_file_path),
completion_callback_(std::move(callback)),
may_block_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), write_priority,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {}
FileTraceDataEndpoint(const FileTraceDataEndpoint&) = delete;
FileTraceDataEndpoint& operator=(const FileTraceDataEndpoint&) = delete;
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
may_block_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&FileTraceDataEndpoint::ReceiveTraceChunkOnBlockingThread, this,
std::move(chunk)));
}
void ReceivedTraceFinalContents() override {
may_block_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&FileTraceDataEndpoint::CloseOnBlockingThread, this));
}
private:
~FileTraceDataEndpoint() override { DCHECK(file_ == nullptr); }
void ReceiveTraceChunkOnBlockingThread(std::unique_ptr<std::string> chunk) {
if (!OpenFileIfNeededOnBlockingThread())
return;
std::ignore = fwrite(chunk->c_str(), chunk->size(), 1, file_);
}
bool OpenFileIfNeededOnBlockingThread() {
if (file_ != nullptr)
return true;
// The temporary trace file is produced in the same folder since paths must
// be on the same volume.
base::File temp_file = CreateAndOpenTemporaryFileInDir(file_path_.DirName(),
&pending_file_path_);
if (temp_file.IsValid()) {
// On Android, fdsan prohibits associating a new stream with a file while
// it's still owned by base::File. So we have to close it first and then
// reopen as FILE*.
temp_file.Close();
file_ = base::OpenFile(pending_file_path_, "w");
} else {
LOG(WARNING) << "Unable to use temporary file " << pending_file_path_
<< ": "
<< base::File::ErrorToString(temp_file.error_details());
pending_file_path_.clear();
file_ = base::OpenFile(file_path_, "w");
LOG_IF(ERROR, file_ == nullptr)
<< "Failed to open " << file_path_.value();
}
return file_ != nullptr;
}
void CloseOnBlockingThread() {
if (OpenFileIfNeededOnBlockingThread()) {
base::CloseFile(file_);
file_ = nullptr;
}
if (!pending_file_path_.empty()) {
base::File::Error error;
if (!base::ReplaceFile(pending_file_path_, file_path_, &error)) {
LOG(ERROR) << "Cannot replace file '" << file_path_
<< "' : " << base::File::ErrorToString(error);
base::DeleteFile(pending_file_path_);
return;
}
}
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&FileTraceDataEndpoint::FinalizeOnUIThread, this));
}
void FinalizeOnUIThread() { std::move(completion_callback_).Run(); }
base::FilePath file_path_;
base::FilePath pending_file_path_;
base::OnceClosure completion_callback_;
raw_ptr<FILE> file_ = nullptr;
const scoped_refptr<base::SequencedTaskRunner> may_block_task_runner_;
};
class CompressedTraceDataEndpoint
: public TracingController::TraceDataEndpoint {
public:
CompressedTraceDataEndpoint(scoped_refptr<TraceDataEndpoint> endpoint,
bool compress_with_background_priority)
: endpoint_(endpoint),
already_tried_open_(false),
background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{compress_with_background_priority
? base::TaskPriority::BEST_EFFORT
: base::TaskPriority::USER_VISIBLE})) {}
CompressedTraceDataEndpoint(const CompressedTraceDataEndpoint&) = delete;
CompressedTraceDataEndpoint& operator=(const CompressedTraceDataEndpoint&) =
delete;
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CompressedTraceDataEndpoint::CompressOnBackgroundThread,
this, std::move(chunk)));
}
void ReceivedTraceFinalContents() override {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CompressedTraceDataEndpoint::CloseOnBackgroundThread,
this));
}
private:
~CompressedTraceDataEndpoint() override = default;
bool OpenZStreamOnBackgroundThread() {
if (stream_)
return true;
if (already_tried_open_)
return false;
already_tried_open_ = true;
stream_ = std::make_unique<z_stream>();
*stream_ = {nullptr};
stream_->zalloc = Z_NULL;
stream_->zfree = Z_NULL;
stream_->opaque = Z_NULL;
int result = deflateInit2(stream_.get(), Z_DEFAULT_COMPRESSION, Z_DEFLATED,
// 16 is added to produce a gzip header + trailer.
MAX_WBITS + 16,
8, // memLevel = 8 is default.
Z_DEFAULT_STRATEGY);
return result == 0;
}
void CompressOnBackgroundThread(std::unique_ptr<std::string> chunk) {
if (!OpenZStreamOnBackgroundThread())
return;
stream_->avail_in = chunk->size();
stream_->next_in = reinterpret_cast<unsigned char*>(&*chunk->begin());
DrainStreamOnBackgroundThread(false);
}
void DrainStreamOnBackgroundThread(bool finished) {
int err;
const int kChunkSize = 0x4000;
char buffer[kChunkSize];
do {
stream_->avail_out = kChunkSize;
stream_->next_out = (unsigned char*)buffer;
err = deflate(stream_.get(), finished ? Z_FINISH : Z_NO_FLUSH);
if (err != Z_OK && (!finished || err != Z_STREAM_END)) {
LOG(ERROR) << "Deflate stream error: " << err;
stream_.reset();
return;
}
int bytes = kChunkSize - stream_->avail_out;
if (bytes) {
std::string compressed(buffer, bytes);
endpoint_->ReceiveTraceChunk(std::make_unique<std::string>(compressed));
}
} while (stream_->avail_out == 0);
}
void CloseOnBackgroundThread() {
if (!OpenZStreamOnBackgroundThread())
return;
DrainStreamOnBackgroundThread(true);
deflateEnd(stream_.get());
stream_.reset();
endpoint_->ReceivedTraceFinalContents();
}
scoped_refptr<TraceDataEndpoint> endpoint_;
std::unique_ptr<z_stream> stream_;
bool already_tried_open_;
const scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
};
} // namespace
scoped_refptr<TracingController::TraceDataEndpoint>
TracingController::CreateStringEndpoint(CompletionCallback callback) {
return new StringTraceDataEndpoint(std::move(callback));
}
scoped_refptr<TracingController::TraceDataEndpoint>
TracingController::CreateFileEndpoint(const base::FilePath& file_path,
base::OnceClosure callback,
base::TaskPriority write_priority) {
return new FileTraceDataEndpoint(file_path, std::move(callback),
write_priority);
}
scoped_refptr<TracingController::TraceDataEndpoint>
TracingControllerImpl::CreateCompressedStringEndpoint(
scoped_refptr<TraceDataEndpoint> endpoint,
bool compress_with_background_priority) {
return new CompressedTraceDataEndpoint(endpoint,
compress_with_background_priority);
}
scoped_refptr<TracingController::TraceDataEndpoint>
TracingControllerImpl::CreateCallbackEndpoint(CompletionCallback callback) {
return new StringTraceDataEndpoint(std::move(callback));
}
} // namespace content