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