##// END OF EJS Templates
debug: add an option to display statistic about a unbundling operation...
marmoute -
r50506:35d4c212 default
parent child Browse files
Show More
@@ -105,6 +105,164 b' def writechunks(ui, chunks, filename, vf'
105 105 os.unlink(cleanup)
106 106
107 107
108 def _dbg_ubdl_line(
109 ui,
110 indent,
111 key,
112 base_value=None,
113 percentage_base=None,
114 percentage_key=None,
115 ):
116 """Print one line of debug_unbundle_debug_info"""
117 line = b"DEBUG-UNBUNDLING: "
118 line += b' ' * (2 * indent)
119 key += b":"
120 padding = b''
121 if base_value is not None:
122 assert len(key) + 1 + (2 * indent) <= _KEY_PART_WIDTH
123 line += key.ljust(_KEY_PART_WIDTH - (2 * indent))
124 if isinstance(base_value, float):
125 line += b"%14.3f seconds" % base_value
126 else:
127 line += b"%10d" % base_value
128 padding = b' '
129 else:
130 line += key
131
132 if percentage_base is not None:
133 line += padding
134 padding = b''
135 assert base_value is not None
136 percentage = base_value * 100 // percentage_base
137 if percentage_key is not None:
138 line += b" (%3d%% of %s)" % (
139 percentage,
140 percentage_key,
141 )
142 else:
143 line += b" (%3d%%)" % percentage
144
145 line += b'\n'
146 ui.write_err(line)
147
148
149 def _sumf(items):
150 # python < 3.8 does not support a `start=0.0` argument to sum
151 # So we have to cheat a bit until we drop support for those version
152 if not items:
153 return 0.0
154 return sum(items)
155
156
157 def display_unbundle_debug_info(ui, debug_info):
158 """display an unbundling report from debug information"""
159 cl_info = []
160 mn_info = []
161 fl_info = []
162 _dispatch = [
163 (b'CHANGELOG:', cl_info),
164 (b'MANIFESTLOG:', mn_info),
165 (b'FILELOG:', fl_info),
166 ]
167 for e in debug_info:
168 for prefix, info in _dispatch:
169 if e["target-revlog"].startswith(prefix):
170 info.append(e)
171 break
172 else:
173 assert False, 'unreachable'
174 each_info = [
175 (b'changelog', cl_info),
176 (b'manifests', mn_info),
177 (b'files', fl_info),
178 ]
179
180 # General Revision Countss
181 _dbg_ubdl_line(ui, 0, b'revisions', len(debug_info))
182 for key, info in each_info:
183 if not info:
184 continue
185 _dbg_ubdl_line(ui, 1, key, len(info), len(debug_info))
186
187 # General Time spent
188 all_durations = [e['duration'] for e in debug_info]
189 all_durations.sort()
190 total_duration = _sumf(all_durations)
191 _dbg_ubdl_line(ui, 0, b'total-time', total_duration)
192
193 for key, info in each_info:
194 if not info:
195 continue
196 durations = [e['duration'] for e in info]
197 durations.sort()
198 _dbg_ubdl_line(ui, 1, key, _sumf(durations), total_duration)
199
200 # Count and cache reuse per delta types
201 each_types = {}
202 for key, info in each_info:
203 each_types[key] = types = {
204 b'full': 0,
205 b'full-cached': 0,
206 b'snapshot': 0,
207 b'snapshot-cached': 0,
208 b'delta': 0,
209 b'delta-cached': 0,
210 b'unknown': 0,
211 b'unknown-cached': 0,
212 }
213 for e in info:
214 types[e['type']] += 1
215 if e['using-cached-base']:
216 types[e['type'] + b'-cached'] += 1
217
218 EXPECTED_TYPES = (b'full', b'snapshot', b'delta', b'unknown')
219 if debug_info:
220 _dbg_ubdl_line(ui, 0, b'type-count')
221 for key, info in each_info:
222 if info:
223 _dbg_ubdl_line(ui, 1, key)
224 t = each_types[key]
225 for tn in EXPECTED_TYPES:
226 if t[tn]:
227 tc = tn + b'-cached'
228 _dbg_ubdl_line(ui, 2, tn, t[tn])
229 _dbg_ubdl_line(ui, 3, b'cached', t[tc], t[tn])
230
231 # time perf delta types and reuse
232 each_type_time = {}
233 for key, info in each_info:
234 each_type_time[key] = t = {
235 b'full': [],
236 b'full-cached': [],
237 b'snapshot': [],
238 b'snapshot-cached': [],
239 b'delta': [],
240 b'delta-cached': [],
241 b'unknown': [],
242 b'unknown-cached': [],
243 }
244 for e in info:
245 t[e['type']].append(e['duration'])
246 if e['using-cached-base']:
247 t[e['type'] + b'-cached'].append(e['duration'])
248 for t_key, value in list(t.items()):
249 value.sort()
250 t[t_key] = _sumf(value)
251
252 if debug_info:
253 _dbg_ubdl_line(ui, 0, b'type-time')
254 for key, info in each_info:
255 if info:
256 _dbg_ubdl_line(ui, 1, key)
257 t = each_type_time[key]
258 td = total_duration # to same space on next lines
259 for tn in EXPECTED_TYPES:
260 if t[tn]:
261 tc = tn + b'-cached'
262 _dbg_ubdl_line(ui, 2, tn, t[tn], td, b"total")
263 _dbg_ubdl_line(ui, 3, b'cached', t[tc], td, b"total")
264
265
108 266 class cg1unpacker:
109 267 """Unpacker for cg1 changegroup streams.
110 268
@@ -254,7 +412,15 b' class cg1unpacker:'
254 412 pos = next
255 413 yield closechunk()
256 414
257 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
415 def _unpackmanifests(
416 self,
417 repo,
418 revmap,
419 trp,
420 prog,
421 addrevisioncb=None,
422 debug_info=None,
423 ):
258 424 self.callback = prog.increment
259 425 # no need to check for empty manifest group here:
260 426 # if the result of the merge of 1 and 2 is the same in 3 and 4,
@@ -263,7 +429,13 b' class cg1unpacker:'
263 429 self.manifestheader()
264 430 deltas = self.deltaiter()
265 431 storage = repo.manifestlog.getstorage(b'')
266 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
432 storage.addgroup(
433 deltas,
434 revmap,
435 trp,
436 addrevisioncb=addrevisioncb,
437 debug_info=debug_info,
438 )
267 439 prog.complete()
268 440 self.callback = None
269 441
@@ -292,6 +464,10 b' class cg1unpacker:'
292 464 """
293 465 repo = repo.unfiltered()
294 466
467 debug_info = None
468 if repo.ui.configbool(b'debug', b'unbundling-stats'):
469 debug_info = []
470
295 471 # Only useful if we're adding sidedata categories. If both peers have
296 472 # the same categories, then we simply don't do anything.
297 473 adding_sidedata = (
@@ -366,6 +542,7 b' class cg1unpacker:'
366 542 alwayscache=True,
367 543 addrevisioncb=onchangelog,
368 544 duplicaterevisioncb=ondupchangelog,
545 debug_info=debug_info,
369 546 ):
370 547 repo.ui.develwarn(
371 548 b'applied empty changelog from changegroup',
@@ -413,6 +590,7 b' class cg1unpacker:'
413 590 trp,
414 591 progress,
415 592 addrevisioncb=on_manifest_rev,
593 debug_info=debug_info,
416 594 )
417 595
418 596 needfiles = {}
@@ -449,6 +627,7 b' class cg1unpacker:'
449 627 efiles,
450 628 needfiles,
451 629 addrevisioncb=on_filelog_rev,
630 debug_info=debug_info,
452 631 )
453 632
454 633 if sidedata_helpers:
@@ -567,6 +746,8 b' class cg1unpacker:'
567 746 b'changegroup-runhooks-%020i' % clstart,
568 747 lambda tr: repo._afterlock(runhooks),
569 748 )
749 if debug_info is not None:
750 display_unbundle_debug_info(repo.ui, debug_info)
570 751 finally:
571 752 repo.ui.flush()
572 753 # never return 0 here:
@@ -626,9 +807,22 b' class cg3unpacker(cg2unpacker):'
626 807 protocol_flags = 0
627 808 return node, p1, p2, deltabase, cs, flags, protocol_flags
628 809
629 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
810 def _unpackmanifests(
811 self,
812 repo,
813 revmap,
814 trp,
815 prog,
816 addrevisioncb=None,
817 debug_info=None,
818 ):
630 819 super(cg3unpacker, self)._unpackmanifests(
631 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
820 repo,
821 revmap,
822 trp,
823 prog,
824 addrevisioncb=addrevisioncb,
825 debug_info=debug_info,
632 826 )
633 827 for chunkdata in iter(self.filelogheader, {}):
634 828 # If we get here, there are directory manifests in the changegroup
@@ -636,7 +830,11 b' class cg3unpacker(cg2unpacker):'
636 830 repo.ui.debug(b"adding %s revisions\n" % d)
637 831 deltas = self.deltaiter()
638 832 if not repo.manifestlog.getstorage(d).addgroup(
639 deltas, revmap, trp, addrevisioncb=addrevisioncb
833 deltas,
834 revmap,
835 trp,
836 addrevisioncb=addrevisioncb,
837 debug_info=debug_info,
640 838 ):
641 839 raise error.Abort(_(b"received dir revlog group is empty"))
642 840
@@ -2173,6 +2371,7 b' def _addchangegroupfiles('
2173 2371 expectedfiles,
2174 2372 needfiles,
2175 2373 addrevisioncb=None,
2374 debug_info=None,
2176 2375 ):
2177 2376 revisions = 0
2178 2377 files = 0
@@ -2193,6 +2392,7 b' def _addchangegroupfiles('
2193 2392 revmap,
2194 2393 trp,
2195 2394 addrevisioncb=addrevisioncb,
2395 debug_info=debug_info,
2196 2396 )
2197 2397 if not added:
2198 2398 raise error.Abort(_(b"received file revlog group is empty"))
@@ -594,6 +594,12 b' coreconfigitem('
594 594 b'bundling-stats',
595 595 default=False,
596 596 )
597 # display extra information about the unbundling process
598 coreconfigitem(
599 b'debug',
600 b'unbundling-stats',
601 default=False,
602 )
597 603 coreconfigitem(
598 604 b'defaults',
599 605 b'.*',
@@ -153,6 +153,7 b' class filelog:'
153 153 addrevisioncb=None,
154 154 duplicaterevisioncb=None,
155 155 maybemissingparents=False,
156 debug_info=None,
156 157 ):
157 158 if maybemissingparents:
158 159 raise error.Abort(
@@ -173,6 +174,7 b' class filelog:'
173 174 transaction,
174 175 addrevisioncb=addrevisioncb,
175 176 duplicaterevisioncb=duplicaterevisioncb,
177 debug_info=debug_info,
176 178 )
177 179
178 180 def getstrippoint(self, minlink):
@@ -1856,6 +1856,7 b' class manifestrevlog:'
1856 1856 alwayscache=False,
1857 1857 addrevisioncb=None,
1858 1858 duplicaterevisioncb=None,
1859 debug_info=None,
1859 1860 ):
1860 1861 return self._revlog.addgroup(
1861 1862 deltas,
@@ -1864,6 +1865,7 b' class manifestrevlog:'
1864 1865 alwayscache=alwayscache,
1865 1866 addrevisioncb=addrevisioncb,
1866 1867 duplicaterevisioncb=duplicaterevisioncb,
1868 debug_info=debug_info,
1867 1869 )
1868 1870
1869 1871 def rawsize(self, rev):
@@ -2640,6 +2640,7 b' class revlog:'
2640 2640 alwayscache=False,
2641 2641 addrevisioncb=None,
2642 2642 duplicaterevisioncb=None,
2643 debug_info=None,
2643 2644 ):
2644 2645 """
2645 2646 add a delta group
@@ -2665,6 +2666,7 b' class revlog:'
2665 2666 deltacomputer = deltautil.deltacomputer(
2666 2667 self,
2667 2668 write_debug=write_debug,
2669 debug_info=debug_info,
2668 2670 )
2669 2671 # loop through our set of deltas
2670 2672 for data in deltas:
@@ -674,7 +674,12 b' def _candidategroups(revlog, textlen, p1'
674 674 deltas_limit = textlen * LIMIT_DELTA2TEXT
675 675
676 676 tested = {nullrev}
677 candidates = _refinedgroups(revlog, p1, p2, cachedelta)
677 candidates = _refinedgroups(
678 revlog,
679 p1,
680 p2,
681 cachedelta,
682 )
678 683 while True:
679 684 temptative = candidates.send(good)
680 685 if temptative is None:
@@ -703,6 +708,7 b' def _candidategroups(revlog, textlen, p1'
703 708 # no delta for rawtext-changing revs (see "candelta" for why)
704 709 if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
705 710 continue
711
706 712 # If we reach here, we are about to build and test a delta.
707 713 # The delta building process will compute the chaininfo in all
708 714 # case, since that computation is cached, it is fine to access it
@@ -768,15 +774,28 b' def _refinedgroups(revlog, p1, p2, cache'
768 774 # This logic only applies to general delta repositories and can be disabled
769 775 # through configuration. Disabling reuse source delta is useful when
770 776 # we want to make sure we recomputed "optimal" deltas.
777 debug_info = None
771 778 if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
772 779 # Assume what we received from the server is a good choice
773 780 # build delta will reuse the cache
781 if debug_info is not None:
782 debug_info['cached-delta.tested'] += 1
774 783 good = yield (cachedelta[0],)
775 784 if good is not None:
785 if debug_info is not None:
786 debug_info['cached-delta.accepted'] += 1
776 787 yield None
777 788 return
789 # XXX cache me higher
778 790 snapshots = collections.defaultdict(list)
779 for candidates in _rawgroups(revlog, p1, p2, cachedelta, snapshots):
791 groups = _rawgroups(
792 revlog,
793 p1,
794 p2,
795 cachedelta,
796 snapshots,
797 )
798 for candidates in groups:
780 799 good = yield candidates
781 800 if good is not None:
782 801 break
@@ -805,7 +824,10 b' def _refinedgroups(revlog, p1, p2, cache'
805 824 children = tuple(sorted(c for c in snapshots[good]))
806 825 good = yield children
807 826
808 # we have found nothing
827 if debug_info is not None:
828 if good is None:
829 debug_info['no-solution'] += 1
830
809 831 yield None
810 832
811 833
@@ -931,10 +953,17 b' def _rawgroups(revlog, p1, p2, cachedelt'
931 953
932 954
933 955 class deltacomputer:
934 def __init__(self, revlog, write_debug=None, debug_search=False):
956 def __init__(
957 self,
958 revlog,
959 write_debug=None,
960 debug_search=False,
961 debug_info=None,
962 ):
935 963 self.revlog = revlog
936 964 self._write_debug = write_debug
937 965 self._debug_search = debug_search
966 self._debug_info = debug_info
938 967
939 968 def buildtext(self, revinfo, fh):
940 969 """Builds a fulltext version of a revision
@@ -1103,11 +1132,14 b' class deltacomputer:'
1103 1132 if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
1104 1133 return self._fullsnapshotinfo(fh, revinfo, target_rev)
1105 1134
1106 if self._write_debug is not None:
1135 gather_debug = (
1136 self._write_debug is not None or self._debug_info is not None
1137 )
1138 debug_search = self._write_debug is not None and self._debug_search
1139
1140 if gather_debug:
1107 1141 start = util.timer()
1108 1142
1109 debug_search = self._write_debug is not None and self._debug_search
1110
1111 1143 # count the number of different delta we tried (for debug purpose)
1112 1144 dbg_try_count = 0
1113 1145 # count the number of "search round" we did. (for debug purpose)
@@ -1122,7 +1154,7 b' class deltacomputer:'
1122 1154 deltainfo = None
1123 1155 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
1124 1156
1125 if self._write_debug is not None:
1157 if gather_debug:
1126 1158 if p1r != nullrev:
1127 1159 p1_chain_len = revlog._chaininfo(p1r)[0]
1128 1160 else:
@@ -1250,9 +1282,8 b' class deltacomputer:'
1250 1282 else:
1251 1283 dbg_type = b"delta"
1252 1284
1253 if self._write_debug is not None:
1285 if gather_debug:
1254 1286 end = util.timer()
1255 assert deltainfo is not None # please pytype
1256 1287 used_cached = (
1257 1288 cachedelta is not None
1258 1289 and dbg_try_rounds == 1
@@ -1262,7 +1293,7 b' class deltacomputer:'
1262 1293 dbg = {
1263 1294 'duration': end - start,
1264 1295 'revision': target_rev,
1265 'delta-base': deltainfo.base,
1296 'delta-base': deltainfo.base, # pytype: disable=attribute-error
1266 1297 'search_round_count': dbg_try_rounds,
1267 1298 'using-cached-base': used_cached,
1268 1299 'delta_try_count': dbg_try_count,
@@ -1294,35 +1325,39 b' class deltacomputer:'
1294 1325 target_revlog += b'%s:' % target_key
1295 1326 dbg['target-revlog'] = target_revlog
1296 1327
1297 msg = (
1298 b"DBG-DELTAS:"
1299 b" %-12s"
1300 b" rev=%d:"
1301 b" delta-base=%d"
1302 b" is-cached=%d"
1303 b" - search-rounds=%d"
1304 b" try-count=%d"
1305 b" - delta-type=%-6s"
1306 b" snap-depth=%d"
1307 b" - p1-chain-length=%d"
1308 b" p2-chain-length=%d"
1309 b" - duration=%f"
1310 b"\n"
1311 )
1312 msg %= (
1313 dbg["target-revlog"],
1314 dbg["revision"],
1315 dbg["delta-base"],
1316 dbg["using-cached-base"],
1317 dbg["search_round_count"],
1318 dbg["delta_try_count"],
1319 dbg["type"],
1320 dbg["snapshot-depth"],
1321 dbg["p1-chain-len"],
1322 dbg["p2-chain-len"],
1323 dbg["duration"],
1324 )
1325 self._write_debug(msg)
1328 if self._debug_info is not None:
1329 self._debug_info.append(dbg)
1330
1331 if self._write_debug is not None:
1332 msg = (
1333 b"DBG-DELTAS:"
1334 b" %-12s"
1335 b" rev=%d:"
1336 b" delta-base=%d"
1337 b" is-cached=%d"
1338 b" - search-rounds=%d"
1339 b" try-count=%d"
1340 b" - delta-type=%-6s"
1341 b" snap-depth=%d"
1342 b" - p1-chain-length=%d"
1343 b" p2-chain-length=%d"
1344 b" - duration=%f"
1345 b"\n"
1346 )
1347 msg %= (
1348 dbg["target-revlog"],
1349 dbg["revision"],
1350 dbg["delta-base"],
1351 dbg["using-cached-base"],
1352 dbg["search_round_count"],
1353 dbg["delta_try_count"],
1354 dbg["type"],
1355 dbg["snapshot-depth"],
1356 dbg["p1-chain-len"],
1357 dbg["p2-chain-len"],
1358 dbg["duration"],
1359 )
1360 self._write_debug(msg)
1326 1361 return deltainfo
1327 1362
1328 1363
@@ -1081,3 +1081,47 b' Test the debug output when applying delt'
1081 1081 new changesets 4fe08cd4693e:4652c276ac4f (3 drafts)
1082 1082 (run 'hg update' to get a working copy)
1083 1083
1084
1085 Test the debug statistic when applying a bundle
1086 -----------------------------------------------
1087
1088 $ hg init bar
1089 $ hg -R bar unbundle ./default.hg --config debug.unbundling-stats=yes
1090 adding changesets
1091 adding manifests
1092 adding file changes
1093 DEBUG-UNBUNDLING: revisions: 9
1094 DEBUG-UNBUNDLING: changelog: 3 ( 33%)
1095 DEBUG-UNBUNDLING: manifests: 3 ( 33%)
1096 DEBUG-UNBUNDLING: files: 3 ( 33%)
1097 DEBUG-UNBUNDLING: total-time: ?????????????? seconds (glob)
1098 DEBUG-UNBUNDLING: changelog: ?????????????? seconds (???%) (glob)
1099 DEBUG-UNBUNDLING: manifests: ?????????????? seconds (???%) (glob)
1100 DEBUG-UNBUNDLING: files: ?????????????? seconds (???%) (glob)
1101 DEBUG-UNBUNDLING: type-count:
1102 DEBUG-UNBUNDLING: changelog:
1103 DEBUG-UNBUNDLING: full: 3
1104 DEBUG-UNBUNDLING: cached: 0 ( 0%)
1105 DEBUG-UNBUNDLING: manifests:
1106 DEBUG-UNBUNDLING: full: 1
1107 DEBUG-UNBUNDLING: cached: 0 ( 0%)
1108 DEBUG-UNBUNDLING: delta: 2
1109 DEBUG-UNBUNDLING: cached: 2 (100%)
1110 DEBUG-UNBUNDLING: files:
1111 DEBUG-UNBUNDLING: full: 3
1112 DEBUG-UNBUNDLING: cached: 0 ( 0%)
1113 DEBUG-UNBUNDLING: type-time:
1114 DEBUG-UNBUNDLING: changelog:
1115 DEBUG-UNBUNDLING: full: ?????????????? seconds (???% of total) (glob)
1116 DEBUG-UNBUNDLING: cached: ?????????????? seconds (???% of total) (glob)
1117 DEBUG-UNBUNDLING: manifests:
1118 DEBUG-UNBUNDLING: full: ?????????????? seconds (???% of total) (glob)
1119 DEBUG-UNBUNDLING: cached: ?????????????? seconds (???% of total) (glob)
1120 DEBUG-UNBUNDLING: delta: ?????????????? seconds (???% of total) (glob)
1121 DEBUG-UNBUNDLING: cached: ?????????????? seconds (???% of total) (glob)
1122 DEBUG-UNBUNDLING: files:
1123 DEBUG-UNBUNDLING: full: ?????????????? seconds (???% of total) (glob)
1124 DEBUG-UNBUNDLING: cached: ?????????????? seconds (???% of total) (glob)
1125 added 3 changesets with 3 changes to 3 files
1126 new changesets 4fe08cd4693e:4652c276ac4f (3 drafts)
1127 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now