Linux 4.1.18
[linux/fpc-iii.git] / scripts / analyze_suspend.py
blob93e1fd40f43053e599c3d986dd15fb574ea2fb76
1 #!/usr/bin/python
3 # Tool for analyzing suspend/resume timing
4 # Copyright (c) 2013, Intel Corporation.
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms and conditions of the GNU General Public License,
8 # version 2, as published by the Free Software Foundation.
10 # This program is distributed in the hope it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
19 # Authors:
20 # Todd Brandt <todd.e.brandt@linux.intel.com>
22 # Description:
23 # This tool is designed to assist kernel and OS developers in optimizing
24 # their linux stack's suspend/resume time. Using a kernel image built
25 # with a few extra options enabled, the tool will execute a suspend and
26 # will capture dmesg and ftrace data until resume is complete. This data
27 # is transformed into a device timeline and a callgraph to give a quick
28 # and detailed view of which devices and callbacks are taking the most
29 # time in suspend/resume. The output is a single html file which can be
30 # viewed in firefox or chrome.
32 # The following kernel build options are required:
33 # CONFIG_PM_DEBUG=y
34 # CONFIG_PM_SLEEP_DEBUG=y
35 # CONFIG_FTRACE=y
36 # CONFIG_FUNCTION_TRACER=y
37 # CONFIG_FUNCTION_GRAPH_TRACER=y
39 # For kernel versions older than 3.15:
40 # The following additional kernel parameters are required:
41 # (e.g. in file /etc/default/grub)
42 # GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
45 # ----------------- LIBRARIES --------------------
47 import sys
48 import time
49 import os
50 import string
51 import re
52 import platform
53 from datetime import datetime
54 import struct
56 # ----------------- CLASSES --------------------
58 # Class: SystemValues
59 # Description:
60 # A global, single-instance container used to
61 # store system values and test parameters
62 class SystemValues:
63 version = 3.0
64 verbose = False
65 testdir = '.'
66 tpath = '/sys/kernel/debug/tracing/'
67 fpdtpath = '/sys/firmware/acpi/tables/FPDT'
68 epath = '/sys/kernel/debug/tracing/events/power/'
69 traceevents = [
70 'suspend_resume',
71 'device_pm_callback_end',
72 'device_pm_callback_start'
74 modename = {
75 'freeze': 'Suspend-To-Idle (S0)',
76 'standby': 'Power-On Suspend (S1)',
77 'mem': 'Suspend-to-RAM (S3)',
78 'disk': 'Suspend-to-disk (S4)'
80 mempath = '/dev/mem'
81 powerfile = '/sys/power/state'
82 suspendmode = 'mem'
83 hostname = 'localhost'
84 prefix = 'test'
85 teststamp = ''
86 dmesgfile = ''
87 ftracefile = ''
88 htmlfile = ''
89 rtcwake = False
90 rtcwaketime = 10
91 rtcpath = ''
92 android = False
93 adb = 'adb'
94 devicefilter = []
95 stamp = 0
96 execcount = 1
97 x2delay = 0
98 usecallgraph = False
99 usetraceevents = False
100 usetraceeventsonly = False
101 notestrun = False
102 altdevname = dict()
103 postresumetime = 0
104 tracertypefmt = '# tracer: (?P<t>.*)'
105 firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
106 postresumefmt = '# post resume time (?P<t>[0-9]*)$'
107 stampfmt = '# suspend-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
108 '(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
109 ' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
110 def __init__(self):
111 self.hostname = platform.node()
112 if(self.hostname == ''):
113 self.hostname = 'localhost'
114 rtc = "rtc0"
115 if os.path.exists('/dev/rtc'):
116 rtc = os.readlink('/dev/rtc')
117 rtc = '/sys/class/rtc/'+rtc
118 if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
119 os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
120 self.rtcpath = rtc
121 def setOutputFile(self):
122 if((self.htmlfile == '') and (self.dmesgfile != '')):
123 m = re.match('(?P<name>.*)_dmesg\.txt$', self.dmesgfile)
124 if(m):
125 self.htmlfile = m.group('name')+'.html'
126 if((self.htmlfile == '') and (self.ftracefile != '')):
127 m = re.match('(?P<name>.*)_ftrace\.txt$', self.ftracefile)
128 if(m):
129 self.htmlfile = m.group('name')+'.html'
130 if(self.htmlfile == ''):
131 self.htmlfile = 'output.html'
132 def initTestOutput(self, subdir):
133 if(not self.android):
134 self.prefix = self.hostname
135 v = open('/proc/version', 'r').read().strip()
136 kver = string.split(v)[2]
137 else:
138 self.prefix = 'android'
139 v = os.popen(self.adb+' shell cat /proc/version').read().strip()
140 kver = string.split(v)[2]
141 testtime = datetime.now().strftime('suspend-%m%d%y-%H%M%S')
142 if(subdir != "."):
143 self.testdir = subdir+"/"+testtime
144 else:
145 self.testdir = testtime
146 self.teststamp = \
147 '# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
148 self.dmesgfile = \
149 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'
150 self.ftracefile = \
151 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'
152 self.htmlfile = \
153 self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
154 os.mkdir(self.testdir)
155 def setDeviceFilter(self, devnames):
156 self.devicefilter = string.split(devnames)
157 def rtcWakeAlarm(self):
158 os.system('echo 0 > '+self.rtcpath+'/wakealarm')
159 outD = open(self.rtcpath+'/date', 'r').read().strip()
160 outT = open(self.rtcpath+'/time', 'r').read().strip()
161 mD = re.match('^(?P<y>[0-9]*)-(?P<m>[0-9]*)-(?P<d>[0-9]*)', outD)
162 mT = re.match('^(?P<h>[0-9]*):(?P<m>[0-9]*):(?P<s>[0-9]*)', outT)
163 if(mD and mT):
164 # get the current time from hardware
165 utcoffset = int((datetime.now() - datetime.utcnow()).total_seconds())
166 dt = datetime(\
167 int(mD.group('y')), int(mD.group('m')), int(mD.group('d')),
168 int(mT.group('h')), int(mT.group('m')), int(mT.group('s')))
169 nowtime = int(dt.strftime('%s')) + utcoffset
170 else:
171 # if hardware time fails, use the software time
172 nowtime = int(datetime.now().strftime('%s'))
173 alarm = nowtime + self.rtcwaketime
174 os.system('echo %d > %s/wakealarm' % (alarm, self.rtcpath))
176 sysvals = SystemValues()
178 # Class: DeviceNode
179 # Description:
180 # A container used to create a device hierachy, with a single root node
181 # and a tree of child nodes. Used by Data.deviceTopology()
182 class DeviceNode:
183 name = ''
184 children = 0
185 depth = 0
186 def __init__(self, nodename, nodedepth):
187 self.name = nodename
188 self.children = []
189 self.depth = nodedepth
191 # Class: Data
192 # Description:
193 # The primary container for suspend/resume test data. There is one for
194 # each test run. The data is organized into a cronological hierarchy:
195 # Data.dmesg {
196 # root structure, started as dmesg & ftrace, but now only ftrace
197 # contents: times for suspend start/end, resume start/end, fwdata
198 # phases {
199 # 10 sequential, non-overlapping phases of S/R
200 # contents: times for phase start/end, order/color data for html
201 # devlist {
202 # device callback or action list for this phase
203 # device {
204 # a single device callback or generic action
205 # contents: start/stop times, pid/cpu/driver info
206 # parents/children, html id for timeline/callgraph
207 # optionally includes an ftrace callgraph
208 # optionally includes intradev trace events
214 class Data:
215 dmesg = {} # root data structure
216 phases = [] # ordered list of phases
217 start = 0.0 # test start
218 end = 0.0 # test end
219 tSuspended = 0.0 # low-level suspend start
220 tResumed = 0.0 # low-level resume start
221 tLow = 0.0 # time spent in low-level suspend (standby/freeze)
222 fwValid = False # is firmware data available
223 fwSuspend = 0 # time spent in firmware suspend
224 fwResume = 0 # time spent in firmware resume
225 dmesgtext = [] # dmesg text file in memory
226 testnumber = 0
227 idstr = ''
228 html_device_id = 0
229 stamp = 0
230 outfile = ''
231 def __init__(self, num):
232 idchar = 'abcdefghijklmnopqrstuvwxyz'
233 self.testnumber = num
234 self.idstr = idchar[num]
235 self.dmesgtext = []
236 self.phases = []
237 self.dmesg = { # fixed list of 10 phases
238 'suspend_prepare': {'list': dict(), 'start': -1.0, 'end': -1.0,
239 'row': 0, 'color': '#CCFFCC', 'order': 0},
240 'suspend': {'list': dict(), 'start': -1.0, 'end': -1.0,
241 'row': 0, 'color': '#88FF88', 'order': 1},
242 'suspend_late': {'list': dict(), 'start': -1.0, 'end': -1.0,
243 'row': 0, 'color': '#00AA00', 'order': 2},
244 'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
245 'row': 0, 'color': '#008888', 'order': 3},
246 'suspend_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
247 'row': 0, 'color': '#0000FF', 'order': 4},
248 'resume_machine': {'list': dict(), 'start': -1.0, 'end': -1.0,
249 'row': 0, 'color': '#FF0000', 'order': 5},
250 'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
251 'row': 0, 'color': '#FF9900', 'order': 6},
252 'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
253 'row': 0, 'color': '#FFCC00', 'order': 7},
254 'resume': {'list': dict(), 'start': -1.0, 'end': -1.0,
255 'row': 0, 'color': '#FFFF88', 'order': 8},
256 'resume_complete': {'list': dict(), 'start': -1.0, 'end': -1.0,
257 'row': 0, 'color': '#FFFFCC', 'order': 9}
259 self.phases = self.sortedPhases()
260 def getStart(self):
261 return self.dmesg[self.phases[0]]['start']
262 def setStart(self, time):
263 self.start = time
264 self.dmesg[self.phases[0]]['start'] = time
265 def getEnd(self):
266 return self.dmesg[self.phases[-1]]['end']
267 def setEnd(self, time):
268 self.end = time
269 self.dmesg[self.phases[-1]]['end'] = time
270 def isTraceEventOutsideDeviceCalls(self, pid, time):
271 for phase in self.phases:
272 list = self.dmesg[phase]['list']
273 for dev in list:
274 d = list[dev]
275 if(d['pid'] == pid and time >= d['start'] and
276 time <= d['end']):
277 return False
278 return True
279 def addIntraDevTraceEvent(self, action, name, pid, time):
280 if(action == 'mutex_lock_try'):
281 color = 'red'
282 elif(action == 'mutex_lock_pass'):
283 color = 'green'
284 elif(action == 'mutex_unlock'):
285 color = 'blue'
286 else:
287 # create separate colors based on the name
288 v1 = len(name)*10 % 256
289 v2 = string.count(name, 'e')*100 % 256
290 v3 = ord(name[0])*20 % 256
291 color = '#%06X' % ((v1*0x10000) + (v2*0x100) + v3)
292 for phase in self.phases:
293 list = self.dmesg[phase]['list']
294 for dev in list:
295 d = list[dev]
296 if(d['pid'] == pid and time >= d['start'] and
297 time <= d['end']):
298 e = TraceEvent(action, name, color, time)
299 if('traceevents' not in d):
300 d['traceevents'] = []
301 d['traceevents'].append(e)
302 return d
303 break
304 return 0
305 def capIntraDevTraceEvent(self, action, name, pid, time):
306 for phase in self.phases:
307 list = self.dmesg[phase]['list']
308 for dev in list:
309 d = list[dev]
310 if(d['pid'] == pid and time >= d['start'] and
311 time <= d['end']):
312 if('traceevents' not in d):
313 return
314 for e in d['traceevents']:
315 if(e.action == action and
316 e.name == name and not e.ready):
317 e.length = time - e.time
318 e.ready = True
319 break
320 return
321 def trimTimeVal(self, t, t0, dT, left):
322 if left:
323 if(t > t0):
324 if(t - dT < t0):
325 return t0
326 return t - dT
327 else:
328 return t
329 else:
330 if(t < t0 + dT):
331 if(t > t0):
332 return t0 + dT
333 return t + dT
334 else:
335 return t
336 def trimTime(self, t0, dT, left):
337 self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
338 self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
339 self.start = self.trimTimeVal(self.start, t0, dT, left)
340 self.end = self.trimTimeVal(self.end, t0, dT, left)
341 for phase in self.phases:
342 p = self.dmesg[phase]
343 p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
344 p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
345 list = p['list']
346 for name in list:
347 d = list[name]
348 d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
349 d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
350 if('ftrace' in d):
351 cg = d['ftrace']
352 cg.start = self.trimTimeVal(cg.start, t0, dT, left)
353 cg.end = self.trimTimeVal(cg.end, t0, dT, left)
354 for line in cg.list:
355 line.time = self.trimTimeVal(line.time, t0, dT, left)
356 if('traceevents' in d):
357 for e in d['traceevents']:
358 e.time = self.trimTimeVal(e.time, t0, dT, left)
359 def normalizeTime(self, tZero):
360 # first trim out any standby or freeze clock time
361 if(self.tSuspended != self.tResumed):
362 if(self.tResumed > tZero):
363 self.trimTime(self.tSuspended, \
364 self.tResumed-self.tSuspended, True)
365 else:
366 self.trimTime(self.tSuspended, \
367 self.tResumed-self.tSuspended, False)
368 # shift the timeline so that tZero is the new 0
369 self.tSuspended -= tZero
370 self.tResumed -= tZero
371 self.start -= tZero
372 self.end -= tZero
373 for phase in self.phases:
374 p = self.dmesg[phase]
375 p['start'] -= tZero
376 p['end'] -= tZero
377 list = p['list']
378 for name in list:
379 d = list[name]
380 d['start'] -= tZero
381 d['end'] -= tZero
382 if('ftrace' in d):
383 cg = d['ftrace']
384 cg.start -= tZero
385 cg.end -= tZero
386 for line in cg.list:
387 line.time -= tZero
388 if('traceevents' in d):
389 for e in d['traceevents']:
390 e.time -= tZero
391 def newPhaseWithSingleAction(self, phasename, devname, start, end, color):
392 for phase in self.phases:
393 self.dmesg[phase]['order'] += 1
394 self.html_device_id += 1
395 devid = '%s%d' % (self.idstr, self.html_device_id)
396 list = dict()
397 list[devname] = \
398 {'start': start, 'end': end, 'pid': 0, 'par': '',
399 'length': (end-start), 'row': 0, 'id': devid, 'drv': '' };
400 self.dmesg[phasename] = \
401 {'list': list, 'start': start, 'end': end,
402 'row': 0, 'color': color, 'order': 0}
403 self.phases = self.sortedPhases()
404 def newPhase(self, phasename, start, end, color, order):
405 if(order < 0):
406 order = len(self.phases)
407 for phase in self.phases[order:]:
408 self.dmesg[phase]['order'] += 1
409 if(order > 0):
410 p = self.phases[order-1]
411 self.dmesg[p]['end'] = start
412 if(order < len(self.phases)):
413 p = self.phases[order]
414 self.dmesg[p]['start'] = end
415 list = dict()
416 self.dmesg[phasename] = \
417 {'list': list, 'start': start, 'end': end,
418 'row': 0, 'color': color, 'order': order}
419 self.phases = self.sortedPhases()
420 def setPhase(self, phase, ktime, isbegin):
421 if(isbegin):
422 self.dmesg[phase]['start'] = ktime
423 else:
424 self.dmesg[phase]['end'] = ktime
425 def dmesgSortVal(self, phase):
426 return self.dmesg[phase]['order']
427 def sortedPhases(self):
428 return sorted(self.dmesg, key=self.dmesgSortVal)
429 def sortedDevices(self, phase):
430 list = self.dmesg[phase]['list']
431 slist = []
432 tmp = dict()
433 for devname in list:
434 dev = list[devname]
435 tmp[dev['start']] = devname
436 for t in sorted(tmp):
437 slist.append(tmp[t])
438 return slist
439 def fixupInitcalls(self, phase, end):
440 # if any calls never returned, clip them at system resume end
441 phaselist = self.dmesg[phase]['list']
442 for devname in phaselist:
443 dev = phaselist[devname]
444 if(dev['end'] < 0):
445 dev['end'] = end
446 vprint('%s (%s): callback didnt return' % (devname, phase))
447 def deviceFilter(self, devicefilter):
448 # remove all by the relatives of the filter devnames
449 filter = []
450 for phase in self.phases:
451 list = self.dmesg[phase]['list']
452 for name in devicefilter:
453 dev = name
454 while(dev in list):
455 if(dev not in filter):
456 filter.append(dev)
457 dev = list[dev]['par']
458 children = self.deviceDescendants(name, phase)
459 for dev in children:
460 if(dev not in filter):
461 filter.append(dev)
462 for phase in self.phases:
463 list = self.dmesg[phase]['list']
464 rmlist = []
465 for name in list:
466 pid = list[name]['pid']
467 if(name not in filter and pid >= 0):
468 rmlist.append(name)
469 for name in rmlist:
470 del list[name]
471 def fixupInitcallsThatDidntReturn(self):
472 # if any calls never returned, clip them at system resume end
473 for phase in self.phases:
474 self.fixupInitcalls(phase, self.getEnd())
475 def newActionGlobal(self, name, start, end):
476 # which phase is this device callback or action "in"
477 targetphase = "none"
478 overlap = 0.0
479 for phase in self.phases:
480 pstart = self.dmesg[phase]['start']
481 pend = self.dmesg[phase]['end']
482 o = max(0, min(end, pend) - max(start, pstart))
483 if(o > overlap):
484 targetphase = phase
485 overlap = o
486 if targetphase in self.phases:
487 self.newAction(targetphase, name, -1, '', start, end, '')
488 return True
489 return False
490 def newAction(self, phase, name, pid, parent, start, end, drv):
491 # new device callback for a specific phase
492 self.html_device_id += 1
493 devid = '%s%d' % (self.idstr, self.html_device_id)
494 list = self.dmesg[phase]['list']
495 length = -1.0
496 if(start >= 0 and end >= 0):
497 length = end - start
498 list[name] = {'start': start, 'end': end, 'pid': pid, 'par': parent,
499 'length': length, 'row': 0, 'id': devid, 'drv': drv }
500 def deviceIDs(self, devlist, phase):
501 idlist = []
502 list = self.dmesg[phase]['list']
503 for devname in list:
504 if devname in devlist:
505 idlist.append(list[devname]['id'])
506 return idlist
507 def deviceParentID(self, devname, phase):
508 pdev = ''
509 pdevid = ''
510 list = self.dmesg[phase]['list']
511 if devname in list:
512 pdev = list[devname]['par']
513 if pdev in list:
514 return list[pdev]['id']
515 return pdev
516 def deviceChildren(self, devname, phase):
517 devlist = []
518 list = self.dmesg[phase]['list']
519 for child in list:
520 if(list[child]['par'] == devname):
521 devlist.append(child)
522 return devlist
523 def deviceDescendants(self, devname, phase):
524 children = self.deviceChildren(devname, phase)
525 family = children
526 for child in children:
527 family += self.deviceDescendants(child, phase)
528 return family
529 def deviceChildrenIDs(self, devname, phase):
530 devlist = self.deviceChildren(devname, phase)
531 return self.deviceIDs(devlist, phase)
532 def printDetails(self):
533 vprint(' test start: %f' % self.start)
534 for phase in self.phases:
535 dc = len(self.dmesg[phase]['list'])
536 vprint(' %16s: %f - %f (%d devices)' % (phase, \
537 self.dmesg[phase]['start'], self.dmesg[phase]['end'], dc))
538 vprint(' test end: %f' % self.end)
539 def masterTopology(self, name, list, depth):
540 node = DeviceNode(name, depth)
541 for cname in list:
542 clist = self.deviceChildren(cname, 'resume')
543 cnode = self.masterTopology(cname, clist, depth+1)
544 node.children.append(cnode)
545 return node
546 def printTopology(self, node):
547 html = ''
548 if node.name:
549 info = ''
550 drv = ''
551 for phase in self.phases:
552 list = self.dmesg[phase]['list']
553 if node.name in list:
554 s = list[node.name]['start']
555 e = list[node.name]['end']
556 if list[node.name]['drv']:
557 drv = ' {'+list[node.name]['drv']+'}'
558 info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
559 html += '<li><b>'+node.name+drv+'</b>'
560 if info:
561 html += '<ul>'+info+'</ul>'
562 html += '</li>'
563 if len(node.children) > 0:
564 html += '<ul>'
565 for cnode in node.children:
566 html += self.printTopology(cnode)
567 html += '</ul>'
568 return html
569 def rootDeviceList(self):
570 # list of devices graphed
571 real = []
572 for phase in self.dmesg:
573 list = self.dmesg[phase]['list']
574 for dev in list:
575 if list[dev]['pid'] >= 0 and dev not in real:
576 real.append(dev)
577 # list of top-most root devices
578 rootlist = []
579 for phase in self.dmesg:
580 list = self.dmesg[phase]['list']
581 for dev in list:
582 pdev = list[dev]['par']
583 if(re.match('[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
584 continue
585 if pdev and pdev not in real and pdev not in rootlist:
586 rootlist.append(pdev)
587 return rootlist
588 def deviceTopology(self):
589 rootlist = self.rootDeviceList()
590 master = self.masterTopology('', rootlist, 0)
591 return self.printTopology(master)
593 # Class: TraceEvent
594 # Description:
595 # A container for trace event data found in the ftrace file
596 class TraceEvent:
597 ready = False
598 name = ''
599 time = 0.0
600 color = '#FFFFFF'
601 length = 0.0
602 action = ''
603 def __init__(self, a, n, c, t):
604 self.action = a
605 self.name = n
606 self.color = c
607 self.time = t
609 # Class: FTraceLine
610 # Description:
611 # A container for a single line of ftrace data. There are six basic types:
612 # callgraph line:
613 # call: " dpm_run_callback() {"
614 # return: " }"
615 # leaf: " dpm_run_callback();"
616 # trace event:
617 # tracing_mark_write: SUSPEND START or RESUME COMPLETE
618 # suspend_resume: phase or custom exec block data
619 # device_pm_callback: device callback info
620 class FTraceLine:
621 time = 0.0
622 length = 0.0
623 fcall = False
624 freturn = False
625 fevent = False
626 depth = 0
627 name = ''
628 type = ''
629 def __init__(self, t, m, d):
630 self.time = float(t)
631 # is this a trace event
632 if(d == 'traceevent' or re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
633 if(d == 'traceevent'):
634 # nop format trace event
635 msg = m
636 else:
637 # function_graph format trace event
638 em = re.match('^ *\/\* *(?P<msg>.*) \*\/ *$', m)
639 msg = em.group('msg')
641 emm = re.match('^(?P<call>.*?): (?P<msg>.*)', msg)
642 if(emm):
643 self.name = emm.group('msg')
644 self.type = emm.group('call')
645 else:
646 self.name = msg
647 self.fevent = True
648 return
649 # convert the duration to seconds
650 if(d):
651 self.length = float(d)/1000000
652 # the indentation determines the depth
653 match = re.match('^(?P<d> *)(?P<o>.*)$', m)
654 if(not match):
655 return
656 self.depth = self.getDepth(match.group('d'))
657 m = match.group('o')
658 # function return
659 if(m[0] == '}'):
660 self.freturn = True
661 if(len(m) > 1):
662 # includes comment with function name
663 match = re.match('^} *\/\* *(?P<n>.*) *\*\/$', m)
664 if(match):
665 self.name = match.group('n')
666 # function call
667 else:
668 self.fcall = True
669 # function call with children
670 if(m[-1] == '{'):
671 match = re.match('^(?P<n>.*) *\(.*', m)
672 if(match):
673 self.name = match.group('n')
674 # function call with no children (leaf)
675 elif(m[-1] == ';'):
676 self.freturn = True
677 match = re.match('^(?P<n>.*) *\(.*', m)
678 if(match):
679 self.name = match.group('n')
680 # something else (possibly a trace marker)
681 else:
682 self.name = m
683 def getDepth(self, str):
684 return len(str)/2
685 def debugPrint(self, dev):
686 if(self.freturn and self.fcall):
687 print('%s -- %f (%02d): %s(); (%.3f us)' % (dev, self.time, \
688 self.depth, self.name, self.length*1000000))
689 elif(self.freturn):
690 print('%s -- %f (%02d): %s} (%.3f us)' % (dev, self.time, \
691 self.depth, self.name, self.length*1000000))
692 else:
693 print('%s -- %f (%02d): %s() { (%.3f us)' % (dev, self.time, \
694 self.depth, self.name, self.length*1000000))
696 # Class: FTraceCallGraph
697 # Description:
698 # A container for the ftrace callgraph of a single recursive function.
699 # This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
700 # Each instance is tied to a single device in a single phase, and is
701 # comprised of an ordered list of FTraceLine objects
702 class FTraceCallGraph:
703 start = -1.0
704 end = -1.0
705 list = []
706 invalid = False
707 depth = 0
708 def __init__(self):
709 self.start = -1.0
710 self.end = -1.0
711 self.list = []
712 self.depth = 0
713 def setDepth(self, line):
714 if(line.fcall and not line.freturn):
715 line.depth = self.depth
716 self.depth += 1
717 elif(line.freturn and not line.fcall):
718 self.depth -= 1
719 line.depth = self.depth
720 else:
721 line.depth = self.depth
722 def addLine(self, line, match):
723 if(not self.invalid):
724 self.setDepth(line)
725 if(line.depth == 0 and line.freturn):
726 if(self.start < 0):
727 self.start = line.time
728 self.end = line.time
729 self.list.append(line)
730 return True
731 if(self.invalid):
732 return False
733 if(len(self.list) >= 1000000 or self.depth < 0):
734 if(len(self.list) > 0):
735 first = self.list[0]
736 self.list = []
737 self.list.append(first)
738 self.invalid = True
739 if(not match):
740 return False
741 id = 'task %s cpu %s' % (match.group('pid'), match.group('cpu'))
742 window = '(%f - %f)' % (self.start, line.time)
743 if(self.depth < 0):
744 print('Too much data for '+id+\
745 ' (buffer overflow), ignoring this callback')
746 else:
747 print('Too much data for '+id+\
748 ' '+window+', ignoring this callback')
749 return False
750 self.list.append(line)
751 if(self.start < 0):
752 self.start = line.time
753 return False
754 def slice(self, t0, tN):
755 minicg = FTraceCallGraph()
756 count = -1
757 firstdepth = 0
758 for l in self.list:
759 if(l.time < t0 or l.time > tN):
760 continue
761 if(count < 0):
762 if(not l.fcall or l.name == 'dev_driver_string'):
763 continue
764 firstdepth = l.depth
765 count = 0
766 l.depth -= firstdepth
767 minicg.addLine(l, 0)
768 if((count == 0 and l.freturn and l.fcall) or
769 (count > 0 and l.depth <= 0)):
770 break
771 count += 1
772 return minicg
773 def sanityCheck(self):
774 stack = dict()
775 cnt = 0
776 for l in self.list:
777 if(l.fcall and not l.freturn):
778 stack[l.depth] = l
779 cnt += 1
780 elif(l.freturn and not l.fcall):
781 if(l.depth not in stack):
782 return False
783 stack[l.depth].length = l.length
784 stack[l.depth] = 0
785 l.length = 0
786 cnt -= 1
787 if(cnt == 0):
788 return True
789 return False
790 def debugPrint(self, filename):
791 if(filename == 'stdout'):
792 print('[%f - %f]') % (self.start, self.end)
793 for l in self.list:
794 if(l.freturn and l.fcall):
795 print('%f (%02d): %s(); (%.3f us)' % (l.time, \
796 l.depth, l.name, l.length*1000000))
797 elif(l.freturn):
798 print('%f (%02d): %s} (%.3f us)' % (l.time, \
799 l.depth, l.name, l.length*1000000))
800 else:
801 print('%f (%02d): %s() { (%.3f us)' % (l.time, \
802 l.depth, l.name, l.length*1000000))
803 print(' ')
804 else:
805 fp = open(filename, 'w')
806 print(filename)
807 for l in self.list:
808 if(l.freturn and l.fcall):
809 fp.write('%f (%02d): %s(); (%.3f us)\n' % (l.time, \
810 l.depth, l.name, l.length*1000000))
811 elif(l.freturn):
812 fp.write('%f (%02d): %s} (%.3f us)\n' % (l.time, \
813 l.depth, l.name, l.length*1000000))
814 else:
815 fp.write('%f (%02d): %s() { (%.3f us)\n' % (l.time, \
816 l.depth, l.name, l.length*1000000))
817 fp.close()
819 # Class: Timeline
820 # Description:
821 # A container for a suspend/resume html timeline. In older versions
822 # of the script there were multiple timelines, but in the latest
823 # there is only one.
824 class Timeline:
825 html = {}
826 scaleH = 0.0 # height of the row as a percent of the timeline height
827 rowH = 0.0 # height of each row in percent of the timeline height
828 row_height_pixels = 30
829 maxrows = 0
830 height = 0
831 def __init__(self):
832 self.html = {
833 'timeline': '',
834 'legend': '',
835 'scale': ''
837 def setRows(self, rows):
838 self.maxrows = int(rows)
839 self.scaleH = 100.0/float(self.maxrows)
840 self.height = self.maxrows*self.row_height_pixels
841 r = float(self.maxrows - 1)
842 if(r < 1.0):
843 r = 1.0
844 self.rowH = (100.0 - self.scaleH)/r
846 # Class: TestRun
847 # Description:
848 # A container for a suspend/resume test run. This is necessary as
849 # there could be more than one, and they need to be separate.
850 class TestRun:
851 ftrace_line_fmt_fg = \
852 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
853 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
854 '[ +!]*(?P<dur>[0-9\.]*) .*\| (?P<msg>.*)'
855 ftrace_line_fmt_nop = \
856 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
857 '(?P<flags>.{4}) *(?P<time>[0-9\.]*): *'+\
858 '(?P<msg>.*)'
859 ftrace_line_fmt = ftrace_line_fmt_nop
860 cgformat = False
861 ftemp = dict()
862 ttemp = dict()
863 inthepipe = False
864 tracertype = ''
865 data = 0
866 def __init__(self, dataobj):
867 self.data = dataobj
868 self.ftemp = dict()
869 self.ttemp = dict()
870 def isReady(self):
871 if(tracertype == '' or not data):
872 return False
873 return True
874 def setTracerType(self, tracer):
875 self.tracertype = tracer
876 if(tracer == 'function_graph'):
877 self.cgformat = True
878 self.ftrace_line_fmt = self.ftrace_line_fmt_fg
879 elif(tracer == 'nop'):
880 self.ftrace_line_fmt = self.ftrace_line_fmt_nop
881 else:
882 doError('Invalid tracer format: [%s]' % tracer, False)
884 # ----------------- FUNCTIONS --------------------
886 # Function: vprint
887 # Description:
888 # verbose print (prints only with -verbose option)
889 # Arguments:
890 # msg: the debug/log message to print
891 def vprint(msg):
892 global sysvals
893 if(sysvals.verbose):
894 print(msg)
896 # Function: initFtrace
897 # Description:
898 # Configure ftrace to use trace events and/or a callgraph
899 def initFtrace():
900 global sysvals
902 tp = sysvals.tpath
903 cf = 'dpm_run_callback'
904 if(sysvals.usetraceeventsonly):
905 cf = '-e dpm_prepare -e dpm_complete -e dpm_run_callback'
906 if(sysvals.usecallgraph or sysvals.usetraceevents):
907 print('INITIALIZING FTRACE...')
908 # turn trace off
909 os.system('echo 0 > '+tp+'tracing_on')
910 # set the trace clock to global
911 os.system('echo global > '+tp+'trace_clock')
912 # set trace buffer to a huge value
913 os.system('echo nop > '+tp+'current_tracer')
914 os.system('echo 100000 > '+tp+'buffer_size_kb')
915 # initialize the callgraph trace, unless this is an x2 run
916 if(sysvals.usecallgraph and sysvals.execcount == 1):
917 # set trace type
918 os.system('echo function_graph > '+tp+'current_tracer')
919 os.system('echo "" > '+tp+'set_ftrace_filter')
920 # set trace format options
921 os.system('echo funcgraph-abstime > '+tp+'trace_options')
922 os.system('echo funcgraph-proc > '+tp+'trace_options')
923 # focus only on device suspend and resume
924 os.system('cat '+tp+'available_filter_functions | grep '+\
925 cf+' > '+tp+'set_graph_function')
926 if(sysvals.usetraceevents):
927 # turn trace events on
928 events = iter(sysvals.traceevents)
929 for e in events:
930 os.system('echo 1 > '+sysvals.epath+e+'/enable')
931 # clear the trace buffer
932 os.system('echo "" > '+tp+'trace')
934 # Function: initFtraceAndroid
935 # Description:
936 # Configure ftrace to capture trace events
937 def initFtraceAndroid():
938 global sysvals
940 tp = sysvals.tpath
941 if(sysvals.usetraceevents):
942 print('INITIALIZING FTRACE...')
943 # turn trace off
944 os.system(sysvals.adb+" shell 'echo 0 > "+tp+"tracing_on'")
945 # set the trace clock to global
946 os.system(sysvals.adb+" shell 'echo global > "+tp+"trace_clock'")
947 # set trace buffer to a huge value
948 os.system(sysvals.adb+" shell 'echo nop > "+tp+"current_tracer'")
949 os.system(sysvals.adb+" shell 'echo 10000 > "+tp+"buffer_size_kb'")
950 # turn trace events on
951 events = iter(sysvals.traceevents)
952 for e in events:
953 os.system(sysvals.adb+" shell 'echo 1 > "+\
954 sysvals.epath+e+"/enable'")
955 # clear the trace buffer
956 os.system(sysvals.adb+" shell 'echo \"\" > "+tp+"trace'")
958 # Function: verifyFtrace
959 # Description:
960 # Check that ftrace is working on the system
961 # Output:
962 # True or False
963 def verifyFtrace():
964 global sysvals
965 # files needed for any trace data
966 files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
967 'trace_marker', 'trace_options', 'tracing_on']
968 # files needed for callgraph trace data
969 tp = sysvals.tpath
970 if(sysvals.usecallgraph):
971 files += [
972 'available_filter_functions',
973 'set_ftrace_filter',
974 'set_graph_function'
976 for f in files:
977 if(sysvals.android):
978 out = os.popen(sysvals.adb+' shell ls '+tp+f).read().strip()
979 if(out != tp+f):
980 return False
981 else:
982 if(os.path.exists(tp+f) == False):
983 return False
984 return True
986 # Function: parseStamp
987 # Description:
988 # Pull in the stamp comment line from the data file(s),
989 # create the stamp, and add it to the global sysvals object
990 # Arguments:
991 # m: the valid re.match output for the stamp line
992 def parseStamp(m, data):
993 global sysvals
994 data.stamp = {'time': '', 'host': '', 'mode': ''}
995 dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
996 int(m.group('d')), int(m.group('H')), int(m.group('M')),
997 int(m.group('S')))
998 data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
999 data.stamp['host'] = m.group('host')
1000 data.stamp['mode'] = m.group('mode')
1001 data.stamp['kernel'] = m.group('kernel')
1002 sysvals.suspendmode = data.stamp['mode']
1003 if not sysvals.stamp:
1004 sysvals.stamp = data.stamp
1006 # Function: diffStamp
1007 # Description:
1008 # compare the host, kernel, and mode fields in 3 stamps
1009 # Arguments:
1010 # stamp1: string array with mode, kernel, and host
1011 # stamp2: string array with mode, kernel, and host
1012 # Return:
1013 # True if stamps differ, False if they're the same
1014 def diffStamp(stamp1, stamp2):
1015 if 'host' in stamp1 and 'host' in stamp2:
1016 if stamp1['host'] != stamp2['host']:
1017 return True
1018 if 'kernel' in stamp1 and 'kernel' in stamp2:
1019 if stamp1['kernel'] != stamp2['kernel']:
1020 return True
1021 if 'mode' in stamp1 and 'mode' in stamp2:
1022 if stamp1['mode'] != stamp2['mode']:
1023 return True
1024 return False
1026 # Function: doesTraceLogHaveTraceEvents
1027 # Description:
1028 # Quickly determine if the ftrace log has some or all of the trace events
1029 # required for primary parsing. Set the usetraceevents and/or
1030 # usetraceeventsonly flags in the global sysvals object
1031 def doesTraceLogHaveTraceEvents():
1032 global sysvals
1034 sysvals.usetraceeventsonly = True
1035 sysvals.usetraceevents = False
1036 for e in sysvals.traceevents:
1037 out = os.popen('cat '+sysvals.ftracefile+' | grep "'+e+': "').read()
1038 if(not out):
1039 sysvals.usetraceeventsonly = False
1040 if(e == 'suspend_resume' and out):
1041 sysvals.usetraceevents = True
1043 # Function: appendIncompleteTraceLog
1044 # Description:
1045 # [deprecated for kernel 3.15 or newer]
1046 # Legacy support of ftrace outputs that lack the device_pm_callback
1047 # and/or suspend_resume trace events. The primary data should be
1048 # taken from dmesg, and this ftrace is used only for callgraph data
1049 # or custom actions in the timeline. The data is appended to the Data
1050 # objects provided.
1051 # Arguments:
1052 # testruns: the array of Data objects obtained from parseKernelLog
1053 def appendIncompleteTraceLog(testruns):
1054 global sysvals
1056 # create TestRun vessels for ftrace parsing
1057 testcnt = len(testruns)
1058 testidx = -1
1059 testrun = []
1060 for data in testruns:
1061 testrun.append(TestRun(data))
1063 # extract the callgraph and traceevent data
1064 vprint('Analyzing the ftrace data...')
1065 tf = open(sysvals.ftracefile, 'r')
1066 for line in tf:
1067 # remove any latent carriage returns
1068 line = line.replace('\r\n', '')
1069 # grab the time stamp first (signifies the start of the test run)
1070 m = re.match(sysvals.stampfmt, line)
1071 if(m):
1072 testidx += 1
1073 parseStamp(m, testrun[testidx].data)
1074 continue
1075 # pull out any firmware data
1076 if(re.match(sysvals.firmwarefmt, line)):
1077 continue
1078 # if we havent found a test time stamp yet keep spinning til we do
1079 if(testidx < 0):
1080 continue
1081 # determine the trace data type (required for further parsing)
1082 m = re.match(sysvals.tracertypefmt, line)
1083 if(m):
1084 tracer = m.group('t')
1085 testrun[testidx].setTracerType(tracer)
1086 continue
1087 # parse only valid lines, if this isnt one move on
1088 m = re.match(testrun[testidx].ftrace_line_fmt, line)
1089 if(not m):
1090 continue
1091 # gather the basic message data from the line
1092 m_time = m.group('time')
1093 m_pid = m.group('pid')
1094 m_msg = m.group('msg')
1095 if(testrun[testidx].cgformat):
1096 m_param3 = m.group('dur')
1097 else:
1098 m_param3 = 'traceevent'
1099 if(m_time and m_pid and m_msg):
1100 t = FTraceLine(m_time, m_msg, m_param3)
1101 pid = int(m_pid)
1102 else:
1103 continue
1104 # the line should be a call, return, or event
1105 if(not t.fcall and not t.freturn and not t.fevent):
1106 continue
1107 # only parse the ftrace data during suspend/resume
1108 data = testrun[testidx].data
1109 if(not testrun[testidx].inthepipe):
1110 # look for the suspend start marker
1111 if(t.fevent):
1112 if(t.name == 'SUSPEND START'):
1113 testrun[testidx].inthepipe = True
1114 data.setStart(t.time)
1115 continue
1116 else:
1117 # trace event processing
1118 if(t.fevent):
1119 if(t.name == 'RESUME COMPLETE'):
1120 testrun[testidx].inthepipe = False
1121 data.setEnd(t.time)
1122 if(testidx == testcnt - 1):
1123 break
1124 continue
1125 # general trace events have two types, begin and end
1126 if(re.match('(?P<name>.*) begin$', t.name)):
1127 isbegin = True
1128 elif(re.match('(?P<name>.*) end$', t.name)):
1129 isbegin = False
1130 else:
1131 continue
1132 m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
1133 if(m):
1134 val = m.group('val')
1135 if val == '0':
1136 name = m.group('name')
1137 else:
1138 name = m.group('name')+'['+val+']'
1139 else:
1140 m = re.match('(?P<name>.*) .*', t.name)
1141 name = m.group('name')
1142 # special processing for trace events
1143 if re.match('dpm_prepare\[.*', name):
1144 continue
1145 elif re.match('machine_suspend.*', name):
1146 continue
1147 elif re.match('suspend_enter\[.*', name):
1148 if(not isbegin):
1149 data.dmesg['suspend_prepare']['end'] = t.time
1150 continue
1151 elif re.match('dpm_suspend\[.*', name):
1152 if(not isbegin):
1153 data.dmesg['suspend']['end'] = t.time
1154 continue
1155 elif re.match('dpm_suspend_late\[.*', name):
1156 if(isbegin):
1157 data.dmesg['suspend_late']['start'] = t.time
1158 else:
1159 data.dmesg['suspend_late']['end'] = t.time
1160 continue
1161 elif re.match('dpm_suspend_noirq\[.*', name):
1162 if(isbegin):
1163 data.dmesg['suspend_noirq']['start'] = t.time
1164 else:
1165 data.dmesg['suspend_noirq']['end'] = t.time
1166 continue
1167 elif re.match('dpm_resume_noirq\[.*', name):
1168 if(isbegin):
1169 data.dmesg['resume_machine']['end'] = t.time
1170 data.dmesg['resume_noirq']['start'] = t.time
1171 else:
1172 data.dmesg['resume_noirq']['end'] = t.time
1173 continue
1174 elif re.match('dpm_resume_early\[.*', name):
1175 if(isbegin):
1176 data.dmesg['resume_early']['start'] = t.time
1177 else:
1178 data.dmesg['resume_early']['end'] = t.time
1179 continue
1180 elif re.match('dpm_resume\[.*', name):
1181 if(isbegin):
1182 data.dmesg['resume']['start'] = t.time
1183 else:
1184 data.dmesg['resume']['end'] = t.time
1185 continue
1186 elif re.match('dpm_complete\[.*', name):
1187 if(isbegin):
1188 data.dmesg['resume_complete']['start'] = t.time
1189 else:
1190 data.dmesg['resume_complete']['end'] = t.time
1191 continue
1192 # is this trace event outside of the devices calls
1193 if(data.isTraceEventOutsideDeviceCalls(pid, t.time)):
1194 # global events (outside device calls) are simply graphed
1195 if(isbegin):
1196 # store each trace event in ttemp
1197 if(name not in testrun[testidx].ttemp):
1198 testrun[testidx].ttemp[name] = []
1199 testrun[testidx].ttemp[name].append(\
1200 {'begin': t.time, 'end': t.time})
1201 else:
1202 # finish off matching trace event in ttemp
1203 if(name in testrun[testidx].ttemp):
1204 testrun[testidx].ttemp[name][-1]['end'] = t.time
1205 else:
1206 if(isbegin):
1207 data.addIntraDevTraceEvent('', name, pid, t.time)
1208 else:
1209 data.capIntraDevTraceEvent('', name, pid, t.time)
1210 # call/return processing
1211 elif sysvals.usecallgraph:
1212 # create a callgraph object for the data
1213 if(pid not in testrun[testidx].ftemp):
1214 testrun[testidx].ftemp[pid] = []
1215 testrun[testidx].ftemp[pid].append(FTraceCallGraph())
1216 # when the call is finished, see which device matches it
1217 cg = testrun[testidx].ftemp[pid][-1]
1218 if(cg.addLine(t, m)):
1219 testrun[testidx].ftemp[pid].append(FTraceCallGraph())
1220 tf.close()
1222 for test in testrun:
1223 # add the traceevent data to the device hierarchy
1224 if(sysvals.usetraceevents):
1225 for name in test.ttemp:
1226 for event in test.ttemp[name]:
1227 begin = event['begin']
1228 end = event['end']
1229 # if event starts before timeline start, expand timeline
1230 if(begin < test.data.start):
1231 test.data.setStart(begin)
1232 # if event ends after timeline end, expand the timeline
1233 if(end > test.data.end):
1234 test.data.setEnd(end)
1235 test.data.newActionGlobal(name, begin, end)
1237 # add the callgraph data to the device hierarchy
1238 for pid in test.ftemp:
1239 for cg in test.ftemp[pid]:
1240 if(not cg.sanityCheck()):
1241 id = 'task %s cpu %s' % (pid, m.group('cpu'))
1242 vprint('Sanity check failed for '+\
1243 id+', ignoring this callback')
1244 continue
1245 callstart = cg.start
1246 callend = cg.end
1247 for p in test.data.phases:
1248 if(test.data.dmesg[p]['start'] <= callstart and
1249 callstart <= test.data.dmesg[p]['end']):
1250 list = test.data.dmesg[p]['list']
1251 for devname in list:
1252 dev = list[devname]
1253 if(pid == dev['pid'] and
1254 callstart <= dev['start'] and
1255 callend >= dev['end']):
1256 dev['ftrace'] = cg
1257 break
1259 if(sysvals.verbose):
1260 test.data.printDetails()
1263 # add the time in between the tests as a new phase so we can see it
1264 if(len(testruns) > 1):
1265 t1e = testruns[0].getEnd()
1266 t2s = testruns[-1].getStart()
1267 testruns[-1].newPhaseWithSingleAction('user mode', \
1268 'user mode', t1e, t2s, '#FF9966')
1270 # Function: parseTraceLog
1271 # Description:
1272 # Analyze an ftrace log output file generated from this app during
1273 # the execution phase. Used when the ftrace log is the primary data source
1274 # and includes the suspend_resume and device_pm_callback trace events
1275 # The ftrace filename is taken from sysvals
1276 # Output:
1277 # An array of Data objects
1278 def parseTraceLog():
1279 global sysvals
1281 vprint('Analyzing the ftrace data...')
1282 if(os.path.exists(sysvals.ftracefile) == False):
1283 doError('%s doesnt exist' % sysvals.ftracefile, False)
1285 # extract the callgraph and traceevent data
1286 testruns = []
1287 testdata = []
1288 testrun = 0
1289 data = 0
1290 tf = open(sysvals.ftracefile, 'r')
1291 phase = 'suspend_prepare'
1292 for line in tf:
1293 # remove any latent carriage returns
1294 line = line.replace('\r\n', '')
1295 # stamp line: each stamp means a new test run
1296 m = re.match(sysvals.stampfmt, line)
1297 if(m):
1298 data = Data(len(testdata))
1299 testdata.append(data)
1300 testrun = TestRun(data)
1301 testruns.append(testrun)
1302 parseStamp(m, data)
1303 continue
1304 if(not data):
1305 continue
1306 # firmware line: pull out any firmware data
1307 m = re.match(sysvals.firmwarefmt, line)
1308 if(m):
1309 data.fwSuspend = int(m.group('s'))
1310 data.fwResume = int(m.group('r'))
1311 if(data.fwSuspend > 0 or data.fwResume > 0):
1312 data.fwValid = True
1313 continue
1314 # tracer type line: determine the trace data type
1315 m = re.match(sysvals.tracertypefmt, line)
1316 if(m):
1317 tracer = m.group('t')
1318 testrun.setTracerType(tracer)
1319 continue
1320 # post resume time line: did this test run include post-resume data
1321 m = re.match(sysvals.postresumefmt, line)
1322 if(m):
1323 t = int(m.group('t'))
1324 if(t > 0):
1325 sysvals.postresumetime = t
1326 continue
1327 # ftrace line: parse only valid lines
1328 m = re.match(testrun.ftrace_line_fmt, line)
1329 if(not m):
1330 continue
1331 # gather the basic message data from the line
1332 m_time = m.group('time')
1333 m_pid = m.group('pid')
1334 m_msg = m.group('msg')
1335 if(testrun.cgformat):
1336 m_param3 = m.group('dur')
1337 else:
1338 m_param3 = 'traceevent'
1339 if(m_time and m_pid and m_msg):
1340 t = FTraceLine(m_time, m_msg, m_param3)
1341 pid = int(m_pid)
1342 else:
1343 continue
1344 # the line should be a call, return, or event
1345 if(not t.fcall and not t.freturn and not t.fevent):
1346 continue
1347 # only parse the ftrace data during suspend/resume
1348 if(not testrun.inthepipe):
1349 # look for the suspend start marker
1350 if(t.fevent):
1351 if(t.name == 'SUSPEND START'):
1352 testrun.inthepipe = True
1353 data.setStart(t.time)
1354 continue
1355 # trace event processing
1356 if(t.fevent):
1357 if(t.name == 'RESUME COMPLETE'):
1358 if(sysvals.postresumetime > 0):
1359 phase = 'post_resume'
1360 data.newPhase(phase, t.time, t.time, '#FF9966', -1)
1361 else:
1362 testrun.inthepipe = False
1363 data.setEnd(t.time)
1364 continue
1365 if(phase == 'post_resume'):
1366 data.setEnd(t.time)
1367 if(t.type == 'suspend_resume'):
1368 # suspend_resume trace events have two types, begin and end
1369 if(re.match('(?P<name>.*) begin$', t.name)):
1370 isbegin = True
1371 elif(re.match('(?P<name>.*) end$', t.name)):
1372 isbegin = False
1373 else:
1374 continue
1375 m = re.match('(?P<name>.*)\[(?P<val>[0-9]*)\] .*', t.name)
1376 if(m):
1377 val = m.group('val')
1378 if val == '0':
1379 name = m.group('name')
1380 else:
1381 name = m.group('name')+'['+val+']'
1382 else:
1383 m = re.match('(?P<name>.*) .*', t.name)
1384 name = m.group('name')
1385 # ignore these events
1386 if(re.match('acpi_suspend\[.*', t.name) or
1387 re.match('suspend_enter\[.*', name)):
1388 continue
1389 # -- phase changes --
1390 # suspend_prepare start
1391 if(re.match('dpm_prepare\[.*', t.name)):
1392 phase = 'suspend_prepare'
1393 if(not isbegin):
1394 data.dmesg[phase]['end'] = t.time
1395 continue
1396 # suspend start
1397 elif(re.match('dpm_suspend\[.*', t.name)):
1398 phase = 'suspend'
1399 data.setPhase(phase, t.time, isbegin)
1400 continue
1401 # suspend_late start
1402 elif(re.match('dpm_suspend_late\[.*', t.name)):
1403 phase = 'suspend_late'
1404 data.setPhase(phase, t.time, isbegin)
1405 continue
1406 # suspend_noirq start
1407 elif(re.match('dpm_suspend_noirq\[.*', t.name)):
1408 phase = 'suspend_noirq'
1409 data.setPhase(phase, t.time, isbegin)
1410 if(not isbegin):
1411 phase = 'suspend_machine'
1412 data.dmesg[phase]['start'] = t.time
1413 continue
1414 # suspend_machine/resume_machine
1415 elif(re.match('machine_suspend\[.*', t.name)):
1416 if(isbegin):
1417 phase = 'suspend_machine'
1418 data.dmesg[phase]['end'] = t.time
1419 data.tSuspended = t.time
1420 else:
1421 if(sysvals.suspendmode in ['mem', 'disk']):
1422 data.dmesg['suspend_machine']['end'] = t.time
1423 data.tSuspended = t.time
1424 phase = 'resume_machine'
1425 data.dmesg[phase]['start'] = t.time
1426 data.tResumed = t.time
1427 data.tLow = data.tResumed - data.tSuspended
1428 continue
1429 # resume_noirq start
1430 elif(re.match('dpm_resume_noirq\[.*', t.name)):
1431 phase = 'resume_noirq'
1432 data.setPhase(phase, t.time, isbegin)
1433 if(isbegin):
1434 data.dmesg['resume_machine']['end'] = t.time
1435 continue
1436 # resume_early start
1437 elif(re.match('dpm_resume_early\[.*', t.name)):
1438 phase = 'resume_early'
1439 data.setPhase(phase, t.time, isbegin)
1440 continue
1441 # resume start
1442 elif(re.match('dpm_resume\[.*', t.name)):
1443 phase = 'resume'
1444 data.setPhase(phase, t.time, isbegin)
1445 continue
1446 # resume complete start
1447 elif(re.match('dpm_complete\[.*', t.name)):
1448 phase = 'resume_complete'
1449 if(isbegin):
1450 data.dmesg[phase]['start'] = t.time
1451 continue
1453 # is this trace event outside of the devices calls
1454 if(data.isTraceEventOutsideDeviceCalls(pid, t.time)):
1455 # global events (outside device calls) are simply graphed
1456 if(name not in testrun.ttemp):
1457 testrun.ttemp[name] = []
1458 if(isbegin):
1459 # create a new list entry
1460 testrun.ttemp[name].append(\
1461 {'begin': t.time, 'end': t.time})
1462 else:
1463 if(len(testrun.ttemp[name]) > 0):
1464 # if an antry exists, assume this is its end
1465 testrun.ttemp[name][-1]['end'] = t.time
1466 elif(phase == 'post_resume'):
1467 # post resume events can just have ends
1468 testrun.ttemp[name].append({
1469 'begin': data.dmesg[phase]['start'],
1470 'end': t.time})
1471 else:
1472 if(isbegin):
1473 data.addIntraDevTraceEvent('', name, pid, t.time)
1474 else:
1475 data.capIntraDevTraceEvent('', name, pid, t.time)
1476 # device callback start
1477 elif(t.type == 'device_pm_callback_start'):
1478 m = re.match('(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
1479 t.name);
1480 if(not m):
1481 continue
1482 drv = m.group('drv')
1483 n = m.group('d')
1484 p = m.group('p')
1485 if(n and p):
1486 data.newAction(phase, n, pid, p, t.time, -1, drv)
1487 # device callback finish
1488 elif(t.type == 'device_pm_callback_end'):
1489 m = re.match('(?P<drv>.*) (?P<d>.*), err.*', t.name);
1490 if(not m):
1491 continue
1492 n = m.group('d')
1493 list = data.dmesg[phase]['list']
1494 if(n in list):
1495 dev = list[n]
1496 dev['length'] = t.time - dev['start']
1497 dev['end'] = t.time
1498 # callgraph processing
1499 elif sysvals.usecallgraph:
1500 # this shouldn't happen, but JIC, ignore callgraph data post-res
1501 if(phase == 'post_resume'):
1502 continue
1503 # create a callgraph object for the data
1504 if(pid not in testrun.ftemp):
1505 testrun.ftemp[pid] = []
1506 testrun.ftemp[pid].append(FTraceCallGraph())
1507 # when the call is finished, see which device matches it
1508 cg = testrun.ftemp[pid][-1]
1509 if(cg.addLine(t, m)):
1510 testrun.ftemp[pid].append(FTraceCallGraph())
1511 tf.close()
1513 for test in testruns:
1514 # add the traceevent data to the device hierarchy
1515 if(sysvals.usetraceevents):
1516 for name in test.ttemp:
1517 for event in test.ttemp[name]:
1518 begin = event['begin']
1519 end = event['end']
1520 # if event starts before timeline start, expand timeline
1521 if(begin < test.data.start):
1522 test.data.setStart(begin)
1523 # if event ends after timeline end, expand the timeline
1524 if(end > test.data.end):
1525 test.data.setEnd(end)
1526 test.data.newActionGlobal(name, begin, end)
1528 # add the callgraph data to the device hierarchy
1529 borderphase = {
1530 'dpm_prepare': 'suspend_prepare',
1531 'dpm_complete': 'resume_complete'
1533 for pid in test.ftemp:
1534 for cg in test.ftemp[pid]:
1535 if len(cg.list) < 2:
1536 continue
1537 if(not cg.sanityCheck()):
1538 id = 'task %s cpu %s' % (pid, m.group('cpu'))
1539 vprint('Sanity check failed for '+\
1540 id+', ignoring this callback')
1541 continue
1542 callstart = cg.start
1543 callend = cg.end
1544 if(cg.list[0].name in borderphase):
1545 p = borderphase[cg.list[0].name]
1546 list = test.data.dmesg[p]['list']
1547 for devname in list:
1548 dev = list[devname]
1549 if(pid == dev['pid'] and
1550 callstart <= dev['start'] and
1551 callend >= dev['end']):
1552 dev['ftrace'] = cg.slice(dev['start'], dev['end'])
1553 continue
1554 if(cg.list[0].name != 'dpm_run_callback'):
1555 continue
1556 for p in test.data.phases:
1557 if(test.data.dmesg[p]['start'] <= callstart and
1558 callstart <= test.data.dmesg[p]['end']):
1559 list = test.data.dmesg[p]['list']
1560 for devname in list:
1561 dev = list[devname]
1562 if(pid == dev['pid'] and
1563 callstart <= dev['start'] and
1564 callend >= dev['end']):
1565 dev['ftrace'] = cg
1566 break
1568 # fill in any missing phases
1569 for data in testdata:
1570 lp = data.phases[0]
1571 for p in data.phases:
1572 if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
1573 print('WARNING: phase "%s" is missing!' % p)
1574 if(data.dmesg[p]['start'] < 0):
1575 data.dmesg[p]['start'] = data.dmesg[lp]['end']
1576 if(p == 'resume_machine'):
1577 data.tSuspended = data.dmesg[lp]['end']
1578 data.tResumed = data.dmesg[lp]['end']
1579 data.tLow = 0
1580 if(data.dmesg[p]['end'] < 0):
1581 data.dmesg[p]['end'] = data.dmesg[p]['start']
1582 lp = p
1584 if(len(sysvals.devicefilter) > 0):
1585 data.deviceFilter(sysvals.devicefilter)
1586 data.fixupInitcallsThatDidntReturn()
1587 if(sysvals.verbose):
1588 data.printDetails()
1590 # add the time in between the tests as a new phase so we can see it
1591 if(len(testdata) > 1):
1592 t1e = testdata[0].getEnd()
1593 t2s = testdata[-1].getStart()
1594 testdata[-1].newPhaseWithSingleAction('user mode', \
1595 'user mode', t1e, t2s, '#FF9966')
1596 return testdata
1598 # Function: loadKernelLog
1599 # Description:
1600 # [deprecated for kernel 3.15.0 or newer]
1601 # load the dmesg file into memory and fix up any ordering issues
1602 # The dmesg filename is taken from sysvals
1603 # Output:
1604 # An array of empty Data objects with only their dmesgtext attributes set
1605 def loadKernelLog():
1606 global sysvals
1608 vprint('Analyzing the dmesg data...')
1609 if(os.path.exists(sysvals.dmesgfile) == False):
1610 doError('%s doesnt exist' % sysvals.dmesgfile, False)
1612 # there can be multiple test runs in a single file delineated by stamps
1613 testruns = []
1614 data = 0
1615 lf = open(sysvals.dmesgfile, 'r')
1616 for line in lf:
1617 line = line.replace('\r\n', '')
1618 idx = line.find('[')
1619 if idx > 1:
1620 line = line[idx:]
1621 m = re.match(sysvals.stampfmt, line)
1622 if(m):
1623 if(data):
1624 testruns.append(data)
1625 data = Data(len(testruns))
1626 parseStamp(m, data)
1627 continue
1628 if(not data):
1629 continue
1630 m = re.match(sysvals.firmwarefmt, line)
1631 if(m):
1632 data.fwSuspend = int(m.group('s'))
1633 data.fwResume = int(m.group('r'))
1634 if(data.fwSuspend > 0 or data.fwResume > 0):
1635 data.fwValid = True
1636 continue
1637 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1638 if(m):
1639 data.dmesgtext.append(line)
1640 if(re.match('ACPI: resume from mwait', m.group('msg'))):
1641 print('NOTE: This suspend appears to be freeze rather than'+\
1642 ' %s, it will be treated as such' % sysvals.suspendmode)
1643 sysvals.suspendmode = 'freeze'
1644 else:
1645 vprint('ignoring dmesg line: %s' % line.replace('\n', ''))
1646 testruns.append(data)
1647 lf.close()
1649 if(not data):
1650 print('ERROR: analyze_suspend header missing from dmesg log')
1651 sys.exit()
1653 # fix lines with same timestamp/function with the call and return swapped
1654 for data in testruns:
1655 last = ''
1656 for line in data.dmesgtext:
1657 mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling '+\
1658 '(?P<f>.*)\+ @ .*, parent: .*', line)
1659 mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
1660 '(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
1661 if(mc and mr and (mc.group('t') == mr.group('t')) and
1662 (mc.group('f') == mr.group('f'))):
1663 i = data.dmesgtext.index(last)
1664 j = data.dmesgtext.index(line)
1665 data.dmesgtext[i] = line
1666 data.dmesgtext[j] = last
1667 last = line
1668 return testruns
1670 # Function: parseKernelLog
1671 # Description:
1672 # [deprecated for kernel 3.15.0 or newer]
1673 # Analyse a dmesg log output file generated from this app during
1674 # the execution phase. Create a set of device structures in memory
1675 # for subsequent formatting in the html output file
1676 # This call is only for legacy support on kernels where the ftrace
1677 # data lacks the suspend_resume or device_pm_callbacks trace events.
1678 # Arguments:
1679 # data: an empty Data object (with dmesgtext) obtained from loadKernelLog
1680 # Output:
1681 # The filled Data object
1682 def parseKernelLog(data):
1683 global sysvals
1685 phase = 'suspend_runtime'
1687 if(data.fwValid):
1688 vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
1689 (data.fwSuspend, data.fwResume))
1691 # dmesg phase match table
1692 dm = {
1693 'suspend_prepare': 'PM: Syncing filesystems.*',
1694 'suspend': 'PM: Entering [a-z]* sleep.*',
1695 'suspend_late': 'PM: suspend of devices complete after.*',
1696 'suspend_noirq': 'PM: late suspend of devices complete after.*',
1697 'suspend_machine': 'PM: noirq suspend of devices complete after.*',
1698 'resume_machine': 'ACPI: Low-level resume complete.*',
1699 'resume_noirq': 'ACPI: Waking up from system sleep state.*',
1700 'resume_early': 'PM: noirq resume of devices complete after.*',
1701 'resume': 'PM: early resume of devices complete after.*',
1702 'resume_complete': 'PM: resume of devices complete after.*',
1703 'post_resume': '.*Restarting tasks \.\.\..*',
1705 if(sysvals.suspendmode == 'standby'):
1706 dm['resume_machine'] = 'PM: Restoring platform NVS memory'
1707 elif(sysvals.suspendmode == 'disk'):
1708 dm['suspend_late'] = 'PM: freeze of devices complete after.*'
1709 dm['suspend_noirq'] = 'PM: late freeze of devices complete after.*'
1710 dm['suspend_machine'] = 'PM: noirq freeze of devices complete after.*'
1711 dm['resume_machine'] = 'PM: Restoring platform NVS memory'
1712 dm['resume_early'] = 'PM: noirq restore of devices complete after.*'
1713 dm['resume'] = 'PM: early restore of devices complete after.*'
1714 dm['resume_complete'] = 'PM: restore of devices complete after.*'
1715 elif(sysvals.suspendmode == 'freeze'):
1716 dm['resume_machine'] = 'ACPI: resume from mwait'
1718 # action table (expected events that occur and show up in dmesg)
1719 at = {
1720 'sync_filesystems': {
1721 'smsg': 'PM: Syncing filesystems.*',
1722 'emsg': 'PM: Preparing system for mem sleep.*' },
1723 'freeze_user_processes': {
1724 'smsg': 'Freezing user space processes .*',
1725 'emsg': 'Freezing remaining freezable tasks.*' },
1726 'freeze_tasks': {
1727 'smsg': 'Freezing remaining freezable tasks.*',
1728 'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' },
1729 'ACPI prepare': {
1730 'smsg': 'ACPI: Preparing to enter system sleep state.*',
1731 'emsg': 'PM: Saving platform NVS memory.*' },
1732 'PM vns': {
1733 'smsg': 'PM: Saving platform NVS memory.*',
1734 'emsg': 'Disabling non-boot CPUs .*' },
1737 t0 = -1.0
1738 cpu_start = -1.0
1739 prevktime = -1.0
1740 actions = dict()
1741 for line in data.dmesgtext:
1742 # -- preprocessing --
1743 # parse each dmesg line into the time and message
1744 m = re.match('[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1745 if(m):
1746 val = m.group('ktime')
1747 try:
1748 ktime = float(val)
1749 except:
1750 doWarning('INVALID DMESG LINE: '+\
1751 line.replace('\n', ''), 'dmesg')
1752 continue
1753 msg = m.group('msg')
1754 # initialize data start to first line time
1755 if t0 < 0:
1756 data.setStart(ktime)
1757 t0 = ktime
1758 else:
1759 continue
1761 # hack for determining resume_machine end for freeze
1762 if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
1763 and phase == 'resume_machine' and \
1764 re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
1765 data.dmesg['resume_machine']['end'] = ktime
1766 phase = 'resume_noirq'
1767 data.dmesg[phase]['start'] = ktime
1769 # -- phase changes --
1770 # suspend start
1771 if(re.match(dm['suspend_prepare'], msg)):
1772 phase = 'suspend_prepare'
1773 data.dmesg[phase]['start'] = ktime
1774 data.setStart(ktime)
1775 # suspend start
1776 elif(re.match(dm['suspend'], msg)):
1777 data.dmesg['suspend_prepare']['end'] = ktime
1778 phase = 'suspend'
1779 data.dmesg[phase]['start'] = ktime
1780 # suspend_late start
1781 elif(re.match(dm['suspend_late'], msg)):
1782 data.dmesg['suspend']['end'] = ktime
1783 phase = 'suspend_late'
1784 data.dmesg[phase]['start'] = ktime
1785 # suspend_noirq start
1786 elif(re.match(dm['suspend_noirq'], msg)):
1787 data.dmesg['suspend_late']['end'] = ktime
1788 phase = 'suspend_noirq'
1789 data.dmesg[phase]['start'] = ktime
1790 # suspend_machine start
1791 elif(re.match(dm['suspend_machine'], msg)):
1792 data.dmesg['suspend_noirq']['end'] = ktime
1793 phase = 'suspend_machine'
1794 data.dmesg[phase]['start'] = ktime
1795 # resume_machine start
1796 elif(re.match(dm['resume_machine'], msg)):
1797 if(sysvals.suspendmode in ['freeze', 'standby']):
1798 data.tSuspended = prevktime
1799 data.dmesg['suspend_machine']['end'] = prevktime
1800 else:
1801 data.tSuspended = ktime
1802 data.dmesg['suspend_machine']['end'] = ktime
1803 phase = 'resume_machine'
1804 data.tResumed = ktime
1805 data.tLow = data.tResumed - data.tSuspended
1806 data.dmesg[phase]['start'] = ktime
1807 # resume_noirq start
1808 elif(re.match(dm['resume_noirq'], msg)):
1809 data.dmesg['resume_machine']['end'] = ktime
1810 phase = 'resume_noirq'
1811 data.dmesg[phase]['start'] = ktime
1812 # resume_early start
1813 elif(re.match(dm['resume_early'], msg)):
1814 data.dmesg['resume_noirq']['end'] = ktime
1815 phase = 'resume_early'
1816 data.dmesg[phase]['start'] = ktime
1817 # resume start
1818 elif(re.match(dm['resume'], msg)):
1819 data.dmesg['resume_early']['end'] = ktime
1820 phase = 'resume'
1821 data.dmesg[phase]['start'] = ktime
1822 # resume complete start
1823 elif(re.match(dm['resume_complete'], msg)):
1824 data.dmesg['resume']['end'] = ktime
1825 phase = 'resume_complete'
1826 data.dmesg[phase]['start'] = ktime
1827 # post resume start
1828 elif(re.match(dm['post_resume'], msg)):
1829 data.dmesg['resume_complete']['end'] = ktime
1830 data.setEnd(ktime)
1831 phase = 'post_resume'
1832 break
1834 # -- device callbacks --
1835 if(phase in data.phases):
1836 # device init call
1837 if(re.match('calling (?P<f>.*)\+ @ .*, parent: .*', msg)):
1838 sm = re.match('calling (?P<f>.*)\+ @ '+\
1839 '(?P<n>.*), parent: (?P<p>.*)', msg);
1840 f = sm.group('f')
1841 n = sm.group('n')
1842 p = sm.group('p')
1843 if(f and n and p):
1844 data.newAction(phase, f, int(n), p, ktime, -1, '')
1845 # device init return
1846 elif(re.match('call (?P<f>.*)\+ returned .* after '+\
1847 '(?P<t>.*) usecs', msg)):
1848 sm = re.match('call (?P<f>.*)\+ returned .* after '+\
1849 '(?P<t>.*) usecs(?P<a>.*)', msg);
1850 f = sm.group('f')
1851 t = sm.group('t')
1852 list = data.dmesg[phase]['list']
1853 if(f in list):
1854 dev = list[f]
1855 dev['length'] = int(t)
1856 dev['end'] = ktime
1858 # -- non-devicecallback actions --
1859 # if trace events are not available, these are better than nothing
1860 if(not sysvals.usetraceevents):
1861 # look for known actions
1862 for a in at:
1863 if(re.match(at[a]['smsg'], msg)):
1864 if(a not in actions):
1865 actions[a] = []
1866 actions[a].append({'begin': ktime, 'end': ktime})
1867 if(re.match(at[a]['emsg'], msg)):
1868 actions[a][-1]['end'] = ktime
1869 # now look for CPU on/off events
1870 if(re.match('Disabling non-boot CPUs .*', msg)):
1871 # start of first cpu suspend
1872 cpu_start = ktime
1873 elif(re.match('Enabling non-boot CPUs .*', msg)):
1874 # start of first cpu resume
1875 cpu_start = ktime
1876 elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)):
1877 # end of a cpu suspend, start of the next
1878 m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
1879 cpu = 'CPU'+m.group('cpu')
1880 if(cpu not in actions):
1881 actions[cpu] = []
1882 actions[cpu].append({'begin': cpu_start, 'end': ktime})
1883 cpu_start = ktime
1884 elif(re.match('CPU(?P<cpu>[0-9]*) is up', msg)):
1885 # end of a cpu resume, start of the next
1886 m = re.match('CPU(?P<cpu>[0-9]*) is up', msg)
1887 cpu = 'CPU'+m.group('cpu')
1888 if(cpu not in actions):
1889 actions[cpu] = []
1890 actions[cpu].append({'begin': cpu_start, 'end': ktime})
1891 cpu_start = ktime
1892 prevktime = ktime
1894 # fill in any missing phases
1895 lp = data.phases[0]
1896 for p in data.phases:
1897 if(data.dmesg[p]['start'] < 0 and data.dmesg[p]['end'] < 0):
1898 print('WARNING: phase "%s" is missing, something went wrong!' % p)
1899 print(' In %s, this dmesg line denotes the start of %s:' % \
1900 (sysvals.suspendmode, p))
1901 print(' "%s"' % dm[p])
1902 if(data.dmesg[p]['start'] < 0):
1903 data.dmesg[p]['start'] = data.dmesg[lp]['end']
1904 if(p == 'resume_machine'):
1905 data.tSuspended = data.dmesg[lp]['end']
1906 data.tResumed = data.dmesg[lp]['end']
1907 data.tLow = 0
1908 if(data.dmesg[p]['end'] < 0):
1909 data.dmesg[p]['end'] = data.dmesg[p]['start']
1910 lp = p
1912 # fill in any actions we've found
1913 for name in actions:
1914 for event in actions[name]:
1915 begin = event['begin']
1916 end = event['end']
1917 # if event starts before timeline start, expand timeline
1918 if(begin < data.start):
1919 data.setStart(begin)
1920 # if event ends after timeline end, expand the timeline
1921 if(end > data.end):
1922 data.setEnd(end)
1923 data.newActionGlobal(name, begin, end)
1925 if(sysvals.verbose):
1926 data.printDetails()
1927 if(len(sysvals.devicefilter) > 0):
1928 data.deviceFilter(sysvals.devicefilter)
1929 data.fixupInitcallsThatDidntReturn()
1930 return True
1932 # Function: setTimelineRows
1933 # Description:
1934 # Organize the timeline entries into the smallest
1935 # number of rows possible, with no entry overlapping
1936 # Arguments:
1937 # list: the list of devices/actions for a single phase
1938 # sortedkeys: cronologically sorted key list to use
1939 # Output:
1940 # The total number of rows needed to display this phase of the timeline
1941 def setTimelineRows(list, sortedkeys):
1943 # clear all rows and set them to undefined
1944 remaining = len(list)
1945 rowdata = dict()
1946 row = 0
1947 for item in list:
1948 list[item]['row'] = -1
1950 # try to pack each row with as many ranges as possible
1951 while(remaining > 0):
1952 if(row not in rowdata):
1953 rowdata[row] = []
1954 for item in sortedkeys:
1955 if(list[item]['row'] < 0):
1956 s = list[item]['start']
1957 e = list[item]['end']
1958 valid = True
1959 for ritem in rowdata[row]:
1960 rs = ritem['start']
1961 re = ritem['end']
1962 if(not (((s <= rs) and (e <= rs)) or
1963 ((s >= re) and (e >= re)))):
1964 valid = False
1965 break
1966 if(valid):
1967 rowdata[row].append(list[item])
1968 list[item]['row'] = row
1969 remaining -= 1
1970 row += 1
1971 return row
1973 # Function: createTimeScale
1974 # Description:
1975 # Create the timescale header for the html timeline
1976 # Arguments:
1977 # t0: start time (suspend begin)
1978 # tMax: end time (resume end)
1979 # tSuspend: time when suspend occurs, i.e. the zero time
1980 # Output:
1981 # The html code needed to display the time scale
1982 def createTimeScale(t0, tMax, tSuspended):
1983 timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
1984 output = '<div id="timescale">\n'
1986 # set scale for timeline
1987 tTotal = tMax - t0
1988 tS = 0.1
1989 if(tTotal <= 0):
1990 return output
1991 if(tTotal > 4):
1992 tS = 1
1993 if(tSuspended < 0):
1994 for i in range(int(tTotal/tS)+1):
1995 pos = '%0.3f' % (100 - ((float(i)*tS*100)/tTotal))
1996 if(i > 0):
1997 val = '%0.fms' % (float(i)*tS*1000)
1998 else:
1999 val = ''
2000 output += timescale.format(pos, val)
2001 else:
2002 tSuspend = tSuspended - t0
2003 divTotal = int(tTotal/tS) + 1
2004 divSuspend = int(tSuspend/tS)
2005 s0 = (tSuspend - tS*divSuspend)*100/tTotal
2006 for i in range(divTotal):
2007 pos = '%0.3f' % (100 - ((float(i)*tS*100)/tTotal) - s0)
2008 if((i == 0) and (s0 < 3)):
2009 val = ''
2010 elif(i == divSuspend):
2011 val = 'S/R'
2012 else:
2013 val = '%0.fms' % (float(i-divSuspend)*tS*1000)
2014 output += timescale.format(pos, val)
2015 output += '</div>\n'
2016 return output
2018 # Function: createHTMLSummarySimple
2019 # Description:
2020 # Create summary html file for a series of tests
2021 # Arguments:
2022 # testruns: array of Data objects from parseTraceLog
2023 def createHTMLSummarySimple(testruns, htmlfile):
2024 global sysvals
2026 # print out the basic summary of all the tests
2027 hf = open(htmlfile, 'w')
2029 # write the html header first (html head, css code, up to body start)
2030 html = '<!DOCTYPE html>\n<html>\n<head>\n\
2031 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
2032 <title>AnalyzeSuspend Summary</title>\n\
2033 <style type=\'text/css\'>\n\
2034 body {overflow-y: scroll;}\n\
2035 .stamp {width: 100%;text-align:center;background-color:#495E09;line-height:30px;color:white;font: 25px Arial;}\n\
2036 table {width:100%;border-collapse: collapse;}\n\
2037 .summary {font: 22px Arial;border:1px solid;}\n\
2038 th {border: 1px solid black;background-color:#A7C942;color:white;}\n\
2039 td {text-align: center;}\n\
2040 tr.alt td {background-color:#EAF2D3;}\n\
2041 tr.avg td {background-color:#BDE34C;}\n\
2042 a:link {color: #90B521;}\n\
2043 a:visited {color: #495E09;}\n\
2044 a:hover {color: #B1DF28;}\n\
2045 a:active {color: #FFFFFF;}\n\
2046 </style>\n</head>\n<body>\n'
2048 # group test header
2049 count = len(testruns)
2050 headline_stamp = '<div class="stamp">{0} {1} {2} {3} ({4} tests)</div>\n'
2051 html += headline_stamp.format(sysvals.stamp['host'],
2052 sysvals.stamp['kernel'], sysvals.stamp['mode'],
2053 sysvals.stamp['time'], count)
2055 # check to see if all the tests have the same value
2056 stampcolumns = False
2057 for data in testruns:
2058 if diffStamp(sysvals.stamp, data.stamp):
2059 stampcolumns = True
2060 break
2062 th = '\t<th>{0}</th>\n'
2063 td = '\t<td>{0}</td>\n'
2064 tdlink = '\t<td><a href="{0}">Click Here</a></td>\n'
2066 # table header
2067 html += '<table class="summary">\n<tr>\n'
2068 html += th.format("Test #")
2069 if stampcolumns:
2070 html += th.format("Hostname")
2071 html += th.format("Kernel Version")
2072 html += th.format("Suspend Mode")
2073 html += th.format("Test Time")
2074 html += th.format("Suspend Time")
2075 html += th.format("Resume Time")
2076 html += th.format("Detail")
2077 html += '</tr>\n'
2079 # test data, 1 row per test
2080 sTimeAvg = 0.0
2081 rTimeAvg = 0.0
2082 num = 1
2083 for data in testruns:
2084 # data.end is the end of post_resume
2085 resumeEnd = data.dmesg['resume_complete']['end']
2086 if num % 2 == 1:
2087 html += '<tr class="alt">\n'
2088 else:
2089 html += '<tr>\n'
2091 # test num
2092 html += td.format("test %d" % num)
2093 num += 1
2094 if stampcolumns:
2095 # host name
2096 val = "unknown"
2097 if('host' in data.stamp):
2098 val = data.stamp['host']
2099 html += td.format(val)
2100 # host kernel
2101 val = "unknown"
2102 if('kernel' in data.stamp):
2103 val = data.stamp['kernel']
2104 html += td.format(val)
2105 # suspend mode
2106 val = "unknown"
2107 if('mode' in data.stamp):
2108 val = data.stamp['mode']
2109 html += td.format(val)
2110 # test time
2111 val = "unknown"
2112 if('time' in data.stamp):
2113 val = data.stamp['time']
2114 html += td.format(val)
2115 # suspend time
2116 sTime = (data.tSuspended - data.start)*1000
2117 sTimeAvg += sTime
2118 html += td.format("%3.3f ms" % sTime)
2119 # resume time
2120 rTime = (resumeEnd - data.tResumed)*1000
2121 rTimeAvg += rTime
2122 html += td.format("%3.3f ms" % rTime)
2123 # link to the output html
2124 html += tdlink.format(data.outfile)
2126 html += '</tr>\n'
2128 # last line: test average
2129 if(count > 0):
2130 sTimeAvg /= count
2131 rTimeAvg /= count
2132 html += '<tr class="avg">\n'
2133 html += td.format('Average') # name
2134 if stampcolumns:
2135 html += td.format('') # host
2136 html += td.format('') # kernel
2137 html += td.format('') # mode
2138 html += td.format('') # time
2139 html += td.format("%3.3f ms" % sTimeAvg) # suspend time
2140 html += td.format("%3.3f ms" % rTimeAvg) # resume time
2141 html += td.format('') # output link
2142 html += '</tr>\n'
2144 # flush the data to file
2145 hf.write(html+'</table>\n')
2146 hf.write('</body>\n</html>\n')
2147 hf.close()
2149 # Function: createHTML
2150 # Description:
2151 # Create the output html file from the resident test data
2152 # Arguments:
2153 # testruns: array of Data objects from parseKernelLog or parseTraceLog
2154 # Output:
2155 # True if the html file was created, false if it failed
2156 def createHTML(testruns):
2157 global sysvals
2159 for data in testruns:
2160 data.normalizeTime(testruns[-1].tSuspended)
2162 x2changes = ['', 'absolute']
2163 if len(testruns) > 1:
2164 x2changes = ['1', 'relative']
2165 # html function templates
2166 headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2167 html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail%s</button>' % x2changes[0]
2168 html_zoombox = '<center><button id="zoomin">ZOOM IN</button><button id="zoomout">ZOOM OUT</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2169 html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2170 html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2171 html_device = '<div id="{0}" title="{1}" class="thread" style="left:{2}%;top:{3}%;height:{4}%;width:{5}%;">{6}</div>\n'
2172 html_traceevent = '<div title="{0}" class="traceevent" style="left:{1}%;top:{2}%;height:{3}%;width:{4}%;border:1px solid {5};background-color:{5}">{6}</div>\n'
2173 html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}%;height:{3}%;background-color:{4}">{5}</div>\n'
2174 html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background-color:{3}"></div>\n'
2175 html_legend = '<div class="square" style="left:{0}%;background-color:{1}">&nbsp;{2}</div>\n'
2176 html_timetotal = '<table class="time1">\n<tr>'\
2177 '<td class="green">{2} Suspend Time: <b>{0} ms</b></td>'\
2178 '<td class="yellow">{2} Resume Time: <b>{1} ms</b></td>'\
2179 '</tr>\n</table>\n'
2180 html_timetotal2 = '<table class="time1">\n<tr>'\
2181 '<td class="green">{3} Suspend Time: <b>{0} ms</b></td>'\
2182 '<td class="gray">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
2183 '<td class="yellow">{3} Resume Time: <b>{2} ms</b></td>'\
2184 '</tr>\n</table>\n'
2185 html_timegroups = '<table class="time2">\n<tr>'\
2186 '<td class="green">{4}Kernel Suspend: {0} ms</td>'\
2187 '<td class="purple">{4}Firmware Suspend: {1} ms</td>'\
2188 '<td class="purple">{4}Firmware Resume: {2} ms</td>'\
2189 '<td class="yellow">{4}Kernel Resume: {3} ms</td>'\
2190 '</tr>\n</table>\n'
2192 # device timeline
2193 vprint('Creating Device Timeline...')
2194 devtl = Timeline()
2196 # Generate the header for this timeline
2197 textnum = ['First', 'Second']
2198 for data in testruns:
2199 tTotal = data.end - data.start
2200 tEnd = data.dmesg['resume_complete']['end']
2201 if(tTotal == 0):
2202 print('ERROR: No timeline data')
2203 sys.exit()
2204 if(data.tLow > 0):
2205 low_time = '%.0f'%(data.tLow*1000)
2206 if data.fwValid:
2207 suspend_time = '%.0f'%((data.tSuspended-data.start)*1000 + \
2208 (data.fwSuspend/1000000.0))
2209 resume_time = '%.0f'%((tEnd-data.tSuspended)*1000 + \
2210 (data.fwResume/1000000.0))
2211 testdesc1 = 'Total'
2212 testdesc2 = ''
2213 if(len(testruns) > 1):
2214 testdesc1 = testdesc2 = textnum[data.testnumber]
2215 testdesc2 += ' '
2216 if(data.tLow == 0):
2217 thtml = html_timetotal.format(suspend_time, \
2218 resume_time, testdesc1)
2219 else:
2220 thtml = html_timetotal2.format(suspend_time, low_time, \
2221 resume_time, testdesc1)
2222 devtl.html['timeline'] += thtml
2223 sktime = '%.3f'%((data.dmesg['suspend_machine']['end'] - \
2224 data.getStart())*1000)
2225 sftime = '%.3f'%(data.fwSuspend / 1000000.0)
2226 rftime = '%.3f'%(data.fwResume / 1000000.0)
2227 rktime = '%.3f'%((data.getEnd() - \
2228 data.dmesg['resume_machine']['start'])*1000)
2229 devtl.html['timeline'] += html_timegroups.format(sktime, \
2230 sftime, rftime, rktime, testdesc2)
2231 else:
2232 suspend_time = '%.0f'%((data.tSuspended-data.start)*1000)
2233 resume_time = '%.0f'%((tEnd-data.tSuspended)*1000)
2234 testdesc = 'Kernel'
2235 if(len(testruns) > 1):
2236 testdesc = textnum[data.testnumber]+' '+testdesc
2237 if(data.tLow == 0):
2238 thtml = html_timetotal.format(suspend_time, \
2239 resume_time, testdesc)
2240 else:
2241 thtml = html_timetotal2.format(suspend_time, low_time, \
2242 resume_time, testdesc)
2243 devtl.html['timeline'] += thtml
2245 # time scale for potentially multiple datasets
2246 t0 = testruns[0].start
2247 tMax = testruns[-1].end
2248 tSuspended = testruns[-1].tSuspended
2249 tTotal = tMax - t0
2251 # determine the maximum number of rows we need to draw
2252 timelinerows = 0
2253 for data in testruns:
2254 for phase in data.dmesg:
2255 list = data.dmesg[phase]['list']
2256 rows = setTimelineRows(list, list)
2257 data.dmesg[phase]['row'] = rows
2258 if(rows > timelinerows):
2259 timelinerows = rows
2261 # calculate the timeline height and create bounding box, add buttons
2262 devtl.setRows(timelinerows + 1)
2263 devtl.html['timeline'] += html_devlist1
2264 if len(testruns) > 1:
2265 devtl.html['timeline'] += html_devlist2
2266 devtl.html['timeline'] += html_zoombox
2267 devtl.html['timeline'] += html_timeline.format('dmesg', devtl.height)
2269 # draw the colored boxes for each of the phases
2270 for data in testruns:
2271 for b in data.dmesg:
2272 phase = data.dmesg[b]
2273 length = phase['end']-phase['start']
2274 left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
2275 width = '%.3f' % ((length*100.0)/tTotal)
2276 devtl.html['timeline'] += html_phase.format(left, width, \
2277 '%.3f'%devtl.scaleH, '%.3f'%(100-devtl.scaleH), \
2278 data.dmesg[b]['color'], '')
2280 # draw the time scale, try to make the number of labels readable
2281 devtl.html['scale'] = createTimeScale(t0, tMax, tSuspended)
2282 devtl.html['timeline'] += devtl.html['scale']
2283 for data in testruns:
2284 for b in data.dmesg:
2285 phaselist = data.dmesg[b]['list']
2286 for d in phaselist:
2287 name = d
2288 drv = ''
2289 dev = phaselist[d]
2290 if(d in sysvals.altdevname):
2291 name = sysvals.altdevname[d]
2292 if('drv' in dev and dev['drv']):
2293 drv = ' {%s}' % dev['drv']
2294 height = (100.0 - devtl.scaleH)/data.dmesg[b]['row']
2295 top = '%.3f' % ((dev['row']*height) + devtl.scaleH)
2296 left = '%.3f' % (((dev['start']-t0)*100)/tTotal)
2297 width = '%.3f' % (((dev['end']-dev['start'])*100)/tTotal)
2298 length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
2299 color = 'rgba(204,204,204,0.5)'
2300 devtl.html['timeline'] += html_device.format(dev['id'], \
2301 d+drv+length+b, left, top, '%.3f'%height, width, name+drv)
2303 # draw any trace events found
2304 for data in testruns:
2305 for b in data.dmesg:
2306 phaselist = data.dmesg[b]['list']
2307 for name in phaselist:
2308 dev = phaselist[name]
2309 if('traceevents' in dev):
2310 vprint('Debug trace events found for device %s' % name)
2311 vprint('%20s %20s %10s %8s' % ('action', \
2312 'name', 'time(ms)', 'length(ms)'))
2313 for e in dev['traceevents']:
2314 vprint('%20s %20s %10.3f %8.3f' % (e.action, \
2315 e.name, e.time*1000, e.length*1000))
2316 height = (100.0 - devtl.scaleH)/data.dmesg[b]['row']
2317 top = '%.3f' % ((dev['row']*height) + devtl.scaleH)
2318 left = '%.3f' % (((e.time-t0)*100)/tTotal)
2319 width = '%.3f' % (e.length*100/tTotal)
2320 color = 'rgba(204,204,204,0.5)'
2321 devtl.html['timeline'] += \
2322 html_traceevent.format(e.action+' '+e.name, \
2323 left, top, '%.3f'%height, \
2324 width, e.color, '')
2326 # timeline is finished
2327 devtl.html['timeline'] += '</div>\n</div>\n'
2329 # draw a legend which describes the phases by color
2330 data = testruns[-1]
2331 devtl.html['legend'] = '<div class="legend">\n'
2332 pdelta = 100.0/len(data.phases)
2333 pmargin = pdelta / 4.0
2334 for phase in data.phases:
2335 order = '%.2f' % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
2336 name = string.replace(phase, '_', ' &nbsp;')
2337 devtl.html['legend'] += html_legend.format(order, \
2338 data.dmesg[phase]['color'], name)
2339 devtl.html['legend'] += '</div>\n'
2341 hf = open(sysvals.htmlfile, 'w')
2342 thread_height = 0
2344 # write the html header first (html head, css code, up to body start)
2345 html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
2346 <meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
2347 <title>AnalyzeSuspend</title>\n\
2348 <style type=\'text/css\'>\n\
2349 body {overflow-y: scroll;}\n\
2350 .stamp {width: 100%;text-align:center;background-color:gray;line-height:30px;color:white;font: 25px Arial;}\n\
2351 .callgraph {margin-top: 30px;box-shadow: 5px 5px 20px black;}\n\
2352 .callgraph article * {padding-left: 28px;}\n\
2353 h1 {color:black;font: bold 30px Times;}\n\
2354 t0 {color:black;font: bold 30px Times;}\n\
2355 t1 {color:black;font: 30px Times;}\n\
2356 t2 {color:black;font: 25px Times;}\n\
2357 t3 {color:black;font: 20px Times;white-space:nowrap;}\n\
2358 t4 {color:black;font: bold 30px Times;line-height:60px;white-space:nowrap;}\n\
2359 table {width:100%;}\n\
2360 .gray {background-color:rgba(80,80,80,0.1);}\n\
2361 .green {background-color:rgba(204,255,204,0.4);}\n\
2362 .purple {background-color:rgba(128,0,128,0.2);}\n\
2363 .yellow {background-color:rgba(255,255,204,0.4);}\n\
2364 .time1 {font: 22px Arial;border:1px solid;}\n\
2365 .time2 {font: 15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
2366 td {text-align: center;}\n\
2367 r {color:#500000;font:15px Tahoma;}\n\
2368 n {color:#505050;font:15px Tahoma;}\n\
2369 .tdhl {color: red;}\n\
2370 .hide {display: none;}\n\
2371 .pf {display: none;}\n\
2372 .pf:checked + label {background: url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/><rect x="8" y="4" width="2" height="10" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
2373 .pf:not(:checked) ~ label {background: url(\'data:image/svg+xml;utf,<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1"><circle cx="9" cy="9" r="8" stroke="black" stroke-width="1" fill="white"/><rect x="4" y="8" width="10" height="2" style="fill:black;stroke-width:0"/></svg>\') no-repeat left center;}\n\
2374 .pf:checked ~ *:not(:nth-child(2)) {display: none;}\n\
2375 .zoombox {position: relative; width: 100%; overflow-x: scroll;}\n\
2376 .timeline {position: relative; font-size: 14px;cursor: pointer;width: 100%; overflow: hidden; background-color:#dddddd;}\n\
2377 .thread {position: absolute; height: '+'%.3f'%thread_height+'%; overflow: hidden; line-height: 30px; border:1px solid;text-align:center;white-space:nowrap;background-color:rgba(204,204,204,0.5);}\n\
2378 .thread:hover {background-color:white;border:1px solid red;z-index:10;}\n\
2379 .hover {background-color:white;border:1px solid red;z-index:10;}\n\
2380 .traceevent {position: absolute;opacity: 0.3;height: '+'%.3f'%thread_height+'%;width:0;overflow:hidden;line-height:30px;text-align:center;white-space:nowrap;}\n\
2381 .phase {position: absolute;overflow: hidden;border:0px;text-align:center;}\n\
2382 .phaselet {position:absolute;overflow:hidden;border:0px;text-align:center;height:100px;font-size:24px;}\n\
2383 .t {position:absolute;top:0%;height:100%;border-right:1px solid black;}\n\
2384 .legend {position: relative; width: 100%; height: 40px; text-align: center;margin-bottom:20px}\n\
2385 .legend .square {position:absolute;top:10px; width: 0px;height: 20px;border:1px solid;padding-left:20px;}\n\
2386 button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
2387 .devlist {position:'+x2changes[1]+';width:190px;}\n\
2388 #devicedetail {height:100px;box-shadow: 5px 5px 20px black;}\n\
2389 </style>\n</head>\n<body>\n'
2390 hf.write(html_header)
2392 # write the test title and general info header
2393 if(sysvals.stamp['time'] != ""):
2394 hf.write(headline_stamp.format(sysvals.stamp['host'],
2395 sysvals.stamp['kernel'], sysvals.stamp['mode'], \
2396 sysvals.stamp['time']))
2398 # write the device timeline
2399 hf.write(devtl.html['timeline'])
2400 hf.write(devtl.html['legend'])
2401 hf.write('<div id="devicedetailtitle"></div>\n')
2402 hf.write('<div id="devicedetail" style="display:none;">\n')
2403 # draw the colored boxes for the device detail section
2404 for data in testruns:
2405 hf.write('<div id="devicedetail%d">\n' % data.testnumber)
2406 for b in data.phases:
2407 phase = data.dmesg[b]
2408 length = phase['end']-phase['start']
2409 left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
2410 width = '%.3f' % ((length*100.0)/tTotal)
2411 hf.write(html_phaselet.format(b, left, width, \
2412 data.dmesg[b]['color']))
2413 hf.write('</div>\n')
2414 hf.write('</div>\n')
2416 # write the ftrace data (callgraph)
2417 data = testruns[-1]
2418 if(sysvals.usecallgraph):
2419 hf.write('<section id="callgraphs" class="callgraph">\n')
2420 # write out the ftrace data converted to html
2421 html_func_top = '<article id="{0}" class="atop" style="background-color:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
2422 html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
2423 html_func_end = '</article>\n'
2424 html_func_leaf = '<article>{0} {1}</article>\n'
2425 num = 0
2426 for p in data.phases:
2427 list = data.dmesg[p]['list']
2428 for devname in data.sortedDevices(p):
2429 if('ftrace' not in list[devname]):
2430 continue
2431 name = devname
2432 if(devname in sysvals.altdevname):
2433 name = sysvals.altdevname[devname]
2434 devid = list[devname]['id']
2435 cg = list[devname]['ftrace']
2436 flen = '<r>(%.3f ms @ %.3f to %.3f)</r>' % \
2437 ((cg.end - cg.start)*1000, cg.start*1000, cg.end*1000)
2438 hf.write(html_func_top.format(devid, data.dmesg[p]['color'], \
2439 num, name+' '+p, flen))
2440 num += 1
2441 for line in cg.list:
2442 if(line.length < 0.000000001):
2443 flen = ''
2444 else:
2445 flen = '<n>(%.3f ms @ %.3f)</n>' % (line.length*1000, \
2446 line.time*1000)
2447 if(line.freturn and line.fcall):
2448 hf.write(html_func_leaf.format(line.name, flen))
2449 elif(line.freturn):
2450 hf.write(html_func_end)
2451 else:
2452 hf.write(html_func_start.format(num, line.name, flen))
2453 num += 1
2454 hf.write(html_func_end)
2455 hf.write('\n\n </section>\n')
2456 # write the footer and close
2457 addScriptCode(hf, testruns)
2458 hf.write('</body>\n</html>\n')
2459 hf.close()
2460 return True
2462 # Function: addScriptCode
2463 # Description:
2464 # Adds the javascript code to the output html
2465 # Arguments:
2466 # hf: the open html file pointer
2467 # testruns: array of Data objects from parseKernelLog or parseTraceLog
2468 def addScriptCode(hf, testruns):
2469 t0 = (testruns[0].start - testruns[-1].tSuspended) * 1000
2470 tMax = (testruns[-1].end - testruns[-1].tSuspended) * 1000
2471 # create an array in javascript memory with the device details
2472 detail = ' var devtable = [];\n'
2473 for data in testruns:
2474 topo = data.deviceTopology()
2475 detail += ' devtable[%d] = "%s";\n' % (data.testnumber, topo)
2476 detail += ' var bounds = [%f,%f];\n' % (t0, tMax)
2477 # add the code which will manipulate the data in the browser
2478 script_code = \
2479 '<script type="text/javascript">\n'+detail+\
2480 ' function zoomTimeline() {\n'\
2481 ' var timescale = document.getElementById("timescale");\n'\
2482 ' var dmesg = document.getElementById("dmesg");\n'\
2483 ' var zoombox = document.getElementById("dmesgzoombox");\n'\
2484 ' var val = parseFloat(dmesg.style.width);\n'\
2485 ' var newval = 100;\n'\
2486 ' var sh = window.outerWidth / 2;\n'\
2487 ' if(this.id == "zoomin") {\n'\
2488 ' newval = val * 1.2;\n'\
2489 ' if(newval > 40000) newval = 40000;\n'\
2490 ' dmesg.style.width = newval+"%";\n'\
2491 ' zoombox.scrollLeft = ((zoombox.scrollLeft + sh) * newval / val) - sh;\n'\
2492 ' } else if (this.id == "zoomout") {\n'\
2493 ' newval = val / 1.2;\n'\
2494 ' if(newval < 100) newval = 100;\n'\
2495 ' dmesg.style.width = newval+"%";\n'\
2496 ' zoombox.scrollLeft = ((zoombox.scrollLeft + sh) * newval / val) - sh;\n'\
2497 ' } else {\n'\
2498 ' zoombox.scrollLeft = 0;\n'\
2499 ' dmesg.style.width = "100%";\n'\
2500 ' }\n'\
2501 ' var html = "";\n'\
2502 ' var t0 = bounds[0];\n'\
2503 ' var tMax = bounds[1];\n'\
2504 ' var tTotal = tMax - t0;\n'\
2505 ' var wTotal = tTotal * 100.0 / newval;\n'\
2506 ' for(var tS = 1000; (wTotal / tS) < 3; tS /= 10);\n'\
2507 ' if(tS < 1) tS = 1;\n'\
2508 ' for(var s = ((t0 / tS)|0) * tS; s < tMax; s += tS) {\n'\
2509 ' var pos = (tMax - s) * 100.0 / tTotal;\n'\
2510 ' var name = (s == 0)?"S/R":(s+"ms");\n'\
2511 ' html += "<div class=\\"t\\" style=\\"right:"+pos+"%\\">"+name+"</div>";\n'\
2512 ' }\n'\
2513 ' timescale.innerHTML = html;\n'\
2514 ' }\n'\
2515 ' function deviceHover() {\n'\
2516 ' var name = this.title.slice(0, this.title.indexOf(" ("));\n'\
2517 ' var dmesg = document.getElementById("dmesg");\n'\
2518 ' var dev = dmesg.getElementsByClassName("thread");\n'\
2519 ' var cpu = -1;\n'\
2520 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
2521 ' cpu = parseInt(name.slice(7));\n'\
2522 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
2523 ' cpu = parseInt(name.slice(8));\n'\
2524 ' for (var i = 0; i < dev.length; i++) {\n'\
2525 ' dname = dev[i].title.slice(0, dev[i].title.indexOf(" ("));\n'\
2526 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
2527 ' (name == dname))\n'\
2528 ' {\n'\
2529 ' dev[i].className = "thread hover";\n'\
2530 ' } else {\n'\
2531 ' dev[i].className = "thread";\n'\
2532 ' }\n'\
2533 ' }\n'\
2534 ' }\n'\
2535 ' function deviceUnhover() {\n'\
2536 ' var dmesg = document.getElementById("dmesg");\n'\
2537 ' var dev = dmesg.getElementsByClassName("thread");\n'\
2538 ' for (var i = 0; i < dev.length; i++) {\n'\
2539 ' dev[i].className = "thread";\n'\
2540 ' }\n'\
2541 ' }\n'\
2542 ' function deviceTitle(title, total, cpu) {\n'\
2543 ' var prefix = "Total";\n'\
2544 ' if(total.length > 3) {\n'\
2545 ' prefix = "Average";\n'\
2546 ' total[1] = (total[1]+total[3])/2;\n'\
2547 ' total[2] = (total[2]+total[4])/2;\n'\
2548 ' }\n'\
2549 ' var devtitle = document.getElementById("devicedetailtitle");\n'\
2550 ' var name = title.slice(0, title.indexOf(" "));\n'\
2551 ' if(cpu >= 0) name = "CPU"+cpu;\n'\
2552 ' var driver = "";\n'\
2553 ' var tS = "<t2>(</t2>";\n'\
2554 ' var tR = "<t2>)</t2>";\n'\
2555 ' if(total[1] > 0)\n'\
2556 ' tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";\n'\
2557 ' if(total[2] > 0)\n'\
2558 ' tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";\n'\
2559 ' var s = title.indexOf("{");\n'\
2560 ' var e = title.indexOf("}");\n'\
2561 ' if((s >= 0) && (e >= 0))\n'\
2562 ' driver = title.slice(s+1, e) + " <t1>@</t1> ";\n'\
2563 ' if(total[1] > 0 && total[2] > 0)\n'\
2564 ' devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;\n'\
2565 ' else\n'\
2566 ' devtitle.innerHTML = "<t0>"+title+"</t0>";\n'\
2567 ' return name;\n'\
2568 ' }\n'\
2569 ' function deviceDetail() {\n'\
2570 ' var devinfo = document.getElementById("devicedetail");\n'\
2571 ' devinfo.style.display = "block";\n'\
2572 ' var name = this.title.slice(0, this.title.indexOf(" ("));\n'\
2573 ' var cpu = -1;\n'\
2574 ' if(name.match("CPU_ON\[[0-9]*\]"))\n'\
2575 ' cpu = parseInt(name.slice(7));\n'\
2576 ' else if(name.match("CPU_OFF\[[0-9]*\]"))\n'\
2577 ' cpu = parseInt(name.slice(8));\n'\
2578 ' var dmesg = document.getElementById("dmesg");\n'\
2579 ' var dev = dmesg.getElementsByClassName("thread");\n'\
2580 ' var idlist = [];\n'\
2581 ' var pdata = [[]];\n'\
2582 ' var pd = pdata[0];\n'\
2583 ' var total = [0.0, 0.0, 0.0];\n'\
2584 ' for (var i = 0; i < dev.length; i++) {\n'\
2585 ' dname = dev[i].title.slice(0, dev[i].title.indexOf(" ("));\n'\
2586 ' if((cpu >= 0 && dname.match("CPU_O[NF]*\\\[*"+cpu+"\\\]")) ||\n'\
2587 ' (name == dname))\n'\
2588 ' {\n'\
2589 ' idlist[idlist.length] = dev[i].id;\n'\
2590 ' var tidx = 1;\n'\
2591 ' if(dev[i].id[0] == "a") {\n'\
2592 ' pd = pdata[0];\n'\
2593 ' } else {\n'\
2594 ' if(pdata.length == 1) pdata[1] = [];\n'\
2595 ' if(total.length == 3) total[3]=total[4]=0.0;\n'\
2596 ' pd = pdata[1];\n'\
2597 ' tidx = 3;\n'\
2598 ' }\n'\
2599 ' var info = dev[i].title.split(" ");\n'\
2600 ' var pname = info[info.length-1];\n'\
2601 ' pd[pname] = parseFloat(info[info.length-3].slice(1));\n'\
2602 ' total[0] += pd[pname];\n'\
2603 ' if(pname.indexOf("suspend") >= 0)\n'\
2604 ' total[tidx] += pd[pname];\n'\
2605 ' else\n'\
2606 ' total[tidx+1] += pd[pname];\n'\
2607 ' }\n'\
2608 ' }\n'\
2609 ' var devname = deviceTitle(this.title, total, cpu);\n'\
2610 ' var left = 0.0;\n'\
2611 ' for (var t = 0; t < pdata.length; t++) {\n'\
2612 ' pd = pdata[t];\n'\
2613 ' devinfo = document.getElementById("devicedetail"+t);\n'\
2614 ' var phases = devinfo.getElementsByClassName("phaselet");\n'\
2615 ' for (var i = 0; i < phases.length; i++) {\n'\
2616 ' if(phases[i].id in pd) {\n'\
2617 ' var w = 100.0*pd[phases[i].id]/total[0];\n'\
2618 ' var fs = 32;\n'\
2619 ' if(w < 8) fs = 4*w | 0;\n'\
2620 ' var fs2 = fs*3/4;\n'\
2621 ' phases[i].style.width = w+"%";\n'\
2622 ' phases[i].style.left = left+"%";\n'\
2623 ' phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";\n'\
2624 ' left += w;\n'\
2625 ' var time = "<t4 style=\\"font-size:"+fs+"px\\">"+pd[phases[i].id]+" ms<br></t4>";\n'\
2626 ' var pname = "<t3 style=\\"font-size:"+fs2+"px\\">"+phases[i].id.replace("_", " ")+"</t3>";\n'\
2627 ' phases[i].innerHTML = time+pname;\n'\
2628 ' } else {\n'\
2629 ' phases[i].style.width = "0%";\n'\
2630 ' phases[i].style.left = left+"%";\n'\
2631 ' }\n'\
2632 ' }\n'\
2633 ' }\n'\
2634 ' var cglist = document.getElementById("callgraphs");\n'\
2635 ' if(!cglist) return;\n'\
2636 ' var cg = cglist.getElementsByClassName("atop");\n'\
2637 ' for (var i = 0; i < cg.length; i++) {\n'\
2638 ' if(idlist.indexOf(cg[i].id) >= 0) {\n'\
2639 ' cg[i].style.display = "block";\n'\
2640 ' } else {\n'\
2641 ' cg[i].style.display = "none";\n'\
2642 ' }\n'\
2643 ' }\n'\
2644 ' }\n'\
2645 ' function devListWindow(e) {\n'\
2646 ' var sx = e.clientX;\n'\
2647 ' if(sx > window.innerWidth - 440)\n'\
2648 ' sx = window.innerWidth - 440;\n'\
2649 ' var cfg="top="+e.screenY+", left="+sx+", width=440, height=720, scrollbars=yes";\n'\
2650 ' var win = window.open("", "_blank", cfg);\n'\
2651 ' if(window.chrome) win.moveBy(sx, 0);\n'\
2652 ' var html = "<title>"+e.target.innerHTML+"</title>"+\n'\
2653 ' "<style type=\\"text/css\\">"+\n'\
2654 ' " ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+\n'\
2655 ' "</style>"\n'\
2656 ' var dt = devtable[0];\n'\
2657 ' if(e.target.id != "devlist1")\n'\
2658 ' dt = devtable[1];\n'\
2659 ' win.document.write(html+dt);\n'\
2660 ' }\n'\
2661 ' window.addEventListener("load", function () {\n'\
2662 ' var dmesg = document.getElementById("dmesg");\n'\
2663 ' dmesg.style.width = "100%"\n'\
2664 ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\
2665 ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\
2666 ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
2667 ' var devlist = document.getElementsByClassName("devlist");\n'\
2668 ' for (var i = 0; i < devlist.length; i++)\n'\
2669 ' devlist[i].onclick = devListWindow;\n'\
2670 ' var dev = dmesg.getElementsByClassName("thread");\n'\
2671 ' for (var i = 0; i < dev.length; i++) {\n'\
2672 ' dev[i].onclick = deviceDetail;\n'\
2673 ' dev[i].onmouseover = deviceHover;\n'\
2674 ' dev[i].onmouseout = deviceUnhover;\n'\
2675 ' }\n'\
2676 ' zoomTimeline();\n'\
2677 ' });\n'\
2678 '</script>\n'
2679 hf.write(script_code);
2681 # Function: executeSuspend
2682 # Description:
2683 # Execute system suspend through the sysfs interface, then copy the output
2684 # dmesg and ftrace files to the test output directory.
2685 def executeSuspend():
2686 global sysvals
2688 detectUSB(False)
2689 t0 = time.time()*1000
2690 tp = sysvals.tpath
2691 # execute however many s/r runs requested
2692 for count in range(1,sysvals.execcount+1):
2693 # clear the kernel ring buffer just as we start
2694 os.system('dmesg -C')
2695 # enable callgraph ftrace only for the second run
2696 if(sysvals.usecallgraph and count == 2):
2697 # set trace type
2698 os.system('echo function_graph > '+tp+'current_tracer')
2699 os.system('echo "" > '+tp+'set_ftrace_filter')
2700 # set trace format options
2701 os.system('echo funcgraph-abstime > '+tp+'trace_options')
2702 os.system('echo funcgraph-proc > '+tp+'trace_options')
2703 # focus only on device suspend and resume
2704 os.system('cat '+tp+'available_filter_functions | '+\
2705 'grep dpm_run_callback > '+tp+'set_graph_function')
2706 # if this is test2 and there's a delay, start here
2707 if(count > 1 and sysvals.x2delay > 0):
2708 tN = time.time()*1000
2709 while (tN - t0) < sysvals.x2delay:
2710 tN = time.time()*1000
2711 time.sleep(0.001)
2712 # start ftrace
2713 if(sysvals.usecallgraph or sysvals.usetraceevents):
2714 print('START TRACING')
2715 os.system('echo 1 > '+tp+'tracing_on')
2716 # initiate suspend
2717 if(sysvals.usecallgraph or sysvals.usetraceevents):
2718 os.system('echo SUSPEND START > '+tp+'trace_marker')
2719 if(sysvals.rtcwake):
2720 print('SUSPEND START')
2721 print('will autoresume in %d seconds' % sysvals.rtcwaketime)
2722 sysvals.rtcWakeAlarm()
2723 else:
2724 print('SUSPEND START (press a key to resume)')
2725 pf = open(sysvals.powerfile, 'w')
2726 pf.write(sysvals.suspendmode)
2727 # execution will pause here
2728 pf.close()
2729 t0 = time.time()*1000
2730 # return from suspend
2731 print('RESUME COMPLETE')
2732 if(sysvals.usecallgraph or sysvals.usetraceevents):
2733 os.system('echo RESUME COMPLETE > '+tp+'trace_marker')
2734 # see if there's firmware timing data to be had
2735 t = sysvals.postresumetime
2736 if(t > 0):
2737 print('Waiting %d seconds for POST-RESUME trace events...' % t)
2738 time.sleep(t)
2739 # stop ftrace
2740 if(sysvals.usecallgraph or sysvals.usetraceevents):
2741 os.system('echo 0 > '+tp+'tracing_on')
2742 print('CAPTURING TRACE')
2743 writeDatafileHeader(sysvals.ftracefile)
2744 os.system('cat '+tp+'trace >> '+sysvals.ftracefile)
2745 os.system('echo "" > '+tp+'trace')
2746 # grab a copy of the dmesg output
2747 print('CAPTURING DMESG')
2748 writeDatafileHeader(sysvals.dmesgfile)
2749 os.system('dmesg -c >> '+sysvals.dmesgfile)
2751 def writeDatafileHeader(filename):
2752 global sysvals
2754 fw = getFPDT(False)
2755 prt = sysvals.postresumetime
2756 fp = open(filename, 'a')
2757 fp.write(sysvals.teststamp+'\n')
2758 if(fw):
2759 fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
2760 if(prt > 0):
2761 fp.write('# post resume time %u\n' % prt)
2762 fp.close()
2764 # Function: executeAndroidSuspend
2765 # Description:
2766 # Execute system suspend through the sysfs interface
2767 # on a remote android device, then transfer the output
2768 # dmesg and ftrace files to the local output directory.
2769 def executeAndroidSuspend():
2770 global sysvals
2772 # check to see if the display is currently off
2773 tp = sysvals.tpath
2774 out = os.popen(sysvals.adb+\
2775 ' shell dumpsys power | grep mScreenOn').read().strip()
2776 # if so we need to turn it on so we can issue a new suspend
2777 if(out.endswith('false')):
2778 print('Waking the device up for the test...')
2779 # send the KEYPAD_POWER keyevent to wake it up
2780 os.system(sysvals.adb+' shell input keyevent 26')
2781 # wait a few seconds so the user can see the device wake up
2782 time.sleep(3)
2783 # execute however many s/r runs requested
2784 for count in range(1,sysvals.execcount+1):
2785 # clear the kernel ring buffer just as we start
2786 os.system(sysvals.adb+' shell dmesg -c > /dev/null 2>&1')
2787 # start ftrace
2788 if(sysvals.usetraceevents):
2789 print('START TRACING')
2790 os.system(sysvals.adb+" shell 'echo 1 > "+tp+"tracing_on'")
2791 # initiate suspend
2792 for count in range(1,sysvals.execcount+1):
2793 if(sysvals.usetraceevents):
2794 os.system(sysvals.adb+\
2795 " shell 'echo SUSPEND START > "+tp+"trace_marker'")
2796 print('SUSPEND START (press a key on the device to resume)')
2797 os.system(sysvals.adb+" shell 'echo "+sysvals.suspendmode+\
2798 " > "+sysvals.powerfile+"'")
2799 # execution will pause here, then adb will exit
2800 while(True):
2801 check = os.popen(sysvals.adb+\
2802 ' shell pwd 2>/dev/null').read().strip()
2803 if(len(check) > 0):
2804 break
2805 time.sleep(1)
2806 if(sysvals.usetraceevents):
2807 os.system(sysvals.adb+" shell 'echo RESUME COMPLETE > "+tp+\
2808 "trace_marker'")
2809 # return from suspend
2810 print('RESUME COMPLETE')
2811 # stop ftrace
2812 if(sysvals.usetraceevents):
2813 os.system(sysvals.adb+" shell 'echo 0 > "+tp+"tracing_on'")
2814 print('CAPTURING TRACE')
2815 os.system('echo "'+sysvals.teststamp+'" > '+sysvals.ftracefile)
2816 os.system(sysvals.adb+' shell cat '+tp+\
2817 'trace >> '+sysvals.ftracefile)
2818 # grab a copy of the dmesg output
2819 print('CAPTURING DMESG')
2820 os.system('echo "'+sysvals.teststamp+'" > '+sysvals.dmesgfile)
2821 os.system(sysvals.adb+' shell dmesg >> '+sysvals.dmesgfile)
2823 # Function: setUSBDevicesAuto
2824 # Description:
2825 # Set the autosuspend control parameter of all USB devices to auto
2826 # This can be dangerous, so use at your own risk, most devices are set
2827 # to always-on since the kernel cant determine if the device can
2828 # properly autosuspend
2829 def setUSBDevicesAuto():
2830 global sysvals
2832 rootCheck()
2833 for dirname, dirnames, filenames in os.walk('/sys/devices'):
2834 if(re.match('.*/usb[0-9]*.*', dirname) and
2835 'idVendor' in filenames and 'idProduct' in filenames):
2836 os.system('echo auto > %s/power/control' % dirname)
2837 name = dirname.split('/')[-1]
2838 desc = os.popen('cat %s/product 2>/dev/null' % \
2839 dirname).read().replace('\n', '')
2840 ctrl = os.popen('cat %s/power/control 2>/dev/null' % \
2841 dirname).read().replace('\n', '')
2842 print('control is %s for %6s: %s' % (ctrl, name, desc))
2844 # Function: yesno
2845 # Description:
2846 # Print out an equivalent Y or N for a set of known parameter values
2847 # Output:
2848 # 'Y', 'N', or ' ' if the value is unknown
2849 def yesno(val):
2850 yesvals = ['auto', 'enabled', 'active', '1']
2851 novals = ['on', 'disabled', 'suspended', 'forbidden', 'unsupported']
2852 if val in yesvals:
2853 return 'Y'
2854 elif val in novals:
2855 return 'N'
2856 return ' '
2858 # Function: ms2nice
2859 # Description:
2860 # Print out a very concise time string in minutes and seconds
2861 # Output:
2862 # The time string, e.g. "1901m16s"
2863 def ms2nice(val):
2864 ms = 0
2865 try:
2866 ms = int(val)
2867 except:
2868 return 0.0
2869 m = ms / 60000
2870 s = (ms / 1000) - (m * 60)
2871 return '%3dm%2ds' % (m, s)
2873 # Function: detectUSB
2874 # Description:
2875 # Detect all the USB hosts and devices currently connected and add
2876 # a list of USB device names to sysvals for better timeline readability
2877 # Arguments:
2878 # output: True to output the info to stdout, False otherwise
2879 def detectUSB(output):
2880 global sysvals
2882 field = {'idVendor':'', 'idProduct':'', 'product':'', 'speed':''}
2883 power = {'async':'', 'autosuspend':'', 'autosuspend_delay_ms':'',
2884 'control':'', 'persist':'', 'runtime_enabled':'',
2885 'runtime_status':'', 'runtime_usage':'',
2886 'runtime_active_time':'',
2887 'runtime_suspended_time':'',
2888 'active_duration':'',
2889 'connected_duration':''}
2890 if(output):
2891 print('LEGEND')
2892 print('---------------------------------------------------------------------------------------------')
2893 print(' A = async/sync PM queue Y/N D = autosuspend delay (seconds)')
2894 print(' S = autosuspend Y/N rACTIVE = runtime active (min/sec)')
2895 print(' P = persist across suspend Y/N rSUSPEN = runtime suspend (min/sec)')
2896 print(' E = runtime suspend enabled/forbidden Y/N ACTIVE = active duration (min/sec)')
2897 print(' R = runtime status active/suspended Y/N CONNECT = connected duration (min/sec)')
2898 print(' U = runtime usage count')
2899 print('---------------------------------------------------------------------------------------------')
2900 print(' NAME ID DESCRIPTION SPEED A S P E R U D rACTIVE rSUSPEN ACTIVE CONNECT')
2901 print('---------------------------------------------------------------------------------------------')
2903 for dirname, dirnames, filenames in os.walk('/sys/devices'):
2904 if(re.match('.*/usb[0-9]*.*', dirname) and
2905 'idVendor' in filenames and 'idProduct' in filenames):
2906 for i in field:
2907 field[i] = os.popen('cat %s/%s 2>/dev/null' % \
2908 (dirname, i)).read().replace('\n', '')
2909 name = dirname.split('/')[-1]
2910 if(len(field['product']) > 0):
2911 sysvals.altdevname[name] = \
2912 '%s [%s]' % (field['product'], name)
2913 else:
2914 sysvals.altdevname[name] = \
2915 '%s:%s [%s]' % (field['idVendor'], \
2916 field['idProduct'], name)
2917 if(output):
2918 for i in power:
2919 power[i] = os.popen('cat %s/power/%s 2>/dev/null' % \
2920 (dirname, i)).read().replace('\n', '')
2921 if(re.match('usb[0-9]*', name)):
2922 first = '%-8s' % name
2923 else:
2924 first = '%8s' % name
2925 print('%s [%s:%s] %-20s %-4s %1s %1s %1s %1s %1s %1s %1s %s %s %s %s' % \
2926 (first, field['idVendor'], field['idProduct'], \
2927 field['product'][0:20], field['speed'], \
2928 yesno(power['async']), \
2929 yesno(power['control']), \
2930 yesno(power['persist']), \
2931 yesno(power['runtime_enabled']), \
2932 yesno(power['runtime_status']), \
2933 power['runtime_usage'], \
2934 power['autosuspend'], \
2935 ms2nice(power['runtime_active_time']), \
2936 ms2nice(power['runtime_suspended_time']), \
2937 ms2nice(power['active_duration']), \
2938 ms2nice(power['connected_duration'])))
2940 # Function: getModes
2941 # Description:
2942 # Determine the supported power modes on this system
2943 # Output:
2944 # A string list of the available modes
2945 def getModes():
2946 global sysvals
2947 modes = ''
2948 if(not sysvals.android):
2949 if(os.path.exists(sysvals.powerfile)):
2950 fp = open(sysvals.powerfile, 'r')
2951 modes = string.split(fp.read())
2952 fp.close()
2953 else:
2954 line = os.popen(sysvals.adb+' shell cat '+\
2955 sysvals.powerfile).read().strip()
2956 modes = string.split(line)
2957 return modes
2959 # Function: getFPDT
2960 # Description:
2961 # Read the acpi bios tables and pull out FPDT, the firmware data
2962 # Arguments:
2963 # output: True to output the info to stdout, False otherwise
2964 def getFPDT(output):
2965 global sysvals
2967 rectype = {}
2968 rectype[0] = 'Firmware Basic Boot Performance Record'
2969 rectype[1] = 'S3 Performance Table Record'
2970 prectype = {}
2971 prectype[0] = 'Basic S3 Resume Performance Record'
2972 prectype[1] = 'Basic S3 Suspend Performance Record'
2974 rootCheck()
2975 if(not os.path.exists(sysvals.fpdtpath)):
2976 if(output):
2977 doError('file doesnt exist: %s' % sysvals.fpdtpath, False)
2978 return False
2979 if(not os.access(sysvals.fpdtpath, os.R_OK)):
2980 if(output):
2981 doError('file isnt readable: %s' % sysvals.fpdtpath, False)
2982 return False
2983 if(not os.path.exists(sysvals.mempath)):
2984 if(output):
2985 doError('file doesnt exist: %s' % sysvals.mempath, False)
2986 return False
2987 if(not os.access(sysvals.mempath, os.R_OK)):
2988 if(output):
2989 doError('file isnt readable: %s' % sysvals.mempath, False)
2990 return False
2992 fp = open(sysvals.fpdtpath, 'rb')
2993 buf = fp.read()
2994 fp.close()
2996 if(len(buf) < 36):
2997 if(output):
2998 doError('Invalid FPDT table data, should '+\
2999 'be at least 36 bytes', False)
3000 return False
3002 table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
3003 if(output):
3004 print('')
3005 print('Firmware Performance Data Table (%s)' % table[0])
3006 print(' Signature : %s' % table[0])
3007 print(' Table Length : %u' % table[1])
3008 print(' Revision : %u' % table[2])
3009 print(' Checksum : 0x%x' % table[3])
3010 print(' OEM ID : %s' % table[4])
3011 print(' OEM Table ID : %s' % table[5])
3012 print(' OEM Revision : %u' % table[6])
3013 print(' Creator ID : %s' % table[7])
3014 print(' Creator Revision : 0x%x' % table[8])
3015 print('')
3017 if(table[0] != 'FPDT'):
3018 if(output):
3019 doError('Invalid FPDT table')
3020 return False
3021 if(len(buf) <= 36):
3022 return False
3023 i = 0
3024 fwData = [0, 0]
3025 records = buf[36:]
3026 fp = open(sysvals.mempath, 'rb')
3027 while(i < len(records)):
3028 header = struct.unpack('HBB', records[i:i+4])
3029 if(header[0] not in rectype):
3030 continue
3031 if(header[1] != 16):
3032 continue
3033 addr = struct.unpack('Q', records[i+8:i+16])[0]
3034 try:
3035 fp.seek(addr)
3036 first = fp.read(8)
3037 except:
3038 doError('Bad address 0x%x in %s' % (addr, sysvals.mempath), False)
3039 rechead = struct.unpack('4sI', first)
3040 recdata = fp.read(rechead[1]-8)
3041 if(rechead[0] == 'FBPT'):
3042 record = struct.unpack('HBBIQQQQQ', recdata)
3043 if(output):
3044 print('%s (%s)' % (rectype[header[0]], rechead[0]))
3045 print(' Reset END : %u ns' % record[4])
3046 print(' OS Loader LoadImage Start : %u ns' % record[5])
3047 print(' OS Loader StartImage Start : %u ns' % record[6])
3048 print(' ExitBootServices Entry : %u ns' % record[7])
3049 print(' ExitBootServices Exit : %u ns' % record[8])
3050 elif(rechead[0] == 'S3PT'):
3051 if(output):
3052 print('%s (%s)' % (rectype[header[0]], rechead[0]))
3053 j = 0
3054 while(j < len(recdata)):
3055 prechead = struct.unpack('HBB', recdata[j:j+4])
3056 if(prechead[0] not in prectype):
3057 continue
3058 if(prechead[0] == 0):
3059 record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
3060 fwData[1] = record[2]
3061 if(output):
3062 print(' %s' % prectype[prechead[0]])
3063 print(' Resume Count : %u' % \
3064 record[1])
3065 print(' FullResume : %u ns' % \
3066 record[2])
3067 print(' AverageResume : %u ns' % \
3068 record[3])
3069 elif(prechead[0] == 1):
3070 record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
3071 fwData[0] = record[1] - record[0]
3072 if(output):
3073 print(' %s' % prectype[prechead[0]])
3074 print(' SuspendStart : %u ns' % \
3075 record[0])
3076 print(' SuspendEnd : %u ns' % \
3077 record[1])
3078 print(' SuspendTime : %u ns' % \
3079 fwData[0])
3080 j += prechead[1]
3081 if(output):
3082 print('')
3083 i += header[1]
3084 fp.close()
3085 return fwData
3087 # Function: statusCheck
3088 # Description:
3089 # Verify that the requested command and options will work, and
3090 # print the results to the terminal
3091 # Output:
3092 # True if the test will work, False if not
3093 def statusCheck():
3094 global sysvals
3095 status = True
3097 if(sysvals.android):
3098 print('Checking the android system ...')
3099 else:
3100 print('Checking this system (%s)...' % platform.node())
3102 # check if adb is connected to a device
3103 if(sysvals.android):
3104 res = 'NO'
3105 out = os.popen(sysvals.adb+' get-state').read().strip()
3106 if(out == 'device'):
3107 res = 'YES'
3108 print(' is android device connected: %s' % res)
3109 if(res != 'YES'):
3110 print(' Please connect the device before using this tool')
3111 return False
3113 # check we have root access
3114 res = 'NO (No features of this tool will work!)'
3115 if(sysvals.android):
3116 out = os.popen(sysvals.adb+' shell id').read().strip()
3117 if('root' in out):
3118 res = 'YES'
3119 else:
3120 if(os.environ['USER'] == 'root'):
3121 res = 'YES'
3122 print(' have root access: %s' % res)
3123 if(res != 'YES'):
3124 if(sysvals.android):
3125 print(' Try running "adb root" to restart the daemon as root')
3126 else:
3127 print(' Try running this script with sudo')
3128 return False
3130 # check sysfs is mounted
3131 res = 'NO (No features of this tool will work!)'
3132 if(sysvals.android):
3133 out = os.popen(sysvals.adb+' shell ls '+\
3134 sysvals.powerfile).read().strip()
3135 if(out == sysvals.powerfile):
3136 res = 'YES'
3137 else:
3138 if(os.path.exists(sysvals.powerfile)):
3139 res = 'YES'
3140 print(' is sysfs mounted: %s' % res)
3141 if(res != 'YES'):
3142 return False
3144 # check target mode is a valid mode
3145 res = 'NO'
3146 modes = getModes()
3147 if(sysvals.suspendmode in modes):
3148 res = 'YES'
3149 else:
3150 status = False
3151 print(' is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
3152 if(res == 'NO'):
3153 print(' valid power modes are: %s' % modes)
3154 print(' please choose one with -m')
3156 # check if the tool can unlock the device
3157 if(sysvals.android):
3158 res = 'YES'
3159 out1 = os.popen(sysvals.adb+\
3160 ' shell dumpsys power | grep mScreenOn').read().strip()
3161 out2 = os.popen(sysvals.adb+\
3162 ' shell input').read().strip()
3163 if(not out1.startswith('mScreenOn') or not out2.startswith('usage')):
3164 res = 'NO (wake the android device up before running the test)'
3165 print(' can I unlock the screen: %s' % res)
3167 # check if ftrace is available
3168 res = 'NO'
3169 ftgood = verifyFtrace()
3170 if(ftgood):
3171 res = 'YES'
3172 elif(sysvals.usecallgraph):
3173 status = False
3174 print(' is ftrace supported: %s' % res)
3176 # what data source are we using
3177 res = 'DMESG'
3178 if(ftgood):
3179 sysvals.usetraceeventsonly = True
3180 sysvals.usetraceevents = False
3181 for e in sysvals.traceevents:
3182 check = False
3183 if(sysvals.android):
3184 out = os.popen(sysvals.adb+' shell ls -d '+\
3185 sysvals.epath+e).read().strip()
3186 if(out == sysvals.epath+e):
3187 check = True
3188 else:
3189 if(os.path.exists(sysvals.epath+e)):
3190 check = True
3191 if(not check):
3192 sysvals.usetraceeventsonly = False
3193 if(e == 'suspend_resume' and check):
3194 sysvals.usetraceevents = True
3195 if(sysvals.usetraceevents and sysvals.usetraceeventsonly):
3196 res = 'FTRACE (all trace events found)'
3197 elif(sysvals.usetraceevents):
3198 res = 'DMESG and FTRACE (suspend_resume trace event found)'
3199 print(' timeline data source: %s' % res)
3201 # check if rtcwake
3202 res = 'NO'
3203 if(sysvals.rtcpath != ''):
3204 res = 'YES'
3205 elif(sysvals.rtcwake):
3206 status = False
3207 print(' is rtcwake supported: %s' % res)
3209 return status
3211 # Function: doError
3212 # Description:
3213 # generic error function for catastrphic failures
3214 # Arguments:
3215 # msg: the error message to print
3216 # help: True if printHelp should be called after, False otherwise
3217 def doError(msg, help):
3218 if(help == True):
3219 printHelp()
3220 print('ERROR: %s\n') % msg
3221 sys.exit()
3223 # Function: doWarning
3224 # Description:
3225 # generic warning function for non-catastrophic anomalies
3226 # Arguments:
3227 # msg: the warning message to print
3228 # file: If not empty, a filename to request be sent to the owner for debug
3229 def doWarning(msg, file):
3230 print('/* %s */') % msg
3231 if(file):
3232 print('/* For a fix, please send this'+\
3233 ' %s file to <todd.e.brandt@intel.com> */' % file)
3235 # Function: rootCheck
3236 # Description:
3237 # quick check to see if we have root access
3238 def rootCheck():
3239 if(os.environ['USER'] != 'root'):
3240 doError('This script must be run as root', False)
3242 # Function: getArgInt
3243 # Description:
3244 # pull out an integer argument from the command line with checks
3245 def getArgInt(name, args, min, max):
3246 try:
3247 arg = args.next()
3248 except:
3249 doError(name+': no argument supplied', True)
3250 try:
3251 val = int(arg)
3252 except:
3253 doError(name+': non-integer value given', True)
3254 if(val < min or val > max):
3255 doError(name+': value should be between %d and %d' % (min, max), True)
3256 return val
3258 # Function: rerunTest
3259 # Description:
3260 # generate an output from an existing set of ftrace/dmesg logs
3261 def rerunTest():
3262 global sysvals
3264 if(sysvals.ftracefile != ''):
3265 doesTraceLogHaveTraceEvents()
3266 if(sysvals.dmesgfile == '' and not sysvals.usetraceeventsonly):
3267 doError('recreating this html output '+\
3268 'requires a dmesg file', False)
3269 sysvals.setOutputFile()
3270 vprint('Output file: %s' % sysvals.htmlfile)
3271 print('PROCESSING DATA')
3272 if(sysvals.usetraceeventsonly):
3273 testruns = parseTraceLog()
3274 else:
3275 testruns = loadKernelLog()
3276 for data in testruns:
3277 parseKernelLog(data)
3278 if(sysvals.ftracefile != ''):
3279 appendIncompleteTraceLog(testruns)
3280 createHTML(testruns)
3282 # Function: runTest
3283 # Description:
3284 # execute a suspend/resume, gather the logs, and generate the output
3285 def runTest(subdir):
3286 global sysvals
3288 # prepare for the test
3289 if(not sysvals.android):
3290 initFtrace()
3291 else:
3292 initFtraceAndroid()
3293 sysvals.initTestOutput(subdir)
3295 vprint('Output files:\n %s' % sysvals.dmesgfile)
3296 if(sysvals.usecallgraph or
3297 sysvals.usetraceevents or
3298 sysvals.usetraceeventsonly):
3299 vprint(' %s' % sysvals.ftracefile)
3300 vprint(' %s' % sysvals.htmlfile)
3302 # execute the test
3303 if(not sysvals.android):
3304 executeSuspend()
3305 else:
3306 executeAndroidSuspend()
3308 # analyze the data and create the html output
3309 print('PROCESSING DATA')
3310 if(sysvals.usetraceeventsonly):
3311 # data for kernels 3.15 or newer is entirely in ftrace
3312 testruns = parseTraceLog()
3313 else:
3314 # data for kernels older than 3.15 is primarily in dmesg
3315 testruns = loadKernelLog()
3316 for data in testruns:
3317 parseKernelLog(data)
3318 if(sysvals.usecallgraph or sysvals.usetraceevents):
3319 appendIncompleteTraceLog(testruns)
3320 createHTML(testruns)
3322 # Function: runSummary
3323 # Description:
3324 # create a summary of tests in a sub-directory
3325 def runSummary(subdir, output):
3326 global sysvals
3328 # get a list of ftrace output files
3329 files = []
3330 for dirname, dirnames, filenames in os.walk(subdir):
3331 for filename in filenames:
3332 if(re.match('.*_ftrace.txt', filename)):
3333 files.append("%s/%s" % (dirname, filename))
3335 # process the files in order and get an array of data objects
3336 testruns = []
3337 for file in sorted(files):
3338 if output:
3339 print("Test found in %s" % os.path.dirname(file))
3340 sysvals.ftracefile = file
3341 sysvals.dmesgfile = file.replace('_ftrace.txt', '_dmesg.txt')
3342 doesTraceLogHaveTraceEvents()
3343 sysvals.usecallgraph = False
3344 if not sysvals.usetraceeventsonly:
3345 if(not os.path.exists(sysvals.dmesgfile)):
3346 print("Skipping %s: not a valid test input" % file)
3347 continue
3348 else:
3349 if output:
3350 f = os.path.basename(sysvals.ftracefile)
3351 d = os.path.basename(sysvals.dmesgfile)
3352 print("\tInput files: %s and %s" % (f, d))
3353 testdata = loadKernelLog()
3354 data = testdata[0]
3355 parseKernelLog(data)
3356 testdata = [data]
3357 appendIncompleteTraceLog(testdata)
3358 else:
3359 if output:
3360 print("\tInput file: %s" % os.path.basename(sysvals.ftracefile))
3361 testdata = parseTraceLog()
3362 data = testdata[0]
3363 data.normalizeTime(data.tSuspended)
3364 link = file.replace(subdir+'/', '').replace('_ftrace.txt', '.html')
3365 data.outfile = link
3366 testruns.append(data)
3368 createHTMLSummarySimple(testruns, subdir+'/summary.html')
3370 # Function: printHelp
3371 # Description:
3372 # print out the help text
3373 def printHelp():
3374 global sysvals
3375 modes = getModes()
3377 print('')
3378 print('AnalyzeSuspend v%.1f' % sysvals.version)
3379 print('Usage: sudo analyze_suspend.py <options>')
3380 print('')
3381 print('Description:')
3382 print(' This tool is designed to assist kernel and OS developers in optimizing')
3383 print(' their linux stack\'s suspend/resume time. Using a kernel image built')
3384 print(' with a few extra options enabled, the tool will execute a suspend and')
3385 print(' capture dmesg and ftrace data until resume is complete. This data is')
3386 print(' transformed into a device timeline and an optional callgraph to give')
3387 print(' a detailed view of which devices/subsystems are taking the most')
3388 print(' time in suspend/resume.')
3389 print('')
3390 print(' Generates output files in subdirectory: suspend-mmddyy-HHMMSS')
3391 print(' HTML output: <hostname>_<mode>.html')
3392 print(' raw dmesg output: <hostname>_<mode>_dmesg.txt')
3393 print(' raw ftrace output: <hostname>_<mode>_ftrace.txt')
3394 print('')
3395 print('Options:')
3396 print(' [general]')
3397 print(' -h Print this help text')
3398 print(' -v Print the current tool version')
3399 print(' -verbose Print extra information during execution and analysis')
3400 print(' -status Test to see if the system is enabled to run this tool')
3401 print(' -modes List available suspend modes')
3402 print(' -m mode Mode to initiate for suspend %s (default: %s)') % (modes, sysvals.suspendmode)
3403 print(' -rtcwake t Use rtcwake to autoresume after <t> seconds (default: disabled)')
3404 print(' [advanced]')
3405 print(' -f Use ftrace to create device callgraphs (default: disabled)')
3406 print(' -filter "d1 d2 ..." Filter out all but this list of dev names')
3407 print(' -x2 Run two suspend/resumes back to back (default: disabled)')
3408 print(' -x2delay t Minimum millisecond delay <t> between the two test runs (default: 0 ms)')
3409 print(' -postres t Time after resume completion to wait for post-resume events (default: 0 S)')
3410 print(' -multi n d Execute <n> consecutive tests at <d> seconds intervals. The outputs will')
3411 print(' be created in a new subdirectory with a summary page.')
3412 print(' [utilities]')
3413 print(' -fpdt Print out the contents of the ACPI Firmware Performance Data Table')
3414 print(' -usbtopo Print out the current USB topology with power info')
3415 print(' -usbauto Enable autosuspend for all connected USB devices')
3416 print(' [android testing]')
3417 print(' -adb binary Use the given adb binary to run the test on an android device.')
3418 print(' The device should already be connected and with root access.')
3419 print(' Commands will be executed on the device using "adb shell"')
3420 print(' [re-analyze data from previous runs]')
3421 print(' -ftrace ftracefile Create HTML output using ftrace input')
3422 print(' -dmesg dmesgfile Create HTML output using dmesg (not needed for kernel >= 3.15)')
3423 print(' -summary directory Create a summary of all test in this dir')
3424 print('')
3425 return True
3427 # ----------------- MAIN --------------------
3428 # exec start (skipped if script is loaded as library)
3429 if __name__ == '__main__':
3430 cmd = ''
3431 cmdarg = ''
3432 multitest = {'run': False, 'count': 0, 'delay': 0}
3433 # loop through the command line arguments
3434 args = iter(sys.argv[1:])
3435 for arg in args:
3436 if(arg == '-m'):
3437 try:
3438 val = args.next()
3439 except:
3440 doError('No mode supplied', True)
3441 sysvals.suspendmode = val
3442 elif(arg == '-adb'):
3443 try:
3444 val = args.next()
3445 except:
3446 doError('No adb binary supplied', True)
3447 if(not os.path.exists(val)):
3448 doError('file doesnt exist: %s' % val, False)
3449 if(not os.access(val, os.X_OK)):
3450 doError('file isnt executable: %s' % val, False)
3451 try:
3452 check = os.popen(val+' version').read().strip()
3453 except:
3454 doError('adb version failed to execute', False)
3455 if(not re.match('Android Debug Bridge .*', check)):
3456 doError('adb version failed to execute', False)
3457 sysvals.adb = val
3458 sysvals.android = True
3459 elif(arg == '-x2'):
3460 if(sysvals.postresumetime > 0):
3461 doError('-x2 is not compatible with -postres', False)
3462 sysvals.execcount = 2
3463 elif(arg == '-x2delay'):
3464 sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
3465 elif(arg == '-postres'):
3466 if(sysvals.execcount != 1):
3467 doError('-x2 is not compatible with -postres', False)
3468 sysvals.postresumetime = getArgInt('-postres', args, 0, 3600)
3469 elif(arg == '-f'):
3470 sysvals.usecallgraph = True
3471 elif(arg == '-modes'):
3472 cmd = 'modes'
3473 elif(arg == '-fpdt'):
3474 cmd = 'fpdt'
3475 elif(arg == '-usbtopo'):
3476 cmd = 'usbtopo'
3477 elif(arg == '-usbauto'):
3478 cmd = 'usbauto'
3479 elif(arg == '-status'):
3480 cmd = 'status'
3481 elif(arg == '-verbose'):
3482 sysvals.verbose = True
3483 elif(arg == '-v'):
3484 print("Version %.1f" % sysvals.version)
3485 sys.exit()
3486 elif(arg == '-rtcwake'):
3487 sysvals.rtcwake = True
3488 sysvals.rtcwaketime = getArgInt('-rtcwake', args, 0, 3600)
3489 elif(arg == '-multi'):
3490 multitest['run'] = True
3491 multitest['count'] = getArgInt('-multi n (exec count)', args, 2, 1000000)
3492 multitest['delay'] = getArgInt('-multi d (delay between tests)', args, 0, 3600)
3493 elif(arg == '-dmesg'):
3494 try:
3495 val = args.next()
3496 except:
3497 doError('No dmesg file supplied', True)
3498 sysvals.notestrun = True
3499 sysvals.dmesgfile = val
3500 if(os.path.exists(sysvals.dmesgfile) == False):
3501 doError('%s doesnt exist' % sysvals.dmesgfile, False)
3502 elif(arg == '-ftrace'):
3503 try:
3504 val = args.next()
3505 except:
3506 doError('No ftrace file supplied', True)
3507 sysvals.notestrun = True
3508 sysvals.usecallgraph = True
3509 sysvals.ftracefile = val
3510 if(os.path.exists(sysvals.ftracefile) == False):
3511 doError('%s doesnt exist' % sysvals.ftracefile, False)
3512 elif(arg == '-summary'):
3513 try:
3514 val = args.next()
3515 except:
3516 doError('No directory supplied', True)
3517 cmd = 'summary'
3518 cmdarg = val
3519 sysvals.notestrun = True
3520 if(os.path.isdir(val) == False):
3521 doError('%s isnt accesible' % val, False)
3522 elif(arg == '-filter'):
3523 try:
3524 val = args.next()
3525 except:
3526 doError('No devnames supplied', True)
3527 sysvals.setDeviceFilter(val)
3528 elif(arg == '-h'):
3529 printHelp()
3530 sys.exit()
3531 else:
3532 doError('Invalid argument: '+arg, True)
3534 # just run a utility command and exit
3535 if(cmd != ''):
3536 if(cmd == 'status'):
3537 statusCheck()
3538 elif(cmd == 'fpdt'):
3539 if(sysvals.android):
3540 doError('cannot read FPDT on android device', False)
3541 getFPDT(True)
3542 elif(cmd == 'usbtopo'):
3543 if(sysvals.android):
3544 doError('cannot read USB topology '+\
3545 'on an android device', False)
3546 detectUSB(True)
3547 elif(cmd == 'modes'):
3548 modes = getModes()
3549 print modes
3550 elif(cmd == 'usbauto'):
3551 setUSBDevicesAuto()
3552 elif(cmd == 'summary'):
3553 print("Generating a summary of folder \"%s\"" % cmdarg)
3554 runSummary(cmdarg, True)
3555 sys.exit()
3557 # run test on android device
3558 if(sysvals.android):
3559 if(sysvals.usecallgraph):
3560 doError('ftrace (-f) is not yet supported '+\
3561 'in the android kernel', False)
3562 if(sysvals.notestrun):
3563 doError('cannot analyze test files on the '+\
3564 'android device', False)
3566 # if instructed, re-analyze existing data files
3567 if(sysvals.notestrun):
3568 rerunTest()
3569 sys.exit()
3571 # verify that we can run a test
3572 if(not statusCheck()):
3573 print('Check FAILED, aborting the test run!')
3574 sys.exit()
3576 if multitest['run']:
3577 # run multiple tests in a separte subdirectory
3578 s = 'x%d' % multitest['count']
3579 subdir = datetime.now().strftime('suspend-'+s+'-%m%d%y-%H%M%S')
3580 os.mkdir(subdir)
3581 for i in range(multitest['count']):
3582 if(i != 0):
3583 print('Waiting %d seconds...' % (multitest['delay']))
3584 time.sleep(multitest['delay'])
3585 print('TEST (%d/%d) START' % (i+1, multitest['count']))
3586 runTest(subdir)
3587 print('TEST (%d/%d) COMPLETE' % (i+1, multitest['count']))
3588 runSummary(subdir, False)
3589 else:
3590 # run the test in the current directory
3591 runTest(".")