| // 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 <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/values.h" |
| #include "starboard/types.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 |
| |
| #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, |
| std::unique_ptr<base::Value> value) { |
| SetBaseValueWithCopiedName(name, *value); |
| } |
| |
| void TracedValue::SetBaseValueWithCopiedName(base::StringPiece name, |
| const base::Value& value) { |
| DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); |
| switch (value.type()) { |
| case base::Value::Type::NONE: |
| 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 Value* 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 (const auto& base_value : *list_value) |
| AppendBaseValue(base_value); |
| EndArray(); |
| } break; |
| } |
| } |
| |
| void TracedValue::AppendBaseValue(const base::Value& value) { |
| DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); |
| switch (value.type()) { |
| case base::Value::Type::NONE: |
| 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 Value* 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 (const auto& base_value : *list_value) |
| AppendBaseValue(base_value); |
| EndArray(); |
| } break; |
| } |
| } |
| |
| std::unique_ptr<base::Value> TracedValue::ToBaseValue() const { |
| 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)); |
| } |
| |
| void TracedValue::AppendAsTraceFormat(std::string* out) const { |
| DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); |
| DCHECK_CONTAINER_STACK_DEPTH_EQ(1u); |
| |
| 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()); |
| } |
| |
| #if !defined(STARBOARD) |
| void TracedValue::EstimateTraceMemoryOverhead( |
| TraceEventMemoryOverhead* overhead) { |
| overhead->Add(TraceEventMemoryOverhead::kTracedValue, |
| /* allocated size */ |
| pickle_.GetTotalAllocatedSize(), |
| /* resident size */ |
| pickle_.size()); |
| } |
| #endif |
| |
| } // namespace trace_event |
| } // namespace base |