| // 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(std::memory_order_relaxed); |
| 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 |