blob: db76fa77ca6c1cfb240656c1a74625fee70db5fa [file] [log] [blame]
Dennis Kempin351024d2013-02-06 11:18:21 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from gzip import GzipFile
Dennis Kempincee37612013-06-14 10:40:41 -07006from mtlib.feedback import FeedbackDownloader
Dennis Kempin351024d2013-02-06 11:18:21 -08007from StringIO import StringIO
8import base64
9import bz2
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040010import imghdr
Dennis Kempin351024d2013-02-06 11:18:21 -080011import os
12import os.path
13import re
14import subprocess
15import sys
16import tarfile
17import tempfile
Dennis Kempinecd9a0c2013-03-27 16:03:46 -070018import zipfile
Dennis Kempin351024d2013-02-06 11:18:21 -080019
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040020# path for temporary screenshot
Dennis Kempin17766a62013-06-17 14:09:33 -070021screenshot_filepath = 'extension/screenshot.jpg'
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040022
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070023def Log(source=None, options=None, activity=None, evdev=None):
Dennis Kempin17766a62013-06-17 14:09:33 -070024 """ Load log from feedback url, device ip or local file path.
25
26 Returns a log object containg activity, evdev and system logs.
27 source can be either an ip address to a device from which to
28 download, a url pointing to a feedback report to pull or a filename.
Dennis Kempin351024d2013-02-06 11:18:21 -080029 """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070030 if source:
31 if os.path.exists(source):
32 if source.endswith('.zip') or source.endswith('.bz2'):
33 return FeedbackLog(source, options)
34 return FileLog(source, options)
Dennis Kempin351024d2013-02-06 11:18:21 -080035 else:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070036 match = re.search('Report/([0-9]+)', source)
37 if match:
38 return FeedbackLog(match.group(1), options)
39 else:
40 # todo add regex and error message
41 return DeviceLog(source, options)
42
43 if activity or evdev:
44 log = AbstractLog()
45 if activity:
46 if os.path.exists(activity):
47 log.activity = open(activity).read()
48 else:
49 log.activity = activity
50 if evdev:
51 if os.path.exists(evdev):
52 log.evdev = open(evdev).read()
53 else:
54 log.evdev = evdev
55 return log
Dennis Kempin351024d2013-02-06 11:18:21 -080056
57
58class AbstractLog(object):
Dennis Kempin17766a62013-06-17 14:09:33 -070059 """ Common functions for log files
60
61 A log file consists of the activity log, the evdev log, a system log, and
62 possibly a screenshot, which can be saved to disk all together.
Dennis Kempin351024d2013-02-06 11:18:21 -080063 """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070064 def __init__(self):
65 self.activity = None
66 self.evdev = None
67 self.system = None
68 self.image = None
69
Dennis Kempin351024d2013-02-06 11:18:21 -080070 def SaveAs(self, filename):
Dennis Kempin17766a62013-06-17 14:09:33 -070071 open(filename, 'w').write(self.activity)
Dennis Kempin351024d2013-02-06 11:18:21 -080072 if self.evdev:
Dennis Kempin17766a62013-06-17 14:09:33 -070073 open(filename + '.evdev', 'w').write(self.evdev)
Dennis Kempin351024d2013-02-06 11:18:21 -080074 if self.system:
Dennis Kempin17766a62013-06-17 14:09:33 -070075 open(filename + '.system', 'w').write(self.system)
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040076 if self.image:
Dennis Kempin17766a62013-06-17 14:09:33 -070077 open(filename + '.jpg', 'w').write(self.image)
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040078
79 def CleanUp(self):
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040080 if os.path.exists(screenshot_filepath):
81 os.remove(screenshot_filepath)
Dennis Kempin351024d2013-02-06 11:18:21 -080082
83
84class FileLog(AbstractLog):
Dennis Kempin17766a62013-06-17 14:09:33 -070085 """ Loads log from file.
86
87 evdev or system logs are optional.
Dennis Kempin351024d2013-02-06 11:18:21 -080088 """
89 def __init__(self, filename, options):
90 self.activity = open(filename).read()
Dennis Kempin17766a62013-06-17 14:09:33 -070091 self.evdev = ''
92 self.system = ''
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040093 self.image = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070094 if options and options.evdev:
Dennis Kempin7b783272013-04-01 15:06:35 -070095 self.evdev = open(options.evdev).read()
Dennis Kempin17766a62013-06-17 14:09:33 -070096 elif os.path.exists(filename + '.evdev'):
97 self.evdev = open(filename + '.evdev').read()
98 if os.path.exists(filename + '.system'):
99 self.system = open(filename + '.system').read()
100 if os.path.exists(filename + '.jpg'):
101 self.image = open(filename + '.jpg').read()
102 file(screenshot_filepath, 'w').write(self.image)
Dennis Kempin351024d2013-02-06 11:18:21 -0800103
104
105class FeedbackLog(AbstractLog):
Dennis Kempin17766a62013-06-17 14:09:33 -0700106 """ Download log from feedback url.
107
108 Downloads logs and (possibly) screenshot from a feedback id or file name
Dennis Kempin351024d2013-02-06 11:18:21 -0800109 """
Dennis Kempinb049d542013-06-13 13:55:18 -0700110 def __init__(self, id_or_filename, options=None, force_latest=None):
WeiNan-Peter, Wene539a182013-05-16 15:31:21 -0400111 self.image = None
Dennis Kempinb049d542013-06-13 13:55:18 -0700112 self.force_latest = force_latest
113 self.try_screenshot = options and options.screenshot
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400114
Dennis Kempin17766a62013-06-17 14:09:33 -0700115 if id_or_filename.endswith('.zip') or id_or_filename.endswith('.bz2'):
Dennis Kempin91e96df2013-03-29 14:21:42 -0700116 self.report = open(id_or_filename).read()
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400117 screenshot_filename = id_or_filename[:-4] + '.jpg'
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -0700118 try:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400119 self.image = open(screenshot_filename).read()
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -0700120 except IOError:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400121 # No corresponding screenshot available.
122 pass
123 else:
Dennis Kempinb049d542013-06-13 13:55:18 -0700124 self.downloader = FeedbackDownloader()
Dennis Kempincee37612013-06-14 10:40:41 -0700125 self.report = self.downloader.DownloadSystemLog(id_or_filename)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400126 self._ExtractSystemLog()
127 self._ExtractLogFiles()
128 if self.try_screenshot:
Dennis Kempincee37612013-06-14 10:40:41 -0700129 self.image = self.downloader.DownloadScreenshot(id_or_filename)
Dennis Kempin351024d2013-02-06 11:18:21 -0800130
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400131 # Only write to screenshot.jpg if we will be viewing the screenshot
Dennis Kempinb049d542013-06-13 13:55:18 -0700132 if options and not options.download and self.image:
Dennis Kempin17766a62013-06-17 14:09:33 -0700133 file(screenshot_filepath, 'w').write(self.image)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400134
Dennis Kempin351024d2013-02-06 11:18:21 -0800135
136 def _GetLatestFile(self, match, tar):
137 # find file name containing match with latest timestamp
138 names = filter(lambda n: n.find(match) >= 0, tar.getnames())
139 names.sort()
Dennis Kempin765ad942013-03-26 13:58:46 -0700140 if not names:
Dennis Kempin17766a62013-06-17 14:09:33 -0700141 print 'Cannot find files named %s in tar file' % match
142 print 'Tar file contents:', tar.getnames()
Dennis Kempin765ad942013-03-26 13:58:46 -0700143 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800144 return tar.extractfile(names[-1])
145
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400146
Dennis Kempin91e96df2013-03-29 14:21:42 -0700147 def _ExtractSystemLog(self):
Dennis Kempin17766a62013-06-17 14:09:33 -0700148 if self.report[0:2] == 'BZ':
Dennis Kempin91e96df2013-03-29 14:21:42 -0700149 self.system = bz2.decompress(self.report)
Dennis Kempin17766a62013-06-17 14:09:33 -0700150 elif self.report[0:2] == 'PK':
Dennis Kempin91e96df2013-03-29 14:21:42 -0700151 io = StringIO(self.report)
Dennis Kempin17766a62013-06-17 14:09:33 -0700152 zip = zipfile.ZipFile(io, 'r')
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700153 self.system = zip.read(zip.namelist()[0])
Dennis Kempin17766a62013-06-17 14:09:33 -0700154 zip.extractall('./')
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700155 else:
Dennis Kempin17766a62013-06-17 14:09:33 -0700156 print 'Cannot download logs file'
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700157 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800158
159 def _ExtractLogFiles(self):
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400160 # Find embedded and uuencoded activity.tar in system log
161
162 def ExtractByInterface(interface):
163 assert interface == 'pad' or interface == 'screen'
164
165 log_index = [
Dennis Kempin17766a62013-06-17 14:09:33 -0700166 (('hack-33025-touch{0}_activity=\'\'\'\n' +
167 'begin-base64 644 touch{0}_activity_log.tar\n').format(interface),
168 '"""'),
169 (('touch{0}_activity=\'\'\'\n' +
170 'begin-base64 644 touch{0}_activity_log.tar\n').format(interface),
171 '"""'),
172 (('hack-33025-touch{0}_activity=<multiline>\n' +
173 '---------- START ----------\n' +
174 'begin-base64 644 touch{0}_activity_log.tar\n').format(interface),
175 '---------- END ----------'),
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400176 ]
177
178 start_index = end_index = None
179 for start, end in log_index:
180 if start in self.system:
181 start_index = self.system.index(start) + len(start)
182 end_index = self.system.index(end, start_index)
183 break
184
185 if start_index is None:
186 return []
187
188 activity_tar_enc = self.system[start_index:end_index]
189
190 # base64 decode
191 activity_tar_data = base64.b64decode(activity_tar_enc)
192
193 # untar
194 activity_tar_file = tarfile.open(fileobj=StringIO(activity_tar_data))
195
196 def ExtractPadFiles(name):
197 # find matching evdev file
198 evdev_name = name[0:name.index('touchpad_activity')]
Dennis Kempin17766a62013-06-17 14:09:33 -0700199 evdev_name = evdev_name + 'cmt_input_events'
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400200
201 for file_name in activity_tar_file.getnames():
202 if file_name.startswith(evdev_name):
203 evdev_name = file_name
204 break
205
206 activity_gz = activity_tar_file.extractfile(name)
207 evdev_gz = activity_tar_file.extractfile(evdev_name)
208
209 # decompress log files
210 return (GzipFile(fileobj=activity_gz).read(),
211 GzipFile(fileobj=evdev_gz).read())
212
213 def ExtractScreenFiles(name):
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400214 # Always try for a screenshot with touchscreen view
215 self.try_screenshot = True
216
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400217 evdev = activity_tar_file.extractfile(name)
218 return (evdev.read(), None)
219
220 extract_func = {
221 'pad': ExtractPadFiles,
222 'screen': ExtractScreenFiles,
223 }
224
225 # return latest log files, we don't include cmt files
226 return [(filename, extract_func[interface])
227 for filename in activity_tar_file.getnames()
228 if filename.find('cmt') == -1]
229
Dennis Kempin17766a62013-06-17 14:09:33 -0700230 if self.force_latest == 'pad':
Dennis Kempinb049d542013-06-13 13:55:18 -0700231 logs = ExtractByInterface('pad')
232 idx = 0
Dennis Kempin17766a62013-06-17 14:09:33 -0700233 elif self.force_latest == 'screen':
Dennis Kempinb049d542013-06-13 13:55:18 -0700234 logs = ExtractByInterface('screen')
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700235 idx = 0
236 else:
Dennis Kempinb049d542013-06-13 13:55:18 -0700237 logs = ExtractByInterface('pad') + ExtractByInterface('screen')
238 if not logs:
Dennis Kempin17766a62013-06-17 14:09:33 -0700239 print ('Cannot find touchpad_activity_log.tar or ' +
240 'touchscreen_activity_log.tar in systems log file.')
Dennis Kempinb049d542013-06-13 13:55:18 -0700241 sys.exit(-1)
242
243 if len(logs) == 1:
244 idx = 0
245 else:
246 while True:
Dennis Kempin17766a62013-06-17 14:09:33 -0700247 print 'Which log file would you like to use?'
Dennis Kempinb049d542013-06-13 13:55:18 -0700248 for i, (name, extract_func) in enumerate(logs):
249 if name.startswith('evdev'):
250 name = 'touchscreen log - ' + name
251 print i, name
Dennis Kempin17766a62013-06-17 14:09:33 -0700252 print '>'
Dennis Kempinb049d542013-06-13 13:55:18 -0700253 selection = sys.stdin.readline()
254 try:
255 idx = int(selection)
256 if idx < 0 or idx >= len(logs):
Dennis Kempin17766a62013-06-17 14:09:33 -0700257 print 'Number out of range'
Dennis Kempinb049d542013-06-13 13:55:18 -0700258 else:
259 break
260 except:
Dennis Kempin17766a62013-06-17 14:09:33 -0700261 print 'Not a number'
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700262
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400263 name, extract_func = logs[idx]
264 self.activity, self.evdev = extract_func(name)
Dennis Kempin351024d2013-02-06 11:18:21 -0800265
266
267identity_message = """\
268In order to access devices in TPTool, you need to have password-less
269auth for chromebooks set up.
270Would you like tptool to run the following command for you?
271$ %s
272Yes/No? (Default: No)"""
273
274class DeviceLog(AbstractLog):
Dennis Kempin17766a62013-06-17 14:09:33 -0700275 """ Downloads logs from a running chromebook via scp. """
Dennis Kempin351024d2013-02-06 11:18:21 -0800276 def __init__(self, ip, options):
Dennis Kempin17766a62013-06-17 14:09:33 -0700277 self.id_path = os.path.expanduser('~/.ssh/identity')
Dennis Kempin351024d2013-02-06 11:18:21 -0800278 if not os.path.exists(self.id_path):
279 self._SetupIdentity()
280
281 if options.new:
282 self._GenerateLogs(ip)
283
Dennis Kempin17766a62013-06-17 14:09:33 -0700284 self.activity = self._Download(ip, 'touchpad_activity_log.txt')
285 self.evdev = self._Download(ip, 'cmt_input_events.dat')
286 self.system = ''
Dennis Kempin351024d2013-02-06 11:18:21 -0800287
288 def _GenerateLogs(self, ip):
Dennis Kempin17766a62013-06-17 14:09:33 -0700289 cmd = ('ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
290 '-q root@%s /opt/google/touchpad/tpcontrol log') % ip
Dennis Kempin351024d2013-02-06 11:18:21 -0800291 process = subprocess.Popen(cmd.split())
292 process.wait()
293
294 def _Download(self, ip, filename):
Dennis Kempin17766a62013-06-17 14:09:33 -0700295 temp = tempfile.NamedTemporaryFile('r')
296 cmd = ('scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
297 '-q root@%s:/var/log/%s %s')
Dennis Kempin351024d2013-02-06 11:18:21 -0800298 cmd = cmd % (ip, filename, temp.name)
299 process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
300 process.wait()
301 temp.seek(0)
302 return temp.read()
303
304 def _SetupIdentity(self):
Dennis Kempin17766a62013-06-17 14:09:33 -0700305 source = os.path.expanduser('~/trunk/chromite/ssh_keys/testing_rsa')
306 command = 'cp %s %s && chmod 600 %s' % (source, self.id_path, self.id_path)
Dennis Kempin351024d2013-02-06 11:18:21 -0800307 print identity_message % command
308 response = sys.stdin.readline().strip()
Dennis Kempin17766a62013-06-17 14:09:33 -0700309 if response in ('Yes', 'yes', 'Y', 'y'):
Dennis Kempin351024d2013-02-06 11:18:21 -0800310 print command
311 os.system(command)