Bug 1409538: Add durations to MSCOM log; r=jimm
authorAaron Klotz <aklotz@mozilla.com>
Tue, 25 Jul 2017 15:57:18 -0600
changeset 440733 6cf608db37596ea9cd03c6ad749340c3f95b72e2
parent 440732 23d3b458f69c56b0f3b2b8faa1453f79e32ce70c
child 440734 bb5d618b1f5fee54bbda2ea102a3c8ae243c1bb2
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs1409538
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1409538: Add durations to MSCOM log; r=jimm This patch adds two additional fields to each mscom log entry: The first is the duration, in microseconds, of time spent in mscom overhead when executing a call from the MTA on behalf of a remote client. The second field is the duration, in microseconds, of time spent actually executing the method within Gecko itself. (In other words, the sum of the two fields will equal the total duration of time spent executing the call.) MozReview-Commit-ID: EhFieEPrhE5
ipc/mscom/Interceptor.cpp
ipc/mscom/Interceptor.h
ipc/mscom/InterceptorLog.cpp
ipc/mscom/InterceptorLog.h
ipc/mscom/MainThreadHandoff.cpp
ipc/mscom/MainThreadInvoker.cpp
ipc/mscom/MainThreadInvoker.h
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -26,28 +26,28 @@
 #include "nsXULAppAPI.h"
 
 #if defined(MOZ_CRASHREPORTER)
 
 #include "nsExceptionHandler.h"
 #include "nsPrintfCString.h"
 
 #define ENSURE_HR_SUCCEEDED(hr) \
-  if (FAILED(hr)) { \
+  if (FAILED((HRESULT)hr)) { \
     nsPrintfCString location("ENSURE_HR_SUCCEEDED \"%s\": %u", __FILE__, __LINE__); \
-    nsPrintfCString hrAsStr("0x%08X", hr); \
+    nsPrintfCString hrAsStr("0x%08X", (HRESULT)hr); \
     CrashReporter::AnnotateCrashReport(location, hrAsStr); \
-    MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr)); \
+    MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED((HRESULT)hr)); \
     return hr; \
   }
 
 #else
 
 #define ENSURE_HR_SUCCEEDED(hr) \
