• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
AlignOffset(int offset)79 inline int AlignOffset(int offset) {
80   return (sizeof(int) - (offset % sizeof(int))) % sizeof(int);
81 }
82 
AlignedSize(int size)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 
shared_memory()118   base::SharedMemory* shared_memory() { return &shared_memory_; }
119 
120   // Accessors for our header pointers
table_header() const121   TableHeader* table_header() const { return table_header_; }
version() const122   int version() const { return table_header_->version; }
size() const123   int size() const { return table_header_->size; }
max_counters() const124   int max_counters() const { return table_header_->max_counters; }
max_threads() const125   int max_threads() const { return table_header_->max_threads; }
126 
127   // Accessors for our tables
thread_name(int slot_id) const128   char* thread_name(int slot_id) const {
129     return &thread_names_table_[
130       (slot_id-1) * (StatsTable::kMaxThreadNameLength)];
131   }
thread_tid(int slot_id) const132   PlatformThreadId* thread_tid(int slot_id) const {
133     return &(thread_tid_table_[slot_id-1]);
134   }
thread_pid(int slot_id) const135   int* thread_pid(int slot_id) const {
136     return &(thread_pid_table_[slot_id-1]);
137   }
counter_name(int counter_id) const138   char* counter_name(int counter_id) const {
139     return &counter_names_table_[
140       (counter_id-1) * (StatsTable::kMaxCounterNameLength)];
141   }
row(int counter_id) const142   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.
StatsTablePrivate()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
New(const std::string & name,int size,int max_threads,int max_counters)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 
InitializeTable(void * memory,int size,int max_counters,int max_threads)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 
ComputeMappedPointers(void * memory)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 
StatsTable(const std::string & name,int max_threads,int max_counters)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 
~StatsTable()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 
RegisterThread(const std::string & name)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 
GetTLSData() const314 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 
UnregisterThread()325 void StatsTable::UnregisterThread() {
326   UnregisterThread(GetTLSData());
327 }
328 
UnregisterThread(StatsTableTLSData * data)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 
SlotReturnFunction(void * data)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 
CountThreadsRegistered() const354 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 
GetSlot() const369 int StatsTable::GetSlot() const {
370   StatsTableTLSData* data = GetTLSData();
371   if (!data)
372     return 0;
373   return data->slot;
374 }
375 
FindEmptyThread() const376 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 
FindCounterOrEmptyRow(const std::string & name) const399 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 
FindCounter(const std::string & name)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 
AddCounter(const std::string & name)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 
GetLocation(int counter_id,int slot_id) const473 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 
GetRowName(int index) const483 const char* StatsTable::GetRowName(int index) const {
484   if (!impl_)
485     return NULL;
486 
487   return impl_->counter_name(index);
488 }
489 
GetRowValue(int index,int pid) const490 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 
GetRowValue(int index) const503 int StatsTable::GetRowValue(int index) const {
504   return GetRowValue(index, 0);
505 }
506 
GetCounterValue(const std::string & name,int pid)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 
GetCounterValue(const std::string & name)517 int StatsTable::GetCounterValue(const std::string& name) {
518   return GetCounterValue(name, 0);
519 }
520 
GetMaxCounters() const521 int StatsTable::GetMaxCounters() const {
522   if (!impl_)
523     return 0;
524   return impl_->max_counters();
525 }
526 
GetMaxThreads() const527 int StatsTable::GetMaxThreads() const {
528   if (!impl_)
529     return 0;
530   return impl_->max_threads();
531 }
532 
FindLocation(const char * name)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