gecko-dev/xpcom/tests/gtest/TestThreadMetrics.cpp

184 строки
5.6 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 "gtest/gtest.h"
#include "gmock/gmock.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/TabGroup.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/TaskCategory.h"
#include "mozilla/PerformanceCounter.h"
#include "nsThreadUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsThread.h"
using namespace mozilla;
using mozilla::Runnable;
class MockSchedulerGroup : public SchedulerGroup {
public:
explicit MockSchedulerGroup(mozilla::dom::DocGroup* aDocGroup)
: mDocGroup(aDocGroup) {}
NS_INLINE_DECL_REFCOUNTING(MockSchedulerGroup);
MOCK_METHOD1(SetValidatingAccess, void(ValidationType aType));
mozilla::dom::DocGroup* DocGroup() { return mDocGroup; }
protected:
virtual ~MockSchedulerGroup() = default;
private:
mozilla::dom::DocGroup* mDocGroup;
};
typedef testing::NiceMock<MockSchedulerGroup> MSchedulerGroup;
/* Timed runnable which simulates some execution time
* and can run a nested runnable.
*/
class TimedRunnable final : public Runnable {
public:
explicit TimedRunnable(uint32_t aExecutionTime1, uint32_t aExecutionTime2,
uint32_t aSubExecutionTime)
: Runnable("TimedRunnable"),
mExecutionTime1(aExecutionTime1),
mExecutionTime2(aExecutionTime2),
mSubExecutionTime(aSubExecutionTime) {}
NS_IMETHODIMP Run() {
PR_Sleep(PR_MillisecondsToInterval(mExecutionTime1 + 5));
if (mSubExecutionTime > 0) {
// Dispatch another runnable so nsThread::ProcessNextEvent is called
// recursively
nsCOMPtr<nsIThread> thread = do_GetMainThread();
nsCOMPtr<nsIRunnable> runnable =
new TimedRunnable(mSubExecutionTime, 0, 0);
thread->Dispatch(runnable, NS_DISPATCH_NORMAL);
(void)NS_ProcessNextEvent(thread, false);
}
PR_Sleep(PR_MillisecondsToInterval(mExecutionTime2 + 5));
return NS_OK;
}
private:
uint32_t mExecutionTime1;
uint32_t mExecutionTime2;
uint32_t mSubExecutionTime;
};
/* test class used for all metrics tests
*
* - sets up the enable_scheduler_timing pref
* - provides a function to dispatch runnables and spin the loop
*/
class ThreadMetrics : public ::testing::Test {
public:
explicit ThreadMetrics() = default;
protected:
virtual void SetUp() {
// building the TabGroup/DocGroup structure
nsCString key = NS_LITERAL_CSTRING("key");
RefPtr<dom::Document> doc;
RefPtr<dom::TabGroup> tabGroup = new dom::TabGroup(false);
mDocGroup = tabGroup->AddDocument(key, doc);
mSchedulerGroup = new MSchedulerGroup(mDocGroup);
mCounter = mDocGroup->GetPerformanceCounter();
mThreadMgr = do_GetService("@mozilla.org/thread-manager;1");
mOther = DispatchCategory(TaskCategory::Other).GetValue();
mDispatchCount = (uint32_t)TaskCategory::Other + 1;
}
virtual void TearDown() {
// and remove the document from the doc group (actually, a nullptr)
mDocGroup->RemoveDocument(nullptr);
mDocGroup = nullptr;
ProcessAllEvents();
}
// this is used to get rid of transient events
void initScheduler() { ProcessAllEvents(); }
nsresult Dispatch(uint32_t aExecutionTime1, uint32_t aExecutionTime2,
uint32_t aSubExecutionTime) {
ProcessAllEvents();
nsCOMPtr<nsIRunnable> runnable =
new TimedRunnable(aExecutionTime1, aExecutionTime2, aSubExecutionTime);
runnable = new SchedulerGroup::Runnable(runnable.forget(), mSchedulerGroup,
mDocGroup);
return mDocGroup->Dispatch(TaskCategory::Other, runnable.forget());
}
void ProcessAllEvents() { mThreadMgr->SpinEventLoopUntilEmpty(); }
uint32_t mOther;
bool mOldPref;
RefPtr<MSchedulerGroup> mSchedulerGroup;
RefPtr<mozilla::dom::DocGroup> mDocGroup;
RefPtr<mozilla::PerformanceCounter> mCounter;
nsCOMPtr<nsIThreadManager> mThreadMgr;
uint32_t mDispatchCount;
};
TEST_F(ThreadMetrics, CollectMetrics) {
nsresult rv;
initScheduler();
// Dispatching a runnable that will last for +50ms
rv = Dispatch(25, 25, 0);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Flush the queue
ProcessAllEvents();
// Let's look at the task category "other" counter
ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u);
// other counters should stay empty
for (uint32_t i = 0; i < mDispatchCount; i++) {
if (i != mOther) {
ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u);
}
}
// Did we get incremented in the docgroup ?
uint64_t duration = mCounter->GetExecutionDuration();
ASSERT_GE(duration, 50000u);
}
TEST_F(ThreadMetrics, CollectRecursiveMetrics) {
nsresult rv;
initScheduler();
// Dispatching a runnable that will last for +50ms
// and run another one recursively that lasts for 400ms
rv = Dispatch(25, 25, 400);
ASSERT_TRUE(NS_SUCCEEDED(rv));
// Flush the queue
ProcessAllEvents();
// let's look at the counters
ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u);
// other counters should stay empty
for (uint32_t i = 0; i < mDispatchCount; i++) {
if (i != mOther) {
ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u);
}
}
// did we get incremented in the docgroup ?
uint64_t duration = mCounter->GetExecutionDuration();
ASSERT_GE(duration, 50000u);
// let's make sure we don't count the time spent in recursive calls
ASSERT_LT(duration, 300000u);
}