-  if (FAILED(hr)) { \
+  if (FAILED((HRESULT)hr)) { \
     return hr; \
   }
 
 #endif // defined(MOZ_CRASHREPORTER)
 
 namespace mozilla {
 namespace mscom {
 namespace detail {
@@ -482,27 +482,90 @@ Interceptor::GetInitialInterceptorForIID
     return hr;
   }
 
   hr = GetInterceptorForIID(aTargetIid, aOutInterceptor);
   ENSURE_HR_SUCCEEDED(hr);
   return hr;
 }
 
+class MOZ_RAII LoggedQIResult final
+{
+public:
+  explicit LoggedQIResult(REFIID aIid)
+    : mIid(aIid)
+    , mHr(E_UNEXPECTED)
+    , mTarget(nullptr)
+    , mInterceptor(nullptr)
+    , mBegin(TimeStamp::Now())
+  {
+  }
+
+  ~LoggedQIResult()
+  {
+    if (!mTarget) {
+      return;
+    }
+
+    TimeStamp end(TimeStamp::Now());
+    TimeDuration total(end - mBegin);
+    TimeDuration overhead(total - mNonOverheadDuration);
+
+    InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead,
+                       &mNonOverheadDuration);
+  }
+
+  void Log(IUnknown* aTarget, IUnknown* aInterceptor)
+  {
+    mTarget = aTarget;
+    mInterceptor = aInterceptor;
+  }
+
+  void operator=(HRESULT aHr)
+  {
+    mHr = aHr;
+  }
+
+  operator HRESULT()
+  {
+    return mHr;
+  }
+
+  operator TimeDuration*()
+  {
+    return &mNonOverheadDuration;
+  }
+
+  LoggedQIResult(const LoggedQIResult&) = delete;
+  LoggedQIResult(LoggedQIResult&&) = delete;
+  LoggedQIResult& operator=(const LoggedQIResult&) = delete;
+  LoggedQIResult& operator=(LoggedQIResult&&) = delete;
+
+private:
+  REFIID        mIid;
+  HRESULT       mHr;
+  IUnknown*     mTarget;
+  IUnknown*     mInterceptor;
+  TimeDuration  mNonOverheadDuration;
+  TimeStamp     mBegin;
+};
+
 /**
  * This method contains the core guts of the handling of QueryInterface calls
  * that are delegated to us from the ICallInterceptor.
  *
  * @param aIid ID of the desired interface
  * @param aOutInterceptor The resulting emulated vtable that corresponds to
  * the interface specified by aIid.
  */
 HRESULT
 Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor)
 {
+  LoggedQIResult result(aIid);
+
   if (!aOutInterceptor) {
     return E_INVALIDARG;
   }
 
   if (aIid == IID_IUnknown) {
     // Special case: When we see IUnknown, we just provide a reference to this
     RefPtr<IInterceptor> intcpt(this);
     intcpt.forget(aOutInterceptor);
@@ -527,36 +590,36 @@ Interceptor::GetInterceptorForIID(REFIID
   }
 
   // (1a) A COM interceptor already exists for this interface, so all we need
   // to do is run a QI on it.
   if (unkInterceptor) {
     // Technically we didn't actually execute a QI on the target interface, but
     // for logging purposes we would like to record the fact that this interface
     // was requested.
-    InterceptorLog::QI(S_OK, mTarget.get(), aIid, interfaceForQILog);
-
-    HRESULT hr = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
-    ENSURE_HR_SUCCEEDED(hr);
-    return hr;
+    result.Log(mTarget.get(), interfaceForQILog);
+    result = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
+    ENSURE_HR_SUCCEEDED(result);
+    return result;
   }
 
   // (2) Obtain a new target interface.
 
   // (2a) First, make sure that the target interface is available
   // NB: We *MUST* query the correct interface! ICallEvents::Invoke casts its
   // pvReceiver argument directly to the required interface! DO NOT assume
   // that COM will use QI or upcast/downcast!
   HRESULT hr;
 
   STAUniquePtr<IUnknown> targetInterface;
   IUnknown* rawTargetInterface = nullptr;
-  hr = QueryInterfaceTarget(interceptorIid, (void**)&rawTargetInterface);
+  hr = QueryInterfaceTarget(interceptorIid, (void**)&rawTargetInterface, result);
   targetInterface.reset(rawTargetInterface);
-  InterceptorLog::QI(hr, mTarget.get(), aIid, targetInterface.get());
+  result = hr;
+  result.Log(mTarget.get(), targetInterface.get());
   MOZ_ASSERT(SUCCEEDED(hr) || hr == E_NOINTERFACE);
   if (hr == E_NOINTERFACE) {
     return hr;
   }
   ENSURE_HR_SUCCEEDED(hr);
 
   // We *really* shouldn't be adding interceptors to proxies
   MOZ_ASSERT(aIid != IID_IMarshal);
@@ -603,30 +666,34 @@ Interceptor::GetInterceptorForIID(REFIID
   }
 
   hr = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
   ENSURE_HR_SUCCEEDED(hr);
   return hr;
 }
 
 HRESULT
-Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput)
+Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput,
+                                  TimeDuration* aOutDuration)
 {
   // NB: This QI needs to run on the main thread because the target object
   // is probably Gecko code that is not thread-safe. Note that this main
   // thread invocation is *synchronous*.
   MainThreadInvoker invoker;
   HRESULT hr;
   auto runOnMainThread = [&]() -> void {
     MOZ_ASSERT(NS_IsMainThread());
     hr = mTarget->QueryInterface(aIid, aOutput);
   };
   if (!invoker.Invoke(NS_NewRunnableFunction("Interceptor::QueryInterface", runOnMainThread))) {
     return E_FAIL;
   }
+  if (aOutDuration) {
+    *aOutDuration = invoker.GetDuration();
+  }
   return hr;
 }
 
 HRESULT
 Interceptor::QueryInterface(REFIID riid, void** ppv)
 {
   return WeakReferenceSupport::QueryInterface(riid, ppv);
 }
--- a/ipc/mscom/Interceptor.h
+++ b/ipc/mscom/Interceptor.h
@@ -120,17 +120,18 @@ private:
 private:
   explicit Interceptor(IInterceptorSink* aSink);
   ~Interceptor();
   HRESULT GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLiveSetLock,
                                       REFIID aTargetIid,
                                       STAUniquePtr<IUnknown> aTarget,
                                       void** aOutInterface);
   MapEntry* Lookup(REFIID aIid);
