summaryrefslogtreecommitdiff
path: root/base/trace_event/process_memory_dump.cc
diff options
context:
space:
mode:
Diffstat (limited to 'base/trace_event/process_memory_dump.cc')
-rw-r--r--base/trace_event/process_memory_dump.cc511
1 files changed, 511 insertions, 0 deletions
diff --git a/base/trace_event/process_memory_dump.cc b/base/trace_event/process_memory_dump.cc
new file mode 100644
index 0000000000..362641c400
--- /dev/null
+++ b/base/trace_event/process_memory_dump.cc
@@ -0,0 +1,511 @@
+// Copyright 2015 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/process_memory_dump.h"
+
+#include <errno.h>
+
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/shared_memory_tracker.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/memory_infra_background_whitelist.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "base/unguessable_token.h"
+#include "build/build_config.h"
+
+#if defined(OS_IOS)
+#include <mach/vm_page_size.h>
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <sys/mman.h>
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h> // Must be in front of other Windows header files
+
+#include <Psapi.h>
+#endif
+
+namespace base {
+namespace trace_event {
+
+namespace {
+
+const char kEdgeTypeOwnership[] = "ownership";
+
+std::string GetSharedGlobalAllocatorDumpName(
+ const MemoryAllocatorDumpGuid& guid) {
+ return "global/" + guid.ToString();
+}
+
+#if defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+size_t GetSystemPageCount(size_t mapped_size, size_t page_size) {
+ return (mapped_size + page_size - 1) / page_size;
+}
+#endif
+
+UnguessableToken GetTokenForCurrentProcess() {
+ static UnguessableToken instance = UnguessableToken::Create();
+ return instance;
+}
+
+} // namespace
+
+// static
+bool ProcessMemoryDump::is_black_hole_non_fatal_for_testing_ = false;
+
+#if defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+// static
+size_t ProcessMemoryDump::GetSystemPageSize() {
+#if defined(OS_IOS)
+ // On iOS, getpagesize() returns the user page sizes, but for allocating
+ // arrays for mincore(), kernel page sizes is needed. Use vm_kernel_page_size
+ // as recommended by Apple, https://forums.developer.apple.com/thread/47532/.
+ // Refer to http://crbug.com/542671 and Apple rdar://23651782
+ return vm_kernel_page_size;
+#else
+ return base::GetPageSize();
+#endif // defined(OS_IOS)
+}
+
+// static
+size_t ProcessMemoryDump::CountResidentBytes(void* start_address,
+ size_t mapped_size) {
+ const size_t page_size = GetSystemPageSize();
+ const uintptr_t start_pointer = reinterpret_cast<uintptr_t>(start_address);
+ DCHECK_EQ(0u, start_pointer % page_size);
+
+ size_t offset = 0;
+ size_t total_resident_pages = 0;
+ bool failure = false;
+
+ // An array as large as number of pages in memory segment needs to be passed
+ // to the query function. To avoid allocating a large array, the given block
+ // of memory is split into chunks of size |kMaxChunkSize|.
+ const size_t kMaxChunkSize = 8 * 1024 * 1024;
+ size_t max_vec_size =
+ GetSystemPageCount(std::min(mapped_size, kMaxChunkSize), page_size);
+#if defined(OS_WIN)
+ std::unique_ptr<PSAPI_WORKING_SET_EX_INFORMATION[]> vec(
+ new PSAPI_WORKING_SET_EX_INFORMATION[max_vec_size]);
+#elif defined(OS_MACOSX)
+ std::unique_ptr<char[]> vec(new char[max_vec_size]);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ std::unique_ptr<unsigned char[]> vec(new unsigned char[max_vec_size]);
+#endif
+
+ while (offset < mapped_size) {
+ uintptr_t chunk_start = (start_pointer + offset);
+ const size_t chunk_size = std::min(mapped_size - offset, kMaxChunkSize);
+ const size_t page_count = GetSystemPageCount(chunk_size, page_size);
+ size_t resident_page_count = 0;
+#if defined(OS_WIN)
+ for (size_t i = 0; i < page_count; i++) {
+ vec[i].VirtualAddress =
+ reinterpret_cast<void*>(chunk_start + i * page_size);
+ }
+ DWORD vec_size = static_cast<DWORD>(
+ page_count * sizeof(PSAPI_WORKING_SET_EX_INFORMATION));
+ failure = !QueryWorkingSetEx(GetCurrentProcess(), vec.get(), vec_size);
+
+ for (size_t i = 0; i < page_count; i++)
+ resident_page_count += vec[i].VirtualAttributes.Valid;
+#elif defined(OS_FUCHSIA)
+ // TODO(fuchsia): Port, see https://crbug.com/706592.
+ ALLOW_UNUSED_LOCAL(chunk_start);
+ ALLOW_UNUSED_LOCAL(page_count);
+#elif defined(OS_MACOSX)
+ // mincore in MAC does not fail with EAGAIN.
+ failure =
+ !!mincore(reinterpret_cast<void*>(chunk_start), chunk_size, vec.get());
+ for (size_t i = 0; i < page_count; i++)
+ resident_page_count += vec[i] & MINCORE_INCORE ? 1 : 0;
+#elif defined(OS_POSIX)
+ int error_counter = 0;
+ int result = 0;
+ // HANDLE_EINTR tries for 100 times. So following the same pattern.
+ do {
+ result =
+#if defined(OS_AIX)
+ mincore(reinterpret_cast<char*>(chunk_start), chunk_size,
+ reinterpret_cast<char*>(vec.get()));
+#else
+ mincore(reinterpret_cast<void*>(chunk_start), chunk_size, vec.get());
+#endif
+ } while (result == -1 && errno == EAGAIN && error_counter++ < 100);
+ failure = !!result;
+
+ for (size_t i = 0; i < page_count; i++)
+ resident_page_count += vec[i] & 1;
+#endif
+
+ if (failure)
+ break;
+
+ total_resident_pages += resident_page_count * page_size;
+ offset += kMaxChunkSize;
+ }
+
+ DCHECK(!failure);
+ if (failure) {
+ total_resident_pages = 0;
+ LOG(ERROR) << "CountResidentBytes failed. The resident size is invalid";
+ }
+ return total_resident_pages;
+}
+
+// static
+base::Optional<size_t> ProcessMemoryDump::CountResidentBytesInSharedMemory(
+ void* start_address,
+ size_t mapped_size) {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // On macOS, use mach_vm_region instead of mincore for performance
+ // (crbug.com/742042).
+ mach_vm_size_t dummy_size = 0;
+ mach_vm_address_t address =
+ reinterpret_cast<mach_vm_address_t>(start_address);
+ vm_region_top_info_data_t info;
+ MachVMRegionResult result =
+ GetTopInfo(mach_task_self(), &dummy_size, &address, &info);
+ if (result == MachVMRegionResult::Error) {
+ LOG(ERROR) << "CountResidentBytesInSharedMemory failed. The resident size "
+ "is invalid";
+ return base::Optional<size_t>();
+ }
+
+ size_t resident_pages =
+ info.private_pages_resident + info.shared_pages_resident;
+
+ // On macOS, measurements for private memory footprint overcount by
+ // faulted pages in anonymous shared memory. To discount for this, we touch
+ // all the resident pages in anonymous shared memory here, thus making them
+ // faulted as well. This relies on two assumptions:
+ //
+ // 1) Consumers use shared memory from front to back. Thus, if there are
+ // (N) resident pages, those pages represent the first N * PAGE_SIZE bytes in
+ // the shared memory region.
+ //
+ // 2) This logic is run shortly before the logic that calculates
+ // phys_footprint, thus ensuring that the discrepancy between faulted and
+ // resident pages is minimal.
+ //
+ // The performance penalty is expected to be small.
+ //
+ // * Most of the time, we expect the pages to already be resident and faulted,
+ // thus incurring a cache penalty read hit [since we read from each resident
+ // page].
+ //
+ // * Rarely, we expect the pages to be resident but not faulted, resulting in
+ // soft faults + cache penalty.
+ //
+ // * If assumption (1) is invalid, this will potentially fault some
+ // previously non-resident pages, thus increasing memory usage, without fixing
+ // the accounting.
+ //
+ // Sanity check in case the mapped size is less than the total size of the
+ // region.
+ size_t pages_to_fault =
+ std::min(resident_pages, (mapped_size + PAGE_SIZE - 1) / PAGE_SIZE);
+
+ volatile char* base_address = static_cast<char*>(start_address);
+ for (size_t i = 0; i < pages_to_fault; ++i) {
+ // Reading from a volatile is a visible side-effect for the purposes of
+ // optimization. This guarantees that the optimizer will not kill this line.
+ base_address[i * PAGE_SIZE];
+ }
+
+ return resident_pages * PAGE_SIZE;
+#else
+ return CountResidentBytes(start_address, mapped_size);
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+}
+
+#endif // defined(COUNT_RESIDENT_BYTES_SUPPORTED)
+
+ProcessMemoryDump::ProcessMemoryDump(
+ const MemoryDumpArgs& dump_args)
+ : process_token_(GetTokenForCurrentProcess()),
+ dump_args_(dump_args) {}
+
+ProcessMemoryDump::~ProcessMemoryDump() = default;
+ProcessMemoryDump::ProcessMemoryDump(ProcessMemoryDump&& other) = default;
+ProcessMemoryDump& ProcessMemoryDump::operator=(ProcessMemoryDump&& other) =
+ default;
+
+MemoryAllocatorDump* ProcessMemoryDump::CreateAllocatorDump(
+ const std::string& absolute_name) {
+ return AddAllocatorDumpInternal(std::make_unique<MemoryAllocatorDump>(
+ absolute_name, dump_args_.level_of_detail, GetDumpId(absolute_name)));
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::CreateAllocatorDump(
+ const std::string& absolute_name,
+ const MemoryAllocatorDumpGuid& guid) {
+ return AddAllocatorDumpInternal(std::make_unique<MemoryAllocatorDump>(
+ absolute_name, dump_args_.level_of_detail, guid));
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::AddAllocatorDumpInternal(
+ std::unique_ptr<MemoryAllocatorDump> mad) {
+ // In background mode return the black hole dump, if invalid dump name is
+ // given.
+ if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND &&
+ !IsMemoryAllocatorDumpNameWhitelisted(mad->absolute_name())) {
+ return GetBlackHoleMad();
+ }
+
+ auto insertion_result = allocator_dumps_.insert(
+ std::make_pair(mad->absolute_name(), std::move(mad)));
+ MemoryAllocatorDump* inserted_mad = insertion_result.first->second.get();
+ DCHECK(insertion_result.second) << "Duplicate name: "
+ << inserted_mad->absolute_name();
+ return inserted_mad;
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::GetAllocatorDump(
+ const std::string& absolute_name) const {
+ auto it = allocator_dumps_.find(absolute_name);
+ if (it != allocator_dumps_.end())
+ return it->second.get();
+ return nullptr;
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::GetOrCreateAllocatorDump(
+ const std::string& absolute_name) {
+ MemoryAllocatorDump* mad = GetAllocatorDump(absolute_name);
+ return mad ? mad : CreateAllocatorDump(absolute_name);
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::CreateSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid) {
+ // A shared allocator dump can be shared within a process and the guid could
+ // have been created already.
+ MemoryAllocatorDump* mad = GetSharedGlobalAllocatorDump(guid);
+ if (mad && mad != black_hole_mad_.get()) {
+ // The weak flag is cleared because this method should create a non-weak
+ // dump.
+ mad->clear_flags(MemoryAllocatorDump::Flags::WEAK);
+ return mad;
+ }
+ return CreateAllocatorDump(GetSharedGlobalAllocatorDumpName(guid), guid);
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::CreateWeakSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid) {
+ MemoryAllocatorDump* mad = GetSharedGlobalAllocatorDump(guid);
+ if (mad && mad != black_hole_mad_.get())
+ return mad;
+ mad = CreateAllocatorDump(GetSharedGlobalAllocatorDumpName(guid), guid);
+ mad->set_flags(MemoryAllocatorDump::Flags::WEAK);
+ return mad;
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::GetSharedGlobalAllocatorDump(
+ const MemoryAllocatorDumpGuid& guid) const {
+ return GetAllocatorDump(GetSharedGlobalAllocatorDumpName(guid));
+}
+
+void ProcessMemoryDump::DumpHeapUsage(
+ const std::unordered_map<base::trace_event::AllocationContext,
+ base::trace_event::AllocationMetrics>&
+ metrics_by_context,
+ base::trace_event::TraceEventMemoryOverhead& overhead,
+ const char* allocator_name) {
+ std::string base_name = base::StringPrintf("tracing/heap_profiler_%s",
+ allocator_name);
+ overhead.DumpInto(base_name.c_str(), this);
+}
+
+void ProcessMemoryDump::SetAllocatorDumpsForSerialization(
+ std::vector<std::unique_ptr<MemoryAllocatorDump>> dumps) {
+ DCHECK(allocator_dumps_.empty());
+ for (std::unique_ptr<MemoryAllocatorDump>& dump : dumps)
+ AddAllocatorDumpInternal(std::move(dump));
+}
+
+std::vector<ProcessMemoryDump::MemoryAllocatorDumpEdge>
+ProcessMemoryDump::GetAllEdgesForSerialization() const {
+ std::vector<MemoryAllocatorDumpEdge> edges;
+ edges.reserve(allocator_dumps_edges_.size());
+ for (const auto& it : allocator_dumps_edges_)
+ edges.push_back(it.second);
+ return edges;
+}
+
+void ProcessMemoryDump::SetAllEdgesForSerialization(
+ const std::vector<ProcessMemoryDump::MemoryAllocatorDumpEdge>& edges) {
+ DCHECK(allocator_dumps_edges_.empty());
+ for (const MemoryAllocatorDumpEdge& edge : edges) {
+ auto it_and_inserted = allocator_dumps_edges_.emplace(edge.source, edge);
+ DCHECK(it_and_inserted.second);
+ }
+}
+
+void ProcessMemoryDump::Clear() {
+ allocator_dumps_.clear();
+ allocator_dumps_edges_.clear();
+}
+
+void ProcessMemoryDump::TakeAllDumpsFrom(ProcessMemoryDump* other) {
+ // Moves the ownership of all MemoryAllocatorDump(s) contained in |other|
+ // into this ProcessMemoryDump, checking for duplicates.
+ for (auto& it : other->allocator_dumps_)
+ AddAllocatorDumpInternal(std::move(it.second));
+ other->allocator_dumps_.clear();
+
+ // Move all the edges.
+ allocator_dumps_edges_.insert(other->allocator_dumps_edges_.begin(),
+ other->allocator_dumps_edges_.end());
+ other->allocator_dumps_edges_.clear();
+}
+
+void ProcessMemoryDump::SerializeAllocatorDumpsInto(TracedValue* value) const {
+ if (allocator_dumps_.size() > 0) {
+ value->BeginDictionary("allocators");
+ for (const auto& allocator_dump_it : allocator_dumps_)
+ allocator_dump_it.second->AsValueInto(value);
+ value->EndDictionary();
+ }
+
+ value->BeginArray("allocators_graph");
+ for (const auto& it : allocator_dumps_edges_) {
+ const MemoryAllocatorDumpEdge& edge = it.second;
+ value->BeginDictionary();
+ value->SetString("source", edge.source.ToString());
+ value->SetString("target", edge.target.ToString());
+ value->SetInteger("importance", edge.importance);
+ value->SetString("type", kEdgeTypeOwnership);
+ value->EndDictionary();
+ }
+ value->EndArray();
+}
+
+void ProcessMemoryDump::AddOwnershipEdge(const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target,
+ int importance) {
+ // This will either override an existing edge or create a new one.
+ auto it = allocator_dumps_edges_.find(source);
+ int max_importance = importance;
+ if (it != allocator_dumps_edges_.end()) {
+ DCHECK_EQ(target.ToUint64(), it->second.target.ToUint64());
+ max_importance = std::max(importance, it->second.importance);
+ }
+ allocator_dumps_edges_[source] = {source, target, max_importance,
+ false /* overridable */};
+}
+
+void ProcessMemoryDump::AddOwnershipEdge(
+ const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target) {
+ AddOwnershipEdge(source, target, 0 /* importance */);
+}
+
+void ProcessMemoryDump::AddOverridableOwnershipEdge(
+ const MemoryAllocatorDumpGuid& source,
+ const MemoryAllocatorDumpGuid& target,
+ int importance) {
+ if (allocator_dumps_edges_.count(source) == 0) {
+ allocator_dumps_edges_[source] = {source, target, importance,
+ true /* overridable */};
+ } else {
+ // An edge between the source and target already exits. So, do nothing here
+ // since the new overridable edge is implicitly overridden by a strong edge
+ // which was created earlier.
+ DCHECK(!allocator_dumps_edges_[source].overridable);
+ }
+}
+
+void ProcessMemoryDump::CreateSharedMemoryOwnershipEdge(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance) {
+ CreateSharedMemoryOwnershipEdgeInternal(client_local_dump_guid,
+ shared_memory_guid, importance,
+ false /*is_weak*/);
+}
+
+void ProcessMemoryDump::CreateWeakSharedMemoryOwnershipEdge(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance) {
+ CreateSharedMemoryOwnershipEdgeInternal(
+ client_local_dump_guid, shared_memory_guid, importance, true /*is_weak*/);
+}
+
+void ProcessMemoryDump::CreateSharedMemoryOwnershipEdgeInternal(
+ const MemoryAllocatorDumpGuid& client_local_dump_guid,
+ const UnguessableToken& shared_memory_guid,
+ int importance,
+ bool is_weak) {
+ DCHECK(!shared_memory_guid.is_empty());
+ // New model where the global dumps created by SharedMemoryTracker are used
+ // for the clients.
+
+ // The guid of the local dump created by SharedMemoryTracker for the memory
+ // segment.
+ auto local_shm_guid =
+ GetDumpId(SharedMemoryTracker::GetDumpNameForTracing(shared_memory_guid));
+
+ // The dump guid of the global dump created by the tracker for the memory
+ // segment.
+ auto global_shm_guid =
+ SharedMemoryTracker::GetGlobalDumpIdForTracing(shared_memory_guid);
+
+ // Create an edge between local dump of the client and the local dump of the
+ // SharedMemoryTracker. Do not need to create the dumps here since the tracker
+ // would create them. The importance is also required here for the case of
+ // single process mode.
+ AddOwnershipEdge(client_local_dump_guid, local_shm_guid, importance);
+
+ // TODO(ssid): Handle the case of weak dumps here. This needs a new function
+ // GetOrCreaetGlobalDump() in PMD since we need to change the behavior of the
+ // created global dump.
+ // Create an edge that overrides the edge created by SharedMemoryTracker.
+ AddOwnershipEdge(local_shm_guid, global_shm_guid, importance);
+}
+
+void ProcessMemoryDump::AddSuballocation(const MemoryAllocatorDumpGuid& source,
+ const std::string& target_node_name) {
+ // Do not create new dumps for suballocations in background mode.
+ if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND)
+ return;
+
+ std::string child_mad_name = target_node_name + "/__" + source.ToString();
+ MemoryAllocatorDump* target_child_mad = CreateAllocatorDump(child_mad_name);
+ AddOwnershipEdge(source, target_child_mad->guid());
+}
+
+MemoryAllocatorDump* ProcessMemoryDump::GetBlackHoleMad() {
+ DCHECK(is_black_hole_non_fatal_for_testing_);
+ if (!black_hole_mad_) {
+ std::string name = "discarded";
+ black_hole_mad_.reset(new MemoryAllocatorDump(
+ name, dump_args_.level_of_detail, GetDumpId(name)));
+ }
+ return black_hole_mad_.get();
+}
+
+MemoryAllocatorDumpGuid ProcessMemoryDump::GetDumpId(
+ const std::string& absolute_name) {
+ return MemoryAllocatorDumpGuid(StringPrintf(
+ "%s:%s", process_token().ToString().c_str(), absolute_name.c_str()));
+}
+
+bool ProcessMemoryDump::MemoryAllocatorDumpEdge::operator==(
+ const MemoryAllocatorDumpEdge& other) const {
+ return source == other.source && target == other.target &&
+ importance == other.importance && overridable == other.overridable;
+}
+
+bool ProcessMemoryDump::MemoryAllocatorDumpEdge::operator!=(
+ const MemoryAllocatorDumpEdge& other) const {
+ return !(*this == other);
+}
+
+} // namespace trace_event
+} // namespace base