Bug 796038 - Default browser prompt shows even after setting defaults for Firefox...
[gecko.git] / tools / profiler / TableTicker.cpp
blobd080f7535e5d055a963b9941564ddb5479bf9990
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/. */
6 #include <string>
7 #include <stdio.h>
8 #include <iostream>
9 #include <fstream>
10 #include <sstream>
11 #include "sps_sampler.h"
12 #include "platform.h"
13 #include "nsXULAppAPI.h"
14 #include "nsThreadUtils.h"
15 #include "prenv.h"
16 #include "shared-libraries.h"
17 #include "mozilla/StackWalk.h"
19 // JSON
20 #include "JSObjectBuilder.h"
21 #include "nsIJSRuntimeService.h"
23 // Meta
24 #include "nsXPCOM.h"
25 #include "nsXPCOMCID.h"
26 #include "nsIHttpProtocolHandler.h"
27 #include "nsServiceManagerUtils.h"
28 #include "nsIXULRuntime.h"
29 #include "nsIXULAppInfo.h"
30 #include "nsDirectoryServiceUtils.h"
31 #include "nsDirectoryServiceDefs.h"
32 #include "nsIObserverService.h"
33 #include "mozilla/Services.h"
35 // JS
36 #include "jsdbgapi.h"
38 // we eventually want to make this runtime switchable
39 #if defined(MOZ_PROFILING) && (defined(XP_UNIX) && !defined(XP_MACOSX))
40 #ifndef ANDROID
41 #define USE_BACKTRACE
42 #endif
43 #endif
44 #ifdef USE_BACKTRACE
45 #include <execinfo.h>
46 #endif
48 #if defined(MOZ_PROFILING) && (defined(XP_MACOSX) || defined(XP_WIN))
49 #define USE_NS_STACKWALK
50 #endif
51 #ifdef USE_NS_STACKWALK
52 #include "nsStackWalk.h"
53 #endif
55 #if defined(MOZ_PROFILING) && defined(ANDROID)
56 #define USE_LIBUNWIND
57 #include <libunwind.h>
58 #include "android-signal-defs.h"
59 #endif
61 using std::string;
62 using namespace mozilla;
64 #ifdef XP_WIN
65 #include <windows.h>
66 #define getpid GetCurrentProcessId
67 #else
68 #include <unistd.h>
69 #endif
71 #ifndef MAXPATHLEN
72 #ifdef PATH_MAX
73 #define MAXPATHLEN PATH_MAX
74 #elif defined(MAX_PATH)
75 #define MAXPATHLEN MAX_PATH
76 #elif defined(_MAX_PATH)
77 #define MAXPATHLEN _MAX_PATH
78 #elif defined(CCHMAXPATH)
79 #define MAXPATHLEN CCHMAXPATH
80 #else
81 #define MAXPATHLEN 1024
82 #endif
83 #endif
85 #if _MSC_VER
86 #define snprintf _snprintf
87 #endif
89 static const int DYNAMIC_MAX_STRING = 512;
91 mozilla::ThreadLocal<ProfileStack *> tlsStack;
92 mozilla::ThreadLocal<TableTicker *> tlsTicker;
93 // We need to track whether we've been initialized otherwise
94 // we end up using tlsStack without initializing it.
95 // Because tlsStack is totally opaque to us we can't reuse
96 // it as the flag itself.
97 bool stack_key_initialized;
99 TimeStamp sLastTracerEvent;
100 int sFrameNumber = 0;
101 int sLastFrameNumber = 0;
103 class ThreadProfile;
105 class ProfileEntry
107 public:
108 ProfileEntry()
109 : mTagData(NULL)
110 , mTagName(0)
113 // aTagData must not need release (i.e. be a string from the text segment)
114 ProfileEntry(char aTagName, const char *aTagData)
115 : mTagData(aTagData)
116 , mTagName(aTagName)
119 ProfileEntry(char aTagName, void *aTagPtr)
120 : mTagPtr(aTagPtr)
121 , mTagName(aTagName)
124 ProfileEntry(char aTagName, double aTagFloat)
125 : mTagFloat(aTagFloat)
126 , mTagName(aTagName)
129 ProfileEntry(char aTagName, uintptr_t aTagOffset)
130 : mTagOffset(aTagOffset)
131 , mTagName(aTagName)
134 ProfileEntry(char aTagName, Address aTagAddress)
135 : mTagAddress(aTagAddress)
136 , mTagName(aTagName)
139 ProfileEntry(char aTagName, int aTagLine)
140 : mTagLine(aTagLine)
141 , mTagName(aTagName)
144 friend std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry);
146 private:
147 friend class ThreadProfile;
148 union {
149 const char* mTagData;
150 char mTagChars[sizeof(void*)];
151 void* mTagPtr;
152 double mTagFloat;
153 Address mTagAddress;
154 uintptr_t mTagOffset;
155 int mTagLine;
157 char mTagName;
160 #define PROFILE_MAX_ENTRY 100000
161 class ThreadProfile
163 public:
164 ThreadProfile(int aEntrySize, ProfileStack *aStack)
165 : mWritePos(0)
166 , mLastFlushPos(0)
167 , mReadPos(0)
168 , mEntrySize(aEntrySize)
169 , mStack(aStack)
171 mEntries = new ProfileEntry[mEntrySize];
174 ~ThreadProfile()
176 delete[] mEntries;
179 void addTag(ProfileEntry aTag)
181 // Called from signal, call only reentrant functions
182 mEntries[mWritePos] = aTag;
183 mWritePos = (mWritePos + 1) % mEntrySize;
184 if (mWritePos == mReadPos) {
185 // Keep one slot open
186 mEntries[mReadPos] = ProfileEntry();
187 mReadPos = (mReadPos + 1) % mEntrySize;
189 // we also need to move the flush pos to ensure we
190 // do not pass it
191 if (mWritePos == mLastFlushPos) {
192 mLastFlushPos = (mLastFlushPos + 1) % mEntrySize;
196 // flush the new entries
197 void flush()
199 mLastFlushPos = mWritePos;
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
206 // r = mReadPos
207 // w = mWritePos
208 // f = mLastFlushPos
210 // r f w
211 // |-----------------------------|
212 // | abcdefghijklmnopq | -> 'abcdefghijklmnopq'
213 // |-----------------------------|
216 // mWritePos and mReadPos have passed mLastFlushPos
217 // f
218 // w r
219 // |-----------------------------|
220 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz|
221 // |-----------------------------|
222 // w
223 // r
224 // |-----------------------------|
225 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> ''
226 // |-----------------------------|
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 // |-----------------------------|
242 // mWritePos has moved past mReadPos
243 // w r f
244 // |-----------------------------|
245 // |ABCDEFdefghijklmnopqrstuvwxyz|
246 // |-----------------------------|
247 // r w
248 // |-----------------------------|
249 // |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
250 // |-----------------------------|
252 void erase()
254 mWritePos = mLastFlushPos;
257 char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff)
259 int readAheadPos = (readPos + 1) % mEntrySize;
260 int tagBuffPos = 0;
262 // Read the string stored in mTagData until the null character is seen
263 bool seenNullByte = false;
264 while (readAheadPos != mLastFlushPos && !seenNullByte) {
265 (*tagsConsumed)++;
266 ProfileEntry readAheadEntry = mEntries[readAheadPos];
267 for (size_t pos = 0; pos < sizeof(void*); pos++) {
268 tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
269 if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
270 seenNullByte = true;
271 break;
273 tagBuffPos++;
275 if (!seenNullByte)
276 readAheadPos = (readAheadPos + 1) % mEntrySize;
278 return tagBuff;
281 friend std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile);
283 JSObject *ToJSObject(JSContext *aCx)
285 JSObjectBuilder b(aCx);
287 JSObject *profile = b.CreateObject();
288 JSObject *samples = b.CreateArray();
289 b.DefineProperty(profile, "samples", samples);
291 JSObject *sample = NULL;
292 JSObject *frames = NULL;
294 int readPos = mReadPos;
295 while (readPos != mLastFlushPos) {
296 // Number of tag consumed
297 int incBy = 1;
298 ProfileEntry entry = mEntries[readPos];
300 // Read ahead to the next tag, if it's a 'd' tag process it now
301 const char* tagStringData = entry.mTagData;
302 int readAheadPos = (readPos + 1) % mEntrySize;
303 char tagBuff[DYNAMIC_MAX_STRING];
304 // Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2
305 tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
307 if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
308 tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
311 switch (entry.mTagName) {
312 case 's':
313 sample = b.CreateObject();
314 b.DefineProperty(sample, "name", tagStringData);
315 frames = b.CreateArray();
316 b.DefineProperty(sample, "frames", frames);
317 b.ArrayPush(samples, sample);
318 break;
319 case 'r':
321 if (sample) {
322 b.DefineProperty(sample, "responsiveness", entry.mTagFloat);
325 break;
326 case 'f':
328 if (sample) {
329 b.DefineProperty(sample, "frameNumber", entry.mTagLine);
332 break;
333 case 't':
335 if (sample) {
336 b.DefineProperty(sample, "time", entry.mTagFloat);
339 break;
340 case 'c':
341 case 'l':
343 if (sample) {
344 JSObject *frame = b.CreateObject();
345 if (entry.mTagName == 'l') {
346 // Bug 753041
347 // We need a double cast here to tell GCC that we don't want to sign
348 // extend 32-bit addresses starting with 0xFXXXXXX.
349 unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
350 snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
351 b.DefineProperty(frame, "location", tagBuff);
352 } else {
353 b.DefineProperty(frame, "location", tagStringData);
354 readAheadPos = (readPos + incBy) % mEntrySize;
355 if (readAheadPos != mLastFlushPos &&
356 mEntries[readAheadPos].mTagName == 'n') {
357 b.DefineProperty(frame, "line",
358 mEntries[readAheadPos].mTagLine);
359 incBy++;
362 b.ArrayPush(frames, frame);
366 readPos = (readPos + incBy) % mEntrySize;
369 return profile;
372 ProfileStack* GetStack()
374 return mStack;
376 private:
377 // Circular buffer 'Keep One Slot Open' implementation
378 // for simplicity
379 ProfileEntry *mEntries;
380 int mWritePos; // points to the next entry we will write to
381 int mLastFlushPos; // points to the next entry since the last flush()
382 int mReadPos; // points to the next entry we will read to
383 int mEntrySize;
384 ProfileStack *mStack;
387 class SaveProfileTask;
389 static bool
390 hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) {
391 for(size_t i = 0; i < aFeatureCount; i++) {
392 if (strcmp(aFeatures[i], aFeature) == 0)
393 return true;
395 return false;
398 class TableTicker: public Sampler {
399 public:
400 TableTicker(int aInterval, int aEntrySize, ProfileStack *aStack,
401 const char** aFeatures, uint32_t aFeatureCount)
402 : Sampler(aInterval, true)
403 , mPrimaryThreadProfile(aEntrySize, aStack)
404 , mStartTime(TimeStamp::Now())
405 , mSaveRequested(false)
407 mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
409 //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
410 mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
411 mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
412 mPrimaryThreadProfile.addTag(ProfileEntry('m', "Start"));
415 ~TableTicker() { if (IsActive()) Stop(); }
417 virtual void SampleStack(TickSample* sample) {}
419 // Called within a signal. This function must be reentrant
420 virtual void Tick(TickSample* sample);
422 // Called within a signal. This function must be reentrant
423 virtual void RequestSave()
425 mSaveRequested = true;
428 virtual void HandleSaveRequest();
430 ThreadProfile* GetPrimaryThreadProfile()
432 return &mPrimaryThreadProfile;
435 JSObject *ToJSObject(JSContext *aCx);
436 JSObject *GetMetaJSObject(JSObjectBuilder& b);
438 const bool ProfileJS() { return mProfileJS; }
440 private:
441 // Not implemented on platforms which do not support backtracing
442 void doBacktrace(ThreadProfile &aProfile, TickSample* aSample);
444 private:
445 // This represent the application's main thread (SAMPLER_INIT)
446 ThreadProfile mPrimaryThreadProfile;
447 TimeStamp mStartTime;
448 bool mSaveRequested;
449 bool mUseStackWalk;
450 bool mJankOnly;
451 bool mProfileJS;
454 std::string GetSharedLibraryInfoString();
456 static JSBool
457 WriteCallback(const jschar *buf, uint32_t len, void *data)
459 std::ofstream& stream = *static_cast<std::ofstream*>(data);
460 nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len);
461 stream << profile.Data();
462 return JS_TRUE;
466 * This is an event used to save the profile on the main thread
467 * to be sure that it is not being modified while saving.
469 class SaveProfileTask : public nsRunnable {
470 public:
471 SaveProfileTask() {}
473 NS_IMETHOD Run() {
474 TableTicker *t = tlsTicker.get();
476 // Pause the profiler during saving.
477 // This will prevent us from recording sampling
478 // regarding profile saving. This will also
479 // prevent bugs caused by the circular buffer not
480 // being thread safe. Bug 750989.
481 t->SetPaused(true);
483 // Get file path
484 #ifdef MOZ_WIDGET_ANDROID
485 nsCString tmpPath;
486 tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
487 #else
488 nsCOMPtr<nsIFile> tmpFile;
489 nsAutoCString tmpPath;
490 if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
491 LOG("Failed to find temporary directory.");
492 return NS_ERROR_FAILURE;
494 tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid());
496 nsresult rv = tmpFile->AppendNative(tmpPath);
497 if (NS_FAILED(rv))
498 return rv;
500 rv = tmpFile->GetNativePath(tmpPath);
501 if (NS_FAILED(rv))
502 return rv;
503 #endif
505 // Create a JSContext to run a JSObjectBuilder :(
506 // Based on XPCShellEnvironment
507 JSRuntime *rt;
508 JSContext *cx;
509 nsCOMPtr<nsIJSRuntimeService> rtsvc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
510 if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
511 LOG("failed to get RuntimeService");
512 return NS_ERROR_FAILURE;;
515 cx = JS_NewContext(rt, 8192);
516 if (!cx) {
517 LOG("Failed to get context");
518 return NS_ERROR_FAILURE;
522 JSAutoRequest ar(cx);
523 static JSClass c = {
524 "global", JSCLASS_GLOBAL_FLAGS,
525 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
526 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
528 JSObject *obj = JS_NewGlobalObject(cx, &c, NULL);
530 std::ofstream stream;
531 stream.open(tmpPath.get());
532 // Pause the profiler during saving.
533 // This will prevent us from recording sampling
534 // regarding profile saving. This will also
535 // prevent bugs caused by the circular buffer not
536 // being thread safe. Bug 750989.
537 t->SetPaused(true);
538 if (stream.is_open()) {
539 JSAutoCompartment autoComp(cx, obj);
540 JSObject* profileObj = mozilla_sampler_get_profile_data(cx);
541 jsval val = OBJECT_TO_JSVAL(profileObj);
542 JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream);
543 stream.close();
544 LOGF("Saved to %s", tmpPath.get());
545 } else {
546 LOG("Fail to open profile log file.");
549 JS_EndRequest(cx);
550 JS_DestroyContext(cx);
552 t->SetPaused(false);
554 return NS_OK;
558 void TableTicker::HandleSaveRequest()
560 if (!mSaveRequested)
561 return;
562 mSaveRequested = false;
564 // TODO: Use use the ipc/chromium Tasks here to support processes
565 // without XPCOM.
566 nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask();
567 NS_DispatchToMainThread(runnable);
570 JSObject* TableTicker::GetMetaJSObject(JSObjectBuilder& b)
572 JSObject *meta = b.CreateObject();
574 b.DefineProperty(meta, "version", 2);
575 b.DefineProperty(meta, "interval", interval());
576 b.DefineProperty(meta, "stackwalk", mUseStackWalk);
577 b.DefineProperty(meta, "jank", mJankOnly);
578 b.DefineProperty(meta, "processType", XRE_GetProcessType());
580 nsresult res;
581 nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
582 if (!NS_FAILED(res)) {
583 nsAutoCString string;
585 res = http->GetPlatform(string);
586 if (!NS_FAILED(res))
587 b.DefineProperty(meta, "platform", string.Data());
589 res = http->GetOscpu(string);
590 if (!NS_FAILED(res))
591 b.DefineProperty(meta, "oscpu", string.Data());
593 res = http->GetMisc(string);
594 if (!NS_FAILED(res))
595 b.DefineProperty(meta, "misc", string.Data());
598 nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
599 if (runtime) {
600 nsAutoCString string;
602 res = runtime->GetXPCOMABI(string);
603 if (!NS_FAILED(res))
604 b.DefineProperty(meta, "abi", string.Data());
606 res = runtime->GetWidgetToolkit(string);
607 if (!NS_FAILED(res))
608 b.DefineProperty(meta, "toolkit", string.Data());
611 nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
612 if (appInfo) {
613 nsAutoCString string;
615 res = appInfo->GetName(string);
616 if (!NS_FAILED(res))
617 b.DefineProperty(meta, "product", string.Data());
620 return meta;
623 JSObject* TableTicker::ToJSObject(JSContext *aCx)
625 JSObjectBuilder b(aCx);
627 JSObject *profile = b.CreateObject();
629 // Put shared library info
630 b.DefineProperty(profile, "libs", GetSharedLibraryInfoString().c_str());
632 // Put meta data
633 JSObject *meta = GetMetaJSObject(b);
634 b.DefineProperty(profile, "meta", meta);
636 // Lists the samples for each ThreadProfile
637 JSObject *threads = b.CreateArray();
638 b.DefineProperty(profile, "threads", threads);
640 // For now we only have one thread
641 SetPaused(true);
642 JSObject* threadSamples = GetPrimaryThreadProfile()->ToJSObject(aCx);
643 b.ArrayPush(threads, threadSamples);
644 SetPaused(false);
646 return profile;
649 static
650 void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile,
651 ProfileStack *stack, void *lastpc)
653 int lineno = -1;
655 // First entry has tagName 's' (start)
656 // Check for magic pointer bit 1 to indicate copy
657 const char* sampleLabel = entry.label();
658 if (entry.isCopyLabel()) {
659 // Store the string using 1 or more 'd' (dynamic) tags
660 // that will happen to the preceding tag
662 aProfile.addTag(ProfileEntry('c', ""));
663 // Add one to store the null termination
664 size_t strLen = strlen(sampleLabel) + 1;
665 for (size_t j = 0; j < strLen;) {
666 // Store as many characters in the void* as the platform allows
667 char text[sizeof(void*)];
668 for (size_t pos = 0; pos < sizeof(void*) && j+pos < strLen; pos++) {
669 text[pos] = sampleLabel[j+pos];
671 j += sizeof(void*)/sizeof(char);
672 // Cast to *((void**) to pass the text data to a void*
673 aProfile.addTag(ProfileEntry('d', *((void**)(&text[0]))));
675 if (entry.js()) {
676 if (!entry.pc()) {
677 // The JIT only allows the top-most entry to have a NULL pc
678 MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]);
679 // If stack-walking was disabled, then that's just unfortunate
680 if (lastpc) {
681 jsbytecode *jspc = js::ProfilingGetPC(stack->mRuntime, entry.script(),
682 lastpc);
683 if (jspc) {
684 lineno = JS_PCToLineNumber(NULL, entry.script(), jspc);
687 } else {
688 lineno = JS_PCToLineNumber(NULL, entry.script(), entry.pc());
690 } else {
691 lineno = entry.line();
693 } else {
694 aProfile.addTag(ProfileEntry('c', sampleLabel));
695 lineno = entry.line();
697 if (lineno != -1) {
698 aProfile.addTag(ProfileEntry('n', lineno));
702 #ifdef USE_BACKTRACE
703 void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample)
705 void *array[100];
706 int count = backtrace (array, 100);
708 aProfile.addTag(ProfileEntry('s', "(root)"));
710 for (int i = 0; i < count; i++) {
711 if( (intptr_t)array[i] == -1 ) break;
712 aProfile.addTag(ProfileEntry('l', (void*)array[i]));
715 #endif
718 #ifdef USE_NS_STACKWALK
719 typedef struct {
720 void** array;
721 void** sp_array;
722 size_t size;
723 size_t count;
724 } PCArray;
726 static
727 void StackWalkCallback(void* aPC, void* aSP, void* aClosure)
729 PCArray* array = static_cast<PCArray*>(aClosure);
730 if (array->count >= array->size) {
731 // too many frames, ignore
732 return;
734 array->sp_array[array->count] = aSP;
735 array->array[array->count] = aPC;
736 array->count++;
739 void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample)
741 #ifndef XP_MACOSX
742 uintptr_t thread = GetThreadHandle(platform_data());
743 MOZ_ASSERT(thread);
744 #endif
745 void* pc_array[1000];
746 void* sp_array[1000];
747 PCArray array = {
748 pc_array,
749 sp_array,
750 mozilla::ArrayLength(pc_array),
754 // Start with the current function.
755 StackWalkCallback(aSample->pc, aSample->sp, &array);
757 #ifdef XP_MACOSX
758 pthread_t pt = GetProfiledThread(platform_data());
759 void *stackEnd = reinterpret_cast<void*>(-1);
760 if (pt)
761 stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt));
762 nsresult rv = FramePointerStackWalk(StackWalkCallback, 0, &array, reinterpret_cast<void**>(aSample->fp), stackEnd);
763 #else
764 nsresult rv = NS_StackWalk(StackWalkCallback, 0, &array, thread);
765 #endif
766 if (NS_SUCCEEDED(rv)) {
767 aProfile.addTag(ProfileEntry('s', "(root)"));
769 ProfileStack* stack = aProfile.GetStack();
770 int pseudoStackPos = 0;
772 /* We have two stacks, the native C stack we extracted from unwinding,
773 * and the pseudostack we managed during execution. We want to consolidate
774 * the two in order. We do so by merging using the approximate stack address
775 * when each entry was push. When pushing JS entry we may not now the stack
776 * address in which case we have a NULL stack address in which case we assume
777 * that it follows immediatly the previous element.
779 * C Stack | Address -- Pseudo Stack | Address
780 * main() | 0x100 run_js() | 0x40
781 * start() | 0x80 jsCanvas() | NULL
782 * timer() | 0x50 drawLine() | NULL
783 * azure() | 0x10
785 * Merged: main(), start(), timer(), run_js(), jsCanvas(), drawLine(), azure()
787 // i is the index in C stack starting at main and decreasing
788 // pseudoStackPos is the position in the Pseudo stack starting
789 // at the first frame (run_js in the example) and increasing.
790 for (size_t i = array.count; i > 0; --i) {
791 while (pseudoStackPos < stack->stackSize()) {
792 volatile StackEntry& entry = stack->mStack[pseudoStackPos];
794 if (entry.stackAddress() < array.sp_array[i-1] && entry.stackAddress())
795 break;
797 addProfileEntry(entry, aProfile, stack, array.array[0]);
798 pseudoStackPos++;
801 aProfile.addTag(ProfileEntry('l', (void*)array.array[i-1]));
805 #endif
807 #if defined(USE_LIBUNWIND) && defined(ANDROID)
808 void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample)
810 void* pc_array[1000];
811 size_t count = 0;
813 unw_cursor_t cursor; unw_context_t uc;
814 unw_word_t ip;
815 unw_getcontext(&uc);
817 // Dirty hack: replace the registers with values from the signal handler
818 // We do this in order to avoid the overhead of walking up to reach the
819 // signal handler frame, and the possibility that libunwind fails to
820 // handle it correctly.
821 unw_tdep_context_t *unw_ctx = reinterpret_cast<unw_tdep_context_t*> (&uc);
822 mcontext_t& mcontext = reinterpret_cast<ucontext_t*> (aSample->context)->uc_mcontext;
823 #define REPLACE_REG(num) unw_ctx->regs[num] = mcontext.gregs[R##num]
824 REPLACE_REG(0);
825 REPLACE_REG(1);
826 REPLACE_REG(2);
827 REPLACE_REG(3);
828 REPLACE_REG(4);
829 REPLACE_REG(5);
830 REPLACE_REG(6);
831 REPLACE_REG(7);
832 REPLACE_REG(8);
833 REPLACE_REG(9);
834 REPLACE_REG(10);
835 REPLACE_REG(11);
836 REPLACE_REG(12);
837 REPLACE_REG(13);
838 REPLACE_REG(14);
839 REPLACE_REG(15);
840 #undef REPLACE_REG
841 unw_init_local(&cursor, &uc);
842 while (count < ArrayLength(pc_array) &&
843 unw_step(&cursor) > 0) {
844 unw_get_reg(&cursor, UNW_REG_IP, &ip);
845 pc_array[count++] = reinterpret_cast<void*> (ip);
848 aProfile.addTag(ProfileEntry('s', "(root)"));
849 for (size_t i = count; i > 0; --i) {
850 aProfile.addTag(ProfileEntry('l', reinterpret_cast<void*>(pc_array[i - 1])));
853 #endif
855 static
856 void doSampleStackTrace(ProfileStack *aStack, ThreadProfile &aProfile, TickSample *sample)
858 // Sample
859 // 's' tag denotes the start of a sample block
860 // followed by 0 or more 'c' tags.
861 aProfile.addTag(ProfileEntry('s', "(root)"));
862 for (uint32_t i = 0; i < aStack->stackSize(); i++) {
863 addProfileEntry(aStack->mStack[i], aProfile, aStack, nullptr);
865 #ifdef ENABLE_SPS_LEAF_DATA
866 if (sample) {
867 aProfile.addTag(ProfileEntry('l', (void*)sample->pc));
868 #ifdef ENABLE_ARM_LR_SAVING
869 aProfile.addTag(ProfileEntry('L', (void*)sample->lr));
870 #endif
872 #endif
875 /* used to keep track of the last event that we sampled during */
876 unsigned int sLastSampledEventGeneration = 0;
878 /* a counter that's incremented everytime we get responsiveness event
879 * note: it might also be worth tracking everytime we go around
880 * the event loop */
881 unsigned int sCurrentEventGeneration = 0;
882 /* we don't need to worry about overflow because we only treat the
883 * case of them being the same as special. i.e. we only run into
884 * a problem if 2^32 events happen between samples that we need
885 * to know are associated with different events */
887 void TableTicker::Tick(TickSample* sample)
889 // Marker(s) come before the sample
890 ProfileStack* stack = mPrimaryThreadProfile.GetStack();
891 for (int i = 0; stack->getMarker(i) != NULL; i++) {
892 mPrimaryThreadProfile.addTag(ProfileEntry('m', stack->getMarker(i)));
894 stack->mQueueClearMarker = true;
896 bool recordSample = true;
897 if (mJankOnly) {
898 // if we are on a different event we can discard any temporary samples
899 // we've kept around
900 if (sLastSampledEventGeneration != sCurrentEventGeneration) {
901 // XXX: we also probably want to add an entry to the profile to help
902 // distinguish which samples are part of the same event. That, or record
903 // the event generation in each sample
904 mPrimaryThreadProfile.erase();
906 sLastSampledEventGeneration = sCurrentEventGeneration;
908 recordSample = false;
909 // only record the events when we have a we haven't seen a tracer event for 100ms
910 if (!sLastTracerEvent.IsNull()) {
911 TimeDuration delta = sample->timestamp - sLastTracerEvent;
912 if (delta.ToMilliseconds() > 100.0) {
913 recordSample = true;
918 #if defined(USE_BACKTRACE) || defined(USE_NS_STACKWALK) || defined(USE_LIBUNWIND)
919 if (mUseStackWalk) {
920 doBacktrace(mPrimaryThreadProfile, sample);
921 } else {
922 doSampleStackTrace(stack, mPrimaryThreadProfile, sample);
924 #else
925 doSampleStackTrace(stack, mPrimaryThreadProfile, sample);
926 #endif
928 if (recordSample)
929 mPrimaryThreadProfile.flush();
931 if (!sLastTracerEvent.IsNull() && sample) {
932 TimeDuration delta = sample->timestamp - sLastTracerEvent;
933 mPrimaryThreadProfile.addTag(ProfileEntry('r', delta.ToMilliseconds()));
936 if (sample) {
937 TimeDuration delta = sample->timestamp - mStartTime;
938 mPrimaryThreadProfile.addTag(ProfileEntry('t', delta.ToMilliseconds()));
941 if (sLastFrameNumber != sFrameNumber) {
942 mPrimaryThreadProfile.addTag(ProfileEntry('f', sFrameNumber));
943 sLastFrameNumber = sFrameNumber;
947 std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile)
949 int readPos = profile.mReadPos;
950 while (readPos != profile.mLastFlushPos) {
951 stream << profile.mEntries[readPos];
952 readPos = (readPos + 1) % profile.mEntrySize;
954 return stream;
957 std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry)
959 if (entry.mTagName == 'r' || entry.mTagName == 't') {
960 stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n";
961 } else if (entry.mTagName == 'l' || entry.mTagName == 'L') {
962 // Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms
963 // Additionally, stringstream seemed to be ignoring formatter flags.
964 char tagBuff[1024];
965 unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
966 snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc);
967 stream << tagBuff;
968 } else if (entry.mTagName == 'd') {
969 // TODO implement 'd' tag for text profile
970 } else {
971 stream << entry.mTagName << "-" << entry.mTagData << "\n";
973 return stream;
976 void mozilla_sampler_init()
978 if (stack_key_initialized)
979 return;
981 if (!tlsStack.init() || !tlsTicker.init()) {
982 LOG("Failed to init.");
983 return;
985 stack_key_initialized = true;
987 ProfileStack *stack = new ProfileStack();
988 tlsStack.set(stack);
990 #if defined(USE_LIBUNWIND) && defined(ANDROID)
991 // Only try debug_frame and exidx unwinding
992 putenv("UNW_ARM_UNWIND_METHOD=5");
993 #endif
995 // Allow the profiler to be started using signals
996 OS::RegisterStartHandler();
998 #if defined(USE_LIBUNWIND) && defined(__arm__) && defined(MOZ_CRASHREPORTER)
999 // On ARM, libunwind defines a signal handler for segmentation faults.
1000 // If SPS is enabled now, the crash reporter will override that signal
1001 // handler, and libunwind will likely break.
1002 return;
1003 #endif
1005 // We can't open pref so we use an environment variable
1006 // to know if we should trigger the profiler on startup
1007 // NOTE: Default
1008 const char *val = PR_GetEnv("MOZ_PROFILER_STARTUP");
1009 if (!val || !*val) {
1010 return;
1013 const char* features = "js";
1014 mozilla_sampler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
1015 &features, 1);
1018 void mozilla_sampler_deinit()
1020 mozilla_sampler_stop();
1021 // We can't delete the Stack because we can be between a
1022 // sampler call_enter/call_exit point.
1023 // TODO Need to find a safe time to delete Stack
1026 void mozilla_sampler_save()
1028 TableTicker *t = tlsTicker.get();
1029 if (!t) {
1030 return;
1033 t->RequestSave();
1034 // We're on the main thread already so we don't
1035 // have to wait to handle the save request.
1036 t->HandleSaveRequest();
1039 char* mozilla_sampler_get_profile()
1041 TableTicker *t = tlsTicker.get();
1042 if (!t) {
1043 return NULL;
1046 std::stringstream profile;
1047 t->SetPaused(true);
1048 profile << *(t->GetPrimaryThreadProfile());
1049 t->SetPaused(false);
1051 std::string profileString = profile.str();
1052 char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) );
1053 strcpy(rtn, profileString.c_str());
1054 return rtn;
1057 JSObject *mozilla_sampler_get_profile_data(JSContext *aCx)
1059 TableTicker *t = tlsTicker.get();
1060 if (!t) {
1061 return NULL;
1064 return t->ToJSObject(aCx);
1068 const char** mozilla_sampler_get_features()
1070 static const char* features[] = {
1071 #if defined(MOZ_PROFILING) && (defined(USE_BACKTRACE) || defined(USE_NS_STACKWALK) || defined(USE_LIBUNWIND))
1072 "stackwalk",
1073 #endif
1074 "jank",
1075 "js",
1076 NULL
1079 return features;
1082 // Values are only honored on the first start
1083 void mozilla_sampler_start(int aProfileEntries, int aInterval,
1084 const char** aFeatures, uint32_t aFeatureCount)
1086 if (!stack_key_initialized)
1087 mozilla_sampler_init();
1089 ProfileStack *stack = tlsStack.get();
1090 if (!stack) {
1091 ASSERT(false);
1092 return;
1095 mozilla_sampler_stop();
1097 TableTicker *t = new TableTicker(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL,
1098 aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY,
1099 stack, aFeatures, aFeatureCount);
1100 tlsTicker.set(t);
1101 t->Start();
1102 if (t->ProfileJS())
1103 stack->enableJSSampling();
1105 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1106 if (os)
1107 os->NotifyObservers(nullptr, "profiler-started", nullptr);
1110 void mozilla_sampler_stop()
1112 if (!stack_key_initialized)
1113 mozilla_sampler_init();
1115 TableTicker *t = tlsTicker.get();
1116 if (!t) {
1117 return;
1120 bool disableJS = t->ProfileJS();
1122 t->Stop();
1123 delete t;
1124 tlsTicker.set(NULL);
1125 ProfileStack *stack = tlsStack.get();
1126 ASSERT(stack != NULL);
1128 if (disableJS)
1129 stack->disableJSSampling();
1131 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1132 if (os)
1133 os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
1136 bool mozilla_sampler_is_active()
1138 if (!stack_key_initialized)
1139 mozilla_sampler_init();
1141 TableTicker *t = tlsTicker.get();
1142 if (!t) {
1143 return false;
1146 return t->IsActive();
1149 double sResponsivenessTimes[100];
1150 double sCurrResponsiveness = 0.f;
1151 unsigned int sResponsivenessLoc = 0;
1152 void mozilla_sampler_responsiveness(TimeStamp aTime)
1154 if (!sLastTracerEvent.IsNull()) {
1155 if (sResponsivenessLoc == 100) {
1156 for(size_t i = 0; i < 100-1; i++) {
1157 sResponsivenessTimes[i] = sResponsivenessTimes[i+1];
1159 sResponsivenessLoc--;
1161 TimeDuration delta = aTime - sLastTracerEvent;
1162 sResponsivenessTimes[sResponsivenessLoc++] = delta.ToMilliseconds();
1164 sCurrentEventGeneration++;
1166 sLastTracerEvent = aTime;
1169 const double* mozilla_sampler_get_responsiveness()
1171 return sResponsivenessTimes;
1174 void mozilla_sampler_frame_number(int frameNumber)
1176 sFrameNumber = frameNumber;