-  HRESULT QueryInterfaceTarget(REFIID aIid, void** aOutput);
+  HRESULT QueryInterfaceTarget(REFIID aIid, void** aOutput,
+                               TimeDuration* aOutDuration = nullptr);
   HRESULT ThreadSafeQueryInterface(REFIID aIid,
                                    IUnknown** aOutInterface) override;
   HRESULT CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput);
   REFIID MarshalAs(REFIID aIid) const;
   HRESULT PublishTarget(detail::LiveSetAutoLock& aLiveSetLock,
                         RefPtr<IUnknown> aInterceptor,
                         REFIID aTargetIid,
                         STAUniquePtr<IUnknown> aTarget);
--- a/ipc/mscom/InterceptorLog.cpp
+++ b/ipc/mscom/InterceptorLog.cpp
@@ -63,18 +63,22 @@ class Logger
 {
 public:
   explicit Logger(const nsACString& aLeafBaseName);
   bool IsValid()
   {
     MutexAutoLock lock(mMutex);
     return !!mThread;
   }
-  void LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface);
-  void LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface);
+  void LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid,
+             IUnknown* aInterface, const TimeDuration* aOverheadDuration,
+             const TimeDuration* aGeckoDuration);
+  void LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface,
+                const TimeDuration& aOverheadDuration,
+                const TimeDuration& aGeckoDuration);
   nsresult Shutdown();
 
 private:
   void OpenFile();
   void Flush();
   void CloseFile();
   void AssertRunningOnLoggerThread();
   bool VariantToString(const VARIANT& aVariant, nsACString& aOut, LONG aIndex = 0);
@@ -259,25 +263,43 @@ Logger::VariantToString(const VARIANT& a
 Logger::GetElapsedTime()
 {
   TimeStamp ts = TimeStamp::Now();
   TimeDuration duration = ts - TimeStamp::ProcessCreation();
   return duration.ToMicroseconds();
 }
 
 void
-Logger::LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface)
+Logger::LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid,
+              IUnknown* aInterface, const TimeDuration* aOverheadDuration,
+              const TimeDuration* aGeckoDuration)
 {
   if (FAILED(aResult)) {
     return;
   }
+
   double elapsed = GetElapsedTime();
 
-  nsPrintfCString line("%fus\t0x%0p\tIUnknown::QueryInterface\t([in] ", elapsed,
-                       aTarget);
+  nsAutoCString strOverheadDuration;
+  if (aOverheadDuration) {
+    strOverheadDuration.AppendPrintf("%.3f", aOverheadDuration->ToMicroseconds());
+  } else {
+    strOverheadDuration.AppendLiteral("(none)");
+  }
+
+  nsAutoCString strGeckoDuration;
+  if (aGeckoDuration) {
+    strGeckoDuration.AppendPrintf("%.3f", aGeckoDuration->ToMicroseconds());
+  } else {
+    strGeckoDuration.AppendLiteral("(none)");
+  }
+
+  nsPrintfCString line("%.3f\t%s\t%s\t0x%0p\tIUnknown::QueryInterface\t([in] ",
+                       elapsed, strOverheadDuration.get(),
+                       strGeckoDuration.get(), aTarget);
 
   WCHAR buf[39] = {0};
   if (StringFromGUID2(aIid, buf, mozilla::ArrayLength(buf))) {
     line.AppendPrintf("%S", buf);
   } else {
     line.AppendLiteral("(IID Conversion Failed)");
   }
   line.AppendPrintf(", [out] 0x%p)\t0x%08X\n", aInterface, aResult);
@@ -310,17 +332,19 @@ Logger::TryParamAsGuid(REFIID aIid, ICal
     return false;
   }
 
   aLine.AppendPrintf("%S", buf);
   return true;
 }
 
 void
