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