blob: 5f2531bbb0079ca97917b6f04bc572d33cefe8d9 [file] [log] [blame]
// Copyright (c) 2007, 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
// Chris Demetriou (refactoring)
//
// Collect profiling data.
#include <config.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/time.h>
#include <string.h>
#include <fcntl.h>
#include "profiledata.h"
#include "base/logging.h"
#include "base/sysinfo.h"
// All of these are initialized in profiledata.h.
const int ProfileData::kMaxStackDepth;
const int ProfileData::kAssociativity;
const int ProfileData::kBuckets;
const int ProfileData::kBufferLength;
ProfileData::Options::Options()
: frequency_(1) {
}
// This function is safe to call from asynchronous signals (but is not
// re-entrant). However, that's not part of its public interface.
void ProfileData::Evict(const Entry& entry) {
const int d = entry.depth;
const int nslots = d + 2; // Number of slots needed in eviction buffer
if (num_evicted_ + nslots > kBufferLength) {
FlushEvicted();
assert(num_evicted_ == 0);
assert(nslots <= kBufferLength);
}
evict_[num_evicted_++] = entry.count;
evict_[num_evicted_++] = d;
memcpy(&evict_[num_evicted_], entry.stack, d * sizeof(Slot));
num_evicted_ += d;
}
ProfileData::ProfileData()
: hash_(0),
evict_(0),
num_evicted_(0),
out_(-1),
count_(0),
evictions_(0),
total_bytes_(0),
fname_(0),
start_time_(0) {
}
bool ProfileData::Start(const char* fname,
const ProfileData::Options& options) {
if (enabled()) {
return false;
}
// Open output file and initialize various data structures
int fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd < 0) {
// Can't open outfile for write
return false;
}
start_time_ = time(NULL);
fname_ = strdup(fname);
// Reset counters
num_evicted_ = 0;
count_ = 0;
evictions_ = 0;
total_bytes_ = 0;
hash_ = new Bucket[kBuckets];
evict_ = new Slot[kBufferLength];
memset(hash_, 0, sizeof(hash_[0]) * kBuckets);
// Record special entries
evict_[num_evicted_++] = 0; // count for header
evict_[num_evicted_++] = 3; // depth for header
evict_[num_evicted_++] = 0; // Version number
CHECK_NE(0, options.frequency());
int period = 1000000 / options.frequency();
evict_[num_evicted_++] = period; // Period (microseconds)
evict_[num_evicted_++] = 0; // Padding
out_ = fd;
return true;
}
ProfileData::~ProfileData() {
Stop();
}
// Dump /proc/maps data to fd. Copied from heap-profile-table.cc.
#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR)
static void FDWrite(int fd, const char* buf, size_t len) {
while (len > 0) {
ssize_t r;
NO_INTR(r = write(fd, buf, len));
RAW_CHECK(r >= 0, "write failed");
buf += r;
len -= r;
}
}
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);
}
}
void ProfileData::Stop() {
if (!enabled()) {
return;
}
// Move data from hash table to eviction buffer
for (int b = 0; b < kBuckets; b++) {
Bucket* bucket = &hash_[b];
for (int a = 0; a < kAssociativity; a++) {
if (bucket->entry[a].count > 0) {
Evict(bucket->entry[a]);
}
}
}
if (num_evicted_ + 3 > kBufferLength) {
// Ensure there is enough room for end of data marker
FlushEvicted();
}
// Write end of data marker
evict_[num_evicted_++] = 0; // count
evict_[num_evicted_++] = 1; // depth
evict_[num_evicted_++] = 0; // end of data marker
FlushEvicted();
// Dump "/proc/self/maps" so we get list of mapped shared libraries
DumpProcSelfMaps(out_);
Reset();
fprintf(stderr, "PROFILE: interrupts/evictions/bytes = %d/%d/%" PRIuS "\n",
count_, evictions_, total_bytes_);
}
void ProfileData::Reset() {
if (!enabled()) {
return;
}
// Don't reset count_, evictions_, or total_bytes_ here. They're used
// by Stop to print information about the profile after reset, and are
// cleared by Start when starting a new profile.
close(out_);
delete[] hash_;
hash_ = 0;
delete[] evict_;
evict_ = 0;
num_evicted_ = 0;
free(fname_);
fname_ = 0;
start_time_ = 0;
out_ = -1;
}
// This function is safe to call from asynchronous signals (but is not
// re-entrant). However, that's not part of its public interface.
void ProfileData::GetCurrentState(State* state) const {
if (enabled()) {
state->enabled = true;
state->start_time = start_time_;
state->samples_gathered = count_;
int buf_size = sizeof(state->profile_name);
strncpy(state->profile_name, fname_, buf_size);
state->profile_name[buf_size-1] = '\0';
} else {
state->enabled = false;
state->start_time = 0;
state->samples_gathered = 0;
state->profile_name[0] = '\0';
}
}
// This function is safe to call from asynchronous signals (but is not
// re-entrant). However, that's not part of its public interface.
void ProfileData::FlushTable() {
if (!enabled()) {
return;
}
// Move data from hash table to eviction buffer
for (int b = 0; b < kBuckets; b++) {
Bucket* bucket = &hash_[b];
for (int a = 0; a < kAssociativity; a++) {
if (bucket->entry[a].count > 0) {
Evict(bucket->entry[a]);
bucket->entry[a].depth = 0;
bucket->entry[a].count = 0;
}
}
}
// Write out all pending data
FlushEvicted();
}
void ProfileData::Add(int depth, const void* const* stack) {
if (!enabled()) {
return;
}
if (depth > kMaxStackDepth) depth = kMaxStackDepth;
RAW_CHECK(depth > 0, "ProfileData::Add depth <= 0");
// Make hash-value
Slot h = 0;
for (int i = 0; i < depth; i++) {
Slot slot = reinterpret_cast<Slot>(stack[i]);
h = (h << 8) | (h >> (8*(sizeof(h)-1)));
h += (slot * 31) + (slot * 7) + (slot * 3);
}
count_++;
// See if table already has an entry for this trace
bool done = false;
Bucket* bucket = &hash_[h % kBuckets];
for (int a = 0; a < kAssociativity; a++) {
Entry* e = &bucket->entry[a];
if (e->depth == depth) {
bool match = true;
for (int i = 0; i < depth; i++) {
if (e->stack[i] != reinterpret_cast<Slot>(stack[i])) {
match = false;
break;
}
}
if (match) {
e->count++;
done = true;
break;
}
}
}
if (!done) {
// Evict entry with smallest count
Entry* e = &bucket->entry[0];
for (int a = 1; a < kAssociativity; a++) {
if (bucket->entry[a].count < e->count) {
e = &bucket->entry[a];
}
}
if (e->count > 0) {
evictions_++;
Evict(*e);
}
// Use the newly evicted entry
e->depth = depth;
e->count = 1;
for (int i = 0; i < depth; i++) {
e->stack[i] = reinterpret_cast<Slot>(stack[i]);
}
}
}
// This function is safe to call from asynchronous signals (but is not
// re-entrant). However, that's not part of its public interface.
void ProfileData::FlushEvicted() {
if (num_evicted_ > 0) {
const char* buf = reinterpret_cast<char*>(evict_);
size_t bytes = sizeof(evict_[0]) * num_evicted_;
total_bytes_ += bytes;
FDWrite(out_, buf, bytes);
}
num_evicted_ = 0;
}