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
18namespace base {
19
20class StatsTableTest : public MultiProcessTest {
21};
22
23// Open a StatsTable and verify that we can write to each of the
24// locations in the table.
25TEST_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.
54const std::string kCounterZero = "CounterZero";
55// Counter1313 will continually be set to 1313.
56const std::string kCounter1313 = "Counter1313";
57// CounterIncrement will be incremented each time.
58const std::string kCounterIncrement = "CounterIncrement";
59// CounterDecrement will be decremented each time.
60const std::string kCounterDecrement = "CounterDecrement";
61// CounterMixed will be incremented by odd numbered threads and
62// decremented by even threads.
63const std::string kCounterMixed = "CounterMixed";
64// The number of thread loops that we will do.
65const int kThreadLoops = 100;
66
67class StatsTableThread : public SimpleThread {
68 public:
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
79void 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
113TEST_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)
167const std::string kMPTableName = "MultipleProcessStatTable";
168
169MULTIPROCESS_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
192TEST_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
240class 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
248TEST_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
290class 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
300TEST_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
332TEST_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
364TEST_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