blob: 09da51da1f5d9748714ef953c40e03910930d34d [file] [log] [blame]
# Copyright 2019 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.
from __future__ import print_function
import json
import os
import sys
import shutil
import subprocess
import tempfile
CHROMIUM_PATH = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..')
TOOLS_PERF_PATH = os.path.join(CHROMIUM_PATH, 'tools', 'perf')
sys.path.insert(1, TOOLS_PERF_PATH)
from core.external_modules import pandas
RUNS_USED_FOR_LIMIT_UPDATE = 30
CHANGE_PERCENTAGE_LIMIT = 0.01
SWARMING_PATH = os.path.join(
CHROMIUM_PATH, 'tools', 'swarming_client', 'swarming.py')
UPPER_LIMITS_DATA_DIR = os.path.join(
CHROMIUM_PATH, 'testing', 'scripts', 'representative_perf_test_data')
def FetchItemIds(tags, limit):
"""Fetches the item id of tasks described by the tags.
Args:
tags: The tags which describe the task such as OS and buildername.
limit: The number of runs to look at.
Returns:
A list containing the item Id of the tasks.
"""
swarming_attributes = (
'tasks/list?tags=name:rendering_representative_perf_tests&tags=os:{os}'
'&tags=buildername:{buildername}&tags=master:chromium.gpu.fyi&state='
'COMPLETED&fields=cursor,items(task_id)').format(**tags)
query = [
SWARMING_PATH, 'query', '-S', 'chromium-swarm.appspot.com', '--limit',
str(limit), swarming_attributes]
output = json.loads(subprocess.check_output(query))
return output.get('items')
def FetchItemData(task_id, benchmark, index, temp_dir):
"""Fetches the performance values (AVG & CI ranges) of tasks.
Args:
task_id: The list of item Ids to fetch dat for.
benchmark: The benchmark these task are on (desktop/mobile).
index: The index field of the data_frame
temp_dir: The temp directory to store task data in.
Returns:
A data_frame containing the averages and confidence interval ranges.
"""
output_directory = os.path.abspath(
os.path.join(temp_dir, task_id))
query = [
SWARMING_PATH, 'collect', '-S', 'chromium-swarm.appspot.com',
'--task-output-dir', output_directory, task_id]
try:
subprocess.check_output(query)
except Exception as e:
print(e)
result_file_path = os.path.join(
output_directory, '0', 'rendering.' + benchmark, 'perf_results.csv')
try:
df = pandas.read_csv(result_file_path)
df = df.loc[df['name'] == 'frame_times']
df = df[['stories', 'avg', 'ci_095']]
df['index'] = index
return df
except:
print("CSV results were not produced!")
def GetPercentileValues(benchmark, tags, limit, percentile):
"""Get the percentile value of recent runs described by given tags.
Given the tags, benchmark this function fetches the data of last {limit}
runs, and find the percentile value for each story.
Args:
benchmark: The benchmark these task are on (desktop/mobile).
tags: The tags which describe the tasks such as OS and buildername.
limit: The number of runs to look at.
percentile: the percentile to return.
Returns:
A dictionary with averages and confidence interval ranges calculated
from the percentile of recent runs.
"""
items = []
for tag_set in tags:
items.extend(FetchItemIds(tag_set, limit))
dfs = []
try:
temp_dir = tempfile.mkdtemp('perf_csvs')
for idx, item in enumerate(items):
dfs.append(FetchItemData(item['task_id'], benchmark, idx, temp_dir))
idx += 1
finally:
shutil.rmtree(temp_dir)
data_frame = pandas.concat(dfs, ignore_index=True)
if not data_frame.empty:
avg_df = data_frame.pivot(index='stories', columns='index', values='avg')
upper_limit = avg_df.quantile(percentile, axis = 1)
ci_df = data_frame.pivot(index='stories', columns='index', values='ci_095')
upper_limit_ci = ci_df.quantile(percentile, axis = 1)
results = {}
for index in avg_df.index:
results[index] = {
'avg': round(upper_limit[index], 3),
'ci_095': round(upper_limit_ci[index], 3)
}
return results
def MeasureNewUpperLimit(old_value, new_value, att_name, max_change):
# There has been an improvement.
if new_value < old_value:
# Decrease the limit gradually in case of improvements.
new_value = (old_value + new_value) / 2.0
change_pct = 0.0
if old_value > 0:
change_pct = (new_value - old_value) / old_value
print(
' {}:\t\t {} -> {} \t({:.2f}%)'.format(
att_name, old_value, new_value, change_pct * 100))
if new_value < 0.01:
print('WARNING: New selected value is close to 0.')
return (
round(new_value, 3),
max(max_change, abs(change_pct))
)
def RecalculateUpperLimits(data_point_count):
"""Recalculates the upper limits using the data of recent runs.
This method replaces the existing JSON file which contains the upper limits
used by representative perf tests if the changes of upper limits are
significant.
Args:
data_point_count: The number of runs to use for recalculation.
"""
with open(os.path.join(UPPER_LIMITS_DATA_DIR,
'platform_specific_tags.json')) as tags_data:
platform_specific_tags = json.load(tags_data)
with open(
os.path.join(
UPPER_LIMITS_DATA_DIR,
'representatives_frame_times_upper_limit.json')) as current_data:
current_upper_limits = json.load(current_data)
max_change = 0.0
results = {}
for platform in platform_specific_tags:
platform_data = platform_specific_tags[platform]
print('\n- Processing data ({})'.format(platform))
results[platform] = GetPercentileValues(
platform_data['benchmark'], platform_data['tags'],
data_point_count, 0.95)
# Loop over results and adjust base on current values.
for story in results[platform]:
if story in current_upper_limits[platform]:
print(story, ':')
new_avg, max_change = MeasureNewUpperLimit(
current_upper_limits[platform][story]['avg'],
results[platform][story]['avg'], 'AVG', max_change)
results[platform][story]['avg'] = new_avg
new_ci, max_change = MeasureNewUpperLimit(
current_upper_limits[platform][story]['ci_095'],
results[platform][story]['ci_095'], 'CI', max_change)
results[platform][story]['ci_095'] = new_ci
if max_change > CHANGE_PERCENTAGE_LIMIT:
with open(
os.path.join(
UPPER_LIMITS_DATA_DIR,
'representatives_frame_times_upper_limit.json'
), 'w') as outfile:
json.dump(results, outfile, separators=(',', ': '), indent=2)
print(
'Upper limits were updated on '
'representatives_frame_times_upper_limit.json')
else:
print('Changes are small, no need for new limits')
if __name__ == '__main__':
sys.exit(RecalculateUpperLimits(RUNS_USED_FOR_LIMIT_UPDATE))