Added support in d8 for memory-mapped counters and added the python
stats-viewer tool. git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@904 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
a56ced1b87
commit
dc2077465f
54
src/d8.cc
54
src/d8.cc
@ -84,6 +84,9 @@ i::SmartPointer<char> DumbLineEditor::Prompt(const char* prompt) {
|
||||
|
||||
|
||||
Shell::CounterMap Shell::counter_map_;
|
||||
i::OS::MemoryMappedFile* Shell::counters_file_ = NULL;
|
||||
CounterCollection Shell::local_counters_;
|
||||
CounterCollection* Shell::counters_ = &local_counters_;
|
||||
Persistent<Context> Shell::utility_context_;
|
||||
Persistent<Context> Shell::evaluation_context_;
|
||||
|
||||
@ -209,20 +212,59 @@ Handle<Array> Shell::GetCompletions(Handle<String> text, Handle<String> full) {
|
||||
}
|
||||
|
||||
|
||||
int32_t* Counter::Bind(const char* name) {
|
||||
int i;
|
||||
for (i = 0; i < kMaxNameSize - 1 && name[i]; i++)
|
||||
name_[i] = static_cast<char>(name[i]);
|
||||
name_[i] = '\0';
|
||||
return &counter_;
|
||||
}
|
||||
|
||||
|
||||
CounterCollection::CounterCollection() {
|
||||
magic_number_ = 0xDEADFACE;
|
||||
max_counters_ = kMaxCounters;
|
||||
max_name_size_ = Counter::kMaxNameSize;
|
||||
counters_in_use_ = 0;
|
||||
}
|
||||
|
||||
|
||||
Counter* CounterCollection::GetNextCounter() {
|
||||
if (counters_in_use_ == kMaxCounters) return NULL;
|
||||
return &counters_[counters_in_use_++];
|
||||
}
|
||||
|
||||
|
||||
void Shell::MapCounters(const char* name) {
|
||||
counters_file_ = i::OS::MemoryMappedFile::create(name,
|
||||
sizeof(CounterCollection), &local_counters_);
|
||||
void* memory = (counters_file_ == NULL) ?
|
||||
NULL : counters_file_->memory();
|
||||
if (memory == NULL) {
|
||||
printf("Could not map counters file %s\n", name);
|
||||
exit(1);
|
||||
}
|
||||
counters_ = static_cast<CounterCollection*>(memory);
|
||||
V8::SetCounterFunction(LookupCounter);
|
||||
}
|
||||
|
||||
|
||||
int* Shell::LookupCounter(const char* name) {
|
||||
CounterMap::iterator item = counter_map_.find(name);
|
||||
if (item != counter_map_.end()) {
|
||||
Counter* result = (*item).second;
|
||||
return result->GetValuePtr();
|
||||
return result->ptr();
|
||||
}
|
||||
Counter* result = new Counter(name);
|
||||
counter_map_[name] = result;
|
||||
return result->GetValuePtr();
|
||||
Counter* result = counters_->GetNextCounter();
|
||||
if (result == NULL) return NULL;
|
||||
return result->Bind(name);
|
||||
}
|
||||
|
||||
|
||||
void Shell::Initialize() {
|
||||
// Set up counters
|
||||
if (i::FLAG_map_counters != NULL)
|
||||
MapCounters(i::FLAG_map_counters);
|
||||
if (i::FLAG_dump_counters)
|
||||
V8::SetCounterFunction(LookupCounter);
|
||||
// Initialize the global objects
|
||||
@ -286,10 +328,12 @@ void Shell::OnExit() {
|
||||
i != counter_map_.end();
|
||||
i++) {
|
||||
Counter* counter = (*i).second;
|
||||
::printf("| %-38s | %8i |\n", counter->name(), counter->value());
|
||||
::printf("| %-38s | %8i |\n", (*i).first, counter->value());
|
||||
}
|
||||
::printf("+----------------------------------------+----------+\n");
|
||||
}
|
||||
if (counters_file_ != NULL)
|
||||
delete counters_file_;
|
||||
}
|
||||
|
||||
|
||||
|
37
src/d8.h
37
src/d8.h
@ -42,16 +42,33 @@ namespace v8 {
|
||||
namespace i = v8::internal;
|
||||
|
||||
|
||||
// A single counter in a counter collection.
|
||||
class Counter {
|
||||
public:
|
||||
explicit Counter(const char* name)
|
||||
: name_(name), value_(0) { }
|
||||
int* GetValuePtr() { return &value_; }
|
||||
const char* name() { return name_; }
|
||||
int value() { return value_; }
|
||||
static const int kMaxNameSize = 64;
|
||||
int32_t* Bind(const char* name);
|
||||
int32_t* ptr() { return &counter_; }
|
||||
int32_t value() { return counter_; }
|
||||
private:
|
||||
const char* name_;
|
||||
int value_;
|
||||
int32_t counter_;
|
||||
uint8_t name_[kMaxNameSize];
|
||||
};
|
||||
|
||||
|
||||
// A set of counters and associated information. An instance of this
|
||||
// class is stored directly in the memory-mapped counters file if
|
||||
// the --map-counters options is used
|
||||
class CounterCollection {
|
||||
public:
|
||||
CounterCollection();
|
||||
Counter* GetNextCounter();
|
||||
private:
|
||||
static const unsigned kMaxCounters = 256;
|
||||
uint32_t magic_number_;
|
||||
uint32_t max_counters_;
|
||||
uint32_t max_name_size_;
|
||||
uint32_t counters_in_use_;
|
||||
Counter counters_[kMaxCounters];
|
||||
};
|
||||
|
||||
|
||||
@ -65,6 +82,7 @@ class Shell: public i::AllStatic {
|
||||
static void Initialize();
|
||||
static void OnExit();
|
||||
static int* LookupCounter(const char* name);
|
||||
static void MapCounters(const char* name);
|
||||
static Handle<String> ReadFile(const char* name);
|
||||
static void RunShell();
|
||||
static int Main(int argc, char* argv[]);
|
||||
@ -83,6 +101,11 @@ class Shell: public i::AllStatic {
|
||||
static Persistent<Context> evaluation_context_;
|
||||
typedef std::map<const char*, Counter*> CounterMap;
|
||||
static CounterMap counter_map_;
|
||||
// We statically allocate a set of local counters to be used if we
|
||||
// don't want to store the stats in a memory-mapped file
|
||||
static CounterCollection local_counters_;
|
||||
static CounterCollection* counters_;
|
||||
static i::OS::MemoryMappedFile* counters_file_;
|
||||
};
|
||||
|
||||
|
||||
|
@ -226,6 +226,7 @@ DEFINE_string(testing_serialization_file, "/tmp/serdes",
|
||||
|
||||
DEFINE_bool(help, false, "Print usage message, including flags, on console")
|
||||
DEFINE_bool(dump_counters, false, "Dump counters on exit")
|
||||
DEFINE_string(map_counters, false, "Map counters to a file")
|
||||
DEFINE_args(js_arguments, JSArguments(),
|
||||
"Pass all remaining arguments to the script. Alias for \"--\".")
|
||||
|
||||
|
372
tools/stats-viewer.py
Executable file
372
tools/stats-viewer.py
Executable file
@ -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])
|
Loading…
Reference in New Issue
Block a user