|
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 <string> |
|
7 #include <stdio.h> |
|
8 #include <fstream> |
|
9 #include <sstream> |
|
10 #include "GeckoProfiler.h" |
|
11 #include "SaveProfileTask.h" |
|
12 #include "ProfileEntry.h" |
|
13 #include "SyncProfile.h" |
|
14 #include "platform.h" |
|
15 #include "nsThreadUtils.h" |
|
16 #include "prenv.h" |
|
17 #include "prtime.h" |
|
18 #include "shared-libraries.h" |
|
19 #include "mozilla/StackWalk.h" |
|
20 #include "TableTicker.h" |
|
21 #include "nsXULAppAPI.h" |
|
22 |
|
23 // JSON |
|
24 #include "JSStreamWriter.h" |
|
25 |
|
26 // Meta |
|
27 #include "nsXPCOM.h" |
|
28 #include "nsXPCOMCID.h" |
|
29 #include "nsIHttpProtocolHandler.h" |
|
30 #include "nsServiceManagerUtils.h" |
|
31 #include "nsIXULRuntime.h" |
|
32 #include "nsIXULAppInfo.h" |
|
33 #include "nsDirectoryServiceUtils.h" |
|
34 #include "nsDirectoryServiceDefs.h" |
|
35 #include "nsIObserverService.h" |
|
36 #include "mozilla/Services.h" |
|
37 #include "PlatformMacros.h" |
|
38 |
|
39 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) |
|
40 #include "AndroidBridge.h" |
|
41 #endif |
|
42 |
|
43 // JS |
|
44 #include "js/OldDebugAPI.h" |
|
45 |
|
46 #if defined(MOZ_PROFILING) && (defined(XP_MACOSX) || defined(XP_WIN)) |
|
47 #define USE_NS_STACKWALK |
|
48 #endif |
|
49 #ifdef USE_NS_STACKWALK |
|
50 #include "nsStackWalk.h" |
|
51 #endif |
|
52 |
|
53 #if defined(XP_WIN) |
|
54 typedef CONTEXT tickcontext_t; |
|
55 #elif defined(LINUX) |
|
56 #include <ucontext.h> |
|
57 typedef ucontext_t tickcontext_t; |
|
58 #endif |
|
59 |
|
60 #if defined(LINUX) || defined(XP_MACOSX) |
|
61 #include <sys/types.h> |
|
62 pid_t gettid(); |
|
63 #endif |
|
64 |
|
65 #if defined(SPS_ARCH_arm) && defined(MOZ_WIDGET_GONK) |
|
66 // Should also work on other Android and ARM Linux, but not tested there yet. |
|
67 #define USE_EHABI_STACKWALK |
|
68 #endif |
|
69 #ifdef USE_EHABI_STACKWALK |
|
70 #include "EHABIStackWalk.h" |
|
71 #endif |
|
72 |
|
73 using std::string; |
|
74 using namespace mozilla; |
|
75 |
|
76 #ifndef MAXPATHLEN |
|
77 #ifdef PATH_MAX |
|
78 #define MAXPATHLEN PATH_MAX |
|
79 #elif defined(MAX_PATH) |
|
80 #define MAXPATHLEN MAX_PATH |
|
81 #elif defined(_MAX_PATH) |
|
82 #define MAXPATHLEN _MAX_PATH |
|
83 #elif defined(CCHMAXPATH) |
|
84 #define MAXPATHLEN CCHMAXPATH |
|
85 #else |
|
86 #define MAXPATHLEN 1024 |
|
87 #endif |
|
88 #endif |
|
89 |
|
90 /////////////////////////////////////////////////////////////////////// |
|
91 // BEGIN SaveProfileTask et al |
|
92 |
|
93 std::string GetSharedLibraryInfoString(); |
|
94 |
|
95 void TableTicker::HandleSaveRequest() |
|
96 { |
|
97 if (!mSaveRequested) |
|
98 return; |
|
99 mSaveRequested = false; |
|
100 |
|
101 // TODO: Use use the ipc/chromium Tasks here to support processes |
|
102 // without XPCOM. |
|
103 nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask(); |
|
104 NS_DispatchToMainThread(runnable); |
|
105 } |
|
106 |
|
107 void TableTicker::StreamMetaJSCustomObject(JSStreamWriter& b) |
|
108 { |
|
109 b.BeginObject(); |
|
110 |
|
111 b.NameValue("version", 2); |
|
112 b.NameValue("interval", interval()); |
|
113 b.NameValue("stackwalk", mUseStackWalk); |
|
114 b.NameValue("jank", mJankOnly); |
|
115 b.NameValue("processType", XRE_GetProcessType()); |
|
116 |
|
117 TimeDuration delta = TimeStamp::Now() - sStartTime; |
|
118 b.NameValue("startTime", static_cast<float>(PR_Now()/1000.0 - delta.ToMilliseconds())); |
|
119 |
|
120 nsresult res; |
|
121 nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res); |
|
122 if (!NS_FAILED(res)) { |
|
123 nsAutoCString string; |
|
124 |
|
125 res = http->GetPlatform(string); |
|
126 if (!NS_FAILED(res)) |
|
127 b.NameValue("platform", string.Data()); |
|
128 |
|
129 res = http->GetOscpu(string); |
|
130 if (!NS_FAILED(res)) |
|
131 b.NameValue("oscpu", string.Data()); |
|
132 |
|
133 res = http->GetMisc(string); |
|
134 if (!NS_FAILED(res)) |
|
135 b.NameValue("misc", string.Data()); |
|
136 } |
|
137 |
|
138 nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1"); |
|
139 if (runtime) { |
|
140 nsAutoCString string; |
|
141 |
|
142 res = runtime->GetXPCOMABI(string); |
|
143 if (!NS_FAILED(res)) |
|
144 b.NameValue("abi", string.Data()); |
|
145 |
|
146 res = runtime->GetWidgetToolkit(string); |
|
147 if (!NS_FAILED(res)) |
|
148 b.NameValue("toolkit", string.Data()); |
|
149 } |
|
150 |
|
151 nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1"); |
|
152 if (appInfo) { |
|
153 nsAutoCString string; |
|
154 |
|
155 res = appInfo->GetName(string); |
|
156 if (!NS_FAILED(res)) |
|
157 b.NameValue("product", string.Data()); |
|
158 } |
|
159 |
|
160 b.EndObject(); |
|
161 } |
|
162 |
|
163 void TableTicker::ToStreamAsJSON(std::ostream& stream) |
|
164 { |
|
165 JSStreamWriter b(stream); |
|
166 StreamJSObject(b); |
|
167 } |
|
168 |
|
169 JSObject* TableTicker::ToJSObject(JSContext *aCx) |
|
170 { |
|
171 JS::RootedValue val(aCx); |
|
172 std::stringstream ss; |
|
173 { |
|
174 // Define a scope to prevent a moving GC during ~JSStreamWriter from |
|
175 // trashing the return value. |
|
176 JSStreamWriter b(ss); |
|
177 StreamJSObject(b); |
|
178 NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str())); |
|
179 JS_ParseJSON(aCx, static_cast<const jschar*>(js_string.get()), js_string.Length(), &val); |
|
180 } |
|
181 return &val.toObject(); |
|
182 } |
|
183 |
|
184 struct SubprocessClosure { |
|
185 SubprocessClosure(JSStreamWriter *aWriter) |
|
186 : mWriter(aWriter) |
|
187 {} |
|
188 |
|
189 JSStreamWriter* mWriter; |
|
190 }; |
|
191 |
|
192 void SubProcessCallback(const char* aProfile, void* aClosure) |
|
193 { |
|
194 // Called by the observer to get their profile data included |
|
195 // as a sub profile |
|
196 SubprocessClosure* closure = (SubprocessClosure*)aClosure; |
|
197 |
|
198 // Add the string profile into the profile |
|
199 closure->mWriter->Value(aProfile); |
|
200 } |
|
201 |
|
202 |
|
203 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) |
|
204 static |
|
205 void BuildJavaThreadJSObject(JSStreamWriter& b) |
|
206 { |
|
207 b.BeginObject(); |
|
208 |
|
209 b.NameValue("name", "Java Main Thread"); |
|
210 |
|
211 b.Name("samples"); |
|
212 b.BeginArray(); |
|
213 |
|
214 // for each sample |
|
215 for (int sampleId = 0; true; sampleId++) { |
|
216 bool firstRun = true; |
|
217 // for each frame |
|
218 for (int frameId = 0; true; frameId++) { |
|
219 nsCString result; |
|
220 bool hasFrame = AndroidBridge::Bridge()->GetFrameNameJavaProfiling(0, sampleId, frameId, result); |
|
221 // when we run out of frames, we stop looping |
|
222 if (!hasFrame) { |
|
223 // if we found at least one frame, we have objects to close |
|
224 if (!firstRun) { |
|
225 b.EndArray(); |
|
226 b.EndObject(); |
|
227 } |
|
228 break; |
|
229 } |
|
230 // the first time around, open the sample object and frames array |
|
231 if (firstRun) { |
|
232 firstRun = false; |
|
233 |
|
234 double sampleTime = |
|
235 mozilla::widget::android::GeckoJavaSampler::GetSampleTimeJavaProfiling(0, sampleId); |
|
236 |
|
237 b.BeginObject(); |
|
238 b.NameValue("time", sampleTime); |
|
239 |
|
240 b.Name("frames"); |
|
241 b.BeginArray(); |
|
242 } |
|
243 // add a frame to the sample |
|
244 b.BeginObject(); |
|
245 b.NameValue("location", result.BeginReading()); |
|
246 b.EndObject(); |
|
247 } |
|
248 // if we found no frames for this sample, we are done |
|
249 if (firstRun) { |
|
250 break; |
|
251 } |
|
252 } |
|
253 |
|
254 b.EndArray(); |
|
255 |
|
256 b.EndObject(); |
|
257 } |
|
258 #endif |
|
259 |
|
260 void TableTicker::StreamJSObject(JSStreamWriter& b) |
|
261 { |
|
262 b.BeginObject(); |
|
263 // Put shared library info |
|
264 b.NameValue("libs", GetSharedLibraryInfoString().c_str()); |
|
265 |
|
266 // Put meta data |
|
267 b.Name("meta"); |
|
268 StreamMetaJSCustomObject(b); |
|
269 |
|
270 // Lists the samples for each ThreadProfile |
|
271 b.Name("threads"); |
|
272 b.BeginArray(); |
|
273 |
|
274 SetPaused(true); |
|
275 |
|
276 { |
|
277 mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); |
|
278 |
|
279 for (size_t i = 0; i < sRegisteredThreads->size(); i++) { |
|
280 // Thread not being profiled, skip it |
|
281 if (!sRegisteredThreads->at(i)->Profile()) |
|
282 continue; |
|
283 |
|
284 MutexAutoLock lock(*sRegisteredThreads->at(i)->Profile()->GetMutex()); |
|
285 |
|
286 sRegisteredThreads->at(i)->Profile()->StreamJSObject(b); |
|
287 } |
|
288 } |
|
289 |
|
290 if (Sampler::CanNotifyObservers()) { |
|
291 // Send a event asking any subprocesses (plugins) to |
|
292 // give us their information |
|
293 SubprocessClosure closure(&b); |
|
294 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
|
295 if (os) { |
|
296 nsRefPtr<ProfileSaveEvent> pse = new ProfileSaveEvent(SubProcessCallback, &closure); |
|
297 os->NotifyObservers(pse, "profiler-subprocess", nullptr); |
|
298 } |
|
299 } |
|
300 |
|
301 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) |
|
302 if (ProfileJava()) { |
|
303 mozilla::widget::android::GeckoJavaSampler::PauseJavaProfiling(); |
|
304 |
|
305 BuildJavaThreadJSObject(b); |
|
306 |
|
307 mozilla::widget::android::GeckoJavaSampler::UnpauseJavaProfiling(); |
|
308 } |
|
309 #endif |
|
310 |
|
311 SetPaused(false); |
|
312 b.EndArray(); |
|
313 |
|
314 b.EndObject(); |
|
315 } |
|
316 |
|
317 // END SaveProfileTask et al |
|
318 //////////////////////////////////////////////////////////////////////// |
|
319 |
|
320 static |
|
321 void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr) |
|
322 { |
|
323 aProfile.addTag(ProfileEntry(aTagName, "")); |
|
324 // Add one to store the null termination |
|
325 size_t strLen = strlen(aStr) + 1; |
|
326 for (size_t j = 0; j < strLen;) { |
|
327 // Store as many characters in the void* as the platform allows |
|
328 char text[sizeof(void*)]; |
|
329 size_t len = sizeof(void*)/sizeof(char); |
|
330 if (j+len >= strLen) { |
|
331 len = strLen - j; |
|
332 } |
|
333 memcpy(text, &aStr[j], len); |
|
334 j += sizeof(void*)/sizeof(char); |
|
335 // Cast to *((void**) to pass the text data to a void* |
|
336 aProfile.addTag(ProfileEntry('d', *((void**)(&text[0])))); |
|
337 } |
|
338 } |
|
339 |
|
340 static |
|
341 void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile, |
|
342 PseudoStack *stack, void *lastpc) |
|
343 { |
|
344 int lineno = -1; |
|
345 |
|
346 // First entry has tagName 's' (start) |
|
347 // Check for magic pointer bit 1 to indicate copy |
|
348 const char* sampleLabel = entry.label(); |
|
349 if (entry.isCopyLabel()) { |
|
350 // Store the string using 1 or more 'd' (dynamic) tags |
|
351 // that will happen to the preceding tag |
|
352 |
|
353 addDynamicTag(aProfile, 'c', sampleLabel); |
|
354 if (entry.js()) { |
|
355 if (!entry.pc()) { |
|
356 // The JIT only allows the top-most entry to have a nullptr pc |
|
357 MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]); |
|
358 // If stack-walking was disabled, then that's just unfortunate |
|
359 if (lastpc) { |
|
360 jsbytecode *jspc = js::ProfilingGetPC(stack->mRuntime, entry.script(), |
|
361 lastpc); |
|
362 if (jspc) { |
|
363 lineno = JS_PCToLineNumber(nullptr, entry.script(), jspc); |
|
364 } |
|
365 } |
|
366 } else { |
|
367 lineno = JS_PCToLineNumber(nullptr, entry.script(), entry.pc()); |
|
368 } |
|
369 } else { |
|
370 lineno = entry.line(); |
|
371 } |
|
372 } else { |
|
373 aProfile.addTag(ProfileEntry('c', sampleLabel)); |
|
374 lineno = entry.line(); |
|
375 } |
|
376 if (lineno != -1) { |
|
377 aProfile.addTag(ProfileEntry('n', lineno)); |
|
378 } |
|
379 } |
|
380 |
|
381 #if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK) |
|
382 typedef struct { |
|
383 void** array; |
|
384 void** sp_array; |
|
385 size_t size; |
|
386 size_t count; |
|
387 } PCArray; |
|
388 |
|
389 static void mergeNativeBacktrace(ThreadProfile &aProfile, const PCArray &array) { |
|
390 aProfile.addTag(ProfileEntry('s', "(root)")); |
|
391 |
|
392 PseudoStack* stack = aProfile.GetPseudoStack(); |
|
393 uint32_t pseudoStackPos = 0; |
|
394 |
|
395 /* We have two stacks, the native C stack we extracted from unwinding, |
|
396 * and the pseudostack we managed during execution. We want to consolidate |
|
397 * the two in order. We do so by merging using the approximate stack address |
|
398 * when each entry was push. When pushing JS entry we may not now the stack |
|
399 * address in which case we have a nullptr stack address in which case we assume |
|
400 * that it follows immediatly the previous element. |
|
401 * |
|
402 * C Stack | Address -- Pseudo Stack | Address |
|
403 * main() | 0x100 run_js() | 0x40 |
|
404 * start() | 0x80 jsCanvas() | nullptr |
|
405 * timer() | 0x50 drawLine() | nullptr |
|
406 * azure() | 0x10 |
|
407 * |
|
408 * Merged: main(), start(), timer(), run_js(), jsCanvas(), drawLine(), azure() |
|
409 */ |
|
410 // i is the index in C stack starting at main and decreasing |
|
411 // pseudoStackPos is the position in the Pseudo stack starting |
|
412 // at the first frame (run_js in the example) and increasing. |
|
413 for (size_t i = array.count; i > 0; --i) { |
|
414 while (pseudoStackPos < stack->stackSize()) { |
|
415 volatile StackEntry& entry = stack->mStack[pseudoStackPos]; |
|
416 |
|
417 if (entry.stackAddress() < array.sp_array[i-1] && entry.stackAddress()) |
|
418 break; |
|
419 |
|
420 addProfileEntry(entry, aProfile, stack, array.array[0]); |
|
421 pseudoStackPos++; |
|
422 } |
|
423 |
|
424 aProfile.addTag(ProfileEntry('l', (void*)array.array[i-1])); |
|
425 } |
|
426 } |
|
427 |
|
428 #endif |
|
429 |
|
430 #ifdef USE_NS_STACKWALK |
|
431 static |
|
432 void StackWalkCallback(void* aPC, void* aSP, void* aClosure) |
|
433 { |
|
434 PCArray* array = static_cast<PCArray*>(aClosure); |
|
435 MOZ_ASSERT(array->count < array->size); |
|
436 array->sp_array[array->count] = aSP; |
|
437 array->array[array->count] = aPC; |
|
438 array->count++; |
|
439 } |
|
440 |
|
441 void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) |
|
442 { |
|
443 #ifndef XP_MACOSX |
|
444 uintptr_t thread = GetThreadHandle(aSample->threadProfile->GetPlatformData()); |
|
445 MOZ_ASSERT(thread); |
|
446 #endif |
|
447 void* pc_array[1000]; |
|
448 void* sp_array[1000]; |
|
449 PCArray array = { |
|
450 pc_array, |
|
451 sp_array, |
|
452 mozilla::ArrayLength(pc_array), |
|
453 0 |
|
454 }; |
|
455 |
|
456 // Start with the current function. |
|
457 StackWalkCallback(aSample->pc, aSample->sp, &array); |
|
458 |
|
459 uint32_t maxFrames = uint32_t(array.size - array.count); |
|
460 #ifdef XP_MACOSX |
|
461 pthread_t pt = GetProfiledThread(aSample->threadProfile->GetPlatformData()); |
|
462 void *stackEnd = reinterpret_cast<void*>(-1); |
|
463 if (pt) |
|
464 stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt)); |
|
465 nsresult rv = NS_OK; |
|
466 if (aSample->fp >= aSample->sp && aSample->fp <= stackEnd) |
|
467 rv = FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0, |
|
468 maxFrames, &array, |
|
469 reinterpret_cast<void**>(aSample->fp), stackEnd); |
|
470 #else |
|
471 void *platformData = nullptr; |
|
472 #ifdef XP_WIN |
|
473 if (aSample->isSamplingCurrentThread) { |
|
474 // In this case we want NS_StackWalk to know that it's walking the |
|
475 // current thread's stack, so we pass 0 as the thread handle. |
|
476 thread = 0; |
|
477 } |
|
478 platformData = aSample->context; |
|
479 #endif // XP_WIN |
|
480 |
|
481 nsresult rv = NS_StackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames, |
|
482 &array, thread, platformData); |
|
483 #endif |
|
484 if (NS_SUCCEEDED(rv)) |
|
485 mergeNativeBacktrace(aProfile, array); |
|
486 } |
|
487 #endif |
|
488 |
|
489 #ifdef USE_EHABI_STACKWALK |
|
490 void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) |
|
491 { |
|
492 void *pc_array[1000]; |
|
493 void *sp_array[1000]; |
|
494 PCArray array = { |
|
495 pc_array, |
|
496 sp_array, |
|
497 mozilla::ArrayLength(pc_array), |
|
498 0 |
|
499 }; |
|
500 |
|
501 const mcontext_t *mcontext = &reinterpret_cast<ucontext_t *>(aSample->context)->uc_mcontext; |
|
502 mcontext_t savedContext; |
|
503 PseudoStack *pseudoStack = aProfile.GetPseudoStack(); |
|
504 |
|
505 array.count = 0; |
|
506 // The pseudostack contains an "EnterJIT" frame whenever we enter |
|
507 // JIT code with profiling enabled; the stack pointer value points |
|
508 // the saved registers. We use this to unwind resume unwinding |
|
509 // after encounting JIT code. |
|
510 for (uint32_t i = pseudoStack->stackSize(); i > 0; --i) { |
|
511 // The pseudostack grows towards higher indices, so we iterate |
|
512 // backwards (from callee to caller). |
|
513 volatile StackEntry &entry = pseudoStack->mStack[i - 1]; |
|
514 if (!entry.js() && strcmp(entry.label(), "EnterJIT") == 0) { |
|
515 // Found JIT entry frame. Unwind up to that point (i.e., force |
|
516 // the stack walk to stop before the block of saved registers; |
|
517 // note that it yields nondecreasing stack pointers), then restore |
|
518 // the saved state. |
|
519 uint32_t *vSP = reinterpret_cast<uint32_t*>(entry.stackAddress()); |
|
520 |
|
521 array.count += EHABIStackWalk(*mcontext, |
|
522 /* stackBase = */ vSP, |
|
523 sp_array + array.count, |
|
524 pc_array + array.count, |
|
525 array.size - array.count); |
|
526 |
|
527 memset(&savedContext, 0, sizeof(savedContext)); |
|
528 // See also: struct EnterJITStack in js/src/jit/arm/Trampoline-arm.cpp |
|
529 savedContext.arm_r4 = *vSP++; |
|
530 savedContext.arm_r5 = *vSP++; |
|
531 savedContext.arm_r6 = *vSP++; |
|
532 savedContext.arm_r7 = *vSP++; |
|
533 savedContext.arm_r8 = *vSP++; |
|
534 savedContext.arm_r9 = *vSP++; |
|
535 savedContext.arm_r10 = *vSP++; |
|
536 savedContext.arm_fp = *vSP++; |
|
537 savedContext.arm_lr = *vSP++; |
|
538 savedContext.arm_sp = reinterpret_cast<uint32_t>(vSP); |
|
539 savedContext.arm_pc = savedContext.arm_lr; |
|
540 mcontext = &savedContext; |
|
541 } |
|
542 } |
|
543 |
|
544 // Now unwind whatever's left (starting from either the last EnterJIT |
|
545 // frame or, if no EnterJIT was found, the original registers). |
|
546 array.count += EHABIStackWalk(*mcontext, |
|
547 aProfile.GetStackTop(), |
|
548 sp_array + array.count, |
|
549 pc_array + array.count, |
|
550 array.size - array.count); |
|
551 |
|
552 mergeNativeBacktrace(aProfile, array); |
|
553 } |
|
554 |
|
555 #endif |
|
556 |
|
557 static |
|
558 void doSampleStackTrace(PseudoStack *aStack, ThreadProfile &aProfile, TickSample *sample) |
|
559 { |
|
560 // Sample |
|
561 // 's' tag denotes the start of a sample block |
|
562 // followed by 0 or more 'c' tags. |
|
563 aProfile.addTag(ProfileEntry('s', "(root)")); |
|
564 for (uint32_t i = 0; i < aStack->stackSize(); i++) { |
|
565 addProfileEntry(aStack->mStack[i], aProfile, aStack, nullptr); |
|
566 } |
|
567 #ifdef ENABLE_SPS_LEAF_DATA |
|
568 if (sample) { |
|
569 aProfile.addTag(ProfileEntry('l', (void*)sample->pc)); |
|
570 #ifdef ENABLE_ARM_LR_SAVING |
|
571 aProfile.addTag(ProfileEntry('L', (void*)sample->lr)); |
|
572 #endif |
|
573 } |
|
574 #endif |
|
575 } |
|
576 |
|
577 void TableTicker::Tick(TickSample* sample) |
|
578 { |
|
579 if (HasUnwinderThread()) { |
|
580 UnwinderTick(sample); |
|
581 } else { |
|
582 InplaceTick(sample); |
|
583 } |
|
584 } |
|
585 |
|
586 void TableTicker::InplaceTick(TickSample* sample) |
|
587 { |
|
588 ThreadProfile& currThreadProfile = *sample->threadProfile; |
|
589 |
|
590 PseudoStack* stack = currThreadProfile.GetPseudoStack(); |
|
591 bool recordSample = true; |
|
592 #if defined(XP_WIN) |
|
593 bool powerSample = false; |
|
594 #endif |
|
595 |
|
596 /* Don't process the PeudoStack's markers or honour jankOnly if we're |
|
597 immediately sampling the current thread. */ |
|
598 if (!sample->isSamplingCurrentThread) { |
|
599 // Marker(s) come before the sample |
|
600 ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers(); |
|
601 while (pendingMarkersList && pendingMarkersList->peek()) { |
|
602 ProfilerMarker* marker = pendingMarkersList->popHead(); |
|
603 stack->addStoredMarker(marker); |
|
604 currThreadProfile.addTag(ProfileEntry('m', marker)); |
|
605 } |
|
606 stack->updateGeneration(currThreadProfile.GetGenerationID()); |
|
607 |
|
608 #if defined(XP_WIN) |
|
609 if (mProfilePower) { |
|
610 mIntelPowerGadget->TakeSample(); |
|
611 powerSample = true; |
|
612 } |
|
613 #endif |
|
614 |
|
615 if (mJankOnly) { |
|
616 // if we are on a different event we can discard any temporary samples |
|
617 // we've kept around |
|
618 if (sLastSampledEventGeneration != sCurrentEventGeneration) { |
|
619 // XXX: we also probably want to add an entry to the profile to help |
|
620 // distinguish which samples are part of the same event. That, or record |
|
621 // the event generation in each sample |
|
622 currThreadProfile.erase(); |
|
623 } |
|
624 sLastSampledEventGeneration = sCurrentEventGeneration; |
|
625 |
|
626 recordSample = false; |
|
627 // only record the events when we have a we haven't seen a tracer event for 100ms |
|
628 if (!sLastTracerEvent.IsNull()) { |
|
629 TimeDuration delta = sample->timestamp - sLastTracerEvent; |
|
630 if (delta.ToMilliseconds() > 100.0) { |
|
631 recordSample = true; |
|
632 } |
|
633 } |
|
634 } |
|
635 } |
|
636 |
|
637 #if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK) |
|
638 if (mUseStackWalk) { |
|
639 doNativeBacktrace(currThreadProfile, sample); |
|
640 } else { |
|
641 doSampleStackTrace(stack, currThreadProfile, mAddLeafAddresses ? sample : nullptr); |
|
642 } |
|
643 #else |
|
644 doSampleStackTrace(stack, currThreadProfile, mAddLeafAddresses ? sample : nullptr); |
|
645 #endif |
|
646 |
|
647 if (recordSample) |
|
648 currThreadProfile.flush(); |
|
649 |
|
650 if (!sLastTracerEvent.IsNull() && sample && currThreadProfile.IsMainThread()) { |
|
651 TimeDuration delta = sample->timestamp - sLastTracerEvent; |
|
652 currThreadProfile.addTag(ProfileEntry('r', static_cast<float>(delta.ToMilliseconds()))); |
|
653 } |
|
654 |
|
655 if (sample) { |
|
656 TimeDuration delta = sample->timestamp - sStartTime; |
|
657 currThreadProfile.addTag(ProfileEntry('t', static_cast<float>(delta.ToMilliseconds()))); |
|
658 } |
|
659 |
|
660 #if defined(XP_WIN) |
|
661 if (powerSample) { |
|
662 currThreadProfile.addTag(ProfileEntry('p', static_cast<float>(mIntelPowerGadget->GetTotalPackagePowerInWatts()))); |
|
663 } |
|
664 #endif |
|
665 |
|
666 if (sLastFrameNumber != sFrameNumber) { |
|
667 currThreadProfile.addTag(ProfileEntry('f', sFrameNumber)); |
|
668 sLastFrameNumber = sFrameNumber; |
|
669 } |
|
670 } |
|
671 |
|
672 namespace { |
|
673 |
|
674 SyncProfile* NewSyncProfile() |
|
675 { |
|
676 PseudoStack* stack = tlsPseudoStack.get(); |
|
677 if (!stack) { |
|
678 MOZ_ASSERT(stack); |
|
679 return nullptr; |
|
680 } |
|
681 Thread::tid_t tid = Thread::GetCurrentId(); |
|
682 |
|
683 SyncProfile* profile = new SyncProfile("SyncProfile", |
|
684 GET_BACKTRACE_DEFAULT_ENTRY, |
|
685 stack, tid, NS_IsMainThread()); |
|
686 return profile; |
|
687 } |
|
688 |
|
689 } // anonymous namespace |
|
690 |
|
691 SyncProfile* TableTicker::GetBacktrace() |
|
692 { |
|
693 SyncProfile* profile = NewSyncProfile(); |
|
694 |
|
695 TickSample sample; |
|
696 sample.threadProfile = profile; |
|
697 |
|
698 #if defined(HAVE_NATIVE_UNWIND) |
|
699 #if defined(XP_WIN) || defined(LINUX) |
|
700 tickcontext_t context; |
|
701 sample.PopulateContext(&context); |
|
702 #elif defined(XP_MACOSX) |
|
703 sample.PopulateContext(nullptr); |
|
704 #endif |
|
705 #endif |
|
706 |
|
707 sample.isSamplingCurrentThread = true; |
|
708 sample.timestamp = mozilla::TimeStamp::Now(); |
|
709 |
|
710 if (!HasUnwinderThread()) { |
|
711 profile->BeginUnwind(); |
|
712 } |
|
713 |
|
714 Tick(&sample); |
|
715 |
|
716 if (!HasUnwinderThread()) { |
|
717 profile->EndUnwind(); |
|
718 } |
|
719 |
|
720 return profile; |
|
721 } |
|
722 |
|
723 static void print_callback(const ProfileEntry& entry, const char* tagStringData) |
|
724 { |
|
725 switch (entry.getTagName()) { |
|
726 case 's': |
|
727 case 'c': |
|
728 printf_stderr(" %s\n", tagStringData); |
|
729 } |
|
730 } |
|
731 |
|
732 void mozilla_sampler_print_location1() |
|
733 { |
|
734 if (!stack_key_initialized) |
|
735 profiler_init(nullptr); |
|
736 |
|
737 SyncProfile* syncProfile = NewSyncProfile(); |
|
738 if (!syncProfile) { |
|
739 return; |
|
740 } |
|
741 |
|
742 syncProfile->BeginUnwind(); |
|
743 doSampleStackTrace(syncProfile->GetPseudoStack(), *syncProfile, nullptr); |
|
744 syncProfile->EndUnwind(); |
|
745 |
|
746 printf_stderr("Backtrace:\n"); |
|
747 syncProfile->IterateTags(print_callback); |
|
748 delete syncProfile; |
|
749 } |
|
750 |
|
751 |