diff --git a/benchmarks/basic_commands.py b/benchmarks/basic_commands.py index 023a385a33abc5ddaa4d3833b0fed1dee67a91c4_YmVuY2htYXJrcy9iYXNpY19jb21tYW5kcy5weQ==..f93307cb26e7c4c5aeea0e1e9c79a65a9756ea42_YmVuY2htYXJrcy9iYXNpY19jb21tYW5kcy5weQ== 100644 --- a/benchmarks/basic_commands.py +++ b/benchmarks/basic_commands.py @@ -1,5 +1,4 @@ from __future__ import print_function -import errno import os import os.path @@ -4,4 +3,3 @@ import os import os.path -import pipes import time @@ -7,12 +5,3 @@ import time -import threading -import re -import shutil -import subprocess -import stat -import sys -import tempfile -import urllib -import pipes from .utils import ( @@ -17,6 +6,5 @@ from .utils import ( - BaseNChangesetsTestSuite, BaseTestSuite, params_as_kwargs, median, @@ -20,10 +8,5 @@ BaseTestSuite, params_as_kwargs, median, - REPOS_DIR, - STRIP_VARIANTS_LIST, - ROLES, - REPO_DETAILS, - not_compatible_with, ) @@ -28,10 +11,5 @@ ) -if sys.version_info[0] == 2: - import Queue as queue -else: - import queue - class TestSuite(BaseTestSuite): timeout = 120 @@ -184,5 +162,4 @@ # self.hg("bundle", "--base", ":(-%d)" % (changesets+1), "/tmp/bundle.bundle") # -class HgWeb(object): @@ -188,187 +165,4 @@ - def __init__(self): - super(HgWeb, self).__init__() - self.queue = queue.Queue() - self.proc = None - self.thread = None - - def start(self, hgpath, environ): - config = os.path.abspath(os.path.join( - os.path.dirname(__file__), os.pardir, 'hgweb.config')) - hgweb_cmd = [ - hgpath, 'serve', '--cwd', REPOS_DIR, - '-a', 'localhost', '-p', '0', - '--config', 'web.push_ssl=False', - '--config', 'web.allow_push=*', - '--webdir-conf', config] - self.proc = subprocess.Popen(hgweb_cmd, env=environ, - stdout=subprocess.PIPE) - # we have to read output in a thread to avoid deadlocks - self.thread = threading.Thread( - target=self._enqueue, args=(self.queue, self.proc.stdout)) - self.thread.daemon = True - self.thread.start() - # wait the server to be started - statusline = self.queue.get() - if not statusline: - self.stop() - raise RuntimeError('hg serve has crashed') - return re.search(':(\d+)', statusline).groups()[0] - - @staticmethod - def _enqueue(queue, fd): - while True: - data = fd.readline() - if not data: - break - queue.put(data) - queue.put(None) - - def stop(self): - self.proc.kill() - self.proc.wait() - self.thread.join() - - -class BaseExchangeMixin(object): - - # this help recovering from failure during setup - _hgserve = None - - def _remote_path_cmd(self, path): - if self.repo_type == 'local': - return [path] - elif self.repo_type == 'ssh': - with open('hg_wrapper', 'wb') as f: - f.write('#!/bin/sh\nexec env -i {} {} $*\n'.format( - ' '.join(['{}={}'.format(k, pipes.quote(v)) - for k, v in self.environ.items()]), - os.path.abspath(self.hgpath))) - st = os.stat('hg_wrapper') - os.chmod('hg_wrapper', st.st_mode | stat.S_IEXEC) - return [ - '--remotecmd', os.path.abspath('hg_wrapper'), - 'ssh://localhost/{}'.format(os.path.abspath(path))] - elif self.repo_type == 'http': - path = os.path.abspath(path) - repo_dir = os.path.abspath(REPOS_DIR) - assert path.startswith(REPOS_DIR), path - return ['http://localhost:{}/{}'.format( - self.hgport, path[len(repo_dir) + 1:])] - else: - raise NotImplementedError - - def _setup_repo_type(self, repo_type): - """setup a hgweb server if we have to""" - self._hgserve = None - if repo_type == 'http' and self.get_asv_rev() in self.get_skip()['hgweb']: - raise NotImplementedError - self.repo_type = repo_type - if repo_type == 'http': - self._hgserve = HgWeb() - self.hgport = self._hgserve.start(self.hgpath, self.environ) - - def _teardown_repo_type(self): - if self._hgserve is not None: - self._hgserve.stop() - self._hgserve = None - - def _setup_revset(self, revset): - """If the operation target a specific revision, resolve it beforehand""" - if revset is not None: - self.rev = self.hg('identify', '-i', '-r', revset).strip() - else: - self.rev = None - - def finalize_teardown(self, exc_info, current_params): - self._teardown_repo_type() - - -class classproperty(object): - def __init__(self, f): - self.f = f - def __get__(self, obj, owner): - return self.f(owner) - -def setup_role(cls): - """install the right partial variants for the configured action""" - cls.params = cls.params[:] - cls.params[cls._partials_idx] = list(sorted(cls.role_data)) - return cls - -class BaseExchangeTimeSuite(BaseExchangeMixin, BaseTestSuite): - # the exchange action we measure - role_action = None - # the subtypes of this action we measure - role_subtype = None - - param_names = BaseTestSuite.param_names + [ - 'repo_type', 'strip', 'revset'] - - params = BaseTestSuite.params + [['local', 'ssh', 'http']] - _partials_idx = len(params) - params += [STRIP_VARIANTS_LIST] - params += [[None, 'tip']] - timeout = 1800 - - @classproperty - def role_data(cls): - """{"partial-key" -> {"repo-key" -> {data}} map for the current role""" - if cls.role_action is None: - return None - if cls.role_subtype is None: - return None - return ROLES.get(cls.role_action, {}).get(cls.role_subtype, {}) - - def run(self, local_repo, command, remote_repo, expected_return_code=None): - if not isinstance(command, (list, tuple)): - command = [command] - cmd = ['--cwd', pipes.quote(local_repo)] + command - cmd.extend(self._remote_path_cmd(remote_repo)) - if self.rev: - cmd.extend(['-r', self.rev]) - if expected_return_code is None: - expected_return_code = 1 if self.partial_key == "same" else 0 - self.hg(*cmd, expected_return_code=expected_return_code) - - @params_as_kwargs - def setup(self, repo, repo_type, strip, revset, **kwargs): - self.partial_key = strip - super(BaseExchangeTimeSuite, self).setup(repo, **kwargs) - self._setup_repo_type(repo_type) - self._setup_revset(revset) - - @property - def clone_path(self): - return self.repo_path_from_id(self.partial_key) - - def repo_path_from_id(self, partial_id): - """Return the absolute path for a given partial - - "reference" is a special value that means the original repository. - """ - if partial_id == 'reference': - return self.repo_path - suffix = urllib.quote_plus(partial_id) - # We need to use the repo name here because the repo doesn't contains - # the hash - partial_name = '{}-partial-{}'.format(self.repo_name, suffix) - return os.path.join(REPOS_DIR, 'partial-references', partial_name) - - def teardown(self, *args, **kwargs): - self._teardown_repo_type() - - def _rsync(self, src, dst): - cmd = [ - 'rsync', - '--inplace', - '--no-whole-file', - '-aH', - '--delete', - '{}/'.format(src), - dst, - ] - self.check_output(*cmd) # class ExchangeTimeSuite(BaseExchangeTimeSuite): @@ -380,73 +174,6 @@ # self.run(self.repo_path, 'outgoing', self.clone_path) -# https://bz.mercurial-scm.org/show_bug.cgi?id=5851 -# long timeout and process leak -not_broken_hgweb = not_compatible_with( - revset="f0a851542a05::877185de62^", - filter_fn=lambda kwargs, current_version: kwargs['repo_type'] == 'http' -) - - -class BaseDiscoveryTimeSuite(BaseExchangeTimeSuite): - # debugdiscovery does not support revset argument - params = BaseTestSuite.params + [ - ['local', 'ssh', 'http'], - STRIP_VARIANTS_LIST, - [None]] - - def _track_discovery(self, *args, **kwargs): - data = self.role_data.get(kwargs['strip'], {}) - data = data.get(self.repo_name) - if data is None: - raise NotImplementedError("no roles data for this partials' key") - source = self.repo_path_from_id(data['source']) - target = self.repo_path_from_id(data['target']) - return self.perfext('perfdiscovery', '--repository', source, target) - -@setup_role -class DiscoveryIdenticalTimeSuite(BaseDiscoveryTimeSuite): - role_action = 'discovery' - role_subtype = 'identical' - - @params_as_kwargs - @not_broken_hgweb - def track_identical(self, *args, **kwargs): - return self._track_discovery(self, *args, **kwargs) - track_identical.benchmark_name = 'exchange.discovery.changesets.track_identical' - -@setup_role -class DiscoverySubsetTimeSuite(BaseDiscoveryTimeSuite): - role_action = 'discovery' - role_subtype = 'subset' - - @params_as_kwargs - @not_broken_hgweb - def track_discovery_subset(self, *args, **kwargs): - return self._track_discovery(self, *args, **kwargs) - track_discovery_subset.benchmark_name = 'exchange.discovery.changesets.track_subset' - -@setup_role -class DiscoverySupersetTimeSuite(BaseDiscoveryTimeSuite): - role_action = 'discovery' - role_subtype = 'superset' - - @params_as_kwargs - @not_broken_hgweb - def track_discovery_superset(self, *args, **kwargs): - return self._track_discovery(self, *args, **kwargs) - track_discovery_superset.benchmark_name = 'exchange.discovery.changesets.track_superset' - -@setup_role -class DiscoveryStandardTimeSuite(BaseDiscoveryTimeSuite): - role_action = 'discovery' - role_subtype = 'balanced' - - @params_as_kwargs - @not_broken_hgweb - def track_balanced(self, *args, **kwargs): - return self._track_discovery(self, *args, **kwargs) - track_balanced.benchmark_name = 'exchange.discovery.changesets.track_balanced' # class UnbundleTimeSuite(BaseExchangeTimeSuite): # params = BaseTestSuite.params + [ diff --git a/benchmarks/discovery.py b/benchmarks/discovery.py new file mode 100644 index 0000000000000000000000000000000000000000..f93307cb26e7c4c5aeea0e1e9c79a65a9756ea42_YmVuY2htYXJrcy9kaXNjb3ZlcnkucHk= --- /dev/null +++ b/benchmarks/discovery.py @@ -0,0 +1,73 @@ +from __future__ import print_function + +from .utils import ( + BaseTestSuite, + params_as_kwargs, + STRIP_VARIANTS_LIST, +) + +from .utils.exchange import ( + BaseExchangeTimeSuite, + not_broken_hgweb, + setup_role, +) + +class BaseDiscoveryTimeSuite(BaseExchangeTimeSuite): + # debugdiscovery does not support revset argument + params = BaseTestSuite.params + [ + ['local', 'ssh', 'http'], + STRIP_VARIANTS_LIST, + [None]] + + def _track_discovery(self, *args, **kwargs): + data = self.role_data.get(kwargs['strip'], {}) + data = data.get(self.repo_name) + if data is None: + raise NotImplementedError("no roles data for this partials' key") + source = self.repo_path_from_id(data['source']) + target = self.repo_path_from_id(data['target']) + return self.perfext('perfdiscovery', '--repository', source, target) + +@setup_role +class DiscoveryIdenticalTimeSuite(BaseDiscoveryTimeSuite): + role_action = 'discovery' + role_subtype = 'identical' + + @params_as_kwargs + @not_broken_hgweb + def track_identical(self, *args, **kwargs): + return self._track_discovery(self, *args, **kwargs) + track_identical.benchmark_name = 'exchange.discovery.changesets.track_identical' + +@setup_role +class DiscoverySubsetTimeSuite(BaseDiscoveryTimeSuite): + role_action = 'discovery' + role_subtype = 'subset' + + @params_as_kwargs + @not_broken_hgweb + def track_discovery_subset(self, *args, **kwargs): + return self._track_discovery(self, *args, **kwargs) + track_discovery_subset.benchmark_name = 'exchange.discovery.changesets.track_subset' + +@setup_role +class DiscoverySupersetTimeSuite(BaseDiscoveryTimeSuite): + role_action = 'discovery' + role_subtype = 'superset' + + @params_as_kwargs + @not_broken_hgweb + def track_discovery_superset(self, *args, **kwargs): + return self._track_discovery(self, *args, **kwargs) + track_discovery_superset.benchmark_name = 'exchange.discovery.changesets.track_superset' + +@setup_role +class DiscoveryStandardTimeSuite(BaseDiscoveryTimeSuite): + role_action = 'discovery' + role_subtype = 'balanced' + + @params_as_kwargs + @not_broken_hgweb + def track_balanced(self, *args, **kwargs): + return self._track_discovery(self, *args, **kwargs) + track_balanced.benchmark_name = 'exchange.discovery.changesets.track_balanced' diff --git a/benchmarks/utils/exchange.py b/benchmarks/utils/exchange.py new file mode 100644 index 0000000000000000000000000000000000000000..f93307cb26e7c4c5aeea0e1e9c79a65a9756ea42_YmVuY2htYXJrcy91dGlscy9leGNoYW5nZS5weQ== --- /dev/null +++ b/benchmarks/utils/exchange.py @@ -0,0 +1,218 @@ +from __future__ import print_function + +import os +import os.path +import pipes +import threading +import re +import subprocess +import stat +import sys +import urllib + +from . import ( + BaseTestSuite, + params_as_kwargs, + REPOS_DIR, + STRIP_VARIANTS_LIST, + ROLES, + BASEDIR, + not_compatible_with, +) + +if sys.version_info[0] == 2: + import Queue as queue +else: + import queue + +class classproperty(object): + def __init__(self, f): + self.f = f + def __get__(self, obj, owner): + return self.f(owner) + +def setup_role(cls): + """install the right partial variants for the configured action""" + cls.params = cls.params[:] + cls.params[cls._partials_idx] = list(sorted(cls.role_data)) + return cls + +# https://bz.mercurial-scm.org/show_bug.cgi?id=5851 +# long timeout and process leak +not_broken_hgweb = not_compatible_with( + revset="f0a851542a05::877185de62^", + filter_fn=lambda kwargs, current_version: kwargs['repo_type'] == 'http' +) + +class HgWeb(object): + + def __init__(self): + super(HgWeb, self).__init__() + self.queue = queue.Queue() + self.proc = None + self.thread = None + + def start(self, hgpath, environ): + config = os.path.abspath(os.path.join(BASEDIR, 'hgweb.config')) + hgweb_cmd = [ + hgpath, 'serve', '--cwd', REPOS_DIR, + '-a', 'localhost', '-p', '0', + '--config', 'web.push_ssl=False', + '--config', 'web.allow_push=*', + '--webdir-conf', config] + self.proc = subprocess.Popen(hgweb_cmd, env=environ, + stdout=subprocess.PIPE) + # we have to read output in a thread to avoid deadlocks + self.thread = threading.Thread( + target=self._enqueue, args=(self.queue, self.proc.stdout)) + self.thread.daemon = True + self.thread.start() + # wait the server to be started + statusline = self.queue.get() + if not statusline: + self.stop() + raise RuntimeError('hg serve has crashed') + return re.search(':(\d+)', statusline).groups()[0] + + @staticmethod + def _enqueue(queue, fd): + while True: + data = fd.readline() + if not data: + break + queue.put(data) + queue.put(None) + + def stop(self): + self.proc.kill() + self.proc.wait() + self.thread.join() + + +class BaseExchangeMixin(object): + + # this help recovering from failure during setup + _hgserve = None + + def _remote_path_cmd(self, path): + if self.repo_type == 'local': + return [path] + elif self.repo_type == 'ssh': + with open('hg_wrapper', 'wb') as f: + f.write('#!/bin/sh\nexec env -i {} {} $*\n'.format( + ' '.join(['{}={}'.format(k, pipes.quote(v)) + for k, v in self.environ.items()]), + os.path.abspath(self.hgpath))) + st = os.stat('hg_wrapper') + os.chmod('hg_wrapper', st.st_mode | stat.S_IEXEC) + return [ + '--remotecmd', os.path.abspath('hg_wrapper'), + 'ssh://localhost/{}'.format(os.path.abspath(path))] + elif self.repo_type == 'http': + path = os.path.abspath(path) + repo_dir = os.path.abspath(REPOS_DIR) + assert path.startswith(REPOS_DIR), path + return ['http://localhost:{}/{}'.format( + self.hgport, path[len(repo_dir) + 1:])] + else: + raise NotImplementedError + + def _setup_repo_type(self, repo_type): + """setup a hgweb server if we have to""" + self._hgserve = None + if repo_type == 'http' and self.get_asv_rev() in self.get_skip()['hgweb']: + raise NotImplementedError + self.repo_type = repo_type + if repo_type == 'http': + self._hgserve = HgWeb() + self.hgport = self._hgserve.start(self.hgpath, self.environ) + + def _teardown_repo_type(self): + if self._hgserve is not None: + self._hgserve.stop() + self._hgserve = None + + def _setup_revset(self, revset): + """If the operation target a specific revision, resolve it beforehand""" + if revset is not None: + self.rev = self.hg('identify', '-i', '-r', revset).strip() + else: + self.rev = None + + def finalize_teardown(self, exc_info, current_params): + self._teardown_repo_type() + + +class BaseExchangeTimeSuite(BaseExchangeMixin, BaseTestSuite): + # the exchange action we measure + role_action = None + # the subtypes of this action we measure + role_subtype = None + + param_names = BaseTestSuite.param_names + [ + 'repo_type', 'strip', 'revset'] + + params = BaseTestSuite.params + [['local', 'ssh', 'http']] + _partials_idx = len(params) + params += [STRIP_VARIANTS_LIST] + params += [[None, 'tip']] + timeout = 1800 + + @classproperty + def role_data(cls): + """{"partial-key" -> {"repo-key" -> {data}} map for the current role""" + if cls.role_action is None: + return None + if cls.role_subtype is None: + return None + return ROLES.get(cls.role_action, {}).get(cls.role_subtype, {}) + + def run(self, local_repo, command, remote_repo, expected_return_code=None): + if not isinstance(command, (list, tuple)): + command = [command] + cmd = ['--cwd', pipes.quote(local_repo)] + command + cmd.extend(self._remote_path_cmd(remote_repo)) + if self.rev: + cmd.extend(['-r', self.rev]) + if expected_return_code is None: + expected_return_code = 1 if self.partial_key == "same" else 0 + self.hg(*cmd, expected_return_code=expected_return_code) + + @params_as_kwargs + def setup(self, repo, repo_type, strip, revset, **kwargs): + self.partial_key = strip + super(BaseExchangeTimeSuite, self).setup(repo, **kwargs) + self._setup_repo_type(repo_type) + self._setup_revset(revset) + + @property + def clone_path(self): + return self.repo_path_from_id(self.partial_key) + + def repo_path_from_id(self, partial_id): + """Return the absolute path for a given partial + + "reference" is a special value that means the original repository. + """ + if partial_id == 'reference': + return self.repo_path + suffix = urllib.quote_plus(partial_id) + # We need to use the repo name here because the repo doesn't contains + # the hash + partial_name = '{}-partial-{}'.format(self.repo_name, suffix) + return os.path.join(REPOS_DIR, 'partial-references', partial_name) + + def teardown(self, *args, **kwargs): + self._teardown_repo_type() + + def _rsync(self, src, dst): + cmd = [ + 'rsync', + '--inplace', + '--no-whole-file', + '-aH', + '--delete', + '{}/'.format(src), + dst, + ] + self.check_output(*cmd)