1 """
2 Crash detection and reporting
3
4 Copyright 2012 Research Foundation State University of New York
5 This file is part of QUB Express.
6
7 QUB Express is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 QUB Express is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License,
18 named LICENSE.txt, in the QUB Express program directory. If not, see
19 <http://www.gnu.org/licenses/>.
20
21 """
22
23 import cStringIO
24 import datetime
25 import errno
26 import gobject
27 import gtk
28 import os
29 import platform
30 import re
31 import shutil
32 import sys
33 import traceback
34
35 import qubx.data_types
36 import qubx.global_namespace
37 import qubx.GTK
38 import qubx.pyenv
39 import qubx.settings
40 import qubx.task
41 import qubx.util_types
42
43 from qubx.http_post import *
44
45 MAX_CRASHREP_DATAPOINTS = 20000
46
47 PIDFILE = 'pid.txt'
48 State = qubx.util_types.Anon()
49
52
54 qubx.pyenv.env.OnScriptable += LogScriptable
55 pid_path = State.pid_path = os.path.join(qubx.pyenv.env.folder, PIDFILE)
56 if os.path.exists(pid_path):
57 try:
58 pid = int(open(pid_path, 'r').read())
59 if pid_exists(pid):
60 State.other_qubx = True
61 else:
62 log_path = os.path.join(qubx.pyenv.env.folder, 'output.log.0')
63 if os.path.exists(log_path):
64 offer_report_crash("It looks like the program may have crashed last time. Send a bug report?", log_path)
65 except:
66 traceback.print_exc()
67 pass
68 if not State.other_qubx:
69 open(pid_path, 'w').write(str(os.getpid()))
70
71
72 old_exc_hook = sys.excepthook
73 def exc_hook(typ, val, tb):
74 old_exc_hook(typ, val, tb)
75 if typ not in (KeyboardInterrupt, SystemExit):
76 offer_report_crash()
77 sys.excepthook = exc_hook
78
79
80 for base, dirs, files in os.walk(qubx.pyenv.env.folder):
81 dirs[:] = []
82 for fname in files:
83 if 'crash_rep_' in fname:
84 os.remove(os.path.join(base, fname))
85
87 if not State.other_qubx:
88 try:
89 os.remove(State.pid_path)
90 except:
91 pass
92
93
95 """Check whether pid exists in the current process table."""
96 if pid < 0:
97 return False
98 try:
99 os.kill(pid, 0)
100 except OSError, e:
101 return e.errno == errno.EPERM
102 except:
103 return is_pid_running_on_windows(pid)
104 else:
105 return True
106
108 import ctypes.wintypes
109
110 kernel32 = ctypes.windll.kernel32
111 handle = kernel32.OpenProcess(1, 0, pid)
112 if handle == 0:
113 return False
114
115
116
117 exit_code = ctypes.wintypes.DWORD()
118 is_running = (kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)) == 0)
119 kernel32.CloseHandle(handle)
120
121
122
123 return is_running or exit_code.value == _STILL_ACTIVE
124
125
127 sys.stdout.flush()
128 log_path = logpath or os.path.join(qubx.pyenv.env.folder, "output.log")
129 try:
130 logtext = open(log_path, 'r').read()
131 except:
132 logtext = traceback.format_exc()
133 settings = qubx.settings.SettingsMgr['qubx.crash_rep'].active
134 dlg = gtk.Dialog('Bug report', None, gtk.DIALOG_MODAL, buttons=None)
135 dlg.set_size_request(600, 400)
136 qubx.GTK.pack_label("", dlg.get_content_area())
137 h = qubx.GTK.pack_item(gtk.HBox(), dlg.get_content_area())
138 qubx.GTK.pack_label(message, h)
139 if qubx.global_namespace.QubX.has_modeling:
140 chkModel = qubx.GTK.pack_check('attach model', dlg.get_content_area(), active=True)
141 chkData = qubx.GTK.pack_check('attach data', dlg.get_content_area(), active=True)
142 h = qubx.GTK.pack_item(gtk.HBox(), dlg.get_content_area())
143 qubx.GTK.pack_label('Report contents:', h)
144 txtv = gtk.TextView()
145 qubx.GTK.pack_scrolled(txtv, dlg.get_content_area(), expand=True)
146 txtv.get_buffer().set_text("""Any comments about the problem and how to reproduce it?
147
148 (type here)
149
150
151 -------> Run log <---------------------------------------------------
152 %s
153
154 %s
155 """ % (logtext, qubx.global_namespace.QubX.has_modeling and qubx.global_namespace.QubX.Models.file or ""))
156 h = qubx.GTK.pack_item(gtk.HBox(), dlg.get_content_area())
157 qubx.GTK.pack_label('Your info (optional):', h)
158 h = qubx.GTK.pack_item(gtk.HBox(), dlg.get_content_area())
159 qubx.GTK.pack_label('Name:', h)
160 txtName = qubx.GTK.pack_item(qubx.GTK.NumEntry(str(settings['name'].data)), h, expand=True)
161 qubx.GTK.pack_label('Email:', h)
162 txtEmail = qubx.GTK.pack_item(qubx.GTK.NumEntry(str(settings['email'].data)), h, expand=True)
163 qubx.GTK.pack_button('Cancel', dlg.action_area, lambda item: dlg.response(gtk.RESPONSE_REJECT), at_end=True)
164 qubx.GTK.pack_button('Send', dlg.action_area, lambda item: dlg.response(gtk.RESPONSE_ACCEPT), at_end=True)
165 response = dlg.run()
166 buf = txtv.get_buffer()
167 logtext = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
168 dlg.destroy()
169 if response == gtk.RESPONSE_ACCEPT:
170 settings['name'].data = txtName.value
171 settings['email'].data = txtEmail.value
172 report_crash(txtName.value, txtEmail.value, log_path, logtext, qubx.global_namespace.QubX.has_modeling and chkModel.get_active(), chkData.get_active())
173
174
175 -def report_crash(name="", email="", logpath="", logtext="", attach_model=True, attach_data=True):
176 task = CrashReportTask(name or "Anonymous", ('@' in email) and email or 'qub@www.qub.buffalo.edu', logpath, logtext, attach_model, attach_data)
177 task.start()
178
180 - def __init__(self, name, email, logpath, logtext, attach_model, attach_data):
181 qubx.task.Task.__init__(self, 'Bug Report')
182 self.name = name
183 self.email = email
184 self.logpath = logpath
185 self.logtext = logtext
186 self.attach_model = attach_model
187 self.attach_data = attach_data
188 try:
189 self.setup_model()
190 except:
191 self.attach_model = False
192 traceback.print_exc()
193 try:
194 self.setup_data()
195 except:
196 self.attach_data = False
197 traceback.print_exc()
198 self.status = 'Sending...'
199 self.__ref_onException = self.__onException
200 self.OnException += self.__ref_onException
202 if typ != KeyboardInterrupt:
203 traceback.print_exception(typ, val, tb)
210 QubX = qubx.global_namespace.QubX
211 if not (self.attach_data and QubX.Data.file):
212 return
213 self.data_name = os.path.split(QubX.Data.file.path)[1] or 'Untitled.qdf'
214 if self.data_name == '<simulation>':
215 self.data_name = 'simulation.qdf'
216 self.data = QubX.Data.file
217 self.data_segs = QubX.Data.view.get_segmentation_screen()
218 if len(self.data_segs) == 0:
219 self.attach_data = False
220 return
221 seg = self.data_segs[0]
222 self.data_segs = [seg]
223 if seg.n > MAX_CRASHREP_DATAPOINTS:
224 seg.l = seg.f + MAX_CRASHREP_DATAPOINTS - 1
225 seg.n = MAX_CRASHREP_DATAPOINTS
226 seg.chunks = [chunk for chunk in seg.chunks if chunk.f <= seg.l]
227 if seg.chunks and (seg.chunks[-1].l > seg.l):
228 seg.chunks[-1].l = seg.l
229 seg.chunks[-1].n = seg.l - seg.chunks[-1].f + 1
230 self.segs_of_signal = [qubx.data_types.get_segmentation_copy(self.data_segs, signal=i) for i in xrange(QubX.Data.file.signals.size)]
231 self.data_path = os.path.join(qubx.pyenv.env.folder, 'crash_rep_%s.qdf' % datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))
232 self.session_name = os.path.splitext(self.data_name)[0]+'.qsf'
233 self.session_path = os.path.splitext(self.data_path)[0]+'.qsf'
234 qubx.data_types.Save(self.data_path, qubx.tree.Node(''), self.segs_of_signal, None, lambda pct: True,
235 QubX.Data.file.constants, QubX.Data.file.signals,
236 QubX.Data.file.stimuli, QubX.Data.view.signals)
238 qubx.task.Tasks.add_task(self)
239 try:
240 profile_log = cStringIO.StringIO()
241 for cat_name in sorted(qubx.settings.SettingsMgr.cats.keys()):
242 profile_log.write("\n%s:--------------------\n" % cat_name)
243 profile_log.write(str(qubx.settings.SettingsMgr[cat_name].active))
244 files = [('runlog', self.logpath, self.logtext),
245 ('profile_log', 'profile_log.txt', profile_log.getvalue())]
246 if self.attach_model:
247 files.append(('model', self.model_name, self.model))
248 if self.attach_data:
249 files.append(('data', self.data_name, open(self.data_path, 'rb').read()))
250 files.append(('session', self.session_name, open(self.session_path, 'rb').read()))
251
252 post_multipart('www.qub.buffalo.edu', '/bugreport.cgi',
253 [('name', self.name), ('email', self.email)],
254 files)
255 except:
256 traceback.print_exc()
257 try:
258 if self.data_path:
259 os.remove(self.data_path)
260 os.remove(self.session_path)
261 except:
262 traceback.print_exc()
263 qubx.task.Tasks.remove_task(self)
264