#!/usr/bin/env python # Copyright (c) 2011 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__: sys.exit(main(sys.argv))