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 #if defined(OS_MACOSX) || defined(THREAD_SANITIZER)
105 #define MAYBE_MultipleThreads DISABLED_MultipleThreads
106 #else
107 #define MAYBE_MultipleThreads MultipleThreads
108 #endif
TEST_F(StatsTableTest,MAYBE_MultipleThreads)109 TEST_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)
163 const std::string kMPTableName = "MultipleProcessStatTable";
164
MULTIPROCESS_TEST_MAIN(StatsTableMultipleProcessMain)165 MULTIPROCESS_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
TEST_F(StatsTableTest,DISABLED_MultipleProcesses)188 TEST_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
236 class MockStatsCounter : public StatsCounter {
237 public:
MockStatsCounter(const std::string & name)238 explicit MockStatsCounter(const std::string& name)
239 : StatsCounter(name) {}
Pointer()240 int* Pointer() { return GetPtr(); }
241 };
242
243 // Test some basic StatsCounter operations
TEST_F(StatsTableTest,StatsCounter)244 TEST_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
286 class MockStatsCounterTimer : public StatsCounterTimer {
287 public:
MockStatsCounterTimer(const std::string & name)288 explicit MockStatsCounterTimer(const std::string& name)
289 : StatsCounterTimer(name) {}
290
start_time()291 TimeTicks start_time() { return start_time_; }
stop_time()292 TimeTicks stop_time() { return stop_time_; }
293 };
294
295 // Test some basic StatsCounterTimer operations
TEST_F(StatsTableTest,StatsCounterTimer)296 TEST_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
TEST_F(StatsTableTest,StatsRate)328 TEST_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
TEST_F(StatsTableTest,StatsScope)360 TEST_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