This source file includes following definitions.
- ByAllocatedSpace
- address_map_
- GetBucket
- GetCallerStackTrace
- RecordAlloc
- RecordFree
- FindAlloc
- FindAllocDetails
- FindInsideAlloc
- MarkAsLive
- MarkAsIgnored
- IterateAllocationAddresses
- MarkCurrentAllocations
- MarkUnmarkedAllocations
- UnparseBucket
- MakeSortedBucketList
- DumpMarkedObjects
- DumpTypeStatistics
- IterateOrderedAllocContexts
- FillOrderedProfile
- DumpBucketIterator
- TallyTypesItererator
- DumpTypesIterator
- DumpNonLiveIterator
- DumpMarkedIterator
- AllocationAddressesIterator
- MarkIterator
- AddIfNonLive
- WriteProfile
- CleanupOldProfiles
- TakeSnapshot
- ReleaseSnapshot
- AddToSnapshot
- NonLiveSnapshot
- ReportCallback
- ReportLeaks
- ReportObject
- ReportIndividualObjects
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#ifdef HAVE_GLOB_H
#include <glob.h>
#ifndef GLOB_NOMATCH
# define GLOB_NOMATCH 0
#endif
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#ifdef HAVE_POLL_H
#include <poll.h>
#endif
#include <errno.h>
#include <stdarg.h>
#include <string>
#include <map>
#include <algorithm>
#include "heap-profile-table.h"
#include "base/logging.h"
#include "raw_printer.h"
#include "symbolize.h"
#include <gperftools/stacktrace.h>
#include <gperftools/malloc_hook.h>
#include "memory_region_map.h"
#include "base/commandlineflags.h"
#include "base/logging.h"
#include "base/sysinfo.h"
using std::sort;
using std::equal;
using std::copy;
using std::string;
using std::map;
using tcmalloc::FillProcSelfMaps;
using tcmalloc::DumpProcSelfMaps;
DEFINE_bool(cleanup_old_heap_profiles,
EnvToBool("HEAP_PROFILE_CLEANUP", true),
"At initialization time, delete old heap profiles.");
DEFINE_int32(heap_check_max_leaks,
EnvToInt("HEAP_CHECK_MAX_LEAKS", 20),
"The maximum number of leak reports to print.");
static const char kProfileHeader[] = "heap profile: ";
static const char kProcSelfMapsHeader[] = "\nMAPPED_LIBRARIES:\n";
#if defined(TYPE_PROFILING)
static const char kTypeProfileStatsHeader[] = "type statistics:\n";
#endif
const char HeapProfileTable::kFileExt[] = ".heap";
static const int kHashTableSize = 179999;
#if !defined(COMPILER_MSVC)
const int HeapProfileTable::kMaxStackDepth;
#endif
#ifdef NDEBUG
static const int kStripFrames = 2;
#else
static const int kStripFrames = 3;
#endif
static bool ByAllocatedSpace(HeapProfileTable::Stats* a,
HeapProfileTable::Stats* b) {
return (a->alloc_size - a->free_size) > (b->alloc_size - b->free_size);
}
HeapProfileTable::HeapProfileTable(Allocator alloc,
DeAllocator dealloc,
bool profile_mmap)
: alloc_(alloc),
dealloc_(dealloc),
bucket_table_(NULL),
profile_mmap_(profile_mmap),
num_buckets_(0),
address_map_(NULL) {
const int table_bytes = kHashTableSize * sizeof(*bucket_table_);
bucket_table_ = static_cast<Bucket**>(alloc_(table_bytes));
memset(bucket_table_, 0, table_bytes);
address_map_ =
new(alloc_(sizeof(AllocationMap))) AllocationMap(alloc_, dealloc_);
memset(&total_, 0, sizeof(total_));
num_buckets_ = 0;
}
HeapProfileTable::~HeapProfileTable() {
address_map_->~AllocationMap();
dealloc_(address_map_);
address_map_ = NULL;
for (int i = 0; i < kHashTableSize; i++) {
for (Bucket* curr = bucket_table_[i]; curr != 0; ) {
Bucket* bucket = curr;
curr = curr->next;
dealloc_(bucket->stack);
dealloc_(bucket);
}
}
dealloc_(bucket_table_);
bucket_table_ = NULL;
}
HeapProfileTable::Bucket* HeapProfileTable::GetBucket(int depth,
const void* const key[]) {
uintptr_t h = 0;
for (int i = 0; i < depth; i++) {
h += reinterpret_cast<uintptr_t>(key[i]);
h += h << 10;
h ^= h >> 6;
}
h += h << 3;
h ^= h >> 11;
unsigned int buck = ((unsigned int) h) % kHashTableSize;
for (Bucket* b = bucket_table_[buck]; b != 0; b = b->next) {
if ((b->hash == h) &&
(b->depth == depth) &&
equal(key, key + depth, b->stack)) {
return b;
}
}
const size_t key_size = sizeof(key[0]) * depth;
const void** kcopy = reinterpret_cast<const void**>(alloc_(key_size));
copy(key, key + depth, kcopy);
Bucket* b = reinterpret_cast<Bucket*>(alloc_(sizeof(Bucket)));
memset(b, 0, sizeof(*b));
b->hash = h;
b->depth = depth;
b->stack = kcopy;
b->next = bucket_table_[buck];
bucket_table_[buck] = b;
num_buckets_++;
return b;
}
int HeapProfileTable::GetCallerStackTrace(
int skip_count, void* stack[kMaxStackDepth]) {
return MallocHook::GetCallerStackTrace(
stack, kMaxStackDepth, kStripFrames + skip_count + 1);
}
void HeapProfileTable::RecordAlloc(
const void* ptr, size_t bytes, int stack_depth,
const void* const call_stack[]) {
Bucket* b = GetBucket(stack_depth, call_stack);
b->allocs++;
b->alloc_size += bytes;
total_.allocs++;
total_.alloc_size += bytes;
AllocValue v;
v.set_bucket(b);
v.bytes = bytes;
address_map_->Insert(ptr, v);
}
void HeapProfileTable::RecordFree(const void* ptr) {
AllocValue v;
if (address_map_->FindAndRemove(ptr, &v)) {
Bucket* b = v.bucket();
b->frees++;
b->free_size += v.bytes;
total_.frees++;
total_.free_size += v.bytes;
}
}
bool HeapProfileTable::FindAlloc(const void* ptr, size_t* object_size) const {
const AllocValue* alloc_value = address_map_->Find(ptr);
if (alloc_value != NULL) *object_size = alloc_value->bytes;
return alloc_value != NULL;
}
bool HeapProfileTable::FindAllocDetails(const void* ptr,
AllocInfo* info) const {
const AllocValue* alloc_value = address_map_->Find(ptr);
if (alloc_value != NULL) {
info->object_size = alloc_value->bytes;
info->call_stack = alloc_value->bucket()->stack;
info->stack_depth = alloc_value->bucket()->depth;
}
return alloc_value != NULL;
}
bool HeapProfileTable::FindInsideAlloc(const void* ptr,
size_t max_size,
const void** object_ptr,
size_t* object_size) const {
const AllocValue* alloc_value =
address_map_->FindInside(&AllocValueSize, max_size, ptr, object_ptr);
if (alloc_value != NULL) *object_size = alloc_value->bytes;
return alloc_value != NULL;
}
bool HeapProfileTable::MarkAsLive(const void* ptr) {
AllocValue* alloc = address_map_->FindMutable(ptr);
if (alloc && !alloc->live()) {
alloc->set_live(true);
return true;
}
return false;
}
void HeapProfileTable::MarkAsIgnored(const void* ptr) {
AllocValue* alloc = address_map_->FindMutable(ptr);
if (alloc) {
alloc->set_ignore(true);
}
}
void HeapProfileTable::IterateAllocationAddresses(AddressIterator f,
void* data) {
const AllocationAddressIteratorArgs args(f, data);
address_map_->Iterate<const AllocationAddressIteratorArgs&>(
AllocationAddressesIterator, args);
}
void HeapProfileTable::MarkCurrentAllocations(AllocationMark mark) {
const MarkArgs args(mark, true);
address_map_->Iterate<const MarkArgs&>(MarkIterator, args);
}
void HeapProfileTable::MarkUnmarkedAllocations(AllocationMark mark) {
const MarkArgs args(mark, false);
address_map_->Iterate<const MarkArgs&>(MarkIterator, args);
}
int HeapProfileTable::UnparseBucket(const Bucket& b,
char* buf, int buflen, int bufsize,
const char* extra,
Stats* profile_stats) {
if (profile_stats != NULL) {
profile_stats->allocs += b.allocs;
profile_stats->alloc_size += b.alloc_size;
profile_stats->frees += b.frees;
profile_stats->free_size += b.free_size;
}
int printed =
snprintf(buf + buflen, bufsize - buflen,
"%6d: %8" PRId64 " [%6d: %8" PRId64 "] @%s",
b.allocs - b.frees,
b.alloc_size - b.free_size,
b.allocs,
b.alloc_size,
extra);
if (printed < 0 || printed >= bufsize - buflen) return buflen;
buflen += printed;
for (int d = 0; d < b.depth; d++) {
printed = snprintf(buf + buflen, bufsize - buflen, " 0x%08" PRIxPTR,
reinterpret_cast<uintptr_t>(b.stack[d]));
if (printed < 0 || printed >= bufsize - buflen) return buflen;
buflen += printed;
}
printed = snprintf(buf + buflen, bufsize - buflen, "\n");
if (printed < 0 || printed >= bufsize - buflen) return buflen;
buflen += printed;
return buflen;
}
HeapProfileTable::Bucket**
HeapProfileTable::MakeSortedBucketList() const {
Bucket** list = static_cast<Bucket**>(alloc_(sizeof(Bucket) * num_buckets_));
int bucket_count = 0;
for (int i = 0; i < kHashTableSize; i++) {
for (Bucket* curr = bucket_table_[i]; curr != 0; curr = curr->next) {
list[bucket_count++] = curr;
}
}
RAW_DCHECK(bucket_count == num_buckets_, "");
sort(list, list + num_buckets_, ByAllocatedSpace);
return list;
}
void HeapProfileTable::DumpMarkedObjects(AllocationMark mark,
const char* file_name) {
RawFD fd = RawOpenForWriting(file_name);
if (fd == kIllegalRawFD) {
RAW_LOG(ERROR, "Failed dumping live objects to %s", file_name);
return;
}
const DumpMarkedArgs args(fd, mark);
address_map_->Iterate<const DumpMarkedArgs&>(DumpMarkedIterator, args);
RawClose(fd);
}
#if defined(TYPE_PROFILING)
void HeapProfileTable::DumpTypeStatistics(const char* file_name) const {
RawFD fd = RawOpenForWriting(file_name);
if (fd == kIllegalRawFD) {
RAW_LOG(ERROR, "Failed dumping type statistics to %s", file_name);
return;
}
AddressMap<TypeCount>* type_size_map;
type_size_map = new(alloc_(sizeof(AddressMap<TypeCount>)))
AddressMap<TypeCount>(alloc_, dealloc_);
address_map_->Iterate(TallyTypesItererator, type_size_map);
RawWrite(fd, kTypeProfileStatsHeader, strlen(kTypeProfileStatsHeader));
const DumpArgs args(fd, NULL);
type_size_map->Iterate<const DumpArgs&>(DumpTypesIterator, args);
RawClose(fd);
type_size_map->~AddressMap<TypeCount>();
dealloc_(type_size_map);
}
#endif
void HeapProfileTable::IterateOrderedAllocContexts(
AllocContextIterator callback) const {
Bucket** list = MakeSortedBucketList();
AllocContextInfo info;
for (int i = 0; i < num_buckets_; ++i) {
*static_cast<Stats*>(&info) = *static_cast<Stats*>(list[i]);
info.stack_depth = list[i]->depth;
info.call_stack = list[i]->stack;
callback(info);
}
dealloc_(list);
}
int HeapProfileTable::FillOrderedProfile(char buf[], int size) const {
Bucket** list = MakeSortedBucketList();
int map_length = snprintf(buf, size, "%s", kProcSelfMapsHeader);
if (map_length < 0 || map_length >= size) return 0;
bool dummy;
map_length += FillProcSelfMaps(buf + map_length, size - map_length, &dummy);
RAW_DCHECK(map_length <= size, "");
char* const map_start = buf + size - map_length;
memmove(map_start, buf, map_length);
size -= map_length;
Stats stats;
memset(&stats, 0, sizeof(stats));
int bucket_length = snprintf(buf, size, "%s", kProfileHeader);
if (bucket_length < 0 || bucket_length >= size) return 0;
bucket_length = UnparseBucket(total_, buf, bucket_length, size,
" heapprofile", &stats);
if (profile_mmap_) {
BufferArgs buffer(buf, bucket_length, size);
MemoryRegionMap::IterateBuckets<BufferArgs*>(DumpBucketIterator, &buffer);
bucket_length = buffer.buflen;
}
for (int i = 0; i < num_buckets_; i++) {
bucket_length = UnparseBucket(*list[i], buf, bucket_length, size, "",
&stats);
}
RAW_DCHECK(bucket_length < size, "");
dealloc_(list);
RAW_DCHECK(buf + bucket_length <= map_start, "");
memmove(buf + bucket_length, map_start, map_length);
return bucket_length + map_length;
}
void HeapProfileTable::DumpBucketIterator(const Bucket* bucket,
BufferArgs* args) {
args->buflen = UnparseBucket(*bucket, args->buf, args->buflen, args->bufsize,
"", NULL);
}
#if defined(TYPE_PROFILING)
void HeapProfileTable::TallyTypesItererator(
const void* ptr,
AllocValue* value,
AddressMap<TypeCount>* type_size_map) {
const std::type_info* type = LookupType(ptr);
const void* key = NULL;
if (type)
key = type->name();
TypeCount* count = type_size_map->FindMutable(key);
if (count) {
count->bytes += value->bytes;
++count->objects;
} else {
type_size_map->Insert(key, TypeCount(value->bytes, 1));
}
}
void HeapProfileTable::DumpTypesIterator(const void* ptr,
TypeCount* count,
const DumpArgs& args) {
char buf[1024];
int len;
const char* mangled_type_name = static_cast<const char*>(ptr);
len = snprintf(buf, sizeof(buf), "%6d: %8" PRId64 " @ %s\n",
count->objects, count->bytes,
mangled_type_name ? mangled_type_name : "(no_typeinfo)");
RawWrite(args.fd, buf, len);
}
#endif
inline
void HeapProfileTable::DumpNonLiveIterator(const void* ptr, AllocValue* v,
const DumpArgs& args) {
if (v->live()) {
v->set_live(false);
return;
}
if (v->ignore()) {
return;
}
Bucket b;
memset(&b, 0, sizeof(b));
b.allocs = 1;
b.alloc_size = v->bytes;
b.depth = v->bucket()->depth;
b.stack = v->bucket()->stack;
char buf[1024];
int len = UnparseBucket(b, buf, 0, sizeof(buf), "", args.profile_stats);
RawWrite(args.fd, buf, len);
}
inline
void HeapProfileTable::DumpMarkedIterator(const void* ptr, AllocValue* v,
const DumpMarkedArgs& args) {
if (v->mark() != args.mark)
return;
Bucket b;
memset(&b, 0, sizeof(b));
b.allocs = 1;
b.alloc_size = v->bytes;
b.depth = v->bucket()->depth;
b.stack = v->bucket()->stack;
char addr[16];
snprintf(addr, 16, "0x%08" PRIxPTR, ptr);
char buf[1024];
int len = UnparseBucket(b, buf, 0, sizeof(buf), addr, NULL);
RawWrite(args.fd, buf, len);
}
inline
void HeapProfileTable::AllocationAddressesIterator(
const void* ptr,
AllocValue* v,
const AllocationAddressIteratorArgs& args) {
args.callback(args.data, ptr);
}
inline
void HeapProfileTable::MarkIterator(const void* ptr, AllocValue* v,
const MarkArgs& args) {
if (!args.mark_all && v->mark() != UNMARKED)
return;
v->set_mark(args.mark);
}
void HeapProfileTable::AddIfNonLive(const void* ptr, AllocValue* v,
AddNonLiveArgs* arg) {
if (v->live()) {
v->set_live(false);
} else {
if (arg->base != NULL && arg->base->map_.Find(ptr) != NULL) {
} else {
arg->dest->Add(ptr, *v);
}
}
}
bool HeapProfileTable::WriteProfile(const char* file_name,
const Bucket& total,
AllocationMap* allocations) {
RAW_VLOG(1, "Dumping non-live heap profile to %s", file_name);
RawFD fd = RawOpenForWriting(file_name);
if (fd == kIllegalRawFD) {
RAW_LOG(ERROR, "Failed dumping filtered heap profile to %s", file_name);
return false;
}
RawWrite(fd, kProfileHeader, strlen(kProfileHeader));
char buf[512];
int len = UnparseBucket(total, buf, 0, sizeof(buf), " heapprofile",
NULL);
RawWrite(fd, buf, len);
const DumpArgs args(fd, NULL);
allocations->Iterate<const DumpArgs&>(DumpNonLiveIterator, args);
RawWrite(fd, kProcSelfMapsHeader, strlen(kProcSelfMapsHeader));
DumpProcSelfMaps(fd);
RawClose(fd);
return true;
}
void HeapProfileTable::CleanupOldProfiles(const char* prefix) {
if (!FLAGS_cleanup_old_heap_profiles)
return;
char buf[1000];
snprintf(buf, 1000,"%s.%05d.", prefix, getpid());
string pattern = string(buf) + ".*" + kFileExt;
#if defined(HAVE_GLOB_H)
glob_t g;
const int r = glob(pattern.c_str(), GLOB_ERR, NULL, &g);
if (r == 0 || r == GLOB_NOMATCH) {
const int prefix_length = strlen(prefix);
for (int i = 0; i < g.gl_pathc; i++) {
const char* fname = g.gl_pathv[i];
if ((strlen(fname) >= prefix_length) &&
(memcmp(fname, prefix, prefix_length) == 0)) {
RAW_VLOG(1, "Removing old heap profile %s", fname);
unlink(fname);
}
}
}
globfree(&g);
#else
RAW_LOG(WARNING, "Unable to remove old heap profiles (can't run glob())");
#endif
}
HeapProfileTable::Snapshot* HeapProfileTable::TakeSnapshot() {
Snapshot* s = new (alloc_(sizeof(Snapshot))) Snapshot(alloc_, dealloc_);
address_map_->Iterate(AddToSnapshot, s);
return s;
}
void HeapProfileTable::ReleaseSnapshot(Snapshot* s) {
s->~Snapshot();
dealloc_(s);
}
void HeapProfileTable::AddToSnapshot(const void* ptr, AllocValue* v,
Snapshot* snapshot) {
snapshot->Add(ptr, *v);
}
HeapProfileTable::Snapshot* HeapProfileTable::NonLiveSnapshot(
Snapshot* base) {
RAW_VLOG(2, "NonLiveSnapshot input: %d %d\n",
int(total_.allocs - total_.frees),
int(total_.alloc_size - total_.free_size));
Snapshot* s = new (alloc_(sizeof(Snapshot))) Snapshot(alloc_, dealloc_);
AddNonLiveArgs args;
args.dest = s;
args.base = base;
address_map_->Iterate<AddNonLiveArgs*>(AddIfNonLive, &args);
RAW_VLOG(2, "NonLiveSnapshot output: %d %d\n",
int(s->total_.allocs - s->total_.frees),
int(s->total_.alloc_size - s->total_.free_size));
return s;
}
struct HeapProfileTable::Snapshot::Entry {
int count;
int bytes;
Bucket* bucket;
Entry() : count(0), bytes(0) { }
bool operator<(const Entry& x) const {
return this->bytes > x.bytes;
}
};
struct HeapProfileTable::Snapshot::ReportState {
map<Bucket*, Entry> buckets_;
};
void HeapProfileTable::Snapshot::ReportCallback(const void* ptr,
AllocValue* v,
ReportState* state) {
Entry* e = &state->buckets_[v->bucket()];
e->bucket = v->bucket();
e->count++;
e->bytes += v->bytes;
}
void HeapProfileTable::Snapshot::ReportLeaks(const char* checker_name,
const char* filename,
bool should_symbolize) {
RAW_LOG(ERROR, "Leak check %s detected leaks of %" PRIuS " bytes "
"in %" PRIuS " objects",
checker_name,
size_t(total_.alloc_size),
size_t(total_.allocs));
ReportState state;
map_.Iterate(&ReportCallback, &state);
const int n = state.buckets_.size();
Entry* entries = new Entry[n];
int dst = 0;
for (map<Bucket*,Entry>::const_iterator iter = state.buckets_.begin();
iter != state.buckets_.end();
++iter) {
entries[dst++] = iter->second;
}
sort(entries, entries + n);
const int to_report =
(FLAGS_heap_check_max_leaks > 0 &&
n > FLAGS_heap_check_max_leaks) ? FLAGS_heap_check_max_leaks : n;
RAW_LOG(ERROR, "The %d largest leaks:", to_report);
SymbolTable symbolization_table;
for (int i = 0; i < to_report; i++) {
const Entry& e = entries[i];
for (int j = 0; j < e.bucket->depth; j++) {
symbolization_table.Add(e.bucket->stack[j]);
}
}
static const int kBufSize = 2<<10;
char buffer[kBufSize];
if (should_symbolize)
symbolization_table.Symbolize();
for (int i = 0; i < to_report; i++) {
const Entry& e = entries[i];
base::RawPrinter printer(buffer, kBufSize);
printer.Printf("Leak of %d bytes in %d objects allocated from:\n",
e.bytes, e.count);
for (int j = 0; j < e.bucket->depth; j++) {
const void* pc = e.bucket->stack[j];
printer.Printf("\t@ %" PRIxPTR " %s\n",
reinterpret_cast<uintptr_t>(pc), symbolization_table.GetSymbol(pc));
}
RAW_LOG(ERROR, "%s", buffer);
}
if (to_report < n) {
RAW_LOG(ERROR, "Skipping leaks numbered %d..%d",
to_report, n-1);
}
delete[] entries;
if (!HeapProfileTable::WriteProfile(filename, total_, &map_)) {
RAW_LOG(ERROR, "Could not write pprof profile to %s", filename);
}
}
void HeapProfileTable::Snapshot::ReportObject(const void* ptr,
AllocValue* v,
char* unused) {
RAW_LOG(ERROR, "leaked %" PRIuS " byte object %p", v->bytes, ptr);
}
void HeapProfileTable::Snapshot::ReportIndividualObjects() {
char unused;
map_.Iterate(ReportObject, &unused);
}