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
5using System;
6using System.Collections;
7using System.Collections.Generic;
8using System.Diagnostics;
9using System.Runtime.InteropServices;
10using System.Text;
11
12namespace 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