// Copyright 2012 The Chromium Authors | |
// 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 | |
} | |
} |