blob: 25bb0baa3d3a9f59c8825abca3b9931e8aa4532e [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 <utility>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/pattern.h"
#include "base/task/post_task.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:
typedef base::Callback<void(std::unique_ptr<const base::DictionaryValue>,
base::RefCountedString*)>
CompletionCallback;
explicit StringTraceDataEndpoint(CompletionCallback callback)
: completion_callback_(callback) {}
void ReceiveTraceFinalContents(
std::unique_ptr<const base::DictionaryValue> metadata) override {
std::string tmp = trace_.str();
trace_.str("");
trace_.clear();
scoped_refptr<base::RefCountedString> str =
base::RefCountedString::TakeString(&tmp);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(completion_callback_, std::move(metadata),
base::RetainedRef(str)));
}
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
trace_ << *chunk;
}
private:
~StringTraceDataEndpoint() override {}
CompletionCallback completion_callback_;
std::ostringstream trace_;
DISALLOW_COPY_AND_ASSIGN(StringTraceDataEndpoint);
};
class FileTraceDataEndpoint : public TracingController::TraceDataEndpoint {
public:
explicit FileTraceDataEndpoint(const base::FilePath& trace_file_path,
const base::Closure& callback)
: file_path_(trace_file_path),
completion_callback_(callback),
file_(nullptr) {}
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&FileTraceDataEndpoint::ReceiveTraceChunkOnBlockingThread, this,
std::move(chunk)));
}
void ReceiveTraceFinalContents(
std::unique_ptr<const base::DictionaryValue>) override {
background_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;
ignore_result(fwrite(chunk->c_str(), chunk->size(), 1, file_));
}
bool OpenFileIfNeededOnBlockingThread() {
base::ScopedBlockingCall scoped_blocking_call(
base::BlockingType::MAY_BLOCK);
if (file_ != nullptr)
return true;
file_ = base::OpenFile(file_path_, "w");
if (file_ == nullptr) {
LOG(ERROR) << "Failed to open " << file_path_.value();
return false;
}
return true;
}
void CloseOnBlockingThread() {
if (OpenFileIfNeededOnBlockingThread()) {
base::CloseFile(file_);
file_ = nullptr;
}
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&FileTraceDataEndpoint::FinalizeOnUIThread, this));
}
void FinalizeOnUIThread() { completion_callback_.Run(); }
base::FilePath file_path_;
base::Closure completion_callback_;
FILE* file_;
const scoped_refptr<base::SequencedTaskRunner> background_task_runner_ =
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT});
DISALLOW_COPY_AND_ASSIGN(FileTraceDataEndpoint);
};
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::CreateSequencedTaskRunnerWithTraits(
{compress_with_background_priority
? base::TaskPriority::BEST_EFFORT
: base::TaskPriority::USER_VISIBLE})) {}
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CompressedTraceDataEndpoint::CompressOnBackgroundThread,
this, std::move(chunk)));
}
void ReceiveTraceFinalContents(
std::unique_ptr<const base::DictionaryValue> metadata) override {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CompressedTraceDataEndpoint::CloseOnBackgroundThread,
this, std::move(metadata)));
}
private:
~CompressedTraceDataEndpoint() override {}
bool OpenZStreamOnBackgroundThread() {
if (stream_)
return true;
if (already_tried_open_)
return false;
already_tried_open_ = true;
stream_.reset(new 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(
std::unique_ptr<const base::DictionaryValue> metadata) {
if (!OpenZStreamOnBackgroundThread())
return;
DrainStreamOnBackgroundThread(true);
deflateEnd(stream_.get());
stream_.reset();
endpoint_->ReceiveTraceFinalContents(std::move(metadata));
}
scoped_refptr<TraceDataEndpoint> endpoint_;
std::unique_ptr<z_stream> stream_;
bool already_tried_open_;
const scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
DISALLOW_COPY_AND_ASSIGN(CompressedTraceDataEndpoint);
};
} // namespace
scoped_refptr<TracingController::TraceDataEndpoint>
TracingController::CreateStringEndpoint(
const base::Callback<void(std::unique_ptr<const base::DictionaryValue>,
base::RefCountedString*)>& callback) {
return new StringTraceDataEndpoint(callback);
}
scoped_refptr<TracingController::TraceDataEndpoint>
TracingController::CreateFileEndpoint(const base::FilePath& file_path,
const base::Closure& callback) {
return new FileTraceDataEndpoint(file_path, callback);
}
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(
const base::Callback<void(std::unique_ptr<const base::DictionaryValue>,
base::RefCountedString*)>& callback) {
return new StringTraceDataEndpoint(callback);
}
} // namespace content