Move V8 to external/v8
Change-Id: If68025d67453785a651c5dfb34fad298c16676a4
diff --git a/tools/stats-viewer.py b/tools/stats-viewer.py
new file mode 100755
index 0000000..bd6a8fb
--- /dev/null
+++ b/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])