blob: 025619f2024222de0fbf833374a8e7aa273761e7 [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/strings/pattern.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 TracingController::TraceDataEndpoint {
public:
typedef base::Callback<void(scoped_ptr<const base::DictionaryValue>,
base::RefCountedString*)> CompletionCallback;
explicit StringTraceDataEndpoint(CompletionCallback callback)
: completion_callback_(callback) {}
void ReceiveTraceFinalContents(
scoped_ptr<const base::DictionaryValue> metadata,
const std::string& contents) override {
std::string tmp = contents;
scoped_refptr<base::RefCountedString> str =
base::RefCountedString::TakeString(&tmp);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(completion_callback_,
base::Passed(std::move(metadata)), str));
}
private:
~StringTraceDataEndpoint() override {}
CompletionCallback completion_callback_;
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_(NULL) {}
void ReceiveTraceChunk(const std::string& chunk) override {
std::string tmp = chunk;
scoped_refptr<base::RefCountedString> chunk_ptr =
base::RefCountedString::TakeString(&tmp);
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&FileTraceDataEndpoint::ReceiveTraceChunkOnFileThread, this,
chunk_ptr));
}
void ReceiveTraceFinalContents(
scoped_ptr<const base::DictionaryValue> ,
const std::string& contents) override {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&FileTraceDataEndpoint::CloseOnFileThread, this));
}
private:
~FileTraceDataEndpoint() override { DCHECK(file_ == NULL); }
void ReceiveTraceChunkOnFileThread(
const scoped_refptr<base::RefCountedString> chunk) {
if (!OpenFileIfNeededOnFileThread())
return;
ignore_result(
fwrite(chunk->data().c_str(), chunk->data().size(), 1, file_));
}
bool OpenFileIfNeededOnFileThread() {
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 CloseOnFileThread() {
if (OpenFileIfNeededOnFileThread()) {
base::CloseFile(file_);
file_ = NULL;
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&FileTraceDataEndpoint::FinalizeOnUIThread, this));
}
void FinalizeOnUIThread() { completion_callback_.Run(); }
base::FilePath file_path_;
base::Closure completion_callback_;
FILE* file_;
DISALLOW_COPY_AND_ASSIGN(FileTraceDataEndpoint);
};
class StringTraceDataSink : public TracingController::TraceDataSink {
public:
explicit StringTraceDataSink(
scoped_refptr<TracingController::TraceDataEndpoint> endpoint)
: endpoint_(endpoint) {}
void AddTraceChunk(const std::string& chunk) override {
std::string trace_string;
if (trace_.empty())
trace_string = "{\"" + std::string(kChromeTraceLabel) + "\":[";
else
trace_string = ",";
trace_string += chunk;
AddTraceChunkAndPassToEndpoint(trace_string);
}
void AddTraceChunkAndPassToEndpoint(const std::string& chunk) {
trace_ += chunk;
endpoint_->ReceiveTraceChunk(chunk);
}
void Close() override {
AddTraceChunkAndPassToEndpoint("]");
for (auto const &it : GetAgentTrace())
AddTraceChunkAndPassToEndpoint(",\"" + it.first + "\": " + it.second);
std::string metadataJSON;
if (base::JSONWriter::Write(*GetMetadataCopy(), &metadataJSON) &&
!metadataJSON.empty()) {
AddTraceChunkAndPassToEndpoint(
",\"" + std::string(kMetadataTraceLabel) + "\": " + metadataJSON);
}
AddTraceChunkAndPassToEndpoint("}");
endpoint_->ReceiveTraceFinalContents(GetMetadataCopy(), trace_);
}
private:
~StringTraceDataSink() override {}
scoped_refptr<TracingController::TraceDataEndpoint> endpoint_;
std::string trace_;
DISALLOW_COPY_AND_ASSIGN(StringTraceDataSink);
};
class CompressedStringTraceDataSink : public TracingController::TraceDataSink {
public:
explicit CompressedStringTraceDataSink(
scoped_refptr<TracingController::TraceDataEndpoint> endpoint)
: endpoint_(endpoint), already_tried_open_(false) {}
void AddTraceChunk(const std::string& chunk) override {
std::string tmp = chunk;
scoped_refptr<base::RefCountedString> chunk_ptr =
base::RefCountedString::TakeString(&tmp);
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&CompressedStringTraceDataSink::AddTraceChunkOnFileThread,
this, chunk_ptr));
}
void Close() override {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&CompressedStringTraceDataSink::CloseOnFileThread, this));
}
private:
~CompressedStringTraceDataSink() 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 AddTraceChunkOnFileThread(
const scoped_refptr<base::RefCountedString> chunk_ptr) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
std::string trace;
if (compressed_trace_data_.empty())
trace = "{\"" + std::string(kChromeTraceLabel) + "\":[";
else
trace = ",";
trace += chunk_ptr->data();
AddTraceChunkAndCompressOnFileThread(trace, false);
}
void AddTraceChunkAndCompressOnFileThread(const std::string& chunk,
bool finished) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
if (!OpenZStreamOnFileThread())
return;
const int kChunkSize = 0x4000;
char buffer[kChunkSize];
int err;
stream_->avail_in = chunk.size();
stream_->next_in = (unsigned char*)chunk.data();
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 && (err != Z_STREAM_END && finished)) {
stream_.reset();
return;
}
int bytes = kChunkSize - stream_->avail_out;
if (bytes) {
std::string compressed_chunk = std::string(buffer, bytes);
compressed_trace_data_ += compressed_chunk;
endpoint_->ReceiveTraceChunk(compressed_chunk);
}
} while (stream_->avail_out == 0);
}
void CloseOnFileThread() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
if (!OpenZStreamOnFileThread())
return;
if (compressed_trace_data_.empty()) {
AddTraceChunkAndCompressOnFileThread(
"{\"" + std::string(kChromeTraceLabel) + "\":[", false);
}
AddTraceChunkAndCompressOnFileThread("]", false);
for (auto const &it : GetAgentTrace()) {
AddTraceChunkAndCompressOnFileThread(
",\"" + it.first + "\": " + it.second, false);
}
std::string metadataJSON;
if (base::JSONWriter::Write(*GetMetadataCopy(), &metadataJSON) &&
!metadataJSON.empty()) {
AddTraceChunkAndCompressOnFileThread(
",\"" + std::string(kMetadataTraceLabel) + "\": " + metadataJSON,
false);
}
AddTraceChunkAndCompressOnFileThread("}", true);
deflateEnd(stream_.get());
stream_.reset();
endpoint_->ReceiveTraceFinalContents(GetMetadataCopy(),
compressed_trace_data_);
}
scoped_refptr<TracingController::TraceDataEndpoint> endpoint_;
scoped_ptr<z_stream> stream_;
bool already_tried_open_;
std::string compressed_trace_data_;
DISALLOW_COPY_AND_ASSIGN(CompressedStringTraceDataSink);
};
} // namespace
TracingController::TraceDataSink::TraceDataSink() {}
TracingController::TraceDataSink::~TraceDataSink() {}
void TracingController::TraceDataSink::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>&
TracingController::TraceDataSink::GetAgentTrace() const {
return additional_tracing_agent_trace_;
}
void TracingController::TraceDataSink::AddMetadata(
const base::DictionaryValue& data) {
metadata_.MergeDictionary(&data);
}
void TracingController::TraceDataSink::SetMetadataFilterPredicate(
const MetadataFilterPredicate& metadata_filter_predicate) {
metadata_filter_predicate_ = metadata_filter_predicate;
}
scoped_ptr<const base::DictionaryValue>
TracingController::TraceDataSink::GetMetadataCopy() const {
if (metadata_filter_predicate_.is_null())
return scoped_ptr<const base::DictionaryValue>(metadata_.DeepCopy());
scoped_ptr<base::DictionaryValue> metadata_copy(new base::DictionaryValue);
for (base::DictionaryValue::Iterator it(metadata_); !it.IsAtEnd();
it.Advance()) {
if (metadata_filter_predicate_.Run(it.key()))
metadata_copy->Set(it.key(), it.value().DeepCopy());
else
metadata_copy->SetString(it.key(), "__stripped__");
}
return std::move(metadata_copy);
}
scoped_refptr<TracingController::TraceDataSink>
TracingController::CreateStringSink(
const base::Callback<void(scoped_ptr<const base::DictionaryValue>,
base::RefCountedString*)>& callback) {
return new StringTraceDataSink(new StringTraceDataEndpoint(callback));
}
scoped_refptr<TracingController::TraceDataSink>
TracingController::CreateCompressedStringSink(
scoped_refptr<TracingController::TraceDataEndpoint> endpoint) {
return new CompressedStringTraceDataSink(endpoint);
}
scoped_refptr<TracingController::TraceDataSink>
TracingController::CreateFileSink(const base::FilePath& file_path,
const base::Closure& callback) {
return new StringTraceDataSink(
CreateFileEndpoint(file_path, callback));
}
scoped_refptr<TracingController::TraceDataEndpoint>
TracingController::CreateCallbackEndpoint(const base::Callback<
void(scoped_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);
}
} // namespace content