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