web_engine_memory_inspector.cc 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "fuchsia_web/webengine/browser/web_engine_memory_inspector.h"
  5. #include <lib/fpromise/promise.h>
  6. #include <lib/inspect/cpp/inspector.h>
  7. #include <sstream>
  8. #include "base/no_destructor.h"
  9. #include "base/strings/string_number_conversions.h"
  10. #include "base/trace_event/memory_dump_request_args.h"
  11. #include "components/fuchsia_component_support/config_reader.h"
  12. #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
  13. namespace {
  14. std::vector<std::string> GetAllocatorDumpNamesFromConfig() {
  15. const absl::optional<base::Value>& config =
  16. fuchsia_component_support::LoadPackageConfig();
  17. if (!config)
  18. return {};
  19. const base::Value::List* names_list =
  20. config->GetDict().FindList("allocator-dump-names");
  21. if (!names_list)
  22. return {};
  23. std::vector<std::string> names;
  24. names.reserve(names_list->size());
  25. for (auto& name : *names_list) {
  26. names.push_back(name.GetString());
  27. }
  28. return names;
  29. }
  30. // List of allocator dump names to include.
  31. const std::vector<std::string>& AllocatorDumpNames() {
  32. static base::NoDestructor<std::vector<std::string>> allocator_dump_names(
  33. GetAllocatorDumpNamesFromConfig());
  34. return *allocator_dump_names;
  35. }
  36. // Returns true if every field in the supplied |dump|, and those of its
  37. // children, are zero. Generally parent nodes summarize the total usage across
  38. // all of their children, such that if the parent is all-zero then the children
  39. // must also be all-zero. This implementation is optimized for the case in
  40. // which that property holds, but also copes gracefully when it does not.
  41. bool AreAllDumpEntriesZero(
  42. const memory_instrumentation::mojom::AllocatorMemDump* dump) {
  43. for (auto& it : dump->numeric_entries) {
  44. if (it.second != 0u)
  45. return false;
  46. }
  47. for (auto& it : dump->children) {
  48. if (!AreAllDumpEntriesZero(it.second.get()))
  49. return false;
  50. }
  51. return true;
  52. }
  53. // Creates a node |name|, under |parent|, populated recursively with the
  54. // contents of |dump|. The returned tree of Nodes are emplace()d to be owned by
  55. // the specified |owner|.
  56. inspect::Node NodeFromAllocatorMemDump(
  57. inspect::Inspector* owner,
  58. inspect::Node* parent,
  59. const std::string& name,
  60. const memory_instrumentation::mojom::AllocatorMemDump* dump) {
  61. auto node = parent->CreateChild(name);
  62. // Add subordinate nodes for any children.
  63. std::vector<const memory_instrumentation::mojom::AllocatorMemDump*> children;
  64. children.reserve(dump->children.size());
  65. for (auto& it : dump->children) {
  66. // If a child contains no information then omit it.
  67. if (AreAllDumpEntriesZero(it.second.get()))
  68. continue;
  69. children.push_back(it.second.get());
  70. owner->emplace(
  71. NodeFromAllocatorMemDump(owner, &node, it.first, it.second.get()));
  72. }
  73. // Publish the allocator-provided fields into the node. Entries are not
  74. // published if there is a single child, with identical entries, to avoid
  75. // redundancy in the emitted output.
  76. bool same_as_child =
  77. (children.size() == 1u &&
  78. (*children.begin())->numeric_entries == dump->numeric_entries);
  79. if (!same_as_child) {
  80. for (auto& it : dump->numeric_entries) {
  81. node.CreateUint(it.first, it.second, owner);
  82. }
  83. }
  84. return node;
  85. }
  86. } // namespace
  87. WebEngineMemoryInspector::WebEngineMemoryInspector(inspect::Node& parent_node) {
  88. // Loading the allocator dump names from the config involves blocking I/O so
  89. // trigger it to be done during construction, before the prohibition on
  90. // blocking the main thread is applied.
  91. AllocatorDumpNames();
  92. node_ = parent_node.CreateLazyNode("memory", [this]() {
  93. return fpromise::make_promise(fit::bind_member(
  94. this, &WebEngineMemoryInspector::ResolveMemoryDumpPromise));
  95. });
  96. }
  97. WebEngineMemoryInspector::~WebEngineMemoryInspector() = default;
  98. fpromise::result<inspect::Inspector>
  99. WebEngineMemoryInspector::ResolveMemoryDumpPromise(fpromise::context& context) {
  100. // If there is a |dump_results_| then resolve the promise with it.
  101. if (dump_results_) {
  102. auto memory_dump = std::move(dump_results_);
  103. return fpromise::ok(*memory_dump);
  104. }
  105. // If MemoryInstrumentation is not initialized then resolve an error.
  106. auto* memory_instrumentation =
  107. memory_instrumentation::MemoryInstrumentation::GetInstance();
  108. if (!memory_instrumentation)
  109. return fpromise::error();
  110. // Request memory usage summaries for all processes, including details for
  111. // any configured allocator dumps.
  112. auto* coordinator = memory_instrumentation->GetCoordinator();
  113. DCHECK(coordinator);
  114. coordinator->RequestGlobalMemoryDump(
  115. base::trace_event::MemoryDumpType::SUMMARY_ONLY,
  116. base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND,
  117. base::trace_event::MemoryDumpDeterminism::NONE, AllocatorDumpNames(),
  118. base::BindOnce(&WebEngineMemoryInspector::OnMemoryDumpComplete,
  119. weak_this_.GetWeakPtr(), base::TimeTicks::Now(),
  120. context.suspend_task()));
  121. return fpromise::pending();
  122. }
  123. void WebEngineMemoryInspector::OnMemoryDumpComplete(
  124. base::TimeTicks requested_at,
  125. fpromise::suspended_task task,
  126. bool success,
  127. memory_instrumentation::mojom::GlobalMemoryDumpPtr raw_dump) {
  128. DCHECK(!dump_results_);
  129. dump_results_ = std::make_unique<inspect::Inspector>();
  130. // If capture failed then there is no data to report.
  131. if (!success || !raw_dump) {
  132. task.resume_task();
  133. return;
  134. }
  135. // Note the delay between requesting the dump, and it being started.
  136. dump_results_->GetRoot().CreateDouble(
  137. "dump_queued_duration_ms",
  138. (raw_dump->start_time - requested_at).InMillisecondsF(),
  139. dump_results_.get());
  140. // Note the delay between starting the dump, and it completing.
  141. dump_results_->GetRoot().CreateDouble(
  142. "dump_duration_ms",
  143. (base::TimeTicks::Now() - raw_dump->start_time).InMillisecondsF(),
  144. dump_results_.get());
  145. for (const auto& process_dump : raw_dump->process_dumps) {
  146. auto node = dump_results_->GetRoot().CreateChild(
  147. base::NumberToString(process_dump->pid));
  148. // Include details of each process' role in the web instance.
  149. std::ostringstream type;
  150. type << process_dump->process_type;
  151. static const inspect::StringReference kTypeNodeName("type");
  152. node.CreateString(kTypeNodeName, type.str(), dump_results_.get());
  153. const auto service_name = process_dump->service_name;
  154. if (service_name) {
  155. static const inspect::StringReference kServiceNodeName("service");
  156. node.CreateString(kServiceNodeName, *service_name, dump_results_.get());
  157. }
  158. // Include the summary of the process' memory usage.
  159. const auto& os_dump = process_dump->os_dump;
  160. static const inspect::StringReference kResidentKbNodeName("resident_kb");
  161. node.CreateUint(kResidentKbNodeName, os_dump->resident_set_kb,
  162. dump_results_.get());
  163. static const inspect::StringReference kPrivateKbNodeName("private_kb");
  164. node.CreateUint(kPrivateKbNodeName, os_dump->private_footprint_kb,
  165. dump_results_.get());
  166. static const inspect::StringReference kSharedKbNodeName("shared_kb");
  167. node.CreateUint(kSharedKbNodeName, os_dump->shared_footprint_kb,
  168. dump_results_.get());
  169. // If provided, include detail from individual allocators.
  170. if (!process_dump->chrome_allocator_dumps.empty()) {
  171. static const inspect::StringReference kAllocatorDumpNodeName(
  172. "allocator_dump");
  173. auto detail_node = node.CreateChild(kAllocatorDumpNodeName);
  174. for (auto& it : process_dump->chrome_allocator_dumps) {
  175. dump_results_->emplace(NodeFromAllocatorMemDump(
  176. dump_results_.get(), &detail_node, it.first, it.second.get()));
  177. }
  178. dump_results_->emplace(std::move(detail_node));
  179. }
  180. dump_results_->emplace(std::move(node));
  181. }
  182. task.resume_task();
  183. }