1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "nsProfiler.h"
8
9 #include <sstream>
10 #include <string>
11 #include <utility>
12
13 #include "GeckoProfiler.h"
14 #include "ProfilerParent.h"
15 #include "js/Array.h" // JS::NewArrayObject
16 #include "js/JSON.h"
17 #include "js/Value.h"
18 #include "mozilla/ErrorResult.h"
19 #include "mozilla/SchedulerGroup.h"
20 #include "mozilla/Services.h"
21 #include "mozilla/dom/Promise.h"
22 #include "mozilla/dom/TypedArray.h"
23 #include "mozilla/Preferences.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsIFileStreams.h"
26 #include "nsIInterfaceRequestor.h"
27 #include "nsIInterfaceRequestorUtils.h"
28 #include "nsILoadContext.h"
29 #include "nsIObserverService.h"
30 #include "nsIWebNavigation.h"
31 #include "nsLocalFile.h"
32 #include "nsMemory.h"
33 #include "nsProfilerStartParams.h"
34 #include "nsProxyRelease.h"
35 #include "nsString.h"
36 #include "nsThreadUtils.h"
37 #include "platform.h"
38 #include "shared-libraries.h"
39 #include "zlib.h"
40
41 using namespace mozilla;
42
43 using dom::AutoJSAPI;
44 using dom::Promise;
45 using std::string;
46
NS_IMPL_ISUPPORTS(nsProfiler,nsIProfiler,nsIObserver)47 NS_IMPL_ISUPPORTS(nsProfiler, nsIProfiler, nsIObserver)
48
49 nsProfiler::nsProfiler()
50 : mLockedForPrivateBrowsing(false),
51 mPendingProfiles(0),
52 mGathering(false) {}
53
~nsProfiler()54 nsProfiler::~nsProfiler() {
55 nsCOMPtr<nsIObserverService> observerService =
56 mozilla::services::GetObserverService();
57 if (observerService) {
58 observerService->RemoveObserver(this, "chrome-document-global-created");
59 observerService->RemoveObserver(this, "last-pb-context-exited");
60 }
61 if (mSymbolTableThread) {
62 mSymbolTableThread->Shutdown();
63 }
64 ResetGathering();
65 }
66
Init()67 nsresult nsProfiler::Init() {
68 nsCOMPtr<nsIObserverService> observerService =
69 mozilla::services::GetObserverService();
70 if (observerService) {
71 observerService->AddObserver(this, "chrome-document-global-created", false);
72 observerService->AddObserver(this, "last-pb-context-exited", false);
73 }
74 return NS_OK;
75 }
76
77 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)78 nsProfiler::Observe(nsISupports* aSubject, const char* aTopic,
79 const char16_t* aData) {
80 // The profiler's handling of private browsing is as simple as possible: it
81 // is stopped when the first PB window opens, and left stopped when the last
82 // PB window closes.
83 if (strcmp(aTopic, "chrome-document-global-created") == 0) {
84 nsCOMPtr<nsIInterfaceRequestor> requestor = do_QueryInterface(aSubject);
85 nsCOMPtr<nsIWebNavigation> parentWebNav = do_GetInterface(requestor);
86 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(parentWebNav);
87 if (loadContext && loadContext->UsePrivateBrowsing() &&
88 !mLockedForPrivateBrowsing) {
89 mLockedForPrivateBrowsing = true;
90 // Allow profiling tests that trigger private browsing.
91 if (!xpc::IsInAutomation()) {
92 profiler_stop();
93 }
94 }
95 } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
96 mLockedForPrivateBrowsing = false;
97 }
98 return NS_OK;
99 }
100
101 NS_IMETHODIMP
CanProfile(bool * aCanProfile)102 nsProfiler::CanProfile(bool* aCanProfile) {
103 *aCanProfile = !mLockedForPrivateBrowsing;
104 return NS_OK;
105 }
106
FillVectorFromStringArray(Vector<const char * > & aVector,const nsTArray<nsCString> & aArray)107 static nsresult FillVectorFromStringArray(Vector<const char*>& aVector,
108 const nsTArray<nsCString>& aArray) {
109 if (NS_WARN_IF(!aVector.reserve(aArray.Length()))) {
110 return NS_ERROR_OUT_OF_MEMORY;
111 }
112 for (auto& entry : aArray) {
113 aVector.infallibleAppend(entry.get());
114 }
115 return NS_OK;
116 }
117
118 NS_IMETHODIMP
StartProfiler(uint32_t aEntries,double aInterval,const nsTArray<nsCString> & aFeatures,const nsTArray<nsCString> & aFilters,uint64_t aActiveTabID,double aDuration)119 nsProfiler::StartProfiler(uint32_t aEntries, double aInterval,
120 const nsTArray<nsCString>& aFeatures,
121 const nsTArray<nsCString>& aFilters,
122 uint64_t aActiveTabID, double aDuration) {
123 if (mLockedForPrivateBrowsing) {
124 return NS_ERROR_NOT_AVAILABLE;
125 }
126
127 ResetGathering();
128
129 Vector<const char*> featureStringVector;
130 nsresult rv = FillVectorFromStringArray(featureStringVector, aFeatures);
131 if (NS_FAILED(rv)) {
132 return rv;
133 }
134 uint32_t features = ParseFeaturesFromStringArray(
135 featureStringVector.begin(), featureStringVector.length());
136 Maybe<double> duration = aDuration > 0.0 ? Some(aDuration) : Nothing();
137
138 Vector<const char*> filterStringVector;
139 rv = FillVectorFromStringArray(filterStringVector, aFilters);
140 if (NS_FAILED(rv)) {
141 return rv;
142 }
143 profiler_start(PowerOfTwo32(aEntries), aInterval, features,
144 filterStringVector.begin(), filterStringVector.length(),
145 aActiveTabID, duration);
146
147 return NS_OK;
148 }
149
150 NS_IMETHODIMP
StopProfiler()151 nsProfiler::StopProfiler() {
152 ResetGathering();
153
154 profiler_stop();
155
156 return NS_OK;
157 }
158
159 NS_IMETHODIMP
IsPaused(bool * aIsPaused)160 nsProfiler::IsPaused(bool* aIsPaused) {
161 *aIsPaused = profiler_is_paused();
162 return NS_OK;
163 }
164
165 NS_IMETHODIMP
Pause()166 nsProfiler::Pause() {
167 profiler_pause();
168 return NS_OK;
169 }
170
171 NS_IMETHODIMP
Resume()172 nsProfiler::Resume() {
173 profiler_resume();
174 return NS_OK;
175 }
176
177 NS_IMETHODIMP
IsSamplingPaused(bool * aIsSamplingPaused)178 nsProfiler::IsSamplingPaused(bool* aIsSamplingPaused) {
179 *aIsSamplingPaused = profiler_is_sampling_paused();
180 return NS_OK;
181 }
182
183 NS_IMETHODIMP
PauseSampling()184 nsProfiler::PauseSampling() {
185 profiler_pause_sampling();
186 return NS_OK;
187 }
188
189 NS_IMETHODIMP
ResumeSampling()190 nsProfiler::ResumeSampling() {
191 profiler_resume_sampling();
192 return NS_OK;
193 }
194
195 NS_IMETHODIMP
ClearAllPages()196 nsProfiler::ClearAllPages() {
197 profiler_clear_all_pages();
198 return NS_OK;
199 }
200
201 NS_IMETHODIMP
WaitOnePeriodicSampling(JSContext * aCx,Promise ** aPromise)202 nsProfiler::WaitOnePeriodicSampling(JSContext* aCx, Promise** aPromise) {
203 MOZ_ASSERT(NS_IsMainThread());
204
205 if (NS_WARN_IF(!aCx)) {
206 return NS_ERROR_FAILURE;
207 }
208
209 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
210 if (NS_WARN_IF(!globalObject)) {
211 return NS_ERROR_FAILURE;
212 }
213
214 ErrorResult result;
215 RefPtr<Promise> promise = Promise::Create(globalObject, result);
216 if (NS_WARN_IF(result.Failed())) {
217 return result.StealNSResult();
218 }
219
220 // The callback cannot officially own the promise RefPtr directly, because
221 // `Promise` doesn't support multi-threading, and the callback could destroy
222 // the promise in the sampler thread.
223 // `nsMainThreadPtrHandle` ensures that the promise can only be destroyed on
224 // the main thread. And the invocation from the Sampler thread immediately
225 // dispatches a task back to the main thread, to resolve/reject the promise.
226 // The lambda needs to be `mutable`, to allow moving-from
227 // `promiseHandleInSampler`.
228 if (!profiler_callback_after_sampling(
229 [promiseHandleInSampler = nsMainThreadPtrHandle<Promise>(
230 new nsMainThreadPtrHolder<Promise>(
231 "WaitOnePeriodicSampling promise for Sampler", promise))](
232 SamplingState aSamplingState) mutable {
233 SchedulerGroup::Dispatch(
234 TaskCategory::Other,
235 NS_NewRunnableFunction(
236 "nsProfiler::WaitOnePeriodicSampling result on main thread",
237 [promiseHandleInMT = std::move(promiseHandleInSampler),
238 aSamplingState]() {
239 AutoJSAPI jsapi;
240 if (NS_WARN_IF(!jsapi.Init(
241 promiseHandleInMT->GetGlobalObject()))) {
242 // We're really hosed if we can't get a JS context for
243 // some reason.
244 promiseHandleInMT->MaybeReject(
245 NS_ERROR_DOM_UNKNOWN_ERR);
246 return;
247 }
248
249 switch (aSamplingState) {
250 case SamplingState::JustStopped:
251 case SamplingState::SamplingPaused:
252 promiseHandleInMT->MaybeReject(NS_ERROR_FAILURE);
253 break;
254
255 case SamplingState::NoStackSamplingCompleted:
256 case SamplingState::SamplingCompleted: {
257 JS::RootedValue val(jsapi.cx());
258 promiseHandleInMT->MaybeResolve(val);
259 } break;
260
261 default:
262 MOZ_ASSERT(false, "Unexpected SamplingState value");
263 promiseHandleInMT->MaybeReject(
264 NS_ERROR_DOM_UNKNOWN_ERR);
265 break;
266 }
267 }));
268 })) {
269 // Callback was not added (e.g., profiler is not running) and will never be
270 // invoked, so we need to resolve the promise here.
271 promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
272 }
273
274 promise.forget(aPromise);
275 return NS_OK;
276 }
277
278 NS_IMETHODIMP
GetProfile(double aSinceTime,char ** aProfile)279 nsProfiler::GetProfile(double aSinceTime, char** aProfile) {
280 mozilla::UniquePtr<char[]> profile = profiler_get_profile(aSinceTime);
281 *aProfile = profile.release();
282 return NS_OK;
283 }
284
285 namespace {
286 struct StringWriteFunc : public JSONWriteFunc {
287 nsAString& mBuffer; // This struct must not outlive this buffer
StringWriteFunc__anondf6d83ea0311::StringWriteFunc288 explicit StringWriteFunc(nsAString& buffer) : mBuffer(buffer) {}
289
Write__anondf6d83ea0311::StringWriteFunc290 void Write(const Span<const char>& aStr) override {
291 mBuffer.Append(NS_ConvertUTF8toUTF16(aStr.data(), aStr.size()));
292 }
293 };
294 } // namespace
295
296 NS_IMETHODIMP
GetSharedLibraries(JSContext * aCx,JS::MutableHandle<JS::Value> aResult)297 nsProfiler::GetSharedLibraries(JSContext* aCx,
298 JS::MutableHandle<JS::Value> aResult) {
299 JS::RootedValue val(aCx);
300 {
301 nsString buffer;
302 JSONWriter w(MakeUnique<StringWriteFunc>(buffer));
303 w.StartArrayElement();
304 AppendSharedLibraries(w);
305 w.EndArray();
306 MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx,
307 static_cast<const char16_t*>(buffer.get()),
308 buffer.Length(), &val));
309 }
310 JS::RootedObject obj(aCx, &val.toObject());
311 if (!obj) {
312 return NS_ERROR_FAILURE;
313 }
314 aResult.setObject(*obj);
315 return NS_OK;
316 }
317
318 NS_IMETHODIMP
GetActiveConfiguration(JSContext * aCx,JS::MutableHandle<JS::Value> aResult)319 nsProfiler::GetActiveConfiguration(JSContext* aCx,
320 JS::MutableHandle<JS::Value> aResult) {
321 JS::RootedValue jsValue(aCx);
322 {
323 nsString buffer;
324 JSONWriter writer(MakeUnique<StringWriteFunc>(buffer));
325 profiler_write_active_configuration(writer);
326 MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx,
327 static_cast<const char16_t*>(buffer.get()),
328 buffer.Length(), &jsValue));
329 }
330 if (jsValue.isNull()) {
331 aResult.setNull();
332 } else {
333 JS::RootedObject obj(aCx, &jsValue.toObject());
334 if (!obj) {
335 return NS_ERROR_FAILURE;
336 }
337 aResult.setObject(*obj);
338 }
339 return NS_OK;
340 }
341
342 NS_IMETHODIMP
DumpProfileToFile(const char * aFilename)343 nsProfiler::DumpProfileToFile(const char* aFilename) {
344 profiler_save_profile_to_file(aFilename);
345 return NS_OK;
346 }
347
348 NS_IMETHODIMP
GetProfileData(double aSinceTime,JSContext * aCx,JS::MutableHandle<JS::Value> aResult)349 nsProfiler::GetProfileData(double aSinceTime, JSContext* aCx,
350 JS::MutableHandle<JS::Value> aResult) {
351 mozilla::UniquePtr<char[]> profile = profiler_get_profile(aSinceTime);
352 if (!profile) {
353 return NS_ERROR_FAILURE;
354 }
355
356 NS_ConvertUTF8toUTF16 js_string(nsDependentCString(profile.get()));
357 auto profile16 = static_cast<const char16_t*>(js_string.get());
358
359 JS::RootedValue val(aCx);
360 MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, profile16, js_string.Length(), &val));
361
362 aResult.set(val);
363 return NS_OK;
364 }
365
366 NS_IMETHODIMP
GetProfileDataAsync(double aSinceTime,JSContext * aCx,Promise ** aPromise)367 nsProfiler::GetProfileDataAsync(double aSinceTime, JSContext* aCx,
368 Promise** aPromise) {
369 MOZ_ASSERT(NS_IsMainThread());
370
371 if (!profiler_is_active()) {
372 return NS_ERROR_FAILURE;
373 }
374
375 if (NS_WARN_IF(!aCx)) {
376 return NS_ERROR_FAILURE;
377 }
378
379 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
380 if (NS_WARN_IF(!globalObject)) {
381 return NS_ERROR_FAILURE;
382 }
383
384 ErrorResult result;
385 RefPtr<Promise> promise = Promise::Create(globalObject, result);
386 if (NS_WARN_IF(result.Failed())) {
387 return result.StealNSResult();
388 }
389
390 StartGathering(aSinceTime)
391 ->Then(
392 GetMainThreadSerialEventTarget(), __func__,
393 [promise](nsCString aResult) {
394 AutoJSAPI jsapi;
395 if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
396 // We're really hosed if we can't get a JS context for some
397 // reason.
398 promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
399 return;
400 }
401
402 JSContext* cx = jsapi.cx();
403
404 // Now parse the JSON so that we resolve with a JS Object.
405 JS::RootedValue val(cx);
406 {
407 NS_ConvertUTF8toUTF16 js_string(aResult);
408 if (!JS_ParseJSON(cx,
409 static_cast<const char16_t*>(js_string.get()),
410 js_string.Length(), &val)) {
411 if (!jsapi.HasException()) {
412 promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
413 } else {
414 JS::RootedValue exn(cx);
415 DebugOnly<bool> gotException = jsapi.StealException(&exn);
416 MOZ_ASSERT(gotException);
417
418 jsapi.ClearException();
419 promise->MaybeReject(exn);
420 }
421 } else {
422 promise->MaybeResolve(val);
423 }
424 }
425 },
426 [promise](nsresult aRv) { promise->MaybeReject(aRv); });
427
428 promise.forget(aPromise);
429 return NS_OK;
430 }
431
432 NS_IMETHODIMP
GetProfileDataAsArrayBuffer(double aSinceTime,JSContext * aCx,Promise ** aPromise)433 nsProfiler::GetProfileDataAsArrayBuffer(double aSinceTime, JSContext* aCx,
434 Promise** aPromise) {
435 MOZ_ASSERT(NS_IsMainThread());
436
437 if (!profiler_is_active()) {
438 return NS_ERROR_FAILURE;
439 }
440
441 if (NS_WARN_IF(!aCx)) {
442 return NS_ERROR_FAILURE;
443 }
444
445 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
446 if (NS_WARN_IF(!globalObject)) {
447 return NS_ERROR_FAILURE;
448 }
449
450 ErrorResult result;
451 RefPtr<Promise> promise = Promise::Create(globalObject, result);
452 if (NS_WARN_IF(result.Failed())) {
453 return result.StealNSResult();
454 }
455
456 StartGathering(aSinceTime)
457 ->Then(
458 GetMainThreadSerialEventTarget(), __func__,
459 [promise](nsCString aResult) {
460 AutoJSAPI jsapi;
461 if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
462 // We're really hosed if we can't get a JS context for some
463 // reason.
464 promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
465 return;
466 }
467
468 JSContext* cx = jsapi.cx();
469 JSObject* typedArray = dom::ArrayBuffer::Create(
470 cx, aResult.Length(),
471 reinterpret_cast<const uint8_t*>(aResult.Data()));
472 if (typedArray) {
473 JS::RootedValue val(cx, JS::ObjectValue(*typedArray));
474 promise->MaybeResolve(val);
475 } else {
476 promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
477 }
478 },
479 [promise](nsresult aRv) { promise->MaybeReject(aRv); });
480
481 promise.forget(aPromise);
482 return NS_OK;
483 }
484
485 NS_IMETHODIMP
GetProfileDataAsGzippedArrayBuffer(double aSinceTime,JSContext * aCx,Promise ** aPromise)486 nsProfiler::GetProfileDataAsGzippedArrayBuffer(double aSinceTime,
487 JSContext* aCx,
488 Promise** aPromise) {
489 MOZ_ASSERT(NS_IsMainThread());
490
491 if (!profiler_is_active()) {
492 return NS_ERROR_FAILURE;
493 }
494
495 if (NS_WARN_IF(!aCx)) {
496 return NS_ERROR_FAILURE;
497 }
498
499 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
500 if (NS_WARN_IF(!globalObject)) {
501 return NS_ERROR_FAILURE;
502 }
503
504 ErrorResult result;
505 RefPtr<Promise> promise = Promise::Create(globalObject, result);
506 if (NS_WARN_IF(result.Failed())) {
507 return result.StealNSResult();
508 }
509
510 StartGathering(aSinceTime)
511 ->Then(
512 GetMainThreadSerialEventTarget(), __func__,
513 [promise](nsCString aResult) {
514 AutoJSAPI jsapi;
515 if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
516 // We're really hosed if we can't get a JS context for some
517 // reason.
518 promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
519 return;
520 }
521
522 // Compress a buffer via zlib (as with `compress()`), but emit a
523 // gzip header as well. Like `compress()`, this is limited to 4GB in
524 // size, but that shouldn't be an issue for our purposes.
525 uLongf outSize = compressBound(aResult.Length());
526 FallibleTArray<uint8_t> outBuff;
527 if (!outBuff.SetLength(outSize, fallible)) {
528 promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
529 return;
530 }
531
532 int zerr;
533 z_stream stream;
534 stream.zalloc = nullptr;
535 stream.zfree = nullptr;
536 stream.opaque = nullptr;
537 stream.next_out = (Bytef*)outBuff.Elements();
538 stream.avail_out = outBuff.Length();
539 stream.next_in = (z_const Bytef*)aResult.Data();
540 stream.avail_in = aResult.Length();
541
542 // A windowBits of 31 is the default (15) plus 16 for emitting a
543 // gzip header; a memLevel of 8 is the default.
544 zerr = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
545 /* windowBits */ 31, /* memLevel */ 8,
546 Z_DEFAULT_STRATEGY);
547 if (zerr != Z_OK) {
548 promise->MaybeReject(NS_ERROR_FAILURE);
549 return;
550 }
551
552 zerr = deflate(&stream, Z_FINISH);
553 outSize = stream.total_out;
554 deflateEnd(&stream);
555
556 if (zerr != Z_STREAM_END) {
557 promise->MaybeReject(NS_ERROR_FAILURE);
558 return;
559 }
560
561 outBuff.TruncateLength(outSize);
562
563 JSContext* cx = jsapi.cx();
564 JSObject* typedArray = dom::ArrayBuffer::Create(
565 cx, outBuff.Length(), outBuff.Elements());
566 if (typedArray) {
567 JS::RootedValue val(cx, JS::ObjectValue(*typedArray));
568 promise->MaybeResolve(val);
569 } else {
570 promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
571 }
572 },
573 [promise](nsresult aRv) { promise->MaybeReject(aRv); });
574
575 promise.forget(aPromise);
576 return NS_OK;
577 }
578
579 NS_IMETHODIMP
DumpProfileToFileAsync(const nsACString & aFilename,double aSinceTime,JSContext * aCx,Promise ** aPromise)580 nsProfiler::DumpProfileToFileAsync(const nsACString& aFilename,
581 double aSinceTime, JSContext* aCx,
582 Promise** aPromise) {
583 MOZ_ASSERT(NS_IsMainThread());
584
585 if (!profiler_is_active()) {
586 return NS_ERROR_FAILURE;
587 }
588
589 if (NS_WARN_IF(!aCx)) {
590 return NS_ERROR_FAILURE;
591 }
592
593 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
594 if (NS_WARN_IF(!globalObject)) {
595 return NS_ERROR_FAILURE;
596 }
597
598 ErrorResult result;
599 RefPtr<Promise> promise = Promise::Create(globalObject, result);
600 if (NS_WARN_IF(result.Failed())) {
601 return result.StealNSResult();
602 }
603
604 nsCString filename(aFilename);
605
606 StartGathering(aSinceTime)
607 ->Then(
608 GetMainThreadSerialEventTarget(), __func__,
609 [filename, promise](const nsCString& aResult) {
610 nsCOMPtr<nsIFile> file =
611 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
612 nsresult rv = file->InitWithNativePath(filename);
613 if (NS_FAILED(rv)) {
614 MOZ_CRASH();
615 }
616 nsCOMPtr<nsIFileOutputStream> of =
617 do_CreateInstance("@mozilla.org/network/file-output-stream;1");
618 of->Init(file, -1, -1, 0);
619 uint32_t sz;
620 of->Write(aResult.get(), aResult.Length(), &sz);
621 of->Close();
622
623 promise->MaybeResolveWithUndefined();
624 },
625 [promise](nsresult aRv) { promise->MaybeReject(aRv); });
626
627 promise.forget(aPromise);
628 return NS_OK;
629 }
630
631 NS_IMETHODIMP
GetSymbolTable(const nsACString & aDebugPath,const nsACString & aBreakpadID,JSContext * aCx,Promise ** aPromise)632 nsProfiler::GetSymbolTable(const nsACString& aDebugPath,
633 const nsACString& aBreakpadID, JSContext* aCx,
634 Promise** aPromise) {
635 MOZ_ASSERT(NS_IsMainThread());
636
637 if (NS_WARN_IF(!aCx)) {
638 return NS_ERROR_FAILURE;
639 }
640
641 nsIGlobalObject* globalObject =
642 xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
643
644 if (NS_WARN_IF(!globalObject)) {
645 return NS_ERROR_FAILURE;
646 }
647
648 ErrorResult result;
649 RefPtr<Promise> promise = Promise::Create(globalObject, result);
650 if (NS_WARN_IF(result.Failed())) {
651 return result.StealNSResult();
652 }
653
654 GetSymbolTableMozPromise(aDebugPath, aBreakpadID)
655 ->Then(
656 GetMainThreadSerialEventTarget(), __func__,
657 [promise](const SymbolTable& aSymbolTable) {
658 AutoJSAPI jsapi;
659 if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
660 // We're really hosed if we can't get a JS context for some
661 // reason.
662 promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
663 return;
664 }
665
666 JSContext* cx = jsapi.cx();
667
668 JS::RootedObject addrsArray(
669 cx, dom::Uint32Array::Create(cx, aSymbolTable.mAddrs.Length(),
670 aSymbolTable.mAddrs.Elements()));
671 JS::RootedObject indexArray(
672 cx, dom::Uint32Array::Create(cx, aSymbolTable.mIndex.Length(),
673 aSymbolTable.mIndex.Elements()));
674 JS::RootedObject bufferArray(
675 cx, dom::Uint8Array::Create(cx, aSymbolTable.mBuffer.Length(),
676 aSymbolTable.mBuffer.Elements()));
677
678 if (addrsArray && indexArray && bufferArray) {
679 JS::RootedObject tuple(cx, JS::NewArrayObject(cx, 3));
680 JS_SetElement(cx, tuple, 0, addrsArray);
681 JS_SetElement(cx, tuple, 1, indexArray);
682 JS_SetElement(cx, tuple, 2, bufferArray);
683 promise->MaybeResolve(tuple);
684 } else {
685 promise->MaybeReject(NS_ERROR_FAILURE);
686 }
687 },
688 [promise](nsresult aRv) { promise->MaybeReject(aRv); });
689
690 promise.forget(aPromise);
691 return NS_OK;
692 }
693
694 NS_IMETHODIMP
GetElapsedTime(double * aElapsedTime)695 nsProfiler::GetElapsedTime(double* aElapsedTime) {
696 *aElapsedTime = profiler_time();
697 return NS_OK;
698 }
699
700 NS_IMETHODIMP
IsActive(bool * aIsActive)701 nsProfiler::IsActive(bool* aIsActive) {
702 *aIsActive = profiler_is_active();
703 return NS_OK;
704 }
705
GetArrayOfStringsForFeatures(uint32_t aFeatures,nsTArray<nsCString> & aFeatureList)706 static void GetArrayOfStringsForFeatures(uint32_t aFeatures,
707 nsTArray<nsCString>& aFeatureList) {
708 #define COUNT_IF_SET(n_, str_, Name_, desc_) \
709 if (ProfilerFeature::Has##Name_(aFeatures)) { \
710 len++; \
711 }
712
713 // Count the number of features in use.
714 uint32_t len = 0;
715 PROFILER_FOR_EACH_FEATURE(COUNT_IF_SET)
716
717 #undef COUNT_IF_SET
718
719 aFeatureList.SetCapacity(len);
720
721 #define DUP_IF_SET(n_, str_, Name_, desc_) \
722 if (ProfilerFeature::Has##Name_(aFeatures)) { \
723 aFeatureList.AppendElement(str_); \
724 }
725
726 // Insert the strings for the features in use.
727 PROFILER_FOR_EACH_FEATURE(DUP_IF_SET)
728
729 #undef DUP_IF_SET
730 }
731
732 NS_IMETHODIMP
GetFeatures(nsTArray<nsCString> & aFeatureList)733 nsProfiler::GetFeatures(nsTArray<nsCString>& aFeatureList) {
734 uint32_t features = profiler_get_available_features();
735 GetArrayOfStringsForFeatures(features, aFeatureList);
736 return NS_OK;
737 }
738
739 NS_IMETHODIMP
GetAllFeatures(nsTArray<nsCString> & aFeatureList)740 nsProfiler::GetAllFeatures(nsTArray<nsCString>& aFeatureList) {
741 GetArrayOfStringsForFeatures((uint32_t)-1, aFeatureList);
742 return NS_OK;
743 }
744
745 NS_IMETHODIMP
GetBufferInfo(uint32_t * aCurrentPosition,uint32_t * aTotalSize,uint32_t * aGeneration)746 nsProfiler::GetBufferInfo(uint32_t* aCurrentPosition, uint32_t* aTotalSize,
747 uint32_t* aGeneration) {
748 MOZ_ASSERT(aCurrentPosition);
749 MOZ_ASSERT(aTotalSize);
750 MOZ_ASSERT(aGeneration);
751 Maybe<ProfilerBufferInfo> info = profiler_get_buffer_info();
752 if (info) {
753 *aCurrentPosition = info->mRangeEnd % info->mEntryCount;
754 *aTotalSize = info->mEntryCount;
755 *aGeneration = info->mRangeEnd / info->mEntryCount;
756 } else {
757 *aCurrentPosition = 0;
758 *aTotalSize = 0;
759 *aGeneration = 0;
760 }
761 return NS_OK;
762 }
763
GatheringTimerCallback(nsITimer * aTimer,void * aClosure)764 /* static */ void nsProfiler::GatheringTimerCallback(nsITimer* aTimer,
765 void* aClosure) {
766 MOZ_RELEASE_ASSERT(NS_IsMainThread());
767 nsCOMPtr<nsIProfiler> profiler(
768 do_GetService("@mozilla.org/tools/profiler;1"));
769 if (!profiler) {
770 // No (more) profiler service.
771 return;
772 }
773 nsProfiler* self = static_cast<nsProfiler*>(profiler.get());
774 if (self != aClosure) {
775 // Different service object!?
776 return;
777 }
778 if (aTimer != self->mGatheringTimer) {
779 // This timer was cancelled after this callback was queued.
780 return;
781 }
782 self->mGatheringTimer = nullptr;
783 if (!profiler_is_active() || !self->mGathering) {
784 // Not gathering anymore.
785 return;
786 }
787 NS_WARNING("Profiler failed to gather profiles from all sub-processes");
788 // We have really reached a timeout while gathering, finish now.
789 // TODO: Add information about missing processes.
790 self->FinishGathering();
791 }
792
GatheredOOPProfile(const nsACString & aProfile)793 void nsProfiler::GatheredOOPProfile(const nsACString& aProfile) {
794 MOZ_RELEASE_ASSERT(NS_IsMainThread());
795
796 if (!profiler_is_active()) {
797 return;
798 }
799
800 if (!mGathering) {
801 // If we're not actively gathering, then we don't actually care that we
802 // gathered a profile here. This can happen for processes that exit while
803 // profiling.
804 return;
805 }
806
807 MOZ_RELEASE_ASSERT(mWriter.isSome(),
808 "Should always have a writer if mGathering is true");
809
810 if (!aProfile.IsEmpty()) {
811 // TODO: Remove PromiseFlatCString, see bug 1657033.
812 mWriter->Splice(PromiseFlatCString(aProfile));
813 }
814
815 mPendingProfiles--;
816
817 if (mPendingProfiles == 0) {
818 // We've got all of the async profiles now. Let's
819 // finish off the profile and resolve the Promise.
820 FinishGathering();
821 }
822
823 // Not finished yet, restart the timer to let any remaining child enough time
824 // to do their profile-streaming.
825 if (mGatheringTimer) {
826 uint32_t delayMs = 0;
827 const nsresult r = mGatheringTimer->GetDelay(&delayMs);
828 mGatheringTimer->Cancel();
829 mGatheringTimer = nullptr;
830 if (NS_SUCCEEDED(r) && delayMs != 0) {
831 Unused << NS_NewTimerWithFuncCallback(
832 getter_AddRefs(mGatheringTimer), GatheringTimerCallback, this,
833 delayMs, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "",
834 GetMainThreadSerialEventTarget());
835 }
836 }
837 }
838
ReceiveShutdownProfile(const nsCString & aProfile)839 void nsProfiler::ReceiveShutdownProfile(const nsCString& aProfile) {
840 MOZ_RELEASE_ASSERT(NS_IsMainThread());
841 profiler_received_exit_profile(aProfile);
842 }
843
StartGathering(double aSinceTime)844 RefPtr<nsProfiler::GatheringPromise> nsProfiler::StartGathering(
845 double aSinceTime) {
846 MOZ_RELEASE_ASSERT(NS_IsMainThread());
847
848 if (mGathering) {
849 // If we're already gathering, return a rejected promise - this isn't
850 // going to end well.
851 return GatheringPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
852 }
853
854 mGathering = true;
855
856 if (mGatheringTimer) {
857 mGatheringTimer->Cancel();
858 mGatheringTimer = nullptr;
859 }
860
861 // Request profiles from the other processes. This will trigger asynchronous
862 // calls to ProfileGatherer::GatheredOOPProfile as the profiles arrive.
863 //
864 // Do this before the call to profiler_stream_json_for_this_process() because
865 // that call is slow and we want to let the other processes grab their
866 // profiles as soon as possible.
867 nsTArray<RefPtr<ProfilerParent::SingleProcessProfilePromise>> profiles =
868 ProfilerParent::GatherProfiles();
869
870 mWriter.emplace();
871
872 TimeStamp streamingStart = TimeStamp::NowUnfuzzed();
873
874 UniquePtr<ProfilerCodeAddressService> service =
875 profiler_code_address_service_for_presymbolication();
876
877 // Start building up the JSON result and grab the profile from this process.
878 mWriter->Start();
879 if (!profiler_stream_json_for_this_process(*mWriter, aSinceTime,
880 /* aIsShuttingDown */ false,
881 service.get())) {
882 // The profiler is inactive. This either means that it was inactive even
883 // at the time that ProfileGatherer::Start() was called, or that it was
884 // stopped on a different thread since that call. Either way, we need to
885 // reject the promise and stop gathering.
886 return GatheringPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
887 }
888
889 mWriter->StartArrayProperty("processes");
890
891 // If we have any process exit profiles, add them immediately.
892 Vector<nsCString> exitProfiles = profiler_move_exit_profiles();
893 for (auto& exitProfile : exitProfiles) {
894 if (!exitProfile.IsEmpty()) {
895 mWriter->Splice(exitProfile);
896 }
897 }
898
899 mPromiseHolder.emplace();
900 RefPtr<GatheringPromise> promise = mPromiseHolder->Ensure(__func__);
901
902 // Keep the array property "processes" and the root object in mWriter open
903 // until FinishGathering() is called. As profiles from the other processes
904 // come in, they will be inserted and end up in the right spot.
905 // FinishGathering() will close the array and the root object.
906
907 mPendingProfiles = profiles.Length();
908 if (mPendingProfiles != 0) {
909 // There *are* pending profiles, let's add handlers for their promises.
910
911 // We want a reasonable timeout value while waiting for child profiles.
912 // We know how long the parent process took to serialize its profile:
913 const uint32_t parentTimeMs = static_cast<uint32_t>(
914 (TimeStamp::NowUnfuzzed() - streamingStart).ToMilliseconds());
915 // We will multiply this by the number of children, to cover the worst case
916 // where all processes take the same time, but because they are working in
917 // parallel on a potential single CPU, they all finish around the same later
918 // time.
919 // And multiply again by 2, for the extra processing and comms, and other
920 // work that may happen.
921 const uint32_t parentToChildrenFactor = mPendingProfiles * 2;
922 // And we add a number seconds by default. In some lopsided cases, the
923 // parent-to-child serializing ratio could be much greater than expected,
924 // so the user could force it to be a bigger number if needed.
925 uint32_t childTimeoutS = Preferences::GetUint(
926 "devtools.performance.recording.child.timeout_s", 0u);
927 if (childTimeoutS == 0) {
928 // If absent or 0, use hard-coded default.
929 childTimeoutS = 1;
930 }
931 // And this gives us a timeout value. The timer will be restarted after we
932 // receive each response.
933 // TODO: Instead of a timeout to cover the whole request-to-response time,
934 // there should be more of a continuous dialog between processes, to only
935 // give up if some processes are really unresponsive. See bug 1673513.
936 const uint32_t streamingTimeoutMs =
937 parentTimeMs * parentToChildrenFactor + childTimeoutS * 1000;
938 Unused << NS_NewTimerWithFuncCallback(
939 getter_AddRefs(mGatheringTimer), GatheringTimerCallback, this,
940 streamingTimeoutMs, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "",
941 GetMainThreadSerialEventTarget());
942
943 for (auto profile : profiles) {
944 profile->Then(
945 GetMainThreadSerialEventTarget(), __func__,
946 [self = RefPtr<nsProfiler>(this)](mozilla::ipc::Shmem&& aResult) {
947 const nsDependentCSubstring profileString(aResult.get<char>(),
948 aResult.Size<char>() - 1);
949 self->GatheredOOPProfile(profileString);
950 },
951 [self =
952 RefPtr<nsProfiler>(this)](ipc::ResponseRejectReason&& aReason) {
953 self->GatheredOOPProfile(""_ns);
954 });
955 }
956 } else {
957 // There are no pending profiles, we're already done.
958 FinishGathering();
959 }
960
961 return promise;
962 }
963
GetSymbolTableMozPromise(const nsACString & aDebugPath,const nsACString & aBreakpadID)964 RefPtr<nsProfiler::SymbolTablePromise> nsProfiler::GetSymbolTableMozPromise(
965 const nsACString& aDebugPath, const nsACString& aBreakpadID) {
966 MozPromiseHolder<SymbolTablePromise> promiseHolder;
967 RefPtr<SymbolTablePromise> promise = promiseHolder.Ensure(__func__);
968
969 if (!mSymbolTableThread) {
970 nsresult rv = NS_NewNamedThread("ProfSymbolTable",
971 getter_AddRefs(mSymbolTableThread));
972 if (NS_WARN_IF(NS_FAILED(rv))) {
973 promiseHolder.Reject(NS_ERROR_FAILURE, __func__);
974 return promise;
975 }
976 }
977
978 nsresult rv = mSymbolTableThread->Dispatch(NS_NewRunnableFunction(
979 "nsProfiler::GetSymbolTableMozPromise runnable on ProfSymbolTable thread",
980 [promiseHolder = std::move(promiseHolder),
981 debugPath = nsCString(aDebugPath),
982 breakpadID = nsCString(aBreakpadID)]() mutable {
983 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("profiler_get_symbol_table",
984 OTHER, debugPath);
985 SymbolTable symbolTable;
986 bool succeeded = profiler_get_symbol_table(
987 debugPath.get(), breakpadID.get(), &symbolTable);
988 if (succeeded) {
989 promiseHolder.Resolve(std::move(symbolTable), __func__);
990 } else {
991 promiseHolder.Reject(NS_ERROR_FAILURE, __func__);
992 }
993 }));
994
995 if (NS_WARN_IF(NS_FAILED(rv))) {
996 // Get-symbol task was not dispatched and therefore won't fulfill the
997 // promise, we must reject the promise now.
998 promiseHolder.Reject(NS_ERROR_FAILURE, __func__);
999 }
1000
1001 return promise;
1002 }
1003
FinishGathering()1004 void nsProfiler::FinishGathering() {
1005 MOZ_RELEASE_ASSERT(NS_IsMainThread());
1006 MOZ_RELEASE_ASSERT(mWriter.isSome());
1007 MOZ_RELEASE_ASSERT(mPromiseHolder.isSome());
1008
1009 // Close the "processes" array property.
1010 mWriter->EndArray();
1011
1012 // Close the root object of the generated JSON.
1013 mWriter->End();
1014
1015 UniquePtr<char[]> buf = mWriter->ChunkedWriteFunc().CopyData();
1016 size_t len = strlen(buf.get());
1017 nsCString result;
1018 result.Adopt(buf.release(), len);
1019 mPromiseHolder->Resolve(std::move(result), __func__);
1020
1021 ResetGathering();
1022 }
1023
ResetGathering()1024 void nsProfiler::ResetGathering() {
1025 // If we have an unfulfilled Promise in flight, we should reject it before
1026 // destroying the promise holder.
1027 if (mPromiseHolder.isSome()) {
1028 mPromiseHolder->RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
1029 mPromiseHolder.reset();
1030 }
1031 mPendingProfiles = 0;
1032 mGathering = false;
1033 if (mGatheringTimer) {
1034 mGatheringTimer->Cancel();
1035 mGatheringTimer = nullptr;
1036 }
1037 mWriter.reset();
1038 }
1039