Home | History | Annotate | Download | only in metrics
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/memory/shared_memory.h"
      6 #include "base/metrics/stats_counters.h"
      7 #include "base/metrics/stats_table.h"
      8 #include "base/process/kill.h"
      9 #include "base/strings/string_piece.h"
     10 #include "base/strings/stringprintf.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "base/test/multiprocess_test.h"
     13 #include "base/threading/platform_thread.h"
     14 #include "base/threading/simple_thread.h"
     15 #include "testing/gtest/include/gtest/gtest.h"
     16 #include "testing/multiprocess_func_list.h"
     17 
     18 namespace base {
     19 
     20 class StatsTableTest : public MultiProcessTest {
     21  public:
     22   void DeleteShmem(const std::string& name) {
     23     SharedMemory mem;
     24     mem.Delete(name);
     25   }
     26 };
     27 
     28 // Open a StatsTable and verify that we can write to each of the
     29 // locations in the table.
     30 TEST_F(StatsTableTest, VerifySlots) {
     31   const std::string kTableName = "VerifySlotsStatTable";
     32   const int kMaxThreads = 1;
     33   const int kMaxCounter = 5;
     34   DeleteShmem(kTableName);
     35   StatsTable table(kTableName, kMaxThreads, kMaxCounter);
     36 
     37   // Register a single thread.
     38   std::string thread_name = "mainThread";
     39   int slot_id = table.RegisterThread(thread_name);
     40   EXPECT_NE(slot_id, 0);
     41 
     42   // Fill up the table with counters.
     43   std::string counter_base_name = "counter";
     44   for (int index = 0; index < kMaxCounter; index++) {
     45     std::string counter_name = counter_base_name;
     46     base::StringAppendF(&counter_name, "counter.ctr%d", index);
     47     int counter_id = table.FindCounter(counter_name);
     48     EXPECT_GT(counter_id, 0);
     49   }
     50 
     51   // Try to allocate an additional thread.  Verify it fails.
     52   slot_id = table.RegisterThread("too many threads");
     53   EXPECT_EQ(slot_id, 0);
     54 
     55   // Try to allocate an additional counter.  Verify it fails.
     56   int counter_id = table.FindCounter(counter_base_name);
     57   EXPECT_EQ(counter_id, 0);
     58 
     59   DeleteShmem(kTableName);
     60 }
     61 
     62 // CounterZero will continually be set to 0.
     63 const std::string kCounterZero = "CounterZero";
     64 // Counter1313 will continually be set to 1313.
     65 const std::string kCounter1313 = "Counter1313";
     66 // CounterIncrement will be incremented each time.
     67 const std::string kCounterIncrement = "CounterIncrement";
     68 // CounterDecrement will be decremented each time.
     69 const std::string kCounterDecrement = "CounterDecrement";
     70 // CounterMixed will be incremented by odd numbered threads and
     71 // decremented by even threads.
     72 const std::string kCounterMixed = "CounterMixed";
     73 // The number of thread loops that we will do.
     74 const int kThreadLoops = 100;
     75 
     76 class StatsTableThread : public SimpleThread {
     77  public:
     78   StatsTableThread(std::string name, int id)
     79       : SimpleThread(name),
     80         id_(id) {}
     81 
     82   virtual void Run() OVERRIDE;
     83 
     84  private:
     85   int id_;
     86 };
     87 
     88 void StatsTableThread::Run() {
     89   // Each thread will open the shared memory and set counters
     90   // concurrently in a loop.  We'll use some pauses to
     91   // mixup the thread scheduling.
     92 
     93   StatsCounter zero_counter(kCounterZero);
     94   StatsCounter lucky13_counter(kCounter1313);
     95   StatsCounter increment_counter(kCounterIncrement);
     96   StatsCounter decrement_counter(kCounterDecrement);
     97   for (int index = 0; index < kThreadLoops; index++) {
     98     StatsCounter mixed_counter(kCounterMixed);  // create this one in the loop
     99     zero_counter.Set(0);
    100     lucky13_counter.Set(1313);
    101     increment_counter.Increment();
    102     decrement_counter.Decrement();
    103     if (id_ % 2)
    104       mixed_counter.Decrement();
    105     else
    106       mixed_counter.Increment();
    107     PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10));
    108   }
    109 }
    110 
    111 // Create a few threads and have them poke on their counters.
    112 // See http://crbug.com/10611 for more information.
    113 #if defined(OS_MACOSX) || defined(THREAD_SANITIZER)
    114 #define MAYBE_MultipleThreads DISABLED_MultipleThreads
    115 #else
    116 #define MAYBE_MultipleThreads MultipleThreads
    117 #endif
    118 TEST_F(StatsTableTest, MAYBE_MultipleThreads) {
    119   // Create a stats table.
    120   const std::string kTableName = "MultipleThreadStatTable";
    121   const int kMaxThreads = 20;
    122   const int kMaxCounter = 5;
    123   DeleteShmem(kTableName);
    124   StatsTable table(kTableName, kMaxThreads, kMaxCounter);
    125   StatsTable::set_current(&table);
    126 
    127   EXPECT_EQ(0, table.CountThreadsRegistered());
    128 
    129   // Spin up a set of threads to go bang on the various counters.
    130   // After we join the threads, we'll make sure the counters
    131   // contain the values we expected.
    132   StatsTableThread* threads[kMaxThreads];
    133 
    134   // Spawn the threads.
    135   for (int index = 0; index < kMaxThreads; index++) {
    136     threads[index] = new StatsTableThread("MultipleThreadsTest", index);
    137     threads[index]->Start();
    138   }
    139 
    140   // Wait for the threads to finish.
    141   for (int index = 0; index < kMaxThreads; index++) {
    142     threads[index]->Join();
    143     delete threads[index];
    144   }
    145 
    146   StatsCounter zero_counter(kCounterZero);
    147   StatsCounter lucky13_counter(kCounter1313);
    148   StatsCounter increment_counter(kCounterIncrement);
    149   StatsCounter decrement_counter(kCounterDecrement);
    150   StatsCounter mixed_counter(kCounterMixed);
    151 
    152   // Verify the various counters are correct.
    153   std::string name;
    154   name = "c:" + kCounterZero;
    155   EXPECT_EQ(0, table.GetCounterValue(name));
    156   name = "c:" + kCounter1313;
    157   EXPECT_EQ(1313 * kMaxThreads,
    158       table.GetCounterValue(name));
    159   name = "c:" + kCounterIncrement;
    160   EXPECT_EQ(kMaxThreads * kThreadLoops,
    161       table.GetCounterValue(name));
    162   name = "c:" + kCounterDecrement;
    163   EXPECT_EQ(-kMaxThreads * kThreadLoops,
    164       table.GetCounterValue(name));
    165   name = "c:" + kCounterMixed;
    166   EXPECT_EQ((kMaxThreads % 2) * kThreadLoops,
    167       table.GetCounterValue(name));
    168   EXPECT_EQ(0, table.CountThreadsRegistered());
    169 
    170   DeleteShmem(kTableName);
    171 }
    172 
    173 const std::string kMPTableName = "MultipleProcessStatTable";
    174 
    175 MULTIPROCESS_TEST_MAIN(StatsTableMultipleProcessMain) {
    176   // Each process will open the shared memory and set counters
    177   // concurrently in a loop.  We'll use some pauses to
    178   // mixup the scheduling.
    179 
    180   StatsTable table(kMPTableName, 0, 0);
    181   StatsTable::set_current(&table);
    182   StatsCounter zero_counter(kCounterZero);
    183   StatsCounter lucky13_counter(kCounter1313);
    184   StatsCounter increment_counter(kCounterIncrement);
    185   StatsCounter decrement_counter(kCounterDecrement);
    186   for (int index = 0; index < kThreadLoops; index++) {
    187     zero_counter.Set(0);
    188     lucky13_counter.Set(1313);
    189     increment_counter.Increment();
    190     decrement_counter.Decrement();
    191     PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10));
    192   }
    193   return 0;
    194 }
    195 
    196 // Create a few processes and have them poke on their counters.
    197 // This test is slow and flaky http://crbug.com/10611
    198 TEST_F(StatsTableTest, DISABLED_MultipleProcesses) {
    199   // Create a stats table.
    200   const int kMaxProcs = 20;
    201   const int kMaxCounter = 5;
    202   DeleteShmem(kMPTableName);
    203   StatsTable table(kMPTableName, kMaxProcs, kMaxCounter);
    204   StatsTable::set_current(&table);
    205   EXPECT_EQ(0, table.CountThreadsRegistered());
    206 
    207   // Spin up a set of processes to go bang on the various counters.
    208   // After we join the processes, we'll make sure the counters
    209   // contain the values we expected.
    210   ProcessHandle procs[kMaxProcs];
    211 
    212   // Spawn the processes.
    213   for (int16 index = 0; index < kMaxProcs; index++) {
    214     procs[index] = this->SpawnChild("StatsTableMultipleProcessMain", false);
    215     EXPECT_NE(kNullProcessHandle, procs[index]);
    216   }
    217 
    218   // Wait for the processes to finish.
    219   for (int index = 0; index < kMaxProcs; index++) {
    220     EXPECT_TRUE(WaitForSingleProcess(
    221         procs[index], base::TimeDelta::FromMinutes(1)));
    222     CloseProcessHandle(procs[index]);
    223   }
    224 
    225   StatsCounter zero_counter(kCounterZero);
    226   StatsCounter lucky13_counter(kCounter1313);
    227   StatsCounter increment_counter(kCounterIncrement);
    228   StatsCounter decrement_counter(kCounterDecrement);
    229 
    230   // Verify the various counters are correct.
    231   std::string name;
    232   name = "c:" + kCounterZero;
    233   EXPECT_EQ(0, table.GetCounterValue(name));
    234   name = "c:" + kCounter1313;
    235   EXPECT_EQ(1313 * kMaxProcs,
    236       table.GetCounterValue(name));
    237   name = "c:" + kCounterIncrement;
    238   EXPECT_EQ(kMaxProcs * kThreadLoops,
    239       table.GetCounterValue(name));
    240   name = "c:" + kCounterDecrement;
    241   EXPECT_EQ(-kMaxProcs * kThreadLoops,
    242       table.GetCounterValue(name));
    243   EXPECT_EQ(0, table.CountThreadsRegistered());
    244 
    245   DeleteShmem(kMPTableName);
    246 }
    247 
    248 class MockStatsCounter : public StatsCounter {
    249  public:
    250   explicit MockStatsCounter(const std::string& name)
    251       : StatsCounter(name) {}
    252   int* Pointer() { return GetPtr(); }
    253 };
    254 
    255 // Test some basic StatsCounter operations
    256 TEST_F(StatsTableTest, StatsCounter) {
    257   // Create a stats table.
    258   const std::string kTableName = "StatTable";
    259   const int kMaxThreads = 20;
    260   const int kMaxCounter = 5;
    261   DeleteShmem(kTableName);
    262   StatsTable table(kTableName, kMaxThreads, kMaxCounter);
    263   StatsTable::set_current(&table);
    264 
    265   MockStatsCounter foo("foo");
    266 
    267   // Test initial state.
    268   EXPECT_TRUE(foo.Enabled());
    269   ASSERT_NE(foo.Pointer(), static_cast<int*>(0));
    270   EXPECT_EQ(0, *(foo.Pointer()));
    271   EXPECT_EQ(0, table.GetCounterValue("c:foo"));
    272 
    273   // Test Increment.
    274   while (*(foo.Pointer()) < 123) foo.Increment();
    275   EXPECT_EQ(123, table.GetCounterValue("c:foo"));
    276   foo.Add(0);
    277   EXPECT_EQ(123, table.GetCounterValue("c:foo"));
    278   foo.Add(-1);
    279   EXPECT_EQ(122, table.GetCounterValue("c:foo"));
    280 
    281   // Test Set.
    282   foo.Set(0);
    283   EXPECT_EQ(0, table.GetCounterValue("c:foo"));
    284   foo.Set(100);
    285   EXPECT_EQ(100, table.GetCounterValue("c:foo"));
    286   foo.Set(-1);
    287   EXPECT_EQ(-1, table.GetCounterValue("c:foo"));
    288   foo.Set(0);
    289   EXPECT_EQ(0, table.GetCounterValue("c:foo"));
    290 
    291   // Test Decrement.
    292   foo.Subtract(1);
    293   EXPECT_EQ(-1, table.GetCounterValue("c:foo"));
    294   foo.Subtract(0);
    295   EXPECT_EQ(-1, table.GetCounterValue("c:foo"));
    296   foo.Subtract(-1);
    297   EXPECT_EQ(0, table.GetCounterValue("c:foo"));
    298 
    299   DeleteShmem(kTableName);
    300 }
    301 
    302 class MockStatsCounterTimer : public StatsCounterTimer {
    303  public:
    304   explicit MockStatsCounterTimer(const std::string& name)
    305       : StatsCounterTimer(name) {}
    306 
    307   TimeTicks start_time() { return start_time_; }
    308   TimeTicks stop_time() { return stop_time_; }
    309 };
    310 
    311 // Test some basic StatsCounterTimer operations
    312 TEST_F(StatsTableTest, StatsCounterTimer) {
    313   // Create a stats table.
    314   const std::string kTableName = "StatTable";
    315   const int kMaxThreads = 20;
    316   const int kMaxCounter = 5;
    317   DeleteShmem(kTableName);
    318   StatsTable table(kTableName, kMaxThreads, kMaxCounter);
    319   StatsTable::set_current(&table);
    320 
    321   MockStatsCounterTimer bar("bar");
    322 
    323   // Test initial state.
    324   EXPECT_FALSE(bar.Running());
    325   EXPECT_TRUE(bar.start_time().is_null());
    326   EXPECT_TRUE(bar.stop_time().is_null());
    327 
    328   const TimeDelta kDuration = TimeDelta::FromMilliseconds(100);
    329 
    330   // Do some timing.
    331   bar.Start();
    332   PlatformThread::Sleep(kDuration);
    333   bar.Stop();
    334   EXPECT_GT(table.GetCounterValue("t:bar"), 0);
    335   EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar"));
    336 
    337   // Verify that timing again is additive.
    338   bar.Start();
    339   PlatformThread::Sleep(kDuration);
    340   bar.Stop();
    341   EXPECT_GT(table.GetCounterValue("t:bar"), 0);
    342   EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar"));
    343   DeleteShmem(kTableName);
    344 }
    345 
    346 // Test some basic StatsRate operations
    347 TEST_F(StatsTableTest, StatsRate) {
    348   // Create a stats table.
    349   const std::string kTableName = "StatTable";
    350   const int kMaxThreads = 20;
    351   const int kMaxCounter = 5;
    352   DeleteShmem(kTableName);
    353   StatsTable table(kTableName, kMaxThreads, kMaxCounter);
    354   StatsTable::set_current(&table);
    355 
    356   StatsRate baz("baz");
    357 
    358   // Test initial state.
    359   EXPECT_FALSE(baz.Running());
    360   EXPECT_EQ(0, table.GetCounterValue("c:baz"));
    361   EXPECT_EQ(0, table.GetCounterValue("t:baz"));
    362 
    363   const TimeDelta kDuration = TimeDelta::FromMilliseconds(100);
    364 
    365   // Do some timing.
    366   baz.Start();
    367   PlatformThread::Sleep(kDuration);
    368   baz.Stop();
    369   EXPECT_EQ(1, table.GetCounterValue("c:baz"));
    370   EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:baz"));
    371 
    372   // Verify that timing again is additive.
    373   baz.Start();
    374   PlatformThread::Sleep(kDuration);
    375   baz.Stop();
    376   EXPECT_EQ(2, table.GetCounterValue("c:baz"));
    377   EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:baz"));
    378   DeleteShmem(kTableName);
    379 }
    380 
    381 // Test some basic StatsScope operations
    382 TEST_F(StatsTableTest, StatsScope) {
    383   // Create a stats table.
    384   const std::string kTableName = "StatTable";
    385   const int kMaxThreads = 20;
    386   const int kMaxCounter = 5;
    387   DeleteShmem(kTableName);
    388   StatsTable table(kTableName, kMaxThreads, kMaxCounter);
    389   StatsTable::set_current(&table);
    390 
    391   StatsCounterTimer foo("foo");
    392   StatsRate bar("bar");
    393 
    394   // Test initial state.
    395   EXPECT_EQ(0, table.GetCounterValue("t:foo"));
    396   EXPECT_EQ(0, table.GetCounterValue("t:bar"));
    397   EXPECT_EQ(0, table.GetCounterValue("c:bar"));
    398 
    399   const TimeDelta kDuration = TimeDelta::FromMilliseconds(100);
    400 
    401   // Try a scope.
    402   {
    403     StatsScope<StatsCounterTimer> timer(foo);
    404     StatsScope<StatsRate> timer2(bar);
    405     PlatformThread::Sleep(kDuration);
    406   }
    407   EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:foo"));
    408   EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar"));
    409   EXPECT_EQ(1, table.GetCounterValue("c:bar"));
    410 
    411   // Try a second scope.
    412   {
    413     StatsScope<StatsCounterTimer> timer(foo);
    414     StatsScope<StatsRate> timer2(bar);
    415     PlatformThread::Sleep(kDuration);
    416   }
    417   EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:foo"));
    418   EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar"));
    419   EXPECT_EQ(2, table.GetCounterValue("c:bar"));
    420 
    421   DeleteShmem(kTableName);
    422 }
    423 
    424 }  // namespace base
    425