michael@0: // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/stats_table.h" michael@0: michael@0: #include "base/logging.h" michael@0: #include "base/platform_thread.h" michael@0: #include "base/process_util.h" michael@0: #include "base/scoped_ptr.h" michael@0: #include "base/shared_memory.h" michael@0: #include "base/string_piece.h" michael@0: #include "base/string_util.h" michael@0: #include "base/sys_string_conversions.h" michael@0: #include "base/thread_local_storage.h" michael@0: michael@0: #if defined(OS_POSIX) michael@0: #include "errno.h" michael@0: #endif michael@0: michael@0: // The StatsTable uses a shared memory segment that is laid out as follows michael@0: // michael@0: // +-------------------------------------------+ michael@0: // | Version | Size | MaxCounters | MaxThreads | michael@0: // +-------------------------------------------+ michael@0: // | Thread names table | michael@0: // +-------------------------------------------+ michael@0: // | Thread TID table | michael@0: // +-------------------------------------------+ michael@0: // | Thread PID table | michael@0: // +-------------------------------------------+ michael@0: // | Counter names table | michael@0: // +-------------------------------------------+ michael@0: // | Data | michael@0: // +-------------------------------------------+ michael@0: // michael@0: // The data layout is a grid, where the columns are the thread_ids and the michael@0: // rows are the counter_ids. michael@0: // michael@0: // If the first character of the thread_name is '\0', then that column is michael@0: // empty. michael@0: // If the first character of the counter_name is '\0', then that row is michael@0: // empty. michael@0: // michael@0: // About Locking: michael@0: // This class is designed to be both multi-thread and multi-process safe. michael@0: // Aside from initialization, this is done by partitioning the data which michael@0: // each thread uses so that no locking is required. However, to allocate michael@0: // the rows and columns of the table to particular threads, locking is michael@0: // required. michael@0: // michael@0: // At the shared-memory level, we have a lock. This lock protects the michael@0: // shared-memory table only, and is used when we create new counters (e.g. michael@0: // use rows) or when we register new threads (e.g. use columns). Reading michael@0: // data from the table does not require any locking at the shared memory michael@0: // level. michael@0: // michael@0: // Each process which accesses the table will create a StatsTable object. michael@0: // The StatsTable maintains a hash table of the existing counters in the michael@0: // table for faster lookup. Since the hash table is process specific, michael@0: // each process maintains its own cache. We avoid complexity here by never michael@0: // de-allocating from the hash table. (Counters are dynamically added, michael@0: // but not dynamically removed). michael@0: michael@0: // In order for external viewers to be able to read our shared memory, michael@0: // we all need to use the same size ints. michael@0: COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints); michael@0: michael@0: namespace { michael@0: michael@0: // An internal version in case we ever change the format of this michael@0: // file, and so that we can identify our table. michael@0: const int kTableVersion = 0x13131313; michael@0: michael@0: // The name for un-named counters and threads in the table. michael@0: const char kUnknownName[] = ""; michael@0: michael@0: // Calculates delta to align an offset to the size of an int michael@0: inline int AlignOffset(int offset) { michael@0: return (sizeof(int) - (offset % sizeof(int))) % sizeof(int); michael@0: } michael@0: michael@0: inline int AlignedSize(int size) { michael@0: return size + AlignOffset(size); michael@0: } michael@0: michael@0: // StatsTableTLSData carries the data stored in the TLS slots for the michael@0: // StatsTable. This is used so that we can properly cleanup when the michael@0: // thread exits and return the table slot. michael@0: // michael@0: // Each thread that calls RegisterThread in the StatsTable will have michael@0: // a StatsTableTLSData stored in its TLS. michael@0: struct StatsTableTLSData { michael@0: StatsTable* table; michael@0: int slot; michael@0: }; michael@0: michael@0: } // namespace michael@0: michael@0: // The StatsTablePrivate maintains convenience pointers into the michael@0: // shared memory segment. Use this class to keep the data structure michael@0: // clean and accessible. michael@0: class StatsTablePrivate { michael@0: public: michael@0: // Various header information contained in the memory mapped segment. michael@0: struct TableHeader { michael@0: int version; michael@0: int size; michael@0: int max_counters; michael@0: int max_threads; michael@0: }; michael@0: michael@0: // Construct a new StatsTablePrivate based on expected size parameters, or michael@0: // return NULL on failure. michael@0: static StatsTablePrivate* New(const std::string& name, int size, michael@0: int max_threads, int max_counters); michael@0: michael@0: base::SharedMemory* shared_memory() { return &shared_memory_; } michael@0: michael@0: // Accessors for our header pointers michael@0: TableHeader* table_header() const { return table_header_; } michael@0: int version() const { return table_header_->version; } michael@0: int size() const { return table_header_->size; } michael@0: int max_counters() const { return table_header_->max_counters; } michael@0: int max_threads() const { return table_header_->max_threads; } michael@0: michael@0: // Accessors for our tables michael@0: char* thread_name(int slot_id) const { michael@0: return &thread_names_table_[ michael@0: (slot_id-1) * (StatsTable::kMaxThreadNameLength)]; michael@0: } michael@0: PlatformThreadId* thread_tid(int slot_id) const { michael@0: return &(thread_tid_table_[slot_id-1]); michael@0: } michael@0: int* thread_pid(int slot_id) const { michael@0: return &(thread_pid_table_[slot_id-1]); michael@0: } michael@0: char* counter_name(int counter_id) const { michael@0: return &counter_names_table_[ michael@0: (counter_id-1) * (StatsTable::kMaxCounterNameLength)]; michael@0: } michael@0: int* row(int counter_id) const { michael@0: return &data_table_[(counter_id-1) * max_threads()]; michael@0: } michael@0: michael@0: private: michael@0: // Constructor is private because you should use New() instead. michael@0: StatsTablePrivate() {} michael@0: michael@0: // Initializes the table on first access. Sets header values michael@0: // appropriately and zeroes all counters. michael@0: void InitializeTable(void* memory, int size, int max_counters, michael@0: int max_threads); michael@0: michael@0: // Initializes our in-memory pointers into a pre-created StatsTable. michael@0: void ComputeMappedPointers(void* memory); michael@0: michael@0: base::SharedMemory shared_memory_; michael@0: TableHeader* table_header_; michael@0: char* thread_names_table_; michael@0: PlatformThreadId* thread_tid_table_; michael@0: int* thread_pid_table_; michael@0: char* counter_names_table_; michael@0: int* data_table_; michael@0: }; michael@0: michael@0: // static michael@0: StatsTablePrivate* StatsTablePrivate::New(const std::string& name, michael@0: int size, michael@0: int max_threads, michael@0: int max_counters) { michael@0: scoped_ptr priv(new StatsTablePrivate()); michael@0: if (!priv->shared_memory_.Create(name, false, true, size)) michael@0: return NULL; michael@0: if (!priv->shared_memory_.Map(size)) michael@0: return NULL; michael@0: void* memory = priv->shared_memory_.memory(); michael@0: michael@0: TableHeader* header = static_cast(memory); michael@0: michael@0: // If the version does not match, then assume the table needs michael@0: // to be initialized. michael@0: if (header->version != kTableVersion) michael@0: priv->InitializeTable(memory, size, max_counters, max_threads); michael@0: michael@0: // We have a valid table, so compute our pointers. michael@0: priv->ComputeMappedPointers(memory); michael@0: michael@0: return priv.release(); michael@0: } michael@0: michael@0: void StatsTablePrivate::InitializeTable(void* memory, int size, michael@0: int max_counters, michael@0: int max_threads) { michael@0: // Zero everything. michael@0: memset(memory, 0, size); michael@0: michael@0: // Initialize the header. michael@0: TableHeader* header = static_cast(memory); michael@0: header->version = kTableVersion; michael@0: header->size = size; michael@0: header->max_counters = max_counters; michael@0: header->max_threads = max_threads; michael@0: } michael@0: michael@0: void StatsTablePrivate::ComputeMappedPointers(void* memory) { michael@0: char* data = static_cast(memory); michael@0: int offset = 0; michael@0: michael@0: table_header_ = reinterpret_cast(data); michael@0: offset += sizeof(*table_header_); michael@0: offset += AlignOffset(offset); michael@0: michael@0: // Verify we're looking at a valid StatsTable. michael@0: DCHECK_EQ(table_header_->version, kTableVersion); michael@0: michael@0: thread_names_table_ = reinterpret_cast(data + offset); michael@0: offset += sizeof(char) * michael@0: max_threads() * StatsTable::kMaxThreadNameLength; michael@0: offset += AlignOffset(offset); michael@0: michael@0: thread_tid_table_ = reinterpret_cast(data + offset); michael@0: offset += sizeof(int) * max_threads(); michael@0: offset += AlignOffset(offset); michael@0: michael@0: thread_pid_table_ = reinterpret_cast(data + offset); michael@0: offset += sizeof(int) * max_threads(); michael@0: offset += AlignOffset(offset); michael@0: michael@0: counter_names_table_ = reinterpret_cast(data + offset); michael@0: offset += sizeof(char) * michael@0: max_counters() * StatsTable::kMaxCounterNameLength; michael@0: offset += AlignOffset(offset); michael@0: michael@0: data_table_ = reinterpret_cast(data + offset); michael@0: offset += sizeof(int) * max_threads() * max_counters(); michael@0: michael@0: DCHECK_EQ(offset, size()); michael@0: } michael@0: michael@0: michael@0: michael@0: // We keep a singleton table which can be easily accessed. michael@0: StatsTable* StatsTable::global_table_ = NULL; michael@0: michael@0: StatsTable::StatsTable(const std::string& name, int max_threads, michael@0: int max_counters) michael@0: : impl_(NULL), michael@0: tls_index_(SlotReturnFunction) { michael@0: int table_size = michael@0: AlignedSize(sizeof(StatsTablePrivate::TableHeader)) + michael@0: AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) + michael@0: AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) + michael@0: AlignedSize(max_threads * sizeof(int)) + michael@0: AlignedSize(max_threads * sizeof(int)) + michael@0: AlignedSize((sizeof(int) * (max_counters * max_threads))); michael@0: michael@0: impl_ = StatsTablePrivate::New(name, table_size, max_threads, max_counters); michael@0: michael@0: // TODO(port): clean up this error reporting. michael@0: #if defined(OS_WIN) michael@0: if (!impl_) michael@0: CHROMIUM_LOG(ERROR) << "StatsTable did not initialize:" << GetLastError(); michael@0: #elif defined(OS_POSIX) michael@0: if (!impl_) michael@0: CHROMIUM_LOG(ERROR) << "StatsTable did not initialize:" << strerror(errno); michael@0: #endif michael@0: } michael@0: michael@0: StatsTable::~StatsTable() { michael@0: // Before we tear down our copy of the table, be sure to michael@0: // unregister our thread. michael@0: UnregisterThread(); michael@0: michael@0: // Return ThreadLocalStorage. At this point, if any registered threads michael@0: // still exist, they cannot Unregister. michael@0: tls_index_.Free(); michael@0: michael@0: // Cleanup our shared memory. michael@0: delete impl_; michael@0: michael@0: // If we are the global table, unregister ourselves. michael@0: if (global_table_ == this) michael@0: global_table_ = NULL; michael@0: } michael@0: michael@0: int StatsTable::RegisterThread(const std::string& name) { michael@0: int slot = 0; michael@0: michael@0: // Registering a thread requires that we lock the shared memory michael@0: // so that two threads don't grab the same slot. Fortunately, michael@0: // thread creation shouldn't happen in inner loops. michael@0: { michael@0: base::SharedMemoryAutoLock lock(impl_->shared_memory()); michael@0: slot = FindEmptyThread(); michael@0: if (!slot) { michael@0: return 0; michael@0: } michael@0: michael@0: DCHECK(impl_); michael@0: michael@0: // We have space, so consume a column in the table. michael@0: std::string thread_name = name; michael@0: if (name.empty()) michael@0: thread_name = kUnknownName; michael@0: base::strlcpy(impl_->thread_name(slot), thread_name.c_str(), michael@0: kMaxThreadNameLength); michael@0: *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); michael@0: *(impl_->thread_pid(slot)) = base::GetCurrentProcId(); michael@0: } michael@0: michael@0: // Set our thread local storage. michael@0: StatsTableTLSData* data = new StatsTableTLSData; michael@0: data->table = this; michael@0: data->slot = slot; michael@0: tls_index_.Set(data); michael@0: return slot; michael@0: } michael@0: michael@0: StatsTableTLSData* StatsTable::GetTLSData() const { michael@0: StatsTableTLSData* data = michael@0: static_cast(tls_index_.Get()); michael@0: if (!data) michael@0: return NULL; michael@0: michael@0: DCHECK(data->slot); michael@0: DCHECK_EQ(data->table, this); michael@0: return data; michael@0: } michael@0: michael@0: void StatsTable::UnregisterThread() { michael@0: UnregisterThread(GetTLSData()); michael@0: } michael@0: michael@0: void StatsTable::UnregisterThread(StatsTableTLSData* data) { michael@0: if (!data) michael@0: return; michael@0: DCHECK(impl_); michael@0: michael@0: // Mark the slot free by zeroing out the thread name. michael@0: char* name = impl_->thread_name(data->slot); michael@0: *name = '\0'; michael@0: michael@0: // Remove the calling thread's TLS so that it cannot use the slot. michael@0: tls_index_.Set(NULL); michael@0: delete data; michael@0: } michael@0: michael@0: void StatsTable::SlotReturnFunction(void* data) { michael@0: // This is called by the TLS destructor, which on some platforms has michael@0: // already cleared the TLS info, so use the tls_data argument michael@0: // rather than trying to fetch it ourselves. michael@0: StatsTableTLSData* tls_data = static_cast(data); michael@0: if (tls_data) { michael@0: DCHECK(tls_data->table); michael@0: tls_data->table->UnregisterThread(tls_data); michael@0: } michael@0: } michael@0: michael@0: int StatsTable::CountThreadsRegistered() const { michael@0: if (!impl_) michael@0: return 0; michael@0: michael@0: // Loop through the shared memory and count the threads that are active. michael@0: // We intentionally do not lock the table during the operation. michael@0: int count = 0; michael@0: for (int index = 1; index <= impl_->max_threads(); index++) { michael@0: char* name = impl_->thread_name(index); michael@0: if (*name != '\0') michael@0: count++; michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: int StatsTable::GetSlot() const { michael@0: StatsTableTLSData* data = GetTLSData(); michael@0: if (!data) michael@0: return 0; michael@0: return data->slot; michael@0: } michael@0: michael@0: int StatsTable::FindEmptyThread() const { michael@0: // Note: the API returns slots numbered from 1..N, although michael@0: // internally, the array is 0..N-1. This is so that we can return michael@0: // zero as "not found". michael@0: // michael@0: // The reason for doing this is because the thread 'slot' is stored michael@0: // in TLS, which is always initialized to zero, not -1. If 0 were michael@0: // returned as a valid slot number, it would be confused with the michael@0: // uninitialized state. michael@0: if (!impl_) michael@0: return 0; michael@0: michael@0: int index = 1; michael@0: for (; index <= impl_->max_threads(); index++) { michael@0: char* name = impl_->thread_name(index); michael@0: if (!*name) michael@0: break; michael@0: } michael@0: if (index > impl_->max_threads()) michael@0: return 0; // The table is full. michael@0: return index; michael@0: } michael@0: michael@0: int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { michael@0: // Note: the API returns slots numbered from 1..N, although michael@0: // internally, the array is 0..N-1. This is so that we can return michael@0: // zero as "not found". michael@0: // michael@0: // There isn't much reason for this other than to be consistent michael@0: // with the way we track columns for thread slots. (See comments michael@0: // in FindEmptyThread for why it is done this way). michael@0: if (!impl_) michael@0: return 0; michael@0: michael@0: int free_slot = 0; michael@0: for (int index = 1; index <= impl_->max_counters(); index++) { michael@0: char* row_name = impl_->counter_name(index); michael@0: if (!*row_name && !free_slot) michael@0: free_slot = index; // save that we found a free slot michael@0: else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength)) michael@0: return index; michael@0: } michael@0: return free_slot; michael@0: } michael@0: michael@0: int StatsTable::FindCounter(const std::string& name) { michael@0: // Note: the API returns counters numbered from 1..N, although michael@0: // internally, the array is 0..N-1. This is so that we can return michael@0: // zero as "not found". michael@0: if (!impl_) michael@0: return 0; michael@0: michael@0: // Create a scope for our auto-lock. michael@0: { michael@0: AutoLock scoped_lock(counters_lock_); michael@0: michael@0: // Attempt to find the counter. michael@0: CountersMap::const_iterator iter; michael@0: iter = counters_.find(name); michael@0: if (iter != counters_.end()) michael@0: return iter->second; michael@0: } michael@0: michael@0: // Counter does not exist, so add it. michael@0: return AddCounter(name); michael@0: } michael@0: michael@0: int StatsTable::AddCounter(const std::string& name) { michael@0: DCHECK(impl_); michael@0: michael@0: if (!impl_) michael@0: return 0; michael@0: michael@0: int counter_id = 0; michael@0: { michael@0: // To add a counter to the shared memory, we need the michael@0: // shared memory lock. michael@0: base::SharedMemoryAutoLock lock(impl_->shared_memory()); michael@0: michael@0: // We have space, so create a new counter. michael@0: counter_id = FindCounterOrEmptyRow(name); michael@0: if (!counter_id) michael@0: return 0; michael@0: michael@0: std::string counter_name = name; michael@0: if (name.empty()) michael@0: counter_name = kUnknownName; michael@0: base::strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), michael@0: kMaxCounterNameLength); michael@0: } michael@0: michael@0: // now add to our in-memory cache michael@0: { michael@0: AutoLock lock(counters_lock_); michael@0: counters_[name] = counter_id; michael@0: } michael@0: return counter_id; michael@0: } michael@0: michael@0: int* StatsTable::GetLocation(int counter_id, int slot_id) const { michael@0: if (!impl_) michael@0: return NULL; michael@0: if (slot_id > impl_->max_threads()) michael@0: return NULL; michael@0: michael@0: int* row = impl_->row(counter_id); michael@0: return &(row[slot_id-1]); michael@0: } michael@0: michael@0: const char* StatsTable::GetRowName(int index) const { michael@0: if (!impl_) michael@0: return NULL; michael@0: michael@0: return impl_->counter_name(index); michael@0: } michael@0: michael@0: int StatsTable::GetRowValue(int index, int pid) const { michael@0: if (!impl_) michael@0: return 0; michael@0: michael@0: int rv = 0; michael@0: int* row = impl_->row(index); michael@0: for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) { michael@0: if (pid == 0 || *impl_->thread_pid(slot_id) == pid) michael@0: rv += row[slot_id]; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: int StatsTable::GetRowValue(int index) const { michael@0: return GetRowValue(index, 0); michael@0: } michael@0: michael@0: int StatsTable::GetCounterValue(const std::string& name, int pid) { michael@0: if (!impl_) michael@0: return 0; michael@0: michael@0: int row = FindCounter(name); michael@0: if (!row) michael@0: return 0; michael@0: return GetRowValue(row, pid); michael@0: } michael@0: michael@0: int StatsTable::GetCounterValue(const std::string& name) { michael@0: return GetCounterValue(name, 0); michael@0: } michael@0: michael@0: int StatsTable::GetMaxCounters() const { michael@0: if (!impl_) michael@0: return 0; michael@0: return impl_->max_counters(); michael@0: } michael@0: michael@0: int StatsTable::GetMaxThreads() const { michael@0: if (!impl_) michael@0: return 0; michael@0: return impl_->max_threads(); michael@0: } michael@0: michael@0: int* StatsTable::FindLocation(const char* name) { michael@0: // Get the static StatsTable michael@0: StatsTable *table = StatsTable::current(); michael@0: if (!table) michael@0: return NULL; michael@0: michael@0: // Get the slot for this thread. Try to register michael@0: // it if none exists. michael@0: int slot = table->GetSlot(); michael@0: if (!slot && !(slot = table->RegisterThread(""))) michael@0: return NULL; michael@0: michael@0: // Find the counter id for the counter. michael@0: std::string str_name(name); michael@0: int counter = table->FindCounter(str_name); michael@0: michael@0: // Now we can find the location in the table. michael@0: return table->GetLocation(counter, slot); michael@0: }