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.
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"
20 class StatsTableTest
: public MultiProcessTest
{
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
);
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);
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);
44 // Try to allocate an additional thread. Verify it fails.
45 slot_id
= table
.RegisterThread("too many threads");
46 EXPECT_EQ(slot_id
, 0);
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);
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;
67 class StatsTableThread
: public SimpleThread
{
69 StatsTableThread(std::string name
, int id
)
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.
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
91 lucky13_counter
.Set(1313);
92 increment_counter
.Increment();
93 decrement_counter
.Decrement();
95 mixed_counter
.Decrement();
97 mixed_counter
.Increment();
98 PlatformThread::Sleep(TimeDelta::FromMilliseconds(index
% 10));
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
111 #define MAYBE_MultipleThreads 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
);
120 EXPECT_EQ(0, table
.CountThreadsRegistered());
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
];
127 // Spawn the threads.
128 for (int index
= 0; index
< kMaxThreads
; index
++) {
129 threads
[index
] = new StatsTableThread("MultipleThreadsTest", index
);
130 threads
[index
]->Start();
133 // Wait for the threads to finish.
134 for (int index
= 0; index
< kMaxThreads
; index
++) {
135 threads
[index
]->Join();
136 delete threads
[index
];
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
);
145 // Verify the various counters are correct.
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());
164 // This multiprocess test only runs on Windows. On Posix, the shared memory
165 // handle is not sent between the processes properly.
167 const std::string kMPTableName
= "MultipleProcessStatTable";
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.
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
++) {
182 lucky13_counter
.Set(1313);
183 increment_counter
.Increment();
184 decrement_counter
.Decrement();
185 PlatformThread::Sleep(TimeDelta::FromMilliseconds(index
% 10));
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());
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
];
205 // Spawn the processes.
206 for (int16 index
= 0; index
< kMaxProcs
; index
++) {
207 procs
[index
] = SpawnChild("StatsTableMultipleProcessMain");
208 EXPECT_NE(kNullProcessHandle
, procs
[index
]);
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
]);
218 StatsCounter
zero_counter(kCounterZero
);
219 StatsCounter
lucky13_counter(kCounter1313
);
220 StatsCounter
increment_counter(kCounterIncrement
);
221 StatsCounter
decrement_counter(kCounterDecrement
);
223 // Verify the various counters are correct.
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());
240 class MockStatsCounter
: public StatsCounter
{
242 explicit MockStatsCounter(const std::string
& name
)
243 : StatsCounter(name
) {}
244 int* Pointer() { return GetPtr(); }
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
);
255 MockStatsCounter
foo("foo");
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"));
264 while (*(foo
.Pointer()) < 123) foo
.Increment();
265 EXPECT_EQ(123, table
.GetCounterValue("c:foo"));
267 EXPECT_EQ(123, table
.GetCounterValue("c:foo"));
269 EXPECT_EQ(122, table
.GetCounterValue("c:foo"));
273 EXPECT_EQ(0, table
.GetCounterValue("c:foo"));
275 EXPECT_EQ(100, table
.GetCounterValue("c:foo"));
277 EXPECT_EQ(-1, table
.GetCounterValue("c:foo"));
279 EXPECT_EQ(0, table
.GetCounterValue("c:foo"));
283 EXPECT_EQ(-1, table
.GetCounterValue("c:foo"));
285 EXPECT_EQ(-1, table
.GetCounterValue("c:foo"));
287 EXPECT_EQ(0, table
.GetCounterValue("c:foo"));
290 class MockStatsCounterTimer
: public StatsCounterTimer
{
292 explicit MockStatsCounterTimer(const std::string
& name
)
293 : StatsCounterTimer(name
) {}
295 TimeTicks
start_time() { return start_time_
; }
296 TimeTicks
stop_time() { return stop_time_
; }
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
);
307 MockStatsCounterTimer
bar("bar");
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());
314 const TimeDelta kDuration
= TimeDelta::FromMilliseconds(100);
318 PlatformThread::Sleep(kDuration
);
320 EXPECT_GT(table
.GetCounterValue("t:bar"), 0);
321 EXPECT_LE(kDuration
.InMilliseconds(), table
.GetCounterValue("t:bar"));
323 // Verify that timing again is additive.
325 PlatformThread::Sleep(kDuration
);
327 EXPECT_GT(table
.GetCounterValue("t:bar"), 0);
328 EXPECT_LE(kDuration
.InMilliseconds() * 2, table
.GetCounterValue("t:bar"));
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
);
339 StatsRate
baz("baz");
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"));
346 const TimeDelta kDuration
= TimeDelta::FromMilliseconds(100);
350 PlatformThread::Sleep(kDuration
);
352 EXPECT_EQ(1, table
.GetCounterValue("c:baz"));
353 EXPECT_LE(kDuration
.InMilliseconds(), table
.GetCounterValue("t:baz"));
355 // Verify that timing again is additive.
357 PlatformThread::Sleep(kDuration
);
359 EXPECT_EQ(2, table
.GetCounterValue("c:baz"));
360 EXPECT_LE(kDuration
.InMilliseconds() * 2, table
.GetCounterValue("t:baz"));
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
);
371 StatsCounterTimer
foo("foo");
372 StatsRate
bar("bar");
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"));
379 const TimeDelta kDuration
= TimeDelta::FromMilliseconds(100);
383 StatsScope
<StatsCounterTimer
> timer(foo
);
384 StatsScope
<StatsRate
> timer2(bar
);
385 PlatformThread::Sleep(kDuration
);
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"));
391 // Try a second scope.
393 StatsScope
<StatsCounterTimer
> timer(foo
);
394 StatsScope
<StatsRate
> timer2(bar
);
395 PlatformThread::Sleep(kDuration
);
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"));