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