The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / tools / stats-viewer.py @ 40c0f755

History | View | Annotate | Download (11.2 KB)

1 40c0f755 Ryan
# Copyright 2008 the V8 project authors. All rights reserved.
2
# Redistribution and use in source and binary forms, with or without
3
# modification, are permitted provided that the following conditions are
4
# met:
5
#
6
#     * Redistributions of source code must retain the above copyright
7
#       notice, this list of conditions and the following disclaimer.
8
#     * Redistributions in binary form must reproduce the above
9
#       copyright notice, this list of conditions and the following
10
#       disclaimer in the documentation and/or other materials provided
11
#       with the distribution.
12
#     * Neither the name of Google Inc. nor the names of its
13
#       contributors may be used to endorse or promote products derived
14
#       from this software without specific prior written permission.
15
#
16
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28
29
"""A cross-platform execution counter viewer.
30

31
The stats viewer reads counters from a binary file and displays them
32
in a window, re-reading and re-displaying with regular intervals.
33
"""
34
35
36
import mmap
37
import os
38
import struct
39
import sys
40
import time
41
import Tkinter
42
43
44
# The interval, in milliseconds, between ui updates
45
UPDATE_INTERVAL_MS = 100
46
47
48
# Mapping from counter prefix to the formatting to be used for the counter
49
COUNTER_LABELS = {"t": "%i ms.", "c": "%i"}
50
51
52
# The magic number used to check if a file is not a counters file
53
COUNTERS_FILE_MAGIC_NUMBER = 0xDEADFACE
54
55
56
class StatsViewer(object):
57
  """The main class that keeps the data used by the stats viewer."""
58
59
  def __init__(self, data_name):
60
    """Creates a new instance.
61

62
    Args:
63
      data_name: the name of the file containing the counters.
64
    """
65
    self.data_name = data_name
66
67
    # The handle created by mmap.mmap to the counters file.  We need
68
    # this to clean it up on exit.
69
    self.shared_mmap = None
70
71
    # A mapping from counter names to the ui element that displays
72
    # them
73
    self.ui_counters = {}
74
75
    # The counter collection used to access the counters file
76
    self.data = None
77
78
    # The Tkinter root window object
79
    self.root = None
80
81
  def Run(self):
82
    """The main entry-point to running the stats viewer."""
83
    try:
84
      self.data = self.MountSharedData()
85
      # OpenWindow blocks until the main window is closed
86
      self.OpenWindow()
87
    finally:
88
      self.CleanUp()
89
90
  def MountSharedData(self):
91
    """Mount the binary counters file as a memory-mapped file.  If
92
    something goes wrong print an informative message and exit the
93
    program."""
94
    if not os.path.exists(self.data_name):
95
      print "File %s doesn't exist." % self.data_name
96
      sys.exit(1)
97
    data_file = open(self.data_name, "r")
98
    size = os.fstat(data_file.fileno()).st_size
99
    fileno = data_file.fileno()
100
    self.shared_mmap = mmap.mmap(fileno, size, access=mmap.ACCESS_READ)
101
    data_access = SharedDataAccess(self.shared_mmap)
102
    if data_access.IntAt(0) != COUNTERS_FILE_MAGIC_NUMBER:
103
      print "File %s is not stats data." % self.data_name
104
      sys.exit(1)
105
    return CounterCollection(data_access)
106
107
  def CleanUp(self):
108
    """Cleans up the memory mapped file if necessary."""
109
    if self.shared_mmap:
110
      self.shared_mmap.close()
111
112
  def UpdateCounters(self):
113
    """Read the contents of the memory-mapped file and update the ui if
114
    necessary.  If the same counters are present in the file as before
115
    we just update the existing labels.  If any counters have been added
116
    or removed we scrap the existing ui and draw a new one.
117
    """
118
    changed = False
119
    counters_in_use = self.data.CountersInUse()
120
    if counters_in_use != len(self.ui_counters):
