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