1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/ipc/chromium/src/base/stats_table.cc Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,559 @@ 1.4 +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 1.5 +// Use of this source code is governed by a BSD-style license that can be 1.6 +// found in the LICENSE file. 1.7 + 1.8 +#include "base/stats_table.h" 1.9 + 1.10 +#include "base/logging.h" 1.11 +#include "base/platform_thread.h" 1.12 +#include "base/process_util.h" 1.13 +#include "base/scoped_ptr.h" 1.14 +#include "base/shared_memory.h" 1.15 +#include "base/string_piece.h" 1.16 +#include "base/string_util.h" 1.17 +#include "base/sys_string_conversions.h" 1.18 +#include "base/thread_local_storage.h" 1.19 + 1.20 +#if defined(OS_POSIX) 1.21 +#include "errno.h" 1.22 +#endif 1.23 + 1.24 +// The StatsTable uses a shared memory segment that is laid out as follows 1.25 +// 1.26 +// +-------------------------------------------+ 1.27 +// | Version | Size | MaxCounters | MaxThreads | 1.28 +// +-------------------------------------------+ 1.29 +// | Thread names table | 1.30 +// +-------------------------------------------+ 1.31 +// | Thread TID table | 1.32 +// +-------------------------------------------+ 1.33 +// | Thread PID table | 1.34 +// +-------------------------------------------+ 1.35 +// | Counter names table | 1.36 +// +-------------------------------------------+ 1.37 +// | Data | 1.38 +// +-------------------------------------------+ 1.39 +// 1.40 +// The data layout is a grid, where the columns are the thread_ids and the 1.41 +// rows are the counter_ids. 1.42 +// 1.43 +// If the first character of the thread_name is '\0', then that column is 1.44 +// empty. 1.45 +// If the first character of the counter_name is '\0', then that row is 1.46 +// empty. 1.47 +// 1.48 +// About Locking: 1.49 +// This class is designed to be both multi-thread and multi-process safe. 1.50 +// Aside from initialization, this is done by partitioning the data which 1.51 +// each thread uses so that no locking is required. However, to allocate 1.52 +// the rows and columns of the table to particular threads, locking is 1.53 +// required. 1.54 +// 1.55 +// At the shared-memory level, we have a lock. This lock protects the 1.56 +// shared-memory table only, and is used when we create new counters (e.g. 1.57 +// use rows) or when we register new threads (e.g. use columns). Reading 1.58 +// data from the table does not require any locking at the shared memory 1.59 +// level. 1.60 +// 1.61 +// Each process which accesses the table will create a StatsTable object. 1.62 +// The StatsTable maintains a hash table of the existing counters in the 1.63 +// table for faster lookup. Since the hash table is process specific, 1.64 +// each process maintains its own cache. We avoid complexity here by never 1.65 +// de-allocating from the hash table. (Counters are dynamically added, 1.66 +// but not dynamically removed). 1.67 + 1.68 +// In order for external viewers to be able to read our shared memory, 1.69 +// we all need to use the same size ints. 1.70 +COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints); 1.71 + 1.72 +namespace { 1.73 + 1.74 +// An internal version in case we ever change the format of this 1.75 +// file, and so that we can identify our table. 1.76 +const int kTableVersion = 0x13131313; 1.77 + 1.78 +// The name for un-named counters and threads in the table. 1.79 +const char kUnknownName[] = "<unknown>"; 1.80 + 1.81 +// Calculates delta to align an offset to the size of an int 1.82 +inline int AlignOffset(int offset) { 1.83 + return (sizeof(int) - (offset % sizeof(int))) % sizeof(int); 1.84 +} 1.85 + 1.86 +inline int AlignedSize(int size) { 1.87 + return size + AlignOffset(size); 1.88 +} 1.89 + 1.90 +// StatsTableTLSData carries the data stored in the TLS slots for the 1.91 +// StatsTable. This is used so that we can properly cleanup when the 1.92 +// thread exits and return the table slot. 1.93 +// 1.94 +// Each thread that calls RegisterThread in the StatsTable will have 1.95 +// a StatsTableTLSData stored in its TLS. 1.96 +struct StatsTableTLSData { 1.97 + StatsTable* table; 1.98 + int slot; 1.99 +}; 1.100 + 1.101 +} // namespace 1.102 + 1.103 +// The StatsTablePrivate maintains convenience pointers into the 1.104 +// shared memory segment. Use this class to keep the data structure 1.105 +// clean and accessible. 1.106 +class StatsTablePrivate { 1.107 + public: 1.108 + // Various header information contained in the memory mapped segment. 1.109 + struct TableHeader { 1.110 + int version; 1.111 + int size; 1.112 + int max_counters; 1.113 + int max_threads; 1.114 + }; 1.115 + 1.116 + // Construct a new StatsTablePrivate based on expected size parameters, or 1.117 + // return NULL on failure. 1.118 + static StatsTablePrivate* New(const std::string& name, int size, 1.119 + int max_threads, int max_counters); 1.120 + 1.121 + base::SharedMemory* shared_memory() { return &shared_memory_; } 1.122 + 1.123 + // Accessors for our header pointers 1.124 + TableHeader* table_header() const { return table_header_; } 1.125 + int version() const { return table_header_->version; } 1.126 + int size() const { return table_header_->size; } 1.127 + int max_counters() const { return table_header_->max_counters; } 1.128 + int max_threads() const { return table_header_->max_threads; } 1.129 + 1.130 + // Accessors for our tables 1.131 + char* thread_name(int slot_id) const { 1.132 + return &thread_names_table_[ 1.133 + (slot_id-1) * (StatsTable::kMaxThreadNameLength)]; 1.134 + } 1.135 + PlatformThreadId* thread_tid(int slot_id) const { 1.136 + return &(thread_tid_table_[slot_id-1]); 1.137 + } 1.138 + int* thread_pid(int slot_id) const { 1.139 + return &(thread_pid_table_[slot_id-1]); 1.140 + } 1.141 + char* counter_name(int counter_id) const { 1.142 + return &counter_names_table_[ 1.143 + (counter_id-1) * (StatsTable::kMaxCounterNameLength)]; 1.144 + } 1.145 + int* row(int counter_id) const { 1.146 + return &data_table_[(counter_id-1) * max_threads()]; 1.147 + } 1.148 + 1.149 + private: 1.150 + // Constructor is private because you should use New() instead. 1.151 + StatsTablePrivate() {} 1.152 + 1.153 + // Initializes the table on first access. Sets header values 1.154 + // appropriately and zeroes all counters. 1.155 + void InitializeTable(void* memory, int size, int max_counters, 1.156 + int max_threads); 1.157 + 1.158 + // Initializes our in-memory pointers into a pre-created StatsTable. 1.159 + void ComputeMappedPointers(void* memory); 1.160 + 1.161 + base::SharedMemory shared_memory_; 1.162 + TableHeader* table_header_; 1.163 + char* thread_names_table_; 1.164 + PlatformThreadId* thread_tid_table_; 1.165 + int* thread_pid_table_; 1.166 + char* counter_names_table_; 1.167 + int* data_table_; 1.168 +}; 1.169 + 1.170 +// static 1.171 +StatsTablePrivate* StatsTablePrivate::New(const std::string& name, 1.172 + int size, 1.173 + int max_threads, 1.174 + int max_counters) { 1.175 + scoped_ptr<StatsTablePrivate> priv(new StatsTablePrivate()); 1.176 + if (!priv->shared_memory_.Create(name, false, true, size)) 1.177 + return NULL; 1.178 + if (!priv->shared_memory_.Map(size)) 1.179 + return NULL; 1.180 + void* memory = priv->shared_memory_.memory(); 1.181 + 1.182 + TableHeader* header = static_cast<TableHeader*>(memory); 1.183 + 1.184 + // If the version does not match, then assume the table needs 1.185 + // to be initialized. 1.186 + if (header->version != kTableVersion) 1.187 + priv->InitializeTable(memory, size, max_counters, max_threads); 1.188 + 1.189 + // We have a valid table, so compute our pointers. 1.190 + priv->ComputeMappedPointers(memory); 1.191 + 1.192 + return priv.release(); 1.193 +} 1.194 + 1.195 +void StatsTablePrivate::InitializeTable(void* memory, int size, 1.196 + int max_counters, 1.197 + int max_threads) { 1.198 + // Zero everything. 1.199 + memset(memory, 0, size); 1.200 + 1.201 + // Initialize the header. 1.202 + TableHeader* header = static_cast<TableHeader*>(memory); 1.203 + header->version = kTableVersion; 1.204 + header->size = size; 1.205 + header->max_counters = max_counters; 1.206 + header->max_threads = max_threads; 1.207 +} 1.208 + 1.209 +void StatsTablePrivate::ComputeMappedPointers(void* memory) { 1.210 + char* data = static_cast<char*>(memory); 1.211 + int offset = 0; 1.212 + 1.213 + table_header_ = reinterpret_cast<TableHeader*>(data); 1.214 + offset += sizeof(*table_header_); 1.215 + offset += AlignOffset(offset); 1.216 + 1.217 + // Verify we're looking at a valid StatsTable. 1.218 + DCHECK_EQ(table_header_->version, kTableVersion); 1.219 + 1.220 + thread_names_table_ = reinterpret_cast<char*>(data + offset); 1.221 + offset += sizeof(char) * 1.222 + max_threads() * StatsTable::kMaxThreadNameLength; 1.223 + offset += AlignOffset(offset); 1.224 + 1.225 + thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset); 1.226 + offset += sizeof(int) * max_threads(); 1.227 + offset += AlignOffset(offset); 1.228 + 1.229 + thread_pid_table_ = reinterpret_cast<int*>(data + offset); 1.230 + offset += sizeof(int) * max_threads(); 1.231 + offset += AlignOffset(offset); 1.232 + 1.233 + counter_names_table_ = reinterpret_cast<char*>(data + offset); 1.234 + offset += sizeof(char) * 1.235 + max_counters() * StatsTable::kMaxCounterNameLength; 1.236 + offset += AlignOffset(offset); 1.237 + 1.238 + data_table_ = reinterpret_cast<int*>(data + offset); 1.239 + offset += sizeof(int) * max_threads() * max_counters(); 1.240 + 1.241 + DCHECK_EQ(offset, size()); 1.242 +} 1.243 + 1.244 + 1.245 + 1.246 +// We keep a singleton table which can be easily accessed. 1.247 +StatsTable* StatsTable::global_table_ = NULL; 1.248 + 1.249 +StatsTable::StatsTable(const std::string& name, int max_threads, 1.250 + int max_counters) 1.251 + : impl_(NULL), 1.252 + tls_index_(SlotReturnFunction) { 1.253 + int table_size = 1.254 + AlignedSize(sizeof(StatsTablePrivate::TableHeader)) + 1.255 + AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) + 1.256 + AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) + 1.257 + AlignedSize(max_threads * sizeof(int)) + 1.258 + AlignedSize(max_threads * sizeof(int)) + 1.259 + AlignedSize((sizeof(int) * (max_counters * max_threads))); 1.260 + 1.261 + impl_ = StatsTablePrivate::New(name, table_size, max_threads, max_counters); 1.262 + 1.263 + // TODO(port): clean up this error reporting. 1.264 +#if defined(OS_WIN) 1.265 + if (!impl_) 1.266 + CHROMIUM_LOG(ERROR) << "StatsTable did not initialize:" << GetLastError(); 1.267 +#elif defined(OS_POSIX) 1.268 + if (!impl_) 1.269 + CHROMIUM_LOG(ERROR) << "StatsTable did not initialize:" << strerror(errno); 1.270 +#endif 1.271 +} 1.272 + 1.273 +StatsTable::~StatsTable() { 1.274 + // Before we tear down our copy of the table, be sure to 1.275 + // unregister our thread. 1.276 + UnregisterThread(); 1.277 + 1.278 + // Return ThreadLocalStorage. At this point, if any registered threads 1.279 + // still exist, they cannot Unregister. 1.280 + tls_index_.Free(); 1.281 + 1.282 + // Cleanup our shared memory. 1.283 + delete impl_; 1.284 + 1.285 + // If we are the global table, unregister ourselves. 1.286 + if (global_table_ == this) 1.287 + global_table_ = NULL; 1.288 +} 1.289 + 1.290 +int StatsTable::RegisterThread(const std::string& name) { 1.291 + int slot = 0; 1.292 + 1.293 + // Registering a thread requires that we lock the shared memory 1.294 + // so that two threads don't grab the same slot. Fortunately, 1.295 + // thread creation shouldn't happen in inner loops. 1.296 + { 1.297 + base::SharedMemoryAutoLock lock(impl_->shared_memory()); 1.298 + slot = FindEmptyThread(); 1.299 + if (!slot) { 1.300 + return 0; 1.301 + } 1.302 + 1.303 + DCHECK(impl_); 1.304 + 1.305 + // We have space, so consume a column in the table. 1.306 + std::string thread_name = name; 1.307 + if (name.empty()) 1.308 + thread_name = kUnknownName; 1.309 + base::strlcpy(impl_->thread_name(slot), thread_name.c_str(), 1.310 + kMaxThreadNameLength); 1.311 + *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); 1.312 + *(impl_->thread_pid(slot)) = base::GetCurrentProcId(); 1.313 + } 1.314 + 1.315 + // Set our thread local storage. 1.316 + StatsTableTLSData* data = new StatsTableTLSData; 1.317 + data->table = this; 1.318 + data->slot = slot; 1.319 + tls_index_.Set(data); 1.320 + return slot; 1.321 +} 1.322 + 1.323 +StatsTableTLSData* StatsTable::GetTLSData() const { 1.324 + StatsTableTLSData* data = 1.325 + static_cast<StatsTableTLSData*>(tls_index_.Get()); 1.326 + if (!data) 1.327 + return NULL; 1.328 + 1.329 + DCHECK(data->slot); 1.330 + DCHECK_EQ(data->table, this); 1.331 + return data; 1.332 +} 1.333 + 1.334 +void StatsTable::UnregisterThread() { 1.335 + UnregisterThread(GetTLSData()); 1.336 +} 1.337 + 1.338 +void StatsTable::UnregisterThread(StatsTableTLSData* data) { 1.339 + if (!data) 1.340 + return; 1.341 + DCHECK(impl_); 1.342 + 1.343 + // Mark the slot free by zeroing out the thread name. 1.344 + char* name = impl_->thread_name(data->slot); 1.345 + *name = '\0'; 1.346 + 1.347 + // Remove the calling thread's TLS so that it cannot use the slot. 1.348 + tls_index_.Set(NULL); 1.349 + delete data; 1.350 +} 1.351 + 1.352 +void StatsTable::SlotReturnFunction(void* data) { 1.353 + // This is called by the TLS destructor, which on some platforms has 1.354 + // already cleared the TLS info, so use the tls_data argument 1.355 + // rather than trying to fetch it ourselves. 1.356 + StatsTableTLSData* tls_data = static_cast<StatsTableTLSData*>(data); 1.357 + if (tls_data) { 1.358 + DCHECK(tls_data->table); 1.359 + tls_data->table->UnregisterThread(tls_data); 1.360 + } 1.361 +} 1.362 + 1.363 +int StatsTable::CountThreadsRegistered() const { 1.364 + if (!impl_) 1.365 + return 0; 1.366 + 1.367 + // Loop through the shared memory and count the threads that are active. 1.368 + // We intentionally do not lock the table during the operation. 1.369 + int count = 0; 1.370 + for (int index = 1; index <= impl_->max_threads(); index++) { 1.371 + char* name = impl_->thread_name(index); 1.372 + if (*name != '\0') 1.373 + count++; 1.374 + } 1.375 + return count; 1.376 +} 1.377 + 1.378 +int StatsTable::GetSlot() const { 1.379 + StatsTableTLSData* data = GetTLSData(); 1.380 + if (!data) 1.381 + return 0; 1.382 + return data->slot; 1.383 +} 1.384 + 1.385 +int StatsTable::FindEmptyThread() const { 1.386 + // Note: the API returns slots numbered from 1..N, although 1.387 + // internally, the array is 0..N-1. This is so that we can return 1.388 + // zero as "not found". 1.389 + // 1.390 + // The reason for doing this is because the thread 'slot' is stored 1.391 + // in TLS, which is always initialized to zero, not -1. If 0 were 1.392 + // returned as a valid slot number, it would be confused with the 1.393 + // uninitialized state. 1.394 + if (!impl_) 1.395 + return 0; 1.396 + 1.397 + int index = 1; 1.398 + for (; index <= impl_->max_threads(); index++) { 1.399 + char* name = impl_->thread_name(index); 1.400 + if (!*name) 1.401 + break; 1.402 + } 1.403 + if (index > impl_->max_threads()) 1.404 + return 0; // The table is full. 1.405 + return index; 1.406 +} 1.407 + 1.408 +int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { 1.409 + // Note: the API returns slots numbered from 1..N, although 1.410 + // internally, the array is 0..N-1. This is so that we can return 1.411 + // zero as "not found". 1.412 + // 1.413 + // There isn't much reason for this other than to be consistent 1.414 + // with the way we track columns for thread slots. (See comments 1.415 + // in FindEmptyThread for why it is done this way). 1.416 + if (!impl_) 1.417 + return 0; 1.418 + 1.419 + int free_slot = 0; 1.420 + for (int index = 1; index <= impl_->max_counters(); index++) { 1.421 + char* row_name = impl_->counter_name(index); 1.422 + if (!*row_name && !free_slot) 1.423 + free_slot = index; // save that we found a free slot 1.424 + else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength)) 1.425 + return index; 1.426 + } 1.427 + return free_slot; 1.428 +} 1.429 + 1.430 +int StatsTable::FindCounter(const std::string& name) { 1.431 + // Note: the API returns counters numbered from 1..N, although 1.432 + // internally, the array is 0..N-1. This is so that we can return 1.433 + // zero as "not found". 1.434 + if (!impl_) 1.435 + return 0; 1.436 + 1.437 + // Create a scope for our auto-lock. 1.438 + { 1.439 + AutoLock scoped_lock(counters_lock_); 1.440 + 1.441 + // Attempt to find the counter. 1.442 + CountersMap::const_iterator iter; 1.443 + iter = counters_.find(name); 1.444 + if (iter != counters_.end()) 1.445 + return iter->second; 1.446 + } 1.447 + 1.448 + // Counter does not exist, so add it. 1.449 + return AddCounter(name); 1.450 +} 1.451 + 1.452 +int StatsTable::AddCounter(const std::string& name) { 1.453 + DCHECK(impl_); 1.454 + 1.455 + if (!impl_) 1.456 + return 0; 1.457 + 1.458 + int counter_id = 0; 1.459 + { 1.460 + // To add a counter to the shared memory, we need the 1.461 + // shared memory lock. 1.462 + base::SharedMemoryAutoLock lock(impl_->shared_memory()); 1.463 + 1.464 + // We have space, so create a new counter. 1.465 + counter_id = FindCounterOrEmptyRow(name); 1.466 + if (!counter_id) 1.467 + return 0; 1.468 + 1.469 + std::string counter_name = name; 1.470 + if (name.empty()) 1.471 + counter_name = kUnknownName; 1.472 + base::strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), 1.473 + kMaxCounterNameLength); 1.474 + } 1.475 + 1.476 + // now add to our in-memory cache 1.477 + { 1.478 + AutoLock lock(counters_lock_); 1.479 + counters_[name] = counter_id; 1.480 + } 1.481 + return counter_id; 1.482 +} 1.483 + 1.484 +int* StatsTable::GetLocation(int counter_id, int slot_id) const { 1.485 + if (!impl_) 1.486 + return NULL; 1.487 + if (slot_id > impl_->max_threads()) 1.488 + return NULL; 1.489 + 1.490 + int* row = impl_->row(counter_id); 1.491 + return &(row[slot_id-1]); 1.492 +} 1.493 + 1.494 +const char* StatsTable::GetRowName(int index) const { 1.495 + if (!impl_) 1.496 + return NULL; 1.497 + 1.498 + return impl_->counter_name(index); 1.499 +} 1.500 + 1.501 +int StatsTable::GetRowValue(int index, int pid) const { 1.502 + if (!impl_) 1.503 + return 0; 1.504 + 1.505 + int rv = 0; 1.506 + int* row = impl_->row(index); 1.507 + for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) { 1.508 + if (pid == 0 || *impl_->thread_pid(slot_id) == pid) 1.509 + rv += row[slot_id]; 1.510 + } 1.511 + return rv; 1.512 +} 1.513 + 1.514 +int StatsTable::GetRowValue(int index) const { 1.515 + return GetRowValue(index, 0); 1.516 +} 1.517 + 1.518 +int StatsTable::GetCounterValue(const std::string& name, int pid) { 1.519 + if (!impl_) 1.520 + return 0; 1.521 + 1.522 + int row = FindCounter(name); 1.523 + if (!row) 1.524 + return 0; 1.525 + return GetRowValue(row, pid); 1.526 +} 1.527 + 1.528 +int StatsTable::GetCounterValue(const std::string& name) { 1.529 + return GetCounterValue(name, 0); 1.530 +} 1.531 + 1.532 +int StatsTable::GetMaxCounters() const { 1.533 + if (!impl_) 1.534 + return 0; 1.535 + return impl_->max_counters(); 1.536 +} 1.537 + 1.538 +int StatsTable::GetMaxThreads() const { 1.539 + if (!impl_) 1.540 + return 0; 1.541 + return impl_->max_threads(); 1.542 +} 1.543 + 1.544 +int* StatsTable::FindLocation(const char* name) { 1.545 + // Get the static StatsTable 1.546 + StatsTable *table = StatsTable::current(); 1.547 + if (!table) 1.548 + return NULL; 1.549 + 1.550 + // Get the slot for this thread. Try to register 1.551 + // it if none exists. 1.552 + int slot = table->GetSlot(); 1.553 + if (!slot && !(slot = table->RegisterThread(""))) 1.554 + return NULL; 1.555 + 1.556 + // Find the counter id for the counter. 1.557 + std::string str_name(name); 1.558 + int counter = table->FindCounter(str_name); 1.559 + 1.560 + // Now we can find the location in the table. 1.561 + return table->GetLocation(counter, slot); 1.562 +}