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