blob: 013da8667d23104e0c1995ef794803b3c09e3c19 [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/ptr_util.h"
#include "base/strings/pattern.h"
#include "base/task_scheduler/post_task.h"
#include "content/browser/tracing/tracing_controller_impl.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/zlib/zlib.h"
namespace content {
namespace {
const char kChromeTraceLabel[] = "traceEvents";
const char kMetadataTraceLabel[] = "metadata";
class StringTraceDataEndpoint : public 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);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(completion_callback_, base::Passed(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 TraceDataEndpoint {
public:
explicit FileTraceDataEndpoint(const base::FilePath& trace_file_path,
const base::Closure& callback)
: file_path_(trace_file_path),
completion_callback_(callback),
file_(NULL) {}
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
background_task_runner_->PostTask(
FROM_HERE,
base::Bind(&FileTraceDataEndpoint::ReceiveTraceChunkOnBlockingThread,
this, base::Passed(std::move(chunk))));
}
void ReceiveTraceFinalContents(
std::unique_ptr<const base::DictionaryValue>) override {
background_task_runner_->PostTask(
FROM_HERE,
base::Bind(&FileTraceDataEndpoint::CloseOnBlockingThread, this));
}
private:
~FileTraceDataEndpoint() override { DCHECK(file_ == NULL); }
void ReceiveTraceChunkOnBlockingThread(std::unique_ptr<std::string> chunk) {
if (!OpenFileIfNeededOnBlockingThread())
return;
ignore_result(fwrite(chunk->c_str(), chunk->size(), 1, file_));
}
bool OpenFileIfNeededOnBlockingThread() {
base::ThreadRestrictions::AssertIOAllowed();
if (file_ != NULL)
return true;
file_ = base::OpenFile(file_path_, "w");
if (file_ == NULL) {
LOG(ERROR) << "Failed to open " << file_path_.value();
return false;
}
return true;
}
void CloseOnBlockingThread() {
if (OpenFileIfNeededOnBlockingThread()) {
base::CloseFile(file_);
file_ = NULL;
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
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::BACKGROUND});
DISALLOW_COPY_AND_ASSIGN(FileTraceDataEndpoint);
};
class TraceDataSinkImplBase : public TracingController::TraceDataSink {
public:
void AddAgentTrace(const std::string& trace_label,
const std::string& trace_data) override;
void AddMetadata(std::unique_ptr<base::DictionaryValue> data) override;
protected:
TraceDataSinkImplBase() : metadata_(new base::DictionaryValue()) {}
~TraceDataSinkImplBase() override {}
// Get a map of TracingAgent's data, which is previously added by
// AddAgentTrace(). The map's key is the trace label and the map's value is
// the trace data.
const std::map<std::string, std::string>& GetAgentTrace() const;
std::unique_ptr<base::DictionaryValue> TakeMetadata();
private:
std::map<std::string, std::string> additional_tracing_agent_trace_;
std::unique_ptr<base::DictionaryValue> metadata_;
DISALLOW_COPY_AND_ASSIGN(TraceDataSinkImplBase);
};
class JSONTraceDataSink : public TraceDataSinkImplBase {
public:
explicit JSONTraceDataSink(scoped_refptr<TraceDataEndpoint> endpoint)
: endpoint_(endpoint), had_received_first_chunk_(false) {}
void AddTraceChunk(const std::string& chunk) override {
std::string trace_string;
if (had_received_first_chunk_)
trace_string = ",";
else
trace_string = "{\"" + std::string(kChromeTraceLabel) + "\":[";
trace_string += chunk;
had_received_first_chunk_ = true;
endpoint_->ReceiveTraceChunk(base::MakeUnique<std::string>(trace_string));
}
void Close() override {
endpoint_->ReceiveTraceChunk(base::MakeUnique<std::string>("]"));
for (auto const &it : GetAgentTrace())
endpoint_->ReceiveTraceChunk(
base::MakeUnique<std::string>(",\"" + it.first + "\": " + it.second));
std::unique_ptr<base::DictionaryValue> metadata(TakeMetadata());
std::string metadataJSON;
if (base::JSONWriter::Write(*metadata, &metadataJSON) &&
!metadataJSON.empty()) {
endpoint_->ReceiveTraceChunk(base::MakeUnique<std::string>(
",\"" + std::string(kMetadataTraceLabel) + "\": " + metadataJSON));
}
endpoint_->ReceiveTraceChunk(base::MakeUnique<std::string>("}"));
endpoint_->ReceiveTraceFinalContents(std::move(metadata));
}
private:
~JSONTraceDataSink() override {}
scoped_refptr<TraceDataEndpoint> endpoint_;
bool had_received_first_chunk_;
DISALLOW_COPY_AND_ASSIGN(JSONTraceDataSink);
};
class CompressedTraceDataEndpoint : public TraceDataEndpoint {
public:
explicit CompressedTraceDataEndpoint(
scoped_refptr<TraceDataEndpoint> endpoint)
: endpoint_(endpoint), already_tried_open_(false) {}
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::BindOnce(&CompressedTraceDataEndpoint::CompressOnFileThread, this,
base::Passed(std::move(chunk))));
}
void ReceiveTraceFinalContents(
std::unique_ptr<const base::DictionaryValue> metadata) override {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::BindOnce(&CompressedTraceDataEndpoint::CloseOnFileThread, this,
base::Passed(std::move(metadata))));
}
private:
~CompressedTraceDataEndpoint() override {}
bool OpenZStreamOnFileThread() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
if (stream_)
return true;
if (already_tried_open_)
return false;
already_tried_open_ = true;
stream_.reset(new z_stream);
*stream_ = {0};
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 CompressOnFileThread(std::unique_ptr<std::string> chunk) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
if (!OpenZStreamOnFileThread())
return;
stream_->avail_in = chunk->size();
stream_->next_in = reinterpret_cast<unsigned char*>(&*chunk->begin());
DrainStreamOnFileThread(false);
}
void DrainStreamOnFileThread(bool finished) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
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(base::MakeUnique<std::string>(compressed));
}
} while (stream_->avail_out == 0);
}
void CloseOnFileThread(
std::unique_ptr<const base::DictionaryValue> metadata) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
if (!OpenZStreamOnFileThread())
return;
DrainStreamOnFileThread(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_;
DISALLOW_COPY_AND_ASSIGN(CompressedTraceDataEndpoint);
};
} // namespace
TracingController::TraceDataSink::TraceDataSink() {}
TracingController::TraceDataSink::~TraceDataSink() {}
void TraceDataSinkImplBase::AddAgentTrace(const std::string& trace_label,
const std::string& trace_data) {
DCHECK(additional_tracing_agent_trace_.find(trace_label) ==
additional_tracing_agent_trace_.end());
additional_tracing_agent_trace_[trace_label] = trace_data;
}
const std::map<std::string, std::string>& TraceDataSinkImplBase::GetAgentTrace()
const {
return additional_tracing_agent_trace_;
}
void TraceDataSinkImplBase::AddMetadata(
std::unique_ptr<base::DictionaryValue> data) {
metadata_->MergeDictionary(data.get());
}
std::unique_ptr<base::DictionaryValue> TraceDataSinkImplBase::TakeMetadata() {
return std::move(metadata_);
}
scoped_refptr<TracingController::TraceDataSink>
TracingController::CreateStringSink(
const base::Callback<void(std::unique_ptr<const base::DictionaryValue>,
base::RefCountedString*)>& callback) {
return new JSONTraceDataSink(new StringTraceDataEndpoint(callback));
}
scoped_refptr<TracingController::TraceDataSink>
TracingController::CreateFileSink(const base::FilePath& file_path,
const base::Closure& callback) {
return new JSONTraceDataSink(new FileTraceDataEndpoint(file_path, callback));
}
scoped_refptr<TracingController::TraceDataSink>
TracingControllerImpl::CreateCompressedStringSink(
scoped_refptr<TraceDataEndpoint> endpoint) {
return new JSONTraceDataSink(new CompressedTraceDataEndpoint(endpoint));
}
scoped_refptr<TraceDataEndpoint> TracingControllerImpl::CreateCallbackEndpoint(
const base::Callback<void(std::unique_ptr<const base::DictionaryValue>,
base::RefCountedString*)>& callback) {
return new StringTraceDataEndpoint(callback);
}
scoped_refptr<TracingController::TraceDataSink>
TracingControllerImpl::CreateJSONSink(
scoped_refptr<TraceDataEndpoint> endpoint) {
return new JSONTraceDataSink(endpoint);
}
} // namespace content