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