// Copyright (c) 2006, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ---
// Author: Sanjay Ghemawat
// Maxim Lifantsev (refactoring)
//
#include "config.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h> // for write()
#endif
#include <fcntl.h> // for open()
#ifdef HAVE_GLOB_H
#include <glob.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h> // for PRIxPTR
#endif
#include <errno.h>
#include <string>
#include <algorithm> // for sort(), equal(), and copy()
#include "heap-profile-table.h"
#include "base/logging.h"
#include <google/stacktrace.h>
#include <google/malloc_hook.h>
#include "base/commandlineflags.h"
#include "base/sysinfo.h"
using std::sort;
using std::equal;
using std::copy;
using std::string;
//----------------------------------------------------------------------
DEFINE_bool(cleanup_old_heap_profiles,
EnvToBool("HEAP_PROFILE_CLEANUP", true),
"At initialization time, delete old heap profiles.");
//----------------------------------------------------------------------
// header of the dumped heap profile
static const char kProfileHeader[] = "heap profile: ";
static const char kProcSelfMapsHeader[] = "\nMAPPED_LIBRARIES:\n";
//----------------------------------------------------------------------
const char HeapProfileTable::kFileExt[] = ".heap";
const int HeapProfileTable::kHashTableSize;
const int HeapProfileTable::kMaxStackTrace;
//----------------------------------------------------------------------
// We strip out different number of stack frames in debug mode
// because less inlining happens in that case
#ifdef NDEBUG
static const int kStripFrames = 2;
#else
static const int kStripFrames = 3;
#endif
// Re-run fn until it doesn't cause EINTR.
#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR)
// Wrapper around ::write to undo it's potential partiality.
static void FDWrite(int fd, const char* buf, size_t len) {
while (len > 0) {
ssize_t r;
NO_INTR(r = write(fd, buf, len));
if (r <= 0) break;
buf += r;
len -= r;
}
}
// For sorting Stats or Buckets by in-use space
static bool ByAllocatedSpace(HeapProfileTable::Stats* a,
HeapProfileTable::Stats* b) {
// Return true iff "a" has more allocated space than "b"
return (a->alloc_size - a->free_size) > (b->alloc_size - b->free_size);
}
// Helper to add the list of mapped shared libraries to a profile.
// Fill formatted "/proc/self/maps" contents into buffer 'buf' of size 'size'
// and return the actual size occupied in 'buf'.
// We do not provision for 0-terminating 'buf'.
static int FillProcSelfMaps(char buf[], int size) {
ProcMapsIterator::Buffer iterbuf;
ProcMapsIterator it(0, &iterbuf); // 0 means "current pid"
uint64 start, end, offset;
int64 inode;
char *flags, *filename;
int bytes_written = 0;
while (it.Next(&start, &end, &flags, &offset, &inode, &filename)) {
bytes_written += it.FormatLine(buf + bytes_written, size - bytes_written,
start, end, flags, offset, inode, filename,
0);
}
return bytes_written;
}
// Dump the same data as FillProcSelfMaps reads to fd.
// It seems easier to repeat parts of FillProcSelfMaps here than to
// reuse it via a call.
static void DumpProcSelfMaps(int fd) {
ProcMapsIterator::Buffer iterbuf;
ProcMapsIterator it(0, &iterbuf); // 0 means "current pid"
uint64 start, end, offset;
int64 inode;
char *flags, *filename;
ProcMapsIterator::Buffer linebuf;
while (it.Next(&start, &end, &flags, &offset, &inode, &filename)) {
int written = it.FormatLine(linebuf.buf_, sizeof(linebuf.buf_),
start, end, flags, offset, inode, filename,
0);
FDWrite(fd, linebuf.buf_, written);
}
}
//----------------------------------------------------------------------
HeapProfileTable::HeapProfileTable(Allocator alloc, DeAllocator dealloc)
: alloc_(alloc), dealloc_(dealloc) {
// Make the table
const int table_bytes = kHashTableSize * sizeof(*table_);
table_ = reinterpret_cast<Bucket**>(alloc_(table_bytes));
memset(table_, 0, table_bytes);
// Make allocation map
allocation_ =
new(alloc_(sizeof(AllocationMap))) AllocationMap(alloc_, dealloc_);
// init the rest:
memset(&total_, 0, sizeof(total_));
num_buckets_ = 0;
}
HeapProfileTable::~HeapProfileTable() {
// free allocation map
allocation_->~AllocationMap();
dealloc_(allocation_);
allocation_ = NULL;
// free hash table
for (int b = 0; b < kHashTableSize; b++) {
for (Bucket* x = table_[b]; x != 0; /**/) {
Bucket* b = x;
x = x->next;
dealloc_(b->stack);
dealloc_(b);
}
}
dealloc_(table_);
table_ = NULL;
}
HeapProfileTable::Bucket* HeapProfileTable::GetBucket(int skip_count) {
// Get raw stack trace
void* key[kMaxStackTrace];
int depth =
MallocHook::GetCallerStackTrace(key, kMaxStackTrace, skip_count+1);
// Make hash-value
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;
// Lookup stack trace in table
unsigned int buck = ((unsigned int) h) % kHashTableSize;
for (Bucket* b = table_[buck]; b != 0; b = b->next) {
if ((b->hash == h) &&
(b->depth == depth) &&
equal(key, key + depth, b->stack)) {
return b;
}
}
// Create new bucket
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 = table_[buck];
table_[buck] = b;
num_buckets_++;
return b;
}
void HeapProfileTable::RecordAlloc(const void* ptr, size_t bytes,
int skip_count) {
Bucket* b = GetBucket(kStripFrames + skip_count + 1);
b->allocs++;
b->alloc_size += bytes;
total_.allocs++;
total_.alloc_size += bytes;
AllocValue v;
v.bucket = b;
v.bytes = bytes;
allocation_->Insert(ptr, v);
}
void HeapProfileTable::RecordFree(const void* ptr) {
AllocValue v;
if (allocation_->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 {
AllocValue alloc_value;
if (allocation_->Find(ptr, &alloc_value)) {
*object_size = alloc_value.bytes;
return true;
}
return false;
}
bool HeapProfileTable::FindAllocDetails(const void* ptr, AllocInfo* info) const {
AllocValue alloc_value;
if (allocation_->Find(ptr, &alloc_value)) {
info->object_size = alloc_value.bytes;
info->call_stack = alloc_value.bucket->stack;
info->stack_depth = alloc_value.bucket->depth;
return true;
}
return false;
}
void HeapProfileTable::MapArgsAllocIterator(
const void* ptr, AllocValue v, AllocIterator callback) {
AllocInfo info;
info.object_size = v.bytes;
info.call_stack = v.bucket->stack;
info.stack_depth = v.bucket->depth;
callback(ptr, info);
}
void HeapProfileTable::IterateAllocs(AllocIterator callback) const {
allocation_->Iterate(MapArgsAllocIterator, callback);
}
// We'd be happier using snprintfer, but we don't to reduce dependencies.
int HeapProfileTable::UnparseBucket(const Bucket& b,
char* buf, int buflen, int bufsize,
Stats* profile_stats) {
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"] @",
b.allocs - b.frees,
b.alloc_size - b.free_size,
b.allocs,
b.alloc_size);
// If it looks like the snprintf failed, ignore the fact we printed anything
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;
}
int HeapProfileTable::FillOrderedProfile(char buf[], int size) const {
// We can't allocate list on the stack, as this would overflow on threads
// running with a small stack size.
Bucket** list =
reinterpret_cast<Bucket**>(alloc_(sizeof(Bucket) * num_buckets_));
int n = 0;
for (int b = 0; b < kHashTableSize; b++) {
for (Bucket* x = table_[b]; x != 0; x = x->next) {
list[n++] = x;
}
}
RAW_DCHECK(n == num_buckets_, "");
sort(list, list + num_buckets_, ByAllocatedSpace);
// Our file format is "bucket, bucket, ..., bucket, proc_self_maps_info".
// In the cases buf is too small, we'd rather leave out the last
// buckets than leave out the /proc/self/maps info. To ensure that,
// we actually print the /proc/self/maps info first, then move it to
// the end of the buffer, then write the bucket info into whatever
// is remaining, and then move the maps info one last time to close
// any gaps. Whew!
int map_length = snprintf(buf, size, "%s", kProcSelfMapsHeader);
if (map_length < 0 || map_length >= size) return 0;
map_length += FillProcSelfMaps(buf + map_length, size - map_length);
RAW_DCHECK(map_length <= size, "");
char* const map_start = buf + size - map_length; // move to end
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, &stats);
for (int i = 0; i < n; 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); // close the gap
return bucket_length + map_length;
}
void HeapProfileTable::FilteredDumpIterator(const void* ptr, AllocValue v,
const DumpArgs& args) {
if (args.filter(ptr, v.bytes)) return;
Bucket b;
memset(&b, 0, sizeof(b));
b.allocs = 1;
b.alloc_size = v.bytes;
const void* stack[kMaxStackTrace + 1];
b.depth = v.bucket->depth + int(args.dump_alloc_addresses);
b.stack = stack;
if (args.dump_alloc_addresses) stack[0] = ptr;
memcpy(stack + int(args.dump_alloc_addresses),
v.bucket->stack, sizeof(stack[0]) * v.bucket->depth);
char buf[1024];
int len = UnparseBucket(b, buf, 0, sizeof(buf), args.profile_stats);
FDWrite(args.fd, buf, len);
}
bool HeapProfileTable::DumpFilteredProfile(const char* file_name,
DumpFilter filter,
bool dump_alloc_addresses,
Stats* profile_stats) const {
RAW_VLOG(1, "Dumping filtered heap profile to %s", file_name);
int fd = open(file_name, O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fd >= 0) {
FDWrite(fd, kProfileHeader, strlen(kProfileHeader));
char buf[512];
int len = UnparseBucket(total_, buf, 0, sizeof(buf), profile_stats);
FDWrite(fd, buf, len);
memset(profile_stats, 0, sizeof(*profile_stats));
const DumpArgs args(fd, dump_alloc_addresses, filter, profile_stats);
allocation_->Iterate<const DumpArgs&>(FilteredDumpIterator, args);
FDWrite(fd, kProcSelfMapsHeader, strlen(kProcSelfMapsHeader));
DumpProcSelfMaps(fd);
NO_INTR(close(fd));
return true;
} else {
RAW_LOG(ERROR, "Failed dumping filtered heap profile to %s", file_name);
return false;
}
}
void HeapProfileTable::CleanupOldProfiles(const char* prefix) {
if (!FLAGS_cleanup_old_heap_profiles)
return;
string pattern = string(prefix) + ".*" + 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(0, "Removing old heap profile %s", fname);
unlink(fname);
}
}
}
globfree(&g);
#else /* HAVE_GLOB_H */
RAW_LOG(WARNING, "Unable to remove old heap profiles (can't run glob())");
#endif
}
syntax highlighted by Code2HTML, v. 0.9.1