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