• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
StatsTableEntry(int id, string name, StatsTable table)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>
GetValue(int filter_pid)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 {
StatsCounter(StatsTableEntry entry)66     public StatsCounter(StatsTableEntry entry) {
67       entry_ = entry;
68     }
69 
70     public string name {
71       get {
72         return entry_.name;
73       }
74     }
75 
GetValue(int filter_pid)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 {
StatsTimer(StatsTableEntry entry)85     public StatsTimer(StatsTableEntry entry)
86     {
87       entry_ = entry;
88     }
89 
90     public string name {
91       get {
92         return entry_.name;
93       }
94     }
95 
GetValue(int filter_pid)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   {
StatsCounterRate(StatsCounter counter, StatsTimer timer)106     public StatsCounterRate(StatsCounter counter, StatsTimer timer) {
107       counter_ = counter;
108       timer_ = timer;
109     }
110 
111     public string name { get { return counter_.name; } }
112 
GetCount(int filter_pid)113     public int GetCount(int filter_pid) {
114       return counter_.GetValue(filter_pid);
115     }
116 
GetTime(int filter_pid)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>
StatsTable()135     public StatsTable() {
136     }
137 
138     #region Public Properties
139     /// <summary>
140     /// Get access to the counters in the table.
141     /// </summary>
Counters()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>
Open(string name)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>
Close()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>
Zero()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>
GetValue(int id, int filter_pid)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>
AlignedSize(long size)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>
StatsTableCounters(StatsTable table)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>
Update()330     public void Update() {
331       FindCounters();
332     }
333 
334     #region IEnumerable Members
GetEnumerator()335     public IEnumerator GetEnumerator() {
336       return counters_.GetEnumerator();
337     }
338     #endregion
339 
340     #region ICollection Members
CopyTo(Array array, int index)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>
NameToCounter(int id, string name)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!
UpgradeCounter(IStatsCounter old_counter, IStatsCounter counter)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>
FindCounters()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>
FindExistingCounter(string name)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>
StatsTableProcesses(StatsTable table)486     public StatsTableProcesses(StatsTable table) {
487       table_ = table;
488       pids_ = new List<int>();
489       Initialize();
490     }
491 
492     #region ICollection Members
CopyTo(Array array, int index)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
GetEnumerator()517     public IEnumerator GetEnumerator() {
518       return pids_.GetEnumerator();
519     }
520     #endregion
521 
522     /// <summary>
523     /// Initialize the pid list.
524     /// </summary>
Initialize()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