Mercurial > hg
view mercurial/httppeer.py @ 20182:04036798ebed
branches: avoid unnecessary changectx.branch() calls
This requires reading from the changelog, which can be costly over NFS.
Note that this does not totally remove reading from the changelog; we
still do that when calling changectx.closesbranch(). That call will be
removed in a later patch.
Running hg branches on the PyPy repo (with 996) over a busy NFS server,
before this change:
$ time hg --profile branches > /dev/null
CallCount Recursive Total(s) Inline(s) module:lineno(function)
2042 0 2.2827 2.2827 <open>
2036 0 0.9840 0.9840 <method 'close' of 'file' objects>
2036 0 0.0464 0.0464 <method 'read' of 'file' objects>
5233 0 0.1985 0.0453 mercurial.repoview:161(changelog)
10462 0 0.0791 0.0314 mercurial.changelog:133(tip)
5233 0 0.0388 0.0176 mercurial.localrepo:26(__get__)
10462 0 0.0250 0.0126 <len>
5233 0 0.0059 0.0039 mercurial.repoview:112(filterrevs)
10462 0 0.0029 0.0029 <hash>
2034 0 0.0444 0.0444 <method 'seek' of 'file' objects>
5340 0 0.0390 0.0390 mercurial.revlog:296(rev)
2582 0 0.0371 0.0371 <zlib.decompress>
3155 0 0.1963 0.0366 mercurial.context:202(__init__)
3155 0 0.1238 0.0306 mercurial.repoview:161(changelog)
3155 0 0.0261 0.0080 mercurial.changelog:183(rev)
9465 0 0.0061 0.0061 <isinstance>
1096 0 0.0023 0.0023 <binascii.unhexlify>
4251 0 0.0014 0.0014 <len>
2059 0 3.7341 0.0332 mercurial.changelog:270(read)
2059 0 3.6304 0.0307 mercurial.revlog:907(revision)
2057 0 0.0262 0.0137 mercurial.changelog:28(decodeextra)
4118 0 0.0094 0.0094 <method 'split' of 'str' objects>
4118 0 0.0270 0.0048 mercurial.encoding:61(tolocal)
2059 0 0.0040 0.0040 <method 'index' of 'str' objects>
10462 0 0.0791 0.0314 mercurial.changelog:133(tip)
10462 0 0.0289 0.0207 mercurial.changelog:190(node)
10462 0 0.0188 0.0091 <len>
52433 20932 0.0478 0.0310 <len>
20932 0 0.0221 0.0168 mercurial.revlog:262(__len__)
2059 0 3.6304 0.0307 mercurial.revlog:907(revision)
real 0m4.361s
user 0m0.986s
sys 0m0.237s
After this change:
$ time hg --profile branches > /dev/null
CallCount Recursive Total(s) Inline(s) module:lineno(function)
1069 0 1.1098 1.1098 <open>
1063 0 0.4865 0.4865 <method 'close' of 'file' objects>
4122 0 0.1811 0.0404 mercurial.repoview:161(changelog)
8240 0 0.0712 0.0272 mercurial.changelog:133(tip)
4122 0 0.0378 0.0177 mercurial.localrepo:26(__get__)
8240 0 0.0221 0.0115 <len>
4122 0 0.0057 0.0033 mercurial.repoview:112(filterrevs)
8240 0 0.0025 0.0025 <hash>
3029 0 0.1979 0.0371 mercurial.context:202(__init__)
3029 0 0.1278 0.0310 mercurial.repoview:161(changelog)
3029 0 0.0230 0.0081 mercurial.changelog:183(rev)
9087 0 0.0061 0.0061 <isinstance>
1096 0 0.0026 0.0026 <binascii.unhexlify>
4125 0 0.0014 0.0014 <len>
4229 0 0.0337 0.0337 mercurial.revlog:296(rev)
1061 0 0.0296 0.0296 <method 'seek' of 'file' objects>
1063 0 0.0292 0.0292 <method 'read' of 'file' objects>
8240 0 0.0712 0.0272 mercurial.changelog:133(tip)
8240 0 0.0271 0.0196 mercurial.changelog:190(node)
8240 0 0.0169 0.0083 <len>
40476 16488 0.0422 0.0271 <len>
16488 0 0.0193 0.0152 mercurial.revlog:262(__len__)
1342 0 0.0241 0.0241 <zlib.decompress>
9445 0 0.0336 0.0224 mercurial.changelog:190(node)
9445 0 0.0112 0.0112 mercurial.revlog:317(node)
1074 0 1.9102 0.0224 mercurial.changelog:270(read)
1074 0 1.8397 0.0202 mercurial.revlog:907(revision)
1073 0 0.0187 0.0099 mercurial.changelog:28(decodeextra)
2148 0 0.0061 0.0061 <method 'split' of 'str' objects>
2148 0 0.0184 0.0034 mercurial.encoding:61(tolocal)
real 0m2.402s
user 0m0.735s
sys 0m0.177s
author | Brodie Rao <brodie@sf.io> |
---|---|
date | Fri, 15 Nov 2013 23:18:08 -0500 |
parents | a07be8953733 |
children | 167047ba3cfa |
line wrap: on
line source
# httppeer.py - HTTP repository proxy classes for mercurial # # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from node import nullid from i18n import _ import changegroup, statichttprepo, error, httpconnection, url, util, wireproto import os, urllib, urllib2, zlib, httplib import errno, socket def zgenerator(f): zd = zlib.decompressobj() try: for chunk in util.filechunkiter(f): while chunk: yield zd.decompress(chunk, 2**18) chunk = zd.unconsumed_tail except httplib.HTTPException: raise IOError(None, _('connection ended unexpectedly')) yield zd.flush() class httppeer(wireproto.wirepeer): def __init__(self, ui, path): self.path = path self.caps = None self.handler = None self.urlopener = None u = util.url(path) if u.query or u.fragment: raise util.Abort(_('unsupported URL component: "%s"') % (u.query or u.fragment)) # urllib cannot handle URLs with embedded user or passwd self._url, authinfo = u.authinfo() self.ui = ui self.ui.debug('using %s\n' % self._url) self.urlopener = url.opener(ui, authinfo) def __del__(self): if self.urlopener: for h in self.urlopener.handlers: h.close() getattr(h, "close_all", lambda : None)() def url(self): return self.path # look up capabilities only when needed def _fetchcaps(self): self.caps = set(self._call('capabilities').split()) def _capabilities(self): if self.caps is None: try: self._fetchcaps() except error.RepoError: self.caps = set() self.ui.debug('capabilities: %s\n' % (' '.join(self.caps or ['none']))) return self.caps def lock(self): raise util.Abort(_('operation not supported over http')) def _callstream(self, cmd, **args): if cmd == 'pushkey': args['data'] = '' data = args.pop('data', None) size = 0 if util.safehasattr(data, 'length'): size = data.length elif data is not None: size = len(data) headers = args.pop('headers', {}) if data is not None and 'Content-Type' not in headers: headers['Content-Type'] = 'application/mercurial-0.1' if size and self.ui.configbool('ui', 'usehttp2', False): headers['Expect'] = '100-Continue' headers['X-HgHttp2'] = '1' self.ui.debug("sending %s command\n" % cmd) q = [('cmd', cmd)] headersize = 0 if len(args) > 0: httpheader = self.capable('httpheader') if httpheader: headersize = int(httpheader.split(',')[0]) if headersize > 0: # The headers can typically carry more data than the URL. encargs = urllib.urlencode(sorted(args.items())) headerfmt = 'X-HgArg-%s' contentlen = headersize - len(headerfmt % '000' + ': \r\n') headernum = 0 for i in xrange(0, len(encargs), contentlen): headernum += 1 header = headerfmt % str(headernum) headers[header] = encargs[i:i + contentlen] varyheaders = [headerfmt % str(h) for h in range(1, headernum + 1)] headers['Vary'] = ','.join(varyheaders) else: q += sorted(args.items()) qs = '?%s' % urllib.urlencode(q) cu = "%s%s" % (self._url, qs) req = urllib2.Request(cu, data, headers) if data is not None: self.ui.debug("sending %s bytes\n" % size) req.add_unredirected_header('Content-Length', '%d' % size) try: resp = self.urlopener.open(req) except urllib2.HTTPError, inst: if inst.code == 401: raise util.Abort(_('authorization failed')) raise except httplib.HTTPException, inst: self.ui.debug('http error while sending %s command\n' % cmd) self.ui.traceback() raise IOError(None, inst) except IndexError: # this only happens with Python 2.3, later versions raise URLError raise util.Abort(_('http error, possibly caused by proxy setting')) # record the url we got redirected to resp_url = resp.geturl() if resp_url.endswith(qs): resp_url = resp_url[:-len(qs)] if self._url.rstrip('/') != resp_url.rstrip('/'): if not self.ui.quiet: self.ui.warn(_('real URL is %s\n') % resp_url) self._url = resp_url try: proto = resp.getheader('content-type') except AttributeError: proto = resp.headers.get('content-type', '') safeurl = util.hidepassword(self._url) if proto.startswith('application/hg-error'): raise error.OutOfBandError(resp.read()) # accept old "text/plain" and "application/hg-changegroup" for now if not (proto.startswith('application/mercurial-') or (proto.startswith('text/plain') and not resp.headers.get('content-length')) or proto.startswith('application/hg-changegroup')): self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu)) raise error.RepoError( _("'%s' does not appear to be an hg repository:\n" "---%%<--- (%s)\n%s\n---%%<---\n") % (safeurl, proto or 'no content-type', resp.read(1024))) if proto.startswith('application/mercurial-'): try: version = proto.split('-', 1)[1] version_info = tuple([int(n) for n in version.split('.')]) except ValueError: raise error.RepoError(_("'%s' sent a broken Content-Type " "header (%s)") % (safeurl, proto)) if version_info > (0, 1): raise error.RepoError(_("'%s' uses newer protocol %s") % (safeurl, version)) return resp def _call(self, cmd, **args): fp = self._callstream(cmd, **args) try: return fp.read() finally: # if using keepalive, allow connection to be reused fp.close() def _callpush(self, cmd, cg, **args): # have to stream bundle to a temp file because we do not have # http 1.1 chunked transfer. types = self.capable('unbundle') try: types = types.split(',') except AttributeError: # servers older than d1b16a746db6 will send 'unbundle' as a # boolean capability. They only support headerless/uncompressed # bundles. types = [""] for x in types: if x in changegroup.bundletypes: type = x break tempname = changegroup.writebundle(cg, None, type) fp = httpconnection.httpsendfile(self.ui, tempname, "rb") headers = {'Content-Type': 'application/mercurial-0.1'} try: try: r = self._call(cmd, data=fp, headers=headers, **args) vals = r.split('\n', 1) if len(vals) < 2: raise error.ResponseError(_("unexpected response:"), r) return vals except socket.error, err: if err.args[0] in (errno.ECONNRESET, errno.EPIPE): raise util.Abort(_('push failed: %s') % err.args[1]) raise util.Abort(err.args[1]) finally: fp.close() os.unlink(tempname) def _abort(self, exception): raise exception def _decompress(self, stream): return util.chunkbuffer(zgenerator(stream)) class httpspeer(httppeer): def __init__(self, ui, path): if not url.has_https: raise util.Abort(_('Python support for SSL and HTTPS ' 'is not installed')) httppeer.__init__(self, ui, path) def instance(ui, path, create): if create: raise util.Abort(_('cannot create new http repository')) try: if path.startswith('https:'): inst = httpspeer(ui, path) else: inst = httppeer(ui, path) try: # Try to do useful work when checking compatibility. # Usually saves a roundtrip since we want the caps anyway. inst._fetchcaps() except error.RepoError: # No luck, try older compatibility check. inst.between([(nullid, nullid)]) return inst except error.RepoError, httpexception: try: r = statichttprepo.instance(ui, "static-" + path, create) ui.note('(falling back to static-http)\n') return r except error.RepoError: raise httpexception # use the original http RepoError instead