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/thread_local_storage.h" 15 #include "base/utf_string_conversions.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(UTF8ToWide(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 if (!impl_) 261 PLOG(ERROR) << "StatsTable did not initialize"; 262 } 263 264 StatsTable::~StatsTable() { 265 // Before we tear down our copy of the table, be sure to 266 // unregister our thread. 267 UnregisterThread(); 268 269 // Return ThreadLocalStorage. At this point, if any registered threads 270 // still exist, they cannot Unregister. 271 tls_index_.Free(); 272 273 // Cleanup our shared memory. 274 delete impl_; 275 276 // If we are the global table, unregister ourselves. 277 if (global_table_ == this) 278 global_table_ = NULL; 279 } 280 281 int StatsTable::RegisterThread(const std::string& name) { 282 int slot = 0; 283 if (!impl_) 284 return 0; 285 286 // Registering a thread requires that we lock the shared memory 287 // so that two threads don't grab the same slot. Fortunately, 288 // thread creation shouldn't happen in inner loops. 289 { 290 base::SharedMemoryAutoLock lock(impl_->shared_memory()); 291 slot = FindEmptyThread(); 292 if (!slot) { 293 return 0; 294 } 295 296 // We have space, so consume a column in the table. 297 std::string thread_name = name; 298 if (name.empty()) 299 thread_name = kUnknownName; 300 base::strlcpy(impl_->thread_name(slot), thread_name.c_str(), 301 kMaxThreadNameLength); 302 *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); 303 *(impl_->thread_pid(slot)) = base::GetCurrentProcId(); 304 } 305 306 // Set our thread local storage. 307 StatsTableTLSData* data = new StatsTableTLSData; 308 data->table = this; 309 data->slot = slot; 310 tls_index_.Set(data); 311 return slot; 312 } 313 314 StatsTableTLSData* StatsTable::GetTLSData() const { 315 StatsTableTLSData* data = 316 static_cast<StatsTableTLSData*>(tls_index_.Get()); 317 if (!data) 318 return NULL; 319 320 DCHECK(data->slot); 321 DCHECK_EQ(data->table, this); 322 return data; 323 } 324 325 void StatsTable::UnregisterThread() { 326 UnregisterThread(GetTLSData()); 327 } 328 329 void StatsTable::UnregisterThread(StatsTableTLSData* data) { 330 if (!data) 331 return; 332 DCHECK(impl_); 333 334 // Mark the slot free by zeroing out the thread name. 335 char* name = impl_->thread_name(data->slot); 336 *name = '\0'; 337 338 // Remove the calling thread's TLS so that it cannot use the slot. 339 tls_index_.Set(NULL); 340 delete data; 341 } 342 343 void StatsTable::SlotReturnFunction(void* data) { 344 // This is called by the TLS destructor, which on some platforms has 345 // already cleared the TLS info, so use the tls_data argument 346 // rather than trying to fetch it ourselves. 347 StatsTableTLSData* tls_data = static_cast<StatsTableTLSData*>(data); 348 if (tls_data) { 349 DCHECK(tls_data->table); 350 tls_data->table->UnregisterThread(tls_data); 351 } 352 } 353 354 int StatsTable::CountThreadsRegistered() const { 355 if (!impl_) 356 return 0; 357 358 // Loop through the shared memory and count the threads that are active. 359 // We intentionally do not lock the table during the operation. 360 int count = 0; 361 for (int index = 1; index <= impl_->max_threads(); index++) { 362 char* name = impl_->thread_name(index); 363 if (*name != '\0') 364 count++; 365 } 366 return count; 367 } 368 369 int StatsTable::GetSlot() const { 370 StatsTableTLSData* data = GetTLSData(); 371 if (!data) 372 return 0; 373 return data->slot; 374 } 375 376 int StatsTable::FindEmptyThread() const { 377 // Note: the API returns slots numbered from 1..N, although 378 // internally, the array is 0..N-1. This is so that we can return 379 // zero as "not found". 380 // 381 // The reason for doing this is because the thread 'slot' is stored 382 // in TLS, which is always initialized to zero, not -1. If 0 were 383 // returned as a valid slot number, it would be confused with the 384 // uninitialized state. 385 if (!impl_) 386 return 0; 387 388 int index = 1; 389 for (; index <= impl_->max_threads(); index++) { 390 char* name = impl_->thread_name(index); 391 if (!*name) 392 break; 393 } 394 if (index > impl_->max_threads()) 395 return 0; // The table is full. 396 return index; 397 } 398 399 int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { 400 // Note: the API returns slots numbered from 1..N, although 401 // internally, the array is 0..N-1. This is so that we can return 402 // zero as "not found". 403 // 404 // There isn't much reason for this other than to be consistent 405 // with the way we track columns for thread slots. (See comments 406 // in FindEmptyThread for why it is done this way). 407 if (!impl_) 408 return 0; 409 410 int free_slot = 0; 411 for (int index = 1; index <= impl_->max_counters(); index++) { 412 char* row_name = impl_->counter_name(index); 413 if (!*row_name && !free_slot) 414 free_slot = index; // save that we found a free slot 415 else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength)) 416 return index; 417 } 418 return free_slot; 419 } 420 421 int StatsTable::FindCounter(const std::string& name) { 422 // Note: the API returns counters numbered from 1..N, although 423 // internally, the array is 0..N-1. This is so that we can return 424 // zero as "not found". 425 if (!impl_) 426 return 0; 427 428 // Create a scope for our auto-lock. 429 { 430 AutoLock scoped_lock(counters_lock_); 431 432 // Attempt to find the counter. 433 CountersMap::const_iterator iter; 434 iter = counters_.find(name); 435 if (iter != counters_.end()) 436 return iter->second; 437 } 438 439 // Counter does not exist, so add it. 440 return AddCounter(name); 441 } 442 443 int StatsTable::AddCounter(const std::string& name) { 444 if (!impl_) 445 return 0; 446 447 int counter_id = 0; 448 { 449 // To add a counter to the shared memory, we need the 450 // shared memory lock. 451 base::SharedMemoryAutoLock lock(impl_->shared_memory()); 452 453 // We have space, so create a new counter. 454 counter_id = FindCounterOrEmptyRow(name); 455 if (!counter_id) 456 return 0; 457 458 std::string counter_name = name; 459 if (name.empty()) 460 counter_name = kUnknownName; 461 base::strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), 462 kMaxCounterNameLength); 463 } 464 465 // now add to our in-memory cache 466 { 467 AutoLock lock(counters_lock_); 468 counters_[name] = counter_id; 469 } 470 return counter_id; 471 } 472 473 int* StatsTable::GetLocation(int counter_id, int slot_id) const { 474 if (!impl_) 475 return NULL; 476 if (slot_id > impl_->max_threads()) 477 return NULL; 478 479 int* row = impl_->row(counter_id); 480 return &(row[slot_id-1]); 481 } 482 483 const char* StatsTable::GetRowName(int index) const { 484 if (!impl_) 485 return NULL; 486 487 return impl_->counter_name(index); 488 } 489 490 int StatsTable::GetRowValue(int index, int pid) const { 491 if (!impl_) 492 return 0; 493 494 int rv = 0; 495 int* row = impl_->row(index); 496 for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) { 497 if (pid == 0 || *impl_->thread_pid(slot_id) == pid) 498 rv += row[slot_id]; 499 } 500 return rv; 501 } 502 503 int StatsTable::GetRowValue(int index) const { 504 return GetRowValue(index, 0); 505 } 506 507 int StatsTable::GetCounterValue(const std::string& name, int pid) { 508 if (!impl_) 509 return 0; 510 511 int row = FindCounter(name); 512 if (!row) 513 return 0; 514 return GetRowValue(row, pid); 515 } 516 517 int StatsTable::GetCounterValue(const std::string& name) { 518 return GetCounterValue(name, 0); 519 } 520 521 int StatsTable::GetMaxCounters() const { 522 if (!impl_) 523 return 0; 524 return impl_->max_counters(); 525 } 526 527 int StatsTable::GetMaxThreads() const { 528 if (!impl_) 529 return 0; 530 return impl_->max_threads(); 531 } 532 533 int* StatsTable::FindLocation(const char* name) { 534 // Get the static StatsTable 535 StatsTable *table = StatsTable::current(); 536 if (!table) 537 return NULL; 538 539 // Get the slot for this thread. Try to register 540 // it if none exists. 541 int slot = table->GetSlot(); 542 if (!slot && !(slot = table->RegisterThread(""))) 543 return NULL; 544 545 // Find the counter id for the counter. 546 std::string str_name(name); 547 int counter = table->FindCounter(str_name); 548 549 // Now we can find the location in the table. 550 return table->GetLocation(counter, slot); 551 } 552