From 44c797d143633f4e734ad7a7def1b13da44039e7 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 16 Jun 2014 16:59:35 -0400 Subject: [PATCH] Add support for keeping a moving avg of test run_time This commit adds a tracking the moving average of individual test run times. It involves added a new column to the tests table and handling updating the test row on each time the test shows up in a subunit stream being processed --- TODO.rst | 2 - subunit2sql/db/models.py | 1 + ...create_avg_runtime_column_in_test_table.py | 37 +++++++++++++++++++ subunit2sql/read_subunit.py | 23 ++++++------ subunit2sql/shell.py | 20 +++++++++- 5 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 subunit2sql/migrations/versions/163fd5aa1380_create_avg_runtime_column_in_test_table.py diff --git a/TODO.rst b/TODO.rst index 19cd79e..c495750 100644 --- a/TODO.rst +++ b/TODO.rst @@ -6,8 +6,6 @@ Short Term * Add a new metadata table for each existing table (run_metadata, test_metadata, test_run_metadata) to store extra info from stream like tags, or attrs and other information about runs like job name. - * Add average runtime column to tests table to keep running average of - how long the test takes to run. * Add artifacts option to CLI on subunit2sql to store log links in runs table * Add unit tests diff --git a/subunit2sql/db/models.py b/subunit2sql/db/models.py index 439b737..9998532 100644 --- a/subunit2sql/db/models.py +++ b/subunit2sql/db/models.py @@ -55,6 +55,7 @@ class Test(BASE, SubunitBase): run_count = sa.Column(sa.Integer()) success = sa.Column(sa.Integer()) failure = sa.Column(sa.Integer()) + run_time = sa.Column(sa.Float()) class Run(BASE, SubunitBase): diff --git a/subunit2sql/migrations/versions/163fd5aa1380_create_avg_runtime_column_in_test_table.py b/subunit2sql/migrations/versions/163fd5aa1380_create_avg_runtime_column_in_test_table.py new file mode 100644 index 0000000..e51cd55 --- /dev/null +++ b/subunit2sql/migrations/versions/163fd5aa1380_create_avg_runtime_column_in_test_table.py @@ -0,0 +1,37 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Create avg runtime column in test table + +Revision ID: 163fd5aa1380 +Revises: 3db7b49816d5 +Create Date: 2014-06-16 15:45:19.221576 + +""" + +# revision identifiers, used by Alembic. +revision = '163fd5aa1380' +down_revision = '3db7b49816d5' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('tests', sa.Column('run_time', sa.Float(), nullable=True)) + + +def downgrade(): + op.drop_column('tests', 'run_time') diff --git a/subunit2sql/read_subunit.py b/subunit2sql/read_subunit.py index c31889c..3a3b9c9 100644 --- a/subunit2sql/read_subunit.py +++ b/subunit2sql/read_subunit.py @@ -20,6 +20,16 @@ import testtools DAY_SECONDS = 60 * 60 * 24 +def get_duration(start, end): + if not start or not end: + duration = '' + else: + delta = end - start + duration = '%d.%06ds' % ( + delta.days * DAY_SECONDS + delta.seconds, delta.microseconds) + return duration + + class ReadSubunit(object): def __init__(self, stream_file): @@ -84,18 +94,9 @@ class ReadSubunit(object): name = newname return name - def get_duration(self, start, end): - if not start or not end: - duration = '' - else: - delta = end - start - duration = '%d.%06ds' % ( - delta.days * DAY_SECONDS + delta.seconds, delta.microseconds) - return duration - def run_time(self): runtime = 0.0 for name, data in self.results.items(): - runtime += float(self.get_duration(data['start_time'], - data['end_time']).strip('s')) + runtime += float(get_duration(data['start_time'], + data['end_time']).strip('s')) return runtime diff --git a/subunit2sql/shell.py b/subunit2sql/shell.py index 9a8a8d8..cdb2e73 100644 --- a/subunit2sql/shell.py +++ b/subunit2sql/shell.py @@ -50,9 +50,24 @@ def parse_args(argv, default_config_files=None): default_config_files=default_config_files) -def increment_counts(run, test, status, session=None): +def running_avg(test, values, result): + count = test.success + avg_prev = test.run_time + curr_runtime = float(subunit.get_duration(result['start_time'], + result['end_time']).strip('s')) + if isinstance(avg_prev, float): + # Using a smoothed moving avg to limit the affect of a single outlier + new_avg = ((count * avg_prev) + curr_runtime) / (count + 1) + values['run_time'] = new_avg + else: + values['run_time'] = curr_runtime + return values + + +def increment_counts(run, test, results, session=None): test_values = {'run_count': test.run_count + 1} run_values = {} + status = results.get('status') run = api.get_run_by_id(run.id, session) if status == 'success': test_values['success'] = test.success + 1 @@ -66,6 +81,7 @@ def increment_counts(run, test, status, session=None): else: msg = "Unknown test status %s" % status raise exceptions.UnknownStatus(msg) + test_values = running_avg(test, test_values, results) if test_values: api.update_test(test_values, test.id) api.update_run(run_values, run.id) @@ -78,7 +94,7 @@ def process_results(results): db_test = api.get_test_by_test_id(test, session) if not db_test: db_test = api.create_test(test) - increment_counts(db_run, db_test, results[test]['status'], session) + increment_counts(db_run, db_test, results[test], session) api.create_test_run(db_test.id, db_run.id, results[test]['status'], results[test]['start_time'], results[test]['end_time'])