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