blob: 8dc1e504af4bfbe6d71838356334441868b71bb3 [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.
"""Django chart model implementation.
This class produces the data behind a google visualisation data table
that can be rendered into a chart.
CLASSES:
BaseChartQuery: building block class.
|->BuildRangedChartQuery: adds from_build/to_build constraint.
|->DateRangedChartQuery: adds from_date/to_date constraint.
|->IntervalRangedChartQuery: adds mysql interval constraint.
OneKeyByBuildLinechart: base chart building model class.
|->OneKeyRangedChartModel: adds from_build/to_build constraint.
FUNCTIONS:
GetChartData(): The principal entry-point.
"""
import logging
import re
from autotest_lib.frontend.afe import readonly_connection
from autotest_lib.frontend.croschart.charterrors import ChartDBError
from autotest_lib.frontend.croschart.charterrors import ChartInputError
import gviz_api
FIELD_SEPARATOR = ','
BUILD_PATTERN = re.compile(
'[\w]+\-[\w]+\-r[\w]+\-'
'([\d]+\.[\d]+\.[\d]+\.[\d]+)-(r[\w]{8})-(b[\d]+)')
COMMON_REGEXP = "'(%s).*'"
###############################################################################
# Queries: These are designed as stateless functions with static relationships.
# e.g. GetBuildRangedChartQuery() depends on
# GetBaseQuery() for efficiency.
COMMON_QUERY_TEMPLATE = """
SELECT %(select_keys)s
FROM tko_perf_view_2
WHERE job_name REGEXP %(job_name)s
AND platform = '%(platform)s'
AND job_owner = 'chromeos-test'
AND NOT ISNULL(iteration_value)
AND iteration_value >= 0.0
AND NOT ISNULL(test_started_time)
AND NOT ISNULL(test_finished_time)
AND NOT ISNULL(job_finished_time)"""
CHART_QUERY_KEYS = """
AND test_name = '%(test_name)s'
AND iteration_key = '%(test_key)s'"""
# Use subqueries to find bracketing dates mapping version to job_names.
RANGE_QUERY_TEMPLATE = """
AND test_started_time >= (%(min_query)s)
AND test_started_time <= (%(max_query)s)"""
DEFAULT_ORDER = 'ORDER BY test_started_time'
def GetBaseQuery(request, raw=False):
"""Fully populates and returns a base query string."""
query = COMMON_QUERY_TEMPLATE + CHART_QUERY_KEYS
boards = '|'.join(request.GET.getlist('board'))
platform = 'netbook_%s' % request.GET.get('system').upper()
test_name, test_key = request.GET.get('testkey').split(FIELD_SEPARATOR)
query_parameters = {}
query_parameters['select_keys'] = 'job_name, job_tag, iteration_value'
query_parameters['job_name'] = (COMMON_REGEXP % boards)
query_parameters['platform'] = platform
query_parameters['test_name'] = test_name
query_parameters['test_key'] = test_key
if raw:
return query, query_parameters
else:
return query % query_parameters
def GetBuildRangedChartQuery(request):
"""Apply a build range against the BaseQuery."""
query = RANGE_QUERY_TEMPLATE
boards = request.GET.getlist('board')
from_build = request.GET.get('from_build')
to_build = request.GET.get('to_build')
base_query, base_query_parameters = GetBaseQuery(request, raw=True)
min_parameters = base_query_parameters.copy()
min_parameters['select_keys'] = (
'IFNULL(MIN(test_started_time), DATE_SUB(NOW(), INTERVAL 1 DAY))')
min_parameters['job_name'] = (COMMON_REGEXP % '|'.join(
'%s-%s' % (b, from_build.replace('.', '\.')) for b in boards))
max_parameters = base_query_parameters
max_parameters['select_keys'] = (
'IFNULL(MAX(test_started_time), NOW())')
max_parameters['job_name'] = (COMMON_REGEXP % '|'.join(
'%s-%s' % (b, to_build.replace('.', '\.')) for b in boards))
query_parameters = {}
query_parameters['min_query'] = (base_query % min_parameters)
query_parameters['max_query'] = (base_query % max_parameters)
"""Fully populates and returns a filter query string."""
return query % query_parameters
def GetDateRangedChartQuery(request):
"""Apply a date range against the BaseQuery."""
query = RANGE_QUERY_TEMPLATE
from_date = request.GET.get('from_date')
to_date = request.GET.get('to_date')
query_parameters = {}
query_parameters['min_query'] = "SELECT '%s'" % from_date
query_parameters['max_query'] = "SELECT '%s'" % to_date
"""Fully populates and returns a filter query string."""
return query % query_parameters
def GetIntervalRangedChartQuery(request):
"""Apply an interval range against the BaseQuery."""
query = RANGE_QUERY_TEMPLATE
interval = request.GET.get('interval')
interval = interval.replace(FIELD_SEPARATOR, ' ')
query_parameters = {}
query_parameters['min_query'] = (
'SELECT DATE_SUB(NOW(), INTERVAL %s)' % interval)
query_parameters['max_query'] = 'SELECT NOW()'
"""Fully populates and returns a filter query string."""
return query % query_parameters
###############################################################################
# Helpers
def AbbreviateBuild(build):
"""Condense full build string for x-axis representation."""
m = re.match(BUILD_PATTERN, build)
if not m:
logging.warning('Skipping poorly formatted build: %s.', build)
return build
new_build = '%s-%s' % (m.group(1), m.group(3))
return new_build
###############################################################################
# Models
def GetOneKeyByBuildLinechartData(test_key, query, query_order=DEFAULT_ORDER):
"""Prepare and run the db query and massage the results."""
def AggregateBuilds(test_key, data_list):
"""Groups and averages data by build and extracts job_tags."""
raw_dict = {} # unsummarized data
builds_inorder = [] # summarized data
job_tags = [] # for click-through to data
# Organize the returned data by build.
# Keep the builds in date order and check build name format.
for build, tag, test_value in data_list:
build = AbbreviateBuild(build)
if not build in raw_dict:
builds_inorder.append({'build': build})
job_tags.append(tag)
value_list = raw_dict.setdefault(build, [])
value_list.append(test_value)
if not builds_inorder:
raise ChartDBError('No data returned')
# Now avg data by build and key. This is the format used by gviz.
for build_dict in builds_inorder:
value_list = raw_dict[build_dict['build']]
build_dict[test_key] = round(sum(value_list, 0.0) / len(value_list), 2)
return job_tags, builds_inorder
def ToGVizJsonTable(test_key, builds_inorder):
"""Massage data into gviz data table in proper order."""
# Now format for gviz table.
gviz_data_table = gviz_api.DataTable({'build': ('string', 'Build'),
test_key: ('number', test_key)})
gviz_data_table.LoadData(builds_inorder)
gviz_data_table = gviz_data_table.ToJSon(['build', test_key])
return gviz_data_table
# Now massage the returned data into a gviz data table.
cursor = readonly_connection.connection().cursor()
cursor.execute('%s %s' % (query, query_order))
job_tags, build_data = AggregateBuilds(test_key, cursor.fetchall())
gviz_data_table = ToGVizJsonTable(test_key, build_data)
return {'gviz_data_table': gviz_data_table, 'job_tags': job_tags}
def GetRangedOneKeyByBuildLinechartData(request):
"""Assemble the proper query and order."""
ranged_queries = {'from_build': GetBuildRangedChartQuery,
'from_date': GetDateRangedChartQuery,
'interval': GetIntervalRangedChartQuery}
query_list = [GetBaseQuery(request)]
for range_key in ['from_build', 'from_date', 'interval', None]:
if request.GET.get(range_key, None):
break
if not range_key:
raise ChartInputError('One interval-type parameter must be supplied.')
query_list.append(ranged_queries[range_key](request))
test_name, test_key = request.GET.get('testkey').split(FIELD_SEPARATOR)
data_dict = GetOneKeyByBuildLinechartData(test_key, ' '.join(query_list))
# Added for chart labeling.
data_dict.update({'test_name': test_name, 'test_key': test_key})
return data_dict