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