blob: ba08f1c475167bbde511ca1e94fb5d336b6e77f7 [file] [log] [blame]
license.botf003cfe2008-08-24 09:55:55 +09001// 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.
initial.commit3f4a7322008-07-27 06:49:38 +09004
5#include "base/stats_table.h"
6
pinkerton@google.com44159e42008-08-14 08:20:03 +09007#include "base/string_util.h"
initial.commit3f4a7322008-07-27 06:49:38 +09008#include "base/logging.h"
9#include "base/thread_local_storage.h"
pinkerton@google.com44159e42008-08-14 08:20:03 +090010#include "base/platform_thread.h"
11
12#if defined(OS_POSIX)
13#include "errno.h"
14#endif
initial.commit3f4a7322008-07-27 06:49:38 +090015
16// The StatsTable uses a shared memory segment that is laid out as follows
17//
18// +-------------------------------------------+
19// | Version | Size | MaxCounters | MaxThreads |
20// +-------------------------------------------+
21// | Thread names table |
22// +-------------------------------------------+
23// | Thread TID table |
24// +-------------------------------------------+
25// | Thread PID table |
26// +-------------------------------------------+
27// | Counter names table |
28// +-------------------------------------------+
29// | Data |
30// +-------------------------------------------+
31//
32// The data layout is a grid, where the columns are the thread_ids and the
33// rows are the counter_ids.
34//
35// If the first character of the thread_name is '\0', then that column is
36// empty.
37// If the first character of the counter_name is '\0', then that row is
38// empty.
39//
40// About Locking:
41// This class is designed to be both multi-thread and multi-process safe.
42// Aside from initialization, this is done by partitioning the data which
43// each thread uses so that no locking is required. However, to allocate
44// the rows and columns of the table to particular threads, locking is
45// required.
46//
47// At the shared-memory level, we have a lock. This lock protects the
48// shared-memory table only, and is used when we create new counters (e.g.
49// use rows) or when we register new threads (e.g. use columns). Reading
50// data from the table does not require any locking at the shared memory
51// level.
52//
53// Each process which accesses the table will create a StatsTable object.
54// The StatsTable maintains a hash table of the existing counters in the
55// table for faster lookup. Since the hash table is process specific,
56// each process maintains its own cache. We avoid complexity here by never
57// de-allocating from the hash table. (Counters are dynamically added,
58// but not dynamically removed).
59
60// In order for external viewers to be able to read our shared memory,
61// we all need to use the same size ints.
62COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints);
63
64namespace {
65
66// An internal version in case we ever change the format of this
67// file, and so that we can identify our table.
68const int kTableVersion = 0x13131313;
69
70// The name for un-named counters and threads in the table.
71const wchar_t kUnknownName[] = L"<unknown>";
72
73// Various header information contained in the memory mapped segment.
74struct TableHeader {
75 int version;
76 int size;
77 int max_counters;
78 int max_threads;
79};
80
81// Calculates delta to align an offset to the size of an int
82inline int AlignOffset(int offset) {
83 return (sizeof(int) - (offset % sizeof(int))) % sizeof(int);
84}
85
86inline int AlignedSize(int size) {
87 return size + AlignOffset(size);
88}
89
90// StatsTableTLSData carries the data stored in the TLS slots for the
91// StatsTable. This is used so that we can properly cleanup when the
92// thread exits and return the table slot.
93//
94// Each thread that calls RegisterThread in the StatsTable will have
95// a StatsTableTLSData stored in its TLS.
96struct StatsTableTLSData {
97 StatsTable* table;
98 int slot;
99};
100
101// The SlotReturnFunction is called at thread exit for each thread
102// which used the StatsTable.
103static void SlotReturnFunction(void* data) {
104 StatsTableTLSData* tls_data = static_cast<StatsTableTLSData*>(data);
105 if (tls_data) {
106 DCHECK(tls_data->table);
107 tls_data->table->UnregisterThread();
108 }
109}
110
111} // namespace
112
113// The StatsTablePrivate maintains convenience pointers into the
114// shared memory segment. Use this class to keep the data structure
115// clean and accessible.
116class StatsTablePrivate {
117 public:
118 // Create the StatsTablePrivate based on expected size parameters.
119 StatsTablePrivate(void* memory, int size, int max_threads, int max_counters);
120
121 // Accessors for our header pointers
122 TableHeader* table_header() const { return table_header_; }
123 int version() const { return table_header_->version; }
124 int size() const { return table_header_->size; }
125 int max_counters() const { return table_header_->max_counters; }
126 int max_threads() const { return table_header_->max_threads; }
127
128 // Accessors for our tables
129 wchar_t* thread_name(int slot_id) const {
130 return &thread_names_table_[
131 (slot_id-1) * (StatsTable::kMaxThreadNameLength)];
132 }
133 int* thread_tid(int slot_id) const {
134 return &(thread_tid_table_[slot_id-1]);
135 }
136 int* thread_pid(int slot_id) const {
137 return &(thread_pid_table_[slot_id-1]);
138 }
139 wchar_t* counter_name(int counter_id) const {
140 return &counter_names_table_[
141 (counter_id-1) * (StatsTable::kMaxCounterNameLength)];
142 }
143 int* row(int counter_id) const {
144 return &data_table_[(counter_id-1) * max_threads()];
145 }
146
147 private:
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 TableHeader* table_header_;
157 wchar_t* thread_names_table_;
158 int* thread_tid_table_;
159 int* thread_pid_table_;
160 wchar_t* counter_names_table_;
161 int* data_table_;
162};
163
164StatsTablePrivate::StatsTablePrivate(void* memory, int size, int max_threads,
165 int max_counters) {
166 TableHeader* header = static_cast<TableHeader*>(memory);
167 // If the version does not match, then assume the table needs
168 // to be initialized.
169 if (header->version != kTableVersion)
170 InitializeTable(memory, size, max_counters, max_threads);
171
172 // We have a valid table, so compute our pointers.
173 ComputeMappedPointers(memory);
174}
175
176void StatsTablePrivate::InitializeTable(void* memory, int size,
177 int max_counters,
178 int max_threads) {
179 // Zero everything.
180 memset(memory, 0, size);
181
182 // Initialize the header.
183 TableHeader* header = static_cast<TableHeader*>(memory);
184 header->version = kTableVersion;
185 header->size = size;
186 header->max_counters = max_counters;
187 header->max_threads = max_threads;
188}
189
190void StatsTablePrivate::ComputeMappedPointers(void* memory) {
191 char* data = static_cast<char*>(memory);
192 int offset = 0;
193
194 table_header_ = reinterpret_cast<TableHeader*>(data);
195 offset += sizeof(*table_header_);
196 offset += AlignOffset(offset);
197
198 // Verify we're looking at a valid StatsTable.
199 DCHECK_EQ(table_header_->version, kTableVersion);
200
201 thread_names_table_ = reinterpret_cast<wchar_t*>(data + offset);
202 offset += sizeof(wchar_t) *
203 max_threads() * StatsTable::kMaxThreadNameLength;
204 offset += AlignOffset(offset);
205
206 thread_tid_table_ = reinterpret_cast<int*>(data + offset);
207 offset += sizeof(int) * max_threads();
208 offset += AlignOffset(offset);
209
210 thread_pid_table_ = reinterpret_cast<int*>(data + offset);
211 offset += sizeof(int) * max_threads();
212 offset += AlignOffset(offset);
213
214 counter_names_table_ = reinterpret_cast<wchar_t*>(data + offset);
215 offset += sizeof(wchar_t) *
216 max_counters() * StatsTable::kMaxCounterNameLength;
217 offset += AlignOffset(offset);
218
219 data_table_ = reinterpret_cast<int*>(data + offset);
220 offset += sizeof(int) * max_threads() * max_counters();
221
222 DCHECK_EQ(offset, size());
223}
224
225
226
227// We keep a singleton table which can be easily accessed.
228StatsTable* StatsTable::global_table_ = NULL;
229
230StatsTable::StatsTable(const std::wstring& name, int max_threads,
231 int max_counters)
evanm@google.comf26fd3a2008-08-21 07:54:52 +0900232 : tls_index_(SlotReturnFunction) {
initial.commit3f4a7322008-07-27 06:49:38 +0900233 int table_size =
234 AlignedSize(sizeof(TableHeader)) +
235 AlignedSize((max_counters * sizeof(wchar_t) * kMaxCounterNameLength)) +
236 AlignedSize((max_threads * sizeof(wchar_t) * kMaxThreadNameLength)) +
237 AlignedSize(max_threads * sizeof(int)) +
238 AlignedSize(max_threads * sizeof(int)) +
239 AlignedSize((sizeof(int) * (max_counters * max_threads)));
240
241 impl_ = NULL;
242 // TODO(mbelshe): Move this out of the constructor
243 if (shared_memory_.Create(name, false, true, table_size))
244 if (shared_memory_.Map(table_size))
245 impl_ = new StatsTablePrivate(shared_memory_.memory(), table_size,
246 max_threads, max_counters);
pinkerton@google.com44159e42008-08-14 08:20:03 +0900247#if defined(OS_WIN)
initial.commit3f4a7322008-07-27 06:49:38 +0900248 if (!impl_)
249 LOG(ERROR) << "StatsTable did not initialize:" << GetLastError();
pinkerton@google.com44159e42008-08-14 08:20:03 +0900250#elif defined(OS_POSIX)
251 if (!impl_)
252 LOG(ERROR) << "StatsTable did not initialize:" << strerror(errno);
253#endif
initial.commit3f4a7322008-07-27 06:49:38 +0900254}
255
256StatsTable::~StatsTable() {
257 // Before we tear down our copy of the table, be sure to
258 // unregister our thread.
259 UnregisterThread();
260
261 // Return ThreadLocalStorage. At this point, if any registered threads
262 // still exist, they cannot Unregister.
evanm@google.comf26fd3a2008-08-21 07:54:52 +0900263 tls_index_.Free();
initial.commit3f4a7322008-07-27 06:49:38 +0900264
265 // Cleanup our shared memory.
266 delete impl_;
267
268 // If we are the global table, unregister ourselves.
269 if (global_table_ == this)
270 global_table_ = NULL;
271}
272
273int StatsTable::RegisterThread(const std::wstring& name) {
274 int slot = 0;
275
276 // Registering a thread requires that we lock the shared memory
277 // so that two threads don't grab the same slot. Fortunately,
278 // thread creation shouldn't happen in inner loops.
279 {
280 SharedMemoryAutoLock lock(&shared_memory_);
281 slot = FindEmptyThread();
282 if (!slot) {
283 return 0;
284 }
285
286 DCHECK(impl_);
287
288 // We have space, so consume a column in the table.
289 std::wstring thread_name = name;
290 if (name.empty())
291 thread_name = kUnknownName;
pinkerton@google.com44159e42008-08-14 08:20:03 +0900292 base::wcslcpy(impl_->thread_name(slot), thread_name.c_str(),
293 kMaxThreadNameLength);
294 *(impl_->thread_tid(slot)) = PlatformThread::CurrentId();
295 // TODO(pinkerton): these should go into process_utils when it's ported
296#if defined(OS_WIN)
initial.commit3f4a7322008-07-27 06:49:38 +0900297 *(impl_->thread_pid(slot)) = GetCurrentProcessId();
pinkerton@google.com44159e42008-08-14 08:20:03 +0900298#elif defined(OS_POSIX)
299 *(impl_->thread_pid(slot)) = getpid();
300#endif
initial.commit3f4a7322008-07-27 06:49:38 +0900301 }
302
303 // Set our thread local storage.
304 StatsTableTLSData* data = new StatsTableTLSData;
305 data->table = this;
306 data->slot = slot;
evanm@google.comf26fd3a2008-08-21 07:54:52 +0900307 tls_index_.Set(data);
initial.commit3f4a7322008-07-27 06:49:38 +0900308 return slot;
309}
310
311StatsTableTLSData* StatsTable::GetTLSData() const {
312 StatsTableTLSData* data =
evanm@google.comf26fd3a2008-08-21 07:54:52 +0900313 static_cast<StatsTableTLSData*>(tls_index_.Get());
initial.commit3f4a7322008-07-27 06:49:38 +0900314 if (!data)
315 return NULL;
316
317 DCHECK(data->slot);
318 DCHECK_EQ(data->table, this);
319 return data;
320}
321
322void StatsTable::UnregisterThread() {
323 StatsTableTLSData* data = GetTLSData();
324 if (!data)
325 return;
326 DCHECK(impl_);
327
328 // Mark the slot free by zeroing out the thread name.
329 wchar_t* name = impl_->thread_name(data->slot);
330 *name = L'\0';
331
332 // Remove the calling thread's TLS so that it cannot use the slot.
evanm@google.comf26fd3a2008-08-21 07:54:52 +0900333 tls_index_.Set(NULL);
initial.commit3f4a7322008-07-27 06:49:38 +0900334 delete data;
335}
336
337int StatsTable::CountThreadsRegistered() const {
338 if (!impl_)
339 return 0;
340
341 // Loop through the shared memory and count the threads that are active.
342 // We intentionally do not lock the table during the operation.
343 int count = 0;
344 for (int index = 1; index <= impl_->max_threads(); index++) {
345 wchar_t* name = impl_->thread_name(index);
346 if (*name != L'\0')
347 count++;
348 }
349 return count;
350}
351
352int StatsTable::GetSlot() const {
353 StatsTableTLSData* data = GetTLSData();
354 if (!data)
355 return 0;
356 return data->slot;
357}
358
359int StatsTable::FindEmptyThread() const {
360 // Note: the API returns slots numbered from 1..N, although
361 // internally, the array is 0..N-1. This is so that we can return
362 // zero as "not found".
363 //
364 // The reason for doing this is because the thread 'slot' is stored
365 // in TLS, which is always initialized to zero, not -1. If 0 were
366 // returned as a valid slot number, it would be confused with the
367 // uninitialized state.
368 if (!impl_)
369 return 0;
370
371 int index = 1;
372 for (; index <= impl_->max_threads(); index++) {
373 wchar_t* name = impl_->thread_name(index);
374 if (!*name)
375 break;
376 }
377 if (index > impl_->max_threads())
378 return 0; // The table is full.
379 return index;
380}
381
382int StatsTable::FindCounterOrEmptyRow(const std::wstring& name) 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 // There isn't much reason for this other than to be consistent
388 // with the way we track columns for thread slots. (See comments
389 // in FindEmptyThread for why it is done this way).
390 if (!impl_)
391 return 0;
392
393 int free_slot = 0;
394 for (int index = 1; index <= impl_->max_counters(); index++) {
395 wchar_t* row_name = impl_->counter_name(index);
396 if (!*row_name && !free_slot)
397 free_slot = index; // save that we found a free slot
398 else if (!wcsncmp(row_name, name.c_str(), kMaxCounterNameLength))
399 return index;
400 }
401 return free_slot;
402}
403
404int StatsTable::FindCounter(const std::wstring& name) {
405 // Note: the API returns counters numbered from 1..N, although
406 // internally, the array is 0..N-1. This is so that we can return
407 // zero as "not found".
408 if (!impl_)
409 return 0;
410
411 // Create a scope for our auto-lock.
412 {
413 AutoLock scoped_lock(counters_lock_);
414
415 // Attempt to find the counter.
416 CountersMap::const_iterator iter;
417 iter = counters_.find(name);
418 if (iter != counters_.end())
419 return iter->second;
420 }
421
422 // Counter does not exist, so add it.
423 return AddCounter(name);
424}
425
426int StatsTable::AddCounter(const std::wstring& name) {
427 DCHECK(impl_);
428
429 if (!impl_)
430 return 0;
431
432 int counter_id = 0;
433 {
434 // To add a counter to the shared memory, we need the
435 // shared memory lock.
436 SharedMemoryAutoLock lock(&shared_memory_);
437
438 // We have space, so create a new counter.
439 counter_id = FindCounterOrEmptyRow(name);
440 if (!counter_id)
441 return 0;
442
443 std::wstring counter_name = name;
444 if (name.empty())
445 counter_name = kUnknownName;
pinkerton@google.com44159e42008-08-14 08:20:03 +0900446 base::wcslcpy(impl_->counter_name(counter_id), counter_name.c_str(),
447 kMaxCounterNameLength);
initial.commit3f4a7322008-07-27 06:49:38 +0900448 }
449
450 // now add to our in-memory cache
451 {
452 AutoLock lock(counters_lock_);
453 counters_[name] = counter_id;
454 }
455 return counter_id;
456}
457
458int* StatsTable::GetLocation(int counter_id, int slot_id) const {
459 if (!impl_)
460 return NULL;
461 if (slot_id > impl_->max_threads())
462 return NULL;
463
464 int* row = impl_->row(counter_id);
465 return &(row[slot_id-1]);
466}
467
468const wchar_t* StatsTable::GetRowName(int index) const {
469 if (!impl_)
470 return NULL;
471
472 return impl_->counter_name(index);
473}
474
475int StatsTable::GetRowValue(int index, int pid) const {
476 if (!impl_)
477 return 0;
478
479 int rv = 0;
480 int* row = impl_->row(index);
481 for (int index = 0; index < impl_->max_threads(); index++) {
482 if (pid == 0 || *impl_->thread_pid(index) == pid)
483 rv += row[index];
484 }
485 return rv;
486}
487
488int StatsTable::GetRowValue(int index) const {
489 return GetRowValue(index, 0);
490}
491
492int StatsTable::GetCounterValue(const std::wstring& name, int pid) {
493 if (!impl_)
494 return 0;
495
496 int row = FindCounter(name);
497 if (!row)
498 return 0;
499 return GetRowValue(row, pid);
500}
501
502int StatsTable::GetCounterValue(const std::wstring& name) {
503 return GetCounterValue(name, 0);
504}
505
506int StatsTable::GetMaxCounters() const {
507 if (!impl_)
508 return 0;
509 return impl_->max_counters();
510}
511
512int StatsTable::GetMaxThreads() const {
513 if (!impl_)
514 return 0;
515 return impl_->max_threads();
516}
517
518int* StatsTable::FindLocation(const wchar_t* name) {
519 // Get the static StatsTable
520 StatsTable *table = StatsTable::current();
521 if (!table)
522 return NULL;
523
524 // Get the slot for this thread. Try to register
525 // it if none exists.
526 int slot = table->GetSlot();
527 if (!slot && !(slot = table->RegisterThread(L"")))
528 return NULL;
529
530 // Find the counter id for the counter.
531 std::wstring str_name(name);
532 int counter = table->FindCounter(str_name);
533
534 // Now we can find the location in the table.
535 return table->GetLocation(counter, slot);
536}
license.botf003cfe2008-08-24 09:55:55 +0900537