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