// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/memory/shared_memory.h" #include "base/metrics/stats_counters.h" #include "base/metrics/stats_table.h" #include "base/process/kill.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/test/multiprocess_test.h" #include "base/threading/platform_thread.h" #include "base/threading/simple_thread.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" namespace base { class StatsTableTest : public MultiProcessTest { }; // Open a StatsTable and verify that we can write to each of the // locations in the table. TEST_F(StatsTableTest, VerifySlots) { const int kMaxThreads = 1; const int kMaxCounter = 5; StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter); // Register a single thread. std::string thread_name = "mainThread"; int slot_id = table.RegisterThread(thread_name); EXPECT_NE(slot_id, 0); // Fill up the table with counters. std::string counter_base_name = "counter"; for (int index = 0; index < kMaxCounter; index++) { std::string counter_name = counter_base_name; base::StringAppendF(&counter_name, "counter.ctr%d", index); int counter_id = table.FindCounter(counter_name); EXPECT_GT(counter_id, 0); } // Try to allocate an additional thread. Verify it fails. slot_id = table.RegisterThread("too many threads"); EXPECT_EQ(slot_id, 0); // Try to allocate an additional counter. Verify it fails. int counter_id = table.FindCounter(counter_base_name); EXPECT_EQ(counter_id, 0); } // CounterZero will continually be set to 0. const std::string kCounterZero = "CounterZero"; // Counter1313 will continually be set to 1313. const std::string kCounter1313 = "Counter1313"; // CounterIncrement will be incremented each time. const std::string kCounterIncrement = "CounterIncrement"; // CounterDecrement will be decremented each time. const std::string kCounterDecrement = "CounterDecrement"; // CounterMixed will be incremented by odd numbered threads and // decremented by even threads. const std::string kCounterMixed = "CounterMixed"; // The number of thread loops that we will do. const int kThreadLoops = 100; class StatsTableThread : public SimpleThread { public: StatsTableThread(std::string name, int id) : SimpleThread(name), id_(id) {} virtual void Run() OVERRIDE; private: int id_; }; void StatsTableThread::Run() { // Each thread will open the shared memory and set counters // concurrently in a loop. We'll use some pauses to // mixup the thread scheduling. StatsCounter zero_counter(kCounterZero); StatsCounter lucky13_counter(kCounter1313); StatsCounter increment_counter(kCounterIncrement); StatsCounter decrement_counter(kCounterDecrement); for (int index = 0; index < kThreadLoops; index++) { StatsCounter mixed_counter(kCounterMixed); // create this one in the loop zero_counter.Set(0); lucky13_counter.Set(1313); increment_counter.Increment(); decrement_counter.Decrement(); if (id_ % 2) mixed_counter.Decrement(); else mixed_counter.Increment(); PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10)); } } // Create a few threads and have them poke on their counters. // See http://crbug.com/10611 for more information. #if defined(OS_MACOSX) || defined(THREAD_SANITIZER) #define MAYBE_MultipleThreads DISABLED_MultipleThreads #else #define MAYBE_MultipleThreads MultipleThreads #endif TEST_F(StatsTableTest, MAYBE_MultipleThreads) { // Create a stats table. const int kMaxThreads = 20; const int kMaxCounter = 5; StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter); StatsTable::set_current(&table); EXPECT_EQ(0, table.CountThreadsRegistered()); // Spin up a set of threads to go bang on the various counters. // After we join the threads, we'll make sure the counters // contain the values we expected. StatsTableThread* threads[kMaxThreads]; // Spawn the threads. for (int index = 0; index < kMaxThreads; index++) { threads[index] = new StatsTableThread("MultipleThreadsTest", index); threads[index]->Start(); } // Wait for the threads to finish. for (int index = 0; index < kMaxThreads; index++) { threads[index]->Join(); delete threads[index]; } StatsCounter zero_counter(kCounterZero); StatsCounter lucky13_counter(kCounter1313); StatsCounter increment_counter(kCounterIncrement); StatsCounter decrement_counter(kCounterDecrement); StatsCounter mixed_counter(kCounterMixed); // Verify the various counters are correct. std::string name; name = "c:" + kCounterZero; EXPECT_EQ(0, table.GetCounterValue(name)); name = "c:" + kCounter1313; EXPECT_EQ(1313 * kMaxThreads, table.GetCounterValue(name)); name = "c:" + kCounterIncrement; EXPECT_EQ(kMaxThreads * kThreadLoops, table.GetCounterValue(name)); name = "c:" + kCounterDecrement; EXPECT_EQ(-kMaxThreads * kThreadLoops, table.GetCounterValue(name)); name = "c:" + kCounterMixed; EXPECT_EQ((kMaxThreads % 2) * kThreadLoops, table.GetCounterValue(name)); EXPECT_EQ(0, table.CountThreadsRegistered()); } // This multiprocess test only runs on Windows. On Posix, the shared memory // handle is not sent between the processes properly. #if defined(OS_WIN) const std::string kMPTableName = "MultipleProcessStatTable"; MULTIPROCESS_TEST_MAIN(StatsTableMultipleProcessMain) { // Each process will open the shared memory and set counters // concurrently in a loop. We'll use some pauses to // mixup the scheduling. StatsTable table(kMPTableName, 0, 0); StatsTable::set_current(&table); StatsCounter zero_counter(kCounterZero); StatsCounter lucky13_counter(kCounter1313); StatsCounter increment_counter(kCounterIncrement); StatsCounter decrement_counter(kCounterDecrement); for (int index = 0; index < kThreadLoops; index++) { zero_counter.Set(0); lucky13_counter.Set(1313); increment_counter.Increment(); decrement_counter.Decrement(); PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10)); } return 0; } // Create a few processes and have them poke on their counters. // This test is slow and flaky http://crbug.com/10611 TEST_F(StatsTableTest, DISABLED_MultipleProcesses) { // Create a stats table. const int kMaxProcs = 20; const int kMaxCounter = 5; StatsTable table(kMPTableName, kMaxProcs, kMaxCounter); StatsTable::set_current(&table); EXPECT_EQ(0, table.CountThreadsRegistered()); // Spin up a set of processes to go bang on the various counters. // After we join the processes, we'll make sure the counters // contain the values we expected. ProcessHandle procs[kMaxProcs]; // Spawn the processes. for (int16 index = 0; index < kMaxProcs; index++) { procs[index] = SpawnChild("StatsTableMultipleProcessMain"); EXPECT_NE(kNullProcessHandle, procs[index]); } // Wait for the processes to finish. for (int index = 0; index < kMaxProcs; index++) { EXPECT_TRUE(WaitForSingleProcess( procs[index], base::TimeDelta::FromMinutes(1))); CloseProcessHandle(procs[index]); } StatsCounter zero_counter(kCounterZero); StatsCounter lucky13_counter(kCounter1313); StatsCounter increment_counter(kCounterIncrement); StatsCounter decrement_counter(kCounterDecrement); // Verify the various counters are correct. std::string name; name = "c:" + kCounterZero; EXPECT_EQ(0, table.GetCounterValue(name)); name = "c:" + kCounter1313; EXPECT_EQ(1313 * kMaxProcs, table.GetCounterValue(name)); name = "c:" + kCounterIncrement; EXPECT_EQ(kMaxProcs * kThreadLoops, table.GetCounterValue(name)); name = "c:" + kCounterDecrement; EXPECT_EQ(-kMaxProcs * kThreadLoops, table.GetCounterValue(name)); EXPECT_EQ(0, table.CountThreadsRegistered()); } #endif class MockStatsCounter : public StatsCounter { public: explicit MockStatsCounter(const std::string& name) : StatsCounter(name) {} int* Pointer() { return GetPtr(); } }; // Test some basic StatsCounter operations TEST_F(StatsTableTest, StatsCounter) { // Create a stats table. const int kMaxThreads = 20; const int kMaxCounter = 5; StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter); StatsTable::set_current(&table); MockStatsCounter foo("foo"); // Test initial state. EXPECT_TRUE(foo.Enabled()); ASSERT_NE(foo.Pointer(), static_cast(0)); EXPECT_EQ(0, *(foo.Pointer())); EXPECT_EQ(0, table.GetCounterValue("c:foo")); // Test Increment. while (*(foo.Pointer()) < 123) foo.Increment(); EXPECT_EQ(123, table.GetCounterValue("c:foo")); foo.Add(0); EXPECT_EQ(123, table.GetCounterValue("c:foo")); foo.Add(-1); EXPECT_EQ(122, table.GetCounterValue("c:foo")); // Test Set. foo.Set(0); EXPECT_EQ(0, table.GetCounterValue("c:foo")); foo.Set(100); EXPECT_EQ(100, table.GetCounterValue("c:foo")); foo.Set(-1); EXPECT_EQ(-1, table.GetCounterValue("c:foo")); foo.Set(0); EXPECT_EQ(0, table.GetCounterValue("c:foo")); // Test Decrement. foo.Subtract(1); EXPECT_EQ(-1, table.GetCounterValue("c:foo")); foo.Subtract(0); EXPECT_EQ(-1, table.GetCounterValue("c:foo")); foo.Subtract(-1); EXPECT_EQ(0, table.GetCounterValue("c:foo")); } class MockStatsCounterTimer : public StatsCounterTimer { public: explicit MockStatsCounterTimer(const std::string& name) : StatsCounterTimer(name) {} TimeTicks start_time() { return start_time_; } TimeTicks stop_time() { return stop_time_; } }; // Test some basic StatsCounterTimer operations TEST_F(StatsTableTest, StatsCounterTimer) { // Create a stats table. const int kMaxThreads = 20; const int kMaxCounter = 5; StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter); StatsTable::set_current(&table); MockStatsCounterTimer bar("bar"); // Test initial state. EXPECT_FALSE(bar.Running()); EXPECT_TRUE(bar.start_time().is_null()); EXPECT_TRUE(bar.stop_time().is_null()); const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); // Do some timing. bar.Start(); PlatformThread::Sleep(kDuration); bar.Stop(); EXPECT_GT(table.GetCounterValue("t:bar"), 0); EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar")); // Verify that timing again is additive. bar.Start(); PlatformThread::Sleep(kDuration); bar.Stop(); EXPECT_GT(table.GetCounterValue("t:bar"), 0); EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar")); } // Test some basic StatsRate operations TEST_F(StatsTableTest, StatsRate) { // Create a stats table. const int kMaxThreads = 20; const int kMaxCounter = 5; StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter); StatsTable::set_current(&table); StatsRate baz("baz"); // Test initial state. EXPECT_FALSE(baz.Running()); EXPECT_EQ(0, table.GetCounterValue("c:baz")); EXPECT_EQ(0, table.GetCounterValue("t:baz")); const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); // Do some timing. baz.Start(); PlatformThread::Sleep(kDuration); baz.Stop(); EXPECT_EQ(1, table.GetCounterValue("c:baz")); EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:baz")); // Verify that timing again is additive. baz.Start(); PlatformThread::Sleep(kDuration); baz.Stop(); EXPECT_EQ(2, table.GetCounterValue("c:baz")); EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:baz")); } // Test some basic StatsScope operations TEST_F(StatsTableTest, StatsScope) { // Create a stats table. const int kMaxThreads = 20; const int kMaxCounter = 5; StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter); StatsTable::set_current(&table); StatsCounterTimer foo("foo"); StatsRate bar("bar"); // Test initial state. EXPECT_EQ(0, table.GetCounterValue("t:foo")); EXPECT_EQ(0, table.GetCounterValue("t:bar")); EXPECT_EQ(0, table.GetCounterValue("c:bar")); const TimeDelta kDuration = TimeDelta::FromMilliseconds(100); // Try a scope. { StatsScope timer(foo); StatsScope timer2(bar); PlatformThread::Sleep(kDuration); } EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:foo")); EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar")); EXPECT_EQ(1, table.GetCounterValue("c:bar")); // Try a second scope. { StatsScope timer(foo); StatsScope timer2(bar); PlatformThread::Sleep(kDuration); } EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:foo")); EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar")); EXPECT_EQ(2, table.GetCounterValue("c:bar")); } } // namespace base