| #!/usr/bin/python |
| # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Parse NetLog output and reformat it to display in Gnuplot.""" |
| |
| import math |
| import optparse |
| import os |
| import re |
| import sys |
| |
| |
| class Entry(object): |
| """A single NetLog line/entry.""" |
| |
| def __init__(self, line): |
| self.id = -1 |
| self.info = '' |
| self.time = 0 |
| |
| time_offset = line.find('t=') |
| if time_offset >= 0: |
| tmp = line[time_offset + 2:].split(None, 1) |
| self.time = tmp[0][:-3] + '.' + tmp[0][-3:] |
| if len(tmp) > 1: |
| self.info = tmp[1] |
| else: |
| self.info = line |
| self.info = re.sub('\[dt=[0-9 ?]*\]', '', self.info) |
| self.info = self.info.strip() |
| |
| |
| class Object(object): |
| """A set of Entries that belong to the same NetLog Object.""" |
| |
| def __init__(self, line, id_offset): |
| self.id = line[id_offset + 4:].rstrip(')') |
| self.type = line.split(None, 1)[0] |
| self.entries = [] |
| |
| def SpaceOutEntries(self): |
| self.__FindGroups(self.__SpaceRange) |
| |
| def SetRotation(self): |
| self.__FindGroups(self.__SetRotationRange) |
| |
| def __FindGroups(self, proc): |
| first = 0 |
| for i in range(len(self.entries)): |
| if self.entries[first].time != self.entries[i].time: |
| proc(first, i - 1) |
| first = i |
| proc(first, len(self.entries) - 1) |
| |
| def __SpaceRange(self, start, end): |
| if end > start: |
| gap = math.floor(100/(end - start + 2)) |
| for i in range(start + 1, end + 1): |
| self.entries[i].time += '%02d'%(gap * (i - start)) |
| |
| def __SetRotationRange(self, start, end): |
| for i in range(start, end + 1): |
| self.entries[i].rotation = 85 - (i - start) * 15 |
| |
| |
| def Parse(stream): |
| """Parse the NetLog into python objects.""" |
| |
| result = [] |
| request_section = 0 |
| obj = None |
| entry = None |
| |
| for line in stream: |
| line = line.strip() |
| if line.startswith('Requests'): |
| request_section = 1 |
| elif line.startswith('Http cache'): |
| request_section = 0 |
| |
| if request_section: |
| id_offset = line.find('(id=') |
| if id_offset >= 0: |
| obj = Object(line, id_offset) |
| result.append(obj) |
| elif line.startswith('(P) t=') or line.startswith('t='): |
| entry = Entry(line) |
| obj.entries.append(entry) |
| elif line.find('source_dependency') >= 0: |
| new_entry = Entry(line) |
| new_entry.time = entry.time |
| new_entry.id = line.split(':')[1].split(',')[0] |
| obj.entries.append(new_entry) |
| new_entry = Entry('t=%s '%entry.time.replace('.', '')) |
| obj.entries.append(new_entry) |
| elif line.startswith('-->'): |
| entry.info = entry.info + ' ' + line[4:] |
| |
| return result |
| |
| |
| def GnuplotRenderNetlog(netlog, filename, labels): |
| """Render a list of NetLog objects into Gnuplot format.""" |
| |
| output = open(filename, 'w') |
| |
| commands = [] |
| data = [] |
| types = [] |
| |
| commands.append('file="%s"'%filename) |
| commands.append('set key bottom') |
| commands.append('set xdata time') |
| commands.append('set timefmt "%s"') |
| commands.append('set datafile separator ","') |
| commands.append('set xlabel "Time (s)"') |
| commands.append('set ylabel "Netlog ID"') |
| |
| plot = [] |
| index = 1 |
| for obj in netlog: |
| if obj.type not in types: |
| types.append(obj.type) |
| type_num = types.index(obj.type) |
| plot.append('file index %d using 1:2 with linespoints lt %d notitle'%( |
| index, type_num)) |
| obj.SetRotation() |
| for entry in obj.entries: |
| graph_id = obj.id |
| if entry.id > 0: |
| graph_id = entry.id |
| data.append('%s,%s'%(entry.time, graph_id)) |
| info = entry.info.replace('"', '') |
| if info and labels: |
| commands.append('set label "%s" at "%s", %s rotate by %d'%( |
| info, entry.time, obj.id, entry.rotation)) |
| data.append('\n') |
| index += 1 |
| |
| for entry_type in types: |
| plot.insert(0, '1/0 lt %d title "%s"'%(types.index(entry_type), entry_type)) |
| commands.append('plot ' + ','.join(plot)) |
| commands.append('exit') |
| result = '\n'.join(commands) + '\n\n\n' + '\n'.join(data) |
| |
| output.write(result) |
| output.close() |
| os.system('gnuplot %s -'%filename) |
| |
| |
| def main(_): |
| parser = optparse.OptionParser('usage: %prog [options] dump_file') |
| parser.add_option_group( |
| optparse.OptionGroup(parser, 'Additional Info', |
| 'When run, the script will generate a file that can ' |
| 'be passed to gnuplot, but will also start gnuplot ' |
| 'for you; left click selects a zoom region, u ' |
| 'unzooms, middle click adds a marker, and q quits.')) |
| parser.add_option('-o', '--output', dest='output', help='Output filename') |
| parser.add_option('-l', '--labels', action='store_true', help='Output labels') |
| options, args = parser.parse_args() |
| if not args: |
| parser.error('Must specify input dump_file.') |
| if not options.output: |
| options.output = args[0] + '.gnuplot' |
| |
| netlog = Parse(open(args[0])) |
| GnuplotRenderNetlog(netlog, options.output, options.labels) |
| return 0 |
| |
| |
| if '__main__' == __name__: |
| ret = main(sys.argv) |
| sys.exit(ret) |
| |