Rework get_test_runs_for_test() rest api
This commit reworks the get_test_runs_for() rest api call and changes it's basic behavior. The first thing done is the numeric data is split from the non-numeric data in the response. This was done to enable mean based resampling on the numeric data. This is necessary because the other large change to the api is that a running mean and a running std dev are added to the response. This was done to enable more detailed graphs to the UI for the run_time of a single test. To perform the running mean and std dev pandas is added to the requirements for the rest api. Change-Id: I3416dc0cad24c90405ac437df31c39b59f524d88
This commit is contained in:
parent
aed95e34a6
commit
9b0b664517
|
@ -309,12 +309,24 @@ def get_test_runs_for_test(test_id):
|
|||
session = Session()
|
||||
start_date = _parse_datetimes(flask.request.args.get('start_date', None))
|
||||
stop_date = _parse_datetimes(flask.request.args.get('stop_date', None))
|
||||
datetime_resolution = flask.request.args.get('datetime_resolution', 'min')
|
||||
|
||||
if datetime_resolution not in ['sec', 'min', 'hour', 'day']:
|
||||
message = ('Datetime resolution: %s, is not a valid'
|
||||
' choice' % datetime_resolution)
|
||||
status_code = 400
|
||||
return abort(make_response(message, status_code))
|
||||
db_test_runs = api.get_test_runs_by_test_test_id(test_id, session=session,
|
||||
start_date=start_date,
|
||||
stop_date=stop_date)
|
||||
if not db_test_runs:
|
||||
# NOTE(mtreinish) if no data is returned from the DB just return an
|
||||
# empty set response, the test_run_aggregator function assumes data
|
||||
# is present.
|
||||
return jsonify({'numeric': {}, 'data': {}})
|
||||
test_runs = test_run_aggregator.convert_test_runs_list_to_time_series_dict(
|
||||
db_test_runs)
|
||||
return jsonify({'test_runs': test_runs})
|
||||
db_test_runs, datetime_resolution)
|
||||
return jsonify(test_runs)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
@ -12,31 +12,78 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pandas as pd
|
||||
from subunit2sql import read_subunit
|
||||
|
||||
from base_aggregator import BaseAggregator
|
||||
|
||||
|
||||
def convert_test_runs_list_to_time_series_dict(test_runs_list):
|
||||
test_runs = {}
|
||||
def convert_test_runs_list_to_time_series_dict(test_runs_list, resample):
|
||||
test_runs = []
|
||||
for test_run in test_runs_list:
|
||||
tr = test_run.to_dict()
|
||||
# Populate dict
|
||||
start_time = test_run.start_time
|
||||
if start_time and test_run.start_time_microsecond:
|
||||
start_time = start_time.replace(
|
||||
microsecond=test_run.start_time_microsecond)
|
||||
tr['start_time'] = start_time
|
||||
tr.pop('start_time_microsecond')
|
||||
if test_run.stop_time:
|
||||
stop_time = test_run.stop_time
|
||||
if test_run.stop_time_microsecond:
|
||||
stop_time = stop_time.replace(
|
||||
microsecond=test_run.stop_time_microsecond)
|
||||
test_run_dict = {
|
||||
'run_time': read_subunit.get_duration(start_time, stop_time),
|
||||
'status': test_run.status,
|
||||
'run_id': test_run.run_id
|
||||
}
|
||||
test_runs[start_time.isoformat()] = test_run_dict
|
||||
return test_runs
|
||||
tr['stop_time'] = stop_time
|
||||
tr['run_time'] = read_subunit.get_duration(start_time,
|
||||
tr.pop('stop_time'))
|
||||
tr.pop('stop_time_microsecond')
|
||||
tr.pop('id')
|
||||
tr.pop('test_id')
|
||||
test_runs.append(tr)
|
||||
|
||||
df = pd.DataFrame(test_runs).set_index('start_time')
|
||||
df.index = pd.DatetimeIndex(df.index)
|
||||
# Add rolling mean and std dev of run_time to datafram
|
||||
df['avg_run_time'] = pd.rolling_mean(df['run_time'], 20)
|
||||
df['stddev_run_time'] = pd.rolling_std(df['run_time'], 20)
|
||||
|
||||
# Resample numeric data for the run_time graph from successful runs
|
||||
resample_matrix = {
|
||||
'day': 'D',
|
||||
'hour': '1H',
|
||||
'min': '1T',
|
||||
'sec': '1S',
|
||||
}
|
||||
numeric_df = df[df['status'] == 'success'].resample(
|
||||
resample_matrix[resample], how='mean')
|
||||
# Drop duplicate or invalid colums
|
||||
del(numeric_df['run_id'])
|
||||
del(df['run_time'])
|
||||
del(df['avg_run_time'])
|
||||
del(df['stddev_run_time'])
|
||||
|
||||
# Drop missing data from the resample
|
||||
numeric_df = numeric_df.dropna(how='all')
|
||||
|
||||
# Convert the dataframes to a dict
|
||||
numeric_dict = dict(
|
||||
(date.isoformat(),
|
||||
{
|
||||
'run_time': run_time,
|
||||
'avg_run_time': avg,
|
||||
'std_dev_run_time': stddev,
|
||||
}) for date, run_time, avg, stddev in zip(
|
||||
numeric_df.index, numeric_df.run_time, numeric_df.avg_run_time,
|
||||
numeric_df.stddev_run_time))
|
||||
temp_dict = dict(
|
||||
(date.isoformat(),
|
||||
{
|
||||
'run_id': run_id,
|
||||
'status': status,
|
||||
}) for date, run_id, status in zip(df.index, df.run_id, df.status))
|
||||
|
||||
return {'numeric': numeric_dict, 'data': temp_dict}
|
||||
|
||||
|
||||
class Status(object):
|
||||
|
|
|
@ -16,6 +16,7 @@ import datetime
|
|||
import json
|
||||
|
||||
import mock
|
||||
import numpy
|
||||
from subunit2sql.db import models
|
||||
|
||||
from openstack_health import api
|
||||
|
@ -664,22 +665,28 @@ class TestRestAPI(base.TestCase):
|
|||
|
||||
@mock.patch('subunit2sql.db.api.get_test_runs_by_test_test_id',
|
||||
return_value=[models.TestRun(
|
||||
id='fake_id', test_id='test.id', run_id='fake_run_id',
|
||||
id=2, test_id=1234, run_id=1234,
|
||||
status='success', start_time=timestamp_a,
|
||||
stop_time=timestamp_b)])
|
||||
start_time_microsecond=0,
|
||||
stop_time=timestamp_b,
|
||||
stop_time_microsecond=0)])
|
||||
def test_get_test_runs_for_test(self, api_mock):
|
||||
api.Session = mock.MagicMock()
|
||||
res = self.app.get('/test_runs/fake.test.id')
|
||||
self.assertEqual(200, res.status_code)
|
||||
exp_result = {'test_runs': {
|
||||
exp_result = {'data': {
|
||||
timestamp_a.isoformat(): {
|
||||
'run_time': 1.0,
|
||||
'run_id': 1234,
|
||||
'status': 'success',
|
||||
'run_id': 'fake_run_id',
|
||||
}
|
||||
}}
|
||||
}}, 'numeric': {
|
||||
timestamp_a.isoformat(): {
|
||||
'avg_run_time': numpy.NaN,
|
||||
'run_time': 1.0,
|
||||
'std_dev_run_time': numpy.NaN
|
||||
}}
|
||||
}
|
||||
response_data = json.loads(res.data)
|
||||
self.assertEqual(exp_result, response_data)
|
||||
numpy.testing.assert_equal(exp_result, response_data)
|
||||
api_mock.assert_called_once_with('fake.test.id', start_date=None,
|
||||
stop_date=None,
|
||||
session=api.Session())
|
||||
|
|
|
@ -8,3 +8,5 @@ sqlalchemy
|
|||
flask-jsonpify
|
||||
PyMySQL>=0.6.2
|
||||
python-dateutil>=2.4.2
|
||||
pandas>=0.17.1
|
||||
numpy>=1.7.0
|
||||
|
|
Loading…
Reference in New Issue