blob: 4addbdad807c4db7d7231dbeebd9c5f381738f2d [file] [log] [blame]
// Copyright (c) 2014 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 "base/trace_event/traced_value.h"
#include <stdint.h>
#include <atomic>
#include <utility>
#include "base/bits.h"
#include "base/containers/circular_deque.h"
#include "base/json/string_escape.h"
#include "base/memory/ptr_util.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_impl.h"
#include "base/trace_event/trace_event_memory_overhead.h"
#include "base/trace_event/trace_log.h"
#include "base/values.h"
namespace base {
namespace trace_event {
namespace {
const char kTypeStartDict = '{';
const char kTypeEndDict = '}';
const char kTypeStartArray = '[';
const char kTypeEndArray = ']';
const char kTypeBool = 'b';
const char kTypeInt = 'i';
const char kTypeDouble = 'd';
const char kTypeString = 's';
const char kTypeCStr = '*'; // only used for key names
std::atomic<TracedValue::WriterFactoryCallback> g_writer_factory_callback;
#ifndef NDEBUG
const bool kStackTypeDict = false;
const bool kStackTypeArray = true;
#define DCHECK_CURRENT_CONTAINER_IS(x) DCHECK_EQ(x, nesting_stack_.back())
#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) DCHECK_EQ(x, nesting_stack_.size())
#define DEBUG_PUSH_CONTAINER(x) nesting_stack_.push_back(x)
#define DEBUG_POP_CONTAINER() nesting_stack_.pop_back()
#else
#define DCHECK_CURRENT_CONTAINER_IS(x) \
do { \
} while (0)
#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) \
do { \
} while (0)
#define DEBUG_PUSH_CONTAINER(x) \
do { \
} while (0)
#define DEBUG_POP_CONTAINER() \
do { \
} while (0)
#endif
inline void WriteKeyNameAsRawPtr(Pickle& pickle, const char* ptr) {
pickle.WriteBytes(&kTypeCStr, 1);
pickle.WriteUInt64(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(ptr)));
}
inline void WriteKeyNameWithCopy(Pickle& pickle, base::StringPiece str) {
pickle.WriteBytes(&kTypeString, 1);
pickle.WriteString(str);
}
std::string ReadKeyName(PickleIterator& pickle_iterator) {
const char* type = nullptr;
bool res = pickle_iterator.ReadBytes(&type, 1);
std::string key_name;
if (res && *type == kTypeCStr) {
uint64_t ptr_value = 0;
res = pickle_iterator.ReadUInt64(&ptr_value);
key_name = reinterpret_cast<const char*>(static_cast<uintptr_t>(ptr_value));
} else if (res && *type == kTypeString) {
res = pickle_iterator.ReadString(&key_name);
}
DCHECK(res);
return key_name;
}
class PickleWriter final : public TracedValue::Writer {
public:
explicit PickleWriter(size_t capacity) {
if (capacity) {
pickle_.Reserve(capacity);
}
}
bool IsPickleWriter() const override { return true; }
bool IsProtoWriter() const override { return false; }
void SetInteger(const char* name, int value) override {
pickle_.WriteBytes(&kTypeInt, 1);
pickle_.WriteInt(value);
WriteKeyNameAsRawPtr(pickle_, name);
}
void SetIntegerWithCopiedName(base::StringPiece name, int value) override {
pickle_.WriteBytes(&kTypeInt, 1);
pickle_.WriteInt(value);
WriteKeyNameWithCopy(pickle_, name);
}
void SetDouble(const char* name, double value) override {
pickle_.WriteBytes(&kTypeDouble, 1);
pickle_.WriteDouble(value);
WriteKeyNameAsRawPtr(pickle_, name);
}
void SetDoubleWithCopiedName(base::StringPiece name, double value) override {
pickle_.WriteBytes(&kTypeDouble, 1);
pickle_.WriteDouble(value);
WriteKeyNameWithCopy(pickle_, name);
}
void SetBoolean(const char* name, bool value) override {
pickle_.WriteBytes(&kTypeBool, 1);
pickle_.WriteBool(value);
WriteKeyNameAsRawPtr(pickle_, name);
}
void SetBooleanWithCopiedName(base::StringPiece name, bool value) override {
pickle_.WriteBytes(&kTypeBool, 1);
pickle_.WriteBool(value);
WriteKeyNameWithCopy(pickle_, name);
}
void SetString(const char* name, base::StringPiece value) override {
pickle_.WriteBytes(&kTypeString, 1);
pickle_.WriteString(value);
WriteKeyNameAsRawPtr(pickle_, name);
}
void SetStringWithCopiedName(base::StringPiece name,
base::StringPiece value) override {
pickle_.WriteBytes(&kTypeString, 1);
pickle_.WriteString(value);
WriteKeyNameWithCopy(pickle_, name);
}
void SetValue(const char* name, Writer* value) override {
DCHECK(value->IsPickleWriter());
const PickleWriter* pickle_writer = static_cast<const PickleWriter*>(value);
BeginDictionary(name);
pickle_.WriteBytes(pickle_writer->pickle_.payload(),
static_cast<int>(pickle_writer->pickle_.payload_size()));
EndDictionary();
}
void SetValueWithCopiedName(base::StringPiece name, Writer* value) override {
DCHECK(value->IsPickleWriter());
const PickleWriter* pickle_writer = static_cast<const PickleWriter*>(value);
BeginDictionaryWithCopiedName(name);
pickle_.WriteBytes(pickle_writer->pickle_.payload(),
static_cast<int>(pickle_writer->pickle_.payload_size()));
EndDictionary();
}
void BeginArray() override { pickle_.WriteBytes(&kTypeStartArray, 1); }
void BeginDictionary() override { pickle_.WriteBytes(&kTypeStartDict, 1); }
void BeginDictionary(const char* name) override {
pickle_.WriteBytes(&kTypeStartDict, 1);
WriteKeyNameAsRawPtr(pickle_, name);
}
void BeginDictionaryWithCopiedName(base::StringPiece name) override {
pickle_.WriteBytes(&kTypeStartDict, 1);
WriteKeyNameWithCopy(pickle_, name);
}
void BeginArray(const char* name) override {
pickle_.WriteBytes(&kTypeStartArray, 1);
WriteKeyNameAsRawPtr(pickle_, name);
}
void BeginArrayWithCopiedName(base::StringPiece name) override {
pickle_.WriteBytes(&kTypeStartArray, 1);
WriteKeyNameWithCopy(pickle_, name);
}
void EndDictionary() override { pickle_.WriteBytes(&kTypeEndDict, 1); }
void EndArray() override { pickle_.WriteBytes(&kTypeEndArray, 1); }
void AppendInteger(int value) override {
pickle_.WriteBytes(&kTypeInt, 1);
pickle_.WriteInt(value);
}
void AppendDouble(double value) override {
pickle_.WriteBytes(&kTypeDouble, 1);
pickle_.WriteDouble(value);
}
void AppendBoolean(bool value) override {
pickle_.WriteBytes(&kTypeBool, 1);
pickle_.WriteBool(value);
}
void AppendString(base::StringPiece value) override {
pickle_.WriteBytes(&kTypeString, 1);
pickle_.WriteString(value);
}
void AppendAsTraceFormat(std::string* out) const override {
struct State {
enum Type { kTypeDict, kTypeArray };
Type type;
bool needs_comma;
};
auto maybe_append_key_name = [](State current_state, PickleIterator* it,
std::string* out) {
if (current_state.type == State::kTypeDict) {
EscapeJSONString(ReadKeyName(*it), true, out);
out->append(":");
}
};
base::circular_deque<State> state_stack;
out->append("{");
state_stack.push_back({State::kTypeDict});
PickleIterator it(pickle_);
for (const char* type; it.ReadBytes(&type, 1);) {
switch (*type) {
case kTypeEndDict:
out->append("}");
state_stack.pop_back();
continue;
case kTypeEndArray:
out->append("]");
state_stack.pop_back();
continue;
}
// Use an index so it will stay valid across resizes.
size_t current_state_index = state_stack.size() - 1;
if (state_stack[current_state_index].needs_comma) {
out->append(",");
}
switch (*type) {
case kTypeStartDict: {
maybe_append_key_name(state_stack[current_state_index], &it, out);
out->append("{");
state_stack.push_back({State::kTypeDict});
break;
}
case kTypeStartArray: {
maybe_append_key_name(state_stack[current_state_index], &it, out);
out->append("[");
state_stack.push_back({State::kTypeArray});
break;
}
case kTypeBool: {
TraceEvent::TraceValue json_value;
CHECK(it.ReadBool(&json_value.as_bool));
maybe_append_key_name(state_stack[current_state_index], &it, out);
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_BOOL, json_value, out);
break;
}
case kTypeInt: {
int value;
CHECK(it.ReadInt(&value));
maybe_append_key_name(state_stack[current_state_index], &it, out);
TraceEvent::TraceValue json_value;
json_value.as_int = value;
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_INT, json_value, out);
break;
}
case kTypeDouble: {
TraceEvent::TraceValue json_value;
CHECK(it.ReadDouble(&json_value.as_double));
maybe_append_key_name(state_stack[current_state_index], &it, out);
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_DOUBLE, json_value,
out);
break;
}
case kTypeString: {
std::string value;
CHECK(it.ReadString(&value));
maybe_append_key_name(state_stack[current_state_index], &it, out);
TraceEvent::TraceValue json_value;
json_value.as_string = value.c_str();
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_STRING, json_value,
out);
break;
}
default:
NOTREACHED();
}
state_stack[current_state_index].needs_comma = true;
}
out->append("}");
state_stack.pop_back();
DCHECK(state_stack.empty());
}
void EstimateTraceMemoryOverhead(
TraceEventMemoryOverhead* overhead) override {
overhead->Add(TraceEventMemoryOverhead::kTracedValue,
/* allocated size */
pickle_.GetTotalAllocatedSize(),
/* resident size */
pickle_.size());
}
std::unique_ptr<base::Value> ToBaseValue() const override {
base::Value root(base::Value::Type::DICTIONARY);
Value* cur_dict = &root;
Value* cur_list = nullptr;
std::vector<Value*> stack;
PickleIterator it(pickle_);
const char* type;
while (it.ReadBytes(&type, 1)) {
DCHECK((cur_dict && !cur_list) || (cur_list && !cur_dict));
switch (*type) {
case kTypeStartDict: {
base::Value new_dict(base::Value::Type::DICTIONARY);
if (cur_dict) {
stack.push_back(cur_dict);
cur_dict = cur_dict->SetKey(ReadKeyName(it), std::move(new_dict));
} else {
cur_list->GetList().push_back(std::move(new_dict));
// |new_dict| is invalidated at this point, so |cur_dict| needs to
// be reset.
cur_dict = &cur_list->GetList().back();
stack.push_back(cur_list);
cur_list = nullptr;
}
} break;
case kTypeEndArray:
case kTypeEndDict: {
if (stack.back()->is_dict()) {
cur_dict = stack.back();
cur_list = nullptr;
} else if (stack.back()->is_list()) {
cur_list = stack.back();
cur_dict = nullptr;
}
stack.pop_back();
} break;
case kTypeStartArray: {
base::Value new_list(base::Value::Type::LIST);
if (cur_dict) {
stack.push_back(cur_dict);
cur_list = cur_dict->SetKey(ReadKeyName(it), std::move(new_list));
cur_dict = nullptr;
} else {
cur_list->GetList().push_back(std::move(new_list));
stack.push_back(cur_list);
// |cur_list| is invalidated at this point by the Append, so it
// needs to be reset.
cur_list = &cur_list->GetList().back();
}
} break;
case kTypeBool: {
bool value;
CHECK(it.ReadBool(&value));
base::Value new_bool(value);
if (cur_dict) {
cur_dict->SetKey(ReadKeyName(it), std::move(new_bool));
} else {
cur_list->GetList().push_back(std::move(new_bool));
}
} break;
case kTypeInt: {
int value;
CHECK(it.ReadInt(&value));
base::Value new_int(value);
if (cur_dict) {
cur_dict->SetKey(ReadKeyName(it), std::move(new_int));
} else {
cur_list->GetList().push_back(std::move(new_int));
}
} break;
case kTypeDouble: {
double value;
CHECK(it.ReadDouble(&value));
base::Value new_double(value);
if (cur_dict) {
cur_dict->SetKey(ReadKeyName(it), std::move(new_double));
} else {
cur_list->GetList().push_back(std::move(new_double));
}
} break;
case kTypeString: {
std::string value;
CHECK(it.ReadString(&value));
base::Value new_str(std::move(value));
if (cur_dict) {
cur_dict->SetKey(ReadKeyName(it), std::move(new_str));
} else {
cur_list->GetList().push_back(std::move(new_str));
}
} break;
default:
NOTREACHED();
}
}
DCHECK(stack.empty());
return base::Value::ToUniquePtrValue(std::move(root));
}
private:
Pickle pickle_;
};
std::unique_ptr<TracedValue::Writer> CreateWriter(size_t capacity) {
TracedValue::WriterFactoryCallback callback =
g_writer_factory_callback.load();
if (callback) {
return callback(capacity);
}
return std::make_unique<PickleWriter>(capacity);
}
} // namespace
bool TracedValue::Writer::AppendToProto(ProtoAppender* appender) {
return false;
}
// static
void TracedValue::SetWriterFactoryCallback(WriterFactoryCallback callback) {
g_writer_factory_callback.store(callback);
}
TracedValue::TracedValue() : TracedValue(0) {}
TracedValue::TracedValue(size_t capacity) {
DEBUG_PUSH_CONTAINER(kStackTypeDict);
writer_ = CreateWriter(capacity);
}
TracedValue::~TracedValue() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_POP_CONTAINER();
DCHECK_CONTAINER_STACK_DEPTH_EQ(0u);
}
void TracedValue::SetInteger(const char* name, int value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetInteger(name, value);
}
void TracedValue::SetIntegerWithCopiedName(base::StringPiece name, int value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetIntegerWithCopiedName(name, value);
}
void TracedValue::SetDouble(const char* name, double value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetDouble(name, value);
}
void TracedValue::SetDoubleWithCopiedName(base::StringPiece name,
double value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetDoubleWithCopiedName(name, value);
}
void TracedValue::SetBoolean(const char* name, bool value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetBoolean(name, value);
}
void TracedValue::SetBooleanWithCopiedName(base::StringPiece name, bool value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetBooleanWithCopiedName(name, value);
}
void TracedValue::SetString(const char* name, base::StringPiece value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetString(name, value);
}
void TracedValue::SetStringWithCopiedName(base::StringPiece name,
base::StringPiece value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetStringWithCopiedName(name, value);
}
void TracedValue::SetValue(const char* name, TracedValue* value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetValue(name, value->writer_.get());
}
void TracedValue::SetValueWithCopiedName(base::StringPiece name,
TracedValue* value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
writer_->SetValueWithCopiedName(name, value->writer_.get());
}
void TracedValue::BeginDictionary(const char* name) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_PUSH_CONTAINER(kStackTypeDict);
writer_->BeginDictionary(name);
}
void TracedValue::BeginDictionaryWithCopiedName(base::StringPiece name) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_PUSH_CONTAINER(kStackTypeDict);
writer_->BeginDictionaryWithCopiedName(name);
}
void TracedValue::BeginArray(const char* name) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_PUSH_CONTAINER(kStackTypeArray);
writer_->BeginArray(name);
}
void TracedValue::BeginArrayWithCopiedName(base::StringPiece name) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_PUSH_CONTAINER(kStackTypeArray);
writer_->BeginArrayWithCopiedName(name);
}
void TracedValue::AppendInteger(int value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
writer_->AppendInteger(value);
}
void TracedValue::AppendDouble(double value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
writer_->AppendDouble(value);
}
void TracedValue::AppendBoolean(bool value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
writer_->AppendBoolean(value);
}
void TracedValue::AppendString(base::StringPiece value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
writer_->AppendString(value);
}
void TracedValue::BeginArray() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
DEBUG_PUSH_CONTAINER(kStackTypeArray);
writer_->BeginArray();
}
void TracedValue::BeginDictionary() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
DEBUG_PUSH_CONTAINER(kStackTypeDict);
writer_->BeginDictionary();
}
void TracedValue::EndArray() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
DEBUG_POP_CONTAINER();
writer_->EndArray();
}
void TracedValue::EndDictionary() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_POP_CONTAINER();
writer_->EndDictionary();
}
std::unique_ptr<base::Value> TracedValue::ToBaseValue() const {
return writer_->ToBaseValue();
}
void TracedValue::AppendAsTraceFormat(std::string* out) const {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DCHECK_CONTAINER_STACK_DEPTH_EQ(1u);
writer_->AppendAsTraceFormat(out);
}
bool TracedValue::AppendToProto(ProtoAppender* appender) {
return writer_->AppendToProto(appender);
}
void TracedValue::EstimateTraceMemoryOverhead(
TraceEventMemoryOverhead* overhead) {
writer_->EstimateTraceMemoryOverhead(overhead);
}
} // namespace trace_event
} // namespace base