1 // Copyright (c) 2012 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 using System; 6 using System.Collections; 7 using System.Collections.Generic; 8 using System.Diagnostics; 9 using System.Runtime.InteropServices; 10 using System.Text; 11 12 namespace StatsViewer 13 { 14 /// <summary> 15 /// The stats table shared memory segment contains this 16 /// header structure. 17 /// </summary> 18 [StructLayout(LayoutKind.Sequential)] 19 internal struct StatsFileHeader { 20 public int version; 21 public int size; 22 public int max_counters; 23 public int max_threads; 24 }; 25 26 /// <summary> 27 /// An entry in the StatsTable. 28 /// </summary> 29 class StatsTableEntry { 30 public StatsTableEntry(int id, string name, StatsTable table) { 31 id_ = id; 32 name_ = name; 33 table_ = table; 34 } 35 36 /// <summary> 37 /// The unique id for this entry 38 /// </summary> 39 public int id { get { return id_; } } 40 41 /// <summary> 42 /// The name for this entry. 43 /// </summary> 44 public string name { get { return name_; } } 45 46 /// <summary> 47 /// The value of this entry now. 48 /// </summary> 49 public int GetValue(int filter_pid) { 50 return table_.GetValue(id_, filter_pid); 51 } 52 53 private int id_; 54 private string name_; 55 private StatsTable table_; 56 } 57 58 // An interface for StatsCounters 59 interface IStatsCounter { 60 // The name of the counter 61 string name { get; } 62 } 63 64 // A counter. 65 class StatsCounter : IStatsCounter { 66 public StatsCounter(StatsTableEntry entry) { 67 entry_ = entry; 68 } 69 70 public string name { 71 get { 72 return entry_.name; 73 } 74 } 75 76 public int GetValue(int filter_pid) { 77 return entry_.GetValue(filter_pid); 78 } 79 80 private StatsTableEntry entry_; 81 } 82 83 // A timer. 84 class StatsTimer : IStatsCounter { 85 public StatsTimer(StatsTableEntry entry) 86 { 87 entry_ = entry; 88 } 89 90 public string name { 91 get { 92 return entry_.name; 93 } 94 } 95 96 public int GetValue(int filter_pid) { 97 return entry_.GetValue(filter_pid); 98 } 99 100 private StatsTableEntry entry_; 101 } 102 103 // A rate. 104 class StatsCounterRate : IStatsCounter 105 { 106 public StatsCounterRate(StatsCounter counter, StatsTimer timer) { 107 counter_ = counter; 108 timer_ = timer; 109 } 110 111 public string name { get { return counter_.name; } } 112 113 public int GetCount(int filter_pid) { 114 return counter_.GetValue(filter_pid); 115 } 116 117 public int GetTime(int filter_pid) { 118 return timer_.GetValue(filter_pid); 119 } 120 121 private StatsCounter counter_; 122 private StatsTimer timer_; 123 } 124 125 /// <summary> 126 /// This is a C# reader for the chrome stats_table. 127 /// </summary> 128 class StatsTable { 129 internal const int kMaxThreadNameLength = 32; 130 internal const int kMaxCounterNameLength = 32; 131 132 /// <summary> 133 /// Open a StatsTable 134 /// </summary> 135 public StatsTable() { 136 } 137 138 #region Public Properties 139 /// <summary> 140 /// Get access to the counters in the table. 141 /// </summary> 142 public StatsTableCounters Counters() { 143 return new StatsTableCounters(this); 144 } 145 146 /// <summary> 147 /// Get access to the processes in the table 148 /// </summary> 149 public ICollection Processes { 150 get { 151 return new StatsTableProcesses(this); 152 } 153 } 154 #endregion 155 156 #region Internal Properties 157 // 158 // The internal methods are accessible to the enumerators 159 // and helper classes below. 160 // 161 162 /// <summary> 163 /// Access to the table header 164 /// </summary> 165 internal StatsFileHeader Header { 166 get { return header_; } 167 } 168 169 /// <summary> 170 /// Get the offset of the ThreadName table 171 /// </summary> 172 internal long ThreadNamesOffset { 173 get { 174 return memory_.ToInt64() + Marshal.SizeOf(typeof(StatsFileHeader)); 175 } 176 } 177 178 /// <summary> 179 /// Get the offset of the PIDs table 180 /// </summary> 181 internal long PidsOffset { 182 get { 183 long offset = ThreadNamesOffset; 184 // Thread names table 185 offset += AlignedSize(header_.max_threads * kMaxThreadNameLength * 2); 186 // Thread TID table 187 offset += AlignedSize(header_.max_threads * 188 Marshal.SizeOf(typeof(int))); 189 return offset; 190 } 191 } 192 193 /// <summary> 194 /// Get the offset of the CounterName table 195 /// </summary> 196 internal long CounterNamesOffset { 197 get { 198 long offset = PidsOffset; 199 // Thread PID table 200 offset += AlignedSize(header_.max_threads * 201 Marshal.SizeOf(typeof(int))); 202 return offset; 203 } 204 } 205 206 /// <summary> 207 /// Get the offset of the Data table 208 /// </summary> 209 internal long DataOffset { 210 get { 211 long offset = CounterNamesOffset; 212 // Counter names table 213 offset += AlignedSize(header_.max_counters * 214 kMaxCounterNameLength * 2); 215 return offset; 216 } 217 } 218 #endregion 219 220 #region Public Methods 221 /// <summary> 222 /// Opens the memory map 223 /// </summary> 224 /// <returns></returns> 225 /// <param name="name">The name of the file to open</param> 226 public bool Open(string name) { 227 map_handle_ = 228 Win32.OpenFileMapping((int)Win32.MapAccess.FILE_MAP_WRITE, false, 229 name); 230 if (map_handle_ == IntPtr.Zero) 231 return false; 232 233 memory_ = 234 Win32.MapViewOfFile(map_handle_, (int)Win32.MapAccess.FILE_MAP_WRITE, 235 0,0, 0); 236 if (memory_ == IntPtr.Zero) { 237 Win32.CloseHandle(map_handle_); 238 return false; 239 } 240 241 header_ = (StatsFileHeader)Marshal.PtrToStructure(memory_, header_.GetType()); 242 return true; 243 } 244 245 /// <summary> 246 /// Close the mapped file. 247 /// </summary> 248 public void Close() { 249 Win32.UnmapViewOfFile(memory_); 250 Win32.CloseHandle(map_handle_); 251 } 252 253 /// <summary> 254 /// Zero out the stats file. 255 /// </summary> 256 public void Zero() { 257 long offset = DataOffset; 258 for (int threads = 0; threads < header_.max_threads; threads++) { 259 for (int counters = 0; counters < header_.max_counters; counters++) { 260 Marshal.WriteInt32((IntPtr) offset, 0); 261 offset += Marshal.SizeOf(typeof(int)); 262 } 263 } 264 } 265 266 /// <summary> 267 /// Get the value for a StatsCounterEntry now. 268 /// </summary> 269 /// <returns></returns> 270 /// <param name="filter_pid">If a specific PID is being queried, filter to this PID. 0 means use all data.</param> 271 /// <param name="id">The id of the CounterEntry to get the value for.</param> 272 public int GetValue(int id, int filter_pid) { 273 long pid_offset = PidsOffset; 274 long data_offset = DataOffset; 275 data_offset += id * (Header.max_threads * 276 Marshal.SizeOf(typeof(int))); 277 int rv = 0; 278 for (int cols = 0; cols < Header.max_threads; cols++) 279 { 280 int pid = Marshal.ReadInt32((IntPtr)pid_offset); 281 if (filter_pid == 0 || filter_pid == pid) 282 { 283 rv += Marshal.ReadInt32((IntPtr)data_offset); 284 } 285 data_offset += Marshal.SizeOf(typeof(int)); 286 pid_offset += Marshal.SizeOf(typeof(int)); 287 } 288 return rv; 289 } 290 #endregion 291 292 #region Private Methods 293 /// <summary> 294 /// Align to 4-byte boundaries 295 /// </summary> 296 /// <param name="size"></param> 297 /// <returns></returns> 298 private long AlignedSize(long size) { 299 Debug.Assert(sizeof(int) == 4); 300 return size + (sizeof(int) - (size % sizeof(int))) % sizeof(int); 301 } 302 #endregion 303 304 #region Private Members 305 private IntPtr memory_; 306 private IntPtr map_handle_; 307 private StatsFileHeader header_; 308 #endregion 309 } 310 311 /// <summary> 312 /// Enumerable list of Counters in the StatsTable 313 /// </summary> 314 class StatsTableCounters : ICollection { 315 /// <summary> 316 /// Create the list of counters 317 /// </summary> 318 /// <param name="table"></param> 319 /// pid</param> 320 public StatsTableCounters(StatsTable table) { 321 table_ = table; 322 counter_hi_water_mark_ = -1; 323 counters_ = new List<IStatsCounter>(); 324 FindCounters(); 325 } 326 327 /// <summary> 328 /// Scans the table for new entries. 329 /// </summary> 330 public void Update() { 331 FindCounters(); 332 } 333 334 #region IEnumerable Members 335 public IEnumerator GetEnumerator() { 336 return counters_.GetEnumerator(); 337 } 338 #endregion 339 340 #region ICollection Members 341 public void CopyTo(Array array, int index) { 342 throw new Exception("The method or operation is not implemented."); 343 } 344 345 public int Count { 346 get { 347 return counters_.Count; 348 } 349 } 350 351 public bool IsSynchronized { 352 get { 353 throw new Exception("The method or operation is not implemented."); 354 } 355 } 356 357 public object SyncRoot { 358 get { 359 throw new Exception("The method or operation is not implemented."); 360 } 361 } 362 #endregion 363 364 #region Private Methods 365 /// <summary> 366 /// Create a counter based on an entry 367 /// </summary> 368 /// <param name="id"></param> 369 /// <param name="name"></param> 370 /// <returns></returns> 371 private IStatsCounter NameToCounter(int id, string name) 372 { 373 IStatsCounter rv = null; 374 375 // check if the name has a type encoded 376 if (name.Length > 2 && name[1] == ':') 377 { 378 StatsTableEntry entry = new StatsTableEntry(id, name.Substring(2), table_); 379 switch (name[0]) 380 { 381 case 't': 382 rv = new StatsTimer(entry); 383 break; 384 case 'c': 385 rv = new StatsCounter(entry); 386 break; 387 } 388 } 389 else 390 { 391 StatsTableEntry entry = new StatsTableEntry(id, name, table_); 392 rv = new StatsCounter(entry); 393 } 394 395 return rv; 396 } 397 398 // If we have two StatsTableEntries with the same name, 399 // attempt to upgrade them to a higher level type. 400 // Example: A counter + a timer == a rate! 401 private void UpgradeCounter(IStatsCounter old_counter, IStatsCounter counter) 402 { 403 if (old_counter is StatsCounter && counter is StatsTimer) 404 { 405 StatsCounterRate rate = new StatsCounterRate(old_counter as StatsCounter, 406 counter as StatsTimer); 407 counters_.Remove(old_counter); 408 counters_.Add(rate); 409 } 410 else if (old_counter is StatsTimer && counter is StatsCounter) 411 { 412 StatsCounterRate rate = new StatsCounterRate(counter as StatsCounter, 413 old_counter as StatsTimer); 414 counters_.Remove(old_counter); 415 counters_.Add(rate); 416 } 417 } 418 419 /// <summary> 420 /// Find the counters in the table and insert into the counters_ 421 /// hash table. 422 /// </summary> 423 private void FindCounters() 424 { 425 Debug.Assert(table_.Header.max_counters > 0); 426 427 int index = counter_hi_water_mark_; 428 429 do 430 { 431 // Find an entry in the table. 432 index++; 433 long offset = table_.CounterNamesOffset + 434 (index * StatsTable.kMaxCounterNameLength * 2); 435 string name = Marshal.PtrToStringUni((IntPtr)offset); 436 if (name.Length == 0) 437 continue; 438 439 // Record that we've already looked at this StatsTableEntry. 440 counter_hi_water_mark_ = index; 441 442 IStatsCounter counter = NameToCounter(index, name); 443 444 if (counter != null) 445 { 446 IStatsCounter old_counter = FindExistingCounter(counter.name); 447 if (old_counter != null) 448 UpgradeCounter(old_counter, counter); 449 else 450 counters_.Add(counter); 451 } 452 } while (index < table_.Header.max_counters - 1); 453 } 454 455 /// <summary> 456 /// Find an existing counter in our table 457 /// </summary> 458 /// <param name="name"></param> 459 private IStatsCounter FindExistingCounter(string name) { 460 foreach (IStatsCounter ctr in counters_) 461 { 462 if (ctr.name == name) 463 return ctr; 464 } 465 return null; 466 } 467 #endregion 468 469 #region Private Members 470 private StatsTable table_; 471 private List<IStatsCounter> counters_; 472 // Highest index of counters processed. 473 private int counter_hi_water_mark_; 474 #endregion 475 } 476 477 /// <summary> 478 /// A collection of processes 479 /// </summary> 480 class StatsTableProcesses : ICollection 481 { 482 /// <summary> 483 /// Constructor 484 /// </summary> 485 /// <param name="table"></param> 486 public StatsTableProcesses(StatsTable table) { 487 table_ = table; 488 pids_ = new List<int>(); 489 Initialize(); 490 } 491 492 #region ICollection Members 493 public void CopyTo(Array array, int index) { 494 throw new Exception("The method or operation is not implemented."); 495 } 496 497 public int Count { 498 get { 499 return pids_.Count; 500 } 501 } 502 503 public bool IsSynchronized { 504 get { 505 throw new Exception("The method or operation is not implemented."); 506 } 507 } 508 509 public object SyncRoot { 510 get { 511 throw new Exception("The method or operation is not implemented."); 512 } 513 } 514 #endregion 515 516 #region IEnumerable Members 517 public IEnumerator GetEnumerator() { 518 return pids_.GetEnumerator(); 519 } 520 #endregion 521 522 /// <summary> 523 /// Initialize the pid list. 524 /// </summary> 525 private void Initialize() { 526 long offset = table_.ThreadNamesOffset; 527 528 for (int index = 0; index < table_.Header.max_threads; index++) { 529 string thread_name = Marshal.PtrToStringUni((IntPtr)offset); 530 if (thread_name.Length > 0) { 531 long pidOffset = table_.PidsOffset + index * 532 Marshal.SizeOf(typeof(int)); 533 int pid = Marshal.ReadInt32((IntPtr)pidOffset); 534 if (!pids_.Contains(pid)) 535 pids_.Add(pid); 536 } 537 offset += StatsTable.kMaxThreadNameLength * 2; 538 } 539 } 540 541 #region Private Members 542 private StatsTable table_; 543 private List<int> pids_; 544 #endregion 545 } 546 } 547