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