Merge lp:~canonical-platform-qa/qakit/performance_solution into lp:qakit

Proposed by Sergio Cazzolato
Status: Merged
Merged at revision: 73
Proposed branch: lp:~canonical-platform-qa/qakit/performance_solution
Merge into: lp:qakit
Diff against target: 1981 lines (+1068/-661)
33 files modified
README (+15/-2)
qakit/appstartup/__init__.py (+0/-33)
qakit/appstartup/dao.py (+0/-102)
qakit/appstartup/data_processor.py (+0/-74)
qakit/appstartup/eve/__init__.py (+0/-16)
qakit/appstartup/eve/run.py (+0/-22)
qakit/appstartup/eve/settings.py (+0/-89)
qakit/appstartup/orchestrator.py (+0/-99)
qakit/appstartup/parser.py (+0/-65)
qakit/appstartup/plotter.py (+0/-63)
qakit/appstartup/report.py (+0/-72)
qakit/dashboard/css/kpi.css (+1/-1)
qakit/dashboard/index.html (+58/-19)
qakit/dashboard/js/appstartup.js (+3/-3)
qakit/dashboard/js/main.js (+10/-0)
qakit/dashboard/js/performance.js (+31/-0)
qakit/dashboard/js/scalability.js (+31/-0)
qakit/perf/__init__.py (+30/-0)
qakit/perf/engine/__init__.py (+16/-0)
qakit/perf/engine/dao.py (+104/-0)
qakit/perf/engine/data_processor.py (+81/-0)
qakit/perf/engine/parser.py (+72/-0)
qakit/perf/engine/plotter.py (+93/-0)
qakit/perf/engine/report.py (+54/-0)
qakit/perf/eve/__init__.py (+16/-0)
qakit/perf/eve/run.py (+24/-0)
qakit/perf/eve/settings.py (+92/-0)
qakit/perf/orchestration/__init__.py (+84/-0)
qakit/perf/orchestration/app_startup.py (+79/-0)
qakit/perf/orchestration/performance.py (+80/-0)
qakit/perf/orchestration/scalability.py (+78/-0)
qakit/practitest/subunit_results.py (+15/-0)
setup.py (+1/-1)
To merge this branch: bzr merge lp:~canonical-platform-qa/qakit/performance_solution
Reviewer Review Type Date Requested Status
Canonical Platform QA Team Pending
Review via email: mp+289117@code.launchpad.net

Commit message

This branch involves many changes in order to extend the performance features that was previously used just for app startup, supporting now different kind of times based tests such as the scalability ones. The change includes:
. Adding scalability to qakit
. html report removed
. Generic report generator created in the engine dir
. Dashboard updated
. configuration moved to config file (see README)
. readme file updated

To post a comment you must log in.
56. By Sergio Cazzolato

Simplify the settings config for the eve service

57. By Sergio Cazzolato

Adding missing files and updating README

58. By Sergio Cazzolato

Editing the scalability names used in the README

59. By Sergio Cazzolato

Fix based on error doing requests.post in jenkins due to an old version installed there

60. By Sergio Cazzolato

Force matplotlib to not use any Xwindows backend

61. By Sergio Cazzolato

Force matplotlib to not use any Xwindows backend

62. By Sergio Cazzolato

Minor fixes on dashboard index js

63. By Sergio Cazzolato

Minor fixes on dashboard index js

64. By Sergio Cazzolato

Using double quotes for jquery locators

65. By Sergio Cazzolato

Updating js to create pdf

66. By Sergio Cazzolato

Adding support for perfomance tests and improving plotting sticks

67. By Sergio Cazzolato

minor change in css to center performance graphs

68. By Sergio Cazzolato

Changing name of report

69. By Sergio Cazzolato

Adding missing files

70. By Sergio Cazzolato

Fix unintentional deletion

71. By Sergio Cazzolato

Allow to join reports

72. By Sergio Cazzolato

Updating config values for performance

73. By Sergio Cazzolato

