ipc/chromium/src/base/stats_table.cc

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:51cf8592b947
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 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(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 // TODO(port): clean up this error reporting.
261 #if defined(OS_WIN)
262 if (!impl_)
263 CHROMIUM_LOG(ERROR) << "StatsTable did not initialize:" << GetLastError();
264 #elif defined(OS_POSIX)
265 if (!impl_)
266 CHROMIUM_LOG(ERROR) << "StatsTable did not initialize:" << strerror(errno);
267 #endif
268 }
269
270 StatsTable::~StatsTable() {
271 // Before we tear down our copy of the table, be sure to
272 // unregister our thread.
273 UnregisterThread();
274
275 // Return ThreadLocalStorage. At this point, if any registered threads
276 // still exist, they cannot Unregister.
277 tls_index_.Free();
278
279 // Cleanup our shared memory.
280 delete impl_;
281
282 // If we are the global table, unregister ourselves.
283 if (global_table_ == this)
284 global_table_ = NULL;
285 }
286
287 int StatsTable::RegisterThread(const std::string& name) {
288 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 base::SharedMemoryAutoLock lock(impl_->shared_memory());
295 slot = FindEmptyThread();
296 if (!slot) {
297 return 0;
298 }
299
300 DCHECK(impl_);
301
302 // We have space, so consume a column in the table.
303 std::string thread_name = name;
304 if (name.empty())
305 thread_name = kUnknownName;
306 base::strlcpy(impl_->thread_name(slot), thread_name.c_str(),
307 kMaxThreadNameLength);
308 *(impl_->thread_tid(slot)) = PlatformThread::CurrentId();
309 *(impl_->thread_pid(slot)) = base::GetCurrentProcId();
310 }
311
312 // Set our thread local storage.
313 StatsTableTLSData* data = new StatsTableTLSData;
314 data->table = this;
315 data->slot = slot;
316 tls_index_.Set(data);
317 return slot;
318 }
319
320 StatsTableTLSData* StatsTable::GetTLSData() const {
321 StatsTableTLSData* data =
322 static_cast<StatsTableTLSData*>(tls_index_.Get());
323 if (!data)
324 return NULL;
325
326 DCHECK(data->slot);
327 DCHECK_EQ(data->table, this);
328 return data;
329 }
330
331 void StatsTable::UnregisterThread() {
332 UnregisterThread(GetTLSData());
333 }
334
335 void StatsTable::UnregisterThread(StatsTableTLSData* data) {
336 if (!data)
337 return;
338 DCHECK(impl_);
339
340 // Mark the slot free by zeroing out the thread name.
341 char* name = impl_->thread_name(data->slot);
342 *name = '\0';
343
344 // Remove the calling thread's TLS so that it cannot use the slot.
345 tls_index_.Set(NULL);
346 delete data;
347 }
348
349 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 StatsTableTLSData* tls_data = static_cast<StatsTableTLSData*>(data);
354 if (tls_data) {
355 DCHECK(tls_data->table);
356 tls_data->table->UnregisterThread(tls_data);
357 }
358 }
359
360 int StatsTable::CountThreadsRegistered() const {
361 if (!impl_)
362 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 int count = 0;
367 for (int index = 1; index <= impl_->max_threads(); index++) {
368 char* name = impl_->thread_name(index);
369 if (*name != '\0')
370 count++;
371 }
372 return count;
373 }
374
375 int StatsTable::GetSlot() const {
376 StatsTableTLSData* data = GetTLSData();
377 if (!data)
378 return 0;
379 return data->slot;
380 }
381
382 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 if (!impl_)
392 return 0;
393
394 int index = 1;
395 for (; index <= impl_->max_threads(); index++) {
396 char* name = impl_->thread_name(index);
397 if (!*name)
398 break;
399 }
400 if (index > impl_->max_threads())
401 return 0; // The table is full.
402 return index;
403 }
404
405 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 if (!impl_)
414 return 0;
415
416 int free_slot = 0;
417 for (int index = 1; index <= impl_->max_counters(); index++) {
418 char* row_name = impl_->counter_name(index);
419 if (!*row_name && !free_slot)
420 free_slot = index; // save that we found a free slot
421 else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength))
422 return index;
423 }
424 return free_slot;
425 }
426
427 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 if (!impl_)
432 return 0;
433
434 // Create a scope for our auto-lock.
435 {
436 AutoLock scoped_lock(counters_lock_);
437
438 // Attempt to find the counter.
439 CountersMap::const_iterator iter;
440 iter = counters_.find(name);
441 if (iter != counters_.end())
442 return iter->second;
443 }
444
445 // Counter does not exist, so add it.
446 return AddCounter(name);
447 }
448
449 int StatsTable::AddCounter(const std::string& name) {
450 DCHECK(impl_);
451
452 if (!impl_)
453 return 0;
454
455 int counter_id = 0;
456 {
457 // To add a counter to the shared memory, we need the
458 // shared memory lock.
459 base::SharedMemoryAutoLock lock(impl_->shared_memory());
460
461 // We have space, so create a new counter.
462 counter_id = FindCounterOrEmptyRow(name);
463 if (!counter_id)
464 return 0;
465
466 std::string counter_name = name;
467 if (name.empty())
468 counter_name = kUnknownName;
469 base::strlcpy(impl_->counter_name(counter_id), counter_name.c_str(),
470 kMaxCounterNameLength);
471 }
472
473 // now add to our in-memory cache
474 {
475 AutoLock lock(counters_lock_);
476 counters_[name] = counter_id;
477 }
478 return counter_id;
479 }
480
481 int* StatsTable::GetLocation(int counter_id, int slot_id) const {
482 if (!impl_)
483 return NULL;
484 if (slot_id > impl_->max_threads())
485 return NULL;
486
487 int* row = impl_->row(counter_id);
488 return &(row[slot_id-1]);
489 }
490
491 const char* StatsTable::GetRowName(int index) const {
492 if (!impl_)
493 return NULL;
494
495 return impl_->counter_name(index);
496 }
497
498 int StatsTable::GetRowValue(int index, int pid) const {
499 if (!impl_)
500 return 0;
501
502 int rv = 0;
503 int* row = impl_->row(index);
504 for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) {
505 if (pid == 0 || *impl_->thread_pid(slot_id) == pid)
506 rv += row[slot_id];
507 }
508 return rv;
509 }
510
511 int StatsTable::GetRowValue(int index) const {
512 return GetRowValue(index, 0);
513 }
514
515 int StatsTable::GetCounterValue(const std::string& name, int pid) {
516 if (!impl_)
517 return 0;
518
519 int row = FindCounter(name);
520 if (!row)
521 return 0;
522 return GetRowValue(row, pid);
523 }
524
525 int StatsTable::GetCounterValue(const std::string& name) {
526 return GetCounterValue(name, 0);
527 }
528
529 int StatsTable::GetMaxCounters() const {
530 if (!impl_)
531 return 0;
532 return impl_->max_counters();
533 }
534
535 int StatsTable::GetMaxThreads() const {
536 if (!impl_)
537 return 0;
538 return impl_->max_threads();
539 }
540
541 int* StatsTable::FindLocation(const char* name) {
542 // Get the static StatsTable
543 StatsTable *table = StatsTable::current();
544 if (!table)
545 return NULL;
546
547 // Get the slot for this thread. Try to register
548 // it if none exists.
549 int slot = table->GetSlot();
550 if (!slot && !(slot = table->RegisterThread("")))
551 return NULL;
552
553 // Find the counter id for the counter.
554 std::string str_name(name);
555 int counter = table->FindCounter(str_name);
556
557 // Now we can find the location in the table.
558 return table->GetLocation(counter, slot);
559 }

mercurial