##// END OF EJS Templates
blackbox: rewrite dirty documentation noting it is expensive
timeless -
r28303:ce24de06 default
parent child Browse files
Show More
@@ -1,243 +1,242 b''
1 1 # blackbox.py - log repository events to a file for post-mortem debugging
2 2 #
3 3 # Copyright 2010 Nicolas Dumazet
4 4 # Copyright 2013 Facebook, Inc.
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """log repository events to a blackbox for debugging
10 10
11 11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
12 12 The events that get logged can be configured via the blackbox.track config key.
13 13
14 If you want to record whether the repository is dirty (a `+` sign, as you'd
15 get when using :hg:`id`), you can set the blackbox.dirty config key.
16
17 14 Examples::
18 15
19 16 [blackbox]
20 17 track = *
18 # dirty is *EXPENSIVE* (slow);
19 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
21 20 dirty = True
22 21
23 22 [blackbox]
24 23 track = command, commandfinish, commandexception, exthook, pythonhook
25 24
26 25 [blackbox]
27 26 track = incoming
28 27
29 28 [blackbox]
30 29 # limit the size of a log file
31 30 maxsize = 1.5 MB
32 31 # rotate up to N log files when the current one gets too big
33 32 maxfiles = 3
34 33
35 34 """
36 35
37 36 from __future__ import absolute_import
38 37
39 38 import errno
40 39 import re
41 40
42 41 from mercurial.i18n import _
43 42 from mercurial.node import hex
44 43
45 44 from mercurial import (
46 45 cmdutil,
47 46 ui as uimod,
48 47 util,
49 48 )
50 49
51 50 cmdtable = {}
52 51 command = cmdutil.command(cmdtable)
53 52 # Note for extension authors: ONLY specify testedwith = 'internal' for
54 53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
55 54 # be specifying the version(s) of Mercurial they are tested with, or
56 55 # leave the attribute unspecified.
57 56 testedwith = 'internal'
58 57 lastui = None
59 58
60 59 filehandles = {}
61 60
62 61 def _openlog(vfs):
63 62 path = vfs.join('blackbox.log')
64 63 if path in filehandles:
65 64 return filehandles[path]
66 65 filehandles[path] = fp = vfs('blackbox.log', 'a')
67 66 return fp
68 67
69 68 def _closelog(vfs):
70 69 path = vfs.join('blackbox.log')
71 70 fp = filehandles[path]
72 71 del filehandles[path]
73 72 fp.close()
74 73
75 74 def hexfn(node):
76 75 if node is None:
77 76 return None
78 77 else:
79 78 return hex(node)
80 79
81 80 def wrapui(ui):
82 81 class blackboxui(ui.__class__):
83 82 def __init__(self, src=None):
84 83 super(blackboxui, self).__init__(src)
85 84 if src is None:
86 85 self._partialinit()
87 86 else:
88 87 self._bbfp = src._bbfp
89 88 self._bbrepo = src._bbrepo
90 89 self._bbvfs = src._bbvfs
91 90
92 91 def _partialinit(self):
93 92 if util.safehasattr(self, '_bbvfs'):
94 93 return
95 94 self._bbfp = None
96 95 self._bbrepo = None
97 96 self._bbvfs = None
98 97
99 98 def copy(self):
100 99 self._partialinit()
101 100 return self.__class__(self)
102 101
103 102 @util.propertycache
104 103 def track(self):
105 104 return self.configlist('blackbox', 'track', ['*'])
106 105
107 106 def _openlogfile(self):
108 107 def rotate(oldpath, newpath):
109 108 try:
110 109 self._bbvfs.unlink(newpath)
111 110 except OSError as err:
112 111 if err.errno != errno.ENOENT:
113 112 self.debug("warning: cannot remove '%s': %s\n" %
114 113 (newpath, err.strerror))
115 114 try:
116 115 if newpath:
117 116 self._bbvfs.rename(oldpath, newpath)
118 117 except OSError as err:
119 118 if err.errno != errno.ENOENT:
120 119 self.debug("warning: cannot rename '%s' to '%s': %s\n" %
121 120 (newpath, oldpath, err.strerror))
122 121
123 122 fp = _openlog(self._bbvfs)
124 123 maxsize = self.configbytes('blackbox', 'maxsize', 1048576)
125 124 if maxsize > 0:
126 125 st = self._bbvfs.fstat(fp)
127 126 if st.st_size >= maxsize:
128 127 path = fp.name
129 128 _closelog(self._bbvfs)
130 129 maxfiles = self.configint('blackbox', 'maxfiles', 7)
131 130 for i in xrange(maxfiles - 1, 1, -1):
132 131 rotate(oldpath='%s.%d' % (path, i - 1),
133 132 newpath='%s.%d' % (path, i))
134 133 rotate(oldpath=path,
135 134 newpath=maxfiles > 0 and path + '.1')
136 135 fp = _openlog(self._bbvfs)
137 136 return fp
138 137
139 138 def _bbwrite(self, fmt, *args):
140 139 self._bbfp.write(fmt % args)
141 140 self._bbfp.flush()
142 141
143 142 def log(self, event, *msg, **opts):
144 143 global lastui
145 144 super(blackboxui, self).log(event, *msg, **opts)
146 145 self._partialinit()
147 146
148 147 if not '*' in self.track and not event in self.track:
149 148 return
150 149
151 150 if self._bbfp:
152 151 ui = self
153 152 elif self._bbvfs:
154 153 try:
155 154 self._bbfp = self._openlogfile()
156 155 except (IOError, OSError) as err:
157 156 self.debug('warning: cannot write to blackbox.log: %s\n' %
158 157 err.strerror)
159 158 del self._bbvfs
160 159 self._bbfp = None
161 160 ui = self
162 161 else:
163 162 # certain ui instances exist outside the context of
164 163 # a repo, so just default to the last blackbox that
165 164 # was seen.
166 165 ui = lastui
167 166
168 167 if ui and ui._bbfp:
169 168 date = util.datestr(None, '%Y/%m/%d %H:%M:%S')
170 169 user = util.getuser()
171 170 pid = str(util.getpid())
172 171 formattedmsg = msg[0] % msg[1:]
173 172 rev = '(unknown)'
174 173 changed = ''
175 174 if ui._bbrepo:
176 175 ctx = ui._bbrepo[None]
177 176 if ctx.rev() is not None:
178 177 rev = hexfn(ctx.node())
179 178 else:
180 179 parents = ctx.parents()
181 180 rev = ('+'.join([hexfn(p.node()) for p in parents]))
182 181 if (ui.configbool('blackbox', 'dirty', False) and (
183 182 any(ui._bbrepo.status()) or
184 183 any(ctx.sub(s).dirty() for s in ctx.substate)
185 184 )):
186 185 changed = '+'
187 186 try:
188 187 ui._bbwrite('%s %s @%s%s (%s)> %s',
189 188 date, user, rev, changed, pid, formattedmsg)
190 189 except IOError as err:
191 190 self.debug('warning: cannot write to blackbox.log: %s\n' %
192 191 err.strerror)
193 192 if not lastui or ui._bbrepo:
194 193 lastui = ui
195 194
196 195 def setrepo(self, repo):
197 196 self._bbfp = None
198 197 self._bbrepo = repo
199 198 self._bbvfs = repo.vfs
200 199
201 200 ui.__class__ = blackboxui
202 201 uimod.ui = blackboxui
203 202
204 203 def uisetup(ui):
205 204 wrapui(ui)
206 205
207 206 def reposetup(ui, repo):
208 207 # During 'hg pull' a httppeer repo is created to represent the remote repo.
209 208 # It doesn't have a .hg directory to put a blackbox in, so we don't do
210 209 # the blackbox setup for it.
211 210 if not repo.local():
212 211 return
213 212
214 213 if util.safehasattr(ui, 'setrepo'):
215 214 ui.setrepo(repo)
216 215
217 216 @command('^blackbox',
218 217 [('l', 'limit', 10, _('the number of events to show')),
219 218 ],
220 219 _('hg blackbox [OPTION]...'))
221 220 def blackbox(ui, repo, *revs, **opts):
222 221 '''view the recent repository events
223 222 '''
224 223
225 224 if not repo.vfs.exists('blackbox.log'):
226 225 return
227 226
228 227 limit = opts.get('limit')
229 228 fp = repo.vfs('blackbox.log', 'r')
230 229 lines = fp.read().split('\n')
231 230
232 231 count = 0
233 232 output = []
234 233 for line in reversed(lines):
235 234 if count >= limit:
236 235 break
237 236
238 237 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
239 238 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
240 239 count += 1
241 240 output.append(line)
242 241
243 242 ui.status('\n'.join(reversed(output)))
General Comments 0
You need to be logged in to leave comments. Login now