Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(965)

Side by Side Diff: tools/stats-viewer.py

Issue 13050: Live counters in d8 (Closed)
Patch Set: Created 12 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/flag-definitions.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # 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])
OLDNEW
« no previous file with comments | « src/flag-definitions.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698