Home | History | Annotate | Download | only in stats_viewer
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 using System;
      6 using System.Collections;
      7 using System.Collections.Generic;
      8 using System.Diagnostics;
      9 using System.Runtime.InteropServices;
     10 using System.Text;
     11 
     12 namespace StatsViewer
     13 {
     14   /// <summary>
     15   /// The stats table shared memory segment contains this
     16   /// header structure.
     17   /// </summary>
     18   [StructLayout(LayoutKind.Sequential)]
     19   internal struct StatsFileHeader {
     20     public int version;
     21     public int size;
     22     public int max_counters;
     23     public int max_threads;
     24   };
     25 
     26   /// <summary>
     27   /// An entry in the StatsTable.
     28   /// </summary>
     29   class StatsTableEntry {
     30     public StatsTableEntry(int id, string name, StatsTable table) {
     31       id_ = id;
     32       name_ = name;
     33       table_ = table;
     34     }
     35 
     36     /// <summary>
     37     /// The unique id for this entry
     38     /// </summary>
     39     public int id { get { return id_; } }
     40 
     41     /// <summary>
     42     /// The name for this entry.
     43     /// </summary>
     44     public string name { get { return name_; } }
     45 
     46     /// <summary>
     47     /// The value of this entry now.  
     48     /// </summary>
     49     public int GetValue(int filter_pid) {
     50       return table_.GetValue(id_, filter_pid);
     51     }
     52 
     53     private int id_;
     54     private string name_;
     55     private StatsTable table_;
     56   }
     57 
     58   // An interface for StatsCounters
     59   interface IStatsCounter {
     60     // The name of the counter
     61     string name { get; }
     62   }
     63 
     64   // A counter.
     65   class StatsCounter : IStatsCounter {
     66     public StatsCounter(StatsTableEntry entry) {
     67       entry_ = entry;
     68     }
     69 
     70     public string name { 
     71       get {
     72         return entry_.name; 
     73       } 
     74     }
     75 
     76     public int GetValue(int filter_pid) {
     77       return entry_.GetValue(filter_pid);
     78     }
     79 
     80     private StatsTableEntry entry_;
     81   }
     82 
     83   // A timer.
     84   class StatsTimer : IStatsCounter {
     85     public StatsTimer(StatsTableEntry entry)
     86     {
     87       entry_ = entry;
     88     }
     89 
     90     public string name { 
     91       get { 
     92         return entry_.name; 
     93       } 
     94     }
     95 
     96     public int GetValue(int filter_pid) {
     97       return entry_.GetValue(filter_pid);
     98     }
     99 
    100     private StatsTableEntry entry_;
    101   }
    102 
    103   // A rate.
    104   class StatsCounterRate : IStatsCounter
    105   {
    106     public StatsCounterRate(StatsCounter counter, StatsTimer timer) {
    107       counter_ = counter;
    108       timer_ = timer;
    109     }
    110 
    111     public string name { get { return counter_.name; } }
    112 
    113     public int GetCount(int filter_pid) { 
    114       return counter_.GetValue(filter_pid);
    115     }
    116 
    117     public int GetTime(int filter_pid) {
    118       return timer_.GetValue(filter_pid);
    119     }
    120 
    121     private StatsCounter counter_;
    122     private StatsTimer timer_;
    123   }
    124 
    125   /// <summary>
    126   /// This is a C# reader for the chrome stats_table.
    127   /// </summary>
    128   class StatsTable {
    129     internal const int kMaxThreadNameLength = 32;
    130     internal const int kMaxCounterNameLength = 32;
    131 
    132     /// <summary>
    133     /// Open a StatsTable
    134     /// </summary>
    135     public StatsTable() {
    136     }
    137 
    138     #region Public Properties
    139     /// <summary>
    140     /// Get access to the counters in the table.
    141     /// </summary>
    142     public StatsTableCounters Counters() {
    143       return new StatsTableCounters(this);
    144     }
    145 
    146     /// <summary>
    147     /// Get access to the processes in the table
    148     /// </summary>
    149     public ICollection Processes {
    150       get {
    151         return new StatsTableProcesses(this);
    152       }
    153     }
    154     #endregion
    155 
    156     #region Internal Properties
    157     // 
    158     // The internal methods are accessible to the enumerators
    159     // and helper classes below.
    160     //
    161     
    162     /// <summary>
    163     /// Access to the table header
    164     /// </summary>
    165     internal StatsFileHeader Header {
    166       get { return header_; }
    167     }
    168 
    169     /// <summary>
    170     /// Get the offset of the ThreadName table
    171     /// </summary>
    172     internal long ThreadNamesOffset {
    173       get {
    174         return memory_.ToInt64() + Marshal.SizeOf(typeof(StatsFileHeader));
    175       }
    176     }
    177 
    178     /// <summary>
    179     /// Get the offset of the PIDs table
    180     /// </summary>
    181     internal long PidsOffset {
    182       get {
    183         long offset = ThreadNamesOffset;
    184         // Thread names table
    185         offset += AlignedSize(header_.max_threads * kMaxThreadNameLength * 2);
    186         // Thread TID table
    187         offset += AlignedSize(header_.max_threads * 
    188           Marshal.SizeOf(typeof(int)));
    189         return offset;
    190       }
    191     }
    192 
    193     /// <summary>
    194     /// Get the offset of the CounterName table
    195     /// </summary>
    196     internal long CounterNamesOffset {
    197       get {
    198         long offset = PidsOffset;
    199         // Thread PID table
    200         offset += AlignedSize(header_.max_threads * 
    201           Marshal.SizeOf(typeof(int)));
    202         return offset;
    203       }
    204     }
    205 
    206     /// <summary>
    207     /// Get the offset of the Data table
    208     /// </summary>
    209     internal long DataOffset {
    210       get {
    211         long offset = CounterNamesOffset;
    212         // Counter names table
    213         offset += AlignedSize(header_.max_counters * 
    214           kMaxCounterNameLength * 2);
    215         return offset;
    216       }
    217     }
    218     #endregion
    219 
    220     #region Public Methods
    221     /// <summary>
    222     /// Opens the memory map
    223     /// </summary>
    224     /// <returns></returns>
    225     /// <param name="name">The name of the file to open</param>
    226     public bool Open(string name) {
    227       map_handle_ = 
    228         Win32.OpenFileMapping((int)Win32.MapAccess.FILE_MAP_WRITE, false, 
    229                               name);
    230       if (map_handle_ == IntPtr.Zero)
    231         return false;
    232 
    233       memory_ = 
    234         Win32.MapViewOfFile(map_handle_, (int)Win32.MapAccess.FILE_MAP_WRITE, 
    235                             0,0, 0);
    236       if (memory_ == IntPtr.Zero) {
    237         Win32.CloseHandle(map_handle_);
    238         return false;
    239       }
    240 
    241       header_ = (StatsFileHeader)Marshal.PtrToStructure(memory_, header_.GetType());
    242       return true;
    243     }
    244 
    245     /// <summary>
    246     /// Close the mapped file.
    247     /// </summary>
    248     public void Close() {
    249       Win32.UnmapViewOfFile(memory_);
    250       Win32.CloseHandle(map_handle_);
    251     }
    252 
    253     /// <summary>
    254     /// Zero out the stats file.
    255     /// </summary>
    256     public void Zero() {
    257       long offset = DataOffset;
    258       for (int threads = 0; threads < header_.max_threads; threads++) {
    259         for (int counters = 0; counters < header_.max_counters; counters++) {
    260           Marshal.WriteInt32((IntPtr) offset, 0);
    261           offset += Marshal.SizeOf(typeof(int));
    262         }
    263       }
    264     }
    265 
    266     /// <summary>
    267     /// Get the value for a StatsCounterEntry now.
    268     /// </summary>
    269     /// <returns></returns>
    270     /// <param name="filter_pid">If a specific PID is being queried, filter to this PID.  0 means use all data.</param>
    271     /// <param name="id">The id of the CounterEntry to get the value for.</param>
    272     public int GetValue(int id, int filter_pid) {
    273       long pid_offset = PidsOffset;
    274       long data_offset = DataOffset;
    275       data_offset += id * (Header.max_threads * 
    276         Marshal.SizeOf(typeof(int)));
    277       int rv = 0;
    278       for (int cols = 0; cols < Header.max_threads; cols++)
    279       {
    280         int pid = Marshal.ReadInt32((IntPtr)pid_offset);
    281         if (filter_pid == 0 || filter_pid == pid)
    282         {
    283           rv += Marshal.ReadInt32((IntPtr)data_offset);
    284         }
    285         data_offset += Marshal.SizeOf(typeof(int));
    286         pid_offset += Marshal.SizeOf(typeof(int));
    287       }
    288       return rv;
    289     }
    290     #endregion
    291 
    292     #region Private Methods
    293     /// <summary>
    294     /// Align to 4-byte boundaries
    295     /// </summary>
    296     /// <param name="size"></param>
    297     /// <returns></returns>
    298     private long AlignedSize(long size) {
    299       Debug.Assert(sizeof(int) == 4);
    300       return size + (sizeof(int) - (size % sizeof(int))) % sizeof(int);
    301     }
    302     #endregion
    303 
    304     #region Private Members
    305     private IntPtr memory_;
    306     private IntPtr map_handle_;
    307     private StatsFileHeader header_;
    308     #endregion
    309   }
    310 
    311   /// <summary>
    312   /// Enumerable list of Counters in the StatsTable
    313   /// </summary>
    314   class StatsTableCounters : ICollection {
    315     /// <summary>
    316     /// Create the list of counters
    317     /// </summary>
    318     /// <param name="table"></param>
    319     /// pid</param>
    320     public StatsTableCounters(StatsTable table) {
    321       table_ = table;
    322       counter_hi_water_mark_ = -1;
    323       counters_ = new List<IStatsCounter>();
    324       FindCounters();
    325     }
    326 
    327     /// <summary>
    328     /// Scans the table for new entries.
    329     /// </summary>
    330     public void Update() {
    331       FindCounters();
    332     }
    333 
    334     #region IEnumerable Members
    335     public IEnumerator GetEnumerator() {
    336       return counters_.GetEnumerator();
    337     }
    338     #endregion
    339 
    340     #region ICollection Members
    341     public void CopyTo(Array array, int index) {
    342       throw new Exception("The method or operation is not implemented.");
    343     }
    344 
    345     public int Count {
    346       get {
    347         return counters_.Count;
    348       }
    349     }
    350 
    351     public bool IsSynchronized {
    352       get { 
    353         throw new Exception("The method or operation is not implemented."); 
    354       }
    355     }
    356 
    357     public object SyncRoot {
    358       get { 
    359         throw new Exception("The method or operation is not implemented."); 
    360       }
    361     }
    362     #endregion
    363 
    364     #region Private Methods
    365     /// <summary>
    366     /// Create a counter based on an entry
    367     /// </summary>
    368     /// <param name="id"></param>
    369     /// <param name="name"></param>
    370     /// <returns></returns>
    371     private IStatsCounter NameToCounter(int id, string name)
    372     {
    373       IStatsCounter rv = null;
    374 
    375       // check if the name has a type encoded
    376       if (name.Length > 2 && name[1] == ':')
    377       {
    378         StatsTableEntry entry = new StatsTableEntry(id, name.Substring(2), table_);
    379         switch (name[0])
    380         {
    381           case 't':
    382             rv = new StatsTimer(entry);
    383             break;
    384           case 'c':
    385             rv = new StatsCounter(entry);
    386             break;
    387         }
    388       }
    389       else
    390       {
    391         StatsTableEntry entry = new StatsTableEntry(id, name, table_);
    392         rv = new StatsCounter(entry);
    393       }
    394 
    395       return rv;
    396     }
    397 
    398     // If we have two StatsTableEntries with the same name, 
    399     // attempt to upgrade them to a higher level type.  
    400     // Example:  A counter + a timer == a rate!
    401     private void UpgradeCounter(IStatsCounter old_counter, IStatsCounter counter)
    402     {
    403       if (old_counter is StatsCounter && counter is StatsTimer)
    404       {
    405         StatsCounterRate rate = new StatsCounterRate(old_counter as StatsCounter,
    406                                           counter as StatsTimer);
    407         counters_.Remove(old_counter);
    408         counters_.Add(rate);
    409       }
    410       else if (old_counter is StatsTimer && counter is StatsCounter)
    411       {
    412         StatsCounterRate rate = new StatsCounterRate(counter as StatsCounter,
    413                                          old_counter as StatsTimer);
    414         counters_.Remove(old_counter);
    415         counters_.Add(rate);
    416       }
    417     }
    418 
    419     /// <summary>
    420     /// Find the counters in the table and insert into the counters_
    421     /// hash table.
    422     /// </summary>
    423     private void FindCounters()
    424     {
    425       Debug.Assert(table_.Header.max_counters > 0);
    426 
    427       int index = counter_hi_water_mark_;
    428 
    429       do
    430       {
    431         // Find an entry in the table.
    432         index++;
    433         long offset = table_.CounterNamesOffset +
    434           (index * StatsTable.kMaxCounterNameLength * 2);
    435         string name = Marshal.PtrToStringUni((IntPtr)offset);
    436         if (name.Length == 0)
    437           continue;
    438 
    439         // Record that we've already looked at this StatsTableEntry.
    440         counter_hi_water_mark_ = index;
    441 
    442         IStatsCounter counter = NameToCounter(index, name);
    443 
    444         if (counter != null)
    445         {
    446           IStatsCounter old_counter = FindExistingCounter(counter.name);
    447           if (old_counter != null)
    448             UpgradeCounter(old_counter, counter);
    449           else
    450             counters_.Add(counter);
    451         }
    452       } while (index < table_.Header.max_counters - 1);
    453     }
    454 
    455     /// <summary>
    456     /// Find an existing counter in our table
    457     /// </summary>
    458     /// <param name="name"></param>
    459     private IStatsCounter FindExistingCounter(string name) {
    460       foreach (IStatsCounter ctr in counters_)
    461       {
    462         if (ctr.name == name)
    463           return ctr;
    464       }
    465       return null;
    466     }
    467     #endregion
    468 
    469     #region Private Members
    470     private StatsTable table_;
    471     private List<IStatsCounter> counters_;
    472     // Highest index of counters processed.
    473     private int counter_hi_water_mark_;
    474     #endregion
    475   }
    476 
    477   /// <summary>
    478   /// A collection of processes
    479   /// </summary>
    480   class StatsTableProcesses : ICollection
    481   {
    482     /// <summary>
    483     /// Constructor
    484     /// </summary>
    485     /// <param name="table"></param>
    486     public StatsTableProcesses(StatsTable table) {
    487       table_ = table;
    488       pids_ = new List<int>();
    489       Initialize();
    490     }
    491 
    492     #region ICollection Members
    493     public void CopyTo(Array array, int index) {
    494       throw new Exception("The method or operation is not implemented.");
    495     }
    496 
    497     public int Count {
    498       get {
    499         return pids_.Count;
    500       }
    501     }
    502 
    503     public bool IsSynchronized {
    504       get {
    505         throw new Exception("The method or operation is not implemented."); 
    506       }
    507     }
    508 
    509     public object SyncRoot {
    510       get { 
    511         throw new Exception("The method or operation is not implemented."); 
    512       }
    513     }
    514     #endregion
    515 
    516     #region IEnumerable Members
    517     public IEnumerator GetEnumerator() {
    518       return pids_.GetEnumerator();
    519     }
    520     #endregion
    521 
    522     /// <summary>
    523     /// Initialize the pid list.
    524     /// </summary>
    525     private void Initialize() {
    526       long offset = table_.ThreadNamesOffset;
    527 
    528       for (int index = 0; index < table_.Header.max_threads; index++) {
    529         string thread_name = Marshal.PtrToStringUni((IntPtr)offset);
    530         if (thread_name.Length > 0) {
    531           long pidOffset = table_.PidsOffset + index * 
    532             Marshal.SizeOf(typeof(int));
    533           int pid = Marshal.ReadInt32((IntPtr)pidOffset);
    534           if (!pids_.Contains(pid))
    535             pids_.Add(pid);
    536         }
    537         offset += StatsTable.kMaxThreadNameLength * 2;
    538       }
    539     }
    540 
    541     #region Private Members
    542     private StatsTable table_;
    543     private List<int> pids_;
    544     #endregion
    545   }
    546 }
    547