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