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