diff options
author | Ryan <ry@tinyclouds.org> | 2009-04-22 19:35:47 +0200 |
---|---|---|
committer | Ryan <ry@tinyclouds.org> | 2009-04-22 19:35:47 +0200 |
commit | 40c0f755c998d2615fe8466aab20c6d81bd463e7 (patch) | |
tree | 51fcb08ba1bd3f745ceb43fd5f814a5700079881 /deps/v8/tools/stats-viewer.py | |
parent | a93cf503073ba0258c55dec4dc325bdc1509b739 (diff) | |
download | android-node-v8-40c0f755c998d2615fe8466aab20c6d81bd463e7.tar.gz android-node-v8-40c0f755c998d2615fe8466aab20c6d81bd463e7.tar.bz2 android-node-v8-40c0f755c998d2615fe8466aab20c6d81bd463e7.zip |
import full versions of dependency libraries!
Diffstat (limited to 'deps/v8/tools/stats-viewer.py')
-rwxr-xr-x | deps/v8/tools/stats-viewer.py | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/deps/v8/tools/stats-viewer.py b/deps/v8/tools/stats-viewer.py new file mode 100755 index 0000000000..bd6a8fb913 --- /dev/null +++ b/deps/v8/tools/stats-viewer.py @@ -0,0 +1,372 @@ +# Copyright 2008 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""A cross-platform execution counter viewer. + +The stats viewer reads counters from a binary file and displays them +in a window, re-reading and re-displaying with regular intervals. +""" + + +import mmap +import os +import struct +import sys +import time +import Tkinter + + +# The interval, in milliseconds, between ui updates +UPDATE_INTERVAL_MS = 100 + + +# Mapping from counter prefix to the formatting to be used for the counter +COUNTER_LABELS = {"t": "%i ms.", "c": "%i"} + + +# The magic number used to check if a file is not a counters file +COUNTERS_FILE_MAGIC_NUMBER = 0xDEADFACE + + +class StatsViewer(object): + """The main class that keeps the data used by the stats viewer.""" + + def __init__(self, data_name): + """Creates a new instance. + + Args: + data_name: the name of the file containing the counters. + """ + self.data_name = data_name + + # The handle created by mmap.mmap to the counters file. We need + # this to clean it up on exit. + self.shared_mmap = None + + # A mapping from counter names to the ui element that displays + # them + self.ui_counters = {} + + # The counter collection used to access the counters file + self.data = None + + # The Tkinter root window object + self.root = None + + def Run(self): + """The main entry-point to running the stats viewer.""" + try: + self.data = self.MountSharedData() + # OpenWindow blocks until the main window is closed + self.OpenWindow() + finally: + self.CleanUp() + + def MountSharedData(self): + """Mount the binary counters file as a memory-mapped file. If + something goes wrong print an informative message and exit the + program.""" + if not os.path.exists(self.data_name): + print "File %s doesn't exist." % self.data_name + sys.exit(1) + data_file = open(self.data_name, "r") + size = os.fstat(data_file.fileno()).st_size + fileno = data_file.fileno() + self.shared_mmap = mmap.mmap(fileno, size, access=mmap.ACCESS_READ) + data_access = SharedDataAccess(self.shared_mmap) + if data_access.IntAt(0) != COUNTERS_FILE_MAGIC_NUMBER: + print "File %s is not stats data." % self.data_name + sys.exit(1) + return CounterCollection(data_access) + + def CleanUp(self): + """Cleans up the memory mapped file if necessary.""" + if self.shared_mmap: + self.shared_mmap.close() + + def UpdateCounters(self): + """Read the contents of the memory-mapped file and update the ui if + necessary. If the same counters are present in the file as before + we just update the existing labels. If any counters have been added + or removed we scrap the existing ui and draw a new one. + """ + changed = False + counters_in_use = self.data.CountersInUse() + if counters_in_use != len(self.ui_counters): + self.RefreshCounters() + changed = True + else: + for i in xrange(self.data.CountersInUse()): + counter = self.data.Counter(i) + name = counter.Name() + if name in self.ui_counters: + value = counter.Value() + ui_counter = self.ui_counters[name] + counter_changed = ui_counter.Set(value) + changed = (changed or counter_changed) + else: + self.RefreshCounters() + changed = True + break + if changed: + # The title of the window shows the last time the file was + # changed. + self.UpdateTime() + self.ScheduleUpdate() + + def UpdateTime(self): + """Update the title of the window with the current time.""" + self.root.title("Stats Viewer [updated %s]" % time.strftime("%H:%M:%S")) + + def ScheduleUpdate(self): + """Schedules the next ui update.""" + self.root.after(UPDATE_INTERVAL_MS, lambda: self.UpdateCounters()) + + def RefreshCounters(self): + """Tear down and rebuild the controls in the main window.""" + counters = self.ComputeCounters() + self.RebuildMainWindow(counters) + + def ComputeCounters(self): + """Group the counters by the suffix of their name. + + Since the same code-level counter (for instance "X") can result in + several variables in the binary counters file that differ only by a + two-character prefix (for instance "c:X" and "t:X") counters are + grouped by suffix and then displayed with custom formatting + depending on their prefix. + + Returns: + A mapping from suffixes to a list of counters with that suffix, + sorted by prefix. + """ + names = {} + for i in xrange(self.data.CountersInUse()): + counter = self.data.Counter(i) + name = counter.Name() + names[name] = counter + + # By sorting the keys we ensure that the prefixes always come in the + # same order ("c:" before "t:") which looks more consistent in the + # ui. + sorted_keys = names.keys() + sorted_keys.sort() + + # Group together the names whose suffix after a ':' are the same. + groups = {} + for name in sorted_keys: + counter = names[name] + if ":" in name: + name = name[name.find(":")+1:] + if not name in groups: + groups[name] = [] + groups[name].append(counter) + + return groups + + def RebuildMainWindow(self, groups): + """Tear down and rebuild the main window. + + Args: + groups: the groups of counters to display + """ + # Remove elements in the current ui + self.ui_counters.clear() + for child in self.root.children.values(): + child.destroy() + + # Build new ui + index = 0 + sorted_groups = groups.keys() + sorted_groups.sort() + for counter_name in sorted_groups: + counter_objs = groups[counter_name] + name = Tkinter.Label(self.root, width=50, anchor=Tkinter.W, + text=counter_name) + name.grid(row=index, column=0, padx=1, pady=1) + count = len(counter_objs) + for i in xrange(count): + counter = counter_objs[i] + name = counter.Name() + var = Tkinter.StringVar() + value = Tkinter.Label(self.root, width=15, anchor=Tkinter.W, + textvariable=var) + value.grid(row=index, column=(1 + i), padx=1, pady=1) + + # If we know how to interpret the prefix of this counter then + # add an appropriate formatting to the variable + if (":" in name) and (name[0] in COUNTER_LABELS): + format = COUNTER_LABELS[name[0]] + else: + format = "%i" + ui_counter = UiCounter(var, format) + self.ui_counters[name] = ui_counter + ui_counter.Set(counter.Value()) + index += 1 + self.root.update() + + def OpenWindow(self): + """Create and display the root window.""" + self.root = Tkinter.Tk() + + # Tkinter is no good at resizing so we disable it + self.root.resizable(width=False, height=False) + self.RefreshCounters() + self.ScheduleUpdate() + self.root.mainloop() + + +class UiCounter(object): + """A counter in the ui.""" + + def __init__(self, var, format): + """Creates a new ui counter. + + Args: + var: the Tkinter string variable for updating the ui + format: the format string used to format this counter + """ + self.var = var + self.format = format + self.last_value = None + + def Set(self, value): + """Updates the ui for this counter. + + Args: + value: The value to display + + Returns: + True if the value had changed, otherwise False. The first call + always returns True. + """ + if value == self.last_value: + return False + else: + self.last_value = value + self.var.set(self.format % value) + return True + + +class SharedDataAccess(object): + """A utility class for reading data from the memory-mapped binary + counters file.""" + + def __init__(self, data): + """Create a new instance. + + Args: + data: A handle to the memory-mapped file, as returned by mmap.mmap. + """ + self.data = data + + def ByteAt(self, index): + """Return the (unsigned) byte at the specified byte index.""" + return ord(self.CharAt(index)) + + def IntAt(self, index): + """Return the little-endian 32-byte int at the specified byte index.""" + word_str = self.data[index:index+4] + result, = struct.unpack("I", word_str) + return result + + def CharAt(self, index): + """Return the ascii character at the specified byte index.""" + return self.data[index] + + +class Counter(object): + """A pointer to a single counter withing a binary counters file.""" + + def __init__(self, data, offset): + """Create a new instance. + + Args: + data: the shared data access object containing the counter + offset: the byte offset of the start of this counter + """ + self.data = data + self.offset = offset + + def Value(self): + """Return the integer value of this counter.""" + return self.data.IntAt(self.offset) + + def Name(self): + """Return the ascii name of this counter.""" + result = "" + index = self.offset + 4 + current = self.data.ByteAt(index) + while current: + result += chr(current) + index += 1 + current = self.data.ByteAt(index) + return result + + +class CounterCollection(object): + """An overlay over a counters file that provides access to the + individual counters contained in the file.""" + + def __init__(self, data): + """Create a new instance. + + Args: + data: the shared data access object + """ + self.data = data + self.max_counters = data.IntAt(4) + self.max_name_size = data.IntAt(8) + + def CountersInUse(self): + """Return the number of counters in active use.""" + return self.data.IntAt(12) + + def Counter(self, index): + """Return the index'th counter.""" + return Counter(self.data, 16 + index * self.CounterSize()) + + def CounterSize(self): + """Return the size of a single counter.""" + return 4 + self.max_name_size + + +def Main(data_file): + """Run the stats counter. + + Args: + data_file: The counters file to monitor. + """ + StatsViewer(data_file).Run() + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print "Usage: stats-viewer.py <stats data>" + sys.exit(1) + Main(sys.argv[1]) |