1// Copyright (c) 2007, Google Inc. 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are 6// met: 7// 8// * Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above 11// copyright notice, this list of conditions and the following disclaimer 12// in the documentation and/or other materials provided with the 13// distribution. 14// * Neither the name of Google Inc. nor the names of its 15// contributors may be used to endorse or promote products derived from 16// this software without specific prior written permission. 17// 18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29// 30// --- 31// Author: Sanjay Ghemawat 32// Chris Demetriou (refactoring) 33// 34// Collect profiling data. 35 36#include <config.h> 37#include <assert.h> 38#include <stdio.h> 39#include <stdlib.h> 40#include <errno.h> 41#ifdef HAVE_UNISTD_H 42#include <unistd.h> 43#endif 44#include <sys/time.h> 45#include <string.h> 46#include <fcntl.h> 47 48#include "profiledata.h" 49 50#include "base/logging.h" 51#include "base/sysinfo.h" 52 53// All of these are initialized in profiledata.h. 54const int ProfileData::kMaxStackDepth; 55const int ProfileData::kAssociativity; 56const int ProfileData::kBuckets; 57const int ProfileData::kBufferLength; 58 59ProfileData::Options::Options() 60 : frequency_(1) { 61} 62 63// This function is safe to call from asynchronous signals (but is not 64// re-entrant). However, that's not part of its public interface. 65void ProfileData::Evict(const Entry& entry) { 66 const int d = entry.depth; 67 const int nslots = d + 2; // Number of slots needed in eviction buffer 68 if (num_evicted_ + nslots > kBufferLength) { 69 FlushEvicted(); 70 assert(num_evicted_ == 0); 71 assert(nslots <= kBufferLength); 72 } 73 evict_[num_evicted_++] = entry.count; 74 evict_[num_evicted_++] = d; 75 memcpy(&evict_[num_evicted_], entry.stack, d * sizeof(Slot)); 76 num_evicted_ += d; 77} 78 79ProfileData::ProfileData() 80 : hash_(0), 81 evict_(0), 82 num_evicted_(0), 83 out_(-1), 84 count_(0), 85 evictions_(0), 86 total_bytes_(0), 87 fname_(0), 88 start_time_(0) { 89} 90 91bool ProfileData::Start(const char* fname, 92 const ProfileData::Options& options) { 93 if (enabled()) { 94 return false; 95 } 96 97 // Open output file and initialize various data structures 98 int fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0666); 99 if (fd < 0) { 100 // Can't open outfile for write 101 return false; 102 } 103 104 start_time_ = time(NULL); 105 fname_ = strdup(fname); 106 107 // Reset counters 108 num_evicted_ = 0; 109 count_ = 0; 110 evictions_ = 0; 111 total_bytes_ = 0; 112 113 hash_ = new Bucket[kBuckets]; 114 evict_ = new Slot[kBufferLength]; 115 memset(hash_, 0, sizeof(hash_[0]) * kBuckets); 116 117 // Record special entries 118 evict_[num_evicted_++] = 0; // count for header 119 evict_[num_evicted_++] = 3; // depth for header 120 evict_[num_evicted_++] = 0; // Version number 121 CHECK_NE(0, options.frequency()); 122 int period = 1000000 / options.frequency(); 123 evict_[num_evicted_++] = period; // Period (microseconds) 124 evict_[num_evicted_++] = 0; // Padding 125 126 out_ = fd; 127 128 return true; 129} 130 131ProfileData::~ProfileData() { 132 Stop(); 133} 134 135// Dump /proc/maps data to fd. Copied from heap-profile-table.cc. 136#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR) 137 138static void FDWrite(int fd, const char* buf, size_t len) { 139 while (len > 0) { 140 ssize_t r; 141 NO_INTR(r = write(fd, buf, len)); 142 RAW_CHECK(r >= 0, "write failed"); 143 buf += r; 144 len -= r; 145 } 146} 147 148static void DumpProcSelfMaps(int fd) { 149 ProcMapsIterator::Buffer iterbuf; 150 ProcMapsIterator it(0, &iterbuf); // 0 means "current pid" 151 152 uint64 start, end, offset; 153 int64 inode; 154 char *flags, *filename; 155 ProcMapsIterator::Buffer linebuf; 156 while (it.Next(&start, &end, &flags, &offset, &inode, &filename)) { 157 int written = it.FormatLine(linebuf.buf_, sizeof(linebuf.buf_), 158 start, end, flags, offset, inode, filename, 159 0); 160 FDWrite(fd, linebuf.buf_, written); 161 } 162} 163 164void ProfileData::Stop() { 165 if (!enabled()) { 166 return; 167 } 168 169 // Move data from hash table to eviction buffer 170 for (int b = 0; b < kBuckets; b++) { 171 Bucket* bucket = &hash_[b]; 172 for (int a = 0; a < kAssociativity; a++) { 173 if (bucket->entry[a].count > 0) { 174 Evict(bucket->entry[a]); 175 } 176 } 177 } 178 179 if (num_evicted_ + 3 > kBufferLength) { 180 // Ensure there is enough room for end of data marker 181 FlushEvicted(); 182 } 183 184 // Write end of data marker 185 evict_[num_evicted_++] = 0; // count 186 evict_[num_evicted_++] = 1; // depth 187 evict_[num_evicted_++] = 0; // end of data marker 188 FlushEvicted(); 189 190 // Dump "/proc/self/maps" so we get list of mapped shared libraries 191 DumpProcSelfMaps(out_); 192 193 Reset(); 194 fprintf(stderr, "PROFILE: interrupts/evictions/bytes = %d/%d/%" PRIuS "\n", 195 count_, evictions_, total_bytes_); 196} 197 198void ProfileData::Reset() { 199 if (!enabled()) { 200 return; 201 } 202 203 // Don't reset count_, evictions_, or total_bytes_ here. They're used 204 // by Stop to print information about the profile after reset, and are 205 // cleared by Start when starting a new profile. 206 close(out_); 207 delete[] hash_; 208 hash_ = 0; 209 delete[] evict_; 210 evict_ = 0; 211 num_evicted_ = 0; 212 free(fname_); 213 fname_ = 0; 214 start_time_ = 0; 215 216 out_ = -1; 217} 218 219// This function is safe to call from asynchronous signals (but is not 220// re-entrant). However, that's not part of its public interface. 221void ProfileData::GetCurrentState(State* state) const { 222 if (enabled()) { 223 state->enabled = true; 224 state->start_time = start_time_; 225 state->samples_gathered = count_; 226 int buf_size = sizeof(state->profile_name); 227 strncpy(state->profile_name, fname_, buf_size); 228 state->profile_name[buf_size-1] = '\0'; 229 } else { 230 state->enabled = false; 231 state->start_time = 0; 232 state->samples_gathered = 0; 233 state->profile_name[0] = '\0'; 234 } 235} 236 237// This function is safe to call from asynchronous signals (but is not 238// re-entrant). However, that's not part of its public interface. 239void ProfileData::FlushTable() { 240 if (!enabled()) { 241 return; 242 } 243 244 // Move data from hash table to eviction buffer 245 for (int b = 0; b < kBuckets; b++) { 246 Bucket* bucket = &hash_[b]; 247 for (int a = 0; a < kAssociativity; a++) { 248 if (bucket->entry[a].count > 0) { 249 Evict(bucket->entry[a]); 250 bucket->entry[a].depth = 0; 251 bucket->entry[a].count = 0; 252 } 253 } 254 } 255 256 // Write out all pending data 257 FlushEvicted(); 258} 259 260void ProfileData::Add(int depth, const void* const* stack) { 261 if (!enabled()) { 262 return; 263 } 264 265 if (depth > kMaxStackDepth) depth = kMaxStackDepth; 266 RAW_CHECK(depth > 0, "ProfileData::Add depth <= 0"); 267 268 // Make hash-value 269 Slot h = 0; 270 for (int i = 0; i < depth; i++) { 271 Slot slot = reinterpret_cast<Slot>(stack[i]); 272 h = (h << 8) | (h >> (8*(sizeof(h)-1))); 273 h += (slot * 31) + (slot * 7) + (slot * 3); 274 } 275 276 count_++; 277 278 // See if table already has an entry for this trace 279 bool done = false; 280 Bucket* bucket = &hash_[h % kBuckets]; 281 for (int a = 0; a < kAssociativity; a++) { 282 Entry* e = &bucket->entry[a]; 283 if (e->depth == depth) { 284 bool match = true; 285 for (int i = 0; i < depth; i++) { 286 if (e->stack[i] != reinterpret_cast<Slot>(stack[i])) { 287 match = false; 288 break; 289 } 290 } 291 if (match) { 292 e->count++; 293 done = true; 294 break; 295 } 296 } 297 } 298 299 if (!done) { 300 // Evict entry with smallest count 301 Entry* e = &bucket->entry[0]; 302 for (int a = 1; a < kAssociativity; a++) { 303 if (bucket->entry[a].count < e->count) { 304 e = &bucket->entry[a]; 305 } 306 } 307 if (e->count > 0) { 308 evictions_++; 309 Evict(*e); 310 } 311 312 // Use the newly evicted entry 313 e->depth = depth; 314 e->count = 1; 315 for (int i = 0; i < depth; i++) { 316 e->stack[i] = reinterpret_cast<Slot>(stack[i]); 317 } 318 } 319} 320 321// This function is safe to call from asynchronous signals (but is not 322// re-entrant). However, that's not part of its public interface. 323void ProfileData::FlushEvicted() { 324 if (num_evicted_ > 0) { 325 const char* buf = reinterpret_cast<char*>(evict_); 326 size_t bytes = sizeof(evict_[0]) * num_evicted_; 327 total_bytes_ += bytes; 328 FDWrite(out_, buf, bytes); 329 } 330 num_evicted_ = 0; 331} 332