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