121
      self.RefreshCounters()
122
      changed = True
123
    else:
124
      for i in xrange(self.data.CountersInUse()):
125
        counter = self.data.Counter(i)
126
        name = counter.Name()
127
        if name in self.ui_counters:
128
          value = counter.Value()
129
          ui_counter = self.ui_counters[name]
130
          counter_changed = ui_counter.Set(value)
131
          changed = (changed or counter_changed)
132
        else:
133
          self.RefreshCounters()
134
          changed = True
135
          break
136
    if changed:
137
      # The title of the window shows the last time the file was
138
      # changed.
139
      self.UpdateTime()
140
    self.ScheduleUpdate()
141
142
  def UpdateTime(self):
143
    """Update the title of the window with the current time."""
144
    self.root.title("Stats Viewer [updated %s]" % time.strftime("%H:%M:%S"))
145
146
  def ScheduleUpdate(self):
147
    """Schedules the next ui update."""
148
    self.root.after(UPDATE_INTERVAL_MS, lambda: self.UpdateCounters())
149
150
  def RefreshCounters(self):
151
    """Tear down and rebuild the controls in the main window."""
152
    counters = self.ComputeCounters()
153
    self.RebuildMainWindow(counters)
154
155
  def ComputeCounters(self):
156
    """Group the counters by the suffix of their name.
157

158
    Since the same code-level counter (for instance "X") can result in
159
    several variables in the binary counters file that differ only by a
160
    two-character prefix (for instance "c:X" and "t:X") counters are
161
    grouped by suffix and then displayed with custom formatting
162
    depending on their prefix.
163

164
    Returns:
165
      A mapping from suffixes to a list of counters with that suffix,
166
      sorted by prefix.
167
    """
168
    names = {}
169
    for i in xrange(self.data.CountersInUse()):
170
      counter = self.data.Counter(i)
171
      name = counter.Name()
172
      names[name] = counter
173
174
    # By sorting the keys we ensure that the prefixes always come in the
175
    # same order ("c:" before "t:") which looks more consistent in the
176
    # ui.
177
    sorted_keys = names.keys()
178
    sorted_keys.sort()
179
180
    # Group together the names whose suffix after a ':' are the same.
181
    groups = {}
182
    for name in sorted_keys:
183
      counter = names[name]
184
      if ":" in name:
185
        name = name[name.find(":")+1:]
186
      if not name in groups:
187
        groups[name] = []
188
      groups[name].append(counter)
189
190
    return groups
191
192
  def RebuildMainWindow(self, groups):
193
    """Tear down and rebuild the main window.
194

195
    Args:
196
      groups: the groups of counters to display
197
    """
198
    # Remove elements in the current ui
199
    self.ui_counters.clear()
200
    for child in self.root.children.values():
201
      child.destroy()
202
203
    # Build new ui
204
    index = 0
205
    sorted_groups = groups.keys()
206
    sorted_groups.sort()
207
    for counter_name in sorted_groups:
208
      counter_objs = groups[counter_name]
209
      name = Tkinter.Label(self.root, width=50, anchor=Tkinter.W,
210
                           text=counter_name)
211
      name.grid(row=index, column=0, padx=1, pady=1)
212
      count = len(counter_objs)
213
      for i in xrange(count):
214
        counter = counter_objs[i]
215
        name = counter.Name()
216
        var = Tkinter.StringVar()
217
        value = Tkinter.Label(self.root, width=15, anchor=Tkinter.W,
218
                              textvariable=var)
219
        value.grid(row=index, column=(1 + i), padx=1, pady=1)
220
221
        # If we know how to interpret the prefix of this counter then
222
        # add an appropriate formatting to the variable
223
        if (":" in name) and (name[0] in COUNTER_LABELS):
224
          format = COUNTER_LABELS[name[0]]
225
        else:
226
          format = "%i"
227
        ui_counter = UiCounter(var, format)
228
        self.ui_counters[name] = ui_counter