Fix reports path dir

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README'
--- README 2016-02-18 14:19:19 +0000
+++ README 2016-04-22 20:41:08 +0000
@@ -88,11 +88,24 @@
88 [APP_STARTUP]88 [APP_STARTUP]
89 EVE_HOST = [dest_ip]89 EVE_HOST = [dest_ip]
90 EVE_PORT = [dest_port]90 EVE_PORT = [dest_port]
91 EVE_PATH = appstartup91 EVE_PATH = perf
92 HTML_REPORT = True
93 JSON_REPORT = True92 JSON_REPORT = True
94 BUILDS_TO_PLOT = 1093 BUILDS_TO_PLOT = 10
9594
95 [APP_STARTUP]
96 ALLOWED_APPS = webbrowser, address_book, calculator, here, dialer, clock, messaging, ebay, camera, system_settings, music, gallery
97 TYPES = cold, hot
98 TITLES = Cold Start, Hot Start
99 COLORS = 3366FF, FF1A00
100 ECOLORS = FF1A00, 3366FF
101
102 [SCALABILITY]
103 ALLOWED_NAMES = pictures_processed, pictures_gallery, my_pictures, pictures_carousel
104 LOADS = 1024, 2048, 4096, 8192, 16384
105 TITLES = Load 1024, Load 2048, Load 4096, Load 8192, Load 16384
106 COLORS = FFBF00, 81F7D8, 9F81F7, F781BE, 0040FF
107 ECOLORS = 0040FF, F781BE, 9F81F7, 81F7D8, FFBF00
108
96Having a test-results.subunit file, run as following: python3 qakit/appstartup/orchestrator.py pathto/test-results.subunit109Having a test-results.subunit file, run as following: python3 qakit/appstartup/orchestrator.py pathto/test-results.subunit
97110
98HTML and json reports are generated in the subunit directory. All the pictures will be stored in the appstartup_data dir.111HTML and json reports are generated in the subunit directory. All the pictures will be stored in the appstartup_data dir.
99112
=== removed directory 'qakit/appstartup'
=== removed file 'qakit/appstartup/__init__.py'
--- qakit/appstartup/__init__.py 2016-01-22 20:54:59 +0000
+++ qakit/appstartup/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,33 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import configparser
19
20import qakit.config as qakit_config
21
22allowed_apps = ['webbrowser', 'address_book', 'calculator', 'here', 'dialer',
23 'clock', 'messaging', 'ebay', 'camera', 'system_settings',
24 'music', 'gallery']
25
26
27def _read_config(config_filepath):
28 """ Parse the config at the given filepath and return it """
29 config_file = configparser.ConfigParser()
30 config_file.read(config_filepath)
31 return config_file
32
33config_dict = _read_config(qakit_config.get_config_file_location())
340
=== removed file 'qakit/appstartup/dao.py'
--- qakit/appstartup/dao.py 2016-02-18 14:19:19 +0000
+++ qakit/appstartup/dao.py 1970-01-01 00:00:00 +0000
@@ -1,102 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import logging
19import requests
20
21from qakit.appstartup import config_dict as config
22
23BASE_URL = 'http://{}:{}/{}'.format(config['APP_STARTUP']['EVE_HOST'],
24 config['APP_STARTUP']['EVE_PORT'],
25 config['APP_STARTUP']['EVE_PATH'])
26
27logger = logging.getLogger(__name__)
28
29
30def get_headers():
31 return {'Content-Type': 'application/json'}
32
33
34def _get_where_param(**params):
35 """ Get a where parameter used to filter when getting a query to mongo
36 :param params: A dict with all the parameters used to filter
37 """
38 where_param = "{"
39 for (k, v) in params.items():
40 where_param += '"{}":"{}",'.format(k, v)
41 return where_param[:-1] + '}'
42
43
44def save_record_to_db(global_info, startup_times):
45 """ Save the record information in the mongo db. The information saved is
46 composed by joining both dicts received as parameter
47 """
48 logger.info('Saving records to db')
49 for instance in startup_times:
50 instance.update(global_info)
51 r = requests.post('{}'.format(BASE_URL),
52 json=instance,
53 headers=get_headers())
54 if not r.status_code == requests.codes.created:
55 logger.error('Error uploading instance data: {}'.format(instance))
56 logger.error(r.status_code, r.reason)
57
58
59def is_run_saved(run_id):
60 """ Indicate if there is at least one record with the run_id saved in the
61 mongo db
62 """
63 where_param = _get_where_param(run_id=run_id)
64 r = requests.get(BASE_URL, params={'where': where_param},
65 headers=get_headers())
66 if r.status_code == requests.codes.ok:
67 return r.json()['_meta']['total'] > 0
68 else:
69 logger.error('Error retrieving information for run: {}'.format(run_id))
70 logger.error(r.status_code, r.reason)
71
72
73def get_records_for_app(app, device, channel):
74 """ Retrieve the records from the mongo db for an specific app, device and
75 channel. The records by default are ordered by build number
76 :param app: The app used to filter the results
77 :param device: The device used to filter the results
78 :param channel: The channel used to filter the results
79 """
80 where_param = _get_where_param(app=app, device=device, channel=channel)
81
82 stop = False
83 page_iter = 1
84 records = []
85 while not stop:
86 r = requests.get(BASE_URL,
87 params={'where': where_param, 'max_results': 50,
88 'page': page_iter},
89 headers=get_headers())
90 if r.status_code == requests.codes.ok:
91 records.extend(r.json()['_items'])
92 else:
93 logger.error('Error retrieving data from url: {}'.format(r.url))
94 logger.error(r.status_code, r.reason)
95
96 metadata = r.json()['_meta']
97 if metadata['max_results'] * metadata['page'] >= metadata['total']:
98 stop = True
99 else:
100 page_iter += 1
101
102 return records
1030
=== removed file 'qakit/appstartup/data_processor.py'
--- qakit/appstartup/data_processor.py 2016-02-18 14:19:19 +0000
+++ qakit/appstartup/data_processor.py 1970-01-01 00:00:00 +0000
@@ -1,74 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import statistics
19
20from qakit.appstartup import config_dict as config
21
22BUILDS_TO_PLOT = config['APP_STARTUP']['BUILDS_TO_PLOT']
23
24
25def get_times_by_build(app_records):
26 """ Calculate the times for the different builds
27 :return: [builds], [mean cold], [stdev cold], [mean hot], [stdev hot]
28 """
29 build_times = {}
30 for record in app_records:
31 build = record['build_number']
32 if build in build_times:
33 build_times[build].append(record)
34 else:
35 build_times[build] = [record]
36
37 times = _process_build_times(build_times)
38 if times:
39 return zip(*times)
40 else:
41 return (), (), (), (), ()
42
43
44def _process_build_times(build_times):
45 """ Process and filter build times """
46 times = []
47 for build in sorted(build_times)[- int(BUILDS_TO_PLOT):]:
48 hot_times = _get_times_by_type(build_times[build], 'hot')
49 cold_times = _get_times_by_type(build_times[build], 'cold')
50
51 times.append((build, _get_average(cold_times),
52 _get_stdev(cold_times),
53 _get_average(hot_times),
54 _get_stdev(hot_times)))
55 return times
56
57
58def _get_average(times):
59 if times:
60 return statistics.mean(times)
61 else:
62 return 0
63
64
65def _get_stdev(times):
66 if len(times) >= 2:
67 return statistics.stdev(times)
68 else:
69 return 0
70
71
72def _get_times_by_type(app_records, type):
73 return [app_record['time'] for app_record in app_records if
74 app_record['type'] == type]
750
=== removed directory 'qakit/appstartup/eve'
=== removed file 'qakit/appstartup/eve/__init__.py'
--- qakit/appstartup/eve/__init__.py 2016-01-19 20:21:59 +0000
+++ qakit/appstartup/eve/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,16 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
170
=== removed file 'qakit/appstartup/eve/run.py'
--- qakit/appstartup/eve/run.py 2016-01-19 20:21:59 +0000
+++ qakit/appstartup/eve/run.py 1970-01-01 00:00:00 +0000
@@ -1,22 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18from eve import Eve
19app = Eve()
20
21if __name__ == '__main__':
22 app.run()
230
=== removed file 'qakit/appstartup/eve/settings.py'
--- qakit/appstartup/eve/settings.py 2016-01-28 05:47:05 +0000
+++ qakit/appstartup/eve/settings.py 1970-01-01 00:00:00 +0000
@@ -1,89 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import os
19
20MONGO_HOST = os.getenv('EVE_MONGO_HOST', '127.0.0.1')
21MONGO_PORT = os.getenv('EVE_MONGO_PORT', '27017')
22MONGO_USERNAME = os.getenv('EVE_MONGO_USERNAME', 'ubuntuUser')
23MONGO_PASSWORD = os.getenv('EVE_MONGO_PASSWORD', 'ubuntuPassword')
24MONGO_DBNAME = os.getenv('EVE_MONGO_DBNAME', 'test')
25SERVER_NAME = os.getenv('EVE_SERVER_NAME', None)
26
27
28appstartup_schema = {
29 # Schema definition, based on Cerberus grammar. Check the Cerberus project
30 # (https://github.com/nicolaiarocci/cerberus) for details.
31 'run_id': {
32 'type': 'string',
33 'required': True,
34 },
35 'time': {
36 'type': 'number',
37 'required': True,
38 },
39 'type': {
40 'type': 'string',
41 'required': True,
42 },
43 'iteration': {
44 'type': 'number',
45 'required': True,
46 },
47 'app': {
48 'type': 'string',
49 'required': False,
50 },
51 'build_number': {
52 'type': 'string',
53 'required': True,
54 },
55 'device': {
56 'type': 'string',
57 'required': True,
58 },
59 'channel': {
60 'type': 'string',
61 'required': True,
62 },
63}
64
65appstartup = {
66 # 'title' tag used in item links. Defaults to the resource title minus
67 # the final, plural 's' (works fine in most cases but not for 'people')
68 'item_title': 'appstartup',
69
70 # by default the standard item entry point is defined as
71 # '/people/<ObjectId>'. We leave it untouched, and we also enable an
72 # additional read-only entry point. This way consumers can also perform
73 # GET requests at '/people/<lastname>'.
74 'additional_lookup': {
75 'url': 'regex("[\w]+")',
76 'field': 'app'
77 },
78
79 # We choose to override global cache-control directives for this resource.
80 'cache_control': 'max-age=10,must-revalidate',
81 'cache_expires': 10,
82
83 # most global settings can be overridden at resource level
84 'schema': appstartup_schema,
85}
86
87DOMAIN = {'appstartup': appstartup}
88RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
89ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']
900
=== removed file 'qakit/appstartup/orchestrator.py'
--- qakit/appstartup/orchestrator.py 2016-02-12 17:01:08 +0000
+++ qakit/appstartup/orchestrator.py 1970-01-01 00:00:00 +0000
@@ -1,99 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18from argparse import ArgumentParser
19import logging
20import os
21
22from qakit.appstartup import allowed_apps
23from qakit.appstartup import config_dict as config
24from qakit.appstartup import dao
25from qakit.appstartup import data_processor
26from qakit.appstartup import parser
27from qakit.appstartup import plotter
28from qakit.appstartup import report
29from qakit.practitest import subunit_results
30
31logger = logging.getLogger(__name__)
32
33HTML_REPORT = config['APP_STARTUP']['HTML_REPORT']
34JSON_REPORT = config['APP_STARTUP']['JSON_REPORT']
35
36
37def save_results_if_needed(global_info, startup_times):
38 if startup_times:
39 run_id = global_info['run_id']
40 if not dao.is_run_saved(run_id):
41 dao.save_record_to_db(global_info, startup_times)
42 else:
43 logger.warning(
44 'Test results already in the db for the run_id {}'.format(
45 run_id))
46
47
48def _collect_records_by_app(global_info):
49 app_records = {}
50 device = global_info['device']
51 channel = global_info['channel']
52 for app in allowed_apps:
53 app_records[app] = dao.get_records_for_app(app, device, channel)
54 return app_records
55
56
57def _plot_records_by_apps(records_by_apps, results_dir):
58 app_plots = {}
59
60 for app in allowed_apps:
61 (builds, cold_times, cold_stdevs, hot_times, hot_stdevs) = \
62 data_processor.get_times_by_build(records_by_apps[app])
63 app_plots[app] = plotter.plot_app_records(app, builds, cold_times,
64 cold_stdevs, hot_times,
65 hot_stdevs, results_dir)
66 return app_plots
67
68
69def _create_graphics_dir(results_dir):
70 dir = os.path.join(results_dir, 'appstartup_data')
71 if not os.path.exists(dir):
72 os.mkdir(dir)
73 return dir
74
75
76def generate_reports(subunit_log, global_info, records_by_apps):
77 if HTML_REPORT or JSON_REPORT:
78 graphics_dir = _create_graphics_dir(os.path.dirname(subunit_log))
79 pics = _plot_records_by_apps(records_by_apps, graphics_dir)
80 if HTML_REPORT:
81 report.create_html_report(pics, os.path.dirname(subunit_log),
82 report_info=global_info)
83 if JSON_REPORT:
84 report.create_json_report(pics, os.path.dirname(subunit_log),
85 report_info=global_info)
86
87
88def main():
89 args_parser = ArgumentParser("Parse app startup times from logs.")
90 args_parser.add_argument("subunit_log")
91 args = args_parser.parse_args()
92 results_list = subunit_results.parse(args.subunit_log)
93 global_info, startup_times = parser.parse_startup_times(results_list)
94 save_results_if_needed(global_info, startup_times)
95 records_by_apps = _collect_records_by_app(global_info)
96 generate_reports(args.subunit_log, global_info, records_by_apps)
97
98if __name__ == "__main__":
99 main()
1000
=== removed file 'qakit/appstartup/parser.py'
--- qakit/appstartup/parser.py 2016-01-22 20:54:59 +0000
+++ qakit/appstartup/parser.py 1970-01-01 00:00:00 +0000
@@ -1,65 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import ast
19
20
21def parse_startup_times(results):
22 """ Retrieve the global run information and a list with all the history
23 instances parsed from the results_list in the subunit test log
24 """
25 startup_times = []
26 tests_logs = _get_results_test_logs(results)
27 global_info = _get_global_information(tests_logs)
28
29 for test_log in tests_logs:
30 times = _get_times(test_log)
31 if global_info and times:
32 startup_times.extend(times)
33 return global_info, startup_times
34
35
36def _get_global_information(tests_logs):
37 for test_log in tests_logs:
38 markers = [line for line in test_log if '<=' in line]
39 if markers:
40 values = markers[0].split('<=')[1].strip()
41 return ast.literal_eval(values)
42 raise RuntimeError('Global info not provided for tests')
43
44
45def _get_results_test_logs(results):
46 test_logs = []
47 for result in results:
48 test_log = result['details'].get('test-log')
49 if test_log:
50 test_logs.append(test_log.as_text().split('\n'))
51 return test_logs
52
53
54def _get_times(test_log):
55 markers = [line for line in test_log if '=>' in line]
56 times = []
57
58 iteration = 1
59 for mark in markers:
60 values = mark.split('=>')[1].strip()
61 values_dict = ast.literal_eval(values)
62 values_dict['iteration'] = iteration
63 times.append(values_dict)
64 iteration += 1
65 return times
660
=== removed file 'qakit/appstartup/plotter.py'
--- qakit/appstartup/plotter.py 2016-02-10 20:18:30 +0000
+++ qakit/appstartup/plotter.py 1970-01-01 00:00:00 +0000
@@ -1,63 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import matplotlib.pyplot as plt
19import numpy as np
20import os
21
22
23def plot_app_records(app, builds, cold_times, cold_stdevs, hot_times,
24 hot_stdevs, plots_dir):
25 """ Generate the graphic and return the graphic path.
26 It is supposed the records are already ordered by build number.
27 """
28
29 # Set descriptions
30 fig, ax = plt.subplots()
31 ax.set_title('Times for {} app'.format(app))
32 ax.set_xlabel('Build Number')
33 ax.set_ylabel('Time (seconds)')
34
35 # Config the graphic
36 ind = np.arange(len(builds))
37 width = 0.35
38 ax.set_xticks(ind + width)
39 ax.set_xticklabels(builds)
40 ax.set_xmargin(0.1)
41 ax.yaxis.grid(True)
42
43 rects_cold = ax.bar(ind, cold_times, width, color='#3366FF',
44 yerr=cold_stdevs, ecolor='#FF1A00', capsize=5,
45 error_kw={'elinewidth': 2})
46 rects_hot = ax.bar(ind + width, hot_times, width, color='#FF1A00',
47 yerr=hot_stdevs, ecolor='#3366FF', capsize=5,
48 error_kw={'elinewidth': 2})
49
50# Shrink current axis's height by 10% on the bottom
51 box = ax.get_position()
52 ax.set_position([box.x0, box.y0 + box.height * 0.1,
53 box.width, box.height * 0.9])
54
55 if rects_cold and rects_hot:
56 ax.legend((rects_cold[0], rects_hot[0]), ('Cold Start', 'Hot Start'),
57 loc='upper center', bbox_to_anchor=(0.85, -0.05),
58 fancybox=True, shadow=True)
59
60 # Save the graphic
61 dst_file = os.path.join(plots_dir, 'barchart_{}.png'.format(app))
62 plt.savefig(dst_file)
63 return dst_file
640
=== removed file 'qakit/appstartup/report.py'
--- qakit/appstartup/report.py 2016-02-12 17:08:11 +0000
+++ qakit/appstartup/report.py 1970-01-01 00:00:00 +0000
@@ -1,72 +0,0 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import json
19import os
20
21html_header = '<html><head><title>{}</title></head><body><div align="center"><table>' # NOQA
22html_footer = '</table></div></body></html>'
23html_picture = '<tr><td><br/><br/><font size="7" color="#8B005A"><center>{}</center></font><br/><img src="{}"/></td></tr>' # NOQA
24html_logo = '<img src="http://design.ubuntu.com/wp-content/uploads/canonical-logo1.png">' # NOQA
25html_title = '<br/><br/><div><font size="7" color="#8B005A">Apps StartUp Execution Report</font></div>' # NOQA
26html_info_title_header = '<br/><br/><div><font size="6" color="#8B005A">General information</font>' # NOQA
27html_info_title_footer = '</div>'
28html_info = '<div><br/><font size="4">{}: {}</font></div>'
29
30
31def create_html_report(out_files, report_dir,
32 report_file="app_startup_report.html",
33 report_info=None):
34 report_path = os.path.join(report_dir, report_file)
35
36 with open(report_path, "w") as report:
37 # write out the html header
38 report.write(html_header.format('App startup times report'))
39 report.write(html_logo)
40
41 report.write(html_title)
42 report.write(html_info_title_header)
43 for info in sorted(report_info.keys()):
44 report.write(html_info.format(info.replace('_', ' ').title(),
45 report_info[info]))
46 report.write(html_info_title_footer)
47
48 for app, path in out_files.items():
49 report.write(html_picture.format(app.title(), path))
50
51 report.write(html_footer)
52
53 return report_path
54
55
56def create_json_report(out_files, report_dir,
57 report_file="app_startup_report.json",
58 report_info=None):
59
60 report_path = os.path.join(report_dir, report_file)
61 data = dict()
62 data['channel'] = report_info['channel']
63 data['device'] = report_info['device']
64 data['build_number'] = report_info['build_number']
65
66 charts = []
67 for app, path in out_files.items():
68 charts.append({"title": app.title(), "path": path})
69 data['charts'] = charts
70
71 with open(report_path, "w") as report:
72 report.write(json.dumps(data, ensure_ascii=False))
730
=== modified file 'qakit/dashboard/css/kpi.css'
--- qakit/dashboard/css/kpi.css 2016-02-11 21:23:21 +0000
+++ qakit/dashboard/css/kpi.css 2016-04-22 20:41:08 +0000
@@ -27,6 +27,6 @@
27 list-style-type: none !important;27 list-style-type: none !important;
28}28}
2929
30.app-startup-elem {30.perf-elem {
31 text-align: center;31 text-align: center;
32}32}
3333
=== modified file 'qakit/dashboard/index.html'
--- qakit/dashboard/index.html 2016-02-12 17:24:39 +0000
+++ qakit/dashboard/index.html 2016-04-22 20:41:08 +0000
@@ -27,6 +27,8 @@
27 <script src="js/test-library.js"></script>27 <script src="js/test-library.js"></script>
28 <script src="js/test-plans.js"></script>28 <script src="js/test-plans.js"></script>
29 <script src="js/appstartup.js"></script>29 <script src="js/appstartup.js"></script>
30 <script src="js/scalability.js"></script>
31 <script src="js/performance.js"></script>
3032
31<style type="text/css">33<style type="text/css">
32 .bs-example{34 .bs-example{
@@ -104,6 +106,20 @@
104 <ul id="app-startup-sublinks" class="sub-links">106 <ul id="app-startup-sublinks" class="sub-links">
105 </ul>107 </ul>
106 </div>108 </div>
109 <div class="link-blue" id="scalability-link">
110 <a href="#scalability-section">
111 <i class="fa fa-tachometer"></i>Scalability
112 </a>
113 <ul id="scalability-sublinks" class="sub-links">
114 </ul>
115 </div>
116 <div class="link-blue" id="performance-link">
117 <a href="#performance-section">
118 <i class="fa fa-tachometer"></i>Performance
119 </a>
120 <ul id="performance-sublinks" class="sub-links">
121 </ul>
122 </div>
107 </div>123 </div>
108 </aside>124 </aside>
109 <div id="main-content" class="main-content">125 <div id="main-content" class="main-content">
@@ -279,6 +295,39 @@
279 </div>295 </div>
280 </div> <!-- app-startup-section -->296 </div> <!-- app-startup-section -->
281297
298 <div id="scalability-section" class="content-section">
299 <div class="panel panel-default">
300 <div class="panel-heading"><h1>Scalability Report</h1></div>
301 <div class="panel-body">
302 <div class="well well-sm">
303 <h3 id="scalability-device"></h3>
304 <h3 id="scalability-channel"></h3>
305 <h3 id="scalability-build-number"></h3>
306 <p><font color="#FFBF00">Load 1024 elements</font></p>
307 <p><font color="#81F7D8">Load 2048 elements</font></p>
308 <p><font color="#9F81F7">Load 4096 elements</font></p>
309 <p><font color="#F781BE">Load 8192 elements</font></p>
310 <p><font color="#0040FF">Load 16384 elements</font></p>
311 </div>
312 <table id="ScalabilityTable" class="table table-striped table-bordered"></table>
313 </div>
314 </div>
315 </div> <!-- scalability-section -->
316
317 <div id="performance-section" class="content-section">
318 <div class="panel panel-default">
319 <div class="panel-heading"><h1>Performance Report</h1></div>
320 <div class="panel-body">
321 <div class="well well-sm">
322 <h3 id="performance-device"></h3>
323 <h3 id="performance-channel"></h3>
324 <h3 id="performance-build-number"></h3>
325 </div>
326 <table id="PerformanceTable" class="table table-striped table-bordered"></table>
327 </div>
328 </div>
329 </div> <!-- performance-section -->
330
282 <div id="applications-manual-section" class="content-section">331 <div id="applications-manual-section" class="content-section">
283 <div class="panel panel-default">332 <div class="panel panel-default">
284 <div class="panel-heading"><h1>Tests by Application Manual</h1></div>333 <div class="panel-heading"><h1>Tests by Application Manual</h1></div>
@@ -326,25 +375,15 @@
326 </div> <!-- main-content -->375 </div> <!-- main-content -->
327376
328 <script>377 <script>
329 $(function () {378 // show only the content-section of id matching hash
330 var links = $('.sidebar-links > div');379 window.onhashchange = function (e) {
331 links.on('click', function () {380 moveToSection();
332 links.removeClass('selected');381 }
333 $(this).addClass('selected');382
334 });383 window.onload = function() {
335 });384 selectSection()
336 </script>385 drawSections(true);
337 <script>386 }
338
339// show only the content-section of id matching hash
340$(window).on("hashchange", function (e) {
341 moveToSection();
342
343});
344
345window.onload = function() {
346 drawSections(true);
347}
348 </script>387 </script>
349</body>388</body>
350</html>389</html>
351390
=== modified file 'qakit/dashboard/js/appstartup.js'
--- qakit/dashboard/js/appstartup.js 2016-02-12 17:24:39 +0000
+++ qakit/dashboard/js/appstartup.js 2016-04-22 20:41:08 +0000
@@ -3,7 +3,7 @@
3function drawAppStartUpSection(firstDraw) {3function drawAppStartUpSection(firstDraw) {
4 $.getJSON("./data/app_startup_report.json", function(data) {4 $.getJSON("./data/app_startup_report.json", function(data) {
5 if (firstDraw){5 if (firstDraw){
6 process(data);6 process_app_startup(data);
7 }7 }
8 }).fail(function(data, status) {8 }).fail(function(data, status) {
9 console.log("Hiding Appstartup section");9 console.log("Hiding Appstartup section");
@@ -12,7 +12,7 @@
12 });12 });
13}13}
1414
15function process(data) {15function process_app_startup(data) {
16 $('#app-startup-channel').text('Channel: ' + data.channel);16 $('#app-startup-channel').text('Channel: ' + data.channel);
17 $('#app-startup-device').text('Device: ' + data.device);17 $('#app-startup-device').text('Device: ' + data.device);
18 $('#app-startup-build-number').text('Build Number: ' + data.build_number);18 $('#app-startup-build-number').text('Build Number: ' + data.build_number);
@@ -22,7 +22,7 @@
22 title = charts[chart].title22 title = charts[chart].title
23 path = "./data/" + basename(charts[chart].path)23 path = "./data/" + basename(charts[chart].path)
24 id = title.toLowerCase() + "-app-startup-section"24 id = title.toLowerCase() + "-app-startup-section"
25 section = '<div class="app-startup-elem" id="' + id + '" parent-section="app-startup-section"><h2>' + title + '</h2><img src="' + path + '"/></div>'25 section = '<div class="perf-elem" id="' + id + '" parent-section="app-startup-section"><h2>' + title + '</h2><img src="' + path + '"/></div>'
26 $( "#AppStartUpTable" ).append(section);26 $( "#AppStartUpTable" ).append(section);
2727
28 link = '<li><a href="#' + id + '">' + title + '</a></li>'28 link = '<li><a href="#' + id + '">' + title + '</a></li>'
2929
=== modified file 'qakit/dashboard/js/main.js'
--- qakit/dashboard/js/main.js 2016-02-11 21:30:40 +0000
+++ qakit/dashboard/js/main.js 2016-04-22 20:41:08 +0000
@@ -9,6 +9,8 @@
9 drawApplicationsSection();9 drawApplicationsSection();
10 drawDomainsSection();10 drawDomainsSection();
11 drawAppStartUpSection(firstDraw);11 drawAppStartUpSection(firstDraw);
12 drawScalabilitySection(firstDraw)
13 drawPerformanceSection(firstDraw)
1214
13 hideUnusedLinks();15 hideUnusedLinks();
14}16}
@@ -49,4 +51,12 @@
49 if (!window.location.hash){51 if (!window.location.hash){
50 drawSections(false);52 drawSections(false);
51 }53 }
54}
55
56function selectSection() {
57 var links = $(".sidebar-links > div");
58 links.on('click', function () {
59 links.removeClass('selected');
60 $(this).addClass('selected');
61 });
52}62}
53\ No newline at end of file63\ No newline at end of file
5464
=== added file 'qakit/dashboard/js/performance.js'
--- qakit/dashboard/js/performance.js 1970-01-01 00:00:00 +0000
+++ qakit/dashboard/js/performance.js 2016-04-22 20:41:08 +0000
@@ -0,0 +1,31 @@
1
2
3function drawPerformanceSection(firstDraw) {
4 $.getJSON("./data/performance_report.json", function(data) {
5 if (firstDraw){
6 process_performance(data);
7 }
8 }).fail(function(data, status) {
9 console.log("Hiding Performance section");
10 $("#performance-section").hide();
11 $("#performance-link").hide();
12 });
13}
14
15function process_performance(data) {
16 $('#performance-channel').text('Channel: ' + data.channel);
17 $('#performance-device').text('Device: ' + data.device);
18 $('#performance-build-number').text('Build Number: ' + data.build_number);
19
20 charts = data.charts
21 for (var chart in charts) {
22 title = charts[chart].title
23 path = "./data/" + basename(charts[chart].path)
24 id = title.toLowerCase() + "-performance-section"
25 section = '<div class="perf-elem" id="' + id + '" parent-section="performance-section"><h2>' + title + '</h2><img src="' + path + '"/></div>'
26 $( "#PerformanceTable" ).append(section);
27
28 link = '<li><a href="#' + id + '">' + title + '</a></li>'
29 $( "#performance-sublinks" ).append(link);
30 }
31}
032
=== added file 'qakit/dashboard/js/scalability.js'
--- qakit/dashboard/js/scalability.js 1970-01-01 00:00:00 +0000
+++ qakit/dashboard/js/scalability.js 2016-04-22 20:41:08 +0000
@@ -0,0 +1,31 @@
1
2
3function drawScalabilitySection(firstDraw) {
4 $.getJSON("./data/scalability_report.json", function(data) {
5 if (firstDraw){
6 process_scalability(data);
7 }
8 }).fail(function(data, status) {
9 console.log("Hiding Scalability section");
10 $("#scalability-section").hide();
11 $("#scalability-link").hide();
12 });
13}
14
15function process_scalability(data) {
16 $('#scalability-channel').text('Channel: ' + data.channel);
17 $('#scalability-device').text('Device: ' + data.device);
18 $('#scalability-build-number').text('Build Number: ' + data.build_number);
19
20 charts = data.charts
21 for (var chart in charts) {
22 title = charts[chart].title
23 path = "./data/" + basename(charts[chart].path)
24 id = title.toLowerCase() + "-scalability-section"
25 section = '<div class="perf-elem" id="' + id + '" parent-section="scalability-section"><h2>' + title.replace('_', ' ') + '</h2><img src="' + path + '"/></div>'
26 $( "#ScalabilityTable" ).append(section);
27
28 link = '<li><a href="#' + id + '">' + title.replace('_', ' ') + '</a></li>'
29 $( "#scalability-sublinks" ).append(link);
30 }
31}
032
=== added directory 'qakit/perf'
=== added file 'qakit/perf/__init__.py'
--- qakit/perf/__init__.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/__init__.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,30 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import configparser
19
20from qakit import config as qakit_config
21
22
23def _read_config(config_filepath):
24 """ Parse the config at the given filepath and return it """
25 config_file = configparser.ConfigParser()
26 config_file.read(config_filepath)
27 return config_file
28
29
30config_dict = _read_config(qakit_config.get_config_file_location())
031
=== added directory 'qakit/perf/engine'
=== added file 'qakit/perf/engine/__init__.py'
--- qakit/perf/engine/__init__.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/engine/__init__.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,16 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
017
=== added file 'qakit/perf/engine/dao.py'
--- qakit/perf/engine/dao.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/engine/dao.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,104 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import json
19import logging
20import requests
21from qakit.perf import config_dict as config
22
23BASE_URL = 'http://{}:{}/{}'.format(config['PERF']['EVE_HOST'],
24 config['PERF']['EVE_PORT'],
25 config['PERF']['EVE_PATH'])
26
27logger = logging.getLogger(__name__)
28
29
30def get_headers():
31 return {'Content-Type': 'application/json'}
32
33
34def _get_where_param(**params):
35 """ Get a where parameter used to filter when getting a query to mongo
36 :param params: A dict with all the parameters used to filter
37 """
38 where_param = "{"
39 for (k, v) in params.items():
40 where_param += '"{}":"{}",'.format(k, v)
41 return where_param[:-1] + '}'
42
43
44def save_record_to_db(global_info, records):
45 """ Save the record information in the mongo db. The information saved is
46 composed by joining both dicts received as parameter
47 """
48 logger.info('Saving records to db')
49 for record in records:
50 record.update(global_info)
51 r = requests.post('{}'.format(BASE_URL), data=json.dumps(record),
52 headers=get_headers())
53 if not r.status_code == requests.codes.created:
54 logger.error('Error uploading record: {}'.format(record))
55 logger.error(r.status_code, r.reason)
56
57
58def is_run_saved(run_id):
59 """ Indicate if there is at least one record with the run_id saved in the
60 mongo db
61 """
62 where_param = _get_where_param(run_id=run_id)
63 r = requests.get(BASE_URL, params={'where': where_param},
64 headers=get_headers())
65 if r.status_code == requests.codes.ok:
66 return r.json()['_meta']['total'] > 0
67 else:
68 logger.error('Error retrieving information for run: {}'.format(run_id))
69 logger.error(r.status_code, r.reason)
70
71
72def get_records_for_key(key, value, device, channel):
73 """ Retrieve the records from the mongo db for an specific key and value,
74 device and channel. The records by default are ordered by build number
75 :param device: The device used to filter the results
76 :param channel: The channel used to filter the results
77 :param key: the key used to filter the results
78 :param value: the value used to filter the results
79
80 """
81 args = {'device': device, 'channel': channel, key: value}
82 where_param = _get_where_param(**args)
83
84 stop = False
85 page_iter = 1
86 records = []
87 while not stop:
88 r = requests.get(BASE_URL,
89 params={'where': where_param, 'max_results': 50,
90 'page': page_iter},
91 headers=get_headers())
92 if r.status_code == requests.codes.ok:
93 records.extend(r.json()['_items'])
94 else:
95 logger.error('Error retrieving data from url: {}'.format(r.url))
96 logger.error(r.status_code, r.reason)
97
98 metadata = r.json()['_meta']
99 if metadata['max_results'] * metadata['page'] >= metadata['total']:
100 stop = True
101 else:
102 page_iter += 1
103
104 return records
0105
=== added file 'qakit/perf/engine/data_processor.py'
--- qakit/perf/engine/data_processor.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/engine/data_processor.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,81 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import statistics
19from qakit.perf import config_dict as config
20
21BUILDS_TO_PLOT = config['PERF']['BUILDS_TO_PLOT']
22
23
24class BuildTime:
25
26 def __init__(self, build, time, stdev):
27 self.build = build
28 self.time = time
29 self.stdev = stdev
30
31
32def get_times_by_build(records, key, values):
33 """ Calculate the times for the different builds and values
34 :param records: the set of time records used to get the desired times
35 :param key: the key to filter
36 :param values: the values to group the results
37 :return: a dict of BuildTime objects by key
38 """
39 build_times = {}
40 for record in records:
41 build = record['build_number']
42 if build in build_times:
43 build_times[build].append(record)
44 else:
45 build_times[build] = [record]
46
47 times = {}
48 for value in values:
49 times[value] = _process_build_times(build_times, key, value)
50
51 return times
52
53
54def _process_build_times(build_times, key, value):
55 """ Process and filter build times """
56 times = []
57 for build in sorted(build_times)[- int(BUILDS_TO_PLOT):]:
58 times_by_key = _get_times_by_key(build_times[build], key, value)
59
60 times.append(BuildTime(build,
61 _get_average(times_by_key),
62 _get_stdev(times_by_key)))
63 return times
64
65
66def _get_average(times):
67 if times:
68 return statistics.mean(times)
69 else:
70 return 0
71
72
73def _get_stdev(times):
74 if len(times) >= 2:
75 return statistics.stdev(times)
76 else:
77 return 0
78
79
80def _get_times_by_key(records, key, value):
81 return [record['time'] for record in records if record[key] == value]
082
=== added file 'qakit/perf/engine/parser.py'
--- qakit/perf/engine/parser.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/engine/parser.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,72 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import ast
19import os
20
21
22def parse_log_times(log_lines, times_class):
23 """ Retrieve the global run information and a list with all the history
24 instances parsed from the results_list in the subunit test log
25 :param log_lines: the lines in the log
26 :param times_class: the class used to filter the times to consider
27 :return: the global information of the run and the times for this run
28 """
29 tests_times = []
30 global_info = _get_global_information(log_lines)
31
32 times = _get_times(log_lines, times_class)
33 if global_info and times:
34 tests_times.extend(times)
35 return global_info, tests_times
36
37
38def parse_log_file(log_file):
39 """ Return a list of lines for the log """
40 if not os.path.exists(log_file):
41 raise RuntimeError('Log file does not exist {}'.format(log_file))
42 with open(log_file, 'r') as file:
43 return file.read().splitlines()
44
45
46def _get_global_information(log_lines):
47 markers = [line for line in log_lines if '<=' in line]
48 if markers:
49 values = markers[0].split('<=')[1].strip()
50 return ast.literal_eval(values)
51 raise RuntimeError('Global info not provided for tests')
52
53
54def _get_results_test_logs(results):
55 test_logs = []
56 for result in results:
57 test_log = result['details'].get('test-log')
58 if test_log:
59 test_logs.append(test_log.as_text().split('\n'))
60 return test_logs
61
62
63def _get_times(test_log, times_class):
64 markers = [line for line in test_log if '=>' in line]
65 times = []
66
67 for mark in markers:
68 values = mark.split('=>')[1].strip()
69 values_dict = ast.literal_eval(values)
70 if 'class' in values_dict and values_dict['class'] == times_class:
71 times.append(values_dict)
72 return times
073
=== added file 'qakit/perf/engine/plotter.py'
--- qakit/perf/engine/plotter.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/engine/plotter.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,93 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18
19import numpy as np
20import os
21
22# Force matplotlib to not use any Xwindows backend.
23import matplotlib
24matplotlib.use('Agg')
25import matplotlib.pyplot as plt # NOQA
26
27
28class TimesBar:
29
30 def __init__(self, times, stdevs, title, color, stdevcolor):
31 self.times = times
32 self.stdevs = stdevs
33 self.title = title
34 self.color = color
35 self.stdevcolor = stdevcolor
36
37
38def _get_xspacing(width, bars):
39 if len(bars) <= 1:
40 return width / 2
41 else:
42 return (len(bars) - 1) * width
43
44
45def plot_records(name, title, builds, bars, plots_dir):
46 """ Generate the graphic and return the graphic path.
47 It is supposed the records are already ordered by build number.
48 :param name: The name which identifies the graph
49 :param title: The title for the graph
50 :param builds: The list of build numbers to plot
51 :param bars: A list of TimesBar objects used to plot bars
52 :param plots_dir: The dir where the graph is stored
53 :return: The destination path of the created graph
54 """
55
56 # Set descriptions
57 fig, ax = plt.subplots()
58 ax.set_title(title)
59 ax.set_xlabel('Build Number')
60 ax.set_ylabel('Time (seconds)')
61
62 # Config the graphic
63 ind = np.arange(len(builds))
64 width = 0.35
65
66 ax.set_xticks(ind + _get_xspacing(width, bars))
67 ax.set_xticklabels(builds)
68 ax.set_xmargin(0.1)
69 ax.yaxis.grid(True)
70
71 rects = []
72 for bar in bars:
73 if bar.times:
74 rects.append(ax.bar(ind + bars.index(bar) * width, bar.times,
75 width, color=bar.color, yerr=bar.stdevs,
76 ecolor=bar.stdevcolor, capsize=5,
77 error_kw={'elinewidth': 2}))
78
79 # Shrink current axis's height by 10% on the bottom
80 box = ax.get_position()
81 ax.set_position([box.x0, box.y0 + box.height * 0.1,
82 box.width, box.height * 0.9])
83
84 if rects and len(rects) <= 2:
85 ax.legend([rect[0] for rect in rects],
86 [bar.title for bar in bars],
87 loc='upper center', bbox_to_anchor=(0.85, -0.05),
88 fancybox=True, shadow=True)
89
90 # Save the graphic
91 dst_file = os.path.join(plots_dir, 'barchart_{}.png'.format(name))
92 plt.savefig(dst_file)
93 return dst_file
094
=== added file 'qakit/perf/engine/report.py'
--- qakit/perf/engine/report.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/engine/report.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,54 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import json
19import os
20
21
22def create_json_report(out_files, report_dir, report_file, report_info):
23 report_path = os.path.join(report_dir, report_file)
24
25 data = dict()
26 data['channel'] = report_info['channel']
27 data['device'] = report_info['device']
28 data['build_number'] = report_info['build_number']
29
30 charts = []
31 if os.path.exists(report_path):
32 charts = get_charts_already_created(report_path, data)
33 os.remove(report_path)
34
35 for name, path in out_files.items():
36 charts.append({"title": name.title().replace('_', ' '), "path": path})
37 data['charts'] = charts
38
39 with open(report_path, "w") as report:
40 report.write(json.dumps(data, ensure_ascii=False))
41
42
43def get_charts_already_created(report_path, current_data):
44 try:
45 with open(report_path) as report_file:
46 report_data = json.load(report_file)
47 if report_data['channel'] == current_data['channel'] and \
48 report_data['device'] == current_data['device'] and \
49 report_data['build_number'] == current_data['build_number']:
50 return report_data['charts']
51 else:
52 return []
53 except Exception:
54 return []
055
=== added directory 'qakit/perf/eve'
=== added file 'qakit/perf/eve/__init__.py'
--- qakit/perf/eve/__init__.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/eve/__init__.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,16 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
017
=== added file 'qakit/perf/eve/run.py'
--- qakit/perf/eve/run.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/eve/run.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,24 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18from eve import Eve
19
20app = Eve()
21
22
23if __name__ == '__main__':
24 app.run()
025
=== added file 'qakit/perf/eve/settings.py'
--- qakit/perf/eve/settings.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/eve/settings.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,92 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import os
19
20MONGO_HOST = os.getenv('EVE_MONGO_HOST', '127.0.0.1')
21MONGO_PORT = os.getenv('EVE_MONGO_PORT', '27017')
22MONGO_USERNAME = os.getenv('EVE_MONGO_USERNAME', 'ubuntuUser')
23MONGO_PASSWORD = os.getenv('EVE_MONGO_PASSWORD', 'ubuntuPassword')
24MONGO_DBNAME = os.getenv('EVE_MONGO_DBNAME', 'test')
25SERVER_NAME = os.getenv('EVE_SERVER_NAME', None)
26
27perf_schema = {
28 # Schema definition, based on Cerberus grammar. Check the Cerberus project
29 # (https://github.com/nicolaiarocci/cerberus) for details.
30 'run_id': {
31 'type': 'string',
32 'required': True,
33 },
34 'class': {
35 'type': 'string',
36 'required': True,
37 },
38 'time': {
39 'type': 'number',
40 'required': True,
41 },
42 'type': {
43 'type': 'string',
44 'required': False,
45 },
46 'name': {
47 'type': 'string',
48 'required': True,
49 },
50 'load': {
51 'type': 'number',
52 'required': False,
53 },
54 'build_number': {
55 'type': 'string',
56 'required': True,
57 },
58 'device': {
59 'type': 'string',
60 'required': True,
61 },
62 'channel': {
63 'type': 'string',
64 'required': True,
65 },
66}
67
68perf = {
69 # 'title' tag used in item links. Defaults to the resource title minus
70 # the final, plural 's' (works fine in most cases but not for 'people')
71 'item_title': 'perf',
72
73 # by default the standard item entry point is defined as
74 # '/people/<ObjectId>'. We leave it untouched, and we also enable an
75 # additional read-only entry point. This way consumers can also perform
76 # GET requests at '/people/<lastname>'.
77 'additional_lookup': {
78 'url': 'regex("[\w]+")',
79 'field': 'name'
80 },
81
82 # We choose to override global cache-control directives for this resource.
83 'cache_control': 'max-age=10,must-revalidate',
84 'cache_expires': 10,
85
86 # most global settings can be overridden at resource level
87 'schema': perf_schema,
88}
89
90DOMAIN = {'perf': perf}
91RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
92ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']
093
=== added directory 'qakit/perf/orchestration'
=== added file 'qakit/perf/orchestration/__init__.py'
--- qakit/perf/orchestration/__init__.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/orchestration/__init__.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,84 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18from argparse import ArgumentParser
19import logging
20import os
21
22from qakit.practitest import subunit_results
23from qakit.perf import config_dict as config
24from qakit.perf.engine import dao, parser
25
26logger = logging.getLogger(__name__)
27
28json_report = config['PERF']['JSON_REPORT']
29
30
31def save_results_if_needed(global_info, times):
32 if times:
33 run_id = global_info['run_id']
34 if not dao.is_run_saved(run_id):
35 dao.save_record_to_db(global_info, times)
36 else:
37 logger.warning(
38 'Test results already in the db for the run_id {}'.format(
39 run_id))
40
41
42def collect_records_by_name(global_info, allowed_names):
43 records = {}
44 device = global_info['device']
45 channel = global_info['channel']
46 for name in allowed_names:
47 records[name] = dao.get_records_for_key('name', name, device, channel)
48 return records
49
50
51def _parse_input(input_class, parser_method):
52 args_parser = ArgumentParser("Parse times from logs.")
53 args_parser.add_argument("log")
54 args = args_parser.parse_args()
55
56 log_lines = parser_method(args.log)
57 output_dir = os.path.dirname(args.log)
58 return parser.parse_log_times(log_lines, input_class), output_dir
59
60
61def parse_subunit_input(input_class):
62 return _parse_input(input_class, subunit_results.get_log_lines)
63
64
65def parse_text_input(input_class):
66 return _parse_input(input_class, parser.parse_log_file)
67
68
69def create_graphics_dir(results_dir, dirname):
70 dir = os.path.join(results_dir, dirname)
71 if not os.path.exists(dir):
72 os.mkdir(dir)
73 return dir
74
75
76def get_from_build_times(build_times, key, attr):
77 """ Get the list of values stored in the attr for every BuildTime stored
78 for the desired key in the build_times
79 """
80 return [getattr(time, attr) for time in build_times[key]]
81
82
83def strip_string_list(string_list):
84 return [string.strip() for string in string_list.split(',')]
085
=== added file 'qakit/perf/orchestration/app_startup.py'
--- qakit/perf/orchestration/app_startup.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/orchestration/app_startup.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,79 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import logging
19
20from qakit.perf import config_dict as config
21from qakit.perf.engine import report, plotter, data_processor
22from qakit.perf import orchestration as orch
23
24logger = logging.getLogger(__name__)
25
26eve_path = 'app_startup'
27allowed_apps = orch.strip_string_list(config['APP_STARTUP']['ALLOWED_APPS'])
28types = orch.strip_string_list(config['APP_STARTUP']['TYPES'])
29titles = orch.strip_string_list(config['APP_STARTUP']['TITLES'])
30colors = orch.strip_string_list(config['APP_STARTUP']['COLORS'])
31ecolors = orch.strip_string_list(config['APP_STARTUP']['ECOLORS'])
32
33
34def _plot_records_by_apps(records_by_apps, results_dir):
35 app_plots = {}
36
37 for app in allowed_apps:
38 title = 'Times for {} app'.format(app)
39 build_times = data_processor.get_times_by_build(records_by_apps[app],
40 'type', types)
41 builds = [times.build for times in build_times[types[0]]]
42
43 bars = []
44 iter = 0
45 for type in types:
46 bars.append(plotter.TimesBar(
47 orch.get_from_build_times(build_times, type, 'time'),
48 orch.get_from_build_times(build_times, type, 'stdev'),
49 titles[iter],
50 '#{}'.format(colors[iter]),
51 '#{}'.format(ecolors[iter])))
52 iter += 1
53
54 app_plots[app] = plotter.plot_records(app, title, builds, bars,
55 results_dir)
56
57 return app_plots
58
59
60def generate_reports(output_dir, global_info, records_by_apps):
61 if orch.json_report:
62 graphics_dir = orch.create_graphics_dir(output_dir, 'appstartup_data')
63 pics = _plot_records_by_apps(records_by_apps, graphics_dir)
64 report.create_json_report(pics, output_dir,
65 report_file='app_startup_report.json',
66 report_info=global_info)
67
68
69def main():
70 (global_info, startup_times), output_dir = orch.parse_subunit_input(
71 'app_startup')
72
73 orch.save_results_if_needed(global_info, startup_times)
74 records_by_apps = orch.collect_records_by_name(global_info, allowed_apps)
75 generate_reports(output_dir, global_info, records_by_apps)
76
77
78if __name__ == "__main__":
79 main()
080
=== added file 'qakit/perf/orchestration/performance.py'
--- qakit/perf/orchestration/performance.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/orchestration/performance.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,80 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import logging
19
20from qakit.perf import config_dict as config
21from qakit.perf.engine import report, plotter, data_processor
22from qakit.perf import orchestration as orch
23
24logger = logging.getLogger(__name__)
25
26eve_path = 'performance'
27allowed_scenarios = orch.strip_string_list(
28 config['PERFORMANCE']['PERFORMANCE_ALLOWED_SCENARIOS'])
29types = orch.strip_string_list(config['PERFORMANCE']['PERFORMANCE_TYPES'])
30titles = orch.strip_string_list(config['PERFORMANCE']['PERFORMANCE_TITLES'])
31colors = orch.strip_string_list(config['PERFORMANCE']['PERFORMANCE_COLORS'])
32ecolors = orch.strip_string_list(config['PERFORMANCE']['PERFORMANCE_ECOLORS'])
33
34
35def _plot_records_by_scenarios(records_by_apps, results_dir):
36 plots = {}
37
38 for scenario in allowed_scenarios:
39 title = 'Times for {} scenario'.format(scenario)
40 build_times = data_processor.get_times_by_build(
41 records_by_apps[scenario], 'type', types)
42 builds = [times.build for times in build_times[types[0]]]
43
44 bars = []
45 iter = 0
46 for type in types:
47 bars.append(plotter.TimesBar(
48 orch.get_from_build_times(build_times, type, 'time'),
49 orch.get_from_build_times(build_times, type, 'stdev'),
50 titles[iter],
51 '#{}'.format(colors[iter]),
52 '#{}'.format(ecolors[iter])))
53 iter += 1
54
55 plots[scenario] = plotter.plot_records(scenario, title, builds, bars,
56 results_dir)
57
58 return plots
59
60
61def generate_reports(output_dir, global_info, records):
62 if orch.json_report:
63 graphics_dir = orch.create_graphics_dir(output_dir, 'performance_data')
64 pics = _plot_records_by_scenarios(records, graphics_dir)
65 report.create_json_report(pics, output_dir,
66 report_file='performance_report.json',
67 report_info=global_info)
68
69
70def main():
71 (global_info, startup_times), output_dir = orch.parse_text_input(
72 'performance')
73
74 orch.save_results_if_needed(global_info, startup_times)
75 records = orch.collect_records_by_name(global_info, allowed_scenarios)
76 generate_reports(output_dir, global_info, records)
77
78
79if __name__ == "__main__":
80 main()
081
=== added file 'qakit/perf/orchestration/scalability.py'
--- qakit/perf/orchestration/scalability.py 1970-01-01 00:00:00 +0000
+++ qakit/perf/orchestration/scalability.py 2016-04-22 20:41:08 +0000
@@ -0,0 +1,78 @@
1#!/usr/bin/python3
2# UESQA Metrics
3# Copyright (C) 2016 Canonical
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import logging
19
20from qakit.perf import config_dict as config
21from qakit.perf.engine import report, plotter, data_processor
22from qakit.perf import orchestration as orch
23
24logger = logging.getLogger(__name__)
25
26allowed_names = orch.strip_string_list(config['SCALABILITY']['ALLOWED_NAMES'])
27loads = [int(load) for load in config['SCALABILITY']['LOADS'].split(',')]
28titles = orch.strip_string_list(config['SCALABILITY']['TITLES'])
29colors = orch.strip_string_list(config['SCALABILITY']['COLORS'])
30ecolors = orch.strip_string_list(config['SCALABILITY']['ECOLORS'])
31
32
33def _plot_records_by_name(records_by_apps, results_dir):
34 plots = {}
35
36 for name in allowed_names:
37 title = 'Times for {} scenario'.format(name.replace('_', ' '))
38 build_times = data_processor.get_times_by_build(records_by_apps[name],
39 'load', loads)
40 builds = [times.build for times in build_times[loads[0]]]
41
42 bars = []
43 iter = 0
44 for load in loads:
45 bars.append(plotter.TimesBar(
46 orch.get_from_build_times(build_times, load, 'time'),
47 orch.get_from_build_times(build_times, load, 'stdev'),
48 titles[iter],
49 '#{}'.format(colors[iter]),
50 '#{}'.format(ecolors[iter])))
51 iter += 1
52
53 plots[name] = plotter.plot_records(name, title, builds, bars,
54 results_dir)
55
56 return plots
57
58
59def generate_reports(output_dir, global_info, records_by_apps):
60 if orch.json_report:
61 graphics_dir = orch.create_graphics_dir(output_dir, 'scalability_data')
62 pics = _plot_records_by_name(records_by_apps, graphics_dir)
63 report.create_json_report(pics, output_dir,
64 report_file='scalability_report.json',
65 report_info=global_info)
66
67
68def main():
69 (global_info, scal_times), output_dir = orch.parse_subunit_input(
70 'scalability')
71
72 orch.save_results_if_needed(global_info, scal_times)
73 records_by_apps = orch.collect_records_by_name(global_info, allowed_names)
74 generate_reports(output_dir, global_info, records_by_apps)
75
76
77if __name__ == "__main__":
78 main()
079
=== modified file 'qakit/practitest/subunit_results.py'
--- qakit/practitest/subunit_results.py 2015-04-15 19:03:17 +0000
+++ qakit/practitest/subunit_results.py 2016-04-22 20:41:08 +0000
@@ -41,3 +41,18 @@
41 if result['id'] == test_name:41 if result['id'] == test_name:
42 return result42 return result
43 return None43 return None
44
45
46def get_log_lines(filepath):
47 """ Parse the subunit file at the given path and return all the lines in
48 the log details for all the tests
49 """
50 results = parse(filepath)
51 test_logs = []
52 for result in results:
53 test_log = result['details'].get('test-log')
54 if test_log:
55 test_logs.append(test_log.as_text().split('\n'))
56
57 log_lines = [line for sublist in test_logs for line in sublist]
58 return log_lines
4459
=== modified file 'setup.py'
--- setup.py 2015-12-09 11:33:15 +0000
+++ setup.py 2016-04-22 20:41:08 +0000
@@ -39,7 +39,7 @@
39 ('subunit-practitest-export = '39 ('subunit-practitest-export = '
40 'qakit.practitest.report_subunit_results_to_practitest:main'),40 'qakit.practitest.report_subunit_results_to_practitest:main'),
41 ('parse-app-startup = '41 ('parse-app-startup = '
42 'qakit.appstartup.parse_startup_times:main'),42 'qakit.perf.parse_startup_times:main'),
43 ]43 ]
44 }44 }
45)45)

Subscribers

People subscribed via source and target branches

to all changes: