// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- // 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 #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #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(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(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(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(evict_); size_t bytes = sizeof(evict_[0]) * num_evicted_; total_bytes_ += bytes; FDWrite(out_, buf, bytes); } num_evicted_ = 0; }