diff --git a/dom/plugins/ipc/PPluginModule.ipdl b/dom/plugins/ipc/PPluginModule.ipdl index 5d91bf081263..2251679e3710 100644 --- a/dom/plugins/ipc/PPluginModule.ipdl +++ b/dom/plugins/ipc/PPluginModule.ipdl @@ -84,6 +84,9 @@ child: rpc PCrashReporter() returns (NativeThreadId tid, uint32_t processType); + rpc GeckoGetProfile() + returns (nsCString aProfile); + parent: /** * This message is only used on X11 platforms. diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index 6be5aedf6ffa..f4d8dd2a6de7 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -56,6 +56,8 @@ #include "PluginUtilsOSX.h" #endif +#include "GeckoProfiler.h" + using namespace mozilla; using namespace mozilla::plugins; using mozilla::dom::CrashReporterChild; @@ -2425,3 +2427,16 @@ PluginModuleChild::ProcessNativeEvents() { CallProcessSomeEvents(); } #endif + +bool +PluginModuleChild::AnswerGeckoGetProfile(nsCString* aProfile) { + char* profile = profiler_get_profile(); + if (profile != NULL) { + *aProfile = nsCString(profile, strlen(profile)); + free(profile); + } else { + *aProfile = nsCString("", 0); + } + return true; +} + diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h index e8440fe39266..d47a6bd40e43 100644 --- a/dom/plugins/ipc/PluginModuleChild.h +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -160,6 +160,9 @@ protected: virtual bool RecvProcessNativeEventsInRPCCall() MOZ_OVERRIDE; + virtual bool + AnswerGeckoGetProfile(nsCString* aProfile); + public: PluginModuleChild(); virtual ~PluginModuleChild(); diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 732558693a03..6d903bdd6c88 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -38,7 +38,9 @@ #include "PluginHangUIParent.h" #include "mozilla/widget/AudioSession.h" #endif -#include "GeckoProfiler.h" +#include "nsIProfileSaveEvent.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" using base::KillProcess; @@ -138,12 +140,16 @@ PluginModuleParent::PluginModuleParent(const char* aFilePath) Preferences::RegisterCallback(TimeoutChanged, kHangUITimeoutPref, this); Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); #endif + + InitPluginProfiling(); } PluginModuleParent::~PluginModuleParent() { NS_ASSERTION(OkToCleanup(), "unsafe destruction"); + ShutdownPluginProfiling(); + if (!mShutdown) { NS_WARNING("Plugin host deleted the module without shutting down."); NPError err; @@ -1710,3 +1716,56 @@ PluginModuleParent::OnCrash(DWORD processID) } #endif // MOZ_CRASHREPORTER_INJECTOR + +class PluginProfilerObserver MOZ_FINAL : public nsIObserver, + public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit PluginProfilerObserver(PluginModuleParent* pmp) + : mPmp(pmp) + {} + +private: + PluginModuleParent* mPmp; +}; + +NS_IMPL_ISUPPORTS2(PluginProfilerObserver, nsIObserver, nsISupportsWeakReference) + +NS_IMETHODIMP +PluginProfilerObserver::Observe(nsISupports *aSubject, + const char *aTopic, + const PRUnichar *aData) +{ + nsCOMPtr pse = do_QueryInterface(aSubject); + if (pse) { + nsCString result; + bool success = mPmp->CallGeckoGetProfile(&result); + if (success && !result.IsEmpty()) { + pse->AddSubProfile(result.get()); + } + } + return NS_OK; +} + +void +PluginModuleParent::InitPluginProfiling() +{ + nsCOMPtr observerService = mozilla::services::GetObserverService(); + if (observerService) { + mProfilerObserver = new PluginProfilerObserver(this); + observerService->AddObserver(mProfilerObserver, "profiler-subprocess", false); + } +} + +void +PluginModuleParent::ShutdownPluginProfiling() +{ + nsCOMPtr observerService = mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess"); + } +} + diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h index b5203ac1cd37..b72ae519028f 100644 --- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -301,6 +301,9 @@ private: static int TimeoutChanged(const char* aPref, void* aModule); void NotifyPluginCrashed(); + void InitPluginProfiling(); + void ShutdownPluginProfiling(); + PluginProcessParent* mSubprocess; // the plugin thread in mSubprocess NativeThreadId mPluginThread; @@ -314,6 +317,7 @@ private: nsString mPluginDumpID; nsString mBrowserDumpID; nsString mHangID; + nsRefPtr mProfilerObserver; #ifdef XP_WIN InfallibleTArray mPluginCpuUsageOnHang; PluginHangUIParent *mHangUIParent; diff --git a/tools/profiler/GeckoProfilerImpl.h b/tools/profiler/GeckoProfilerImpl.h index 11f092de9721..6a38507a439f 100644 --- a/tools/profiler/GeckoProfilerImpl.h +++ b/tools/profiler/GeckoProfilerImpl.h @@ -19,7 +19,7 @@ #include "jsfriendapi.h" #include "GeckoProfilerFunc.h" #include "PseudoStack.h" - +#include "nsISupports.h" /* QT has a #define for the word "slots" and jsfriendapi.h has a struct with * this variable name, causing compilation problems. Alleviate this for now by diff --git a/tools/profiler/Makefile.in b/tools/profiler/Makefile.in index d7e1b9bfbe4a..f9fa46ce5954 100644 --- a/tools/profiler/Makefile.in +++ b/tools/profiler/Makefile.in @@ -42,6 +42,7 @@ CPPSRCS = \ nsProfilerFactory.cpp \ nsProfiler.cpp \ TableTicker.cpp \ + SaveProfileTask.cpp \ BreakpadSampler.cpp \ UnwinderThread2.cpp \ ProfileEntry.cpp \ diff --git a/tools/profiler/ProfileEntry.cpp b/tools/profiler/ProfileEntry.cpp index d3608cace5fc..cc7a2e7cab81 100644 --- a/tools/profiler/ProfileEntry.cpp +++ b/tools/profiler/ProfileEntry.cpp @@ -7,6 +7,7 @@ #include "GeckoProfilerImpl.h" #include "platform.h" #include "nsThreadUtils.h" +#include "nsXULAppAPI.h" // JSON #include "JSObjectBuilder.h" @@ -307,8 +308,15 @@ JSCustomObject* ThreadProfile::ToJSObject(JSContext *aCx) } void ThreadProfile::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile) { + // Thread meta data - b.DefineProperty(profile, "name", mName); + if (XRE_GetProcessType() == GeckoProcessType_Plugin) { + // TODO Add the proper plugin name + b.DefineProperty(profile, "name", "Plugin"); + } else { + b.DefineProperty(profile, "name", mName); + } + b.DefineProperty(profile, "tid", mThreadId); JSCustomArray *samples = b.CreateArray(); diff --git a/tools/profiler/SaveProfileTask.cpp b/tools/profiler/SaveProfileTask.cpp new file mode 100644 index 000000000000..cd9ba3ad8d10 --- /dev/null +++ b/tools/profiler/SaveProfileTask.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SaveProfileTask.h" +#include "GeckoProfilerImpl.h" + +static JSBool +WriteCallback(const jschar *buf, uint32_t len, void *data) +{ + std::ofstream& stream = *static_cast(data); + nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len); + stream << profile.Data(); + return JS_TRUE; +} + +nsresult +SaveProfileTask::Run() { + // Get file path +#if defined(SPS_PLAT_arm_android) && !defined(MOZ_WIDGET_GONK) + nsCString tmpPath; + tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid()); +#else + nsCOMPtr tmpFile; + nsAutoCString tmpPath; + if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) { + LOG("Failed to find temporary directory."); + return NS_ERROR_FAILURE; + } + tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid()); + + nsresult rv = tmpFile->AppendNative(tmpPath); + if (NS_FAILED(rv)) + return rv; + + rv = tmpFile->GetNativePath(tmpPath); + if (NS_FAILED(rv)) + return rv; +#endif + + // Create a JSContext to run a JSObjectBuilder :( + // Based on XPCShellEnvironment + JSRuntime *rt; + JSContext *cx; + nsCOMPtr rtsvc + = do_GetService("@mozilla.org/js/xpc/RuntimeService;1"); + if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) { + LOG("failed to get RuntimeService"); + return NS_ERROR_FAILURE;; + } + + cx = JS_NewContext(rt, 8192); + if (!cx) { + LOG("Failed to get context"); + return NS_ERROR_FAILURE; + } + + { + JSAutoRequest ar(cx); + static JSClass c = { + "global", JSCLASS_GLOBAL_FLAGS, + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub + }; + JSObject *obj = JS_NewGlobalObject(cx, &c, NULL); + + std::ofstream stream; + stream.open(tmpPath.get()); + if (stream.is_open()) { + JSAutoCompartment autoComp(cx, obj); + JSObject* profileObj = profiler_get_profile_jsobject(cx); + jsval val = OBJECT_TO_JSVAL(profileObj); + JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream); + stream.close(); + LOGF("Saved to %s", tmpPath.get()); + } else { + LOG("Fail to open profile log file."); + } + } + JS_EndRequest(cx); + JS_DestroyContext(cx); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS1(ProfileSaveEvent, nsIProfileSaveEvent) + +nsresult +ProfileSaveEvent::AddSubProfile(const char* aProfile) { + mFunc(aProfile, mClosure); + return NS_OK; +} + diff --git a/tools/profiler/SaveProfileTask.h b/tools/profiler/SaveProfileTask.h index afe8a0f94261..9b0dd60f19ea 100644 --- a/tools/profiler/SaveProfileTask.h +++ b/tools/profiler/SaveProfileTask.h @@ -12,7 +12,12 @@ #include "nsDirectoryServiceUtils.h" #include "nsDirectoryServiceDefs.h" #include "nsXULAppAPI.h" +#include "jsfriendapi.h" #include "nsIJSRuntimeService.h" +#include "nsIProfileSaveEvent.h" + +#include +#include #ifdef XP_WIN #include @@ -21,15 +26,6 @@ #include #endif -static JSBool -WriteCallback(const jschar *buf, uint32_t len, void *data) -{ - std::ofstream& stream = *static_cast(data); - nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len); - stream << profile.Data(); - return JS_TRUE; -} - /** * This is an event used to save the profile on the main thread * to be sure that it is not being modified while saving. @@ -38,73 +34,25 @@ class SaveProfileTask : public nsRunnable { public: SaveProfileTask() {} - NS_IMETHOD Run() { - // Get file path -# if defined(SPS_PLAT_arm_android) && !defined(MOZ_WIDGET_GONK) - nsCString tmpPath; - tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid()); -# else - nsCOMPtr tmpFile; - nsAutoCString tmpPath; - if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) { - LOG("Failed to find temporary directory."); - return NS_ERROR_FAILURE; - } - tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid()); + NS_IMETHOD Run(); +}; - nsresult rv = tmpFile->AppendNative(tmpPath); - if (NS_FAILED(rv)) - return rv; +class ProfileSaveEvent MOZ_FINAL : public nsIProfileSaveEvent { +public: + typedef void (*AddSubProfileFunc)(const char* aProfile, void* aClosure); + NS_DECL_ISUPPORTS - rv = tmpFile->GetNativePath(tmpPath); - if (NS_FAILED(rv)) - return rv; -# endif + ProfileSaveEvent(AddSubProfileFunc aFunc, void* aClosure) + : mFunc(aFunc) + , mClosure(aClosure) + {} - // Create a JSContext to run a JSObjectBuilder :( - // Based on XPCShellEnvironment - JSRuntime *rt; - JSContext *cx; - nsCOMPtr rtsvc - = do_GetService("@mozilla.org/js/xpc/RuntimeService;1"); - if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) { - LOG("failed to get RuntimeService"); - return NS_ERROR_FAILURE;; - } + ~ProfileSaveEvent() {} - cx = JS_NewContext(rt, 8192); - if (!cx) { - LOG("Failed to get context"); - return NS_ERROR_FAILURE; - } - - { - JSAutoRequest ar(cx); - static JSClass c = { - "global", JSCLASS_GLOBAL_FLAGS, - JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, - JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub - }; - JSObject *obj = JS_NewGlobalObject(cx, &c, NULL); - - std::ofstream stream; - stream.open(tmpPath.get()); - if (stream.is_open()) { - JSAutoCompartment autoComp(cx, obj); - JSObject* profileObj = profiler_get_profile_jsobject(cx); - jsval val = OBJECT_TO_JSVAL(profileObj); - JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream); - stream.close(); - LOGF("Saved to %s", tmpPath.get()); - } else { - LOGF("Fail to open profile log file '%s'.", tmpPath.get()); - } - } - JS_EndRequest(cx); - JS_DestroyContext(cx); - - return NS_OK; - } + NS_IMETHOD AddSubProfile(const char* aProfile); +private: + AddSubProfileFunc mFunc; + void* mClosure; }; #endif diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index 67441071e8d5..76507731d84f 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -16,6 +16,7 @@ #include "shared-libraries.h" #include "mozilla/StackWalk.h" #include "TableTicker.h" +#include "nsXULAppAPI.h" // JSON #include "JSObjectBuilder.h" @@ -164,6 +165,20 @@ JSObject* TableTicker::ToJSObject(JSContext *aCx) return jsProfile; } +struct SubprocessClosure { + JSAObjectBuilder* mBuilder; + JSCustomArray* mThreads; +}; + +void SubProcessCallback(const char* aProfile, void* aClosure) +{ + // Called by the observer to get their profile data included + // as a sub profile + SubprocessClosure* closure = (SubprocessClosure*)aClosure; + + closure->mBuilder->ArrayPush(closure->mThreads, aProfile); +} + #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) static JSCustomObject* BuildJavaThreadJSObject(JSAObjectBuilder& b) @@ -255,6 +270,17 @@ void TableTicker::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile) #endif SetPaused(false); + + // Send a event asking any subprocesses (plugins) to + // give us their information + SubprocessClosure closure; + closure.mBuilder = &b; + closure.mThreads = threads; + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + nsRefPtr pse = new ProfileSaveEvent(SubProcessCallback, &closure); + os->NotifyObservers(pse, "profiler-subprocess", nullptr); + } } // END SaveProfileTask et al diff --git a/tools/profiler/moz.build b/tools/profiler/moz.build index 4ed5e658a76b..f8dc0acd14e8 100644 --- a/tools/profiler/moz.build +++ b/tools/profiler/moz.build @@ -7,6 +7,7 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']: MODULE = 'profiler' XPIDL_SOURCES += [ + 'nsIProfileSaveEvent.idl', 'nsIProfiler.idl', ] EXPORTS += [ diff --git a/tools/profiler/nsIProfileSaveEvent.idl b/tools/profiler/nsIProfileSaveEvent.idl new file mode 100644 index 000000000000..c2c4bed021f8 --- /dev/null +++ b/tools/profiler/nsIProfileSaveEvent.idl @@ -0,0 +1,19 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[uuid(f5ad0830-e178-41f9-b253-db9b4fae4cb3)] +interface nsIProfileSaveEvent : nsISupports +{ + /** + * Call this method when observing this event to include + * a sub profile origining from an external source such + * as a non native thread or another process. + */ + void AddSubProfile(in string aMarker); +}; + + diff --git a/tools/profiler/platform.cpp b/tools/profiler/platform.cpp index f360b314cdc7..b20f10ff3203 100644 --- a/tools/profiler/platform.cpp +++ b/tools/profiler/platform.cpp @@ -350,15 +350,10 @@ char* mozilla_sampler_get_profile() return NULL; } - std::stringstream profile; - t->SetPaused(true); - profile << *(t->GetPrimaryThreadProfile()); - t->SetPaused(false); - - std::string profileString = profile.str(); - char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) ); - strcpy(rtn, profileString.c_str()); - return rtn; + std::stringstream stream; + t->ToStreamAsJSON(stream); + char* profile = strdup(stream.str().c_str()); + return profile; } JSObject *mozilla_sampler_get_profile_data(JSContext *aCx)