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])