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