// 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 { /// /// The stats table shared memory segment contains this /// header structure. /// [StructLayout(LayoutKind.Sequential)] internal struct StatsFileHeader { public int version; public int size; public int max_counters; public int max_threads; }; /// /// An entry in the StatsTable. /// class StatsTableEntry { public StatsTableEntry(int id, string name, StatsTable table) { id_ = id; name_ = name; table_ = table; } /// /// The unique id for this entry /// public int id { get { return id_; } } /// /// The name for this entry. /// public string name { get { return name_; } } /// /// The value of this entry now. /// 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_; } /// /// This is a C# reader for the chrome stats_table. /// class StatsTable { internal const int kMaxThreadNameLength = 32; internal const int kMaxCounterNameLength = 32; /// /// Open a StatsTable /// public StatsTable() { } #region Public Properties /// /// Get access to the counters in the table. /// public StatsTableCounters Counters() { return new StatsTableCounters(this); } /// /// Get access to the processes in the table /// public ICollection Processes { get { return new StatsTableProcesses(this); } } #endregion #region Internal Properties // // The internal methods are accessible to the enumerators // and helper classes below. // /// /// Access to the table header /// internal StatsFileHeader Header { get { return header_; } } /// /// Get the offset of the ThreadName table /// internal long ThreadNamesOffset { get { return memory_.ToInt64() + Marshal.SizeOf(typeof(StatsFileHeader)); } } /// /// Get the offset of the PIDs table /// 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; } } /// /// Get the offset of the CounterName table /// internal long CounterNamesOffset { get { long offset = PidsOffset; // Thread PID table offset += AlignedSize(header_.max_threads * Marshal.SizeOf(typeof(int))); return offset; } } /// /// Get the offset of the Data table /// internal long DataOffset { get { long offset = CounterNamesOffset; // Counter names table offset += AlignedSize(header_.max_counters * kMaxCounterNameLength * 2); return offset; } } #endregion #region Public Methods /// /// Opens the memory map /// /// /// The name of the file to open 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; } /// /// Close the mapped file. /// public void Close() { Win32.UnmapViewOfFile(memory_); Win32.CloseHandle(map_handle_); } /// /// Zero out the stats file. /// 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)); } } } /// /// Get the value for a StatsCounterEntry now. /// /// /// If a specific PID is being queried, filter to this PID. 0 means use all data. /// The id of the CounterEntry to get the value for. 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 /// /// Align to 4-byte boundaries /// /// /// 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 } /// /// Enumerable list of Counters in the StatsTable /// class StatsTableCounters : ICollection { /// /// Create the list of counters /// /// /// pid public StatsTableCounters(StatsTable table) { table_ = table; counter_hi_water_mark_ = -1; counters_ = new List(); FindCounters(); } /// /// Scans the table for new entries. /// 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 /// /// Create a counter based on an entry /// /// /// /// 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); } } /// /// Find the counters in the table and insert into the counters_ /// hash table. /// 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); } /// /// Find an existing counter in our table /// /// 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 counters_; // Highest index of counters processed. private int counter_hi_water_mark_; #endregion } /// /// A collection of processes /// class StatsTableProcesses : ICollection { /// /// Constructor /// /// public StatsTableProcesses(StatsTable table) { table_ = table; pids_ = new List(); 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 /// /// Initialize the pid list. /// 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 pids_; #endregion } }