stats_table.cc revision 513209b27ff55e2841eac0e4120199c23acce758
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_table.h"
6
7#include "base/logging.h"
8#include "base/platform_thread.h"
9#include "base/process_util.h"
10#include "base/scoped_ptr.h"
11#include "base/shared_memory.h"
12#include "base/string_piece.h"
13#include "base/string_util.h"
14#include "base/thread_local_storage.h"
15#include "base/utf_string_conversions.h"
16
17#if defined(OS_POSIX)
18#include "errno.h"
19#endif
20
21namespace base {
22
23// The StatsTable uses a shared memory segment that is laid out as follows
24//
25// +-------------------------------------------+
26// | Version | Size | MaxCounters | MaxThreads |
27// +-------------------------------------------+
28// | Thread names table                        |
29// +-------------------------------------------+
30// | Thread TID table                          |
31// +-------------------------------------------+
32// | Thread PID table                          |
33// +-------------------------------------------+
34// | Counter names table                       |
35// +-------------------------------------------+
36// | Data                                      |
37// +-------------------------------------------+
38//
39// The data layout is a grid, where the columns are the thread_ids and the
40// rows are the counter_ids.
41//
42// If the first character of the thread_name is '\0', then that column is
43// empty.
44// If the first character of the counter_name is '\0', then that row is
45// empty.
46//
47// About Locking:
48// This class is designed to be both multi-thread and multi-process safe.
49// Aside from initialization, this is done by partitioning the data which
50// each thread uses so that no locking is required.  However, to allocate
51// the rows and columns of the table to particular threads, locking is
52// required.
53//
54// At the shared-memory level, we have a lock.  This lock protects the
55// shared-memory table only, and is used when we create new counters (e.g.
56// use rows) or when we register new threads (e.g. use columns).  Reading
57// data from the table does not require any locking at the shared memory
58// level.
59//
60// Each process which accesses the table will create a StatsTable object.
61// The StatsTable maintains a hash table of the existing counters in the
62// table for faster lookup.  Since the hash table is process specific,
63// each process maintains its own cache.  We avoid complexity here by never
64// de-allocating from the hash table.  (Counters are dynamically added,
65// but not dynamically removed).
66
67// In order for external viewers to be able to read our shared memory,
68// we all need to use the same size ints.
69COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints);
70
71namespace {
72
73// An internal version in case we ever change the format of this
74// file, and so that we can identify our table.
75const int kTableVersion = 0x13131313;
76
77// The name for un-named counters and threads in the table.
78const char kUnknownName[] = "<unknown>";
79
80// Calculates delta to align an offset to the size of an int
81inline int AlignOffset(int offset) {
82  return (sizeof(int) - (offset % sizeof(int))) % sizeof(int);
83}
84
85inline int AlignedSize(int size) {
86  return size + AlignOffset(size);
87}
88
89}  // namespace
90
91// The StatsTable::Private maintains convenience pointers into the
92// shared memory segment.  Use this class to keep the data structure
93// clean and accessible.
94class StatsTable::Private {
95 public:
96  // Various header information contained in the memory mapped segment.
97  struct TableHeader {
98    int version;
99    int size;
100    int max_counters;
101    int max_threads;
102  };
103
104  // Construct a new Private based on expected size parameters, or
105  // return NULL on failure.
106  static Private* New(const std::string& name, int size,
107                                int max_threads, int max_counters);
108
109  SharedMemory* shared_memory() { return &shared_memory_; }
110
111  // Accessors for our header pointers
112  TableHeader* table_header() const { return table_header_; }
113  int version() const { return table_header_->version; }
114  int size() const { return table_header_->size; }
115  int max_counters() const { return table_header_->max_counters; }
116  int max_threads() const { return table_header_->max_threads; }
117
118  // Accessors for our tables
119  char* thread_name(int slot_id) const {
120    return &thread_names_table_[
121      (slot_id-1) * (StatsTable::kMaxThreadNameLength)];
122  }
123  PlatformThreadId* thread_tid(int slot_id) const {
124    return &(thread_tid_table_[slot_id-1]);
125  }
126  int* thread_pid(int slot_id) const {
127    return &(thread_pid_table_[slot_id-1]);
128  }
129  char* counter_name(int counter_id) const {
130    return &counter_names_table_[
131      (counter_id-1) * (StatsTable::kMaxCounterNameLength)];
132  }
133  int* row(int counter_id) const {
134    return &data_table_[(counter_id-1) * max_threads()];
135  }
136
137 private:
138  // Constructor is private because you should use New() instead.
139  Private() {}
140
141  // Initializes the table on first access.  Sets header values
142  // appropriately and zeroes all counters.
143  void InitializeTable(void* memory, int size, int max_counters,
144                       int max_threads);
145
146  // Initializes our in-memory pointers into a pre-created StatsTable.
147  void ComputeMappedPointers(void* memory);
148
149  SharedMemory shared_memory_;
150  TableHeader* table_header_;
151  char* thread_names_table_;
152  PlatformThreadId* thread_tid_table_;
153  int* thread_pid_table_;
154  char* counter_names_table_;
155  int* data_table_;
156};
157
158// static
159StatsTable::Private* StatsTable::Private::New(const std::string& name,
160                                              int size,
161                                              int max_threads,
162                                              int max_counters) {
163#ifdef ANDROID
164  return NULL;
165#else
166  scoped_ptr<Private> priv(new Private());
167  if (!priv->shared_memory_.CreateNamed(name, true, size))
168    return NULL;
169  if (!priv->shared_memory_.Map(size))
170    return NULL;
171  void* memory = priv->shared_memory_.memory();
172
173  TableHeader* header = static_cast<TableHeader*>(memory);
174
175  // If the version does not match, then assume the table needs
176  // to be initialized.
177  if (header->version != kTableVersion)
178    priv->InitializeTable(memory, size, max_counters, max_threads);
179
180  // We have a valid table, so compute our pointers.
181  priv->ComputeMappedPointers(memory);
182
183  return priv.release();
184#endif
185}
186
187void StatsTable::Private::InitializeTable(void* memory, int size,
188                                          int max_counters,
189                                          int max_threads) {
190  // Zero everything.
191  memset(memory, 0, size);
192
193  // Initialize the header.
194  TableHeader* header = static_cast<TableHeader*>(memory);
195  header->version = kTableVersion;
196  header->size = size;
197  header->max_counters = max_counters;
198  header->max_threads = max_threads;
199}
200
201void StatsTable::Private::ComputeMappedPointers(void* memory) {
202  char* data = static_cast<char*>(memory);
203  int offset = 0;
204
205  table_header_ = reinterpret_cast<TableHeader*>(data);
206  offset += sizeof(*table_header_);
207  offset += AlignOffset(offset);
208
209  // Verify we're looking at a valid StatsTable.
210  DCHECK_EQ(table_header_->version, kTableVersion);
211
212  thread_names_table_ = reinterpret_cast<char*>(data + offset);
213  offset += sizeof(char) *
214            max_threads() * StatsTable::kMaxThreadNameLength;
215  offset += AlignOffset(offset);
216
217  thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset);
218  offset += sizeof(int) * max_threads();
219  offset += AlignOffset(offset);
220
221  thread_pid_table_ = reinterpret_cast<int*>(data + offset);
222  offset += sizeof(int) * max_threads();
223  offset += AlignOffset(offset);
224
225  counter_names_table_ = reinterpret_cast<char*>(data + offset);
226  offset += sizeof(char) *
227            max_counters() * StatsTable::kMaxCounterNameLength;
228  offset += AlignOffset(offset);
229
230  data_table_ = reinterpret_cast<int*>(data + offset);
231  offset += sizeof(int) * max_threads() * max_counters();
232
233  DCHECK_EQ(offset, size());
234}
235
236// TLSData carries the data stored in the TLS slots for the
237// StatsTable.  This is used so that we can properly cleanup when the
238// thread exits and return the table slot.
239//
240// Each thread that calls RegisterThread in the StatsTable will have
241// a TLSData stored in its TLS.
242struct StatsTable::TLSData {
243  StatsTable* table;
244  int slot;
245};
246
247// We keep a singleton table which can be easily accessed.
248StatsTable* StatsTable::global_table_ = NULL;
249
250StatsTable::StatsTable(const std::string& name, int max_threads,
251                       int max_counters)
252    : impl_(NULL),
253      tls_index_(SlotReturnFunction) {
254  int table_size =
255    AlignedSize(sizeof(Private::TableHeader)) +
256    AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) +
257    AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) +
258    AlignedSize(max_threads * sizeof(int)) +
259    AlignedSize(max_threads * sizeof(int)) +
260    AlignedSize((sizeof(int) * (max_counters * max_threads)));
261
262  impl_ = Private::New(name, table_size, max_threads, max_counters);
263
264  if (!impl_)
265    PLOG(ERROR) << "StatsTable did not initialize";
266}
267
268StatsTable::~StatsTable() {
269  // Before we tear down our copy of the table, be sure to
270  // unregister our thread.
271  UnregisterThread();
272
273  // Return ThreadLocalStorage.  At this point, if any registered threads
274  // still exist, they cannot Unregister.
275  tls_index_.Free();
276
277  // Cleanup our shared memory.
278  delete impl_;
279
280  // If we are the global table, unregister ourselves.
281  if (global_table_ == this)
282    global_table_ = NULL;
283}
284
285int StatsTable::RegisterThread(const std::string& name) {
286#ifdef ANDROID
287  return 0;
288#else
289  int slot = 0;
290  if (!impl_)
291    return 0;
292
293  // Registering a thread requires that we lock the shared memory
294  // so that two threads don't grab the same slot.  Fortunately,
295  // thread creation shouldn't happen in inner loops.
296  {
297    SharedMemoryAutoLock lock(impl_->shared_memory());
298    slot = FindEmptyThread();
299    if (!slot) {
300      return 0;
301    }
302
303    // We have space, so consume a column in the table.
304    std::string thread_name = name;
305    if (name.empty())
306      thread_name = kUnknownName;
307    strlcpy(impl_->thread_name(slot), thread_name.c_str(),
308            kMaxThreadNameLength);
309    *(impl_->thread_tid(slot)) = PlatformThread::CurrentId();
310    *(impl_->thread_pid(slot)) = GetCurrentProcId();
311  }
312
313  // Set our thread local storage.
314  TLSData* data = new TLSData;
315  data->table = this;
316  data->slot = slot;
317  tls_index_.Set(data);
318  return slot;
319#endif
320}
321
322StatsTable::TLSData* StatsTable::GetTLSData() const {
323  TLSData* data =
324    static_cast<TLSData*>(tls_index_.Get());
325  if (!data)
326    return NULL;
327
328  DCHECK(data->slot);
329  DCHECK_EQ(data->table, this);
330  return data;
331}
332
333void StatsTable::UnregisterThread() {
334  UnregisterThread(GetTLSData());
335}
336
337void StatsTable::UnregisterThread(TLSData* data) {
338  if (!data)
339    return;
340  DCHECK(impl_);
341
342  // Mark the slot free by zeroing out the thread name.
343  char* name = impl_->thread_name(data->slot);
344  *name = '\0';
345
346  // Remove the calling thread's TLS so that it cannot use the slot.
347  tls_index_.Set(NULL);
348  delete data;
349}
350
351void StatsTable::SlotReturnFunction(void* data) {
352  // This is called by the TLS destructor, which on some platforms has
353  // already cleared the TLS info, so use the tls_data argument
354  // rather than trying to fetch it ourselves.
355  TLSData* tls_data = static_cast<TLSData*>(data);
356  if (tls_data) {
357    DCHECK(tls_data->table);
358    tls_data->table->UnregisterThread(tls_data);
359  }
360}
361
362int StatsTable::CountThreadsRegistered() const {
363  if (!impl_)
364    return 0;
365
366  // Loop through the shared memory and count the threads that are active.
367  // We intentionally do not lock the table during the operation.
368  int count = 0;
369  for (int index = 1; index <= impl_->max_threads(); index++) {
370    char* name = impl_->thread_name(index);
371    if (*name != '\0')
372      count++;
373  }
374  return count;
375}
376
377int StatsTable::GetSlot() const {
378  TLSData* data = GetTLSData();
379  if (!data)
380    return 0;
381  return data->slot;
382}
383
384int StatsTable::FindEmptyThread() const {
385  // Note: the API returns slots numbered from 1..N, although
386  // internally, the array is 0..N-1.  This is so that we can return
387  // zero as "not found".
388  //
389  // The reason for doing this is because the thread 'slot' is stored
390  // in TLS, which is always initialized to zero, not -1.  If 0 were
391  // returned as a valid slot number, it would be confused with the
392  // uninitialized state.
393  if (!impl_)
394    return 0;
395
396  int index = 1;
397  for (; index <= impl_->max_threads(); index++) {
398    char* name = impl_->thread_name(index);
399    if (!*name)
400      break;
401  }
402  if (index > impl_->max_threads())
403    return 0;  // The table is full.
404  return index;
405}
406
407int StatsTable::FindCounterOrEmptyRow(const std::string& name) const {
408  // Note: the API returns slots numbered from 1..N, although
409  // internally, the array is 0..N-1.  This is so that we can return
410  // zero as "not found".
411  //
412  // There isn't much reason for this other than to be consistent
413  // with the way we track columns for thread slots.  (See comments
414  // in FindEmptyThread for why it is done this way).
415  if (!impl_)
416    return 0;
417
418  int free_slot = 0;
419  for (int index = 1; index <= impl_->max_counters(); index++) {
420    char* row_name = impl_->counter_name(index);
421    if (!*row_name && !free_slot)
422      free_slot = index;  // save that we found a free slot
423    else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength))
424      return index;
425  }
426  return free_slot;
427}
428
429int StatsTable::FindCounter(const std::string& name) {
430  // Note: the API returns counters numbered from 1..N, although
431  // internally, the array is 0..N-1.  This is so that we can return
432  // zero as "not found".
433  if (!impl_)
434    return 0;
435
436  // Create a scope for our auto-lock.
437  {
438    AutoLock scoped_lock(counters_lock_);
439
440    // Attempt to find the counter.
441    CountersMap::const_iterator iter;
442    iter = counters_.find(name);
443    if (iter != counters_.end())
444      return iter->second;
445  }
446
447  // Counter does not exist, so add it.
448  return AddCounter(name);
449}
450
451int StatsTable::AddCounter(const std::string& name) {
452#ifdef ANDROID
453  return 0;
454#else
455
456  if (!impl_)
457    return 0;
458
459  int counter_id = 0;
460  {
461    // To add a counter to the shared memory, we need the
462    // shared memory lock.
463    SharedMemoryAutoLock lock(impl_->shared_memory());
464
465    // We have space, so create a new counter.
466    counter_id = FindCounterOrEmptyRow(name);
467    if (!counter_id)
468      return 0;
469
470    std::string counter_name = name;
471    if (name.empty())
472      counter_name = kUnknownName;
473    strlcpy(impl_->counter_name(counter_id), counter_name.c_str(),
474            kMaxCounterNameLength);
475  }
476
477  // now add to our in-memory cache
478  {
479    AutoLock lock(counters_lock_);
480    counters_[name] = counter_id;
481  }
482  return counter_id;
483#endif
484}
485
486int* StatsTable::GetLocation(int counter_id, int slot_id) const {
487  if (!impl_)
488    return NULL;
489  if (slot_id > impl_->max_threads())
490    return NULL;
491
492  int* row = impl_->row(counter_id);
493  return &(row[slot_id-1]);
494}
495
496const char* StatsTable::GetRowName(int index) const {
497  if (!impl_)
498    return NULL;
499
500  return impl_->counter_name(index);
501}
502
503int StatsTable::GetRowValue(int index, int pid) const {
504  if (!impl_)
505    return 0;
506
507  int rv = 0;
508  int* row = impl_->row(index);
509  for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) {
510    if (pid == 0 || *impl_->thread_pid(slot_id) == pid)
511      rv += row[slot_id];
512  }
513  return rv;
514}
515
516int StatsTable::GetRowValue(int index) const {
517  return GetRowValue(index, 0);
518}
519
520int StatsTable::GetCounterValue(const std::string& name, int pid) {
521  if (!impl_)
522    return 0;
523
524  int row = FindCounter(name);
525  if (!row)
526    return 0;
527  return GetRowValue(row, pid);
528}
529
530int StatsTable::GetCounterValue(const std::string& name) {
531  return GetCounterValue(name, 0);
532}
533
534int StatsTable::GetMaxCounters() const {
535  if (!impl_)
536    return 0;
537  return impl_->max_counters();
538}
539
540int StatsTable::GetMaxThreads() const {
541  if (!impl_)
542    return 0;
543  return impl_->max_threads();
544}
545
546int* StatsTable::FindLocation(const char* name) {
547  // Get the static StatsTable
548  StatsTable *table = StatsTable::current();
549  if (!table)
550    return NULL;
551
552  // Get the slot for this thread.  Try to register
553  // it if none exists.
554  int slot = table->GetSlot();
555  if (!slot && !(slot = table->RegisterThread("")))
556      return NULL;
557
558  // Find the counter id for the counter.
559  std::string str_name(name);
560  int counter = table->FindCounter(str_name);
561
562  // Now we can find the location in the table.
563  return table->GetLocation(counter, slot);
564}
565
566}  // namespace base
567