stats_table.cc revision 58e6fbe4ee35d65e14b626c557d37565bf8ad179
1// Copyright (c) 2011 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/memory/scoped_ptr.h"
9#include "base/memory/shared_memory.h"
10#include "base/process/process_handle.h"
11#include "base/strings/string_piece.h"
12#include "base/strings/string_util.h"
13#include "base/strings/utf_string_conversions.h"
14#include "base/threading/platform_thread.h"
15#include "base/threading/thread_local_storage.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      : table_header_(NULL),
141        thread_names_table_(NULL),
142        thread_tid_table_(NULL),
143        thread_pid_table_(NULL),
144        counter_names_table_(NULL),
145        data_table_(NULL) {
146  }
147
148  // Initializes the table on first access.  Sets header values
149  // appropriately and zeroes all counters.
150  void InitializeTable(void* memory, int size, int max_counters,
151                       int max_threads);
152
153  // Initializes our in-memory pointers into a pre-created StatsTable.
154  void ComputeMappedPointers(void* memory);
155
156  SharedMemory shared_memory_;
157  TableHeader* table_header_;
158  char* thread_names_table_;
159  PlatformThreadId* thread_tid_table_;
160  int* thread_pid_table_;
161  char* counter_names_table_;
162  int* data_table_;
163};
164
165// static
166StatsTable::Private* StatsTable::Private::New(const std::string& name,
167                                              int size,
168                                              int max_threads,
169                                              int max_counters) {
170  scoped_ptr<Private> priv(new Private());
171  if (!priv->shared_memory_.CreateNamed(name, true, size))
172    return NULL;
173  if (!priv->shared_memory_.Map(size))
174    return NULL;
175  void* memory = priv->shared_memory_.memory();
176
177  TableHeader* header = static_cast<TableHeader*>(memory);
178
179  // If the version does not match, then assume the table needs
180  // to be initialized.
181  if (header->version != kTableVersion)
182    priv->InitializeTable(memory, size, max_counters, max_threads);
183
184  // We have a valid table, so compute our pointers.
185  priv->ComputeMappedPointers(memory);
186
187  return priv.release();
188}
189
190void StatsTable::Private::InitializeTable(void* memory, int size,
191                                          int max_counters,
192                                          int max_threads) {
193  // Zero everything.
194  memset(memory, 0, size);
195
196  // Initialize the header.
197  TableHeader* header = static_cast<TableHeader*>(memory);
198  header->version = kTableVersion;
199  header->size = size;
200  header->max_counters = max_counters;
201  header->max_threads = max_threads;
202}
203
204void StatsTable::Private::ComputeMappedPointers(void* memory) {
205  char* data = static_cast<char*>(memory);
206  int offset = 0;
207
208  table_header_ = reinterpret_cast<TableHeader*>(data);
209  offset += sizeof(*table_header_);
210  offset += AlignOffset(offset);
211
212  // Verify we're looking at a valid StatsTable.
213  DCHECK_EQ(table_header_->version, kTableVersion);
214
215  thread_names_table_ = reinterpret_cast<char*>(data + offset);
216  offset += sizeof(char) *
217            max_threads() * StatsTable::kMaxThreadNameLength;
218  offset += AlignOffset(offset);
219
220  thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset);
221  offset += sizeof(int) * max_threads();
222  offset += AlignOffset(offset);
223
224  thread_pid_table_ = reinterpret_cast<int*>(data + offset);
225  offset += sizeof(int) * max_threads();
226  offset += AlignOffset(offset);
227
228  counter_names_table_ = reinterpret_cast<char*>(data + offset);
229  offset += sizeof(char) *
230            max_counters() * StatsTable::kMaxCounterNameLength;
231  offset += AlignOffset(offset);
232
233  data_table_ = reinterpret_cast<int*>(data + offset);
234  offset += sizeof(int) * max_threads() * max_counters();
235
236  DCHECK_EQ(offset, size());
237}
238
239// TLSData carries the data stored in the TLS slots for the
240// StatsTable.  This is used so that we can properly cleanup when the
241// thread exits and return the table slot.
242//
243// Each thread that calls RegisterThread in the StatsTable will have
244// a TLSData stored in its TLS.
245struct StatsTable::TLSData {
246  StatsTable* table;
247  int slot;
248};
249
250// We keep a singleton table which can be easily accessed.
251StatsTable* global_table = NULL;
252
253StatsTable::StatsTable(const std::string& name, int max_threads,
254                       int max_counters)
255    : impl_(NULL),
256      tls_index_(SlotReturnFunction) {
257  int table_size =
258    AlignedSize(sizeof(Private::TableHeader)) +
259    AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) +
260    AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) +
261    AlignedSize(max_threads * sizeof(int)) +
262    AlignedSize(max_threads * sizeof(int)) +
263    AlignedSize((sizeof(int) * (max_counters * max_threads)));
264
265  impl_ = Private::New(name, table_size, max_threads, max_counters);
266
267  if (!impl_)
268    DPLOG(ERROR) << "StatsTable did not initialize";
269}
270
271StatsTable::~StatsTable() {
272  // Before we tear down our copy of the table, be sure to
273  // unregister our thread.
274  UnregisterThread();
275
276  // Return ThreadLocalStorage.  At this point, if any registered threads
277  // still exist, they cannot Unregister.
278  tls_index_.Free();
279
280  // Cleanup our shared memory.
281  delete impl_;
282
283  // If we are the global table, unregister ourselves.
284  if (global_table == this)
285    global_table = NULL;
286}
287
288StatsTable* StatsTable::current() {
289  return global_table;
290}
291
292void StatsTable::set_current(StatsTable* value) {
293  global_table = value;
294}
295
296int StatsTable::GetSlot() const {
297  TLSData* data = GetTLSData();
298  if (!data)
299    return 0;
300  return data->slot;
301}
302
303int StatsTable::RegisterThread(const std::string& name) {
304  int slot = 0;
305  if (!impl_)
306    return 0;
307
308  // Registering a thread requires that we lock the shared memory
309  // so that two threads don't grab the same slot.  Fortunately,
310  // thread creation shouldn't happen in inner loops.
311  {
312    SharedMemoryAutoLock lock(impl_->shared_memory());
313    slot = FindEmptyThread();
314    if (!slot) {
315      return 0;
316    }
317
318    // We have space, so consume a column in the table.
319    std::string thread_name = name;
320    if (name.empty())
321      thread_name = kUnknownName;
322    strlcpy(impl_->thread_name(slot), thread_name.c_str(),
323            kMaxThreadNameLength);
324    *(impl_->thread_tid(slot)) = PlatformThread::CurrentId();
325    *(impl_->thread_pid(slot)) = GetCurrentProcId();
326  }
327
328  // Set our thread local storage.
329  TLSData* data = new TLSData;
330  data->table = this;
331  data->slot = slot;
332  tls_index_.Set(data);
333  return slot;
334}
335
336int StatsTable::CountThreadsRegistered() const {
337  if (!impl_)
338    return 0;
339
340  // Loop through the shared memory and count the threads that are active.
341  // We intentionally do not lock the table during the operation.
342  int count = 0;
343  for (int index = 1; index <= impl_->max_threads(); index++) {
344    char* name = impl_->thread_name(index);
345    if (*name != '\0')
346      count++;
347  }
348  return count;
349}
350
351int StatsTable::FindCounter(const std::string& name) {
352  // Note: the API returns counters numbered from 1..N, although
353  // internally, the array is 0..N-1.  This is so that we can return
354  // zero as "not found".
355  if (!impl_)
356    return 0;
357
358  // Create a scope for our auto-lock.
359  {
360    AutoLock scoped_lock(counters_lock_);
361
362    // Attempt to find the counter.
363    CountersMap::const_iterator iter;
364    iter = counters_.find(name);
365    if (iter != counters_.end())
366      return iter->second;
367  }
368
369  // Counter does not exist, so add it.
370  return AddCounter(name);
371}
372
373int* StatsTable::GetLocation(int counter_id, int slot_id) const {
374  if (!impl_)
375    return NULL;
376  if (slot_id > impl_->max_threads())
377    return NULL;
378
379  int* row = impl_->row(counter_id);
380  return &(row[slot_id-1]);
381}
382
383const char* StatsTable::GetRowName(int index) const {
384  if (!impl_)
385    return NULL;
386
387  return impl_->counter_name(index);
388}
389
390int StatsTable::GetRowValue(int index) const {
391  return GetRowValue(index, 0);
392}
393
394int StatsTable::GetRowValue(int index, int pid) const {
395  if (!impl_)
396    return 0;
397
398  int rv = 0;
399  int* row = impl_->row(index);
400  for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) {
401    if (pid == 0 || *impl_->thread_pid(slot_id) == pid)
402      rv += row[slot_id];
403  }
404  return rv;
405}
406
407int StatsTable::GetCounterValue(const std::string& name) {
408  return GetCounterValue(name, 0);
409}
410
411int StatsTable::GetCounterValue(const std::string& name, int pid) {
412  if (!impl_)
413    return 0;
414
415  int row = FindCounter(name);
416  if (!row)
417    return 0;
418  return GetRowValue(row, pid);
419}
420
421int StatsTable::GetMaxCounters() const {
422  if (!impl_)
423    return 0;
424  return impl_->max_counters();
425}
426
427int StatsTable::GetMaxThreads() const {
428  if (!impl_)
429    return 0;
430  return impl_->max_threads();
431}
432
433int* StatsTable::FindLocation(const char* name) {
434  // Get the static StatsTable
435  StatsTable *table = StatsTable::current();
436  if (!table)
437    return NULL;
438
439  // Get the slot for this thread.  Try to register
440  // it if none exists.
441  int slot = table->GetSlot();
442  if (!slot && !(slot = table->RegisterThread(std::string())))
443    return NULL;
444
445  // Find the counter id for the counter.
446  std::string str_name(name);
447  int counter = table->FindCounter(str_name);
448
449  // Now we can find the location in the table.
450  return table->GetLocation(counter, slot);
451}
452
453void StatsTable::UnregisterThread() {
454  UnregisterThread(GetTLSData());
455}
456
457void StatsTable::UnregisterThread(TLSData* data) {
458  if (!data)
459    return;
460  DCHECK(impl_);
461
462  // Mark the slot free by zeroing out the thread name.
463  char* name = impl_->thread_name(data->slot);
464  *name = '\0';
465
466  // Remove the calling thread's TLS so that it cannot use the slot.
467  tls_index_.Set(NULL);
468  delete data;
469}
470
471void StatsTable::SlotReturnFunction(void* data) {
472  // This is called by the TLS destructor, which on some platforms has
473  // already cleared the TLS info, so use the tls_data argument
474  // rather than trying to fetch it ourselves.
475  TLSData* tls_data = static_cast<TLSData*>(data);
476  if (tls_data) {
477    DCHECK(tls_data->table);
478    tls_data->table->UnregisterThread(tls_data);
479  }
480}
481
482int StatsTable::FindEmptyThread() const {
483  // Note: the API returns slots numbered from 1..N, although
484  // internally, the array is 0..N-1.  This is so that we can return
485  // zero as "not found".
486  //
487  // The reason for doing this is because the thread 'slot' is stored
488  // in TLS, which is always initialized to zero, not -1.  If 0 were
489  // returned as a valid slot number, it would be confused with the
490  // uninitialized state.
491  if (!impl_)
492    return 0;
493
494  int index = 1;
495  for (; index <= impl_->max_threads(); index++) {
496    char* name = impl_->thread_name(index);
497    if (!*name)
498      break;
499  }
500  if (index > impl_->max_threads())
501    return 0;  // The table is full.
502  return index;
503}
504
505int StatsTable::FindCounterOrEmptyRow(const std::string& name) const {
506  // Note: the API returns slots numbered from 1..N, although
507  // internally, the array is 0..N-1.  This is so that we can return
508  // zero as "not found".
509  //
510  // There isn't much reason for this other than to be consistent
511  // with the way we track columns for thread slots.  (See comments
512  // in FindEmptyThread for why it is done this way).
513  if (!impl_)
514    return 0;
515
516  int free_slot = 0;
517  for (int index = 1; index <= impl_->max_counters(); index++) {
518    char* row_name = impl_->counter_name(index);
519    if (!*row_name && !free_slot)
520      free_slot = index;  // save that we found a free slot
521    else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength))
522      return index;
523  }
524  return free_slot;
525}
526
527int StatsTable::AddCounter(const std::string& name) {
528  if (!impl_)
529    return 0;
530
531  int counter_id = 0;
532  {
533    // To add a counter to the shared memory, we need the
534    // shared memory lock.
535    SharedMemoryAutoLock lock(impl_->shared_memory());
536
537    // We have space, so create a new counter.
538    counter_id = FindCounterOrEmptyRow(name);
539    if (!counter_id)
540      return 0;
541
542    std::string counter_name = name;
543    if (name.empty())
544      counter_name = kUnknownName;
545    strlcpy(impl_->counter_name(counter_id), counter_name.c_str(),
546            kMaxCounterNameLength);
547  }
548
549  // now add to our in-memory cache
550  {
551    AutoLock lock(counters_lock_);
552    counters_[name] = counter_id;
553  }
554  return counter_id;
555}
556
557StatsTable::TLSData* StatsTable::GetTLSData() const {
558  TLSData* data =
559    static_cast<TLSData*>(tls_index_.Get());
560  if (!data)
561    return NULL;
562
563  DCHECK(data->slot);
564  DCHECK_EQ(data->table, this);
565  return data;
566}
567
568}  // namespace base
569