blob: 800be80b23ab992d48057aa3bc43b954fedbdc80 [file] [log] [blame]
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Functions called to preprocess perf data for regressions."""
import logging
import operator
import dash_util
# String resources.
from dash_strings import PREPROCESSED_TAG
from dash_strings import EMAIL_ALERT_DELTA_TABLE_SKELETON
from dash_strings import EMAIL_ALERT_DELTA_TABLE_ROW
PREFIX_LEN = 9
class PreprocessFunctions(object):
"""Class to contain and invoke preprocessing functions."""
def Invoke(self, function_name, params, keyvals, checks):
"""Dispatch function and return True if failed."""
if not hasattr(self, function_name):
logging.debug('Preprocessing function %s not found.', function_name)
return False
return getattr(self, function_name)(params, keyvals, checks)
def _MakePreprocessedKey(self, key, seq=None):
"""Helper to distinguish created preprocessing keys from regulars."""
new_key = '%s%s' % (key, PREPROCESSED_TAG)
if seq:
new_key = '%s%s%s' % (seq, PREPROCESSED_TAG, new_key)
return new_key
def _IsPreprocessedKey(self, key):
"""Helper to decide if a key was produced by us."""
key_len = len(key)
pp_len = len(PREPROCESSED_TAG)
return key[key_len-pp_len:] == PREPROCESSED_TAG, pp_len, key_len
def _GetOriginalKeySeq(self, key):
"""Helper to distinguish created preprocessing keys from regulars."""
new_key = ''
seq = 0
is_pp, pp_len, key_len = self._IsPreprocessedKey(key)
if is_pp:
new_key = key[:key_len-pp_len]
n = new_key.find(PREPROCESSED_TAG)
if n > -1:
seq = int(new_key[:n])
new_key = new_key[n+pp_len:]
return new_key, seq
def IsPreprocessedKey(self, key):
is_pp, _, _ = self._IsPreprocessedKey(key)
return is_pp
def PreprocessorHTML(self, test_name, regressed_keys):
"""Process preprocessed keys and related details into a summary."""
# Preserve order using seq hints.
preprocessed_in_order = []
preprocessed_details = {}
for test_key, regressed_stats in regressed_keys.iteritems():
build_val = regressed_stats['build_mean']
build_npoints = regressed_stats['build_samples']
expected_val = regressed_stats['historical_mean']
expected_npoints = regressed_stats['historical_samples']
is_pp, _, _ = self._IsPreprocessedKey(test_key)
if is_pp:
orig_key, seq = self._GetOriginalKeySeq(test_key)
details_row = preprocessed_details.setdefault(
orig_key, [0.0, 0.0, 0.0, 0.0])
details_row[0] = build_val
details_row[1] = expected_val
preprocessed_in_order.append((orig_key, seq))
else:
details_row = preprocessed_details.setdefault(
test_key, [0.0, 0.0, 0.0, 0.0])
details_row[2] = build_val
details_row[3] = expected_val
preprocessed_in_order.sort(key=operator.itemgetter(1))
# Build html.
converter = dash_util.HumanReadableFloat()
current_table = []
table_list = [current_table]
previous_key = None
for one_key, seq in preprocessed_in_order:
if previous_key and (previous_key[:PREFIX_LEN] != one_key[:PREFIX_LEN]):
current_table = []
table_list.append(current_table)
pp_build_val, pp_expected_val, build_val, expected_val = (
preprocessed_details[one_key])
current_table.append(
EMAIL_ALERT_DELTA_TABLE_ROW % {
'key': one_key,
'pp_latest': converter.Convert(pp_build_val),
'pp_average': converter.Convert(pp_expected_val),
'latest': converter.Convert(build_val),
'average': converter.Convert(expected_val)})
previous_key = one_key
preprocessed_html = []
for current_table in table_list:
preprocessed_html.append(
EMAIL_ALERT_DELTA_TABLE_SKELETON % {
'test_name': test_name,
'body': ''.join(current_table)})
return preprocessed_html
def GroupDeltas(self, params, keyvals, checks):
"""Create new keyvals using deltas based on existing keyvals."""
# Average the values for each checked key and build into a structure
# just like the existing keyvals.
stub_test_id = 0
key_build_averages = {}
build_key_counts = {}
for one_key in checks:
key_build_averages[one_key] = {}
for build, values in keyvals[one_key].iteritems():
key_set = build_key_counts.setdefault(build, set())
key_set.add(one_key)
key_build_averages[one_key][build] = (
[sum(values[0], 0.0) / len(values[0])], [stub_test_id])
# Figure out the relative order of the keys in increasing
# order of one build's average values. Use the build with the
# most keys as a reference.
high_water_build = None
high_water_count = 0
for build, key_set in build_key_counts.iteritems():
if len(key_set) > high_water_count:
high_water_count = len(key_set)
high_water_build = build
averages = []
for one_key in checks:
if (not high_water_build or
not high_water_build in key_build_averages[one_key]):
logging.warning(
'Key %s is missing build %s in GroupDeltas().',
one_key, high_water_build)
else:
averages.append((
one_key, key_build_averages[one_key][high_water_build][0]))
averages.sort(key=operator.itemgetter(1))
# Generate the new keys that use deltas as values.
# Group them according to a prefix on each key.
prefix_groups = {}
for one_key, _ in averages:
key_list = prefix_groups.setdefault(one_key[:PREFIX_LEN], [])
key_list.append(one_key)
i = 1 # For later sorting of the group by value.
delta_prefix_groups = prefix_groups.keys()
delta_prefix_groups.sort()
for one_key_group in delta_prefix_groups:
previous_key = None
for one_key in prefix_groups[one_key_group]:
new_key_name = self._MakePreprocessedKey(one_key, i)
# Add the new key to the checks.
checks[new_key_name] = checks[one_key]
# Add the new key and data into keyvals.
if previous_key:
# Calculate the deltas of calculated averages.
for build in key_build_averages[one_key].iterkeys():
if (build in key_build_averages[one_key] and
build in key_build_averages[previous_key]):
new_keyval = keyvals.setdefault(new_key_name, {})
new_keyval[build] = ([
key_build_averages[one_key][build][0][0] -
key_build_averages[previous_key][build][0][0]],
[stub_test_id])
else:
# Copy the structure from the averages calculated.
keyvals[new_key_name] = key_build_averages[one_key]
previous_key = one_key
i += 1