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