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