OLD | NEW |
| (Empty) |
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. | |
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: | |
69 StatsTableThread(std::string name, int id) | |
70 : SimpleThread(name), | |
71 id_(id) {} | |
72 | |
73 void Run() override; | |
74 | |
75 private: | |
76 int id_; | |
77 }; | |
78 | |
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 | |
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 | |
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 | |
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 Process procs[kMaxProcs]; | |
204 | |
205 // Spawn the processes. | |
206 for (int16 index = 0; index < kMaxProcs; index++) { | |
207 procs[index] = SpawnChild("StatsTableMultipleProcessMain"); | |
208 EXPECT_TRUE(procs[index].IsValid()); | |
209 } | |
210 | |
211 // Wait for the processes to finish. | |
212 for (int index = 0; index < kMaxProcs; index++) { | |
213 EXPECT_TRUE(WaitForSingleProcess(procs[index].Handle(), | |
214 base::TimeDelta::FromMinutes(1))); | |
215 procs[index].Close(); | |
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: | |
242 explicit MockStatsCounter(const std::string& name) | |
243 : StatsCounter(name) {} | |
244 int* Pointer() { return GetPtr(); } | |
245 }; | |
246 | |
247 // Test some basic StatsCounter operations | |
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: | |
292 explicit MockStatsCounterTimer(const std::string& name) | |
293 : StatsCounterTimer(name) {} | |
294 | |
295 TimeTicks start_time() { return start_time_; } | |
296 TimeTicks stop_time() { return stop_time_; } | |
297 }; | |
298 | |
299 // Test some basic StatsCounterTimer operations | |
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 | |
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 | |
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 | |
OLD | NEW |