tools/profiler/ProfileEntry.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:b08126d9034c
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include <ostream>
7 #include <sstream>
8 #include "platform.h"
9 #include "nsThreadUtils.h"
10 #include "nsXULAppAPI.h"
11 #include "jsapi.h"
12
13 // JSON
14 #include "JSStreamWriter.h"
15
16 // Self
17 #include "ProfileEntry.h"
18
19 #if _MSC_VER
20 #define snprintf _snprintf
21 #endif
22
23 ////////////////////////////////////////////////////////////////////////
24 // BEGIN ProfileEntry
25
26 ProfileEntry::ProfileEntry()
27 : mTagData(nullptr)
28 , mTagName(0)
29 { }
30
31 // aTagData must not need release (i.e. be a string from the text segment)
32 ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
33 : mTagData(aTagData)
34 , mTagName(aTagName)
35 { }
36
37 ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker)
38 : mTagMarker(aTagMarker)
39 , mTagName(aTagName)
40 { }
41
42 ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
43 : mTagPtr(aTagPtr)
44 , mTagName(aTagName)
45 { }
46
47 ProfileEntry::ProfileEntry(char aTagName, float aTagFloat)
48 : mTagFloat(aTagFloat)
49 , mTagName(aTagName)
50 { }
51
52 ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset)
53 : mTagOffset(aTagOffset)
54 , mTagName(aTagName)
55 { }
56
57 ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
58 : mTagAddress(aTagAddress)
59 , mTagName(aTagName)
60 { }
61
62 ProfileEntry::ProfileEntry(char aTagName, int aTagLine)
63 : mTagLine(aTagLine)
64 , mTagName(aTagName)
65 { }
66
67 ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
68 : mTagChar(aTagChar)
69 , mTagName(aTagName)
70 { }
71
72 bool ProfileEntry::is_ent_hint(char hintChar) {
73 return mTagName == 'h' && mTagChar == hintChar;
74 }
75
76 bool ProfileEntry::is_ent_hint() {
77 return mTagName == 'h';
78 }
79
80 bool ProfileEntry::is_ent(char tagChar) {
81 return mTagName == tagChar;
82 }
83
84 void* ProfileEntry::get_tagPtr() {
85 // No consistency checking. Oh well.
86 return mTagPtr;
87 }
88
89 void ProfileEntry::log()
90 {
91 // There is no compiler enforced mapping between tag chars
92 // and union variant fields, so the following was derived
93 // by looking through all the use points of TableTicker.cpp.
94 // mTagMarker (ProfilerMarker*) m
95 // mTagData (const char*) c,s
96 // mTagPtr (void*) d,l,L,B (immediate backtrace), S(start-of-stack)
97 // mTagLine (int) n,f
98 // mTagChar (char) h
99 // mTagFloat (double) r,t,p
100 switch (mTagName) {
101 case 'm':
102 LOGF("%c \"%s\"", mTagName, mTagMarker->GetMarkerName()); break;
103 case 'c': case 's':
104 LOGF("%c \"%s\"", mTagName, mTagData); break;
105 case 'd': case 'l': case 'L': case 'B': case 'S':
106 LOGF("%c %p", mTagName, mTagPtr); break;
107 case 'n': case 'f':
108 LOGF("%c %d", mTagName, mTagLine); break;
109 case 'h':
110 LOGF("%c \'%c\'", mTagName, mTagChar); break;
111 case 'r': case 't': case 'p':
112 LOGF("%c %f", mTagName, mTagFloat); break;
113 default:
114 LOGF("'%c' unknown_tag", mTagName); break;
115 }
116 }
117
118 std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry)
119 {
120 if (entry.mTagName == 'r' || entry.mTagName == 't') {
121 stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n";
122 } else if (entry.mTagName == 'l' || entry.mTagName == 'L') {
123 // Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms
124 // Additionally, stringstream seemed to be ignoring formatter flags.
125 char tagBuff[1024];
126 unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
127 snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc);
128 stream << tagBuff;
129 } else if (entry.mTagName == 'd') {
130 // TODO implement 'd' tag for text profile
131 } else {
132 stream << entry.mTagName << "-" << entry.mTagData << "\n";
133 }
134 return stream;
135 }
136
137 // END ProfileEntry
138 ////////////////////////////////////////////////////////////////////////
139
140
141 ////////////////////////////////////////////////////////////////////////
142 // BEGIN ThreadProfile
143
144 #define DYNAMIC_MAX_STRING 512
145
146 ThreadProfile::ThreadProfile(const char* aName, int aEntrySize,
147 PseudoStack *aStack, Thread::tid_t aThreadId,
148 PlatformData* aPlatform,
149 bool aIsMainThread, void *aStackTop)
150 : mWritePos(0)
151 , mLastFlushPos(0)
152 , mReadPos(0)
153 , mEntrySize(aEntrySize)
154 , mPseudoStack(aStack)
155 , mMutex("ThreadProfile::mMutex")
156 , mName(strdup(aName))
157 , mThreadId(aThreadId)
158 , mIsMainThread(aIsMainThread)
159 , mPlatformData(aPlatform)
160 , mGeneration(0)
161 , mPendingGenerationFlush(0)
162 , mStackTop(aStackTop)
163 {
164 mEntries = new ProfileEntry[mEntrySize];
165 }
166
167 ThreadProfile::~ThreadProfile()
168 {
169 free(mName);
170 delete[] mEntries;
171 }
172
173 void ThreadProfile::addTag(ProfileEntry aTag)
174 {
175 // Called from signal, call only reentrant functions
176 mEntries[mWritePos] = aTag;
177 mWritePos = mWritePos + 1;
178 if (mWritePos >= mEntrySize) {
179 mPendingGenerationFlush++;
180 mWritePos = mWritePos % mEntrySize;
181 }
182 if (mWritePos == mReadPos) {
183 // Keep one slot open
184 mEntries[mReadPos] = ProfileEntry();
185 mReadPos = (mReadPos + 1) % mEntrySize;
186 }
187 // we also need to move the flush pos to ensure we
188 // do not pass it
189 if (mWritePos == mLastFlushPos) {
190 mLastFlushPos = (mLastFlushPos + 1) % mEntrySize;
191 }
192 }
193
194 // flush the new entries
195 void ThreadProfile::flush()
196 {
197 mLastFlushPos = mWritePos;
198 mGeneration += mPendingGenerationFlush;
199 mPendingGenerationFlush = 0;
200 }
201
202 // discards all of the entries since the last flush()
203 // NOTE: that if mWritePos happens to wrap around past
204 // mLastFlushPos we actually only discard mWritePos - mLastFlushPos entries
205 //
206 // r = mReadPos
207 // w = mWritePos
208 // f = mLastFlushPos
209 //
210 // r f w
211 // |-----------------------------|
212 // | abcdefghijklmnopq | -> 'abcdefghijklmnopq'
213 // |-----------------------------|
214 //
215 //
216 // mWritePos and mReadPos have passed mLastFlushPos
217 // f
218 // w r
219 // |-----------------------------|
220 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz|
221 // |-----------------------------|
222 // w
223 // r
224 // |-----------------------------|
225 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> ''
226 // |-----------------------------|
227 //
228 //
229 // mWritePos will end up the same as mReadPos
230 // r
231 // w f
232 // |-----------------------------|
233 // |ABCDEFGHIJKLMklmnopqrstuvwxyz|
234 // |-----------------------------|
235 // r
236 // w
237 // |-----------------------------|
238 // |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> ''
239 // |-----------------------------|
240 //
241 //
242 // mWritePos has moved past mReadPos
243 // w r f
244 // |-----------------------------|
245 // |ABCDEFdefghijklmnopqrstuvwxyz|
246 // |-----------------------------|
247 // r w
248 // |-----------------------------|
249 // |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
250 // |-----------------------------|
251
252 void ThreadProfile::erase()
253 {
254 mWritePos = mLastFlushPos;
255 mPendingGenerationFlush = 0;
256 }
257
258 char* ThreadProfile::processDynamicTag(int readPos,
259 int* tagsConsumed, char* tagBuff)
260 {
261 int readAheadPos = (readPos + 1) % mEntrySize;
262 int tagBuffPos = 0;
263
264 // Read the string stored in mTagData until the null character is seen
265 bool seenNullByte = false;
266 while (readAheadPos != mLastFlushPos && !seenNullByte) {
267 (*tagsConsumed)++;
268 ProfileEntry readAheadEntry = mEntries[readAheadPos];
269 for (size_t pos = 0; pos < sizeof(void*); pos++) {
270 tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
271 if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
272 seenNullByte = true;
273 break;
274 }
275 tagBuffPos++;
276 }
277 if (!seenNullByte)
278 readAheadPos = (readAheadPos + 1) % mEntrySize;
279 }
280 return tagBuff;
281 }
282
283 void ThreadProfile::IterateTags(IterateTagsCallback aCallback)
284 {
285 MOZ_ASSERT(aCallback);
286
287 int readPos = mReadPos;
288 while (readPos != mLastFlushPos) {
289 // Number of tag consumed
290 int incBy = 1;
291 const ProfileEntry& entry = mEntries[readPos];
292
293 // Read ahead to the next tag, if it's a 'd' tag process it now
294 const char* tagStringData = entry.mTagData;
295 int readAheadPos = (readPos + 1) % mEntrySize;
296 char tagBuff[DYNAMIC_MAX_STRING];
297 // Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2
298 tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
299
300 if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
301 tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
302 }
303
304 aCallback(entry, tagStringData);
305
306 readPos = (readPos + incBy) % mEntrySize;
307 }
308 }
309
310 void ThreadProfile::ToStreamAsJSON(std::ostream& stream)
311 {
312 JSStreamWriter b(stream);
313 StreamJSObject(b);
314 }
315
316 void ThreadProfile::StreamJSObject(JSStreamWriter& b)
317 {
318 b.BeginObject();
319 // Thread meta data
320 if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
321 // TODO Add the proper plugin name
322 b.NameValue("name", "Plugin");
323 } else {
324 b.NameValue("name", mName);
325 }
326 b.NameValue("tid", static_cast<int>(mThreadId));
327
328 b.Name("samples");
329 b.BeginArray();
330
331 bool sample = false;
332 int readPos = mReadPos;
333 while (readPos != mLastFlushPos) {
334 // Number of tag consumed
335 ProfileEntry entry = mEntries[readPos];
336
337 switch (entry.mTagName) {
338 case 'r':
339 {
340 if (sample) {
341 b.NameValue("responsiveness", entry.mTagFloat);
342 }
343 }
344 break;
345 case 'p':
346 {
347 if (sample) {
348 b.NameValue("power", entry.mTagFloat);
349 }
350 }
351 break;
352 case 'f':
353 {
354 if (sample) {
355 b.NameValue("frameNumber", entry.mTagLine);
356 }
357 }
358 break;
359 case 't':
360 {
361 if (sample) {
362 b.NameValue("time", entry.mTagFloat);
363 }
364 }
365 break;
366 case 's':
367 {
368 // end the previous sample if there was one
369 if (sample) {
370 b.EndObject();
371 }
372 // begin the next sample
373 b.BeginObject();
374
375 sample = true;
376
377 // Seek forward through the entire sample, looking for frames
378 // this is an easier approach to reason about than adding more
379 // control variables and cases to the loop that goes through the buffer once
380 b.Name("frames");
381 b.BeginArray();
382
383 b.BeginObject();
384 b.NameValue("location", "(root)");
385 b.EndObject();
386
387 int framePos = (readPos + 1) % mEntrySize;
388 ProfileEntry frame = mEntries[framePos];
389 while (framePos != mLastFlushPos && frame.mTagName != 's') {
390 int incBy = 1;
391 frame = mEntries[framePos];
392 // Read ahead to the next tag, if it's a 'd' tag process it now
393 const char* tagStringData = frame.mTagData;
394 int readAheadPos = (framePos + 1) % mEntrySize;
395 char tagBuff[DYNAMIC_MAX_STRING];
396 // Make sure the string is always null terminated if it fills up
397 // DYNAMIC_MAX_STRING-2
398 tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
399
400 if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
401 tagStringData = processDynamicTag(framePos, &incBy, tagBuff);
402 }
403
404 // Write one frame. It can have either
405 // 1. only location - 'l' containing a memory address
406 // 2. location and line number - 'c' followed by 'd's and an optional 'n'
407 if (frame.mTagName == 'l') {
408 b.BeginObject();
409 // Bug 753041
410 // We need a double cast here to tell GCC that we don't want to sign
411 // extend 32-bit addresses starting with 0xFXXXXXX.
412 unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
413 snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
414 b.NameValue("location", tagBuff);
415 b.EndObject();
416 } else if (frame.mTagName == 'c') {
417 b.BeginObject();
418 b.NameValue("location", tagStringData);
419 readAheadPos = (framePos + incBy) % mEntrySize;
420 if (readAheadPos != mLastFlushPos &&
421 mEntries[readAheadPos].mTagName == 'n') {
422 b.NameValue("line", mEntries[readAheadPos].mTagLine);
423 incBy++;
424 }
425 b.EndObject();
426 }
427 framePos = (framePos + incBy) % mEntrySize;
428 }
429 b.EndArray();
430 }
431 break;
432 }
433 readPos = (readPos + 1) % mEntrySize;
434 }
435 if (sample) {
436 b.EndObject();
437 }
438 b.EndArray();
439
440 b.Name("markers");
441 b.BeginArray();
442 readPos = mReadPos;
443 while (readPos != mLastFlushPos) {
444 ProfileEntry entry = mEntries[readPos];
445 if (entry.mTagName == 'm') {
446 entry.getMarker()->StreamJSObject(b);
447 }
448 readPos = (readPos + 1) % mEntrySize;
449 }
450 b.EndArray();
451 b.EndObject();
452 }
453
454 JSObject* ThreadProfile::ToJSObject(JSContext *aCx)
455 {
456 JS::RootedValue val(aCx);
457 std::stringstream ss;
458 {
459 // Define a scope to prevent a moving GC during ~JSStreamWriter from
460 // trashing the return value.
461 JSStreamWriter b(ss);
462 StreamJSObject(b);
463 NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str()));
464 JS_ParseJSON(aCx, static_cast<const jschar*>(js_string.get()), js_string.Length(), &val);
465 }
466 return &val.toObject();
467 }
468
469 PseudoStack* ThreadProfile::GetPseudoStack()
470 {
471 return mPseudoStack;
472 }
473
474 void ThreadProfile::BeginUnwind()
475 {
476 mMutex.Lock();
477 }
478
479 void ThreadProfile::EndUnwind()
480 {
481 mMutex.Unlock();
482 }
483
484 mozilla::Mutex* ThreadProfile::GetMutex()
485 {
486 return &mMutex;
487 }
488
489 void ThreadProfile::DuplicateLastSample() {
490 // Scan the whole buffer (even unflushed parts)
491 // Adding mEntrySize makes the result of the modulus positive
492 // We search backwards from mWritePos-1 to mReadPos
493 for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize;
494 readPos != (mReadPos + mEntrySize - 1) % mEntrySize;
495 readPos = (readPos + mEntrySize - 1) % mEntrySize) {
496 if (mEntries[readPos].mTagName == 's') {
497 // Found the start of the last entry at position readPos
498 int copyEndIdx = mWritePos;
499 // Go through the whole entry and duplicate it
500 for (;readPos != copyEndIdx; readPos = (readPos + 1) % mEntrySize) {
501 switch (mEntries[readPos].mTagName) {
502 // Copy with new time
503 case 't':
504 addTag(ProfileEntry('t', static_cast<float>((mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds())));
505 break;
506 // Don't copy markers
507 case 'm':
508 break;
509 // Copy anything else we don't know about
510 // L, B, S, c, s, d, l, f, h, r, t, p
511 default:
512 addTag(mEntries[readPos]);
513 break;
514 }
515 }
516 break;
517 }
518 }
519 }
520
521 std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile)
522 {
523 int readPos = profile.mReadPos;
524 while (readPos != profile.mLastFlushPos) {
525 stream << profile.mEntries[readPos];
526 readPos = (readPos + 1) % profile.mEntrySize;
527 }
528 return stream;
529 }
530
531 // END ThreadProfile
532 ////////////////////////////////////////////////////////////////////////

mercurial