• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/process_util.h"
10 #include "base/shared_memory.h"
11 #include "base/string_piece.h"
12 #include "base/string_util.h"
13 #include "base/threading/platform_thread.h"
14 #include "base/threading/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 namespace 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.
69 COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints);
70 
71 namespace {
72 
73 // An internal version in case we ever change the format of this
74 // file, and so that we can identify our table.
75 const int kTableVersion = 0x13131313;
76 
77 // The name for un-named counters and threads in the table.
78 const char kUnknownName[] = "<unknown>";
79 
80 // Calculates delta to align an offset to the size of an int
AlignOffset(int offset)81 inline int AlignOffset(int offset) {
82   return (sizeof(int) - (offset % sizeof(int))) % sizeof(int);
83 }
84 
AlignedSize(int size)85 inline 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.
94 class 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 
shared_memory()109   SharedMemory* shared_memory() { return &shared_memory_; }
110 
111   // Accessors for our header pointers
table_header() const112   TableHeader* table_header() const { return table_header_; }
version() const113   int version() const { return table_header_->version; }
size() const114   int size() const { return table_header_->size; }
max_counters() const115   int max_counters() const { return table_header_->max_counters; }
max_threads() const116   int max_threads() const { return table_header_->max_threads; }
117 
118   // Accessors for our tables
thread_name(int slot_id) const119   char* thread_name(int slot_id) const {
120     return &thread_names_table_[
121       (slot_id-1) * (StatsTable::kMaxThreadNameLength)];
122   }
thread_tid(int slot_id) const123   PlatformThreadId* thread_tid(int slot_id) const {
124     return &(thread_tid_table_[slot_id-1]);
125   }
thread_pid(int slot_id) const126   int* thread_pid(int slot_id) const {
127     return &(thread_pid_table_[slot_id-1]);
128   }
counter_name(int counter_id) const129   char* counter_name(int counter_id) const {
130     return &counter_names_table_[
131       (counter_id-1) * (StatsTable::kMaxCounterNameLength)];
132   }
row(int counter_id) const133   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.
Private()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
New(const std::string & name,int size,int max_threads,int max_counters)166 StatsTable::Private* StatsTable::Private::New(const std::string& name,
167                                               int size,
168                                               int max_threads,
169                                               int max_counters) {
170 #ifdef ANDROID
171   return NULL;
172 #else
173   scoped_ptr<Private> priv(new Private());
174   if (!priv->shared_memory_.CreateNamed(name, true, size))
175     return NULL;
176   if (!priv->shared_memory_.Map(size))
177     return NULL;
178   void* memory = priv->shared_memory_.memory();
179 
180   TableHeader* header = static_cast<TableHeader*>(memory);
181 
182   // If the version does not match, then assume the table needs
183   // to be initialized.
184   if (header->version != kTableVersion)
185     priv->InitializeTable(memory, size, max_counters, max_threads);
186 
187   // We have a valid table, so compute our pointers.
188   priv->ComputeMappedPointers(memory);
189 
190   return priv.release();
191 #endif
192 }
193 
InitializeTable(void * memory,int size,int max_counters,int max_threads)194 void StatsTable::Private::InitializeTable(void* memory, int size,
195                                           int max_counters,
196                                           int max_threads) {
197   // Zero everything.
198   memset(memory, 0, size);
199 
200   // Initialize the header.
201   TableHeader* header = static_cast<TableHeader*>(memory);
202   header->version = kTableVersion;
203   header->size = size;
204   header->max_counters = max_counters;
205   header->max_threads = max_threads;
206 }
207 
ComputeMappedPointers(void * memory)208 void StatsTable::Private::ComputeMappedPointers(void* memory) {
209   char* data = static_cast<char*>(memory);
210   int offset = 0;
211 
212   table_header_ = reinterpret_cast<TableHeader*>(data);
213   offset += sizeof(*table_header_);
214   offset += AlignOffset(offset);
215 
216   // Verify we're looking at a valid StatsTable.
217   DCHECK_EQ(table_header_->version, kTableVersion);
218 
219   thread_names_table_ = reinterpret_cast<char*>(data + offset);
220   offset += sizeof(char) *
221             max_threads() * StatsTable::kMaxThreadNameLength;
222   offset += AlignOffset(offset);
223 
224   thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset);
225   offset += sizeof(int) * max_threads();
226   offset += AlignOffset(offset);
227 
228   thread_pid_table_ = reinterpret_cast<int*>(data + offset);
229   offset += sizeof(int) * max_threads();
230   offset += AlignOffset(offset);
231 
232   counter_names_table_ = reinterpret_cast<char*>(data + offset);
233   offset += sizeof(char) *
234             max_counters() * StatsTable::kMaxCounterNameLength;
235   offset += AlignOffset(offset);
236 
237   data_table_ = reinterpret_cast<int*>(data + offset);
238   offset += sizeof(int) * max_threads() * max_counters();
239 
240   DCHECK_EQ(offset, size());
241 }
242 
243 // TLSData carries the data stored in the TLS slots for the
244 // StatsTable.  This is used so that we can properly cleanup when the
245 // thread exits and return the table slot.
246 //
247 // Each thread that calls RegisterThread in the StatsTable will have
248 // a TLSData stored in its TLS.
249 struct StatsTable::TLSData {
250   StatsTable* table;
251   int slot;
252 };
253 
254 // We keep a singleton table which can be easily accessed.
255 StatsTable* StatsTable::global_table_ = NULL;
256 
StatsTable(const std::string & name,int max_threads,int max_counters)257 StatsTable::StatsTable(const std::string& name, int max_threads,
258                        int max_counters)
259     : impl_(NULL),
260       tls_index_(SlotReturnFunction) {
261   int table_size =
262     AlignedSize(sizeof(Private::TableHeader)) +
263     AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) +
264     AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) +
265     AlignedSize(max_threads * sizeof(int)) +
266     AlignedSize(max_threads * sizeof(int)) +
267     AlignedSize((sizeof(int) * (max_counters * max_threads)));
268 
269   impl_ = Private::New(name, table_size, max_threads, max_counters);
270 
271   if (!impl_)
272     PLOG(ERROR) << "StatsTable did not initialize";
273 }
274 
~StatsTable()275 StatsTable::~StatsTable() {
276   // Before we tear down our copy of the table, be sure to
277   // unregister our thread.
278   UnregisterThread();
279 
280   // Return ThreadLocalStorage.  At this point, if any registered threads
281   // still exist, they cannot Unregister.
282   tls_index_.Free();
283 
284   // Cleanup our shared memory.
285   delete impl_;
286 
287   // If we are the global table, unregister ourselves.
288   if (global_table_ == this)
289     global_table_ = NULL;
290 }
291 
GetSlot() const292 int StatsTable::GetSlot() const {
293   TLSData* data = GetTLSData();
294   if (!data)
295     return 0;
296   return data->slot;
297 }
298 
RegisterThread(const std::string & name)299 int StatsTable::RegisterThread(const std::string& name) {
300 #ifdef ANDROID
301   return 0;
302 #else
303   int slot = 0;
304   if (!impl_)
305     return 0;
306 
307   // Registering a thread requires that we lock the shared memory
308   // so that two threads don't grab the same slot.  Fortunately,
309   // thread creation shouldn't happen in inner loops.
310   {
311     SharedMemoryAutoLock lock(impl_->shared_memory());
312     slot = FindEmptyThread();
313     if (!slot) {
314       return 0;
315     }
316 
317     // We have space, so consume a column in the table.
318     std::string thread_name = name;
319     if (name.empty())
320       thread_name = kUnknownName;
321     strlcpy(impl_->thread_name(slot), thread_name.c_str(),
322             kMaxThreadNameLength);
323     *(impl_->thread_tid(slot)) = PlatformThread::CurrentId();
324     *(impl_->thread_pid(slot)) = GetCurrentProcId();
325   }
326 
327   // Set our thread local storage.
328   TLSData* data = new TLSData;
329   data->table = this;
330   data->slot = slot;
331   tls_index_.Set(data);
332   return slot;
333 #endif
334 }
335 
CountThreadsRegistered() const336 int 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 
FindCounter(const std::string & name)351 int 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 
GetLocation(int counter_id,int slot_id) const373 int* 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 
GetRowName(int index) const383 const char* StatsTable::GetRowName(int index) const {
384   if (!impl_)
385     return NULL;
386 
387   return impl_->counter_name(index);
388 }
389 
GetRowValue(int index) const390 int StatsTable::GetRowValue(int index) const {
391   return GetRowValue(index, 0);
392 }
393 
GetRowValue(int index,int pid) const394 int 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 
GetCounterValue(const std::string & name)407 int StatsTable::GetCounterValue(const std::string& name) {
408   return GetCounterValue(name, 0);
409 }
410 
GetCounterValue(const std::string & name,int pid)411 int 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 
GetMaxCounters() const421 int StatsTable::GetMaxCounters() const {
422   if (!impl_)
423     return 0;
424   return impl_->max_counters();
425 }
426 
GetMaxThreads() const427 int StatsTable::GetMaxThreads() const {
428   if (!impl_)
429     return 0;
430   return impl_->max_threads();
431 }
432 
FindLocation(const char * name)433 int* 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("")))
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 
UnregisterThread()453 void StatsTable::UnregisterThread() {
454   UnregisterThread(GetTLSData());
455 }
456 
UnregisterThread(TLSData * data)457 void 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 
SlotReturnFunction(void * data)471 void 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 
FindEmptyThread() const482 int 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 
FindCounterOrEmptyRow(const std::string & name) const505 int 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 
AddCounter(const std::string & name)527 int StatsTable::AddCounter(const std::string& name) {
528 #ifdef ANDROID
529   return 0;
530 #else
531 
532   if (!impl_)
533     return 0;
534 
535   int counter_id = 0;
536   {
537     // To add a counter to the shared memory, we need the
538     // shared memory lock.
539     SharedMemoryAutoLock lock(impl_->shared_memory());
540 
541     // We have space, so create a new counter.
542     counter_id = FindCounterOrEmptyRow(name);
543     if (!counter_id)
544       return 0;
545 
546     std::string counter_name = name;
547     if (name.empty())
548       counter_name = kUnknownName;
549     strlcpy(impl_->counter_name(counter_id), counter_name.c_str(),
550             kMaxCounterNameLength);
551   }
552 
553   // now add to our in-memory cache
554   {
555     AutoLock lock(counters_lock_);
556     counters_[name] = counter_id;
557   }
558   return counter_id;
559 #endif
560 }
561 
GetTLSData() const562 StatsTable::TLSData* StatsTable::GetTLSData() const {
563   TLSData* data =
564     static_cast<TLSData*>(tls_index_.Get());
565   if (!data)
566     return NULL;
567 
568   DCHECK(data->slot);
569   DCHECK_EQ(data->table, this);
570   return data;
571 }
572 
573 }  // namespace base
574