| Index: chrome/test/functional/perf/endure_result_parser.py
|
| ===================================================================
|
| --- chrome/test/functional/perf/endure_result_parser.py (revision 261231)
|
| +++ chrome/test/functional/perf/endure_result_parser.py (working copy)
|
| @@ -1,824 +0,0 @@
|
| -#!/usr/bin/env python
|
| -# Copyright (c) 2012 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.
|
| -
|
| -"""Script to parse perf data from Chrome Endure test executions, to be graphed.
|
| -
|
| -This script connects via HTTP to a buildbot master in order to scrape and parse
|
| -perf data from Chrome Endure tests that have been run. The perf data is then
|
| -stored in local text files to be graphed by the Chrome Endure graphing code.
|
| -
|
| -It is assumed that any Chrome Endure tests that show up on the waterfall have
|
| -names that are of the following form:
|
| -
|
| -"endure_<webapp_name>-<test_name>"
|
| -
|
| -This script accepts either a URL or a local path as a buildbot location.
|
| -It switches its behavior if a URL is given, or a local path is given.
|
| -
|
| -When a URL is given, it gets buildbot logs from the buildbot builders URL
|
| -e.g. http://build.chromium.org/p/chromium.endure/builders/.
|
| -
|
| -When a local path is given, it gets buildbot logs from buildbot's internal
|
| -files in the directory e.g. /home/chrome-bot/buildbot.
|
| -"""
|
| -
|
| -import cPickle
|
| -import getpass
|
| -import logging
|
| -import optparse
|
| -import os
|
| -import re
|
| -import simplejson
|
| -import socket
|
| -import string
|
| -import sys
|
| -import time
|
| -import urllib
|
| -import urllib2
|
| -
|
| -
|
| -CHROME_ENDURE_SLAVE_NAMES = [
|
| - 'Linux QA Perf (0)',
|
| - 'Linux QA Perf (1)',
|
| - 'Linux QA Perf (2)',
|
| - 'Linux QA Perf (3)',
|
| - 'Linux QA Perf (4)',
|
| - 'Linux QA Perf (dbg)(0)',
|
| - 'Linux QA Perf (dbg)(1)',
|
| - 'Linux QA Perf (dbg)(2)',
|
| - 'Linux QA Perf (dbg)(3)',
|
| - 'Linux QA Perf (dbg)(4)',
|
| -]
|
| -
|
| -BUILDER_URL_BASE = 'http://build.chromium.org/p/chromium.endure/builders/'
|
| -LAST_BUILD_NUM_PROCESSED_FILE = os.path.join(os.path.dirname(__file__),
|
| - '_parser_last_processed.txt')
|
| -LOCAL_GRAPH_DIR = '/home/%s/www/chrome_endure_clean' % getpass.getuser()
|
| -MANGLE_TRANSLATION = string.maketrans(' ()', '___')
|
| -
|
| -def SetupBaseGraphDirIfNeeded(webapp_name, test_name, dest_dir):
|
| - """Sets up the directory containing results for a particular test, if needed.
|
| -
|
| - Args:
|
| - webapp_name: The string name of the webapp associated with the given test.
|
| - test_name: The string name of the test.
|
| - dest_dir: The name of the destination directory that needs to be set up.
|
| - """
|
| - if not os.path.exists(dest_dir):
|
| - os.mkdir(dest_dir) # Test name directory.
|
| - os.chmod(dest_dir, 0755)
|
| -
|
| - # Create config file.
|
| - config_file = os.path.join(dest_dir, 'config.js')
|
| - if not os.path.exists(config_file):
|
| - with open(config_file, 'w') as f:
|
| - f.write('var Config = {\n')
|
| - f.write('buildslave: "Chrome Endure Bots",\n')
|
| - f.write('title: "Chrome Endure %s Test: %s",\n' % (webapp_name.upper(),
|
| - test_name))
|
| - f.write('};\n')
|
| - os.chmod(config_file, 0755)
|
| -
|
| - # Set up symbolic links to the real graphing files.
|
| - link_file = os.path.join(dest_dir, 'index.html')
|
| - if not os.path.exists(link_file):
|
| - os.symlink('../../endure_plotter.html', link_file)
|
| - link_file = os.path.join(dest_dir, 'endure_plotter.js')
|
| - if not os.path.exists(link_file):
|
| - os.symlink('../../endure_plotter.js', link_file)
|
| - link_file = os.path.join(dest_dir, 'js')
|
| - if not os.path.exists(link_file):
|
| - os.symlink('../../js', link_file)
|
| -
|
| -
|
| -def WriteToDataFile(new_line, existing_lines, revision, data_file):
|
| - """Writes a new entry to an existing perf data file to be graphed.
|
| -
|
| - If there's an existing line with the same revision number, overwrite its data
|
| - with the new line. Else, prepend the info for the new revision.
|
| -
|
| - Args:
|
| - new_line: A dictionary representing perf information for the new entry.
|
| - existing_lines: A list of string lines from the existing perf data file.
|
| - revision: The string revision number associated with the new perf entry.
|
| - data_file: The string name of the perf data file to which to write.
|
| - """
|
| - overwritten = False
|
| - for i, line in enumerate(existing_lines):
|
| - line_dict = simplejson.loads(line)
|
| - if line_dict['rev'] == revision:
|
| - existing_lines[i] = simplejson.dumps(new_line)
|
| - overwritten = True
|
| - break
|
| - elif int(line_dict['rev']) < int(revision):
|
| - break
|
| - if not overwritten:
|
| - existing_lines.insert(0, simplejson.dumps(new_line))
|
| -
|
| - with open(data_file, 'w') as f:
|
| - f.write('\n'.join(existing_lines))
|
| - os.chmod(data_file, 0755)
|
| -
|
| -
|
| -def OutputPerfData(revision, graph_name, values, units, units_x, dest_dir,
|
| - is_stacked=False, stack_order=[]):
|
| - """Outputs perf data to a local text file to be graphed.
|
| -
|
| - Args:
|
| - revision: The string revision number associated with the perf data.
|
| - graph_name: The string name of the graph on which to plot the data.
|
| - values: A dict which maps a description to a value. A value is either a
|
| - single data value to be graphed, or a list of 2-tuples
|
| - representing (x, y) points to be graphed for long-running tests.
|
| - units: The string description for the y-axis units on the graph.
|
| - units_x: The string description for the x-axis units on the graph. Should
|
| - be set to None if the results are not for long-running graphs.
|
| - dest_dir: The name of the destination directory to which to write.
|
| - is_stacked: True to draw a "stacked" graph. First-come values are
|
| - stacked at bottom by default.
|
| - stack_order: A list that contains key strings in the order to stack values
|
| - in the graph.
|
| - """
|
| - # Update graphs.dat, which contains metadata associated with each graph.
|
| - existing_graphs = []
|
| - graphs_file = os.path.join(dest_dir, 'graphs.dat')
|
| - if os.path.exists(graphs_file):
|
| - with open(graphs_file, 'r') as f:
|
| - existing_graphs = simplejson.loads(f.read())
|
| - is_new_graph = True
|
| - for graph in existing_graphs:
|
| - if graph['name'] == graph_name:
|
| - is_new_graph = False
|
| - break
|
| - if is_new_graph:
|
| - new_graph = {
|
| - 'name': graph_name,
|
| - 'units': units,
|
| - 'important': False,
|
| - }
|
| - if units_x:
|
| - new_graph['units_x'] = units_x
|
| - existing_graphs.append(new_graph)
|
| - existing_graphs = sorted(existing_graphs, key=lambda x: x['name'])
|
| - with open(graphs_file, 'w') as f:
|
| - f.write(simplejson.dumps(existing_graphs, indent=2))
|
| - os.chmod(graphs_file, 0755)
|
| -
|
| - # Update summary data file, containing the actual data to be graphed.
|
| - data_file_name = graph_name + '-summary.dat'
|
| - existing_lines = []
|
| - data_file = os.path.join(dest_dir, data_file_name)
|
| - if os.path.exists(data_file):
|
| - with open(data_file, 'r') as f:
|
| - existing_lines = f.readlines()
|
| - existing_lines = map(lambda x: x.strip(), existing_lines)
|
| - new_traces = {}
|
| - for description in values:
|
| - value = values[description]
|
| - if units_x:
|
| - points = []
|
| - for point in value:
|
| - points.append([str(point[0]), str(point[1])])
|
| - new_traces[description] = points
|
| - else:
|
| - new_traces[description] = [str(value), str(0.0)]
|
| - new_line = {
|
| - 'traces': new_traces,
|
| - 'rev': revision
|
| - }
|
| - if is_stacked:
|
| - new_line['stack'] = True
|
| - new_line['stack_order'] = stack_order
|
| -
|
| - WriteToDataFile(new_line, existing_lines, revision, data_file)
|
| -
|
| -
|
| -def OutputEventData(revision, event_dict, dest_dir):
|
| - """Outputs event data to a local text file to be graphed.
|
| -
|
| - Args:
|
| - revision: The string revision number associated with the event data.
|
| - event_dict: A dict which maps a description to an array of tuples
|
| - representing event data to be graphed.
|
| - dest_dir: The name of the destination directory to which to write.
|
| - """
|
| - data_file_name = '_EVENT_-summary.dat'
|
| - existing_lines = []
|
| - data_file = os.path.join(dest_dir, data_file_name)
|
| - if os.path.exists(data_file):
|
| - with open(data_file, 'r') as f:
|
| - existing_lines = f.readlines()
|
| - existing_lines = map(lambda x: x.strip(), existing_lines)
|
| -
|
| - new_events = {}
|
| - for description in event_dict:
|
| - event_list = event_dict[description]
|
| - value_list = []
|
| - for event_time, event_data in event_list:
|
| - value_list.append([str(event_time), event_data])
|
| - new_events[description] = value_list
|
| -
|
| - new_line = {
|
| - 'rev': revision,
|
| - 'events': new_events
|
| - }
|
| -
|
| - WriteToDataFile(new_line, existing_lines, revision, data_file)
|
| -
|
| -
|
| -def UpdatePerfDataFromFetchedContent(
|
| - revision, content, webapp_name, test_name, graph_dir, only_dmp=False):
|
| - """Update perf data from fetched stdio data.
|
| -
|
| - Args:
|
| - revision: The string revision number associated with the new perf entry.
|
| - content: Fetched stdio data.
|
| - webapp_name: A name of the webapp.
|
| - test_name: A name of the test.
|
| - graph_dir: A path to the graph directory.
|
| - only_dmp: True if only Deep Memory Profiler results should be used.
|
| - """
|
| - perf_data_raw = []
|
| -
|
| - def AppendRawPerfData(graph_name, description, value, units, units_x,
|
| - webapp_name, test_name, is_stacked=False):
|
| - perf_data_raw.append({
|
| - 'graph_name': graph_name,
|
| - 'description': description,
|
| - 'value': value,
|
| - 'units': units,
|
| - 'units_x': units_x,
|
| - 'webapp_name': webapp_name,
|
| - 'test_name': test_name,
|
| - 'stack': is_stacked,
|
| - })
|
| -
|
| - # First scan for short-running perf test results.
|
| - for match in re.findall(
|
| - r'RESULT ([^:]+): ([^=]+)= ([-\d\.]+) (\S+)', content):
|
| - if (not only_dmp) or match[0].endswith('-DMP'):
|
| - try:
|
| - match2 = eval(match[2])
|
| - except SyntaxError:
|
| - match2 = None
|
| - if match2:
|
| - AppendRawPerfData(match[0], match[1], match2, match[3], None,
|
| - webapp_name, webapp_name)
|
| -
|
| - # Next scan for long-running perf test results.
|
| - for match in re.findall(
|
| - r'RESULT ([^:]+): ([^=]+)= (\[[^\]]+\]) (\S+) (\S+)', content):
|
| - if (not only_dmp) or match[0].endswith('-DMP'):
|
| - try:
|
| - match2 = eval(match[2])
|
| - except SyntaxError:
|
| - match2 = None
|
| - # TODO(dmikurube): Change the condition to use stacked graph when we
|
| - # determine how to specify it.
|
| - if match2:
|
| - AppendRawPerfData(match[0], match[1], match2, match[3], match[4],
|
| - webapp_name, test_name, match[0].endswith('-DMP'))
|
| -
|
| - # Next scan for events in the test results.
|
| - for match in re.findall(
|
| - r'RESULT _EVENT_: ([^=]+)= (\[[^\]]+\])', content):
|
| - try:
|
| - match1 = eval(match[1])
|
| - except SyntaxError:
|
| - match1 = None
|
| - if match1:
|
| - AppendRawPerfData('_EVENT_', match[0], match1, None, None,
|
| - webapp_name, test_name)
|
| -
|
| - # For each graph_name/description pair that refers to a long-running test
|
| - # result or an event, concatenate all the results together (assume results
|
| - # in the input file are in the correct order). For short-running test
|
| - # results, keep just one if more than one is specified.
|
| - perf_data = {} # Maps a graph-line key to a perf data dictionary.
|
| - for data in perf_data_raw:
|
| - key_graph = data['graph_name']
|
| - key_description = data['description']
|
| - if not key_graph in perf_data:
|
| - perf_data[key_graph] = {
|
| - 'graph_name': data['graph_name'],
|
| - 'value': {},
|
| - 'units': data['units'],
|
| - 'units_x': data['units_x'],
|
| - 'webapp_name': data['webapp_name'],
|
| - 'test_name': data['test_name'],
|
| - }
|
| - perf_data[key_graph]['stack'] = data['stack']
|
| - if 'stack_order' not in perf_data[key_graph]:
|
| - perf_data[key_graph]['stack_order'] = []
|
| - if (data['stack'] and
|
| - data['description'] not in perf_data[key_graph]['stack_order']):
|
| - perf_data[key_graph]['stack_order'].append(data['description'])
|
| -
|
| - if data['graph_name'] != '_EVENT_' and not data['units_x']:
|
| - # Short-running test result.
|
| - perf_data[key_graph]['value'][key_description] = data['value']
|
| - else:
|
| - # Long-running test result or event.
|
| - if key_description in perf_data[key_graph]['value']:
|
| - perf_data[key_graph]['value'][key_description] += data['value']
|
| - else:
|
| - perf_data[key_graph]['value'][key_description] = data['value']
|
| -
|
| - # Finally, for each graph-line in |perf_data|, update the associated local
|
| - # graph data files if necessary.
|
| - for perf_data_key in perf_data:
|
| - perf_data_dict = perf_data[perf_data_key]
|
| -
|
| - dest_dir = os.path.join(graph_dir, perf_data_dict['webapp_name'])
|
| - if not os.path.exists(dest_dir):
|
| - os.mkdir(dest_dir) # Webapp name directory.
|
| - os.chmod(dest_dir, 0755)
|
| - dest_dir = os.path.join(dest_dir, perf_data_dict['test_name'])
|
| -
|
| - SetupBaseGraphDirIfNeeded(perf_data_dict['webapp_name'],
|
| - perf_data_dict['test_name'], dest_dir)
|
| - if perf_data_dict['graph_name'] == '_EVENT_':
|
| - OutputEventData(revision, perf_data_dict['value'], dest_dir)
|
| - else:
|
| - OutputPerfData(revision, perf_data_dict['graph_name'],
|
| - perf_data_dict['value'],
|
| - perf_data_dict['units'], perf_data_dict['units_x'],
|
| - dest_dir,
|
| - perf_data_dict['stack'], perf_data_dict['stack_order'])
|
| -
|
| -
|
| -def SlaveLocation(master_location, slave_info):
|
| - """Returns slave location for |master_location| and |slave_info|."""
|
| - if master_location.startswith('http://'):
|
| - return master_location + urllib.quote(slave_info['slave_name'])
|
| - else:
|
| - return os.path.join(master_location,
|
| - slave_info['slave_name'].translate(MANGLE_TRANSLATION))
|
| -
|
| -
|
| -def GetRevisionAndLogs(slave_location, build_num):
|
| - """Get a revision number and log locations.
|
| -
|
| - Args:
|
| - slave_location: A URL or a path to the build slave data.
|
| - build_num: A build number.
|
| -
|
| - Returns:
|
| - A pair of the revision number and a list of strings that contain locations
|
| - of logs. (False, []) in case of error.
|
| - """
|
| - if slave_location.startswith('http://'):
|
| - location = slave_location + '/builds/' + str(build_num)
|
| - else:
|
| - location = os.path.join(slave_location, str(build_num))
|
| -
|
| - revision = False
|
| - logs = []
|
| - fp = None
|
| - try:
|
| - if location.startswith('http://'):
|
| - fp = urllib2.urlopen(location)
|
| - contents = fp.read()
|
| - revisions = re.findall(r'<td class="left">got_revision</td>\s+'
|
| - '<td>(\d+)</td>\s+<td>Source</td>', contents)
|
| - if revisions:
|
| - revision = revisions[0]
|
| - logs = [location + link + '/text' for link
|
| - in re.findall(r'(/steps/endure[^/]+/logs/stdio)', contents)]
|
| - else:
|
| - fp = open(location, 'rb')
|
| - build = cPickle.load(fp)
|
| - properties = build.getProperties()
|
| - if properties.has_key('got_revision'):
|
| - revision = build.getProperty('got_revision')
|
| - candidates = os.listdir(slave_location)
|
| - logs = [os.path.join(slave_location, filename)
|
| - for filename in candidates
|
| - if re.match(r'%d-log-endure[^/]+-stdio' % build_num, filename)]
|
| -
|
| - except urllib2.URLError, e:
|
| - logging.exception('Error reading build URL "%s": %s', location, str(e))
|
| - return False, []
|
| - except (IOError, OSError), e:
|
| - logging.exception('Error reading build file "%s": %s', location, str(e))
|
| - return False, []
|
| - finally:
|
| - if fp:
|
| - fp.close()
|
| -
|
| - return revision, logs
|
| -
|
| -
|
| -def ExtractTestNames(log_location, is_dbg):
|
| - """Extract test names from |log_location|.
|
| -
|
| - Returns:
|
| - A dict of a log location, webapp's name and test's name. False if error.
|
| - """
|
| - if log_location.startswith('http://'):
|
| - location = urllib.unquote(log_location)
|
| - test_pattern = r'endure_([^_]+)(_test |-)([^/]+)/'
|
| - else:
|
| - location = log_location
|
| - test_pattern = r'endure_([^_]+)(_test_|-)([^/]+)-stdio'
|
| -
|
| - match = match[0]
|
| - webapp_name = match[0]
|
| - webapp_name = webapp_name + '_dbg' if is_dbg else webapp_name
|
| - test_name = match[2]
|
| -
|
| - return {
|
| - 'location': log_location,
|
| - 'webapp_name': webapp_name,
|
| - 'test_name': test_name,
|
| - }
|
| -
|
| -
|
| -def GetStdioContents(stdio_location):
|
| - """Gets appropriate stdio contents.
|
| -
|
| - Returns:
|
| - A content string of the stdio log. None in case of error.
|
| - """
|
| - fp = None
|
| - contents = ''
|
| - try:
|
| - if stdio_location.startswith('http://'):
|
| - fp = urllib2.urlopen(stdio_location, timeout=60)
|
| - # Since in-progress test output is sent chunked, there's no EOF. We need
|
| - # to specially handle this case so we don't hang here waiting for the
|
| - # test to complete.
|
| - start_time = time.time()
|
| - while True:
|
| - data = fp.read(1024)
|
| - if not data:
|
| - break
|
| - contents += data
|
| - if time.time() - start_time >= 30: # Read for at most 30 seconds.
|
| - break
|
| - else:
|
| - fp = open(stdio_location)
|
| - data = fp.read()
|
| - contents = ''
|
| - index = 0
|
| -
|
| - # Buildbot log files are stored in the netstring format.
|
| - # http://en.wikipedia.org/wiki/Netstring
|
| - while index < len(data):
|
| - index2 = index
|
| - while data[index2].isdigit():
|
| - index2 += 1
|
| - if data[index2] != ':':
|
| - logging.error('Log file is not in expected format: %s' %
|
| - stdio_location)
|
| - contents = None
|
| - break
|
| - length = int(data[index:index2])
|
| - index = index2 + 1
|
| - channel = int(data[index])
|
| - index += 1
|
| - if data[index+length-1] != ',':
|
| - logging.error('Log file is not in expected format: %s' %
|
| - stdio_location)
|
| - contents = None
|
| - break
|
| - if channel == 0:
|
| - contents += data[index:(index+length-1)]
|
| - index += length
|
| -
|
| - except (urllib2.URLError, socket.error, IOError, OSError), e:
|
| - # Issue warning but continue to the next stdio link.
|
| - logging.warning('Error reading test stdio data "%s": %s',
|
| - stdio_location, str(e))
|
| - finally:
|
| - if fp:
|
| - fp.close()
|
| -
|
| - return contents
|
| -
|
| -
|
| -def UpdatePerfDataForSlaveAndBuild(
|
| - slave_info, build_num, graph_dir, master_location):
|
| - """Process updated perf data for a particular slave and build number.
|
| -
|
| - Args:
|
| - slave_info: A dictionary containing information about the slave to process.
|
| - build_num: The particular build number on the slave to process.
|
| - graph_dir: A path to the graph directory.
|
| - master_location: A URL or a path to the build master data.
|
| -
|
| - Returns:
|
| - True if the perf data for the given slave/build is updated properly, or
|
| - False if any critical error occurred.
|
| - """
|
| - if not master_location.startswith('http://'):
|
| - # Source is a file.
|
| - from buildbot.status import builder
|
| -
|
| - slave_location = SlaveLocation(master_location, slave_info)
|
| - logging.debug(' %s, build %d.', slave_info['slave_name'], build_num)
|
| - is_dbg = '(dbg)' in slave_info['slave_name']
|
| -
|
| - revision, logs = GetRevisionAndLogs(slave_location, build_num)
|
| - if not revision:
|
| - return False
|
| -
|
| - stdios = []
|
| - for log_location in logs:
|
| - stdio = ExtractTestNames(log_location, is_dbg)
|
| - if not stdio:
|
| - return False
|
| - stdios.append(stdio)
|
| -
|
| - for stdio in stdios:
|
| - stdio_location = stdio['location']
|
| - contents = GetStdioContents(stdio_location)
|
| -
|
| - if contents:
|
| - UpdatePerfDataFromFetchedContent(revision, contents,
|
| - stdio['webapp_name'],
|
| - stdio['test_name'],
|
| - graph_dir, is_dbg)
|
| -
|
| - return True
|
| -
|
| -
|
| -def GetMostRecentBuildNum(master_location, slave_name):
|
| - """Gets the most recent buld number for |slave_name| in |master_location|."""
|
| - most_recent_build_num = None
|
| -
|
| - if master_location.startswith('http://'):
|
| - slave_url = master_location + urllib.quote(slave_name)
|
| -
|
| - url_contents = ''
|
| - fp = None
|
| - try:
|
| - fp = urllib2.urlopen(slave_url, timeout=60)
|
| - url_contents = fp.read()
|
| - except urllib2.URLError, e:
|
| - logging.exception('Error reading builder URL: %s', str(e))
|
| - return None
|
| - finally:
|
| - if fp:
|
| - fp.close()
|
| -
|
| - matches = re.findall(r'/(\d+)/stop', url_contents)
|
| - if matches:
|
| - most_recent_build_num = int(matches[0])
|
| - else:
|
| - matches = re.findall(r'#(\d+)</a></td>', url_contents)
|
| - if matches:
|
| - most_recent_build_num = sorted(map(int, matches), reverse=True)[0]
|
| -
|
| - else:
|
| - slave_path = os.path.join(master_location,
|
| - slave_name.translate(MANGLE_TRANSLATION))
|
| - files = os.listdir(slave_path)
|
| - number_files = [int(filename) for filename in files if filename.isdigit()]
|
| - if number_files:
|
| - most_recent_build_num = sorted(number_files, reverse=True)[0]
|
| -
|
| - if most_recent_build_num:
|
| - logging.debug('%s most recent build number: %s',
|
| - slave_name, most_recent_build_num)
|
| - else:
|
| - logging.error('Could not identify latest build number for slave %s.',
|
| - slave_name)
|
| -
|
| - return most_recent_build_num
|
| -
|
| -
|
| -def UpdatePerfDataFiles(graph_dir, master_location):
|
| - """Updates the Chrome Endure graph data files with the latest test results.
|
| -
|
| - For each known Chrome Endure slave, we scan its latest test results looking
|
| - for any new test data. Any new data that is found is then appended to the
|
| - data files used to display the Chrome Endure graphs.
|
| -
|
| - Args:
|
| - graph_dir: A path to the graph directory.
|
| - master_location: A URL or a path to the build master data.
|
| -
|
| - Returns:
|
| - True if all graph data files are updated properly, or
|
| - False if any error occurred.
|
| - """
|
| - slave_list = []
|
| - for slave_name in CHROME_ENDURE_SLAVE_NAMES:
|
| - slave_info = {}
|
| - slave_info['slave_name'] = slave_name
|
| - slave_info['most_recent_build_num'] = None
|
| - slave_info['last_processed_build_num'] = None
|
| - slave_list.append(slave_info)
|
| -
|
| - # Identify the most recent build number for each slave.
|
| - logging.debug('Searching for latest build numbers for each slave...')
|
| - for slave in slave_list:
|
| - slave_name = slave['slave_name']
|
| - slave['most_recent_build_num'] = GetMostRecentBuildNum(
|
| - master_location, slave_name)
|
| -
|
| - # Identify the last-processed build number for each slave.
|
| - logging.debug('Identifying last processed build numbers...')
|
| - if not os.path.exists(LAST_BUILD_NUM_PROCESSED_FILE):
|
| - for slave_info in slave_list:
|
| - slave_info['last_processed_build_num'] = 0
|
| - else:
|
| - with open(LAST_BUILD_NUM_PROCESSED_FILE, 'r') as fp:
|
| - file_contents = fp.read()
|
| - for match in re.findall(r'([^:]+):(\d+)', file_contents):
|
| - slave_name = match[0].strip()
|
| - last_processed_build_num = match[1].strip()
|
| - for slave_info in slave_list:
|
| - if slave_info['slave_name'] == slave_name:
|
| - slave_info['last_processed_build_num'] = int(
|
| - last_processed_build_num)
|
| - for slave_info in slave_list:
|
| - if not slave_info['last_processed_build_num']:
|
| - slave_info['last_processed_build_num'] = 0
|
| - logging.debug('Done identifying last processed build numbers.')
|
| -
|
| - # For each Chrome Endure slave, process each build in-between the last
|
| - # processed build num and the most recent build num, inclusive. To process
|
| - # each one, first get the revision number for that build, then scan the test
|
| - # result stdio for any performance data, and add any new performance data to
|
| - # local files to be graphed.
|
| - for slave_info in slave_list:
|
| - logging.debug('Processing %s, builds %d-%d...',
|
| - slave_info['slave_name'],
|
| - slave_info['last_processed_build_num'],
|
| - slave_info['most_recent_build_num'])
|
| - curr_build_num = slave_info['last_processed_build_num']
|
| - while curr_build_num <= slave_info['most_recent_build_num']:
|
| - if not UpdatePerfDataForSlaveAndBuild(slave_info, curr_build_num,
|
| - graph_dir, master_location):
|
| - # Do not give up. The first files might be removed by buildbot.
|
| - logging.warning('Logs do not exist in buildbot for #%d of %s.' %
|
| - (curr_build_num, slave_info['slave_name']))
|
| - curr_build_num += 1
|
| -
|
| - # Log the newly-processed build numbers.
|
| - logging.debug('Logging the newly-processed build numbers...')
|
| - with open(LAST_BUILD_NUM_PROCESSED_FILE, 'w') as f:
|
| - for slave_info in slave_list:
|
| - f.write('%s:%s\n' % (slave_info['slave_name'],
|
| - slave_info['most_recent_build_num']))
|
| -
|
| - return True
|
| -
|
| -
|
| -def GenerateIndexPage(graph_dir):
|
| - """Generates a summary (landing) page for the Chrome Endure graphs.
|
| -
|
| - Args:
|
| - graph_dir: A path to the graph directory.
|
| - """
|
| - logging.debug('Generating new index.html page...')
|
| -
|
| - # Page header.
|
| - page = """
|
| - <html>
|
| -
|
| - <head>
|
| - <title>Chrome Endure Overview</title>
|
| - <script language="javascript">
|
| - function DisplayGraph(name, graph) {
|
| - document.write(
|
| - '<td><iframe scrolling="no" height="438" width="700" src="');
|
| - document.write(name);
|
| - document.write('"></iframe></td>');
|
| - }
|
| - </script>
|
| - </head>
|
| -
|
| - <body>
|
| - <center>
|
| -
|
| - <h1>
|
| - Chrome Endure
|
| - </h1>
|
| - """
|
| - # Print current time.
|
| - page += '<p>Updated: %s</p>\n' % (
|
| - time.strftime('%A, %B %d, %Y at %I:%M:%S %p %Z'))
|
| -
|
| - # Links for each webapp.
|
| - webapp_names = [x for x in os.listdir(graph_dir) if
|
| - x not in ['js', 'old_data', '.svn', '.git'] and
|
| - os.path.isdir(os.path.join(graph_dir, x))]
|
| - webapp_names = sorted(webapp_names)
|
| -
|
| - page += '<p> ['
|
| - for i, name in enumerate(webapp_names):
|
| - page += '<a href="#%s">%s</a>' % (name.upper(), name.upper())
|
| - if i < len(webapp_names) - 1:
|
| - page += ' | '
|
| - page += '] </p>\n'
|
| -
|
| - # Print out the data for each webapp.
|
| - for webapp_name in webapp_names:
|
| - page += '\n<h1 id="%s">%s</h1>\n' % (webapp_name.upper(),
|
| - webapp_name.upper())
|
| -
|
| - # Links for each test for this webapp.
|
| - test_names = [x for x in
|
| - os.listdir(os.path.join(graph_dir, webapp_name))]
|
| - test_names = sorted(test_names)
|
| -
|
| - page += '<p> ['
|
| - for i, name in enumerate(test_names):
|
| - page += '<a href="#%s">%s</a>' % (name, name)
|
| - if i < len(test_names) - 1:
|
| - page += ' | '
|
| - page += '] </p>\n'
|
| -
|
| - # Print out the data for each test for this webapp.
|
| - for test_name in test_names:
|
| - # Get the set of graph names for this test.
|
| - graph_names = [x[:x.find('-summary.dat')] for x in
|
| - os.listdir(os.path.join(graph_dir,
|
| - webapp_name, test_name))
|
| - if '-summary.dat' in x and '_EVENT_' not in x]
|
| - graph_names = sorted(graph_names)
|
| -
|
| - page += '<h2 id="%s">%s</h2>\n' % (test_name, test_name)
|
| - page += '<table>\n'
|
| -
|
| - for i, graph_name in enumerate(graph_names):
|
| - if i % 2 == 0:
|
| - page += ' <tr>\n'
|
| - page += (' <script>DisplayGraph("%s/%s?graph=%s&lookout=1");'
|
| - '</script>\n' % (webapp_name, test_name, graph_name))
|
| - if i % 2 == 1:
|
| - page += ' </tr>\n'
|
| - if len(graph_names) % 2 == 1:
|
| - page += ' </tr>\n'
|
| - page += '</table>\n'
|
| -
|
| - # Page footer.
|
| - page += """
|
| - </center>
|
| - </body>
|
| -
|
| - </html>
|
| - """
|
| -
|
| - index_file = os.path.join(graph_dir, 'index.html')
|
| - with open(index_file, 'w') as f:
|
| - f.write(page)
|
| - os.chmod(index_file, 0755)
|
| -
|
| -
|
| -def main():
|
| - parser = optparse.OptionParser()
|
| - parser.add_option(
|
| - '-v', '--verbose', action='store_true', default=False,
|
| - help='Use verbose logging.')
|
| - parser.add_option(
|
| - '-s', '--stdin', action='store_true', default=False,
|
| - help='Input from stdin instead of slaves for testing this script.')
|
| - parser.add_option(
|
| - '-b', '--buildbot', dest='buildbot', metavar="BUILDBOT",
|
| - default=BUILDER_URL_BASE,
|
| - help='Use log files in a buildbot at BUILDBOT. BUILDBOT can be a '
|
| - 'buildbot\'s builder URL or a local path to a buildbot directory. '
|
| - 'Both an absolute path and a relative path are available, e.g. '
|
| - '"/home/chrome-bot/buildbot" or "../buildbot". '
|
| - '[default: %default]')
|
| - parser.add_option(
|
| - '-g', '--graph', dest='graph_dir', metavar="DIR", default=LOCAL_GRAPH_DIR,
|
| - help='Output graph data files to DIR. [default: %default]')
|
| - options, _ = parser.parse_args(sys.argv)
|
| -
|
| - logging_level = logging.DEBUG if options.verbose else logging.INFO
|
| - logging.basicConfig(level=logging_level,
|
| - format='[%(asctime)s] %(levelname)s: %(message)s')
|
| -
|
| - if options.stdin:
|
| - content = sys.stdin.read()
|
| - UpdatePerfDataFromFetchedContent(
|
| - '12345', content, 'webapp', 'test', options.graph_dir)
|
| - else:
|
| - if options.buildbot.startswith('http://'):
|
| - master_location = options.buildbot
|
| - else:
|
| - build_dir = os.path.join(options.buildbot, 'build')
|
| - third_party_dir = os.path.join(build_dir, 'third_party')
|
| - sys.path.append(third_party_dir)
|
| - sys.path.append(os.path.join(third_party_dir, 'buildbot_8_4p1'))
|
| - sys.path.append(os.path.join(third_party_dir, 'twisted_10_2'))
|
| - master_location = os.path.join(build_dir, 'masters',
|
| - 'master.chromium.endure')
|
| - success = UpdatePerfDataFiles(options.graph_dir, master_location)
|
| - if not success:
|
| - logging.error('Failed to update perf data files.')
|
| - sys.exit(0)
|
| -
|
| - GenerateIndexPage(options.graph_dir)
|
| - logging.debug('All done!')
|
| -
|
| -
|
| -if __name__ == '__main__':
|
| - main()
|
|
|