blob: e5d0894e37c579d090e023812e7d7a7c43784e93 [file] [log] [blame]
// 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
}
}