Files
tubestation/toolkit/components/backgroundhangmonitor/HangDetails.cpp

345 lines
9.4 KiB
C++

#include "HangDetails.h"
#include "nsIHangDetails.h"
#include "nsPrintfCString.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/Unused.h"
#include "mozilla/GfxMessageUtils.h" // For ParamTraits<GeckoProcessType>
namespace mozilla {
NS_IMETHODIMP
nsHangDetails::GetDuration(uint32_t* aDuration)
{
*aDuration = mDetails.mDuration;
return NS_OK;
}
NS_IMETHODIMP
nsHangDetails::GetThread(nsACString& aName)
{
aName.Assign(mDetails.mThreadName);
return NS_OK;
}
NS_IMETHODIMP
nsHangDetails::GetRunnableName(nsACString& aRunnableName)
{
aRunnableName.Assign(mDetails.mRunnableName);
return NS_OK;
}
NS_IMETHODIMP
nsHangDetails::GetProcess(nsACString& aName)
{
aName.AssignASCII(XRE_ChildProcessTypeToString(mDetails.mProcess));
return NS_OK;
}
NS_IMETHODIMP
nsHangDetails::GetRemoteType(nsAString& aName)
{
aName.Assign(mDetails.mRemoteType);
return NS_OK;
}
NS_IMETHODIMP
nsHangDetails::GetAnnotations(JSContext* aCx, JS::MutableHandleValue aVal)
{
// We create an object with { "key" : "value" } string pairs for each item in
// our annotations object.
JS::RootedObject jsAnnotation(aCx, JS_NewPlainObject(aCx));
if (!jsAnnotation) {
return NS_ERROR_OUT_OF_MEMORY;
}
for (auto& annot : mDetails.mAnnotations) {
JSString* jsString = JS_NewUCStringCopyN(aCx, annot.mValue.get(), annot.mValue.Length());
if (!jsString) {
return NS_ERROR_OUT_OF_MEMORY;
}
JS::RootedValue jsValue(aCx);
jsValue.setString(jsString);
if (!JS_DefineUCProperty(aCx, jsAnnotation, annot.mName.get(), annot.mName.Length(),
jsValue, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
aVal.setObject(*jsAnnotation);
return NS_OK;
}
namespace {
nsresult
StringFrame(JSContext* aCx,
JS::RootedObject& aTarget,
size_t aIndex,
const char* aString)
{
JSString* jsString = JS_NewStringCopyZ(aCx, aString);
if (!jsString) {
return NS_ERROR_OUT_OF_MEMORY;
}
JS::RootedString string(aCx, jsString);
if (!string) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (!JS_DefineElement(aCx, aTarget, aIndex, string, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
} // anonymous namespace
NS_IMETHODIMP
nsHangDetails::GetStack(JSContext* aCx, JS::MutableHandle<JS::Value> aVal)
{
JS::RootedObject ret(aCx, JS_NewArrayObject(aCx, mDetails.mStack.length()));
if (!ret) {
return NS_ERROR_OUT_OF_MEMORY;
}
for (size_t i = 0; i < mDetails.mStack.length(); ++i) {
const HangStack::Frame& frame = mDetails.mStack[i];
switch (frame.GetKind()) {
case HangStack::Frame::Kind::STRING: {
nsresult rv = StringFrame(aCx, ret, i, frame.AsString());
NS_ENSURE_SUCCESS(rv, rv);
break;
}
case HangStack::Frame::Kind::MODOFFSET: {
JS::RootedObject jsFrame(aCx, JS_NewArrayObject(aCx, 2));
if (!jsFrame) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (!JS_DefineElement(aCx, jsFrame, 0, frame.AsModOffset().mModule, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsPrintfCString hexString("%" PRIxPTR, (uintptr_t)frame.AsModOffset().mOffset);
JS::RootedString hex(aCx, JS_NewStringCopyZ(aCx, hexString.get()));
if (!hex || !JS_DefineElement(aCx, jsFrame, 1, hex, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (!JS_DefineElement(aCx, ret, i, jsFrame, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
break;
}
case HangStack::Frame::Kind::PC: {
JS::RootedObject jsFrame(aCx, JS_NewArrayObject(aCx, 2));
if (!jsFrame) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (!JS_DefineElement(aCx, jsFrame, 0, -1, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsPrintfCString hexString("%" PRIxPTR, frame.AsPC());
JS::RootedString hex(aCx, JS_NewStringCopyZ(aCx, hexString.get()));
if (!hex || !JS_DefineElement(aCx, jsFrame, 1, hex, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (!JS_DefineElement(aCx, ret, i, jsFrame, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
break;
}
case HangStack::Frame::Kind::CONTENT: {
nsresult rv = StringFrame(aCx, ret, i, "(content script)");
NS_ENSURE_SUCCESS(rv, rv);
break;
}
case HangStack::Frame::Kind::JIT: {
nsresult rv = StringFrame(aCx, ret, i, "(jit frame)");
NS_ENSURE_SUCCESS(rv, rv);
break;
}
case HangStack::Frame::Kind::WASM: {
nsresult rv = StringFrame(aCx, ret, i, "(wasm)");
NS_ENSURE_SUCCESS(rv, rv);
break;
}
case HangStack::Frame::Kind::SUPPRESSED: {
nsresult rv = StringFrame(aCx, ret, i, "(profiling suppressed)");
NS_ENSURE_SUCCESS(rv, rv);
break;
}
default:
MOZ_ASSERT_UNREACHABLE("Invalid variant");
break;
}
}
aVal.setObject(*ret);
return NS_OK;
}
NS_IMETHODIMP
nsHangDetails::GetModules(JSContext* aCx, JS::MutableHandleValue aVal)
{
auto& modules = mDetails.mStack.GetModules();
size_t length = modules.Length();
JS::RootedObject retObj(aCx, JS_NewArrayObject(aCx, length));
if (!retObj) {
return NS_ERROR_OUT_OF_MEMORY;
}
for (size_t i = 0; i < length; ++i) {
const HangStack::Module& module = modules[i];
JS::RootedObject jsModule(aCx, JS_NewArrayObject(aCx, 2));
if (!jsModule) {
return NS_ERROR_OUT_OF_MEMORY;
}
JS::RootedString name(aCx, JS_NewUCStringCopyN(aCx, module.mName.BeginReading(), module.mName.Length()));
if (!JS_DefineElement(aCx, jsModule, 0, name, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
JS::RootedString breakpadId(aCx, JS_NewStringCopyN(aCx, module.mBreakpadId.BeginReading(), module.mBreakpadId.Length()));
if (!JS_DefineElement(aCx, jsModule, 1, breakpadId, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (!JS_DefineElement(aCx, retObj, i, jsModule, JSPROP_ENUMERATE)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
aVal.setObject(*retObj);
return NS_OK;
}
// Processing and submitting the stack as an observer notification.
void
nsHangDetails::Submit()
{
if (NS_WARN_IF(!SystemGroup::Initialized())) {
return;
}
RefPtr<nsHangDetails> hangDetails = this;
nsCOMPtr<nsIRunnable> notifyObservers = NS_NewRunnableFunction("NotifyBHRHangObservers", [hangDetails] {
// The place we need to report the hang to varies depending on process.
//
// In child processes, we report the hang to our parent process, while if
// we're in the parent process, we report a bhr-thread-hang observer
// notification.
switch (XRE_GetProcessType()) {
case GeckoProcessType_Content: {
auto cc = dom::ContentChild::GetSingleton();
if (cc) {
hangDetails->mDetails.mRemoteType.Assign(cc->GetRemoteType());
Unused << cc->SendBHRThreadHang(hangDetails->mDetails);
}
break;
}
case GeckoProcessType_GPU: {
auto gp = gfx::GPUParent::GetSingleton();
if (gp) {
Unused << gp->SendBHRThreadHang(hangDetails->mDetails);
}
break;
}
case GeckoProcessType_Default: {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
}
break;
}
default:
// XXX: Consider handling GeckoProcessType_GMPlugin and
// GeckoProcessType_Plugin?
NS_WARNING("Unsupported BHR process type - discarding hang.");
break;
}
});
nsresult rv = SystemGroup::Dispatch(TaskCategory::Other,
notifyObservers.forget());
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
}
NS_IMPL_ISUPPORTS(nsHangDetails, nsIHangDetails)
NS_IMETHODIMP
ProcessHangStackRunnable::Run()
{
// NOTE: Reading module information can take a long time, which is why we do
// it off-main-thread.
mHangDetails.mStack.ReadModuleInformation();
RefPtr<nsHangDetails> hangDetails = new nsHangDetails(Move(mHangDetails));
hangDetails->Submit();
return NS_OK;
}
} // namespace mozilla
/**
* IPC Serialization / Deserialization logic
*/
namespace IPC {
void
ParamTraits<mozilla::HangDetails>::Write(Message* aMsg, const mozilla::HangDetails& aParam)
{
WriteParam(aMsg, aParam.mDuration);
WriteParam(aMsg, aParam.mProcess);
WriteParam(aMsg, aParam.mRemoteType);
WriteParam(aMsg, aParam.mThreadName);
WriteParam(aMsg, aParam.mRunnableName);
WriteParam(aMsg, aParam.mStack);
WriteParam(aMsg, aParam.mAnnotations);
}
bool
ParamTraits<mozilla::HangDetails>::Read(const Message* aMsg,
PickleIterator* aIter,
mozilla::HangDetails* aResult)
{
if (!ReadParam(aMsg, aIter, &aResult->mDuration)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mProcess)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mRemoteType)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mThreadName)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mRunnableName)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mStack)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mAnnotations)) {
return false;
}
return true;
}
} // namespace IPC