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/. */
11 #include "sps_sampler.h"
13 #include "nsXULAppAPI.h"
14 #include "nsThreadUtils.h"
16 #include "shared-libraries.h"
17 #include "mozilla/StackWalk.h"
20 #include "JSObjectBuilder.h"
21 #include "nsIJSRuntimeService.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"
38 // we eventually want to make this runtime switchable
39 #if defined(MOZ_PROFILING) && (defined(XP_UNIX) && !defined(XP_MACOSX))
48 #if defined(MOZ_PROFILING) && (defined(XP_MACOSX) || defined(XP_WIN))
49 #define USE_NS_STACKWALK
51 #ifdef USE_NS_STACKWALK
52 #include "nsStackWalk.h"
55 #if defined(MOZ_PROFILING) && defined(ANDROID)
57 #include <libunwind.h>
58 #include "android-signal-defs.h"
62 using namespace mozilla
;
66 #define getpid GetCurrentProcessId
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
81 #define MAXPATHLEN 1024
86 #define snprintf _snprintf
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;
113 // aTagData must not need release (i.e. be a string from the text segment)
114 ProfileEntry(char aTagName
, const char *aTagData
)
119 ProfileEntry(char aTagName
, void *aTagPtr
)
124 ProfileEntry(char aTagName
, double aTagFloat
)
125 : mTagFloat(aTagFloat
)
129 ProfileEntry(char aTagName
, uintptr_t aTagOffset
)
130 : mTagOffset(aTagOffset
)
134 ProfileEntry(char aTagName
, Address aTagAddress
)
135 : mTagAddress(aTagAddress
)
139 ProfileEntry(char aTagName
, int aTagLine
)
144 friend std::ostream
& operator<<(std::ostream
& stream
, const ProfileEntry
& entry
);
147 friend class ThreadProfile
;
149 const char* mTagData
;
150 char mTagChars
[sizeof(void*)];
154 uintptr_t mTagOffset
;
160 #define PROFILE_MAX_ENTRY 100000
164 ThreadProfile(int aEntrySize
, ProfileStack
*aStack
)
168 , mEntrySize(aEntrySize
)
171 mEntries
= new ProfileEntry
[mEntrySize
];
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
191 if (mWritePos
== mLastFlushPos
) {
192 mLastFlushPos
= (mLastFlushPos
+ 1) % mEntrySize
;
196 // flush the new entries
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
211 // |-----------------------------|
212 // | abcdefghijklmnopq | -> 'abcdefghijklmnopq'
213 // |-----------------------------|
216 // mWritePos and mReadPos have passed mLastFlushPos
219 // |-----------------------------|
220 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz|
221 // |-----------------------------|
224 // |-----------------------------|
225 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> ''
226 // |-----------------------------|
229 // mWritePos will end up the same as mReadPos
232 // |-----------------------------|
233 // |ABCDEFGHIJKLMklmnopqrstuvwxyz|
234 // |-----------------------------|
237 // |-----------------------------|
238 // |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> ''
239 // |-----------------------------|
242 // mWritePos has moved past mReadPos
244 // |-----------------------------|
245 // |ABCDEFdefghijklmnopqrstuvwxyz|
246 // |-----------------------------|
248 // |-----------------------------|
249 // |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
250 // |-----------------------------|
254 mWritePos
= mLastFlushPos
;
257 char* processDynamicTag(int readPos
, int* tagsConsumed
, char* tagBuff
)
259 int readAheadPos
= (readPos
+ 1) % mEntrySize
;
262 // Read the string stored in mTagData until the null character is seen
263 bool seenNullByte
= false;
264 while (readAheadPos
!= mLastFlushPos
&& !seenNullByte
) {
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) {
276 readAheadPos
= (readAheadPos
+ 1) % mEntrySize
;
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
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
) {
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
);
322 b
.DefineProperty(sample
, "responsiveness", entry
.mTagFloat
);
329 b
.DefineProperty(sample
, "frameNumber", entry
.mTagLine
);
336 b
.DefineProperty(sample
, "time", entry
.mTagFloat
);
344 JSObject
*frame
= b
.CreateObject();
345 if (entry
.mTagName
== 'l') {
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
);
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
);
362 b
.ArrayPush(frames
, frame
);
366 readPos
= (readPos
+ incBy
) % mEntrySize
;
372 ProfileStack
* GetStack()
377 // Circular buffer 'Keep One Slot Open' implementation
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
384 ProfileStack
*mStack
;
387 class SaveProfileTask
;
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)
398 class TableTicker
: public Sampler
{
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
; }
441 // Not implemented on platforms which do not support backtracing
442 void doBacktrace(ThreadProfile
&aProfile
, TickSample
* aSample
);
445 // This represent the application's main thread (SAMPLER_INIT)
446 ThreadProfile mPrimaryThreadProfile
;
447 TimeStamp mStartTime
;
454 std::string
GetSharedLibraryInfoString();
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();
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
{
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.
484 #ifdef MOZ_WIDGET_ANDROID
486 tmpPath
.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
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
);
500 rv
= tmpFile
->GetNativePath(tmpPath
);
505 // Create a JSContext to run a JSObjectBuilder :(
506 // Based on XPCShellEnvironment
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);
517 LOG("Failed to get context");
518 return NS_ERROR_FAILURE
;
522 JSAutoRequest
ar(cx
);
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.
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
);
544 LOGF("Saved to %s", tmpPath
.get());
546 LOG("Fail to open profile log file.");
550 JS_DestroyContext(cx
);
558 void TableTicker::HandleSaveRequest()
562 mSaveRequested
= false;
564 // TODO: Use use the ipc/chromium Tasks here to support processes
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());
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
);
587 b
.DefineProperty(meta
, "platform", string
.Data());
589 res
= http
->GetOscpu(string
);
591 b
.DefineProperty(meta
, "oscpu", string
.Data());
593 res
= http
->GetMisc(string
);
595 b
.DefineProperty(meta
, "misc", string
.Data());
598 nsCOMPtr
<nsIXULRuntime
> runtime
= do_GetService("@mozilla.org/xre/runtime;1");
600 nsAutoCString string
;
602 res
= runtime
->GetXPCOMABI(string
);
604 b
.DefineProperty(meta
, "abi", string
.Data());
606 res
= runtime
->GetWidgetToolkit(string
);
608 b
.DefineProperty(meta
, "toolkit", string
.Data());
611 nsCOMPtr
<nsIXULAppInfo
> appInfo
= do_GetService("@mozilla.org/xre/app-info;1");
613 nsAutoCString string
;
615 res
= appInfo
->GetName(string
);
617 b
.DefineProperty(meta
, "product", string
.Data());
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());
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
642 JSObject
* threadSamples
= GetPrimaryThreadProfile()->ToJSObject(aCx
);
643 b
.ArrayPush(threads
, threadSamples
);
650 void addProfileEntry(volatile StackEntry
&entry
, ThreadProfile
&aProfile
,
651 ProfileStack
*stack
, void *lastpc
)
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]))));
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
681 jsbytecode
*jspc
= js::ProfilingGetPC(stack
->mRuntime
, entry
.script(),
684 lineno
= JS_PCToLineNumber(NULL
, entry
.script(), jspc
);
688 lineno
= JS_PCToLineNumber(NULL
, entry
.script(), entry
.pc());
691 lineno
= entry
.line();
694 aProfile
.addTag(ProfileEntry('c', sampleLabel
));
695 lineno
= entry
.line();
698 aProfile
.addTag(ProfileEntry('n', lineno
));
703 void TableTicker::doBacktrace(ThreadProfile
&aProfile
, TickSample
* aSample
)
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
]));
718 #ifdef USE_NS_STACKWALK
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
734 array
->sp_array
[array
->count
] = aSP
;
735 array
->array
[array
->count
] = aPC
;
739 void TableTicker::doBacktrace(ThreadProfile
&aProfile
, TickSample
* aSample
)
742 uintptr_t thread
= GetThreadHandle(platform_data());
745 void* pc_array
[1000];
746 void* sp_array
[1000];
750 mozilla::ArrayLength(pc_array
),
754 // Start with the current function.
755 StackWalkCallback(aSample
->pc
, aSample
->sp
, &array
);
758 pthread_t pt
= GetProfiledThread(platform_data());
759 void *stackEnd
= reinterpret_cast<void*>(-1);
761 stackEnd
= static_cast<char*>(pthread_get_stackaddr_np(pt
));
762 nsresult rv
= FramePointerStackWalk(StackWalkCallback
, 0, &array
, reinterpret_cast<void**>(aSample
->fp
), stackEnd
);
764 nsresult rv
= NS_StackWalk(StackWalkCallback
, 0, &array
, thread
);
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
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())
797 addProfileEntry(entry
, aProfile
, stack
, array
.array
[0]);
801 aProfile
.addTag(ProfileEntry('l', (void*)array
.array
[i
-1]));
807 #if defined(USE_LIBUNWIND) && defined(ANDROID)
808 void TableTicker::doBacktrace(ThreadProfile
&aProfile
, TickSample
* aSample
)
810 void* pc_array
[1000];
813 unw_cursor_t cursor
; unw_context_t 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]
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])));
856 void doSampleStackTrace(ProfileStack
*aStack
, ThreadProfile
&aProfile
, TickSample
*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
867 aProfile
.addTag(ProfileEntry('l', (void*)sample
->pc
));
868 #ifdef ENABLE_ARM_LR_SAVING
869 aProfile
.addTag(ProfileEntry('L', (void*)sample
->lr
));
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
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;
898 // if we are on a different event we can discard any temporary samples
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) {
918 #if defined(USE_BACKTRACE) || defined(USE_NS_STACKWALK) || defined(USE_LIBUNWIND)
920 doBacktrace(mPrimaryThreadProfile
, sample
);
922 doSampleStackTrace(stack
, mPrimaryThreadProfile
, sample
);
925 doSampleStackTrace(stack
, mPrimaryThreadProfile
, sample
);
929 mPrimaryThreadProfile
.flush();
931 if (!sLastTracerEvent
.IsNull() && sample
) {
932 TimeDuration delta
= sample
->timestamp
- sLastTracerEvent
;
933 mPrimaryThreadProfile
.addTag(ProfileEntry('r', delta
.ToMilliseconds()));
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
;
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.
965 unsigned long long pc
= (unsigned long long)(uintptr_t)entry
.mTagPtr
;
966 snprintf(tagBuff
, 1024, "%c-%#llx\n", entry
.mTagName
, pc
);
968 } else if (entry
.mTagName
== 'd') {
969 // TODO implement 'd' tag for text profile
971 stream
<< entry
.mTagName
<< "-" << entry
.mTagData
<< "\n";
976 void mozilla_sampler_init()
978 if (stack_key_initialized
)
981 if (!tlsStack
.init() || !tlsTicker
.init()) {
982 LOG("Failed to init.");
985 stack_key_initialized
= true;
987 ProfileStack
*stack
= new ProfileStack();
990 #if defined(USE_LIBUNWIND) && defined(ANDROID)
991 // Only try debug_frame and exidx unwinding
992 putenv("UNW_ARM_UNWIND_METHOD=5");
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.
1005 // We can't open pref so we use an environment variable
1006 // to know if we should trigger the profiler on startup
1008 const char *val
= PR_GetEnv("MOZ_PROFILER_STARTUP");
1009 if (!val
|| !*val
) {
1013 const char* features
= "js";
1014 mozilla_sampler_start(PROFILE_DEFAULT_ENTRY
, PROFILE_DEFAULT_INTERVAL
,
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();
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();
1046 std::stringstream profile
;
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());
1057 JSObject
*mozilla_sampler_get_profile_data(JSContext
*aCx
)
1059 TableTicker
*t
= tlsTicker
.get();
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))
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();
1095 mozilla_sampler_stop();
1097 TableTicker
*t
= new TableTicker(aInterval
? aInterval
: PROFILE_DEFAULT_INTERVAL
,
1098 aProfileEntries
? aProfileEntries
: PROFILE_DEFAULT_ENTRY
,
1099 stack
, aFeatures
, aFeatureCount
);
1103 stack
->enableJSSampling();
1105 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
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();
1120 bool disableJS
= t
->ProfileJS();
1124 tlsTicker
.set(NULL
);
1125 ProfileStack
*stack
= tlsStack
.get();
1126 ASSERT(stack
!= NULL
);
1129 stack
->disableJSSampling();
1131 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
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();
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
;