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