##// END OF EJS Templates
tests: updated load tests CLIs
marcink -
r3811:0c0d4d8c stable
parent child Browse files
Show More
@@ -0,0 +1,2 b''
1 big/CPython
2 big/CPython/commits
@@ -1,69 +1,73 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import timeit
21 import timeit
22 import logging
23 import click
22
24
23 server = "localhost:5000"
25 log = logging.getLogger(__name__)
26
24
27
25 pages = [
28 @click.command()
26 "cpython",
29 @click.option('--server', help='Server url to connect to. e.g http://rc.local.com', required=True)
27 "cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Makefile.pre.in",
30 @click.option('--pages', help='load pages to visit from a file', required=True, type=click.File())
28 "cpython/changelog",
31 @click.option('--repeat', help='number of times to repeat', default=10, type=int)
29 "cpython/changeset/e0f681f4ade3af52915d5f32daac97ada580d71a",
32 def main(server, repeat, pages):
30 "cpython/compare/tag@v3.4.1rc1...tag@v3.4.1?target_repo=cpython",
33
31 "cpython/files/tip/",
34 print("Repeating each URL %d times\n" % repeat)
32 "cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Grammar",
35 pages = pages.readlines()
33 "",
36
34 "git",
37 for page_url in pages:
35 "git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/.gitignore",
38
36 "git/changelog",
39 url = "%s/%s" % (server, page_url.strip())
37 "git/changeset/d299e9e550c1bf8640907fdba1f03cc585ee71df",
40 print(url)
38 "git/compare/rev@1200...rev@1300?target_repo=git",
41
39 "git/files/tip/",
42 stmt = "requests.get('%s', timeout=120)" % url
40 "git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/.gitignore"
43 t = timeit.Timer(stmt=stmt, setup="import requests")
41 ]
42
44
43 svn_pages = [
45 result = t.repeat(repeat=repeat, number=1)
44 "svn-apache",
46 print(" %.3f (min) - %.3f (max) - %.3f (avg)\n" %
45 "svn-apache/annotate/672129/cocoon/trunk/README.txt",
47 (min(result), max(result), sum(result) / len(result)))
46 "svn-apache/changelog",
48
47 "svn-apache/changeset/1164362",
48 "svn-apache/compare/rev@1164350...rev@1164360?target_repo=svn-apache",
49 "svn-apache/compare/rev@1164300...rev@1164360?target_repo=svn-apache",
50 "svn-apache/files/tip/",
51 "svn-apache/files/1164363/cocoon/trunk/README.txt",
52 ]
53
49
54 # Uncomment to check also svn performance
50 if __name__ == '__main__':
55 # pages = pages + svn_pages
51 main()
52
53
54
55
56
57
58
56
59
57 repeat = 10
60
61
58
62
59 print("Repeating each URL x%d\n" % repeat)
63
60 for page in pages:
64
61 url = "http://%s/%s" % (server, page)
65
62 print(url)
63
66
64 stmt = "urllib2.urlopen('%s', timeout=120)" % url
67
65 t = timeit.Timer(stmt=stmt, setup="import urllib2")
68
69
66
70
67 result = t.repeat(repeat=repeat, number=1)
71
68 print("\t%.3f (min) - %.3f (max) - %.3f (avg)\n" %
72
69 (min(result), max(result), sum(result)/len(result)))
73
@@ -1,305 +1,306 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 VCS Performance measurement tool
22 VCS Performance measurement tool
23
23
24 Usage:
24 Usage:
25
25
26 - Check that required vcs keys can be found in ~/.hgrc and ~/.netrc
26 - Check that required vcs keys can be found in ~/.hgrc and ~/.netrc
27
27
28 - Start a local instance of RhodeCode Enterprise
28 - Start a local instance of RhodeCode Enterprise
29
29
30 - Launch the script:
30 - Launch the script:
31
31
32 TMPDIR=/tmp python vcs_performance.py \
32 TMPDIR=/tmp python vcs_performance.py \
33 --host=http://vm:5000 \
33 --host=http://vm:5000 \
34 --api-key=55c4a33688577da24183dcac5fde4dddfdbf18dc \
34 --api-key=55c4a33688577da24183dcac5fde4dddfdbf18dc \
35 --commits=10 --repositories=100 --log-level=info
35 --commits=10 --repositories=100 --log-level=info
36 """
36 """
37
37
38 import argparse
38 import argparse
39 import functools
39 import functools
40 import logging
40 import logging
41 import os
41 import os
42 import shutil
42 import shutil
43 import subprocess32
43 import subprocess32
44 import tempfile
44 import tempfile
45 import time
45 import time
46 from itertools import chain
46 from itertools import chain
47
47
48 from api import RCApi, ApiError
48 from api import RCApi, ApiError
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def timed(f):
54 def timed(f):
55 """Decorator that returns the time it took to execute the function."""
55 """Decorator that returns the time it took to execute the function."""
56 @functools.wraps(f)
56 @functools.wraps(f)
57 def wrapped_f(*args, **kwargs):
57 def wrapped_f(*args, **kwargs):
58 start_time = time.time()
58 start_time = time.time()
59 try:
59 try:
60 f(*args, **kwargs)
60 f(*args, **kwargs)
61 finally:
61 finally:
62 return time.time() - start_time
62 return time.time() - start_time
63
63
64 return wrapped_f
64 return wrapped_f
65
65
66
66
67 def mean(container):
67 def mean(container):
68 """Return the mean of the container."""
68 """Return the mean of the container."""
69 if not container:
69 if not container:
70 return -1.0
70 return -1.0
71 return sum(container) / len(container)
71 return sum(container) / len(container)
72
72
73
73
74 class Config(object):
74 class Config(object):
75 args = None
75 args = None
76
76
77 def __init__(self):
77 def __init__(self):
78 parser = argparse.ArgumentParser(description='Runs VCS load tests')
78 parser = argparse.ArgumentParser(description='Runs VCS load tests')
79 parser.add_argument(
79 parser.add_argument(
80 '--host', dest='host', action='store', required=True,
80 '--host', dest='host', action='store', required=True,
81 help='RhodeCode Enterprise host')
81 help='RhodeCode Enterprise host')
82 parser.add_argument(
82 parser.add_argument(
83 '--api-key', dest='api_key', action='store', required=True,
83 '--api-key', dest='api_key', action='store', required=True,
84 help='API Key')
84 help='API Key')
85 parser.add_argument(
85 parser.add_argument(
86 '--file-size', dest='file_size', action='store', required=False,
86 '--file-size', dest='file_size', action='store', required=False,
87 default=1, type=int, help='File size in MB')
87 default=1, type=int, help='File size in MB')
88 parser.add_argument(
88 parser.add_argument(
89 '--repositories', dest='repositories', action='store',
89 '--repositories', dest='repositories', action='store',
90 required=False, default=1, type=int,
90 required=False, default=1, type=int,
91 help='Number of repositories')
91 help='Number of repositories')
92 parser.add_argument(
92 parser.add_argument(
93 '--commits', dest='commits', action='store', required=False,
93 '--commits', dest='commits', action='store', required=False,
94 default=1, type=int, help='Number of commits')
94 default=1, type=int, help='Number of commits')
95 parser.add_argument(
95 parser.add_argument(
96 '--log-level', dest='log_level', action='store', required=False,
96 '--log-level', dest='log_level', action='store', required=False,
97 default='error', help='Logging level')
97 default='error', help='Logging level')
98 self.args = parser.parse_args()
98 self.args = parser.parse_args()
99
99
100 def __getattr__(self, attr):
100 def __getattr__(self, attr):
101 return getattr(self.args, attr)
101 return getattr(self.args, attr)
102
102
103
103
104 class Repository(object):
104 class Repository(object):
105 FILE_NAME_TEMPLATE = "test_{:09d}.bin"
105 FILE_NAME_TEMPLATE = "test_{:09d}.bin"
106
106
107 def __init__(self, name, base_path, api):
107 def __init__(self, name, base_path, api):
108 self.name = name
108 self.name = name
109 self.path = os.path.join(base_path, name)
109 self.path = os.path.join(base_path, name)
110 self.api = api
110 self.api = api
111 self.url = None
111
112
112 def create(self):
113 def create(self):
113 self._create_filesystem_repo(self.path)
114 self._create_filesystem_repo(self.path)
114 try:
115 try:
115 self.url = self.api.create_repo(
116 self.url = self.api.create_repo(self.name, self.TYPE, 'Performance tests')
116 self.name, self.TYPE, 'Performance tests')
117 except ApiError as e:
117 except ApiError as e:
118 log.error('api: {}'.format(e))
118 log.error('api: {}'.format(e))
119
119
120 def delete(self):
120 def delete(self):
121 self._delete_filesystem_repo()
121 self._delete_filesystem_repo()
122 try:
122 try:
123 self.api.delete_repo(self.name)
123 self.api.delete_repo(self.name)
124 except ApiError as e:
124 except ApiError as e:
125 log.error('api: {}'.format(e))
125 log.error('api: {}'.format(e))
126
126
127 def create_commits(self, number, file_size):
127 def create_commits(self, number, file_size):
128 for i in xrange(number):
128 for i in xrange(number):
129 file_name = self.FILE_NAME_TEMPLATE.format(i)
129 file_name = self.FILE_NAME_TEMPLATE.format(i)
130 log.debug("Create commit {}".format(file_name))
130 log.debug("Create commit[{}] {}".format(self.name, file_name))
131 self._create_file(file_name, file_size)
131 self._create_file(file_name, file_size)
132 self._create_commit(file_name)
132 self._create_commit(file_name)
133
133
134 @timed
134 @timed
135 def push(self):
135 def push(self):
136 raise NotImplementedError()
136 raise NotImplementedError()
137
137
138 @timed
138 @timed
139 def clone(self, destination_path):
139 def clone(self, destination_path):
140 raise NotImplementedError()
140 raise NotImplementedError()
141
141
142 @timed
142 @timed
143 def pull(self):
143 def pull(self):
144 raise NotImplementedError()
144 raise NotImplementedError()
145
145
146 def _run(self, *args):
146 def _run(self, *args):
147 command = [self.BASE_COMMAND] + list(args)
147 command = [self.BASE_COMMAND] + list(args)
148 process = subprocess32.Popen(
148 process = subprocess32.Popen(
149 command, stdout=subprocess32.PIPE, stderr=subprocess32.PIPE)
149 command, stdout=subprocess32.PIPE, stderr=subprocess32.PIPE)
150 return process.communicate()
150 return process.communicate()
151
151
152 def _create_file(self, name, size):
152 def _create_file(self, name, size):
153 file_name = os.path.join(self.path, name)
153 file_name = os.path.join(self.path, name)
154 with open(file_name, 'wb') as f:
154 with open(file_name, 'wb') as f:
155 f.write(os.urandom(1024))
155 f.write(os.urandom(1024))
156
156
157 def _delete_filesystem_repo(self):
157 def _delete_filesystem_repo(self):
158 shutil.rmtree(self.path)
158 shutil.rmtree(self.path)
159
159
160 def _create_filesystem_repo(self, path):
160 def _create_filesystem_repo(self, path):
161 raise NotImplementedError()
161 raise NotImplementedError()
162
162
163 def _create_commit(self, file_name):
163 def _create_commit(self, file_name):
164 raise NotImplementedError()
164 raise NotImplementedError()
165
165
166
166
167 class GitRepository(Repository):
167 class GitRepository(Repository):
168 TYPE = 'git'
168 TYPE = 'git'
169 BASE_COMMAND = 'git'
169 BASE_COMMAND = 'git'
170
170
171 @timed
171 @timed
172 def push(self):
172 def push(self):
173 os.chdir(self.path)
173 os.chdir(self.path)
174 self._run('push', '--set-upstream', self.url, 'master')
174 self._run('push', '--set-upstream', self.url, 'master')
175
175
176 @timed
176 @timed
177 def clone(self, destination_path):
177 def clone(self, destination_path):
178 self._run('clone', self.url, os.path.join(destination_path, self.name))
178 self._run('clone', self.url, os.path.join(destination_path, self.name))
179
179
180 @timed
180 @timed
181 def pull(self, destination_path):
181 def pull(self, destination_path):
182 path = os.path.join(destination_path, self.name)
182 path = os.path.join(destination_path, self.name)
183 self._create_filesystem_repo(path)
183 self._create_filesystem_repo(path)
184 os.chdir(path)
184 os.chdir(path)
185 self._run('remote', 'add', 'origin', self.url)
185 self._run('remote', 'add', 'origin', self.url)
186 self._run('pull', 'origin', 'master')
186 self._run('pull', 'origin', 'master')
187
187
188 def _create_filesystem_repo(self, path):
188 def _create_filesystem_repo(self, path):
189 self._run('init', path)
189 self._run('init', path)
190
190
191 def _create_commit(self, file_name):
191 def _create_commit(self, file_name):
192 os.chdir(self.path)
192 os.chdir(self.path)
193 self._run('add', file_name)
193 self._run('add', file_name)
194 self._run('commit', file_name, '-m', '"Add {}"'.format(file_name))
194 self._run('commit', file_name, '-m', '"Add {}"'.format(file_name))
195
195
196
196
197 class HgRepository(Repository):
197 class HgRepository(Repository):
198 TYPE = 'hg'
198 TYPE = 'hg'
199 BASE_COMMAND = 'hg'
199 BASE_COMMAND = 'hg'
200
200
201 @timed
201 @timed
202 def push(self):
202 def push(self):
203 os.chdir(self.path)
203 os.chdir(self.path)
204 self._run('push', self.url)
204 self._run('push', self.url)
205
205
206 @timed
206 @timed
207 def clone(self, destination_path):
207 def clone(self, destination_path):
208 self._run('clone', self.url, os.path.join(destination_path, self.name))
208 self._run('clone', self.url, os.path.join(destination_path, self.name))
209
209
210 @timed
210 @timed
211 def pull(self, destination_path):
211 def pull(self, destination_path):
212 path = os.path.join(destination_path, self.name)
212 path = os.path.join(destination_path, self.name)
213 self._create_filesystem_repo(path)
213 self._create_filesystem_repo(path)
214 os.chdir(path)
214 os.chdir(path)
215 self._run('pull', '-r', 'tip', self.url)
215 self._run('pull', '-r', 'tip', self.url)
216
216
217 def _create_filesystem_repo(self, path):
217 def _create_filesystem_repo(self, path):
218 self._run('init', path)
218 self._run('init', path)
219
219
220 def _create_commit(self, file_name):
220 def _create_commit(self, file_name):
221 os.chdir(self.path)
221 os.chdir(self.path)
222 self._run('add', file_name)
222 self._run('add', file_name)
223 self._run('commit', file_name, '-m', '"Add {}"'.format(file_name))
223 self._run('commit', file_name, '-m', '"Add {}"'.format(file_name))
224
224
225
225
226 class Benchmark(object):
226 class Benchmark(object):
227 REPO_CLASSES = {
227 REPO_CLASSES = {
228 'git': GitRepository,
228 'git': GitRepository,
229 'hg': HgRepository
229 'hg': HgRepository
230 }
230 }
231 REPO_NAME = '{}_performance_{:03d}'
231 REPO_NAME = '{}_performance_{:03d}'
232
232
233 def __init__(self, config):
233 def __init__(self, config):
234 self.api = RCApi(api_key=config.api_key, rc_endpoint=config.host)
234 self.api = RCApi(api_key=config.api_key, rc_endpoint=config.host)
235 self.source_path = tempfile.mkdtemp(suffix='vcsperformance')
235 self.source_path = tempfile.mkdtemp(suffix='vcsperformance')
236
236
237 self.config = config
237 self.config = config
238 self.git_repos = []
238 self.git_repos = []
239 self.hg_repos = []
239 self.hg_repos = []
240
240
241 self._set_log_level()
241 self._set_log_level()
242
242
243 def start(self):
243 def start(self):
244 self._create_repos()
244 self._create_repos()
245 repos = {
245 repos = {
246 'git': self.git_repos,
246 'git': self.git_repos,
247 'hg': self.hg_repos
247 'hg': self.hg_repos
248 }
248 }
249
249
250 clone_destination_path = tempfile.mkdtemp(suffix='clone')
250 clone_destination_path = tempfile.mkdtemp(suffix='clone')
251 pull_destination_path = tempfile.mkdtemp(suffix='pull')
251 pull_destination_path = tempfile.mkdtemp(suffix='pull')
252 operations = [
252 operations = [
253 ('push', ),
253 ('push', ),
254 ('clone', clone_destination_path),
254 ('clone', clone_destination_path),
255 ('pull', pull_destination_path)
255 ('pull', pull_destination_path)
256 ]
256 ]
257
257
258 for operation in operations:
258 for operation in operations:
259 for type_ in repos:
259 for type_ in repos:
260 times = self._measure(repos[type_], *operation)
260 times = self._measure(repos[type_], *operation)
261 print("Mean {} {} time: {:.3f} sec.".format(
261 print("Mean[of {}] {:5s} {:5s} time: {:.3f} sec.".format(
262 type_, operation[0], mean(times)))
262 len(times), type_, operation[0], mean(times)))
263
263
264 def cleanup(self):
264 def cleanup(self):
265 log.info("Cleaning up...")
265 log.info("Cleaning up...")
266 for repo in chain(self.git_repos, self.hg_repos):
266 for repo in chain(self.git_repos, self.hg_repos):
267 repo.delete()
267 repo.delete()
268
268
269 def _measure(self, repos, operation, *args):
269 def _measure(self, repos, operation, *args):
270 times = []
270 times = []
271 for repo in repos:
271 for repo in repos:
272 method = getattr(repo, operation)
272 method = getattr(repo, operation)
273 times.append(method(*args))
273 times.append(method(*args))
274 return times
274 return times
275
275
276 def _create_repos(self):
276 def _create_repos(self):
277 log.info("Creating repositories...")
277 log.info("Creating repositories...")
278 for i in xrange(self.config.repositories):
278 for i in xrange(self.config.repositories):
279 self.git_repos.append(self._create_repo('git', i))
279 self.git_repos.append(self._create_repo('git', i))
280 self.hg_repos.append(self._create_repo('hg', i))
280 self.hg_repos.append(self._create_repo('hg', i))
281
281
282 def _create_repo(self, type_, id_):
282 def _create_repo(self, type_, id_):
283 RepoClass = self.REPO_CLASSES[type_]
283 RepoClass = self.REPO_CLASSES[type_]
284 repo = RepoClass(
284 repo = RepoClass(
285 self.REPO_NAME.format(type_, id_), self.source_path, self.api)
285 self.REPO_NAME.format(type_, id_), self.source_path, self.api)
286 repo.create()
286 repo.create()
287 repo.create_commits(self.config.commits, self.config.file_size)
287 repo.create_commits(self.config.commits, self.config.file_size)
288 return repo
288 return repo
289
289
290 def _set_log_level(self):
290 def _set_log_level(self):
291 try:
291 try:
292 log_level = getattr(logging, config.log_level.upper())
292 log_level = getattr(logging, config.log_level.upper())
293 except:
293 except:
294 log_level = logging.ERROR
294 log_level = logging.ERROR
295 handler = logging.StreamHandler()
295 handler = logging.StreamHandler()
296 log.addHandler(handler)
296 log.addHandler(handler)
297 log.setLevel(log_level)
297 log.setLevel(log_level)
298
298
299
299 if __name__ == '__main__':
300 if __name__ == '__main__':
300 config = Config()
301 config = Config()
301 benchmark = Benchmark(config)
302 benchmark = Benchmark(config)
302 try:
303 try:
303 benchmark.start()
304 benchmark.start()
304 finally:
305 finally:
305 benchmark.cleanup()
306 benchmark.cleanup()
General Comments 0
You need to be logged in to leave comments. Login now