// 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/trace_event_argument.h"
#include <stdint.h>
#include <utility>
#include "base/bits.h"
#include "base/json/json_writer.h"
#include "base/trace_event/trace_event_memory_overhead.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 = '*';
#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;
}
} // namespace
TracedValue::TracedValue() : TracedValue(0) {
}
TracedValue::TracedValue(size_t capacity) {
DEBUG_PUSH_CONTAINER(kStackTypeDict);
if (capacity)
pickle_.Reserve(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);
pickle_.WriteBytes(&kTypeInt, 1);
pickle_.WriteInt(value);
WriteKeyNameAsRawPtr(pickle_, name);
}
void TracedValue::SetIntegerWithCopiedName(base::StringPiece name, int value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
pickle_.WriteBytes(&kTypeInt, 1);
pickle_.WriteInt(value);
WriteKeyNameWithCopy(pickle_, name);
}
void TracedValue::SetDouble(const char* name, double value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
pickle_.WriteBytes(&kTypeDouble, 1);
pickle_.WriteDouble(value);
WriteKeyNameAsRawPtr(pickle_, name);
}
void TracedValue::SetDoubleWithCopiedName(base::StringPiece name,
double value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
pickle_.WriteBytes(&kTypeDouble, 1);
pickle_.WriteDouble(value);
WriteKeyNameWithCopy(pickle_, name);
}
void TracedValue::SetBoolean(const char* name, bool value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
pickle_.WriteBytes(&kTypeBool, 1);
pickle_.WriteBool(value);
WriteKeyNameAsRawPtr(pickle_, name);
}
void TracedValue::SetBooleanWithCopiedName(base::StringPiece name,
bool value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
pickle_.WriteBytes(&kTypeBool, 1);
pickle_.WriteBool(value);
WriteKeyNameWithCopy(pickle_, name);
}
void TracedValue::SetString(const char* name, base::StringPiece value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
pickle_.WriteBytes(&kTypeString, 1);
pickle_.WriteString(value);
WriteKeyNameAsRawPtr(pickle_, name);
}
void TracedValue::SetStringWithCopiedName(base::StringPiece name,
base::StringPiece value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
pickle_.WriteBytes(&kTypeString, 1);
pickle_.WriteString(value);
WriteKeyNameWithCopy(pickle_, name);
}
void TracedValue::SetValue(const char* name, const TracedValue& value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
BeginDictionary(name);
pickle_.WriteBytes(value.pickle_.payload(),
static_cast<int>(value.pickle_.payload_size()));
EndDictionary();
}
void TracedValue::SetValueWithCopiedName(base::StringPiece name,
const TracedValue& value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
BeginDictionaryWithCopiedName(name);
pickle_.WriteBytes(value.pickle_.payload(),
static_cast<int>(value.pickle_.payload_size()));
EndDictionary();
}
void TracedValue::BeginDictionary(const char* name) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_PUSH_CONTAINER(kStackTypeDict);
pickle_.WriteBytes(&kTypeStartDict, 1);
WriteKeyNameAsRawPtr(pickle_, name);
}
void TracedValue::BeginDictionaryWithCopiedName(base::StringPiece name) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_PUSH_CONTAINER(kStackTypeDict);
pickle_.WriteBytes(&kTypeStartDict, 1);
WriteKeyNameWithCopy(pickle_, name);
}
void TracedValue::BeginArray(const char* name) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_PUSH_CONTAINER(kStackTypeArray);
pickle_.WriteBytes(&kTypeStartArray, 1);
WriteKeyNameAsRawPtr(pickle_, name);
}
void TracedValue::BeginArrayWithCopiedName(base::StringPiece name) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_PUSH_CONTAINER(kStackTypeArray);
pickle_.WriteBytes(&kTypeStartArray, 1);
WriteKeyNameWithCopy(pickle_, name);
}
void TracedValue::EndDictionary() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DEBUG_POP_CONTAINER();
pickle_.WriteBytes(&kTypeEndDict, 1);
}
void TracedValue::AppendInteger(int value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
pickle_.WriteBytes(&kTypeInt, 1);
pickle_.WriteInt(value);
}
void TracedValue::AppendDouble(double value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
pickle_.WriteBytes(&kTypeDouble, 1);
pickle_.WriteDouble(value);
}
void TracedValue::AppendBoolean(bool value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
pickle_.WriteBytes(&kTypeBool, 1);
pickle_.WriteBool(value);
}
void TracedValue::AppendString(base::StringPiece value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
pickle_.WriteBytes(&kTypeString, 1);
pickle_.WriteString(value);
}
void TracedValue::BeginArray() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
DEBUG_PUSH_CONTAINER(kStackTypeArray);
pickle_.WriteBytes(&kTypeStartArray, 1);
}
void TracedValue::BeginDictionary() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
DEBUG_PUSH_CONTAINER(kStackTypeDict);
pickle_.WriteBytes(&kTypeStartDict, 1);
}
void TracedValue::EndArray() {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
DEBUG_POP_CONTAINER();
pickle_.WriteBytes(&kTypeEndArray, 1);
}
void TracedValue::SetValue(const char* name, scoped_ptr<base::Value> value) {
SetBaseValueWithCopiedName(name, *value);
}
void TracedValue::SetBaseValueWithCopiedName(base::StringPiece name,
const base::Value& value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
switch (value.GetType()) {
case base::Value::TYPE_NULL:
case base::Value::TYPE_BINARY:
NOTREACHED();
break;
case base::Value::TYPE_BOOLEAN: {
bool bool_value;
value.GetAsBoolean(&bool_value);
SetBooleanWithCopiedName(name, bool_value);
} break;
case base::Value::TYPE_INTEGER: {
int int_value;
value.GetAsInteger(&int_value);
SetIntegerWithCopiedName(name, int_value);
} break;
case base::Value::TYPE_DOUBLE: {
double double_value;
value.GetAsDouble(&double_value);
SetDoubleWithCopiedName(name, double_value);
} break;
case base::Value::TYPE_STRING: {
const StringValue* string_value;
value.GetAsString(&string_value);
SetStringWithCopiedName(name, string_value->GetString());
} break;
case base::Value::TYPE_DICTIONARY: {
const DictionaryValue* dict_value;
value.GetAsDictionary(&dict_value);
BeginDictionaryWithCopiedName(name);
for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd();
it.Advance()) {
SetBaseValueWithCopiedName(it.key(), it.value());
}
EndDictionary();
} break;
case base::Value::TYPE_LIST: {
const ListValue* list_value;
value.GetAsList(&list_value);
BeginArrayWithCopiedName(name);
for (base::Value* base_value : *list_value)
AppendBaseValue(*base_value);
EndArray();
} break;
}
}
void TracedValue::AppendBaseValue(const base::Value& value) {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
switch (value.GetType()) {
case base::Value::TYPE_NULL:
case base::Value::TYPE_BINARY:
NOTREACHED();
break;
case base::Value::TYPE_BOOLEAN: {
bool bool_value;
value.GetAsBoolean(&bool_value);
AppendBoolean(bool_value);
} break;
case base::Value::TYPE_INTEGER: {
int int_value;
value.GetAsInteger(&int_value);
AppendInteger(int_value);
} break;
case base::Value::TYPE_DOUBLE: {
double double_value;
value.GetAsDouble(&double_value);
AppendDouble(double_value);
} break;
case base::Value::TYPE_STRING: {
const StringValue* string_value;
value.GetAsString(&string_value);
AppendString(string_value->GetString());
} break;
case base::Value::TYPE_DICTIONARY: {
const DictionaryValue* dict_value;
value.GetAsDictionary(&dict_value);
BeginDictionary();
for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd();
it.Advance()) {
SetBaseValueWithCopiedName(it.key(), it.value());
}
EndDictionary();
} break;
case base::Value::TYPE_LIST: {
const ListValue* list_value;
value.GetAsList(&list_value);
BeginArray();
for (base::Value* base_value : *list_value)
AppendBaseValue(*base_value);
EndArray();
} break;
}
}
scoped_ptr<base::Value> TracedValue::ToBaseValue() const {
scoped_ptr<DictionaryValue> root(new DictionaryValue);
DictionaryValue* cur_dict = root.get();
ListValue* 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: {
auto new_dict = new DictionaryValue();
if (cur_dict) {
cur_dict->SetWithoutPathExpansion(ReadKeyName(it),
make_scoped_ptr(new_dict));
stack.push_back(cur_dict);
cur_dict = new_dict;
} else {
cur_list->Append(make_scoped_ptr(new_dict));
stack.push_back(cur_list);
cur_list = nullptr;
cur_dict = new_dict;
}
} break;
case kTypeEndArray:
case kTypeEndDict: {
if (stack.back()->GetAsDictionary(&cur_dict)) {
cur_list = nullptr;
} else if (stack.back()->GetAsList(&cur_list)) {
cur_dict = nullptr;
}
stack.pop_back();
} break;
case kTypeStartArray: {
auto new_list = new ListValue();
if (cur_dict) {
cur_dict->SetWithoutPathExpansion(ReadKeyName(it),
make_scoped_ptr(new_list));
stack.push_back(cur_dict);
cur_dict = nullptr;
cur_list = new_list;
} else {
cur_list->Append(make_scoped_ptr(new_list));
stack.push_back(cur_list);
cur_list = new_list;
}
} break;
case kTypeBool: {
bool value;
CHECK(it.ReadBool(&value));
if (cur_dict) {
cur_dict->SetBooleanWithoutPathExpansion(ReadKeyName(it), value);
} else {
cur_list->AppendBoolean(value);
}
} break;
case kTypeInt: {
int value;
CHECK(it.ReadInt(&value));
if (cur_dict) {
cur_dict->SetIntegerWithoutPathExpansion(ReadKeyName(it), value);
} else {
cur_list->AppendInteger(value);
}
} break;
case kTypeDouble: {
double value;
CHECK(it.ReadDouble(&value));
if (cur_dict) {
cur_dict->SetDoubleWithoutPathExpansion(ReadKeyName(it), value);
} else {
cur_list->AppendDouble(value);
}
} break;
case kTypeString: {
std::string value;
CHECK(it.ReadString(&value));
if (cur_dict) {
cur_dict->SetStringWithoutPathExpansion(ReadKeyName(it), value);
} else {
cur_list->AppendString(value);
}
} break;
default:
NOTREACHED();
}
}
DCHECK(stack.empty());
return std::move(root);
}
void TracedValue::AppendAsTraceFormat(std::string* out) const {
DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
DCHECK_CONTAINER_STACK_DEPTH_EQ(1u);
// TODO(primiano): this could be smarter, skip the ToBaseValue encoding and
// produce the JSON on its own. This will require refactoring JSONWriter
// to decouple the base::Value traversal from the JSON writing bits
std::string tmp;
JSONWriter::Write(*ToBaseValue(), &tmp);
*out += tmp;
}
void TracedValue::EstimateTraceMemoryOverhead(
TraceEventMemoryOverhead* overhead) {
const size_t kPickleHeapAlign = 4096; // Must be == Pickle::kPickleHeapAlign.
overhead->Add("TracedValue",
/* allocated size */
bits::Align(pickle_.GetTotalAllocatedSize(), kPickleHeapAlign),
/* resident size */
bits::Align(pickle_.size(), kPickleHeapAlign));
}
} // namespace trace_event
} // namespace base