chromium/tools/stats_viewer/stats_table.cs

// 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
  }
}