229
        ui_counter.Set(counter.Value())
230
      index += 1
231
    self.root.update()
232
233
  def OpenWindow(self):
234
    """Create and display the root window."""
235
    self.root = Tkinter.Tk()
236
237
    # Tkinter is no good at resizing so we disable it
238
    self.root.resizable(width=False, height=False)
239
    self.RefreshCounters()
240
    self.ScheduleUpdate()
241
    self.root.mainloop()
242
243
244
class UiCounter(object):
245
  """A counter in the ui."""
246
247
  def __init__(self, var, format):
248
    """Creates a new ui counter.
249

250
    Args:
251
      var: the Tkinter string variable for updating the ui
252
      format: the format string used to format this counter
253
    """
254
    self.var = var
255
    self.format = format
256
    self.last_value = None
257
258
  def Set(self, value):
259
    """Updates the ui for this counter.
260

261
    Args:
262
      value: The value to display
263

264
    Returns:
265
      True if the value had changed, otherwise False.  The first call
266
      always returns True.
267
    """
268
    if value == self.last_value:
269
      return False
270
    else:
271
      self.last_value = value
272
      self.var.set(self.format % value)
273
      return True
274
275
276
class SharedDataAccess(object):
277
  """A utility class for reading data from the memory-mapped binary
278
  counters file."""
279
280
  def __init__(self, data):
281
    """Create a new instance.
282

283
    Args:
284
      data: A handle to the memory-mapped file, as returned by mmap.mmap.
285
    """
286
    self.data = data
287
288
  def ByteAt(self, index):
289
    """Return the (unsigned) byte at the specified byte index."""
290
    return ord(self.CharAt(index))
291
292
  def IntAt(self, index):
293
    """Return the little-endian 32-byte int at the specified byte index."""
294
    word_str = self.data[index:index+4]
295
    result, = struct.unpack("I", word_str)
296
    return result
297
298
  def CharAt(self, index):
299
    """Return the ascii character at the specified byte index."""
300
    return self.data[index]
301
302
303
class Counter(object):
304
  """A pointer to a single counter withing a binary counters file."""
305
306
  def __init__(self, data, offset):
307
    """Create a new instance.
308

309
    Args:
310
      data: the shared data access object containing the counter
311
      offset: the byte offset of the start of this counter
312
    """
313
    self.data = data
314
    self.offset = offset
315
316
  def Value(self):
317
    """Return the integer value of this counter."""
318
    return self.data.IntAt(self.offset)
319
320
  def Name(self):
321
    """Return the ascii name of this counter."""
322
    result = ""
323
    index = self.offset + 4
324
    current = self.data.ByteAt(index)
325
    while current:
326
      result += chr(current)
327
      index += 1
328
      current = self.data.ByteAt(index)
329
    return result
330
331
332
class CounterCollection(object):
333
  """An overlay over a counters file that provides access to the
334
  individual counters contained in the file."""
335
336
  def __init__(self, data):
337
    """Create a new instance.
338

339
    Args:
340
      data: the shared data access object
341
    """
342
    self.data = data
343
    self.max_counters = data.IntAt(4)
344
    self.max_name_size = data.IntAt(8)
345
346
  def CountersInUse(self):
347
    """Return the number of counters in active use."""
348
    return self.data.IntAt(12)
349
350
  def Counter(self, index):
351
    """Return the index'th counter."""
352
    return Counter(self.data, 16 + index * self.CounterSize())
353
354
  def CounterSize(self):
355
    """Return the size of a single counter."""
356
    return 4 + self.max_name_size
357
358
359
def Main(data_file):
360
  """Run the stats counter.
361

362
  Args:
363
    data_file: The counters file to monitor.
364
  """
365
  StatsViewer(data_file).Run()
366
367
368
if __name__ == "__main__":
369
  if len(sys.argv) != 2:
370
    print "Usage: stats-viewer.py <stats data>"
371
    sys.exit(1)
372
  Main(sys.argv[1])