547 lines
14 KiB
C#
547 lines
14 KiB
C#
// 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
|
|
}
|
|
}
|