-Logger::LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
+Logger::LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface,
+                 const TimeDuration& aOverheadDuration,
+                 const TimeDuration& aGeckoDuration)
 {
   // (1) Gather info about the call
   double elapsed = GetElapsedTime();
 
   CALLFRAMEINFO callInfo;
   HRESULT hr = aCallFrame->GetInfo(&callInfo);
   if (FAILED(hr)) {
     return;
@@ -329,17 +353,19 @@ Logger::LogEvent(ICallFrame* aCallFrame,
   PWSTR interfaceName = nullptr;
   PWSTR methodName = nullptr;
   hr = aCallFrame->GetNames(&interfaceName, &methodName);
   if (FAILED(hr)) {
     return;
   }
 
   // (2) Serialize the call
-  nsPrintfCString line("%fus\t0x%p\t%S::%S\t(", elapsed,
+  nsPrintfCString line("%.3f\t%.3f\t%.3f\t0x%p\t%S::%S\t(", elapsed,
+                       aOverheadDuration.ToMicroseconds(),
+                       aGeckoDuration.ToMicroseconds(),
                        aTargetInterface, interfaceName, methodName);
 
   CoTaskMemFree(interfaceName);
   interfaceName = nullptr;
   CoTaskMemFree(methodName);
   methodName = nullptr;
 
   // Check for supplemental array data
@@ -471,28 +497,34 @@ namespace mscom {
 /* static */ bool
 InterceptorLog::Init()
 {
   static const bool isEnabled = MaybeCreateLog("MOZ_MSCOM_LOG_BASENAME");
   return isEnabled;
 }
 
 /* static */ void
-InterceptorLog::QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface)
+InterceptorLog::QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid,
+                   IUnknown* aInterface, const TimeDuration* aOverheadDuration,
+                   const TimeDuration* aGeckoDuration)
 {
   if (!sLogger) {
     return;
   }
-  sLogger->LogQI(aResult, aTarget, aIid, aInterface);
+  sLogger->LogQI(aResult, aTarget, aIid, aInterface, aOverheadDuration,
+                 aGeckoDuration);
 }
 
 /* static */ void
-InterceptorLog::Event(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
+InterceptorLog::Event(ICallFrame* aCallFrame, IUnknown* aTargetInterface,
+                      const TimeDuration& aOverheadDuration,
+                      const TimeDuration& aGeckoDuration)
 {
   if (!sLogger) {
     return;
   }
-  sLogger->LogEvent(aCallFrame, aTargetInterface);
+  sLogger->LogEvent(aCallFrame, aTargetInterface, aOverheadDuration,
+                    aGeckoDuration);
 }
 
 } // namespace mscom
 } // namespace mozilla
 
--- a/ipc/mscom/InterceptorLog.h
+++ b/ipc/mscom/InterceptorLog.h
@@ -2,27 +2,34 @@
 /* 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 mozilla_mscom_InterceptorLog_h
 #define mozilla_mscom_InterceptorLog_h
 
+#include "mozilla/TimeStamp.h"
+
 struct ICallFrame;
 struct IUnknown;
 
 namespace mozilla {
 namespace mscom {
 
 class InterceptorLog
 {
 public:
   static bool Init();
-  static void QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface);
-  static void Event(ICallFrame* aCallFrame, IUnknown* aTarget);
+  static void QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid,
+                 IUnknown* aInterface,
+                 const TimeDuration* aOverheadDuration = nullptr,
+                 const TimeDuration* aGeckoDuration = nullptr);
+  static void Event(ICallFrame* aCallFrame, IUnknown* aTarget,
+                    const TimeDuration& aOverheadDuration,
+                    const TimeDuration& aGeckoDuration);
 };
 
 } // namespace mscom
 } // namespace mozilla
 
 #endif // mozilla_mscom_InterceptorLog_h
 
--- a/ipc/mscom/MainThreadHandoff.cpp
+++ b/ipc/mscom/MainThreadHandoff.cpp
@@ -1,24 +1,25 @@
 /* -*- 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 "mozilla/mscom/MainThreadHandoff.h"
 
+#include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/Move.h"
 #include "mozilla/mscom/AgileReference.h"
 #include "mozilla/mscom/InterceptorLog.h"
 #include "mozilla/mscom/Registration.h"
 #include "mozilla/mscom/Utils.h"
-#include "mozilla/Assertions.h"
-#include "mozilla/DebugOnly.h"
+#include "mozilla/TimeStamp.h"
 #include "mozilla/ThreadLocal.h"
 #include "nsThreadUtils.h"
 #include "nsProxyRelease.h"
 
 using mozilla::DebugOnly;
 using mozilla::mscom::AgileReference;
 
 namespace {
@@ -305,16 +306,18 @@ MainThreadHandoff::FixIServiceProvider(I
   return OnWalkInterface(**iidOutParam,
                          reinterpret_cast<void**>(varIfaceOut.ppunkVal), FALSE,
                          TRUE);
 }
 
 HRESULT
 MainThreadHandoff::OnCall(ICallFrame* aFrame)
 {
+  TimeStamp callStart(TimeStamp::Now());
+
   // (1) Get info about the method call
   HRESULT hr;
   IID iid;
   ULONG method;
   hr = aFrame->GetIIDAndMethod(&iid, &method);
   if (FAILED(hr)) {
     return hr;
   }
@@ -341,19 +344,24 @@ MainThreadHandoff::OnCall(ICallFrame* aF
     return E_UNEXPECTED;
   }
   hr = handoffInfo->GetResult();
   MOZ_ASSERT(SUCCEEDED(hr));
   if (FAILED(hr)) {
     return hr;
   }
 
+  TimeStamp callEnd(TimeStamp::Now());
+  TimeDuration totalTime(callEnd - callStart);
+  TimeDuration overhead(totalTime - invoker.GetDuration());
+
   // (3) Log *before* wrapping outputs so that the log will contain pointers to
   // the true target interface, not the wrapped ones.
-  InterceptorLog::Event(aFrame, targetInterface.get());
+  InterceptorLog::Event(aFrame, targetInterface.get(), overhead,
+                        invoker.GetDuration());
 
   // (4) Scan the function call for outparams that contain interface pointers.
   // Those will need to be wrapped with MainThreadHandoff so that they too will
   // be exeuted on the main thread.
 
   hr = aFrame->GetReturnValue();
   if (FAILED(hr)) {
     // If the call resulted in an error then there's not going to be anything
--- a/ipc/mscom/MainThreadInvoker.cpp
+++ b/ipc/mscom/MainThreadInvoker.cpp
@@ -39,31 +39,41 @@ public:
 
   NS_IMETHOD Run()
   {
     if (mHasRun) {
       return NS_OK;
     }
     mHasRun = true;
 
+    TimeStamp runStart(TimeStamp::Now());
     mRunnable->Run();
+    TimeStamp runEnd(TimeStamp::Now());
+
+    mDuration = runEnd - runStart;
 
     mEvent.Signal();
     return NS_OK;
   }
 
   bool WaitUntilComplete()
   {
     return mEvent.Wait(mozilla::mscom::MainThreadInvoker::GetTargetThread());
   }
 
+  const mozilla::TimeDuration& GetDuration() const
+  {
+    return mDuration;
+  }
+
 private:
   bool                      mHasRun = false;
   nsCOMPtr<nsIRunnable>     mRunnable;
   mozilla::mscom::SpinEvent mEvent;
+  mozilla::TimeDuration     mDuration;
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace mscom {
 
 HANDLE MainThreadInvoker::sMainThread = nullptr;
@@ -118,17 +128,19 @@ MainThreadInvoker::Invoke(already_AddRef
   // This ref gets released in MainThreadAPC when it runs.
   SyncRunnable* syncRunnableRef = syncRunnable.get();
   NS_ADDREF(syncRunnableRef);
   if (!::QueueUserAPC(&MainThreadAPC, sMainThread,
                       reinterpret_cast<UINT_PTR>(syncRunnableRef))) {
     return false;
   }
 
-  return syncRunnable->WaitUntilComplete();
+  bool result = syncRunnable->WaitUntilComplete();
+  mDuration = syncRunnable->GetDuration();
+  return result;
 }
 
 /* static */ VOID CALLBACK
 MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam)
 {
   AUTO_PROFILER_THREAD_WAKE;
   mozilla::HangMonitor::NotifyActivity(mozilla::HangMonitor::kGeneralActivity);
   MOZ_ASSERT(NS_IsMainThread());
--- a/ipc/mscom/MainThreadInvoker.h
+++ b/ipc/mscom/MainThreadInvoker.h
@@ -5,35 +5,39 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_mscom_MainThreadInvoker_h
 #define mozilla_mscom_MainThreadInvoker_h
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/Move.h"
 #include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
 #include "nsCOMPtr.h"
 #include "nsThreadUtils.h"
 
 #include <windows.h>
 
 class nsIRunnable;
 
 namespace mozilla {
 namespace mscom {
 
 class MainThreadInvoker
 {
 public:
   MainThreadInvoker();
 
   bool Invoke(already_AddRefed<nsIRunnable>&& aRunnable);
+  const TimeDuration& GetDuration() const { return mDuration; }
   static HANDLE GetTargetThread() { return sMainThread; }
 
 private:
+  TimeDuration  mDuration;
+
   static bool InitStatics();
   static VOID CALLBACK MainThreadAPC(ULONG_PTR aParam);
 
   static HANDLE sMainThread;
 };
 
 template <typename Class, typename... Args>
 inline bool