Bug 1417976 - Part 1: Store the information of DocShells in CorePS r=mstange,bzbarsky

Added a mechanism to register and unregister the DocShells from the CorePS depending
on the state of the profiler. Registering mechanism is straightforward. During
unregistration, if profiler is not active, we remove the DocShell information
immediately. If profiler is active, we don't remove and we keep the profiler buffer
position at that moment. During another DocShell registration we Discard the
unregistered DocShells. If the profiler buffer position is greater than the position
when we captured during unregistration, we delete the DocShell since that means there
can't be any markers associated to this DocShell anymore.

MozReview-Commit-ID: IVuKQ6drvkR

Differential Revision: https://phabricator.services.mozilla.com/D4914

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nazım Can Altınova 2018-11-06 21:45:07 +00:00
parent 9058324771
commit aaba02d9ee
6 changed files with 298 additions and 2 deletions

View File

@ -424,6 +424,10 @@ nsDocShell::~nsDocShell()
// Avoid notifying observers while we're in the dtor.
mIsBeingDestroyed = true;
#ifdef MOZ_GECKO_PROFILER
profiler_unregister_pages(mHistoryID);
#endif
Destroy();
if (mSessionHistory) {
@ -11358,6 +11362,21 @@ nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
}
}
#ifdef MOZ_GECKO_PROFILER
// We register the page load only if the load updates the history and it's not
// a refresh. This also registers the iframes in shift-reload case, but it's
// reasonable to register since we are updating the historyId in that case.
if (updateSHistory) {
uint32_t id = 0;
nsAutoCString spec;
if (mLSHE) {
mLSHE->GetID(&id);
}
aURI->GetSpec(spec);
profiler_register_page(mHistoryID, id, spec, IsFrame());
}
#endif
// If this is a POST request, we do not want to include this in global
// history.
if (updateGHistory && aAddToGlobalHistory && !ChannelIsPost(aChannel)) {
@ -11668,6 +11687,12 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
// we'll just set mOSHE here.
mOSHE = newSHEntry;
#ifdef MOZ_GECKO_PROFILER
uint32_t id = 0;
GetOSHEId(&id);
profiler_register_page(mHistoryID, id, NS_ConvertUTF16toUTF8(aURL), IsFrame());
#endif
} else {
newSHEntry = mOSHE;
newSHEntry->SetURI(newURI);

View File

@ -0,0 +1,33 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "PageInformation.h"
PageInformation::PageInformation(const nsID& aDocShellId,
uint32_t aDocShellHistoryId,
const nsCString& aUrl,
bool aIsSubFrame)
: mDocShellId(aDocShellId)
, mDocShellHistoryId(aDocShellHistoryId)
, mUrl(aUrl)
, mIsSubFrame(aIsSubFrame)
{
}
bool
PageInformation::Equals(PageInformation* aOtherPageInfo)
{
return DocShellHistoryId() == aOtherPageInfo->DocShellHistoryId() &&
DocShellId().Equals(aOtherPageInfo->DocShellId()) &&
IsSubFrame() == aOtherPageInfo->IsSubFrame();
}
size_t
PageInformation::SizeOfIncludingThis(
mozilla::MallocSizeOf aMallocSizeOf) const
{
return aMallocSizeOf(this);
}

View File

@ -0,0 +1,63 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef PageInformation_h
#define PageInformation_h
#include "nsID.h"
#include "nsString.h"
#include "platform.h"
#include "ProfileJSONWriter.h"
// This class contains information that's relevant to a single page only
// while the page information is important and registered with the profiler,
// but regardless of whether the profiler is running. All accesses to it are
// protected by the profiler state lock.
// When the page gets unregistered, we keep the profiler buffer position
// to determine if we are still using this page. If not, we unregister
// it in the next page registration.
class PageInformation final
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PageInformation)
PageInformation(const nsID& aDocShellId,
uint32_t aDocShellHistoryId,
const nsCString& aUrl,
bool aIsSubFrame);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
bool Equals(PageInformation* aOtherDocShellInfo);
uint32_t DocShellHistoryId() { return mDocShellHistoryId; }
const nsID& DocShellId() { return mDocShellId; }
const nsCString& Url() { return mUrl; }
bool IsSubFrame() { return mIsSubFrame; }
mozilla::Maybe<uint64_t> BufferPositionWhenUnregistered()
{
return mBufferPositionWhenUnregistered;
}
void NotifyUnregistered(uint64_t aBufferPosition)
{
mBufferPositionWhenUnregistered = mozilla::Some(aBufferPosition);
}
private:
const nsID mDocShellId;
const uint32_t mDocShellHistoryId;
const nsCString mUrl;
const bool mIsSubFrame;
// Holds the buffer position when DocShell is unregistered.
// It's used to determine if we still use this DocShell in the profiler or
// not.
mozilla::Maybe<uint64_t> mBufferPositionWhenUnregistered;
virtual ~PageInformation() = default;
};
#endif // PageInformation_h

