Mercurial > hg-stable
comparison contrib/pull_logger.py @ 49513:791050360486
contrib: add pull_logger extension
This extension logs the pull parameters, i.e. the remote and common heads,
when pulling from the local repository.
The collected data should give an idea of the state of a pair of repositories
and allow replaying past synchronisations between them. This is particularly
useful for working on data exchange, bundling and caching-related
optimisations.
author | pacien <pacien.trangirard@pacien.net> |
---|---|
date | Mon, 25 Jul 2022 22:47:15 +0200 |
parents | |
children | 946c023212b8 |
comparison
equal
deleted
inserted
replaced
49512:44bc045a43ca | 49513:791050360486 |
---|---|
1 # pull_logger.py - Logs pulls to a JSON-line file in the repo's VFS. | |
2 # | |
3 # Copyright 2022 Pacien TRAN-GIRARD <pacien.trangirard@pacien.net> | |
4 # | |
5 # This software may be used and distributed according to the terms of the | |
6 # GNU General Public License version 2 or any later version. | |
7 | |
8 | |
9 '''logs pull parameters to a file | |
10 | |
11 This extension logs the pull parameters, i.e. the remote and common heads, | |
12 when pulling from the local repository. | |
13 | |
14 The collected data should give an idea of the state of a pair of repositories | |
15 and allow replaying past synchronisations between them. This is particularly | |
16 useful for working on data exchange, bundling and caching-related | |
17 optimisations. | |
18 | |
19 The record is a JSON-line file located in the repository's VFS at | |
20 .hg/pull_log.jsonl. | |
21 | |
22 Log write failures are not considered fatal: log writes may be skipped for any | |
23 reason such as insufficient storage or a timeout. | |
24 | |
25 The timeouts of the exclusive lock used when writing to the lock file can be | |
26 configured through the 'timeout.lock' and 'timeout.warn' options of this | |
27 plugin. Those are not expected to be held for a significant time in practice.:: | |
28 | |
29 [pull-logger] | |
30 timeout.lock = 300 | |
31 timeout.warn = 100 | |
32 | |
33 Note: there is no automatic log rotation and the size of the log is not capped. | |
34 ''' | |
35 | |
36 | |
37 import json | |
38 import time | |
39 | |
40 from mercurial.i18n import _ | |
41 from mercurial.utils import stringutil | |
42 from mercurial import ( | |
43 error, | |
44 extensions, | |
45 lock, | |
46 registrar, | |
47 wireprotov1server, | |
48 ) | |
49 | |
50 EXT_NAME = b'pull-logger' | |
51 EXT_VERSION_CODE = 0 | |
52 | |
53 LOG_FILE = b'pull_log.jsonl' | |
54 LOCK_NAME = LOG_FILE + b'.lock' | |
55 | |
56 configtable = {} | |
57 configitem = registrar.configitem(configtable) | |
58 configitem(EXT_NAME, b'timeout.lock', default=600) | |
59 configitem(EXT_NAME, b'timeout.warn', default=120) | |
60 | |
61 | |
62 def wrap_getbundle(orig, repo, proto, others, *args, **kwargs): | |
63 heads, common = extract_pull_heads(others) | |
64 log_entry = { | |
65 'timestamp': time.time(), | |
66 'logger_version': EXT_VERSION_CODE, | |
67 'heads': sorted(heads), | |
68 'common': sorted(common), | |
69 } | |
70 | |
71 try: | |
72 write_to_log(repo, log_entry) | |
73 except (IOError, error.LockError) as err: | |
74 msg = stringutil.forcebytestr(err) | |
75 repo.ui.warn(_(b'unable to append to pull log: %s\n') % msg) | |
76 | |
77 return orig(repo, proto, others, *args, **kwargs) | |
78 | |
79 | |
80 def extract_pull_heads(bundle_args): | |
81 opts = wireprotov1server.options( | |
82 b'getbundle', | |
83 wireprotov1server.wireprototypes.GETBUNDLE_ARGUMENTS.keys(), | |
84 bundle_args.copy(), # this call consumes the args destructively | |
85 ) | |
86 | |
87 heads = opts.get(b'heads', b'').decode('utf-8').split(' ') | |
88 common = opts.get(b'common', b'').decode('utf-8').split(' ') | |
89 return (heads, common) | |
90 | |
91 | |
92 def write_to_log(repo, entry): | |
93 locktimeout = repo.ui.configint(EXT_NAME, b'timeout.lock') | |
94 lockwarntimeout = repo.ui.configint(EXT_NAME, b'timeout.warn') | |
95 | |
96 with lock.trylock( | |
97 ui=repo.ui, | |
98 vfs=repo.vfs, | |
99 lockname=LOCK_NAME, | |
100 timeout=locktimeout, | |
101 warntimeout=lockwarntimeout, | |
102 ): | |
103 with repo.vfs.open(LOG_FILE, b'a+') as logfile: | |
104 serialised = json.dumps(entry, sort_keys=True) | |
105 logfile.write(serialised.encode('utf-8')) | |
106 logfile.write(b'\n') | |
107 logfile.flush() | |
108 | |
109 | |
110 def reposetup(ui, repo): | |
111 if repo.local(): | |
112 repo._wlockfreeprefix.add(LOG_FILE) | |
113 | |
114 | |
115 def uisetup(ui): | |
116 del wireprotov1server.commands[b'getbundle'] | |
117 decorator = wireprotov1server.wireprotocommand( | |
118 name=b'getbundle', | |
119 args=b'*', | |
120 permission=b'pull', | |
121 ) | |
122 | |
123 extensions.wrapfunction( | |
124 container=wireprotov1server, | |
125 funcname='getbundle', | |
126 wrapper=wrap_getbundle, | |
127 ) | |
128 | |
129 decorator(wireprotov1server.getbundle) |