deep-heap-profile.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
16f56ab789cb470620554d624c37f488285b3b04eDan Albert// Copyright (c) 2012 The Chromium Authors. All rights reserved.
26f56ab789cb470620554d624c37f488285b3b04eDan Albert// Use of this source code is governed by a BSD-style license that can be
36f56ab789cb470620554d624c37f488285b3b04eDan Albert// found in the LICENSE file.
46f56ab789cb470620554d624c37f488285b3b04eDan Albert
56f56ab789cb470620554d624c37f488285b3b04eDan Albert// ---
66f56ab789cb470620554d624c37f488285b3b04eDan Albert// Author: Sainbayar Sukhbaatar
76f56ab789cb470620554d624c37f488285b3b04eDan Albert//         Dai Mikurube
86f56ab789cb470620554d624c37f488285b3b04eDan Albert//
96f56ab789cb470620554d624c37f488285b3b04eDan Albert
106f56ab789cb470620554d624c37f488285b3b04eDan Albert#include "deep-heap-profile.h"
116f56ab789cb470620554d624c37f488285b3b04eDan Albert
126f56ab789cb470620554d624c37f488285b3b04eDan Albert#ifdef USE_DEEP_HEAP_PROFILE
136f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <algorithm>
146f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <fcntl.h>
156f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <sys/stat.h>
166f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <sys/types.h>
176f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <time.h>
186f56ab789cb470620554d624c37f488285b3b04eDan Albert#ifdef HAVE_UNISTD_H
196f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <unistd.h>  // for getpagesize and getpid
206f56ab789cb470620554d624c37f488285b3b04eDan Albert#endif  // HAVE_UNISTD_H
216f56ab789cb470620554d624c37f488285b3b04eDan Albert
226f56ab789cb470620554d624c37f488285b3b04eDan Albert#if defined(__linux__)
236f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <endian.h>
246f56ab789cb470620554d624c37f488285b3b04eDan Albert#if !defined(__LITTLE_ENDIAN__) and !defined(__BIG_ENDIAN__)
256f56ab789cb470620554d624c37f488285b3b04eDan Albert#if __BYTE_ORDER == __BIG_ENDIAN
266f56ab789cb470620554d624c37f488285b3b04eDan Albert#define __BIG_ENDIAN__
276f56ab789cb470620554d624c37f488285b3b04eDan Albert#endif  // __BYTE_ORDER == __BIG_ENDIAN
286f56ab789cb470620554d624c37f488285b3b04eDan Albert#endif  // !defined(__LITTLE_ENDIAN__) and !defined(__BIG_ENDIAN__)
296f56ab789cb470620554d624c37f488285b3b04eDan Albert#if defined(__BIG_ENDIAN__)
306f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <byteswap.h>
316f56ab789cb470620554d624c37f488285b3b04eDan Albert#endif  // defined(__BIG_ENDIAN__)
32#endif  // defined(__linux__)
33
34#include "base/cycleclock.h"
35#include "base/sysinfo.h"
36#include "internal_logging.h"  // for ASSERT, etc
37
38static const int kProfilerBufferSize = 1 << 20;
39static const int kHashTableSize = 179999;  // Same as heap-profile-table.cc.
40
41static const int PAGEMAP_BYTES = 8;
42static const int KPAGECOUNT_BYTES = 8;
43static const uint64 MAX_ADDRESS = kuint64max;
44
45// Tag strings in heap profile dumps.
46static const char kProfileHeader[] = "heap profile: ";
47static const char kProfileVersion[] = "DUMP_DEEP_6";
48static const char kMetaInformationHeader[] = "META:\n";
49static const char kMMapListHeader[] = "MMAP_LIST:\n";
50static const char kGlobalStatsHeader[] = "GLOBAL_STATS:\n";
51static const char kStacktraceHeader[] = "STACKTRACES:\n";
52static const char kProcSelfMapsHeader[] = "\nMAPPED_LIBRARIES:\n";
53
54static const char kVirtualLabel[] = "virtual";
55static const char kCommittedLabel[] = "committed";
56
57#if defined(__linux__)
58
59bool DeepHeapProfile::AppendCommandLine(TextBuffer* buffer) {
60  RawFD fd;
61  char filename[100];
62  char cmdline[4096];
63  snprintf(filename, sizeof(filename), "/proc/%d/cmdline",
64           static_cast<int>(getpid()));
65  fd = open(filename, O_RDONLY);
66  if (fd == kIllegalRawFD) {
67    RAW_LOG(0, "Failed to open /proc/self/cmdline");
68    return false;
69  }
70
71  size_t length = read(fd, cmdline, sizeof(cmdline) - 1);
72  close(fd);
73
74  for (int i = 0; i < length; ++i)
75    if (cmdline[i] == '\0')
76      cmdline[i] = ' ';
77  cmdline[length] = '\0';
78
79  buffer->AppendString("CommandLine: ", 0);
80  buffer->AppendString(cmdline, 0);
81  buffer->AppendChar('\n');
82
83  return true;
84}
85
86#else  // defined(__linux__)
87
88bool DeepHeapProfile::AppendCommandLine(TextBuffer* buffer) {
89  return false;
90}
91
92#endif  // defined(__linux__)
93
94#if defined(__linux__)
95
96void DeepHeapProfile::MemoryInfoGetterLinux::Initialize() {
97  char filename[100];
98  snprintf(filename, sizeof(filename), "/proc/%d/pagemap",
99           static_cast<int>(getpid()));
100  pagemap_fd_ = open(filename, O_RDONLY);
101  RAW_CHECK(pagemap_fd_ != -1, "Failed to open /proc/self/pagemap");
102
103  if (pageframe_type_ == DUMP_PAGECOUNT) {
104    snprintf(filename, sizeof(filename), "/proc/kpagecount",
105             static_cast<int>(getpid()));
106    kpagecount_fd_ = open(filename, O_RDONLY);
107    if (kpagecount_fd_ == -1)
108      RAW_LOG(0, "Failed to open /proc/kpagecount");
109  }
110}
111
112size_t DeepHeapProfile::MemoryInfoGetterLinux::CommittedSize(
113    uint64 first_address,
114    uint64 last_address,
115    DeepHeapProfile::TextBuffer* buffer) const {
116  int page_size = getpagesize();
117  uint64 page_address = (first_address / page_size) * page_size;
118  size_t committed_size = 0;
119  size_t pageframe_list_length = 0;
120
121  Seek(first_address);
122
123  // Check every page on which the allocation resides.
124  while (page_address <= last_address) {
125    // Read corresponding physical page.
126    State state;
127    // TODO(dmikurube): Read pagemap in bulk for speed.
128    // TODO(dmikurube): Consider using mincore(2).
129    if (Read(&state, pageframe_type_ != DUMP_NO_PAGEFRAME) == false) {
130      // We can't read the last region (e.g vsyscall).
131#ifndef NDEBUG
132      RAW_LOG(0, "pagemap read failed @ %#llx %"PRId64" bytes",
133              first_address, last_address - first_address + 1);
134#endif
135      return 0;
136    }
137
138    // Dump pageframes of resident pages.  Non-resident pages are just skipped.
139    if (pageframe_type_ != DUMP_NO_PAGEFRAME &&
140        buffer != NULL && state.pfn != 0) {
141      if (pageframe_list_length == 0) {
142        buffer->AppendString("  PF:", 0);
143        pageframe_list_length = 5;
144      }
145      buffer->AppendChar(' ');
146      if (page_address < first_address)
147        buffer->AppendChar('<');
148      buffer->AppendBase64(state.pfn, 4);
149      pageframe_list_length += 5;
150      if (pageframe_type_ == DUMP_PAGECOUNT && IsPageCountAvailable()) {
151        uint64 pagecount = ReadPageCount(state.pfn);
152        // Assume pagecount == 63 if the pageframe is mapped more than 63 times.
153        if (pagecount > 63)
154          pagecount = 63;
155        buffer->AppendChar('#');
156        buffer->AppendBase64(pagecount, 1);
157        pageframe_list_length += 2;
158      }
159      if (last_address < page_address - 1 + page_size)
160        buffer->AppendChar('>');
161      // Begins a new line every 94 characters.
162      if (pageframe_list_length > 94) {
163        buffer->AppendChar('\n');
164        pageframe_list_length = 0;
165      }
166    }
167
168    if (state.is_committed) {
169      // Calculate the size of the allocation part in this page.
170      size_t bytes = page_size;
171
172      // If looking at the last page in a given region.
173      if (last_address <= page_address - 1 + page_size) {
174        bytes = last_address - page_address + 1;
175      }
176
177      // If looking at the first page in a given region.
178      if (page_address < first_address) {
179        bytes -= first_address - page_address;
180      }
181
182      committed_size += bytes;
183    }
184    if (page_address > MAX_ADDRESS - page_size) {
185      break;
186    }
187    page_address += page_size;
188  }
189
190  if (pageframe_type_ != DUMP_NO_PAGEFRAME &&
191      buffer != NULL && pageframe_list_length != 0) {
192    buffer->AppendChar('\n');
193  }
194
195  return committed_size;
196}
197
198uint64 DeepHeapProfile::MemoryInfoGetterLinux::ReadPageCount(uint64 pfn) const {
199  int64 index = pfn * KPAGECOUNT_BYTES;
200  int64 offset = lseek64(kpagecount_fd_, index, SEEK_SET);
201  RAW_DCHECK(offset == index, "Failed in seeking in kpagecount.");
202
203  uint64 kpagecount_value;
204  int result = read(kpagecount_fd_, &kpagecount_value, KPAGECOUNT_BYTES);
205  if (result != KPAGECOUNT_BYTES)
206    return 0;
207
208  return kpagecount_value;
209}
210
211bool DeepHeapProfile::MemoryInfoGetterLinux::Seek(uint64 address) const {
212  int64 index = (address / getpagesize()) * PAGEMAP_BYTES;
213  RAW_DCHECK(pagemap_fd_ != -1, "Failed to seek in /proc/self/pagemap");
214  int64 offset = lseek64(pagemap_fd_, index, SEEK_SET);
215  RAW_DCHECK(offset == index, "Failed in seeking.");
216  return offset >= 0;
217}
218
219bool DeepHeapProfile::MemoryInfoGetterLinux::Read(
220    State* state, bool get_pfn) const {
221  static const uint64 U64_1 = 1;
222  static const uint64 PFN_FILTER = (U64_1 << 55) - U64_1;
223  static const uint64 PAGE_PRESENT = U64_1 << 63;
224  static const uint64 PAGE_SWAP = U64_1 << 62;
225  static const uint64 PAGE_RESERVED = U64_1 << 61;
226  static const uint64 FLAG_NOPAGE = U64_1 << 20;
227  static const uint64 FLAG_KSM = U64_1 << 21;
228  static const uint64 FLAG_MMAP = U64_1 << 11;
229
230  uint64 pagemap_value;
231  RAW_DCHECK(pagemap_fd_ != -1, "Failed to read from /proc/self/pagemap");
232  int result = read(pagemap_fd_, &pagemap_value, PAGEMAP_BYTES);
233  if (result != PAGEMAP_BYTES) {
234    return false;
235  }
236
237  // Check if the page is committed.
238  state->is_committed = (pagemap_value & (PAGE_PRESENT | PAGE_SWAP));
239
240  state->is_present = (pagemap_value & PAGE_PRESENT);
241  state->is_swapped = (pagemap_value & PAGE_SWAP);
242  state->is_shared = false;
243
244  if (get_pfn && state->is_present && !state->is_swapped)
245    state->pfn = (pagemap_value & PFN_FILTER);
246  else
247    state->pfn = 0;
248
249  return true;
250}
251
252bool DeepHeapProfile::MemoryInfoGetterLinux::IsPageCountAvailable() const {
253  return kpagecount_fd_ != -1;
254}
255
256#endif  // defined(__linux__)
257
258DeepHeapProfile::MemoryResidenceInfoGetterInterface::
259    MemoryResidenceInfoGetterInterface() {}
260
261DeepHeapProfile::MemoryResidenceInfoGetterInterface::
262    ~MemoryResidenceInfoGetterInterface() {}
263
264DeepHeapProfile::MemoryResidenceInfoGetterInterface*
265    DeepHeapProfile::MemoryResidenceInfoGetterInterface::Create(
266        PageFrameType pageframe_type) {
267#if defined(__linux__)
268  return new MemoryInfoGetterLinux(pageframe_type);
269#else
270  return NULL;
271#endif
272}
273
274DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile,
275                                 const char* prefix,
276                                 enum PageFrameType pageframe_type)
277    : memory_residence_info_getter_(
278          MemoryResidenceInfoGetterInterface::Create(pageframe_type)),
279      most_recent_pid_(-1),
280      stats_(),
281      dump_count_(0),
282      filename_prefix_(NULL),
283      deep_table_(kHashTableSize, heap_profile->alloc_, heap_profile->dealloc_),
284      pageframe_type_(pageframe_type),
285      heap_profile_(heap_profile) {
286  // Copy filename prefix.
287  const int prefix_length = strlen(prefix);
288  filename_prefix_ =
289      reinterpret_cast<char*>(heap_profile_->alloc_(prefix_length + 1));
290  memcpy(filename_prefix_, prefix, prefix_length);
291  filename_prefix_[prefix_length] = '\0';
292}
293
294DeepHeapProfile::~DeepHeapProfile() {
295  heap_profile_->dealloc_(filename_prefix_);
296  delete memory_residence_info_getter_;
297}
298
299// Global malloc() should not be used in this function.
300// Use LowLevelAlloc if required.
301void DeepHeapProfile::DumpOrderedProfile(const char* reason,
302                                         char raw_buffer[],
303                                         int buffer_size,
304                                         RawFD fd) {
305  TextBuffer buffer(raw_buffer, buffer_size, fd);
306
307#ifndef NDEBUG
308  int64 starting_cycles = CycleClock::Now();
309#endif
310
311  // Get the time before starting snapshot.
312  // TODO(dmikurube): Consider gettimeofday if available.
313  time_t time_value = time(NULL);
314
315  ++dump_count_;
316
317  // Re-open files in /proc/pid/ if the process is newly forked one.
318  if (most_recent_pid_ != getpid()) {
319    most_recent_pid_ = getpid();
320
321    memory_residence_info_getter_->Initialize();
322    deep_table_.ResetIsLogged();
323
324    // Write maps into "|filename_prefix_|.<pid>.maps".
325    WriteProcMaps(filename_prefix_, raw_buffer, buffer_size);
326  }
327
328  // Reset committed sizes of buckets.
329  deep_table_.ResetCommittedSize();
330
331  // Record committed sizes.
332  stats_.SnapshotAllocations(this);
333
334  // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf.
335  // glibc's snprintf internally allocates memory by alloca normally, but it
336  // allocates memory by malloc if large memory is required.
337
338  buffer.AppendString(kProfileHeader, 0);
339  buffer.AppendString(kProfileVersion, 0);
340  buffer.AppendString("\n", 0);
341
342  // Fill buffer with meta information.
343  buffer.AppendString(kMetaInformationHeader, 0);
344
345  buffer.AppendString("Time: ", 0);
346  buffer.AppendUnsignedLong(time_value, 0);
347  buffer.AppendChar('\n');
348
349  if (reason != NULL) {
350    buffer.AppendString("Reason: ", 0);
351    buffer.AppendString(reason, 0);
352    buffer.AppendChar('\n');
353  }
354
355  AppendCommandLine(&buffer);
356
357  buffer.AppendString("PageSize: ", 0);
358  buffer.AppendInt(getpagesize(), 0, 0);
359  buffer.AppendChar('\n');
360
361  // Assumes the physical memory <= 64GB (PFN < 2^24).
362  if (pageframe_type_ == DUMP_PAGECOUNT &&
363      memory_residence_info_getter_->IsPageCountAvailable()) {
364    buffer.AppendString("PageFrame: 24,Base64,PageCount", 0);
365    buffer.AppendChar('\n');
366  } else if (pageframe_type_ != DUMP_NO_PAGEFRAME) {
367    buffer.AppendString("PageFrame: 24,Base64", 0);
368    buffer.AppendChar('\n');
369  }
370
371  // Fill buffer with the global stats.
372  buffer.AppendString(kMMapListHeader, 0);
373
374  stats_.SnapshotMaps(memory_residence_info_getter_, this, &buffer);
375
376  // Fill buffer with the global stats.
377  buffer.AppendString(kGlobalStatsHeader, 0);
378
379  stats_.Unparse(&buffer);
380
381  buffer.AppendString(kStacktraceHeader, 0);
382  buffer.AppendString(kVirtualLabel, 10);
383  buffer.AppendChar(' ');
384  buffer.AppendString(kCommittedLabel, 10);
385  buffer.AppendString("\n", 0);
386
387  // Fill buffer.
388  deep_table_.UnparseForStats(&buffer);
389
390  buffer.Flush();
391
392  // Write the bucket listing into a .bucket file.
393  deep_table_.WriteForBucketFile(
394      filename_prefix_, dump_count_, raw_buffer, buffer_size);
395
396#ifndef NDEBUG
397  int64 elapsed_cycles = CycleClock::Now() - starting_cycles;
398  double elapsed_seconds = elapsed_cycles / CyclesPerSecond();
399  RAW_LOG(0, "Time spent on DeepProfiler: %.3f sec\n", elapsed_seconds);
400#endif
401}
402
403int DeepHeapProfile::TextBuffer::Size() {
404  return size_;
405}
406
407int DeepHeapProfile::TextBuffer::FilledBytes() {
408  return cursor_;
409}
410
411void DeepHeapProfile::TextBuffer::Clear() {
412  cursor_ = 0;
413}
414
415void DeepHeapProfile::TextBuffer::Flush() {
416  RawWrite(fd_, buffer_, cursor_);
417  cursor_ = 0;
418}
419
420// TODO(dmikurube): These Append* functions should not use snprintf.
421bool DeepHeapProfile::TextBuffer::AppendChar(char value) {
422  return ForwardCursor(snprintf(buffer_ + cursor_, size_ - cursor_,
423                                "%c", value));
424}
425
426bool DeepHeapProfile::TextBuffer::AppendString(const char* value, int width) {
427  char* position = buffer_ + cursor_;
428  int available = size_ - cursor_;
429  int appended;
430  if (width == 0)
431    appended = snprintf(position, available, "%s", value);
432  else
433    appended = snprintf(position, available, "%*s",
434                        width, value);
435  return ForwardCursor(appended);
436}
437
438bool DeepHeapProfile::TextBuffer::AppendInt(int value, int width,
439                                            bool leading_zero) {
440  char* position = buffer_ + cursor_;
441  int available = size_ - cursor_;
442  int appended;
443  if (width == 0)
444    appended = snprintf(position, available, "%d", value);
445  else if (leading_zero)
446    appended = snprintf(position, available, "%0*d", width, value);
447  else
448    appended = snprintf(position, available, "%*d", width, value);
449  return ForwardCursor(appended);
450}
451
452bool DeepHeapProfile::TextBuffer::AppendLong(long value, int width) {
453  char* position = buffer_ + cursor_;
454  int available = size_ - cursor_;
455  int appended;
456  if (width == 0)
457    appended = snprintf(position, available, "%ld", value);
458  else
459    appended = snprintf(position, available, "%*ld", width, value);
460  return ForwardCursor(appended);
461}
462
463bool DeepHeapProfile::TextBuffer::AppendUnsignedLong(unsigned long value,
464                                                     int width) {
465  char* position = buffer_ + cursor_;
466  int available = size_ - cursor_;
467  int appended;
468  if (width == 0)
469    appended = snprintf(position, available, "%lu", value);
470  else
471    appended = snprintf(position, available, "%*lu", width, value);
472  return ForwardCursor(appended);
473}
474
475bool DeepHeapProfile::TextBuffer::AppendInt64(int64 value, int width) {
476  char* position = buffer_ + cursor_;
477  int available = size_ - cursor_;
478  int appended;
479  if (width == 0)
480    appended = snprintf(position, available, "%"PRId64, value);
481  else
482    appended = snprintf(position, available, "%*"PRId64, width, value);
483  return ForwardCursor(appended);
484}
485
486bool DeepHeapProfile::TextBuffer::AppendPtr(uint64 value, int width) {
487  char* position = buffer_ + cursor_;
488  int available = size_ - cursor_;
489  int appended;
490  if (width == 0)
491    appended = snprintf(position, available, "%"PRIx64, value);
492  else
493    appended = snprintf(position, available, "%0*"PRIx64, width, value);
494  return ForwardCursor(appended);
495}
496
497bool DeepHeapProfile::TextBuffer::AppendBase64(uint64 value, int width) {
498  static const char base64[65] =
499      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
500#if defined(__BIG_ENDIAN__)
501  value = bswap_64(value);
502#endif
503  for (int shift = (width - 1) * 6; shift >= 0; shift -= 6) {
504    if (!AppendChar(base64[(value >> shift) & 0x3f]))
505      return false;
506  }
507  return true;
508}
509
510bool DeepHeapProfile::TextBuffer::ForwardCursor(int appended) {
511  if (appended < 0 || appended >= size_ - cursor_)
512    return false;
513  cursor_ += appended;
514  if (cursor_ > size_ * 4 / 5)
515    Flush();
516  return true;
517}
518
519void DeepHeapProfile::DeepBucket::UnparseForStats(TextBuffer* buffer) {
520  buffer->AppendInt64(bucket->alloc_size - bucket->free_size, 10);
521  buffer->AppendChar(' ');
522  buffer->AppendInt64(committed_size, 10);
523  buffer->AppendChar(' ');
524  buffer->AppendInt(bucket->allocs, 6, false);
525  buffer->AppendChar(' ');
526  buffer->AppendInt(bucket->frees, 6, false);
527  buffer->AppendString(" @ ", 0);
528  buffer->AppendInt(id, 0, false);
529  buffer->AppendString("\n", 0);
530}
531
532void DeepHeapProfile::DeepBucket::UnparseForBucketFile(TextBuffer* buffer) {
533  buffer->AppendInt(id, 0, false);
534  buffer->AppendChar(' ');
535  buffer->AppendString(is_mmap ? "mmap" : "malloc", 0);
536
537#if defined(TYPE_PROFILING)
538  buffer->AppendString(" t0x", 0);
539  buffer->AppendPtr(reinterpret_cast<uintptr_t>(type), 0);
540  if (type == NULL) {
541    buffer->AppendString(" nno_typeinfo", 0);
542  } else {
543    buffer->AppendString(" n", 0);
544    buffer->AppendString(type->name(), 0);
545  }
546#endif
547
548  for (int depth = 0; depth < bucket->depth; depth++) {
549    buffer->AppendString(" 0x", 0);
550    buffer->AppendPtr(reinterpret_cast<uintptr_t>(bucket->stack[depth]), 8);
551  }
552  buffer->AppendString("\n", 0);
553}
554
555DeepHeapProfile::DeepBucketTable::DeepBucketTable(
556    int table_size,
557    HeapProfileTable::Allocator alloc,
558    HeapProfileTable::DeAllocator dealloc)
559    : table_(NULL),
560      table_size_(table_size),
561      alloc_(alloc),
562      dealloc_(dealloc),
563      bucket_id_(0) {
564  const int bytes = table_size * sizeof(DeepBucket*);
565  table_ = reinterpret_cast<DeepBucket**>(alloc(bytes));
566  memset(table_, 0, bytes);
567}
568
569DeepHeapProfile::DeepBucketTable::~DeepBucketTable() {
570  ASSERT(table_ != NULL);
571  for (int db = 0; db < table_size_; db++) {
572    for (DeepBucket* x = table_[db]; x != 0; /**/) {
573      DeepBucket* db = x;
574      x = x->next;
575      dealloc_(db);
576    }
577  }
578  dealloc_(table_);
579}
580
581DeepHeapProfile::DeepBucket* DeepHeapProfile::DeepBucketTable::Lookup(
582    Bucket* bucket,
583#if defined(TYPE_PROFILING)
584    const std::type_info* type,
585#endif
586    bool is_mmap) {
587  // Make hash-value
588  uintptr_t h = 0;
589
590  AddToHashValue(reinterpret_cast<uintptr_t>(bucket), &h);
591  if (is_mmap) {
592    AddToHashValue(1, &h);
593  } else {
594    AddToHashValue(0, &h);
595  }
596
597#if defined(TYPE_PROFILING)
598  if (type == NULL) {
599    AddToHashValue(0, &h);
600  } else {
601    AddToHashValue(reinterpret_cast<uintptr_t>(type->name()), &h);
602  }
603#endif
604
605  FinishHashValue(&h);
606
607  // Lookup stack trace in table
608  unsigned int buck = ((unsigned int) h) % table_size_;
609  for (DeepBucket* db = table_[buck]; db != 0; db = db->next) {
610    if (db->bucket == bucket) {
611      return db;
612    }
613  }
614
615  // Create a new bucket
616  DeepBucket* db = reinterpret_cast<DeepBucket*>(alloc_(sizeof(DeepBucket)));
617  memset(db, 0, sizeof(*db));
618  db->bucket         = bucket;
619#if defined(TYPE_PROFILING)
620  db->type           = type;
621#endif
622  db->committed_size = 0;
623  db->is_mmap        = is_mmap;
624  db->id             = (bucket_id_++);
625  db->is_logged      = false;
626  db->next           = table_[buck];
627  table_[buck] = db;
628  return db;
629}
630
631// TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf.
632void DeepHeapProfile::DeepBucketTable::UnparseForStats(TextBuffer* buffer) {
633  for (int i = 0; i < table_size_; i++) {
634    for (DeepBucket* deep_bucket = table_[i];
635         deep_bucket != NULL;
636         deep_bucket = deep_bucket->next) {
637      Bucket* bucket = deep_bucket->bucket;
638      if (bucket->alloc_size - bucket->free_size == 0) {
639        continue;  // Skip empty buckets.
640      }
641      deep_bucket->UnparseForStats(buffer);
642    }
643  }
644}
645
646void DeepHeapProfile::DeepBucketTable::WriteForBucketFile(
647    const char* prefix, int dump_count, char raw_buffer[], int buffer_size) {
648  char filename[100];
649  snprintf(filename, sizeof(filename),
650           "%s.%05d.%04d.buckets", prefix, getpid(), dump_count);
651  RawFD fd = RawOpenForWriting(filename);
652  RAW_DCHECK(fd != kIllegalRawFD, "");
653
654  TextBuffer buffer(raw_buffer, buffer_size, fd);
655
656  for (int i = 0; i < table_size_; i++) {
657    for (DeepBucket* deep_bucket = table_[i];
658         deep_bucket != NULL;
659         deep_bucket = deep_bucket->next) {
660      Bucket* bucket = deep_bucket->bucket;
661      if (deep_bucket->is_logged) {
662        continue;  // Skip the bucket if it is already logged.
663      }
664      if (!deep_bucket->is_mmap &&
665          bucket->alloc_size - bucket->free_size <= 64) {
666        continue;  // Skip small malloc buckets.
667      }
668
669      deep_bucket->UnparseForBucketFile(&buffer);
670      deep_bucket->is_logged = true;
671    }
672  }
673
674  buffer.Flush();
675  RawClose(fd);
676}
677
678void DeepHeapProfile::DeepBucketTable::ResetCommittedSize() {
679  for (int i = 0; i < table_size_; i++) {
680    for (DeepBucket* deep_bucket = table_[i];
681         deep_bucket != NULL;
682         deep_bucket = deep_bucket->next) {
683      deep_bucket->committed_size = 0;
684    }
685  }
686}
687
688void DeepHeapProfile::DeepBucketTable::ResetIsLogged() {
689  for (int i = 0; i < table_size_; i++) {
690    for (DeepBucket* deep_bucket = table_[i];
691         deep_bucket != NULL;
692         deep_bucket = deep_bucket->next) {
693      deep_bucket->is_logged = false;
694    }
695  }
696}
697
698// This hash function is from HeapProfileTable::GetBucket.
699// static
700void DeepHeapProfile::DeepBucketTable::AddToHashValue(
701    uintptr_t add, uintptr_t* hash_value) {
702  *hash_value += add;
703  *hash_value += *hash_value << 10;
704  *hash_value ^= *hash_value >> 6;
705}
706
707// This hash function is from HeapProfileTable::GetBucket.
708// static
709void DeepHeapProfile::DeepBucketTable::FinishHashValue(uintptr_t* hash_value) {
710  *hash_value += *hash_value << 3;
711  *hash_value ^= *hash_value >> 11;
712}
713
714void DeepHeapProfile::RegionStats::Initialize() {
715  virtual_bytes_ = 0;
716  committed_bytes_ = 0;
717}
718
719uint64 DeepHeapProfile::RegionStats::Record(
720    const MemoryResidenceInfoGetterInterface* memory_residence_info_getter,
721    uint64 first_address,
722    uint64 last_address,
723    TextBuffer* buffer) {
724  uint64 committed;
725  virtual_bytes_ += static_cast<size_t>(last_address - first_address + 1);
726  committed = memory_residence_info_getter->CommittedSize(first_address,
727                                                          last_address,
728                                                          buffer);
729  committed_bytes_ += committed;
730  return committed;
731}
732
733void DeepHeapProfile::RegionStats::Unparse(const char* name,
734                                           TextBuffer* buffer) {
735  buffer->AppendString(name, 25);
736  buffer->AppendChar(' ');
737  buffer->AppendLong(virtual_bytes_, 12);
738  buffer->AppendChar(' ');
739  buffer->AppendLong(committed_bytes_, 12);
740  buffer->AppendString("\n", 0);
741}
742
743// Snapshots all virtual memory mappging stats by merging mmap(2) records from
744// MemoryRegionMap and /proc/maps, the OS-level memory mapping information.
745// Memory regions described in /proc/maps, but which are not created by mmap,
746// are accounted as "unhooked" memory regions.
747//
748// This function assumes that every memory region created by mmap is covered
749// by VMA(s) described in /proc/maps except for http://crbug.com/189114.
750// Note that memory regions created with mmap don't align with borders of VMAs
751// in /proc/maps.  In other words, a memory region by mmap can cut across many
752// VMAs.  Also, of course a VMA can include many memory regions by mmap.
753// It means that the following situation happens:
754//
755// => Virtual address
756// <----- VMA #1 -----><----- VMA #2 ----->...<----- VMA #3 -----><- VMA #4 ->
757// ..< mmap #1 >.<- mmap #2 -><- mmap #3 ->...<- mmap #4 ->..<-- mmap #5 -->..
758//
759// It can happen easily as permission can be changed by mprotect(2) for a part
760// of a memory region.  A change in permission splits VMA(s).
761//
762// To deal with the situation, this function iterates over MemoryRegionMap and
763// /proc/maps independently.  The iterator for MemoryRegionMap is initialized
764// at the top outside the loop for /proc/maps, and it goes forward inside the
765// loop while comparing their addresses.
766//
767// TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf.
768void DeepHeapProfile::GlobalStats::SnapshotMaps(
769    const MemoryResidenceInfoGetterInterface* memory_residence_info_getter,
770    DeepHeapProfile* deep_profile,
771    TextBuffer* mmap_dump_buffer) {
772  MemoryRegionMap::LockHolder lock_holder;
773  ProcMapsIterator::Buffer procmaps_iter_buffer;
774  ProcMapsIterator procmaps_iter(0, &procmaps_iter_buffer);
775  uint64 vma_start_addr, vma_last_addr, offset;
776  int64 inode;
777  char* flags;
778  char* filename;
779  enum MapsRegionType type;
780  for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) {
781    all_[i].Initialize();
782    unhooked_[i].Initialize();
783  }
784  profiled_mmap_.Initialize();
785
786  MemoryRegionMap::RegionIterator mmap_iter =
787      MemoryRegionMap::BeginRegionLocked();
788  DeepBucket* deep_bucket = NULL;
789  if (mmap_iter != MemoryRegionMap::EndRegionLocked()) {
790    deep_bucket = GetInformationOfMemoryRegion(
791        mmap_iter, memory_residence_info_getter, deep_profile);
792  }
793
794  while (procmaps_iter.Next(&vma_start_addr, &vma_last_addr,
795                            &flags, &offset, &inode, &filename)) {
796    if (mmap_dump_buffer) {
797      char buffer[1024];
798      int written = procmaps_iter.FormatLine(buffer, sizeof(buffer),
799                                             vma_start_addr, vma_last_addr,
800                                             flags, offset, inode, filename, 0);
801      mmap_dump_buffer->AppendString(buffer, 0);
802    }
803
804    // 'vma_last_addr' should be the last inclusive address of the region.
805    vma_last_addr -= 1;
806    if (strcmp("[vsyscall]", filename) == 0) {
807      continue;  // Reading pagemap will fail in [vsyscall].
808    }
809
810    // TODO(dmikurube): |type| will be deprecated in the dump.
811    // See http://crbug.com/245603.
812    type = ABSENT;
813    if (filename[0] == '/') {
814      if (flags[2] == 'x')
815        type = FILE_EXEC;
816      else
817        type = FILE_NONEXEC;
818    } else if (filename[0] == '\0' || filename[0] == '\n') {
819      type = ANONYMOUS;
820    } else if (strcmp(filename, "[stack]") == 0) {
821      type = STACK;
822    } else {
823      type = OTHER;
824    }
825    // TODO(dmikurube): This |all_| count should be removed in future soon.
826    // See http://crbug.com/245603.
827    uint64 vma_total = all_[type].Record(
828        memory_residence_info_getter, vma_start_addr, vma_last_addr, NULL);
829    uint64 vma_subtotal = 0;
830
831    // TODO(dmikurube): Stop double-counting pagemap.
832    // It will be fixed when http://crbug.com/245603 finishes.
833    if (MemoryRegionMap::IsRecordingLocked()) {
834      uint64 cursor = vma_start_addr;
835      bool first = true;
836
837      // Iterates over MemoryRegionMap until the iterator moves out of the VMA.
838      do {
839        if (!first) {
840          cursor = mmap_iter->end_addr;
841          ++mmap_iter;
842          // Don't break here even if mmap_iter == EndRegionLocked().
843
844          if (mmap_iter != MemoryRegionMap::EndRegionLocked()) {
845            deep_bucket = GetInformationOfMemoryRegion(
846                mmap_iter, memory_residence_info_getter, deep_profile);
847          }
848        }
849        first = false;
850
851        uint64 last_address_of_unhooked;
852        // If the next mmap entry is away from the current VMA.
853        if (mmap_iter == MemoryRegionMap::EndRegionLocked() ||
854            mmap_iter->start_addr > vma_last_addr) {
855          last_address_of_unhooked = vma_last_addr;
856        } else {
857          last_address_of_unhooked = mmap_iter->start_addr - 1;
858        }
859
860        if (last_address_of_unhooked + 1 > cursor) {
861          RAW_CHECK(cursor >= vma_start_addr,
862                    "Wrong calculation for unhooked");
863          RAW_CHECK(last_address_of_unhooked <= vma_last_addr,
864                    "Wrong calculation for unhooked");
865          uint64 committed_size = unhooked_[type].Record(
866              memory_residence_info_getter,
867              cursor,
868              last_address_of_unhooked,
869              mmap_dump_buffer);
870          vma_subtotal += committed_size;
871          if (mmap_dump_buffer) {
872            mmap_dump_buffer->AppendString("  ", 0);
873            mmap_dump_buffer->AppendPtr(cursor, 0);
874            mmap_dump_buffer->AppendString(" - ", 0);
875            mmap_dump_buffer->AppendPtr(last_address_of_unhooked + 1, 0);
876            mmap_dump_buffer->AppendString("  unhooked ", 0);
877            mmap_dump_buffer->AppendInt64(committed_size, 0);
878            mmap_dump_buffer->AppendString(" / ", 0);
879            mmap_dump_buffer->AppendInt64(
880                last_address_of_unhooked - cursor + 1, 0);
881            mmap_dump_buffer->AppendString("\n", 0);
882          }
883          cursor = last_address_of_unhooked + 1;
884        }
885
886        if (mmap_iter != MemoryRegionMap::EndRegionLocked() &&
887            mmap_iter->start_addr <= vma_last_addr &&
888            mmap_dump_buffer) {
889          bool trailing = mmap_iter->start_addr < vma_start_addr;
890          bool continued = mmap_iter->end_addr - 1 > vma_last_addr;
891          uint64 partial_first_address, partial_last_address;
892          if (trailing)
893            partial_first_address = vma_start_addr;
894          else
895            partial_first_address = mmap_iter->start_addr;
896          if (continued)
897            partial_last_address = vma_last_addr;
898          else
899            partial_last_address = mmap_iter->end_addr - 1;
900          uint64 committed_size = memory_residence_info_getter->CommittedSize(
901              partial_first_address, partial_last_address, mmap_dump_buffer);
902          vma_subtotal += committed_size;
903          mmap_dump_buffer->AppendString(trailing ? " (" : "  ", 0);
904          mmap_dump_buffer->AppendPtr(mmap_iter->start_addr, 0);
905          mmap_dump_buffer->AppendString(trailing ? ")" : " ", 0);
906          mmap_dump_buffer->AppendString("-", 0);
907          mmap_dump_buffer->AppendString(continued ? "(" : " ", 0);
908          mmap_dump_buffer->AppendPtr(mmap_iter->end_addr, 0);
909          mmap_dump_buffer->AppendString(continued ? ")" : " ", 0);
910          mmap_dump_buffer->AppendString(" hooked ", 0);
911          mmap_dump_buffer->AppendInt64(committed_size, 0);
912          mmap_dump_buffer->AppendString(" / ", 0);
913          mmap_dump_buffer->AppendInt64(
914              partial_last_address - partial_first_address + 1, 0);
915          mmap_dump_buffer->AppendString(" @ ", 0);
916          if (deep_bucket != NULL) {
917            mmap_dump_buffer->AppendInt(deep_bucket->id, 0, false);
918          } else {
919            mmap_dump_buffer->AppendInt(0, 0, false);
920          }
921          mmap_dump_buffer->AppendString("\n", 0);
922        }
923      } while (mmap_iter != MemoryRegionMap::EndRegionLocked() &&
924               mmap_iter->end_addr - 1 <= vma_last_addr);
925    }
926
927    if (vma_total != vma_subtotal) {
928      char buffer[1024];
929      int written = procmaps_iter.FormatLine(buffer, sizeof(buffer),
930                                             vma_start_addr, vma_last_addr,
931                                             flags, offset, inode, filename, 0);
932      RAW_LOG(0, "[%d] Mismatched total in VMA %"PRId64":%"PRId64" (%"PRId64")",
933              getpid(), vma_total, vma_subtotal, vma_total - vma_subtotal);
934      RAW_LOG(0, "[%d]   in %s", getpid(), buffer);
935    }
936  }
937
938  // TODO(dmikurube): Investigate and fix http://crbug.com/189114.
939  //
940  // The total committed memory usage in all_ (from /proc/<pid>/maps) is
941  // sometimes smaller than the sum of the committed mmap'ed addresses and
942  // unhooked regions.  Within our observation, the difference was only 4KB
943  // in committed usage, zero in reserved virtual addresses
944  //
945  // A guess is that an uncommitted (but reserved) page may become committed
946  // during counting memory usage in the loop above.
947  //
948  // The difference is accounted as "ABSENT" to investigate such cases.
949  //
950  // It will be fixed when http://crbug.com/245603 finishes (no double count).
951
952  RegionStats all_total;
953  RegionStats unhooked_total;
954  for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) {
955    all_total.AddAnotherRegionStat(all_[i]);
956    unhooked_total.AddAnotherRegionStat(unhooked_[i]);
957  }
958
959  size_t absent_virtual = profiled_mmap_.virtual_bytes() +
960                          unhooked_total.virtual_bytes() -
961                          all_total.virtual_bytes();
962  if (absent_virtual > 0)
963    all_[ABSENT].AddToVirtualBytes(absent_virtual);
964
965  size_t absent_committed = profiled_mmap_.committed_bytes() +
966                            unhooked_total.committed_bytes() -
967                            all_total.committed_bytes();
968  if (absent_committed > 0)
969    all_[ABSENT].AddToCommittedBytes(absent_committed);
970}
971
972void DeepHeapProfile::GlobalStats::SnapshotAllocations(
973    DeepHeapProfile* deep_profile) {
974  profiled_malloc_.Initialize();
975
976  deep_profile->heap_profile_->address_map_->Iterate(RecordAlloc, deep_profile);
977}
978
979void DeepHeapProfile::GlobalStats::Unparse(TextBuffer* buffer) {
980  RegionStats all_total;
981  RegionStats unhooked_total;
982  for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) {
983    all_total.AddAnotherRegionStat(all_[i]);
984    unhooked_total.AddAnotherRegionStat(unhooked_[i]);
985  }
986
987  // "# total (%lu) %c= profiled-mmap (%lu) + nonprofiled-* (%lu)\n"
988  buffer->AppendString("# total (", 0);
989  buffer->AppendUnsignedLong(all_total.committed_bytes(), 0);
990  buffer->AppendString(") ", 0);
991  buffer->AppendChar(all_total.committed_bytes() ==
992                     profiled_mmap_.committed_bytes() +
993                     unhooked_total.committed_bytes() ? '=' : '!');
994  buffer->AppendString("= profiled-mmap (", 0);
995  buffer->AppendUnsignedLong(profiled_mmap_.committed_bytes(), 0);
996  buffer->AppendString(") + nonprofiled-* (", 0);
997  buffer->AppendUnsignedLong(unhooked_total.committed_bytes(), 0);
998  buffer->AppendString(")\n", 0);
999
1000  // "                               virtual    committed"
1001  buffer->AppendString("", 26);
1002  buffer->AppendString(kVirtualLabel, 12);
1003  buffer->AppendChar(' ');
1004  buffer->AppendString(kCommittedLabel, 12);
1005  buffer->AppendString("\n", 0);
1006
1007  all_total.Unparse("total", buffer);
1008  all_[ABSENT].Unparse("absent", buffer);
1009  all_[FILE_EXEC].Unparse("file-exec", buffer);
1010  all_[FILE_NONEXEC].Unparse("file-nonexec", buffer);
1011  all_[ANONYMOUS].Unparse("anonymous", buffer);
1012  all_[STACK].Unparse("stack", buffer);
1013  all_[OTHER].Unparse("other", buffer);
1014  unhooked_total.Unparse("nonprofiled-total", buffer);
1015  unhooked_[ABSENT].Unparse("nonprofiled-absent", buffer);
1016  unhooked_[ANONYMOUS].Unparse("nonprofiled-anonymous", buffer);
1017  unhooked_[FILE_EXEC].Unparse("nonprofiled-file-exec", buffer);
1018  unhooked_[FILE_NONEXEC].Unparse("nonprofiled-file-nonexec", buffer);
1019  unhooked_[STACK].Unparse("nonprofiled-stack", buffer);
1020  unhooked_[OTHER].Unparse("nonprofiled-other", buffer);
1021  profiled_mmap_.Unparse("profiled-mmap", buffer);
1022  profiled_malloc_.Unparse("profiled-malloc", buffer);
1023}
1024
1025// static
1026void DeepHeapProfile::GlobalStats::RecordAlloc(const void* pointer,
1027                                               AllocValue* alloc_value,
1028                                               DeepHeapProfile* deep_profile) {
1029  uint64 address = reinterpret_cast<uintptr_t>(pointer);
1030  size_t committed = deep_profile->memory_residence_info_getter_->CommittedSize(
1031      address, address + alloc_value->bytes - 1, NULL);
1032
1033  DeepBucket* deep_bucket = deep_profile->deep_table_.Lookup(
1034      alloc_value->bucket(),
1035#if defined(TYPE_PROFILING)
1036      LookupType(pointer),
1037#endif
1038      /* is_mmap */ false);
1039  deep_bucket->committed_size += committed;
1040  deep_profile->stats_.profiled_malloc_.AddToVirtualBytes(alloc_value->bytes);
1041  deep_profile->stats_.profiled_malloc_.AddToCommittedBytes(committed);
1042}
1043
1044DeepHeapProfile::DeepBucket*
1045    DeepHeapProfile::GlobalStats::GetInformationOfMemoryRegion(
1046        const MemoryRegionMap::RegionIterator& mmap_iter,
1047        const MemoryResidenceInfoGetterInterface* memory_residence_info_getter,
1048        DeepHeapProfile* deep_profile) {
1049  size_t committed = deep_profile->memory_residence_info_getter_->
1050      CommittedSize(mmap_iter->start_addr, mmap_iter->end_addr - 1, NULL);
1051
1052  // TODO(dmikurube): Store a reference to the bucket in region.
1053  Bucket* bucket = MemoryRegionMap::GetBucket(
1054      mmap_iter->call_stack_depth, mmap_iter->call_stack);
1055  DeepBucket* deep_bucket = NULL;
1056  if (bucket != NULL) {
1057    deep_bucket = deep_profile->deep_table_.Lookup(
1058        bucket,
1059#if defined(TYPE_PROFILING)
1060        NULL,  // No type information for memory regions by mmap.
1061#endif
1062        /* is_mmap */ true);
1063    if (deep_bucket != NULL)
1064      deep_bucket->committed_size += committed;
1065  }
1066
1067  profiled_mmap_.AddToVirtualBytes(
1068      mmap_iter->end_addr - mmap_iter->start_addr);
1069  profiled_mmap_.AddToCommittedBytes(committed);
1070
1071  return deep_bucket;
1072}
1073
1074// static
1075void DeepHeapProfile::WriteProcMaps(const char* prefix,
1076                                    char raw_buffer[],
1077                                    int buffer_size) {
1078  char filename[100];
1079  snprintf(filename, sizeof(filename),
1080           "%s.%05d.maps", prefix, static_cast<int>(getpid()));
1081
1082  RawFD fd = RawOpenForWriting(filename);
1083  RAW_DCHECK(fd != kIllegalRawFD, "");
1084
1085  int length;
1086  bool wrote_all;
1087  length = tcmalloc::FillProcSelfMaps(raw_buffer, buffer_size, &wrote_all);
1088  RAW_DCHECK(wrote_all, "");
1089  RAW_DCHECK(length <= buffer_size, "");
1090  RawWrite(fd, raw_buffer, length);
1091  RawClose(fd);
1092}
1093#else  // USE_DEEP_HEAP_PROFILE
1094
1095DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile,
1096                                 const char* prefix)
1097    : heap_profile_(heap_profile) {
1098}
1099
1100DeepHeapProfile::~DeepHeapProfile() {
1101}
1102
1103int DeepHeapProfile::DumpOrderedProfile(const char* reason,
1104                                        char raw_buffer[],
1105                                        int buffer_size,
1106                                        RawFD fd) {
1107}
1108
1109#endif  // USE_DEEP_HEAP_PROFILE
1110