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