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