1 // Copyright 2015 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 5 #include "base/trace_event/process_memory_dump.h" 6 7 #include <errno.h> 8 9 #include <vector> 10 11 #include "base/memory/ptr_util.h" 12 #include "base/process/process_metrics.h" 13 #include "base/strings/stringprintf.h" 14 #include "base/trace_event/heap_profiler_heap_dump_writer.h" 15 #include "base/trace_event/memory_infra_background_whitelist.h" 16 #include "base/trace_event/process_memory_totals.h" 17 #include "base/trace_event/trace_event_argument.h" 18 #include "build/build_config.h" 19 20 #if defined(OS_IOS) 21 #include <sys/sysctl.h> 22 #endif 23 24 #if defined(OS_POSIX) 25 #include <sys/mman.h> 26 #endif 27 28 #if defined(OS_WIN) 29 #include <Psapi.h> 30 #endif 31 32 namespace base { 33 namespace trace_event { 34 35 namespace { 36 37 const char kEdgeTypeOwnership[] = "ownership"; 38 39 std::string GetSharedGlobalAllocatorDumpName( 40 const MemoryAllocatorDumpGuid& guid) { 41 return "global/" + guid.ToString(); 42 } 43 44 #if defined(COUNT_RESIDENT_BYTES_SUPPORTED) 45 size_t GetSystemPageCount(size_t mapped_size, size_t page_size) { 46 return (mapped_size + page_size - 1) / page_size; 47 } 48 #endif 49 50 } // namespace 51 52 // static 53 bool ProcessMemoryDump::is_black_hole_non_fatal_for_testing_ = false; 54 55 #if defined(COUNT_RESIDENT_BYTES_SUPPORTED) 56 // static 57 size_t ProcessMemoryDump::GetSystemPageSize() { 58 #if defined(OS_IOS) 59 // On iOS, getpagesize() returns the user page sizes, but for allocating 60 // arrays for mincore(), kernel page sizes is needed. sysctlbyname() should 61 // be used for this. Refer to crbug.com/542671 and Apple rdar://23651782 62 int pagesize; 63 size_t pagesize_len; 64 int status = sysctlbyname("vm.pagesize", NULL, &pagesize_len, nullptr, 0); 65 if (!status && pagesize_len == sizeof(pagesize)) { 66 if (!sysctlbyname("vm.pagesize", &pagesize, &pagesize_len, nullptr, 0)) 67 return pagesize; 68 } 69 LOG(ERROR) << "sysctlbyname(\"vm.pagesize\") failed."; 70 // Falls back to getpagesize() although it may be wrong in certain cases. 71 #endif // defined(OS_IOS) 72 return base::GetPageSize(); 73 } 74 75 // static 76 size_t ProcessMemoryDump::CountResidentBytes(void* start_address, 77 size_t mapped_size) { 78 const size_t page_size = GetSystemPageSize(); 79 const uintptr_t start_pointer = reinterpret_cast<uintptr_t>(start_address); 80 DCHECK_EQ(0u, start_pointer % page_size); 81 82 size_t offset = 0; 83 size_t total_resident_size = 0; 84 bool failure = false; 85 86 // An array as large as number of pages in memory segment needs to be passed 87 // to the query function. To avoid allocating a large array, the given block 88 // of memory is split into chunks of size |kMaxChunkSize|. 89 const size_t kMaxChunkSize = 8 * 1024 * 1024; 90 size_t max_vec_size = 91 GetSystemPageCount(std::min(mapped_size, kMaxChunkSize), page_size); 92 #if defined(OS_MACOSX) || defined(OS_IOS) 93 std::unique_ptr<char[]> vec(new char[max_vec_size]); 94 #elif defined(OS_WIN) 95 std::unique_ptr<PSAPI_WORKING_SET_EX_INFORMATION[]> vec( 96 new PSAPI_WORKING_SET_EX_INFORMATION[max_vec_size]); 97 #elif defined(OS_POSIX) 98 std::unique_ptr<unsigned char[]> vec(new unsigned char[max_vec_size]); 99 #endif 100 101 while (offset < mapped_size) { 102 uintptr_t chunk_start = (start_pointer + offset); 103 const size_t chunk_size = std::min(mapped_size - offset, kMaxChunkSize); 104 const size_t page_count = GetSystemPageCount(chunk_size, page_size); 105 size_t resident_page_count = 0; 106 107 #if defined(OS_MACOSX) || defined(OS_IOS) 108 // mincore in MAC does not fail with EAGAIN. 109 failure = 110 !!mincore(reinterpret_cast<void*>(chunk_start), chunk_size, vec.get()); 111 for (size_t i = 0; i < page_count; i++) 112 resident_page_count += vec[i] & MINCORE_INCORE ? 1 : 0; 113 #elif defined(OS_WIN) 114 for (size_t i = 0; i < page_count; i++) { 115 vec[i].VirtualAddress = 116 reinterpret_cast<void*>(chunk_start + i * page_size); 117 } 118 DWORD vec_size = static_cast<DWORD>( 119 page_count * sizeof(PSAPI_WORKING_SET_EX_INFORMATION)); 120 failure = !QueryWorkingSetEx(GetCurrentProcess(), vec.get(), vec_size); 121 122 for (size_t i = 0; i < page_count; i++) 123 resident_page_count += vec[i].VirtualAttributes.Valid; 124 #elif defined(OS_POSIX) 125 int error_counter = 0; 126 int result = 0; 127 // HANDLE_EINTR tries for 100 times. So following the same pattern. 128 do { 129 result = 130 mincore(reinterpret_cast<void*>(chunk_start), chunk_size, vec.get()); 131 } while (result == -1 && errno == EAGAIN && error_counter++ < 100); 132 failure = !!result; 133 134 for (size_t i = 0; i < page_count; i++) 135 resident_page_count += vec[i] & 1; 136 #endif 137 138 if (failure) 139 break; 140 141 total_resident_size += resident_page_count * page_size; 142 offset += kMaxChunkSize; 143 } 144 145 DCHECK(!failure); 146 if (failure) { 147 total_resident_size = 0; 148 LOG(ERROR) << "CountResidentBytes failed. The resident size is invalid"; 149 } 150 return total_resident_size; 151 } 152 #endif // defined(COUNT_RESIDENT_BYTES_SUPPORTED) 153 154 ProcessMemoryDump::ProcessMemoryDump( 155 scoped_refptr<MemoryDumpSessionState> session_state, 156 const MemoryDumpArgs& dump_args) 157 : has_process_totals_(false), 158 has_process_mmaps_(false), 159 session_state_(std::move(session_state)), 160 dump_args_(dump_args) {} 161 162 ProcessMemoryDump::~ProcessMemoryDump() {} 163 164 MemoryAllocatorDump* ProcessMemoryDump::CreateAllocatorDump( 165 const std::string& absolute_name) { 166 return AddAllocatorDumpInternal( 167 WrapUnique(new MemoryAllocatorDump(absolute_name, this))); 168 } 169 170 MemoryAllocatorDump* ProcessMemoryDump::CreateAllocatorDump( 171 const std::string& absolute_name, 172 const MemoryAllocatorDumpGuid& guid) { 173 return AddAllocatorDumpInternal( 174 WrapUnique(new MemoryAllocatorDump(absolute_name, this, guid))); 175 } 176 177 MemoryAllocatorDump* ProcessMemoryDump::AddAllocatorDumpInternal( 178 std::unique_ptr<MemoryAllocatorDump> mad) { 179 // In background mode return the black hole dump, if invalid dump name is 180 // given. 181 if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND && 182 !IsMemoryAllocatorDumpNameWhitelisted(mad->absolute_name())) { 183 return GetBlackHoleMad(); 184 } 185 186 auto insertion_result = allocator_dumps_.insert( 187 std::make_pair(mad->absolute_name(), std::move(mad))); 188 MemoryAllocatorDump* inserted_mad = insertion_result.first->second.get(); 189 DCHECK(insertion_result.second) << "Duplicate name: " 190 << inserted_mad->absolute_name(); 191 return inserted_mad; 192 } 193 194 MemoryAllocatorDump* ProcessMemoryDump::GetAllocatorDump( 195 const std::string& absolute_name) const { 196 auto it = allocator_dumps_.find(absolute_name); 197 if (it != allocator_dumps_.end()) 198 return it->second.get(); 199 if (black_hole_mad_) 200 return black_hole_mad_.get(); 201 return nullptr; 202 } 203 204 MemoryAllocatorDump* ProcessMemoryDump::GetOrCreateAllocatorDump( 205 const std::string& absolute_name) { 206 MemoryAllocatorDump* mad = GetAllocatorDump(absolute_name); 207 return mad ? mad : CreateAllocatorDump(absolute_name); 208 } 209 210 MemoryAllocatorDump* ProcessMemoryDump::CreateSharedGlobalAllocatorDump( 211 const MemoryAllocatorDumpGuid& guid) { 212 // Global dumps are disabled in background mode. 213 if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND) 214 return GetBlackHoleMad(); 215 216 // A shared allocator dump can be shared within a process and the guid could 217 // have been created already. 218 MemoryAllocatorDump* mad = GetSharedGlobalAllocatorDump(guid); 219 if (mad) { 220 // The weak flag is cleared because this method should create a non-weak 221 // dump. 222 mad->clear_flags(MemoryAllocatorDump::Flags::WEAK); 223 return mad; 224 } 225 return CreateAllocatorDump(GetSharedGlobalAllocatorDumpName(guid), guid); 226 } 227 228 MemoryAllocatorDump* ProcessMemoryDump::CreateWeakSharedGlobalAllocatorDump( 229 const MemoryAllocatorDumpGuid& guid) { 230 // Global dumps are disabled in background mode. 231 if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND) 232 return GetBlackHoleMad(); 233 234 MemoryAllocatorDump* mad = GetSharedGlobalAllocatorDump(guid); 235 if (mad) 236 return mad; 237 mad = CreateAllocatorDump(GetSharedGlobalAllocatorDumpName(guid), guid); 238 mad->set_flags(MemoryAllocatorDump::Flags::WEAK); 239 return mad; 240 } 241 242 MemoryAllocatorDump* ProcessMemoryDump::GetSharedGlobalAllocatorDump( 243 const MemoryAllocatorDumpGuid& guid) const { 244 return GetAllocatorDump(GetSharedGlobalAllocatorDumpName(guid)); 245 } 246 247 void ProcessMemoryDump::DumpHeapUsage( 248 const base::hash_map<base::trace_event::AllocationContext, 249 base::trace_event::AllocationMetrics>& metrics_by_context, 250 base::trace_event::TraceEventMemoryOverhead& overhead, 251 const char* allocator_name) { 252 if (!metrics_by_context.empty()) { 253 DCHECK_EQ(0ul, heap_dumps_.count(allocator_name)); 254 std::unique_ptr<TracedValue> heap_dump = ExportHeapDump( 255 metrics_by_context, *session_state()); 256 heap_dumps_[allocator_name] = std::move(heap_dump); 257 } 258 259 std::string base_name = base::StringPrintf("tracing/heap_profiler_%s", 260 allocator_name); 261 overhead.DumpInto(base_name.c_str(), this); 262 } 263 264 void ProcessMemoryDump::Clear() { 265 if (has_process_totals_) { 266 process_totals_.Clear(); 267 has_process_totals_ = false; 268 } 269 270 if (has_process_mmaps_) { 271 process_mmaps_.Clear(); 272 has_process_mmaps_ = false; 273 } 274 275 allocator_dumps_.clear(); 276 allocator_dumps_edges_.clear(); 277 heap_dumps_.clear(); 278 } 279 280 void ProcessMemoryDump::TakeAllDumpsFrom(ProcessMemoryDump* other) { 281 DCHECK(!other->has_process_totals() && !other->has_process_mmaps()); 282 283 // Moves the ownership of all MemoryAllocatorDump(s) contained in |other| 284 // into this ProcessMemoryDump, checking for duplicates. 285 for (auto& it : other->allocator_dumps_) 286 AddAllocatorDumpInternal(std::move(it.second)); 287 other->allocator_dumps_.clear(); 288 289 // Move all the edges. 290 allocator_dumps_edges_.insert(allocator_dumps_edges_.end(), 291 other->allocator_dumps_edges_.begin(), 292 other->allocator_dumps_edges_.end()); 293 other->allocator_dumps_edges_.clear(); 294 295 for (auto& it : other->heap_dumps_) { 296 DCHECK_EQ(0ul, heap_dumps_.count(it.first)); 297 heap_dumps_.insert(std::make_pair(it.first, std::move(it.second))); 298 } 299 other->heap_dumps_.clear(); 300 } 301 302 void ProcessMemoryDump::AsValueInto(TracedValue* value) const { 303 if (has_process_totals_) { 304 value->BeginDictionary("process_totals"); 305 process_totals_.AsValueInto(value); 306 value->EndDictionary(); 307 } 308 309 if (has_process_mmaps_) { 310 value->BeginDictionary("process_mmaps"); 311 process_mmaps_.AsValueInto(value); 312 value->EndDictionary(); 313 } 314 315 if (allocator_dumps_.size() > 0) { 316 value->BeginDictionary("allocators"); 317 for (const auto& allocator_dump_it : allocator_dumps_) 318 allocator_dump_it.second->AsValueInto(value); 319 value->EndDictionary(); 320 } 321 322 if (heap_dumps_.size() > 0) { 323 value->BeginDictionary("heaps"); 324 for (const auto& name_and_dump : heap_dumps_) 325 value->SetValueWithCopiedName(name_and_dump.first, *name_and_dump.second); 326 value->EndDictionary(); // "heaps" 327 } 328 329 value->BeginArray("allocators_graph"); 330 for (const MemoryAllocatorDumpEdge& edge : allocator_dumps_edges_) { 331 value->BeginDictionary(); 332 value->SetString("source", edge.source.ToString()); 333 value->SetString("target", edge.target.ToString()); 334 value->SetInteger("importance", edge.importance); 335 value->SetString("type", edge.type); 336 value->EndDictionary(); 337 } 338 value->EndArray(); 339 } 340 341 void ProcessMemoryDump::AddOwnershipEdge(const MemoryAllocatorDumpGuid& source, 342 const MemoryAllocatorDumpGuid& target, 343 int importance) { 344 allocator_dumps_edges_.push_back( 345 {source, target, importance, kEdgeTypeOwnership}); 346 } 347 348 void ProcessMemoryDump::AddOwnershipEdge( 349 const MemoryAllocatorDumpGuid& source, 350 const MemoryAllocatorDumpGuid& target) { 351 AddOwnershipEdge(source, target, 0 /* importance */); 352 } 353 354 void ProcessMemoryDump::AddSuballocation(const MemoryAllocatorDumpGuid& source, 355 const std::string& target_node_name) { 356 // Do not create new dumps for suballocations in background mode. 357 if (dump_args_.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND) 358 return; 359 360 std::string child_mad_name = target_node_name + "/__" + source.ToString(); 361 MemoryAllocatorDump* target_child_mad = CreateAllocatorDump(child_mad_name); 362 AddOwnershipEdge(source, target_child_mad->guid()); 363 } 364 365 MemoryAllocatorDump* ProcessMemoryDump::GetBlackHoleMad() { 366 DCHECK(is_black_hole_non_fatal_for_testing_); 367 if (!black_hole_mad_) 368 black_hole_mad_.reset(new MemoryAllocatorDump("discarded", this)); 369 return black_hole_mad_.get(); 370 } 371 372 } // namespace trace_event 373 } // namespace base 374