|  | // 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; | 
|  | } |