View File

@ -264,10 +264,16 @@ public:
aProfSize += registeredThread->SizeOfIncludingThis(aMallocSizeOf);
}
for (auto& registeredPage : sInstance->mRegisteredPages) {
aProfSize += registeredPage->SizeOfIncludingThis(aMallocSizeOf);
}
// Measurement of the following things may be added later if DMD finds it
// is worthwhile:
// - CorePS::mRegisteredThreads itself (its elements' children are measured
// above)
// - CorePS::mRegisteredThreads itself (its elements' children are
// measured above)
// - CorePS::mRegisteredPages itself (its elements' children are
// measured above)
// - CorePS::mInterposeObserver
#if defined(USE_LUL_STACKWALK)
@ -296,6 +302,38 @@ public:
[&](UniquePtr<RegisteredThread>& rt) { return rt.get() == aRegisteredThread; });
}
PS_GET(nsTArray<RefPtr<PageInformation>>&, RegisteredPages)
static void AppendRegisteredPage(
PSLockRef,
RefPtr<PageInformation>&& aRegisteredPage)
{
#ifdef DEBUG
struct RegisteredPageComparator
{
bool Equals(PageInformation* aA,
PageInformation* aB) const
{
return aA->Equals(aB);
}
};
MOZ_ASSERT(!sInstance->mRegisteredPages.Contains(
aRegisteredPage, RegisteredPageComparator()));
#endif
sInstance->mRegisteredPages.AppendElement(
std::move(aRegisteredPage));
}
static void RemoveRegisteredPages(PSLockRef,
const nsID& aRegisteredDocShellId)
{
// Remove RegisteredPage from mRegisteredPages by given DocShell Id.
sInstance->mRegisteredPages.RemoveElementsBy(
[&](RefPtr<PageInformation>& rd) {
return rd->DocShellId().Equals(aRegisteredDocShellId);
});
}
PS_GET(const nsTArray<BaseProfilerCount*>&, Counters)
static void AppendCounter(PSLockRef, BaseProfilerCount* aCounter)
@ -332,6 +370,10 @@ private:
// ThreadIds in mRegisteredThreads are unique.
nsTArray<UniquePtr<RegisteredThread>> mRegisteredThreads;
// Info on all the registered pages.
// DocShellId and DocShellHistoryId pairs in mRegisteredPages are unique.
nsTArray<RefPtr<PageInformation>> mRegisteredPages;
// Non-owning pointers to all active counters
nsTArray<BaseProfilerCount*> mCounters;
@ -606,6 +648,20 @@ public:
return array;
}
static nsTArray<RefPtr<PageInformation>> ProfiledPages(PSLockRef aLock)
{
nsTArray<RefPtr<PageInformation>> array;
for (auto& d : CorePS::RegisteredPages(aLock)) {
array.AppendElement(d);
}
for (auto& d : sInstance->mDeadProfiledPages) {
array.AppendElement(d);
}
// We don't need to sort the DocShells like threads since we won't show them
// as a list.
return array;
}
// Do a linear search through mLiveProfiledThreads to find the
// ProfiledThreadData object for a RegisteredThread.
static ProfiledThreadData* GetProfiledThreadData(PSLockRef,
@ -669,6 +725,35 @@ public:
});
}
static void UnregisterPages(PSLockRef aLock,
const nsID& aRegisteredDocShellId)
{
auto& registeredPages = CorePS::RegisteredPages(aLock);
for (size_t i = 0; i < registeredPages.Length(); i++) {
RefPtr<PageInformation>& page = registeredPages[i];
if (page->DocShellId().Equals(aRegisteredDocShellId)) {
page->NotifyUnregistered(sInstance->mBuffer->mRangeEnd);
sInstance->mDeadProfiledPages.AppendElement(std::move(page));
registeredPages.RemoveElementAt(i--);
}
}
}
static void DiscardExpiredPages(PSLockRef)
{
uint64_t bufferRangeStart = sInstance->mBuffer->mRangeStart;
// Discard any dead pages that were unregistered before
// bufferRangeStart.
sInstance->mDeadProfiledPages.RemoveElementsBy(
[bufferRangeStart](RefPtr<PageInformation>& aProfiledPage) {
Maybe<uint64_t> bufferPosition =
aProfiledPage->BufferPositionWhenUnregistered();
MOZ_RELEASE_ASSERT(bufferPosition,
"should have unregistered this page");
return *bufferPosition < bufferRangeStart;
});
}
private:
// The singleton instance.
static ActivePS* sInstance;
@ -717,6 +802,12 @@ private:
nsTArray<LiveProfiledThreadData> mLiveProfiledThreads;
nsTArray<UniquePtr<ProfiledThreadData>> mDeadProfiledThreads;
// Info on all the dead pages.
// Registered pages are being moved to this array after unregistration.
// We are keeping them in case we need them in the profile data.
// We are removing them when we ensure that we won't need them anymore.
nsTArray<RefPtr<PageInformation>> mDeadProfiledPages;
// The current sampler thread. This class is not responsible for destroying
// the SamplerThread object; the Destroy() method returns it so the caller
// can destroy it.
@ -3442,6 +3533,60 @@ profiler_unregister_thread()
}
}
void
profiler_register_page(const nsID& aDocShellId,
uint32_t aHistoryId,
const nsCString& aUrl,
bool aIsSubFrame)
{
DEBUG_LOG("profiler_register_page(%s, %u, %s, %d)",
aDocShellId.ToString(),
aHistoryId,
aUrl.get(),
aIsSubFrame);
MOZ_RELEASE_ASSERT(CorePS::Exists());
PSAutoLock lock(gPSMutex);
// If profiler is not active, delete all the previous page entries of the
// given DocShell since we won't need those.
if (!ActivePS::Exists(lock)) {
CorePS::RemoveRegisteredPages(lock, aDocShellId);
}
RefPtr<PageInformation> pageInfo =
new PageInformation(aDocShellId, aHistoryId, aUrl, aIsSubFrame);
CorePS::AppendRegisteredPage(lock, std::move(pageInfo));
// After appending the given page to CorePS, look for the expired
// pages and remove them if there are any.
if (ActivePS::Exists(lock)) {
ActivePS::DiscardExpiredPages(lock);
}
}
void
profiler_unregister_pages(const nsID& aRegisteredDocShellId)
{
if (!CorePS::Exists()) {
// This function can be called after the main thread has already shut down.
return;
}
PSAutoLock lock(gPSMutex);
// During unregistration, if the profiler is active, we have to keep the
// page information since there may be some markers associated with the given
// page. But if profiler is not active. we have no reason to keep the
// page information here because there can't be any marker associated with it.
if (ActivePS::Exists(lock)) {
ActivePS::UnregisterPages(lock, aRegisteredDocShellId);
} else {
CorePS::RemoveRegisteredPages(lock, aRegisteredDocShellId);
}
}
void
profiler_thread_sleep()
{

View File

@ -20,6 +20,7 @@ if CONFIG['MOZ_GECKO_PROFILER']:
'public/shared-libraries.h',
]
UNIFIED_SOURCES += [
'core/PageInformation.cpp',
'core/platform.cpp',
'core/ProfileBuffer.cpp',
'core/ProfileBufferEntry.cpp',

View File

@ -75,6 +75,8 @@
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "nscore.h"
#include "nsID.h"
#include "nsString.h"
// Make sure that we can use std::min here without the Windows headers messing
// with us.
@ -294,6 +296,33 @@ void profiler_ensure_started(uint32_t aCapacity, double aInterval,
ProfilingStack* profiler_register_thread(const char* name, void* guessStackTop);
void profiler_unregister_thread();
// Register pages with the profiler.
//
// The `page` means every new history entry for docShells.
// DocShellId + HistoryID is a unique pair to identify these pages.
// We also keep these pairs inside markers to associate with the pages.
// That allows us to see which markers belong to a specific page and filter the
// markers by a page.
// We register pages in these cases:
// - If there is a navigation through a link or URL bar.
// - If there is a navigation through `location.replace` or `history.pushState`.
// We do not register pages in these cases:
// - If there is a history navigation through the back and forward buttons.
// - If there is a navigation through `history.replaceState` or anchor scrolls.
//
// "aDocShellId" is the ID of the docShell that page belongs to.
// "aHistoryId" is the ID of the history entry on the given docShell.
// "aUrl" is the URL of the page.
// "aIsSubFrame" is true if the page is a sub frame.
void profiler_register_page(const nsID& aDocShellId,
uint32_t aHistoryId,
const nsCString& aUrl,
bool aIsSubFrame);
// Unregister pages with the profiler.
//
// Take a docShellId and unregister all the page entries that have the given ID.
void profiler_unregister_pages(const nsID& aRegisteredDocShellId);
class BaseProfilerCount;
void profiler_add_sampled_counter(BaseProfilerCount* aCounter);
void profiler_remove_sampled_counter(BaseProfilerCount* aCounter);