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#if defined(OS_MACOSX) || defined(THREAD_SANITIZER)
105#define MAYBE_MultipleThreads DISABLED_MultipleThreads
106#else
107#define MAYBE_MultipleThreads MultipleThreads
108#endif
109TEST_F(StatsTableTest, MAYBE_MultipleThreads) {
110  // Create a stats table.
111  const int kMaxThreads = 20;
112  const int kMaxCounter = 5;
113  StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter);
114  StatsTable::set_current(&table);
115
116  EXPECT_EQ(0, table.CountThreadsRegistered());
117
118  // Spin up a set of threads to go bang on the various counters.
119  // After we join the threads, we'll make sure the counters
120  // contain the values we expected.
121  StatsTableThread* threads[kMaxThreads];
122
123  // Spawn the threads.
124  for (int index = 0; index < kMaxThreads; index++) {
125    threads[index] = new StatsTableThread("MultipleThreadsTest", index);
126    threads[index]->Start();
127  }
128
129  // Wait for the threads to finish.
130  for (int index = 0; index < kMaxThreads; index++) {
131    threads[index]->Join();
132    delete threads[index];
133  }
134
135  StatsCounter zero_counter(kCounterZero);
136  StatsCounter lucky13_counter(kCounter1313);
137  StatsCounter increment_counter(kCounterIncrement);
138  StatsCounter decrement_counter(kCounterDecrement);
139  StatsCounter mixed_counter(kCounterMixed);
140
141  // Verify the various counters are correct.
142  std::string name;
143  name = "c:" + kCounterZero;
144  EXPECT_EQ(0, table.GetCounterValue(name));
145  name = "c:" + kCounter1313;
146  EXPECT_EQ(1313 * kMaxThreads,
147      table.GetCounterValue(name));
148  name = "c:" + kCounterIncrement;
149  EXPECT_EQ(kMaxThreads * kThreadLoops,
150      table.GetCounterValue(name));
151  name = "c:" + kCounterDecrement;
152  EXPECT_EQ(-kMaxThreads * kThreadLoops,
153      table.GetCounterValue(name));
154  name = "c:" + kCounterMixed;
155  EXPECT_EQ((kMaxThreads % 2) * kThreadLoops,
156      table.GetCounterValue(name));
157  EXPECT_EQ(0, table.CountThreadsRegistered());
158}
159
160// This multiprocess test only runs on Windows. On Posix, the shared memory
161// handle is not sent between the processes properly.
162#if defined(OS_WIN)
163const std::string kMPTableName = "MultipleProcessStatTable";
164
165MULTIPROCESS_TEST_MAIN(StatsTableMultipleProcessMain) {
166  // Each process will open the shared memory and set counters
167  // concurrently in a loop.  We'll use some pauses to
168  // mixup the scheduling.
169
170  StatsTable table(kMPTableName, 0, 0);
171  StatsTable::set_current(&table);
172  StatsCounter zero_counter(kCounterZero);
173  StatsCounter lucky13_counter(kCounter1313);
174  StatsCounter increment_counter(kCounterIncrement);
175  StatsCounter decrement_counter(kCounterDecrement);
176  for (int index = 0; index < kThreadLoops; index++) {
177    zero_counter.Set(0);
178    lucky13_counter.Set(1313);
179    increment_counter.Increment();
180    decrement_counter.Decrement();
181    PlatformThread::Sleep(TimeDelta::FromMilliseconds(index % 10));
182  }
183  return 0;
184}
185
186// Create a few processes and have them poke on their counters.
187// This test is slow and flaky http://crbug.com/10611
188TEST_F(StatsTableTest, DISABLED_MultipleProcesses) {
189  // Create a stats table.
190  const int kMaxProcs = 20;
191  const int kMaxCounter = 5;
192  StatsTable table(kMPTableName, kMaxProcs, kMaxCounter);
193  StatsTable::set_current(&table);
194  EXPECT_EQ(0, table.CountThreadsRegistered());
195
196  // Spin up a set of processes to go bang on the various counters.
197  // After we join the processes, we'll make sure the counters
198  // contain the values we expected.
199  ProcessHandle procs[kMaxProcs];
200
201  // Spawn the processes.
202  for (int16 index = 0; index < kMaxProcs; index++) {
203    procs[index] = SpawnChild("StatsTableMultipleProcessMain");
204    EXPECT_NE(kNullProcessHandle, procs[index]);
205  }
206
207  // Wait for the processes to finish.
208  for (int index = 0; index < kMaxProcs; index++) {
209    EXPECT_TRUE(WaitForSingleProcess(
210        procs[index], base::TimeDelta::FromMinutes(1)));
211    CloseProcessHandle(procs[index]);
212  }
213
214  StatsCounter zero_counter(kCounterZero);
215  StatsCounter lucky13_counter(kCounter1313);
216  StatsCounter increment_counter(kCounterIncrement);
217  StatsCounter decrement_counter(kCounterDecrement);
218
219  // Verify the various counters are correct.
220  std::string name;
221  name = "c:" + kCounterZero;
222  EXPECT_EQ(0, table.GetCounterValue(name));
223  name = "c:" + kCounter1313;
224  EXPECT_EQ(1313 * kMaxProcs,
225      table.GetCounterValue(name));
226  name = "c:" + kCounterIncrement;
227  EXPECT_EQ(kMaxProcs * kThreadLoops,
228      table.GetCounterValue(name));
229  name = "c:" + kCounterDecrement;
230  EXPECT_EQ(-kMaxProcs * kThreadLoops,
231      table.GetCounterValue(name));
232  EXPECT_EQ(0, table.CountThreadsRegistered());
233}
234#endif
235
236class MockStatsCounter : public StatsCounter {
237 public:
238  explicit MockStatsCounter(const std::string& name)
239      : StatsCounter(name) {}
240  int* Pointer() { return GetPtr(); }
241};
242
243// Test some basic StatsCounter operations
244TEST_F(StatsTableTest, StatsCounter) {
245  // Create a stats table.
246  const int kMaxThreads = 20;
247  const int kMaxCounter = 5;
248  StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter);
249  StatsTable::set_current(&table);
250
251  MockStatsCounter foo("foo");
252
253  // Test initial state.
254  EXPECT_TRUE(foo.Enabled());
255  ASSERT_NE(foo.Pointer(), static_cast<int*>(0));
256  EXPECT_EQ(0, *(foo.Pointer()));
257  EXPECT_EQ(0, table.GetCounterValue("c:foo"));
258
259  // Test Increment.
260  while (*(foo.Pointer()) < 123) foo.Increment();
261  EXPECT_EQ(123, table.GetCounterValue("c:foo"));
262  foo.Add(0);
263  EXPECT_EQ(123, table.GetCounterValue("c:foo"));
264  foo.Add(-1);
265  EXPECT_EQ(122, table.GetCounterValue("c:foo"));
266
267  // Test Set.
268  foo.Set(0);
269  EXPECT_EQ(0, table.GetCounterValue("c:foo"));
270  foo.Set(100);
271  EXPECT_EQ(100, table.GetCounterValue("c:foo"));
272  foo.Set(-1);
273  EXPECT_EQ(-1, table.GetCounterValue("c:foo"));
274  foo.Set(0);
275  EXPECT_EQ(0, table.GetCounterValue("c:foo"));
276
277  // Test Decrement.
278  foo.Subtract(1);
279  EXPECT_EQ(-1, table.GetCounterValue("c:foo"));
280  foo.Subtract(0);
281  EXPECT_EQ(-1, table.GetCounterValue("c:foo"));
282  foo.Subtract(-1);
283  EXPECT_EQ(0, table.GetCounterValue("c:foo"));
284}
285
286class MockStatsCounterTimer : public StatsCounterTimer {
287 public:
288  explicit MockStatsCounterTimer(const std::string& name)
289      : StatsCounterTimer(name) {}
290
291  TimeTicks start_time() { return start_time_; }
292  TimeTicks stop_time() { return stop_time_; }
293};
294
295// Test some basic StatsCounterTimer operations
296TEST_F(StatsTableTest, StatsCounterTimer) {
297  // Create a stats table.
298  const int kMaxThreads = 20;
299  const int kMaxCounter = 5;
300  StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter);
301  StatsTable::set_current(&table);
302
303  MockStatsCounterTimer bar("bar");
304
305  // Test initial state.
306  EXPECT_FALSE(bar.Running());
307  EXPECT_TRUE(bar.start_time().is_null());
308  EXPECT_TRUE(bar.stop_time().is_null());
309
310  const TimeDelta kDuration = TimeDelta::FromMilliseconds(100);
311
312  // Do some timing.
313  bar.Start();
314  PlatformThread::Sleep(kDuration);
315  bar.Stop();
316  EXPECT_GT(table.GetCounterValue("t:bar"), 0);
317  EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar"));
318
319  // Verify that timing again is additive.
320  bar.Start();
321  PlatformThread::Sleep(kDuration);
322  bar.Stop();
323  EXPECT_GT(table.GetCounterValue("t:bar"), 0);
324  EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar"));
325}
326
327// Test some basic StatsRate operations
328TEST_F(StatsTableTest, StatsRate) {
329  // Create a stats table.
330  const int kMaxThreads = 20;
331  const int kMaxCounter = 5;
332  StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter);
333  StatsTable::set_current(&table);
334
335  StatsRate baz("baz");
336
337  // Test initial state.
338  EXPECT_FALSE(baz.Running());
339  EXPECT_EQ(0, table.GetCounterValue("c:baz"));
340  EXPECT_EQ(0, table.GetCounterValue("t:baz"));
341
342  const TimeDelta kDuration = TimeDelta::FromMilliseconds(100);
343
344  // Do some timing.
345  baz.Start();
346  PlatformThread::Sleep(kDuration);
347  baz.Stop();
348  EXPECT_EQ(1, table.GetCounterValue("c:baz"));
349  EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:baz"));
350
351  // Verify that timing again is additive.
352  baz.Start();
353  PlatformThread::Sleep(kDuration);
354  baz.Stop();
355  EXPECT_EQ(2, table.GetCounterValue("c:baz"));
356  EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:baz"));
357}
358
359// Test some basic StatsScope operations
360TEST_F(StatsTableTest, StatsScope) {
361  // Create a stats table.
362  const int kMaxThreads = 20;
363  const int kMaxCounter = 5;
364  StatsTable table(StatsTable::TableIdentifier(), kMaxThreads, kMaxCounter);
365  StatsTable::set_current(&table);
366
367  StatsCounterTimer foo("foo");
368  StatsRate bar("bar");
369
370  // Test initial state.
371  EXPECT_EQ(0, table.GetCounterValue("t:foo"));
372  EXPECT_EQ(0, table.GetCounterValue("t:bar"));
373  EXPECT_EQ(0, table.GetCounterValue("c:bar"));
374
375  const TimeDelta kDuration = TimeDelta::FromMilliseconds(100);
376
377  // Try a scope.
378  {
379    StatsScope<StatsCounterTimer> timer(foo);
380    StatsScope<StatsRate> timer2(bar);
381    PlatformThread::Sleep(kDuration);
382  }
383  EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:foo"));
384  EXPECT_LE(kDuration.InMilliseconds(), table.GetCounterValue("t:bar"));
385  EXPECT_EQ(1, table.GetCounterValue("c:bar"));
386
387  // Try a second scope.
388  {
389    StatsScope<StatsCounterTimer> timer(foo);
390    StatsScope<StatsRate> timer2(bar);
391    PlatformThread::Sleep(kDuration);
392  }
393  EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:foo"));
394  EXPECT_LE(kDuration.InMilliseconds() * 2, table.GetCounterValue("t:bar"));
395  EXPECT_EQ(2, table.GetCounterValue("c:bar"));
396}
397
398}  // namespace base
399