| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| using System; |
| using System.Collections; |
| using System.Collections.Generic; |
| using System.Diagnostics; |
| using System.Runtime.InteropServices; |
| using System.Text; |
| |
| namespace StatsViewer |
| { |
| /// <summary> |
| /// The stats table shared memory segment contains this |
| /// header structure. |
| /// </summary> |
| [StructLayout(LayoutKind.Sequential)] |
| internal struct StatsFileHeader { |
| public int version; |
| public int size; |
| public int max_counters; |
| public int max_threads; |
| }; |
| |
| /// <summary> |
| /// An entry in the StatsTable. |
| /// </summary> |
| class StatsTableEntry { |
| public StatsTableEntry(int id, string name, StatsTable table) { |
| id_ = id; |
| name_ = name; |
| table_ = table; |
| } |
| |
| /// <summary> |
| /// The unique id for this entry |
| /// </summary> |
| public int id { get { return id_; } } |
| |
| /// <summary> |
| /// The name for this entry. |
| /// </summary> |
| public string name { get { return name_; } } |
| |
| /// <summary> |
| /// The value of this entry now. |
| /// </summary> |
| public int GetValue(int filter_pid) { |
| return table_.GetValue(id_, filter_pid); |
| } |
| |
| private int id_; |
| private string name_; |
| private StatsTable table_; |
| } |
| |
| // An interface for StatsCounters |
| interface IStatsCounter { |
| // The name of the counter |
| string name { get; } |
| } |
| |
| // A counter. |
| class StatsCounter : IStatsCounter { |
| public StatsCounter(StatsTableEntry entry) { |
| entry_ = entry; |
| } |
| |
| public string name { |
| get { |
| return entry_.name; |
| } |
| } |
| |
| public int GetValue(int filter_pid) { |
| return entry_.GetValue(filter_pid); |
| } |
| |
| private StatsTableEntry entry_; |
| } |
| |
| // A timer. |
| class StatsTimer : IStatsCounter { |
| public StatsTimer(StatsTableEntry entry) |
| { |
| entry_ = entry; |
| } |
| |
| public string name { |
| get { |
| return entry_.name; |
| } |
| } |
| |
| public int GetValue(int filter_pid) { |
| return entry_.GetValue(filter_pid); |
| } |
| |
| private StatsTableEntry entry_; |
| } |
| |
| // A rate. |
| class StatsCounterRate : IStatsCounter |
| { |
| public StatsCounterRate(StatsCounter counter, StatsTimer timer) { |
| counter_ = counter; |
| timer_ = timer; |
| } |
| |
| public string name { get { return counter_.name; } } |
| |
| public int GetCount(int filter_pid) { |
| return counter_.GetValue(filter_pid); |
| } |
| |
| public int GetTime(int filter_pid) { |
| return timer_.GetValue(filter_pid); |
| } |
| |
| private StatsCounter counter_; |
| private StatsTimer timer_; |
| } |
| |
| /// <summary> |
| /// This is a C# reader for the chrome stats_table. |
| /// </summary> |
| class StatsTable { |
| internal const int kMaxThreadNameLength = 32; |
| internal const int kMaxCounterNameLength = 32; |
| |
| /// <summary> |
| /// Open a StatsTable |
| /// </summary> |
| public StatsTable() { |
| } |
| |
| #region Public Properties |
| /// <summary> |
| /// Get access to the counters in the table. |
| /// </summary> |
| public StatsTableCounters Counters() { |
| return new StatsTableCounters(this); |
| } |
| |
| /// <summary> |
| /// Get access to the processes in the table |
| /// </summary> |
| public ICollection Processes { |
| get { |
| return new StatsTableProcesses(this); |
| } |
| } |
| #endregion |
| |
| #region Internal Properties |
| // |
| // The internal methods are accessible to the enumerators |
| // and helper classes below. |
| // |
| |
| /// <summary> |
| /// Access to the table header |
| /// </summary> |
| internal StatsFileHeader Header { |
| get { return header_; } |
| } |
| |
| /// <summary> |
| /// Get the offset of the ThreadName table |
| /// </summary> |
| internal long ThreadNamesOffset { |
| get { |
| return memory_.ToInt64() + Marshal.SizeOf(typeof(StatsFileHeader)); |
| } |
| } |
| |
| /// <summary> |
| /// Get the offset of the PIDs table |
| /// </summary> |
| internal long PidsOffset { |
| get { |
| long offset = ThreadNamesOffset; |
| // Thread names table |
| offset += AlignedSize(header_.max_threads * kMaxThreadNameLength * 2); |
| // Thread TID table |
| offset += AlignedSize(header_.max_threads * |
| Marshal.SizeOf(typeof(int))); |
| return offset; |
| } |
| } |
| |
| /// <summary> |
| /// Get the offset of the CounterName table |
| /// </summary> |
| internal long CounterNamesOffset { |
| get { |
| long offset = PidsOffset; |
| // Thread PID table |
| offset += AlignedSize(header_.max_threads * |
| Marshal.SizeOf(typeof(int))); |
| return offset; |
| } |
| } |
| |
| /// <summary> |
| /// Get the offset of the Data table |
| /// </summary> |
| internal long DataOffset { |
| get { |
| long offset = CounterNamesOffset; |
| // Counter names table |
| offset += AlignedSize(header_.max_counters * |
| kMaxCounterNameLength * 2); |
| return offset; |
| } |
| } |
| #endregion |
| |
| #region Public Methods |
| /// <summary> |
| /// Opens the memory map |
| /// </summary> |
| /// <returns></returns> |
| /// <param name="name">The name of the file to open</param> |
| public bool Open(string name) { |
| map_handle_ = |
| Win32.OpenFileMapping((int)Win32.MapAccess.FILE_MAP_WRITE, false, |
| name); |
| if (map_handle_ == IntPtr.Zero) |
| return false; |
| |
| memory_ = |
| Win32.MapViewOfFile(map_handle_, (int)Win32.MapAccess.FILE_MAP_WRITE, |
| 0,0, 0); |
| if (memory_ == IntPtr.Zero) { |
| Win32.CloseHandle(map_handle_); |
| return false; |
| } |
| |
| header_ = (StatsFileHeader)Marshal.PtrToStructure(memory_, header_.GetType()); |
| return true; |
| } |
| |
| /// <summary> |
| /// Close the mapped file. |
| /// </summary> |
| public void Close() { |
| Win32.UnmapViewOfFile(memory_); |
| Win32.CloseHandle(map_handle_); |
| } |
| |
| /// <summary> |
| /// Zero out the stats file. |
| /// </summary> |
| public void Zero() { |
| long offset = DataOffset; |
| for (int threads = 0; threads < header_.max_threads; threads++) { |
| for (int counters = 0; counters < header_.max_counters; counters++) { |
| Marshal.WriteInt32((IntPtr) offset, 0); |
| offset += Marshal.SizeOf(typeof(int)); |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Get the value for a StatsCounterEntry now. |
| /// </summary> |
| /// <returns></returns> |
| /// <param name="filter_pid">If a specific PID is being queried, filter to this PID. 0 means use all data.</param> |
| /// <param name="id">The id of the CounterEntry to get the value for.</param> |
| public int GetValue(int id, int filter_pid) { |
| long pid_offset = PidsOffset; |
| long data_offset = DataOffset; |
| data_offset += id * (Header.max_threads * |
| Marshal.SizeOf(typeof(int))); |
| int rv = 0; |
| for (int cols = 0; cols < Header.max_threads; cols++) |
| { |
| int pid = Marshal.ReadInt32((IntPtr)pid_offset); |
| if (filter_pid == 0 || filter_pid == pid) |
| { |
| rv += Marshal.ReadInt32((IntPtr)data_offset); |
| } |
| data_offset += Marshal.SizeOf(typeof(int)); |
| pid_offset += Marshal.SizeOf(typeof(int)); |
| } |
| return rv; |
| } |
| #endregion |
| |
| #region Private Methods |
| /// <summary> |
| /// Align to 4-byte boundaries |
| /// </summary> |
| /// <param name="size"></param> |
| /// <returns></returns> |
| private long AlignedSize(long size) { |
| Debug.Assert(sizeof(int) == 4); |
| return size + (sizeof(int) - (size % sizeof(int))) % sizeof(int); |
| } |
| #endregion |
| |
| #region Private Members |
| private IntPtr memory_; |
| private IntPtr map_handle_; |
| private StatsFileHeader header_; |
| #endregion |
| } |
| |
| /// <summary> |
| /// Enumerable list of Counters in the StatsTable |
| /// </summary> |
| class StatsTableCounters : ICollection { |
| /// <summary> |
| /// Create the list of counters |
| /// </summary> |
| /// <param name="table"></param> |
| /// pid</param> |
| public StatsTableCounters(StatsTable table) { |
| table_ = table; |
| counter_hi_water_mark_ = -1; |
| counters_ = new List<IStatsCounter>(); |
| FindCounters(); |
| } |
| |
| /// <summary> |
| /// Scans the table for new entries. |
| /// </summary> |
| public void Update() { |
| FindCounters(); |
| } |
| |
| #region IEnumerable Members |
| public IEnumerator GetEnumerator() { |
| return counters_.GetEnumerator(); |
| } |
| #endregion |
| |
| #region ICollection Members |
| public void CopyTo(Array array, int index) { |
| throw new Exception("The method or operation is not implemented."); |
| } |
| |
| public int Count { |
| get { |
| return counters_.Count; |
| } |
| } |
| |
| public bool IsSynchronized { |
| get { |
| throw new Exception("The method or operation is not implemented."); |
| } |
| } |
| |
| public object SyncRoot { |
| get { |
| throw new Exception("The method or operation is not implemented."); |
| } |
| } |
| #endregion |
| |
| #region Private Methods |
| /// <summary> |
| /// Create a counter based on an entry |
| /// </summary> |
| /// <param name="id"></param> |
| /// <param name="name"></param> |
| /// <returns></returns> |
| private IStatsCounter NameToCounter(int id, string name) |
| { |
| IStatsCounter rv = null; |
| |
| // check if the name has a type encoded |
| if (name.Length > 2 && name[1] == ':') |
| { |
| StatsTableEntry entry = new StatsTableEntry(id, name.Substring(2), table_); |
| switch (name[0]) |
| { |
| case 't': |
| rv = new StatsTimer(entry); |
| break; |
| case 'c': |
| rv = new StatsCounter(entry); |
| break; |
| } |
| } |
| else |
| { |
| StatsTableEntry entry = new StatsTableEntry(id, name, table_); |
| rv = new StatsCounter(entry); |
| } |
| |
| return rv; |
| } |
| |
| // If we have two StatsTableEntries with the same name, |
| // attempt to upgrade them to a higher level type. |
| // Example: A counter + a timer == a rate! |
| private void UpgradeCounter(IStatsCounter old_counter, IStatsCounter counter) |
| { |
| if (old_counter is StatsCounter && counter is StatsTimer) |
| { |
| StatsCounterRate rate = new StatsCounterRate(old_counter as StatsCounter, |
| counter as StatsTimer); |
| counters_.Remove(old_counter); |
| counters_.Add(rate); |
| } |
| else if (old_counter is StatsTimer && counter is StatsCounter) |
| { |
| StatsCounterRate rate = new StatsCounterRate(counter as StatsCounter, |
| old_counter as StatsTimer); |
| counters_.Remove(old_counter); |
| counters_.Add(rate); |
| } |
| } |
| |
| /// <summary> |
| /// Find the counters in the table and insert into the counters_ |
| /// hash table. |
| /// </summary> |
| private void FindCounters() |
| { |
| Debug.Assert(table_.Header.max_counters > 0); |
| |
| int index = counter_hi_water_mark_; |
| |
| do |
| { |
| // Find an entry in the table. |
| index++; |
| long offset = table_.CounterNamesOffset + |
| (index * StatsTable.kMaxCounterNameLength * 2); |
| string name = Marshal.PtrToStringUni((IntPtr)offset); |
| if (name.Length == 0) |
| continue; |
| |
| // Record that we've already looked at this StatsTableEntry. |
| counter_hi_water_mark_ = index; |
| |
| IStatsCounter counter = NameToCounter(index, name); |
| |
| if (counter != null) |
| { |
| IStatsCounter old_counter = FindExistingCounter(counter.name); |
| if (old_counter != null) |
| UpgradeCounter(old_counter, counter); |
| else |
| counters_.Add(counter); |
| } |
| } while (index < table_.Header.max_counters - 1); |
| } |
| |
| /// <summary> |
| /// Find an existing counter in our table |
| /// </summary> |
| /// <param name="name"></param> |
| private IStatsCounter FindExistingCounter(string name) { |
| foreach (IStatsCounter ctr in counters_) |
| { |
| if (ctr.name == name) |
| return ctr; |
| } |
| return null; |
| } |
| #endregion |
| |
| #region Private Members |
| private StatsTable table_; |
| private List<IStatsCounter> counters_; |
| // Highest index of counters processed. |
| private int counter_hi_water_mark_; |
| #endregion |
| } |
| |
| /// <summary> |
| /// A collection of processes |
| /// </summary> |
| class StatsTableProcesses : ICollection |
| { |
| /// <summary> |
| /// Constructor |
| /// </summary> |
| /// <param name="table"></param> |
| public StatsTableProcesses(StatsTable table) { |
| table_ = table; |
| pids_ = new List<int>(); |
| Initialize(); |
| } |
| |
| #region ICollection Members |
| public void CopyTo(Array array, int index) { |
| throw new Exception("The method or operation is not implemented."); |
| } |
| |
| public int Count { |
| get { |
| return pids_.Count; |
| } |
| } |
| |
| public bool IsSynchronized { |
| get { |
| throw new Exception("The method or operation is not implemented."); |
| } |
| } |
| |
| public object SyncRoot { |
| get { |
| throw new Exception("The method or operation is not implemented."); |
| } |
| } |
| #endregion |
| |
| #region IEnumerable Members |
| public IEnumerator GetEnumerator() { |
| return pids_.GetEnumerator(); |
| } |
| #endregion |
| |
| /// <summary> |
| /// Initialize the pid list. |
| /// </summary> |
| private void Initialize() { |
| long offset = table_.ThreadNamesOffset; |
| |
| for (int index = 0; index < table_.Header.max_threads; index++) { |
| string thread_name = Marshal.PtrToStringUni((IntPtr)offset); |
| if (thread_name.Length > 0) { |
| long pidOffset = table_.PidsOffset + index * |
| Marshal.SizeOf(typeof(int)); |
| int pid = Marshal.ReadInt32((IntPtr)pidOffset); |
| if (!pids_.Contains(pid)) |
| pids_.Add(pid); |
| } |
| offset += StatsTable.kMaxThreadNameLength * 2; |
| } |
| } |
| |
| #region Private Members |
| private StatsTable table_; |
| private List<int> pids_; |
| #endregion |
| } |
| } |