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 <algorithm>
7 #include <string>
8 #include <stdio.h>
9 #include <fstream>
10 #include <sstream>
11 #include "GeckoProfiler.h"
12 #ifndef SPS_STANDALONE
13 #include "SaveProfileTask.h"
14 #include "nsThreadUtils.h"
15 #include "prenv.h"
16 #include "prtime.h"
17 #include "nsXULAppAPI.h"
18 #endif
19 #include "ProfileEntry.h"
20 #include "SyncProfile.h"
21 #include "platform.h"
22 #include "shared-libraries.h"
23 #include "mozilla/StackWalk.h"
24 #include "GeckoSampler.h"
25
26 // JSON
27 #include "ProfileJSONWriter.h"
28
29 #ifndef SPS_STANDALONE
30 // Meta
31 #include "nsXPCOM.h"
32 #include "nsXPCOMCID.h"
33 #include "nsIHttpProtocolHandler.h"
34 #include "nsServiceManagerUtils.h"
35 #include "nsIXULRuntime.h"
36 #include "nsIXULAppInfo.h"
37 #include "nsDirectoryServiceUtils.h"
38 #include "nsDirectoryServiceDefs.h"
39 #include "nsIObserverService.h"
40 #include "mozilla/Services.h"
41 #include "PlatformMacros.h"
42 #include "nsTArray.h"
43
44 #include "mozilla/ProfileGatherer.h"
45 #endif
46
47 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
48 #include "FennecJNIWrappers.h"
49 #endif
50
51 #ifndef SPS_STANDALONE
52 // JS
53 #include "jsfriendapi.h"
54 #include "js/ProfilingFrameIterator.h"
55 #endif
56
57 #if defined(MOZ_PROFILING) && (defined(XP_MACOSX) || defined(XP_WIN))
58 #define USE_NS_STACKWALK
59 #endif
60
61 #if defined(XP_WIN)
62 typedef CONTEXT tickcontext_t;
63 #elif defined(LINUX)
64 #include <ucontext.h>
65 typedef ucontext_t tickcontext_t;
66 #endif
67
68 #if defined(LINUX) || defined(XP_MACOSX)
69 #include <sys/types.h>
70 pid_t gettid();
71 #endif
72
73 #if defined(__arm__) && defined(ANDROID)
74 // Should also work on ARM Linux, but not tested there yet.
75 #define USE_EHABI_STACKWALK
76 #endif
77 #ifdef USE_EHABI_STACKWALK
78 #include "EHABIStackWalk.h"
79 #endif
80
81 #ifndef SPS_STANDALONE
82 #if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux)
83 # define USE_LUL_STACKWALK
84 # include "lul/LulMain.h"
85 # include "lul/platform-linux-lul.h"
86 #endif
87 #endif
88
89 using std::string;
90 using namespace mozilla;
91
92 #ifndef MAXPATHLEN
93 #ifdef PATH_MAX
94 #define MAXPATHLEN PATH_MAX
95 #elif defined(MAX_PATH)
96 #define MAXPATHLEN MAX_PATH
97 #elif defined(_MAX_PATH)
98 #define MAXPATHLEN _MAX_PATH
99 #elif defined(CCHMAXPATH)
100 #define MAXPATHLEN CCHMAXPATH
101 #else
102 #define MAXPATHLEN 1024
103 #endif
104 #endif
105
106 #ifdef MOZ_VALGRIND
107 # include <valgrind/memcheck.h>
108 #else
109 # define VALGRIND_MAKE_MEM_DEFINED(_addr,_len) ((void)0)
110 #endif
111
112
113 ///////////////////////////////////////////////////////////////////////
114 // BEGIN SaveProfileTask et al
115
116 static void
AddSharedLibraryInfoToStream(std::ostream & aStream,const SharedLibrary & aLib)117 AddSharedLibraryInfoToStream(std::ostream& aStream, const SharedLibrary& aLib)
118 {
119 aStream << "{";
120 aStream << "\"start\":" << aLib.GetStart();
121 aStream << ",\"end\":" << aLib.GetEnd();
122 aStream << ",\"offset\":" << aLib.GetOffset();
123 aStream << ",\"name\":\"" << aLib.GetName() << "\"";
124 const std::string &breakpadId = aLib.GetBreakpadId();
125 aStream << ",\"breakpadId\":\"" << breakpadId << "\"";
126 #ifdef XP_WIN
127 // FIXME: remove this XP_WIN code when the profiler plugin has switched to
128 // using breakpadId.
129 std::string pdbSignature = breakpadId.substr(0, 32);
130 std::string pdbAgeStr = breakpadId.substr(32, breakpadId.size() - 1);
131
132 std::stringstream stream;
133 stream << pdbAgeStr;
134
135 unsigned pdbAge;
136 stream << std::hex;
137 stream >> pdbAge;
138
139 #ifdef DEBUG
140 std::ostringstream oStream;
141 oStream << pdbSignature << std::hex << std::uppercase << pdbAge;
142 MOZ_ASSERT(breakpadId == oStream.str());
143 #endif
144
145 aStream << ",\"pdbSignature\":\"" << pdbSignature << "\"";
146 aStream << ",\"pdbAge\":" << pdbAge;
147 aStream << ",\"pdbName\":\"" << aLib.GetName() << "\"";
148 #endif
149 aStream << "}";
150 }
151
152 std::string
GetSharedLibraryInfoStringInternal()153 GetSharedLibraryInfoStringInternal()
154 {
155 SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
156 if (info.GetSize() == 0)
157 return "[]";
158
159 std::ostringstream os;
160 os << "[";
161 AddSharedLibraryInfoToStream(os, info.GetEntry(0));
162
163 for (size_t i = 1; i < info.GetSize(); i++) {
164 os << ",";
165 AddSharedLibraryInfoToStream(os, info.GetEntry(i));
166 }
167
168 os << "]";
169 return os.str();
170 }
171
172 static bool
hasFeature(const char ** aFeatures,uint32_t aFeatureCount,const char * aFeature)173 hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) {
174 for(size_t i = 0; i < aFeatureCount; i++) {
175 if (strcmp(aFeatures[i], aFeature) == 0)
176 return true;
177 }
178 return false;
179 }
180
GeckoSampler(double aInterval,int aEntrySize,const char ** aFeatures,uint32_t aFeatureCount,const char ** aThreadNameFilters,uint32_t aFilterCount)181 GeckoSampler::GeckoSampler(double aInterval, int aEntrySize,
182 const char** aFeatures, uint32_t aFeatureCount,
183 const char** aThreadNameFilters, uint32_t aFilterCount)
184 : Sampler(aInterval, true, aEntrySize)
185 , mPrimaryThreadProfile(nullptr)
186 , mBuffer(new ProfileBuffer(aEntrySize))
187 , mSaveRequested(false)
188 #if defined(XP_WIN)
189 , mIntelPowerGadget(nullptr)
190 #endif
191 {
192 mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
193
194 mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
195 mProfileGPU = hasFeature(aFeatures, aFeatureCount, "gpu");
196 mProfilePower = hasFeature(aFeatures, aFeatureCount, "power");
197 // Users sometimes ask to filter by a list of threads but forget to request
198 // profiling non main threads. Let's make it implificit if we have a filter
199 mProfileThreads = hasFeature(aFeatures, aFeatureCount, "threads") || aFilterCount > 0;
200 mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf");
201 mPrivacyMode = hasFeature(aFeatures, aFeatureCount, "privacy");
202 mAddMainThreadIO = hasFeature(aFeatures, aFeatureCount, "mainthreadio");
203 mProfileMemory = hasFeature(aFeatures, aFeatureCount, "memory");
204 mTaskTracer = hasFeature(aFeatures, aFeatureCount, "tasktracer");
205 mLayersDump = hasFeature(aFeatures, aFeatureCount, "layersdump");
206 mDisplayListDump = hasFeature(aFeatures, aFeatureCount, "displaylistdump");
207 mProfileRestyle = hasFeature(aFeatures, aFeatureCount, "restyle");
208
209 #if defined(XP_WIN)
210 if (mProfilePower) {
211 mIntelPowerGadget = new IntelPowerGadget();
212 mProfilePower = mIntelPowerGadget->Init();
213 }
214 #endif
215
216 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
217 mProfileJava = mozilla::jni::IsFennec() &&
218 hasFeature(aFeatures, aFeatureCount, "java");
219 #else
220 mProfileJava = false;
221 #endif
222
223 // Deep copy aThreadNameFilters
224 MOZ_ALWAYS_TRUE(mThreadNameFilters.resize(aFilterCount));
225 for (uint32_t i = 0; i < aFilterCount; ++i) {
226 mThreadNameFilters[i] = aThreadNameFilters[i];
227 }
228
229 // Deep copy aFeatures
230 MOZ_ALWAYS_TRUE(mFeatures.resize(aFeatureCount));
231 for (uint32_t i = 0; i < aFeatureCount; ++i) {
232 mFeatures[i] = aFeatures[i];
233 }
234
235 bool ignore;
236 sStartTime = mozilla::TimeStamp::ProcessCreation(ignore);
237
238 {
239 ::MutexAutoLock lock(*sRegisteredThreadsMutex);
240
241 // Create ThreadProfile for each registered thread
242 for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
243 ThreadInfo* info = sRegisteredThreads->at(i);
244
245 RegisterThread(info);
246 }
247
248 SetActiveSampler(this);
249 }
250
251 #ifdef MOZ_TASK_TRACER
252 if (mTaskTracer) {
253 mozilla::tasktracer::StartLogging();
254 }
255 #endif
256
257 mGatherer = new mozilla::ProfileGatherer(this);
258 }
259
~GeckoSampler()260 GeckoSampler::~GeckoSampler()
261 {
262 if (IsActive())
263 Stop();
264
265 SetActiveSampler(nullptr);
266
267 // Destroy ThreadProfile for all threads
268 {
269 ::MutexAutoLock lock(*sRegisteredThreadsMutex);
270
271 for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
272 ThreadInfo* info = sRegisteredThreads->at(i);
273 ThreadProfile* profile = info->Profile();
274 if (profile) {
275 delete profile;
276 info->SetProfile(nullptr);
277 }
278 // We've stopped profiling. We no longer need to retain
279 // information for an old thread.
280 if (info->IsPendingDelete()) {
281 delete info;
282 sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
283 i--;
284 }
285 }
286 }
287 #if defined(XP_WIN)
288 delete mIntelPowerGadget;
289 #endif
290
291 // Cancel any in-flight async profile gatherering
292 // requests
293 mGatherer->Cancel();
294 }
295
HandleSaveRequest()296 void GeckoSampler::HandleSaveRequest()
297 {
298 if (!mSaveRequested)
299 return;
300 mSaveRequested = false;
301
302 #ifndef SPS_STANDALONE
303 // TODO: Use use the ipc/chromium Tasks here to support processes
304 // without XPCOM.
305 nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask();
306 NS_DispatchToMainThread(runnable);
307 #endif
308 }
309
DeleteExpiredMarkers()310 void GeckoSampler::DeleteExpiredMarkers()
311 {
312 mBuffer->deleteExpiredStoredMarkers();
313 }
314
StreamTaskTracer(SpliceableJSONWriter & aWriter)315 void GeckoSampler::StreamTaskTracer(SpliceableJSONWriter& aWriter)
316 {
317 #ifdef MOZ_TASK_TRACER
318 aWriter.StartArrayProperty("data");
319 UniquePtr<nsTArray<nsCString>> data = mozilla::tasktracer::GetLoggedData(sStartTime);
320 for (uint32_t i = 0; i < data->Length(); ++i) {
321 aWriter.StringElement((data->ElementAt(i)).get());
322 }
323 aWriter.EndArray();
324
325 aWriter.StartArrayProperty("threads");
326 ::MutexAutoLock lock(*sRegisteredThreadsMutex);
327 for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
328 // Thread meta data
329 ThreadInfo* info = sRegisteredThreads->at(i);
330 aWriter.StartObjectElement();
331 if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
332 // TODO Add the proper plugin name
333 aWriter.StringProperty("name", "Plugin");
334 } else {
335 aWriter.StringProperty("name", info->Name());
336 }
337 aWriter.IntProperty("tid", static_cast<int>(info->ThreadId()));
338 aWriter.EndObject();
339 }
340 aWriter.EndArray();
341
342 aWriter.DoubleProperty("start", static_cast<double>(mozilla::tasktracer::GetStartTime()));
343 #endif
344 }
345
346
StreamMetaJSCustomObject(SpliceableJSONWriter & aWriter)347 void GeckoSampler::StreamMetaJSCustomObject(SpliceableJSONWriter& aWriter)
348 {
349 aWriter.IntProperty("version", 3);
350 aWriter.DoubleProperty("interval", interval());
351 aWriter.IntProperty("stackwalk", mUseStackWalk);
352
353 #ifndef SPS_STANDALONE
354 mozilla::TimeDuration delta = mozilla::TimeStamp::Now() - sStartTime;
355 aWriter.DoubleProperty("startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
356
357 aWriter.IntProperty("processType", XRE_GetProcessType());
358
359 nsresult res;
360 nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
361 if (!NS_FAILED(res)) {
362 nsAutoCString string;
363
364 res = http->GetPlatform(string);
365 if (!NS_FAILED(res))
366 aWriter.StringProperty("platform", string.Data());
367
368 res = http->GetOscpu(string);
369 if (!NS_FAILED(res))
370 aWriter.StringProperty("oscpu", string.Data());
371
372 res = http->GetMisc(string);
373 if (!NS_FAILED(res))
374 aWriter.StringProperty("misc", string.Data());
375 }
376
377 nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
378 if (runtime) {
379 nsAutoCString string;
380
381 res = runtime->GetXPCOMABI(string);
382 if (!NS_FAILED(res))
383 aWriter.StringProperty("abi", string.Data());
384
385 res = runtime->GetWidgetToolkit(string);
386 if (!NS_FAILED(res))
387 aWriter.StringProperty("toolkit", string.Data());
388 }
389
390 nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
391 if (appInfo) {
392 nsAutoCString string;
393
394 res = appInfo->GetName(string);
395 if (!NS_FAILED(res))
396 aWriter.StringProperty("product", string.Data());
397 }
398 #endif
399 }
400
ToStreamAsJSON(std::ostream & stream,double aSinceTime)401 void GeckoSampler::ToStreamAsJSON(std::ostream& stream, double aSinceTime)
402 {
403 SpliceableJSONWriter b(mozilla::MakeUnique<OStreamJSONWriteFunc>(stream));
404 StreamJSON(b, aSinceTime);
405 }
406
407 #ifndef SPS_STANDALONE
ToJSObject(JSContext * aCx,double aSinceTime)408 JSObject* GeckoSampler::ToJSObject(JSContext *aCx, double aSinceTime)
409 {
410 JS::RootedValue val(aCx);
411 {
412 UniquePtr<char[]> buf = ToJSON(aSinceTime);
413 NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get()));
414 MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(js_string.get()),
415 js_string.Length(), &val));
416 }
417 return &val.toObject();
418 }
419
GetGatherer(nsISupports ** aRetVal)420 void GeckoSampler::GetGatherer(nsISupports** aRetVal)
421 {
422 if (!aRetVal || NS_WARN_IF(!mGatherer)) {
423 return;
424 }
425 NS_ADDREF(*aRetVal = mGatherer);
426 }
427 #endif
428
ToJSON(double aSinceTime)429 UniquePtr<char[]> GeckoSampler::ToJSON(double aSinceTime)
430 {
431 SpliceableChunkedJSONWriter b;
432 StreamJSON(b, aSinceTime);
433 return b.WriteFunc()->CopyData();
434 }
435
ToJSObjectAsync(double aSinceTime,mozilla::dom::Promise * aPromise)436 void GeckoSampler::ToJSObjectAsync(double aSinceTime,
437 mozilla::dom::Promise* aPromise)
438 {
439 if (NS_WARN_IF(!mGatherer)) {
440 return;
441 }
442
443 mGatherer->Start(aSinceTime, aPromise);
444 }
445
446 struct SubprocessClosure {
SubprocessClosureSubprocessClosure447 explicit SubprocessClosure(SpliceableJSONWriter* aWriter)
448 : mWriter(aWriter)
449 {}
450
451 SpliceableJSONWriter* mWriter;
452 };
453
SubProcessCallback(const char * aProfile,void * aClosure)454 void SubProcessCallback(const char* aProfile, void* aClosure)
455 {
456 // Called by the observer to get their profile data included
457 // as a sub profile
458 SubprocessClosure* closure = (SubprocessClosure*)aClosure;
459
460 // Add the string profile into the profile
461 closure->mWriter->StringElement(aProfile);
462 }
463
464
465 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
466 static
BuildJavaThreadJSObject(SpliceableJSONWriter & aWriter)467 void BuildJavaThreadJSObject(SpliceableJSONWriter& aWriter)
468 {
469 aWriter.StringProperty("name", "Java Main Thread");
470
471 aWriter.StartArrayProperty("samples");
472
473 // for each sample
474 for (int sampleId = 0; true; sampleId++) {
475 bool firstRun = true;
476 // for each frame
477 for (int frameId = 0; true; frameId++) {
478 jni::String::LocalRef frameName =
479 java::GeckoJavaSampler::GetFrameName(0, sampleId, frameId);
480 // when we run out of frames, we stop looping
481 if (!frameName) {
482 // if we found at least one frame, we have objects to close
483 if (!firstRun) {
484 aWriter.EndArray();
485 aWriter.EndObject();
486 }
487 break;
488 }
489 // the first time around, open the sample object and frames array
490 if (firstRun) {
491 firstRun = false;
492
493 double sampleTime =
494 java::GeckoJavaSampler::GetSampleTime(0, sampleId);
495
496 aWriter.StartObjectElement();
497 aWriter.DoubleProperty("time", sampleTime);
498
499 aWriter.StartArrayProperty("frames");
500 }
501 // add a frame to the sample
502 aWriter.StartObjectElement();
503 aWriter.StringProperty("location",
504 frameName->ToCString().BeginReading());
505 aWriter.EndObject();
506 }
507 // if we found no frames for this sample, we are done
508 if (firstRun) {
509 break;
510 }
511 }
512
513 aWriter.EndArray();
514 }
515 #endif
516
StreamJSON(SpliceableJSONWriter & aWriter,double aSinceTime)517 void GeckoSampler::StreamJSON(SpliceableJSONWriter& aWriter, double aSinceTime)
518 {
519 aWriter.Start(SpliceableJSONWriter::SingleLineStyle);
520 {
521 // Put shared library info
522 aWriter.StringProperty("libs", GetSharedLibraryInfoStringInternal().c_str());
523
524 // Put meta data
525 aWriter.StartObjectProperty("meta");
526 StreamMetaJSCustomObject(aWriter);
527 aWriter.EndObject();
528
529 // Data of TaskTracer doesn't belong in the circular buffer.
530 if (TaskTracer()) {
531 aWriter.StartObjectProperty("tasktracer");
532 StreamTaskTracer(aWriter);
533 aWriter.EndObject();
534 }
535
536 // Lists the samples for each ThreadProfile
537 aWriter.StartArrayProperty("threads");
538 {
539 SetPaused(true);
540
541 {
542 ::MutexAutoLock lock(*sRegisteredThreadsMutex);
543
544 for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
545 // Thread not being profiled, skip it
546 if (!sRegisteredThreads->at(i)->Profile())
547 continue;
548
549 // Note that we intentionally include ThreadProfile which
550 // have been marked for pending delete.
551
552 ::MutexAutoLock lock(sRegisteredThreads->at(i)->Profile()->GetMutex());
553
554 sRegisteredThreads->at(i)->Profile()->StreamJSON(aWriter, aSinceTime);
555 }
556 }
557
558 #ifndef SPS_STANDALONE
559 if (Sampler::CanNotifyObservers()) {
560 // Send a event asking any subprocesses (plugins) to
561 // give us their information
562 SubprocessClosure closure(&aWriter);
563 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
564 if (os) {
565 RefPtr<ProfileSaveEvent> pse = new ProfileSaveEvent(SubProcessCallback, &closure);
566 os->NotifyObservers(pse, "profiler-subprocess", nullptr);
567 }
568 }
569
570 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
571 if (ProfileJava()) {
572 java::GeckoJavaSampler::Pause();
573
574 aWriter.Start();
575 {
576 BuildJavaThreadJSObject(aWriter);
577 }
578 aWriter.End();
579
580 java::GeckoJavaSampler::Unpause();
581 }
582 #endif
583 #endif
584
585 SetPaused(false);
586 }
587 aWriter.EndArray();
588 }
589 aWriter.End();
590 }
591
FlushOnJSShutdown(JSContext * aContext)592 void GeckoSampler::FlushOnJSShutdown(JSContext* aContext)
593 {
594 #ifndef SPS_STANDALONE
595 SetPaused(true);
596
597 {
598 ::MutexAutoLock lock(*sRegisteredThreadsMutex);
599
600 for (size_t i = 0; i < sRegisteredThreads->size(); i++) {
601 // Thread not being profiled, skip it.
602 if (!sRegisteredThreads->at(i)->Profile() ||
603 sRegisteredThreads->at(i)->IsPendingDelete()) {
604 continue;
605 }
606
607 // Thread not profiling the context that's going away, skip it.
608 if (sRegisteredThreads->at(i)->Profile()->GetPseudoStack()->mContext != aContext) {
609 continue;
610 }
611
612 ::MutexAutoLock lock(sRegisteredThreads->at(i)->Profile()->GetMutex());
613 sRegisteredThreads->at(i)->Profile()->FlushSamplesAndMarkers();
614 }
615 }
616
617 SetPaused(false);
618 #endif
619 }
620
flushSamplerOnJSShutdown()621 void PseudoStack::flushSamplerOnJSShutdown()
622 {
623 #ifndef SPS_STANDALONE
624 MOZ_ASSERT(mContext);
625 GeckoSampler* t = tlsTicker.get();
626 if (t) {
627 t->FlushOnJSShutdown(mContext);
628 }
629 #endif
630 }
631
632 // END SaveProfileTask et al
633 ////////////////////////////////////////////////////////////////////////
634
635 static
addDynamicTag(ThreadProfile & aProfile,char aTagName,const char * aStr)636 void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr)
637 {
638 aProfile.addTag(ProfileEntry(aTagName, ""));
639 // Add one to store the null termination
640 size_t strLen = strlen(aStr) + 1;
641 for (size_t j = 0; j < strLen;) {
642 // Store as many characters in the void* as the platform allows
643 char text[sizeof(void*)];
644 size_t len = sizeof(void*)/sizeof(char);
645 if (j+len >= strLen) {
646 len = strLen - j;
647 }
648 memcpy(text, &aStr[j], len);
649 j += sizeof(void*)/sizeof(char);
650 // Cast to *((void**) to pass the text data to a void*
651 aProfile.addTag(ProfileEntry('d', *((void**)(&text[0]))));
652 }
653 }
654
655 static
addPseudoEntry(volatile StackEntry & entry,ThreadProfile & aProfile,PseudoStack * stack,void * lastpc)656 void addPseudoEntry(volatile StackEntry &entry, ThreadProfile &aProfile,
657 PseudoStack *stack, void *lastpc)
658 {
659 // Pseudo-frames with the BEGIN_PSEUDO_JS flag are just annotations
660 // and should not be recorded in the profile.
661 if (entry.hasFlag(StackEntry::BEGIN_PSEUDO_JS))
662 return;
663
664 int lineno = -1;
665
666 // First entry has tagName 's' (start)
667 // Check for magic pointer bit 1 to indicate copy
668 const char* sampleLabel = entry.label();
669 if (entry.isCopyLabel()) {
670 // Store the string using 1 or more 'd' (dynamic) tags
671 // that will happen to the preceding tag
672
673 addDynamicTag(aProfile, 'c', sampleLabel);
674 #ifndef SPS_STANDALONE
675 if (entry.isJs()) {
676 JSScript* script = entry.script();
677 if (script) {
678 if (!entry.pc()) {
679 // The JIT only allows the top-most entry to have a nullptr pc
680 MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]);
681 // If stack-walking was disabled, then that's just unfortunate
682 if (lastpc) {
683 jsbytecode *jspc = js::ProfilingGetPC(stack->mContext, script,
684 lastpc);
685 if (jspc) {
686 lineno = JS_PCToLineNumber(script, jspc);
687 }
688 }
689 } else {
690 lineno = JS_PCToLineNumber(script, entry.pc());
691 }
692 }
693 } else {
694 lineno = entry.line();
695 }
696 #endif
697 } else {
698 aProfile.addTag(ProfileEntry('c', sampleLabel));
699
700 // XXX: Bug 1010578. Don't assume a CPP entry and try to get the
701 // line for js entries as well.
702 if (entry.isCpp()) {
703 lineno = entry.line();
704 }
705 }
706
707 if (lineno != -1) {
708 aProfile.addTag(ProfileEntry('n', lineno));
709 }
710
711 uint32_t category = entry.category();
712 MOZ_ASSERT(!(category & StackEntry::IS_CPP_ENTRY));
713 MOZ_ASSERT(!(category & StackEntry::FRAME_LABEL_COPY));
714
715 if (category) {
716 aProfile.addTag(ProfileEntry('y', (int)category));
717 }
718 }
719
720 struct NativeStack
721 {
722 void** pc_array;
723 void** sp_array;
724 size_t size;
725 size_t count;
726 };
727
728 mozilla::Atomic<bool> WALKING_JS_STACK(false);
729
730 struct AutoWalkJSStack {
731 bool walkAllowed;
732
AutoWalkJSStackAutoWalkJSStack733 AutoWalkJSStack() : walkAllowed(false) {
734 walkAllowed = WALKING_JS_STACK.compareExchange(false, true);
735 }
736
~AutoWalkJSStackAutoWalkJSStack737 ~AutoWalkJSStack() {
738 if (walkAllowed)
739 WALKING_JS_STACK = false;
740 }
741 };
742
743 static
mergeStacksIntoProfile(ThreadProfile & aProfile,TickSample * aSample,NativeStack & aNativeStack)744 void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, NativeStack& aNativeStack)
745 {
746 PseudoStack* pseudoStack = aProfile.GetPseudoStack();
747 volatile StackEntry *pseudoFrames = pseudoStack->mStack;
748 uint32_t pseudoCount = pseudoStack->stackSize();
749
750 // Make a copy of the JS stack into a JSFrame array. This is necessary since,
751 // like the native stack, the JS stack is iterated youngest-to-oldest and we
752 // need to iterate oldest-to-youngest when adding entries to aProfile.
753
754 // Synchronous sampling reports an invalid buffer generation to
755 // ProfilingFrameIterator to avoid incorrectly resetting the generation of
756 // sampled JIT entries inside the JS engine. See note below concerning 'J'
757 // entries.
758 uint32_t startBufferGen;
759 if (aSample->isSamplingCurrentThread) {
760 startBufferGen = UINT32_MAX;
761 } else {
762 startBufferGen = aProfile.bufferGeneration();
763 }
764 uint32_t jsCount = 0;
765 #ifndef SPS_STANDALONE
766 JS::ProfilingFrameIterator::Frame jsFrames[1000];
767 // Only walk jit stack if profiling frame iterator is turned on.
768 if (pseudoStack->mContext && JS::IsProfilingEnabledForContext(pseudoStack->mContext)) {
769 AutoWalkJSStack autoWalkJSStack;
770 const uint32_t maxFrames = mozilla::ArrayLength(jsFrames);
771
772 if (aSample && autoWalkJSStack.walkAllowed) {
773 JS::ProfilingFrameIterator::RegisterState registerState;
774 registerState.pc = aSample->pc;
775 registerState.sp = aSample->sp;
776 #ifdef ENABLE_ARM_LR_SAVING
777 registerState.lr = aSample->lr;
778 #endif
779
780 JS::ProfilingFrameIterator jsIter(pseudoStack->mContext,
781 registerState,
782 startBufferGen);
783 for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) {
784 // See note below regarding 'J' entries.
785 if (aSample->isSamplingCurrentThread || jsIter.isWasm()) {
786 uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames);
787 jsCount += extracted;
788 if (jsCount == maxFrames)
789 break;
790 } else {
791 mozilla::Maybe<JS::ProfilingFrameIterator::Frame> frame =
792 jsIter.getPhysicalFrameWithoutLabel();
793 if (frame.isSome())
794 jsFrames[jsCount++] = mozilla::Move(frame.ref());
795 }
796 }
797 }
798 }
799 #endif
800
801 // Start the sample with a root entry.
802 aProfile.addTag(ProfileEntry('s', "(root)"));
803
804 // While the pseudo-stack array is ordered oldest-to-youngest, the JS and
805 // native arrays are ordered youngest-to-oldest. We must add frames to
806 // aProfile oldest-to-youngest. Thus, iterate over the pseudo-stack forwards
807 // and JS and native arrays backwards. Note: this means the terminating
808 // condition jsIndex and nativeIndex is being < 0.
809 uint32_t pseudoIndex = 0;
810 int32_t jsIndex = jsCount - 1;
811 int32_t nativeIndex = aNativeStack.count - 1;
812
813 uint8_t *lastPseudoCppStackAddr = nullptr;
814
815 // Iterate as long as there is at least one frame remaining.
816 while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) {
817 // There are 1 to 3 frames available. Find and add the oldest.
818
819 uint8_t *pseudoStackAddr = nullptr;
820 uint8_t *jsStackAddr = nullptr;
821 uint8_t *nativeStackAddr = nullptr;
822
823 if (pseudoIndex != pseudoCount) {
824 volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex];
825
826 if (pseudoFrame.isCpp())
827 lastPseudoCppStackAddr = (uint8_t *) pseudoFrame.stackAddress();
828
829 #ifndef SPS_STANDALONE
830 // Skip any pseudo-stack JS frames which are marked isOSR
831 // Pseudostack frames are marked isOSR when the JS interpreter
832 // enters a jit frame on a loop edge (via on-stack-replacement,
833 // or OSR). To avoid both the pseudoframe and jit frame being
834 // recorded (and showing up twice), the interpreter marks the
835 // interpreter pseudostack entry with the OSR flag to ensure that
836 // it doesn't get counted.
837 if (pseudoFrame.isJs() && pseudoFrame.isOSR()) {
838 pseudoIndex++;
839 continue;
840 }
841 #endif
842
843 MOZ_ASSERT(lastPseudoCppStackAddr);
844 pseudoStackAddr = lastPseudoCppStackAddr;
845 }
846
847 #ifndef SPS_STANDALONE
848 if (jsIndex >= 0)
849 jsStackAddr = (uint8_t *) jsFrames[jsIndex].stackAddress;
850 #endif
851
852 if (nativeIndex >= 0)
853 nativeStackAddr = (uint8_t *) aNativeStack.sp_array[nativeIndex];
854
855 // If there's a native stack entry which has the same SP as a
856 // pseudo stack entry, pretend we didn't see the native stack
857 // entry. Ditto for a native stack entry which has the same SP as
858 // a JS stack entry. In effect this means pseudo or JS entries
859 // trump conflicting native entries.
860 if (nativeStackAddr && (pseudoStackAddr == nativeStackAddr || jsStackAddr == nativeStackAddr)) {
861 nativeStackAddr = nullptr;
862 nativeIndex--;
863 MOZ_ASSERT(pseudoStackAddr || jsStackAddr);
864 }
865
866 // Sanity checks.
867 MOZ_ASSERT_IF(pseudoStackAddr, pseudoStackAddr != jsStackAddr &&
868 pseudoStackAddr != nativeStackAddr);
869 MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr &&
870 jsStackAddr != nativeStackAddr);
871 MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr &&
872 nativeStackAddr != jsStackAddr);
873
874 // Check to see if pseudoStack frame is top-most.
875 if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) {
876 MOZ_ASSERT(pseudoIndex < pseudoCount);
877 volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex];
878 addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr);
879 pseudoIndex++;
880 continue;
881 }
882
883 #ifndef SPS_STANDALONE
884 // Check to see if JS jit stack frame is top-most
885 if (jsStackAddr > nativeStackAddr) {
886 MOZ_ASSERT(jsIndex >= 0);
887 const JS::ProfilingFrameIterator::Frame& jsFrame = jsFrames[jsIndex];
888
889 // Stringifying non-wasm JIT frames is delayed until streaming
890 // time. To re-lookup the entry in the JitcodeGlobalTable, we need to
891 // store the JIT code address ('J') in the circular buffer.
892 //
893 // Note that we cannot do this when we are sychronously sampling the
894 // current thread; that is, when called from profiler_get_backtrace. The
895 // captured backtrace is usually externally stored for an indeterminate
896 // amount of time, such as in nsRefreshDriver. Problematically, the
897 // stored backtrace may be alive across a GC during which the profiler
898 // itself is disabled. In that case, the JS engine is free to discard
899 // its JIT code. This means that if we inserted such 'J' entries into
900 // the buffer, nsRefreshDriver would now be holding on to a backtrace
901 // with stale JIT code return addresses.
902 if (aSample->isSamplingCurrentThread ||
903 jsFrame.kind == JS::ProfilingFrameIterator::Frame_Wasm) {
904 addDynamicTag(aProfile, 'c', jsFrame.label.get());
905 } else {
906 MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion ||
907 jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline);
908 aProfile.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress));
909 }
910
911 jsIndex--;
912 continue;
913 }
914 #endif
915
916 // If we reach here, there must be a native stack entry and it must be the
917 // greatest entry.
918 if (nativeStackAddr) {
919 MOZ_ASSERT(nativeIndex >= 0);
920 aProfile
921 .addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex]));
922 }
923 if (nativeIndex >= 0) {
924 nativeIndex--;
925 }
926 }
927
928 #ifndef SPS_STANDALONE
929 // Update the JS context with the current profile sample buffer generation.
930 //
931 // Do not do this for synchronous sampling, which create their own
932 // ProfileBuffers.
933 if (!aSample->isSamplingCurrentThread && pseudoStack->mContext) {
934 MOZ_ASSERT(aProfile.bufferGeneration() >= startBufferGen);
935 uint32_t lapCount = aProfile.bufferGeneration() - startBufferGen;
936 JS::UpdateJSContextProfilerSampleBufferGen(pseudoStack->mContext,
937 aProfile.bufferGeneration(),
938 lapCount);
939 }
940 #endif
941 }
942
943 #ifdef USE_NS_STACKWALK
944 static
StackWalkCallback(uint32_t aFrameNumber,void * aPC,void * aSP,void * aClosure)945 void StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP,
946 void* aClosure)
947 {
948 NativeStack* nativeStack = static_cast<NativeStack*>(aClosure);
949 MOZ_ASSERT(nativeStack->count < nativeStack->size);
950 nativeStack->sp_array[nativeStack->count] = aSP;
951 nativeStack->pc_array[nativeStack->count] = aPC;
952 nativeStack->count++;
953 }
954
doNativeBacktrace(ThreadProfile & aProfile,TickSample * aSample)955 void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
956 {
957 void* pc_array[1000];
958 void* sp_array[1000];
959 NativeStack nativeStack = {
960 pc_array,
961 sp_array,
962 mozilla::ArrayLength(pc_array),
963 0
964 };
965
966 // Start with the current function. We use 0 as the frame number here because
967 // the FramePointerStackWalk() and MozStackWalk() calls below will use 1..N.
968 // This is a bit weird but it doesn't matter because StackWalkCallback()
969 // doesn't use the frame number argument.
970 StackWalkCallback(/* frameNumber */ 0, aSample->pc, aSample->sp, &nativeStack);
971
972 uint32_t maxFrames = uint32_t(nativeStack.size - nativeStack.count);
973 // win X64 doesn't support disabling frame pointers emission so we need
974 // to fallback to using StackWalk64 which is slower.
975 #if defined(XP_MACOSX) || (defined(XP_WIN) && !defined(V8_HOST_ARCH_X64))
976 void *stackEnd = aSample->threadProfile->GetStackTop();
977 bool rv = true;
978 if (aSample->fp >= aSample->sp && aSample->fp <= stackEnd)
979 rv = FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0,
980 maxFrames, &nativeStack,
981 reinterpret_cast<void**>(aSample->fp), stackEnd);
982 #else
983 void *platformData = nullptr;
984
985 uintptr_t thread = GetThreadHandle(aSample->threadProfile->GetPlatformData());
986 MOZ_ASSERT(thread);
987 bool rv = MozStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames,
988 &nativeStack, thread, platformData);
989 #endif
990 if (rv)
991 mergeStacksIntoProfile(aProfile, aSample, nativeStack);
992 }
993 #endif
994
995
996 #ifdef USE_EHABI_STACKWALK
doNativeBacktrace(ThreadProfile & aProfile,TickSample * aSample)997 void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
998 {
999 void *pc_array[1000];
1000 void *sp_array[1000];
1001 NativeStack nativeStack = {
1002 pc_array,
1003 sp_array,
1004 mozilla::ArrayLength(pc_array),
1005 0
1006 };
1007
1008 const mcontext_t *mcontext = &reinterpret_cast<ucontext_t *>(aSample->context)->uc_mcontext;
1009 mcontext_t savedContext;
1010 PseudoStack *pseudoStack = aProfile.GetPseudoStack();
1011
1012 nativeStack.count = 0;
1013 // The pseudostack contains an "EnterJIT" frame whenever we enter
1014 // JIT code with profiling enabled; the stack pointer value points
1015 // the saved registers. We use this to unwind resume unwinding
1016 // after encounting JIT code.
1017 for (uint32_t i = pseudoStack->stackSize(); i > 0; --i) {
1018 // The pseudostack grows towards higher indices, so we iterate
1019 // backwards (from callee to caller).
1020 volatile StackEntry &entry = pseudoStack->mStack[i - 1];
1021 if (!entry.isJs() && strcmp(entry.label(), "EnterJIT") == 0) {
1022 // Found JIT entry frame. Unwind up to that point (i.e., force
1023 // the stack walk to stop before the block of saved registers;
1024 // note that it yields nondecreasing stack pointers), then restore
1025 // the saved state.
1026 uint32_t *vSP = reinterpret_cast<uint32_t*>(entry.stackAddress());
1027
1028 nativeStack.count += EHABIStackWalk(*mcontext,
1029 /* stackBase = */ vSP,
1030 sp_array + nativeStack.count,
1031 pc_array + nativeStack.count,
1032 nativeStack.size - nativeStack.count);
1033
1034 memset(&savedContext, 0, sizeof(savedContext));
1035 // See also: struct EnterJITStack in js/src/jit/arm/Trampoline-arm.cpp
1036 savedContext.arm_r4 = *vSP++;
1037 savedContext.arm_r5 = *vSP++;
1038 savedContext.arm_r6 = *vSP++;
1039 savedContext.arm_r7 = *vSP++;
1040 savedContext.arm_r8 = *vSP++;
1041 savedContext.arm_r9 = *vSP++;
1042 savedContext.arm_r10 = *vSP++;
1043 savedContext.arm_fp = *vSP++;
1044 savedContext.arm_lr = *vSP++;
1045 savedContext.arm_sp = reinterpret_cast<uint32_t>(vSP);
1046 savedContext.arm_pc = savedContext.arm_lr;
1047 mcontext = &savedContext;
1048 }
1049 }
1050
1051 // Now unwind whatever's left (starting from either the last EnterJIT
1052 // frame or, if no EnterJIT was found, the original registers).
1053 nativeStack.count += EHABIStackWalk(*mcontext,
1054 aProfile.GetStackTop(),
1055 sp_array + nativeStack.count,
1056 pc_array + nativeStack.count,
1057 nativeStack.size - nativeStack.count);
1058
1059 mergeStacksIntoProfile(aProfile, aSample, nativeStack);
1060 }
1061 #endif
1062
1063
1064 #ifdef USE_LUL_STACKWALK
doNativeBacktrace(ThreadProfile & aProfile,TickSample * aSample)1065 void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample)
1066 {
1067 const mcontext_t* mc
1068 = &reinterpret_cast<ucontext_t *>(aSample->context)->uc_mcontext;
1069
1070 lul::UnwindRegs startRegs;
1071 memset(&startRegs, 0, sizeof(startRegs));
1072
1073 # if defined(SPS_PLAT_amd64_linux)
1074 startRegs.xip = lul::TaggedUWord(mc->gregs[REG_RIP]);
1075 startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_RSP]);
1076 startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_RBP]);
1077 # elif defined(SPS_PLAT_arm_android)
1078 startRegs.r15 = lul::TaggedUWord(mc->arm_pc);
1079 startRegs.r14 = lul::TaggedUWord(mc->arm_lr);
1080 startRegs.r13 = lul::TaggedUWord(mc->arm_sp);
1081 startRegs.r12 = lul::TaggedUWord(mc->arm_ip);
1082 startRegs.r11 = lul::TaggedUWord(mc->arm_fp);
1083 startRegs.r7 = lul::TaggedUWord(mc->arm_r7);
1084 # elif defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android)
1085 startRegs.xip = lul::TaggedUWord(mc->gregs[REG_EIP]);
1086 startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_ESP]);
1087 startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_EBP]);
1088 # else
1089 # error "Unknown plat"
1090 # endif
1091
1092 /* Copy up to N_STACK_BYTES from rsp-REDZONE upwards, but not
1093 going past the stack's registered top point. Do some basic
1094 sanity checks too. This assumes that the TaggedUWord holding
1095 the stack pointer value is valid, but it should be, since it
1096 was constructed that way in the code just above. */
1097
1098 lul::StackImage stackImg;
1099
1100 {
1101 # if defined(SPS_PLAT_amd64_linux)
1102 uintptr_t rEDZONE_SIZE = 128;
1103 uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
1104 # elif defined(SPS_PLAT_arm_android)
1105 uintptr_t rEDZONE_SIZE = 0;
1106 uintptr_t start = startRegs.r13.Value() - rEDZONE_SIZE;
1107 # elif defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android)
1108 uintptr_t rEDZONE_SIZE = 0;
1109 uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
1110 # else
1111 # error "Unknown plat"
1112 # endif
1113 uintptr_t end = reinterpret_cast<uintptr_t>(aProfile.GetStackTop());
1114 uintptr_t ws = sizeof(void*);
1115 start &= ~(ws-1);
1116 end &= ~(ws-1);
1117 uintptr_t nToCopy = 0;
1118 if (start < end) {
1119 nToCopy = end - start;
1120 if (nToCopy > lul::N_STACK_BYTES)
1121 nToCopy = lul::N_STACK_BYTES;
1122 }
1123 MOZ_ASSERT(nToCopy <= lul::N_STACK_BYTES);
1124 stackImg.mLen = nToCopy;
1125 stackImg.mStartAvma = start;
1126 if (nToCopy > 0) {
1127 memcpy(&stackImg.mContents[0], (void*)start, nToCopy);
1128 (void)VALGRIND_MAKE_MEM_DEFINED(&stackImg.mContents[0], nToCopy);
1129 }
1130 }
1131
1132 // The maximum number of frames that LUL will produce. Setting it
1133 // too high gives a risk of it wasting a lot of time looping on
1134 // corrupted stacks.
1135 const int MAX_NATIVE_FRAMES = 256;
1136
1137 size_t scannedFramesAllowed = 0;
1138
1139 uintptr_t framePCs[MAX_NATIVE_FRAMES];
1140 uintptr_t frameSPs[MAX_NATIVE_FRAMES];
1141 size_t framesAvail = mozilla::ArrayLength(framePCs);
1142 size_t framesUsed = 0;
1143 size_t scannedFramesAcquired = 0;
1144 sLUL->Unwind( &framePCs[0], &frameSPs[0],
1145 &framesUsed, &scannedFramesAcquired,
1146 framesAvail, scannedFramesAllowed,
1147 &startRegs, &stackImg );
1148
1149 NativeStack nativeStack = {
1150 reinterpret_cast<void**>(framePCs),
1151 reinterpret_cast<void**>(frameSPs),
1152 mozilla::ArrayLength(framePCs),
1153 0
1154 };
1155
1156 nativeStack.count = framesUsed;
1157
1158 mergeStacksIntoProfile(aProfile, aSample, nativeStack);
1159
1160 // Update stats in the LUL stats object. Unfortunately this requires
1161 // three global memory operations.
1162 sLUL->mStats.mContext += 1;
1163 sLUL->mStats.mCFI += framesUsed - 1 - scannedFramesAcquired;
1164 sLUL->mStats.mScanned += scannedFramesAcquired;
1165 }
1166 #endif
1167
1168
1169 static
doSampleStackTrace(ThreadProfile & aProfile,TickSample * aSample,bool aAddLeafAddresses)1170 void doSampleStackTrace(ThreadProfile &aProfile, TickSample *aSample, bool aAddLeafAddresses)
1171 {
1172 NativeStack nativeStack = { nullptr, nullptr, 0, 0 };
1173 mergeStacksIntoProfile(aProfile, aSample, nativeStack);
1174
1175 #ifdef ENABLE_SPS_LEAF_DATA
1176 if (aSample && aAddLeafAddresses) {
1177 aProfile.addTag(ProfileEntry('l', (void*)aSample->pc));
1178 #ifdef ENABLE_ARM_LR_SAVING
1179 aProfile.addTag(ProfileEntry('L', (void*)aSample->lr));
1180 #endif
1181 }
1182 #endif
1183 }
1184
Tick(TickSample * sample)1185 void GeckoSampler::Tick(TickSample* sample)
1186 {
1187 // Don't allow for ticks to happen within other ticks.
1188 InplaceTick(sample);
1189 }
1190
InplaceTick(TickSample * sample)1191 void GeckoSampler::InplaceTick(TickSample* sample)
1192 {
1193 ThreadProfile& currThreadProfile = *sample->threadProfile;
1194
1195 currThreadProfile.addTag(ProfileEntry('T', currThreadProfile.ThreadId()));
1196
1197 if (sample) {
1198 mozilla::TimeDuration delta = sample->timestamp - sStartTime;
1199 currThreadProfile.addTag(ProfileEntry('t', delta.ToMilliseconds()));
1200 }
1201
1202 PseudoStack* stack = currThreadProfile.GetPseudoStack();
1203
1204 #if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK) || \
1205 defined(USE_LUL_STACKWALK)
1206 if (mUseStackWalk) {
1207 doNativeBacktrace(currThreadProfile, sample);
1208 } else {
1209 doSampleStackTrace(currThreadProfile, sample, mAddLeafAddresses);
1210 }
1211 #else
1212 doSampleStackTrace(currThreadProfile, sample, mAddLeafAddresses);
1213 #endif
1214
1215 // Don't process the PeudoStack's markers if we're
1216 // synchronously sampling the current thread.
1217 if (!sample->isSamplingCurrentThread) {
1218 ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers();
1219 while (pendingMarkersList && pendingMarkersList->peek()) {
1220 ProfilerMarker* marker = pendingMarkersList->popHead();
1221 currThreadProfile.addStoredMarker(marker);
1222 currThreadProfile.addTag(ProfileEntry('m', marker));
1223 }
1224 }
1225
1226 #ifndef SPS_STANDALONE
1227 if (sample && currThreadProfile.GetThreadResponsiveness()->HasData()) {
1228 mozilla::TimeDuration delta = currThreadProfile.GetThreadResponsiveness()->GetUnresponsiveDuration(sample->timestamp);
1229 currThreadProfile.addTag(ProfileEntry('r', delta.ToMilliseconds()));
1230 }
1231 #endif
1232
1233 // rssMemory is equal to 0 when we are not recording.
1234 if (sample && sample->rssMemory != 0) {
1235 currThreadProfile.addTag(ProfileEntry('R', static_cast<double>(sample->rssMemory)));
1236 }
1237
1238 // ussMemory is equal to 0 when we are not recording.
1239 if (sample && sample->ussMemory != 0) {
1240 currThreadProfile.addTag(ProfileEntry('U', static_cast<double>(sample->ussMemory)));
1241 }
1242
1243 #if defined(XP_WIN)
1244 if (mProfilePower) {
1245 mIntelPowerGadget->TakeSample();
1246 currThreadProfile.addTag(ProfileEntry('p', static_cast<double>(mIntelPowerGadget->GetTotalPackagePowerInWatts())));
1247 }
1248 #endif
1249
1250 if (sLastFrameNumber != sFrameNumber) {
1251 currThreadProfile.addTag(ProfileEntry('f', sFrameNumber));
1252 sLastFrameNumber = sFrameNumber;
1253 }
1254 }
1255
1256 namespace {
1257
NewSyncProfile()1258 SyncProfile* NewSyncProfile()
1259 {
1260 PseudoStack* stack = tlsPseudoStack.get();
1261 if (!stack) {
1262 MOZ_ASSERT(stack);
1263 return nullptr;
1264 }
1265 Thread::tid_t tid = Thread::GetCurrentId();
1266
1267 ThreadInfo* info = new ThreadInfo("SyncProfile", tid, false, stack, nullptr);
1268 SyncProfile* profile = new SyncProfile(info, GET_BACKTRACE_DEFAULT_ENTRY);
1269 return profile;
1270 }
1271
1272 } // namespace
1273
GetBacktrace()1274 SyncProfile* GeckoSampler::GetBacktrace()
1275 {
1276 SyncProfile* profile = NewSyncProfile();
1277
1278 TickSample sample;
1279 sample.threadProfile = profile;
1280
1281 #if defined(HAVE_NATIVE_UNWIND) || defined(USE_LUL_STACKWALK)
1282 #if defined(XP_WIN) || defined(LINUX)
1283 tickcontext_t context;
1284 sample.PopulateContext(&context);
1285 #elif defined(XP_MACOSX)
1286 sample.PopulateContext(nullptr);
1287 #endif
1288 #endif
1289
1290 sample.isSamplingCurrentThread = true;
1291 sample.timestamp = mozilla::TimeStamp::Now();
1292
1293 profile->BeginUnwind();
1294 Tick(&sample);
1295 profile->EndUnwind();
1296
1297 return profile;
1298 }
1299
1300 void
GetBufferInfo(uint32_t * aCurrentPosition,uint32_t * aTotalSize,uint32_t * aGeneration)1301 GeckoSampler::GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration)
1302 {
1303 *aCurrentPosition = mBuffer->mWritePos;
1304 *aTotalSize = mBuffer->mEntrySize;
1305 *aGeneration = mBuffer->mGeneration;
1306 }
1307