##// END OF EJS Templates
revsetbenchmarks: clarify comment based on irc discussion
Augie Fackler -
r25533:4bdf6f58 default
parent child Browse files
Show More
@@ -1,200 +1,203
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 # Measure the performance of a list of revsets against multiple revisions
3 # Measure the performance of a list of revsets against multiple revisions
4 # defined by parameter. Checkout one by one and run perfrevset with every
4 # defined by parameter. Checkout one by one and run perfrevset with every
5 # revset in the list to benchmark its performance.
5 # revset in the list to benchmark its performance.
6 #
6 #
7 # - First argument is a revset of mercurial own repo to runs against.
7 # - First argument is a revset of mercurial own repo to runs against.
8 # - Second argument is the file from which the revset array will be taken
8 # - Second argument is the file from which the revset array will be taken
9 # If second argument is omitted read it from standard input
9 # If second argument is omitted read it from standard input
10 #
10 #
11 # You should run this from the root of your mercurial repository.
11 # You should run this from the root of your mercurial repository.
12 #
12 #
13 # This script also does one run of the current version of mercurial installed
13 # This script also does one run of the current version of mercurial installed
14 # to compare performance.
14 # to compare performance.
15
15
16 import sys
16 import sys
17 import os
17 import os
18 import re
18 import re
19 from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE
19 from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE
20 # cannot use argparse, python 2.7 only
20 # cannot use argparse, python 2.7 only
21 from optparse import OptionParser
21 from optparse import OptionParser
22
22
23 def check_output(*args, **kwargs):
23 def check_output(*args, **kwargs):
24 kwargs.setdefault('stderr', PIPE)
24 kwargs.setdefault('stderr', PIPE)
25 kwargs.setdefault('stdout', PIPE)
25 kwargs.setdefault('stdout', PIPE)
26 proc = Popen(*args, **kwargs)
26 proc = Popen(*args, **kwargs)
27 output, error = proc.communicate()
27 output, error = proc.communicate()
28 if proc.returncode != 0:
28 if proc.returncode != 0:
29 raise CalledProcessError(proc.returncode, ' '.join(args[0]))
29 raise CalledProcessError(proc.returncode, ' '.join(args[0]))
30 return output
30 return output
31
31
32 def update(rev):
32 def update(rev):
33 """update the repo to a revision"""
33 """update the repo to a revision"""
34 try:
34 try:
35 check_call(['hg', 'update', '--quiet', '--check', str(rev)])
35 check_call(['hg', 'update', '--quiet', '--check', str(rev)])
36 except CalledProcessError, exc:
36 except CalledProcessError, exc:
37 print >> sys.stderr, 'update to revision %s failed, aborting' % rev
37 print >> sys.stderr, 'update to revision %s failed, aborting' % rev
38 sys.exit(exc.returncode)
38 sys.exit(exc.returncode)
39
39
40
40
41 def hg(cmd, repo=None):
41 def hg(cmd, repo=None):
42 """run a mercurial command
42 """run a mercurial command
43
43
44 <cmd> is the list of command + argument,
44 <cmd> is the list of command + argument,
45 <repo> is an optional repository path to run this command in."""
45 <repo> is an optional repository path to run this command in."""
46 fullcmd = ['./hg']
46 fullcmd = ['./hg']
47 if repo is not None:
47 if repo is not None:
48 fullcmd += ['-R', repo]
48 fullcmd += ['-R', repo]
49 fullcmd += ['--config',
49 fullcmd += ['--config',
50 'extensions.perf=' + os.path.join(contribdir, 'perf.py')]
50 'extensions.perf=' + os.path.join(contribdir, 'perf.py')]
51 fullcmd += cmd
51 fullcmd += cmd
52 return check_output(fullcmd, stderr=STDOUT)
52 return check_output(fullcmd, stderr=STDOUT)
53
53
54 def perf(revset, target=None):
54 def perf(revset, target=None):
55 """run benchmark for this very revset"""
55 """run benchmark for this very revset"""
56 try:
56 try:
57 output = hg(['perfrevset', revset], repo=target)
57 output = hg(['perfrevset', revset], repo=target)
58 return parseoutput(output)
58 return parseoutput(output)
59 except CalledProcessError, exc:
59 except CalledProcessError, exc:
60 print >> sys.stderr, 'abort: cannot run revset benchmark: %s' % exc.cmd
60 print >> sys.stderr, 'abort: cannot run revset benchmark: %s' % exc.cmd
61 if exc.output is None:
61 if exc.output is None:
62 print >> sys.stderr, '(no ouput)'
62 print >> sys.stderr, '(no ouput)'
63 else:
63 else:
64 print >> sys.stderr, exc.output
64 print >> sys.stderr, exc.output
65 sys.exit(exc.returncode)
65 sys.exit(exc.returncode)
66
66
67 outputre = re.compile(r'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
67 outputre = re.compile(r'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
68 'sys (\d+.\d+) \(best of (\d+)\)')
68 'sys (\d+.\d+) \(best of (\d+)\)')
69
69
70 def parseoutput(output):
70 def parseoutput(output):
71 """parse a textual output into a dict
71 """parse a textual output into a dict
72
72
73 We cannot just use json because we want to compare with old
73 We cannot just use json because we want to compare with old
74 versions of Mercurial that may not support json output.
74 versions of Mercurial that may not support json output.
75 """
75 """
76 match = outputre.search(output)
76 match = outputre.search(output)
77 if not match:
77 if not match:
78 print >> sys.stderr, 'abort: invalid output:'
78 print >> sys.stderr, 'abort: invalid output:'
79 print >> sys.stderr, output
79 print >> sys.stderr, output
80 sys.exit(1)
80 sys.exit(1)
81 return {'comb': float(match.group(2)),
81 return {'comb': float(match.group(2)),
82 'count': int(match.group(5)),
82 'count': int(match.group(5)),
83 'sys': float(match.group(3)),
83 'sys': float(match.group(3)),
84 'user': float(match.group(4)),
84 'user': float(match.group(4)),
85 'wall': float(match.group(1)),
85 'wall': float(match.group(1)),
86 }
86 }
87
87
88 def printrevision(rev):
88 def printrevision(rev):
89 """print data about a revision"""
89 """print data about a revision"""
90 sys.stdout.write("Revision: ")
90 sys.stdout.write("Revision: ")
91 sys.stdout.flush()
91 sys.stdout.flush()
92 check_call(['hg', 'log', '--rev', str(rev), '--template',
92 check_call(['hg', 'log', '--rev', str(rev), '--template',
93 '{desc|firstline}\n'])
93 '{desc|firstline}\n'])
94
94
95 def idxwidth(nbidx):
95 def idxwidth(nbidx):
96 """return the max width of number used for index
96 """return the max width of number used for index
97
97
98 Yes, this is basically a log10."""
98 This is similar to log10(nbidx), but we use custom code here
99 because we start with zero and we'd rather not deal with all the
100 extra rounding business that log10 would imply.
101 """
99 nbidx -= 1 # starts at 0
102 nbidx -= 1 # starts at 0
100 idxwidth = 0
103 idxwidth = 0
101 while nbidx:
104 while nbidx:
102 idxwidth += 1
105 idxwidth += 1
103 nbidx //= 10
106 nbidx //= 10
104 if not idxwidth:
107 if not idxwidth:
105 idxwidth = 1
108 idxwidth = 1
106 return idxwidth
109 return idxwidth
107
110
108 def printresult(idx, data, maxidx):
111 def printresult(idx, data, maxidx):
109 """print a line of result to stdout"""
112 """print a line of result to stdout"""
110 mask = '%%0%ii) %%s' % idxwidth(maxidx)
113 mask = '%%0%ii) %%s' % idxwidth(maxidx)
111
114
112 out = ("wall %f comb %f user %f sys %f (best of %d)"
115 out = ("wall %f comb %f user %f sys %f (best of %d)"
113 % (data['wall'], data['comb'], data['user'],
116 % (data['wall'], data['comb'], data['user'],
114 data['sys'], data['count']))
117 data['sys'], data['count']))
115
118
116 print mask % (idx, out)
119 print mask % (idx, out)
117
120
118 def getrevs(spec):
121 def getrevs(spec):
119 """get the list of rev matched by a revset"""
122 """get the list of rev matched by a revset"""
120 try:
123 try:
121 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
124 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
122 except CalledProcessError, exc:
125 except CalledProcessError, exc:
123 print >> sys.stderr, "abort, can't get revision from %s" % spec
126 print >> sys.stderr, "abort, can't get revision from %s" % spec
124 sys.exit(exc.returncode)
127 sys.exit(exc.returncode)
125 return [r for r in out.split() if r]
128 return [r for r in out.split() if r]
126
129
127
130
128 parser = OptionParser(usage="usage: %prog [options] <revs>")
131 parser = OptionParser(usage="usage: %prog [options] <revs>")
129 parser.add_option("-f", "--file",
132 parser.add_option("-f", "--file",
130 help="read revset from FILE (stdin if omitted)",
133 help="read revset from FILE (stdin if omitted)",
131 metavar="FILE")
134 metavar="FILE")
132 parser.add_option("-R", "--repo",
135 parser.add_option("-R", "--repo",
133 help="run benchmark on REPO", metavar="REPO")
136 help="run benchmark on REPO", metavar="REPO")
134
137
135 (options, args) = parser.parse_args()
138 (options, args) = parser.parse_args()
136
139
137 if len(sys.argv) < 2:
140 if len(sys.argv) < 2:
138 parser.print_help()
141 parser.print_help()
139 sys.exit(255)
142 sys.exit(255)
140
143
141 # the directory where both this script and the perf.py extension live.
144 # the directory where both this script and the perf.py extension live.
142 contribdir = os.path.dirname(__file__)
145 contribdir = os.path.dirname(__file__)
143
146
144 target_rev = args[0]
147 target_rev = args[0]
145
148
146 revsetsfile = sys.stdin
149 revsetsfile = sys.stdin
147 if options.file:
150 if options.file:
148 revsetsfile = open(options.file)
151 revsetsfile = open(options.file)
149
152
150 revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
153 revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
151
154
152 print "Revsets to benchmark"
155 print "Revsets to benchmark"
153 print "----------------------------"
156 print "----------------------------"
154
157
155 for idx, rset in enumerate(revsets):
158 for idx, rset in enumerate(revsets):
156 print "%i) %s" % (idx, rset)
159 print "%i) %s" % (idx, rset)
157
160
158 print "----------------------------"
161 print "----------------------------"
159 print
162 print
160
163
161
164
162 revs = getrevs(target_rev)
165 revs = getrevs(target_rev)
163
166
164 results = []
167 results = []
165 for r in revs:
168 for r in revs:
166 print "----------------------------"
169 print "----------------------------"
167 printrevision(r)
170 printrevision(r)
168 print "----------------------------"
171 print "----------------------------"
169 update(r)
172 update(r)
170 res = []
173 res = []
171 results.append(res)
174 results.append(res)
172 for idx, rset in enumerate(revsets):
175 for idx, rset in enumerate(revsets):
173 data = perf(rset, target=options.repo)
176 data = perf(rset, target=options.repo)
174 res.append(data)
177 res.append(data)
175 printresult(idx, data, len(revsets))
178 printresult(idx, data, len(revsets))
176 sys.stdout.flush()
179 sys.stdout.flush()
177 print "----------------------------"
180 print "----------------------------"
178
181
179
182
180 print """
183 print """
181
184
182 Result by revset
185 Result by revset
183 ================
186 ================
184 """
187 """
185
188
186 print 'Revision:', revs
189 print 'Revision:', revs
187 for idx, rev in enumerate(revs):
190 for idx, rev in enumerate(revs):
188 sys.stdout.write('%i) ' % idx)
191 sys.stdout.write('%i) ' % idx)
189 sys.stdout.flush()
192 sys.stdout.flush()
190 printrevision(rev)
193 printrevision(rev)
191
194
192 print
195 print
193 print
196 print
194
197
195 for ridx, rset in enumerate(revsets):
198 for ridx, rset in enumerate(revsets):
196
199
197 print "revset #%i: %s" % (ridx, rset)
200 print "revset #%i: %s" % (ridx, rset)
198 for idx, data in enumerate(results):
201 for idx, data in enumerate(results):
199 printresult(idx, data[ridx], len(results))
202 printresult(idx, data[ridx], len(results))
200 print
203 print
General Comments 0
You need to be logged in to leave comments. Login now