зеркало из https://github.com/mozilla/gecko-dev.git
233 строки
7.2 KiB
C++
233 строки
7.2 KiB
C++
/* -*- Mode: C++; tab-width: 8; 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 "ProfilerMarkers.h"
|
|
|
|
#include "GeckoProfiler.h"
|
|
#include "MainThreadUtils.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/Services.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsXULAppAPI.h"
|
|
|
|
#include <objbase.h>
|
|
#include <objidlbase.h>
|
|
|
|
// {9DBE6B28-E5E7-4FDE-AF00-9404604E74DC}
|
|
static const GUID GUID_MozProfilerMarkerExtension = {
|
|
0x9dbe6b28, 0xe5e7, 0x4fde, {0xaf, 0x0, 0x94, 0x4, 0x60, 0x4e, 0x74, 0xdc}};
|
|
|
|
namespace {
|
|
|
|
class ProfilerMarkerChannelHook final : public IChannelHook {
|
|
~ProfilerMarkerChannelHook() = default;
|
|
|
|
public:
|
|
ProfilerMarkerChannelHook() : mRefCnt(0), mMainThreadORPCDepth(0) {}
|
|
|
|
// IUnknown
|
|
STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override;
|
|
STDMETHODIMP_(ULONG) AddRef() override;
|
|
STDMETHODIMP_(ULONG) Release() override;
|
|
|
|
/**
|
|
* IChannelHook exposes six methods: The Client* methods are called when
|
|
* a client is sending an IPC request, whereas the Server* methods are called
|
|
* when a server is receiving an IPC request.
|
|
*
|
|
* For our purposes, we only care about the client-side methods. The COM
|
|
* runtime invokes the methods in the following order:
|
|
* 1. ClientGetSize, where the hook specifies the size of its payload;
|
|
* 2. ClientFillBuffer, where the hook fills the channel's buffer with its
|
|
* payload information. NOTE: This method is only called when ClientGetSize
|
|
* specifies a non-zero payload size. For our purposes, since we are not
|
|
* sending a payload, this method will never be called!
|
|
* 3. ClientNotify, when the response has been received from the server.
|
|
*
|
|
* Since we want to use these hooks to record the beginning and end of a COM
|
|
* IPC call, we use ClientGetSize for logging the start, and ClientNotify for
|
|
* logging the end.
|
|
*
|
|
* Finally, our implementation responds to any request matching our extension
|
|
* ID, however we only care about main thread COM calls.
|
|
*
|
|
* Further note that COM allows re-entrancy, however for our purposes we only
|
|
* care about the outermost IPC call on the main thread, so we use the
|
|
* mMainThreadORPCDepth variable to track that.
|
|
*/
|
|
|
|
// IChannelHook
|
|
STDMETHODIMP_(void)
|
|
ClientGetSize(REFGUID aExtensionId, REFIID aIid,
|
|
ULONG* aOutDataSize) override;
|
|
|
|
// No-op (see the large comment above)
|
|
STDMETHODIMP_(void)
|
|
ClientFillBuffer(REFGUID aExtensionId, REFIID aIid, ULONG* aDataSize,
|
|
void* aDataBuf) override {}
|
|
|
|
STDMETHODIMP_(void)
|
|
ClientNotify(REFGUID aExtensionId, REFIID aIid, ULONG aDataSize,
|
|
void* aDataBuffer, DWORD aDataRep, HRESULT aFault) override;
|
|
|
|
// We don't care about the server-side notifications, so leave as no-ops.
|
|
STDMETHODIMP_(void)
|
|
ServerNotify(REFGUID aExtensionId, REFIID aIid, ULONG aDataSize,
|
|
void* aDataBuf, DWORD aDataRep) override {}
|
|
|
|
STDMETHODIMP_(void)
|
|
ServerGetSize(REFGUID aExtensionId, REFIID aIid, HRESULT aFault,
|
|
ULONG* aOutDataSize) override {}
|
|
|
|
STDMETHODIMP_(void)
|
|
ServerFillBuffer(REFGUID aExtensionId, REFIID aIid, ULONG* aDataSize,
|
|
void* aDataBuf, HRESULT aFault) override {}
|
|
|
|
private:
|
|
mozilla::Atomic<ULONG> mRefCnt;
|
|
ULONG mMainThreadORPCDepth;
|
|
};
|
|
|
|
HRESULT ProfilerMarkerChannelHook::QueryInterface(REFIID aIid,
|
|
void** aOutInterface) {
|
|
if (aIid == IID_IChannelHook || aIid == IID_IUnknown) {
|
|
RefPtr<IChannelHook> ptr(this);
|
|
ptr.forget(aOutInterface);
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ULONG ProfilerMarkerChannelHook::AddRef() { return ++mRefCnt; }
|
|
|
|
ULONG ProfilerMarkerChannelHook::Release() {
|
|
ULONG result = --mRefCnt;
|
|
if (!result) {
|
|
delete this;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void ProfilerMarkerChannelHook::ClientGetSize(REFGUID aExtensionId, REFIID aIid,
|
|
ULONG* aOutDataSize) {
|
|
if (aExtensionId == GUID_MozProfilerMarkerExtension) {
|
|
if (NS_IsMainThread()) {
|
|
if (!mMainThreadORPCDepth) {
|
|
PROFILER_TRACING_MARKER("MSCOM", "ORPC Call", IPC,
|
|
TRACING_INTERVAL_START);
|
|
}
|
|
|
|
++mMainThreadORPCDepth;
|
|
}
|
|
|
|
if (aOutDataSize) {
|
|
// We don't add any payload data to the channel
|
|
*aOutDataSize = 0UL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProfilerMarkerChannelHook::ClientNotify(REFGUID aExtensionId, REFIID aIid,
|
|
ULONG aDataSize, void* aDataBuffer,
|
|
DWORD aDataRep, HRESULT aFault) {
|
|
if (aExtensionId == GUID_MozProfilerMarkerExtension && NS_IsMainThread()) {
|
|
MOZ_ASSERT(mMainThreadORPCDepth > 0);
|
|
--mMainThreadORPCDepth;
|
|
if (!mMainThreadORPCDepth) {
|
|
PROFILER_TRACING_MARKER("MSCOM", "ORPC Call", IPC, TRACING_INTERVAL_END);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
static void RegisterChannelHook() {
|
|
RefPtr<ProfilerMarkerChannelHook> hook(new ProfilerMarkerChannelHook());
|
|
mozilla::DebugOnly<HRESULT> hr =
|
|
::CoRegisterChannelHook(GUID_MozProfilerMarkerExtension, hook);
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
}
|
|
|
|
namespace {
|
|
|
|
class ProfilerStartupObserver final : public nsIObserver {
|
|
~ProfilerStartupObserver() = default;
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ProfilerStartupObserver, nsIObserver)
|
|
|
|
NS_IMETHODIMP ProfilerStartupObserver::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (strcmp(aTopic, "profiler-started")) {
|
|
return NS_OK;
|
|
}
|
|
|
|
RegisterChannelHook();
|
|
|
|
// Once we've set the channel hook, we don't care about this notification
|
|
// anymore; our channel hook will remain set for the lifetime of the process.
|
|
nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
|
|
MOZ_ASSERT(!!obsServ);
|
|
if (!obsServ) {
|
|
return NS_OK;
|
|
}
|
|
|
|
obsServ->RemoveObserver(this, "profiler-started");
|
|
return NS_OK;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace mozilla {
|
|
namespace mscom {
|
|
|
|
void InitProfilerMarkers() {
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
if (!XRE_IsParentProcess()) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (!NS_IsMainThread()) {
|
|
return;
|
|
}
|
|
|
|
if (profiler_is_active()) {
|
|
// If the profiler is already running, we'll immediately register our
|
|
// channel hook.
|
|
RegisterChannelHook();
|
|
return;
|
|
}
|
|
|
|
// The profiler is not running yet. To avoid unnecessary invocations of the
|
|
// channel hook, we won't bother with installing it until the profiler starts.
|
|
// Set up an observer to watch for this.
|
|
nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
|
|
MOZ_ASSERT(!!obsServ);
|
|
if (!obsServ) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIObserver> obs(new ProfilerStartupObserver());
|
|
obsServ->AddObserver(obs, "profiler-started", false);
|
|
#endif // MOZ_GECKO_PROFILER
|
|
}
|
|
|
|
} // namespace mscom
|
|
} // namespace mozilla
|