Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/power/pm-graph/sleepgraph.py
26285 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0-only
3
#
4
# Tool for analyzing suspend/resume timing
5
# Copyright (c) 2013, Intel Corporation.
6
#
7
# This program is free software; you can redistribute it and/or modify it
8
# under the terms and conditions of the GNU General Public License,
9
# version 2, as published by the Free Software Foundation.
10
#
11
# This program is distributed in the hope it will be useful, but WITHOUT
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14
# more details.
15
#
16
# Authors:
17
# Todd Brandt <[email protected]>
18
#
19
# Links:
20
# Home Page
21
# https://www.intel.com/content/www/us/en/developer/topic-technology/open/pm-graph/overview.html
22
# Source repo
23
# [email protected]:intel/pm-graph
24
#
25
# Description:
26
# This tool is designed to assist kernel and OS developers in optimizing
27
# their linux stack's suspend/resume time. Using a kernel image built
28
# with a few extra options enabled, the tool will execute a suspend and
29
# will capture dmesg and ftrace data until resume is complete. This data
30
# is transformed into a device timeline and a callgraph to give a quick
31
# and detailed view of which devices and callbacks are taking the most
32
# time in suspend/resume. The output is a single html file which can be
33
# viewed in firefox or chrome.
34
#
35
# The following kernel build options are required:
36
# CONFIG_DEVMEM=y
37
# CONFIG_PM_DEBUG=y
38
# CONFIG_PM_SLEEP_DEBUG=y
39
# CONFIG_FTRACE=y
40
# CONFIG_FUNCTION_TRACER=y
41
# CONFIG_FUNCTION_GRAPH_TRACER=y
42
# CONFIG_KPROBES=y
43
# CONFIG_KPROBES_ON_FTRACE=y
44
#
45
# For kernel versions older than 3.15:
46
# The following additional kernel parameters are required:
47
# (e.g. in file /etc/default/grub)
48
# GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
49
#
50
51
# ----------------- LIBRARIES --------------------
52
53
import sys
54
import time
55
import os
56
import string
57
import re
58
import platform
59
import signal
60
import codecs
61
from datetime import datetime, timedelta
62
import struct
63
import configparser
64
import gzip
65
from threading import Thread
66
from subprocess import call, Popen, PIPE
67
import base64
68
import traceback
69
70
debugtiming = False
71
mystarttime = time.time()
72
def pprint(msg):
73
if debugtiming:
74
print('[%09.3f] %s' % (time.time()-mystarttime, msg))
75
else:
76
print(msg)
77
sys.stdout.flush()
78
79
def ascii(text):
80
return text.decode('ascii', 'ignore')
81
82
# ----------------- CLASSES --------------------
83
84
# Class: SystemValues
85
# Description:
86
# A global, single-instance container used to
87
# store system values and test parameters
88
class SystemValues:
89
title = 'SleepGraph'
90
version = '5.13'
91
ansi = False
92
rs = 0
93
display = ''
94
gzip = False
95
sync = False
96
wifi = False
97
netfix = False
98
verbose = False
99
testlog = True
100
dmesglog = True
101
ftracelog = False
102
acpidebug = True
103
tstat = True
104
wifitrace = False
105
mindevlen = 0.0001
106
mincglen = 0.0
107
cgphase = ''
108
cgtest = -1
109
cgskip = ''
110
maxfail = 0
111
multitest = {'run': False, 'count': 1000000, 'delay': 0}
112
max_graph_depth = 0
113
callloopmaxgap = 0.0001
114
callloopmaxlen = 0.005
115
bufsize = 0
116
cpucount = 0
117
memtotal = 204800
118
memfree = 204800
119
osversion = ''
120
srgap = 0
121
cgexp = False
122
testdir = ''
123
outdir = ''
124
tpath = '/sys/kernel/tracing/'
125
fpdtpath = '/sys/firmware/acpi/tables/FPDT'
126
epath = '/sys/kernel/tracing/events/power/'
127
pmdpath = '/sys/power/pm_debug_messages'
128
s0ixpath = '/sys/module/intel_pmc_core/parameters/warn_on_s0ix_failures'
129
s0ixres = '/sys/devices/system/cpu/cpuidle/low_power_idle_system_residency_us'
130
acpipath='/sys/module/acpi/parameters/debug_level'
131
traceevents = [
132
'suspend_resume',
133
'wakeup_source_activate',
134
'wakeup_source_deactivate',
135
'device_pm_callback_end',
136
'device_pm_callback_start'
137
]
138
logmsg = ''
139
testcommand = ''
140
mempath = '/dev/mem'
141
powerfile = '/sys/power/state'
142
mempowerfile = '/sys/power/mem_sleep'
143
diskpowerfile = '/sys/power/disk'
144
suspendmode = 'mem'
145
memmode = ''
146
diskmode = ''
147
hostname = 'localhost'
148
prefix = 'test'
149
teststamp = ''
150
sysstamp = ''
151
dmesgstart = 0.0
152
dmesgfile = ''
153
ftracefile = ''
154
htmlfile = 'output.html'
155
result = ''
156
rtcwake = True
157
rtcwaketime = 15
158
rtcpath = ''
159
devicefilter = []
160
cgfilter = []
161
stamp = 0
162
execcount = 1
163
x2delay = 0
164
skiphtml = False
165
usecallgraph = False
166
ftopfunc = 'pm_suspend'
167
ftop = False
168
usetraceevents = False
169
usetracemarkers = True
170
useftrace = True
171
usekprobes = True
172
usedevsrc = False
173
useprocmon = False
174
notestrun = False
175
cgdump = False
176
devdump = False
177
mixedphaseheight = True
178
devprops = dict()
179
cfgdef = dict()
180
platinfo = []
181
predelay = 0
182
postdelay = 0
183
tmstart = 'SUSPEND START %Y%m%d-%H:%M:%S.%f'
184
tmend = 'RESUME COMPLETE %Y%m%d-%H:%M:%S.%f'
185
tracefuncs = {
186
'async_synchronize_full': {},
187
'sys_sync': {},
188
'ksys_sync': {},
189
'__pm_notifier_call_chain': {},
190
'pm_prepare_console': {},
191
'pm_notifier_call_chain': {},
192
'freeze_processes': {},
193
'freeze_kernel_threads': {},
194
'pm_restrict_gfp_mask': {},
195
'acpi_suspend_begin': {},
196
'acpi_hibernation_begin': {},
197
'acpi_hibernation_enter': {},
198
'acpi_hibernation_leave': {},
199
'acpi_pm_freeze': {},
200
'acpi_pm_thaw': {},
201
'acpi_s2idle_end': {},
202
'acpi_s2idle_sync': {},
203
'acpi_s2idle_begin': {},
204
'acpi_s2idle_prepare': {},
205
'acpi_s2idle_prepare_late': {},
206
'acpi_s2idle_wake': {},
207
'acpi_s2idle_wakeup': {},
208
'acpi_s2idle_restore': {},
209
'acpi_s2idle_restore_early': {},
210
'hibernate_preallocate_memory': {},
211
'create_basic_memory_bitmaps': {},
212
'swsusp_write': {},
213
'console_suspend_all': {},
214
'acpi_pm_prepare': {},
215
'syscore_suspend': {},
216
'arch_enable_nonboot_cpus_end': {},
217
'syscore_resume': {},
218
'acpi_pm_finish': {},
219
'console_resume_all': {},
220
'acpi_pm_end': {},
221
'pm_restore_gfp_mask': {},
222
'thaw_processes': {},
223
'pm_restore_console': {},
224
'CPU_OFF': {
225
'func':'_cpu_down',
226
'args_x86_64': {'cpu':'%di:s32'},
227
'format': 'CPU_OFF[{cpu}]'
228
},
229
'CPU_ON': {
230
'func':'_cpu_up',
231
'args_x86_64': {'cpu':'%di:s32'},
232
'format': 'CPU_ON[{cpu}]'
233
},
234
}
235
dev_tracefuncs = {
236
# general wait/delay/sleep
237
'msleep': { 'args_x86_64': {'time':'%di:s32'}, 'ub': 1 },
238
'schedule_timeout': { 'args_x86_64': {'timeout':'%di:s32'}, 'ub': 1 },
239
'udelay': { 'func':'__const_udelay', 'args_x86_64': {'loops':'%di:s32'}, 'ub': 1 },
240
'usleep_range': {
241
'func':'usleep_range_state',
242
'args_x86_64': {'min':'%di:s32', 'max':'%si:s32'},
243
'ub': 1
244
},
245
'mutex_lock_slowpath': { 'func':'__mutex_lock_slowpath', 'ub': 1 },
246
'acpi_os_stall': {'ub': 1},
247
'rt_mutex_slowlock': {'ub': 1},
248
# ACPI
249
'acpi_resume_power_resources': {},
250
'acpi_ps_execute_method': { 'args_x86_64': {
251
'fullpath':'+0(+40(%di)):string',
252
}},
253
# mei_me
254
'mei_reset': {},
255
# filesystem
256
'ext4_sync_fs': {},
257
# 80211
258
'ath10k_bmi_read_memory': { 'args_x86_64': {'length':'%cx:s32'} },
259
'ath10k_bmi_write_memory': { 'args_x86_64': {'length':'%cx:s32'} },
260
'ath10k_bmi_fast_download': { 'args_x86_64': {'length':'%cx:s32'} },
261
'iwlagn_mac_start': {},
262
'iwlagn_alloc_bcast_station': {},
263
'iwl_trans_pcie_start_hw': {},
264
'iwl_trans_pcie_start_fw': {},
265
'iwl_run_init_ucode': {},
266
'iwl_load_ucode_wait_alive': {},
267
'iwl_alive_start': {},
268
'iwlagn_mac_stop': {},
269
'iwlagn_mac_suspend': {},
270
'iwlagn_mac_resume': {},
271
'iwlagn_mac_add_interface': {},
272
'iwlagn_mac_remove_interface': {},
273
'iwlagn_mac_change_interface': {},
274
'iwlagn_mac_config': {},
275
'iwlagn_configure_filter': {},
276
'iwlagn_mac_hw_scan': {},
277
'iwlagn_bss_info_changed': {},
278
'iwlagn_mac_channel_switch': {},
279
'iwlagn_mac_flush': {},
280
# ATA
281
'ata_eh_recover': { 'args_x86_64': {'port':'+36(%di):s32'} },
282
# i915
283
'i915_gem_resume': {},
284
'i915_restore_state': {},
285
'intel_opregion_setup': {},
286
'g4x_pre_enable_dp': {},
287
'vlv_pre_enable_dp': {},
288
'chv_pre_enable_dp': {},
289
'g4x_enable_dp': {},
290
'vlv_enable_dp': {},
291
'intel_hpd_init': {},
292
'intel_opregion_register': {},
293
'intel_dp_detect': {},
294
'intel_hdmi_detect': {},
295
'intel_opregion_init': {},
296
'intel_fbdev_set_suspend': {},
297
}
298
infocmds = [
299
[0, 'sysinfo', 'uname', '-a'],
300
[0, 'cpuinfo', 'head', '-7', '/proc/cpuinfo'],
301
[0, 'kparams', 'cat', '/proc/cmdline'],
302
[0, 'mcelog', 'mcelog'],
303
[0, 'pcidevices', 'lspci', '-tv'],
304
[0, 'usbdevices', 'lsusb', '-tv'],
305
[0, 'acpidevices', 'sh', '-c', 'ls -l /sys/bus/acpi/devices/*/physical_node'],
306
[0, 's0ix_require', 'cat', '/sys/kernel/debug/pmc_core/substate_requirements'],
307
[0, 's0ix_debug', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_debug_status'],
308
[0, 'ethtool', 'ethtool', '{ethdev}'],
309
[1, 's0ix_residency', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_residency_usec'],
310
[1, 'interrupts', 'cat', '/proc/interrupts'],
311
[1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'],
312
[2, 'gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/*'],
313
[2, 'suspendstats', 'sh', '-c', 'grep -v invalid /sys/power/suspend_stats/*'],
314
[2, 'cpuidle', 'sh', '-c', 'grep -v invalid /sys/devices/system/cpu/cpu*/cpuidle/state*/s2idle/*'],
315
[2, 'battery', 'sh', '-c', 'grep -v invalid /sys/class/power_supply/*/*'],
316
[2, 'thermal', 'sh', '-c', 'grep . /sys/class/thermal/thermal_zone*/temp'],
317
]
318
cgblacklist = []
319
kprobes = dict()
320
timeformat = '%.3f'
321
cmdline = '%s %s' % \
322
(os.path.basename(sys.argv[0]), ' '.join(sys.argv[1:]))
323
sudouser = ''
324
def __init__(self):
325
self.archargs = 'args_'+platform.machine()
326
self.hostname = platform.node()
327
if(self.hostname == ''):
328
self.hostname = 'localhost'
329
rtc = "rtc0"
330
if os.path.exists('/dev/rtc'):
331
rtc = os.readlink('/dev/rtc')
332
rtc = '/sys/class/rtc/'+rtc
333
if os.path.exists(rtc) and os.path.exists(rtc+'/date') and \
334
os.path.exists(rtc+'/time') and os.path.exists(rtc+'/wakealarm'):
335
self.rtcpath = rtc
336
if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()):
337
self.ansi = True
338
self.testdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S')
339
if os.getuid() == 0 and 'SUDO_USER' in os.environ and \
340
os.environ['SUDO_USER']:
341
self.sudouser = os.environ['SUDO_USER']
342
def resetlog(self):
343
self.logmsg = ''
344
self.platinfo = []
345
def vprint(self, msg):
346
self.logmsg += msg+'\n'
347
if self.verbose or msg.startswith('WARNING:'):
348
pprint(msg)
349
def signalHandler(self, signum, frame):
350
signame = self.signames[signum] if signum in self.signames else 'UNKNOWN'
351
if signame in ['SIGUSR1', 'SIGUSR2', 'SIGSEGV']:
352
traceback.print_stack()
353
stack = traceback.format_list(traceback.extract_stack())
354
self.outputResult({'stack':stack})
355
if signame == 'SIGUSR1':
356
return
357
msg = '%s caused a tool exit, line %d' % (signame, frame.f_lineno)
358
pprint(msg)
359
self.outputResult({'error':msg})
360
os.kill(os.getpid(), signal.SIGKILL)
361
sys.exit(3)
362
def signalHandlerInit(self):
363
capture = ['BUS', 'SYS', 'XCPU', 'XFSZ', 'PWR', 'HUP', 'INT', 'QUIT',
364
'ILL', 'ABRT', 'FPE', 'SEGV', 'TERM', 'USR1', 'USR2']
365
self.signames = dict()
366
for i in capture:
367
s = 'SIG'+i
368
try:
369
signum = getattr(signal, s)
370
signal.signal(signum, self.signalHandler)
371
except:
372
continue
373
self.signames[signum] = s
374
def rootCheck(self, fatal=True):
375
if(os.access(self.powerfile, os.W_OK)):
376
return True
377
if fatal:
378
msg = 'This command requires sysfs mount and root access'
379
pprint('ERROR: %s\n' % msg)
380
self.outputResult({'error':msg})
381
sys.exit(1)
382
return False
383
def rootUser(self, fatal=False):
384
if 'USER' in os.environ and os.environ['USER'] == 'root':
385
return True
386
if fatal:
387
msg = 'This command must be run as root'
388
pprint('ERROR: %s\n' % msg)
389
self.outputResult({'error':msg})
390
sys.exit(1)
391
return False
392
def usable(self, file, ishtml=False):
393
if not os.path.exists(file) or os.path.getsize(file) < 1:
394
return False
395
if ishtml:
396
try:
397
fp = open(file, 'r')
398
res = fp.read(1000)
399
fp.close()
400
except:
401
return False
402
if '<html>' not in res:
403
return False
404
return True
405
def getExec(self, cmd):
406
try:
407
fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
408
out = ascii(fp.read()).strip()
409
fp.close()
410
except:
411
out = ''
412
if out:
413
return out
414
for path in ['/sbin', '/bin', '/usr/sbin', '/usr/bin',
415
'/usr/local/sbin', '/usr/local/bin']:
416
cmdfull = os.path.join(path, cmd)
417
if os.path.exists(cmdfull):
418
return cmdfull
419
return out
420
def setPrecision(self, num):
421
if num < 0 or num > 6:
422
return
423
self.timeformat = '%.{0}f'.format(num)
424
def setOutputFolder(self, value):
425
args = dict()
426
n = datetime.now()
427
args['date'] = n.strftime('%y%m%d')
428
args['time'] = n.strftime('%H%M%S')
429
args['hostname'] = args['host'] = self.hostname
430
args['mode'] = self.suspendmode
431
return value.format(**args)
432
def setOutputFile(self):
433
if self.dmesgfile != '':
434
m = re.match(r'(?P<name>.*)_dmesg\.txt.*', self.dmesgfile)
435
if(m):
436
self.htmlfile = m.group('name')+'.html'
437
if self.ftracefile != '':
438
m = re.match(r'(?P<name>.*)_ftrace\.txt.*', self.ftracefile)
439
if(m):
440
self.htmlfile = m.group('name')+'.html'
441
def systemInfo(self, info):
442
p = m = ''
443
if 'baseboard-manufacturer' in info:
444
m = info['baseboard-manufacturer']
445
elif 'system-manufacturer' in info:
446
m = info['system-manufacturer']
447
if 'system-product-name' in info:
448
p = info['system-product-name']
449
elif 'baseboard-product-name' in info:
450
p = info['baseboard-product-name']
451
if m[:5].lower() == 'intel' and 'baseboard-product-name' in info:
452
p = info['baseboard-product-name']
453
c = info['processor-version'] if 'processor-version' in info else ''
454
b = info['bios-version'] if 'bios-version' in info else ''
455
r = info['bios-release-date'] if 'bios-release-date' in info else ''
456
self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
457
(m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
458
if self.osversion:
459
self.sysstamp += ' | os:%s' % self.osversion
460
def printSystemInfo(self, fatal=False):
461
self.rootCheck(True)
462
out = dmidecode(self.mempath, fatal)
463
if len(out) < 1:
464
return
465
fmt = '%-24s: %s'
466
if self.osversion:
467
print(fmt % ('os-version', self.osversion))
468
for name in sorted(out):
469
print(fmt % (name, out[name]))
470
print(fmt % ('cpucount', ('%d' % self.cpucount)))
471
print(fmt % ('memtotal', ('%d kB' % self.memtotal)))
472
print(fmt % ('memfree', ('%d kB' % self.memfree)))
473
def cpuInfo(self):
474
self.cpucount = 0
475
if os.path.exists('/proc/cpuinfo'):
476
with open('/proc/cpuinfo', 'r') as fp:
477
for line in fp:
478
if re.match(r'^processor[ \t]*:[ \t]*[0-9]*', line):
479
self.cpucount += 1
480
if os.path.exists('/proc/meminfo'):
481
with open('/proc/meminfo', 'r') as fp:
482
for line in fp:
483
m = re.match(r'^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
484
if m:
485
self.memtotal = int(m.group('sz'))
486
m = re.match(r'^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
487
if m:
488
self.memfree = int(m.group('sz'))
489
if os.path.exists('/etc/os-release'):
490
with open('/etc/os-release', 'r') as fp:
491
for line in fp:
492
if line.startswith('PRETTY_NAME='):
493
self.osversion = line[12:].strip().replace('"', '')
494
def initTestOutput(self, name):
495
self.prefix = self.hostname
496
v = open('/proc/version', 'r').read().strip()
497
kver = v.split()[2]
498
fmt = name+'-%m%d%y-%H%M%S'
499
testtime = datetime.now().strftime(fmt)
500
self.teststamp = \
501
'# '+testtime+' '+self.prefix+' '+self.suspendmode+' '+kver
502
ext = ''
503
if self.gzip:
504
ext = '.gz'
505
self.dmesgfile = \
506
self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_dmesg.txt'+ext
507
self.ftracefile = \
508
self.testdir+'/'+self.prefix+'_'+self.suspendmode+'_ftrace.txt'+ext
509
self.htmlfile = \
510
self.testdir+'/'+self.prefix+'_'+self.suspendmode+'.html'
511
if not os.path.isdir(self.testdir):
512
os.makedirs(self.testdir)
513
self.sudoUserchown(self.testdir)
514
def getValueList(self, value):
515
out = []
516
for i in value.split(','):
517
if i.strip():
518
out.append(i.strip())
519
return out
520
def setDeviceFilter(self, value):
521
self.devicefilter = self.getValueList(value)
522
def setCallgraphFilter(self, value):
523
self.cgfilter = self.getValueList(value)
524
def skipKprobes(self, value):
525
for k in self.getValueList(value):
526
if k in self.tracefuncs:
527
del self.tracefuncs[k]
528
if k in self.dev_tracefuncs:
529
del self.dev_tracefuncs[k]
530
def setCallgraphBlacklist(self, file):
531
self.cgblacklist = self.listFromFile(file)
532
def rtcWakeAlarmOn(self):
533
call('echo 0 > '+self.rtcpath+'/wakealarm', shell=True)
534
nowtime = open(self.rtcpath+'/since_epoch', 'r').read().strip()
535
if nowtime:
536
nowtime = int(nowtime)
537
else:
538
# if hardware time fails, use the software time
539
nowtime = int(datetime.now().strftime('%s'))
540
alarm = nowtime + self.rtcwaketime
541
call('echo %d > %s/wakealarm' % (alarm, self.rtcpath), shell=True)
542
def rtcWakeAlarmOff(self):
543
call('echo 0 > %s/wakealarm' % self.rtcpath, shell=True)
544
def initdmesg(self):
545
# get the latest time stamp from the dmesg log
546
lines = Popen('dmesg', stdout=PIPE).stdout.readlines()
547
ktime = '0'
548
for line in reversed(lines):
549
line = ascii(line).replace('\r\n', '')
550
idx = line.find('[')
551
if idx > 1:
552
line = line[idx:]
553
m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
554
if(m):
555
ktime = m.group('ktime')
556
break
557
self.dmesgstart = float(ktime)
558
def getdmesg(self, testdata):
559
op = self.writeDatafileHeader(self.dmesgfile, testdata)
560
# store all new dmesg lines since initdmesg was called
561
fp = Popen('dmesg', stdout=PIPE).stdout
562
for line in fp:
563
line = ascii(line).replace('\r\n', '')
564
idx = line.find('[')
565
if idx > 1:
566
line = line[idx:]
567
m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
568
if(not m):
569
continue
570
ktime = float(m.group('ktime'))
571
if ktime > self.dmesgstart:
572
op.write(line)
573
fp.close()
574
op.close()
575
def listFromFile(self, file):
576
list = []
577
fp = open(file)
578
for i in fp.read().split('\n'):
579
i = i.strip()
580
if i and i[0] != '#':
581
list.append(i)
582
fp.close()
583
return list
584
def addFtraceFilterFunctions(self, file):
585
for i in self.listFromFile(file):
586
if len(i) < 2:
587
continue
588
self.tracefuncs[i] = dict()
589
def getFtraceFilterFunctions(self, current):
590
self.rootCheck(True)
591
if not current:
592
call('cat '+self.tpath+'available_filter_functions', shell=True)
593
return
594
master = self.listFromFile(self.tpath+'available_filter_functions')
595
for i in sorted(self.tracefuncs):
596
if 'func' in self.tracefuncs[i]:
597
i = self.tracefuncs[i]['func']
598
if i in master:
599
print(i)
600
else:
601
print(self.colorText(i))
602
def setFtraceFilterFunctions(self, list):
603
master = self.listFromFile(self.tpath+'available_filter_functions')
604
flist = ''
605
for i in list:
606
if i not in master:
607
continue
608
if ' [' in i:
609
flist += i.split(' ')[0]+'\n'
610
else:
611
flist += i+'\n'
612
fp = open(self.tpath+'set_graph_function', 'w')
613
fp.write(flist)
614
fp.close()
615
def basicKprobe(self, name):
616
self.kprobes[name] = {'name': name,'func': name,'args': dict(),'format': name}
617
def defaultKprobe(self, name, kdata):
618
k = kdata
619
for field in ['name', 'format', 'func']:
620
if field not in k:
621
k[field] = name
622
if self.archargs in k:
623
k['args'] = k[self.archargs]
624
else:
625
k['args'] = dict()
626
k['format'] = name
627
self.kprobes[name] = k
628
def kprobeColor(self, name):
629
if name not in self.kprobes or 'color' not in self.kprobes[name]:
630
return ''
631
return self.kprobes[name]['color']
632
def kprobeDisplayName(self, name, dataraw):
633
if name not in self.kprobes:
634
self.basicKprobe(name)
635
data = ''
636
quote=0
637
# first remvoe any spaces inside quotes, and the quotes
638
for c in dataraw:
639
if c == '"':
640
quote = (quote + 1) % 2
641
if quote and c == ' ':
642
data += '_'
643
elif c != '"':
644
data += c
645
fmt, args = self.kprobes[name]['format'], self.kprobes[name]['args']
646
arglist = dict()
647
# now process the args
648
for arg in sorted(args):
649
arglist[arg] = ''
650
m = re.match(r'.* '+arg+'=(?P<arg>.*) ', data);
651
if m:
652
arglist[arg] = m.group('arg')
653
else:
654
m = re.match(r'.* '+arg+'=(?P<arg>.*)', data);
655
if m:
656
arglist[arg] = m.group('arg')
657
out = fmt.format(**arglist)
658
out = out.replace(' ', '_').replace('"', '')
659
return out
660
def kprobeText(self, kname, kprobe):
661
name = fmt = func = kname
662
args = dict()
663
if 'name' in kprobe:
664
name = kprobe['name']
665
if 'format' in kprobe:
666
fmt = kprobe['format']
667
if 'func' in kprobe:
668
func = kprobe['func']
669
if self.archargs in kprobe:
670
args = kprobe[self.archargs]
671
if 'args' in kprobe:
672
args = kprobe['args']
673
if re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', func):
674
doError('Kprobe "%s" has format info in the function name "%s"' % (name, func))
675
for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', fmt):
676
if arg not in args:
677
doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
678
val = 'p:%s_cal %s' % (name, func)
679
for i in sorted(args):
680
val += ' %s=%s' % (i, args[i])
681
val += '\nr:%s_ret %s $retval\n' % (name, func)
682
return val
683
def addKprobes(self, output=False):
684
if len(self.kprobes) < 1:
685
return
686
if output:
687
pprint(' kprobe functions in this kernel:')
688
# first test each kprobe
689
rejects = []
690
# sort kprobes: trace, ub-dev, custom, dev
691
kpl = [[], [], [], []]
692
linesout = len(self.kprobes)
693
for name in sorted(self.kprobes):
694
res = self.colorText('YES', 32)
695
if not self.testKprobe(name, self.kprobes[name]):
696
res = self.colorText('NO')
697
rejects.append(name)
698
else:
699
if name in self.tracefuncs:
700
kpl[0].append(name)
701
elif name in self.dev_tracefuncs:
702
if 'ub' in self.dev_tracefuncs[name]:
703
kpl[1].append(name)
704
else:
705
kpl[3].append(name)
706
else:
707
kpl[2].append(name)
708
if output:
709
pprint(' %s: %s' % (name, res))
710
kplist = kpl[0] + kpl[1] + kpl[2] + kpl[3]
711
# remove all failed ones from the list
712
for name in rejects:
713
self.kprobes.pop(name)
714
# set the kprobes all at once
715
self.fsetVal('', 'kprobe_events')
716
kprobeevents = ''
717
for kp in kplist:
718
kprobeevents += self.kprobeText(kp, self.kprobes[kp])
719
self.fsetVal(kprobeevents, 'kprobe_events')
720
if output:
721
check = self.fgetVal('kprobe_events')
722
linesack = (len(check.split('\n')) - 1) // 2
723
pprint(' kprobe functions enabled: %d/%d' % (linesack, linesout))
724
self.fsetVal('1', 'events/kprobes/enable')
725
def testKprobe(self, kname, kprobe):
726
self.fsetVal('0', 'events/kprobes/enable')
727
kprobeevents = self.kprobeText(kname, kprobe)
728
if not kprobeevents:
729
return False
730
try:
731
self.fsetVal(kprobeevents, 'kprobe_events')
732
check = self.fgetVal('kprobe_events')
733
except:
734
return False
735
linesout = len(kprobeevents.split('\n'))
736
linesack = len(check.split('\n'))
737
if linesack < linesout:
738
return False
739
return True
740
def setVal(self, val, file):
741
if not os.path.exists(file):
742
return False
743
try:
744
fp = open(file, 'wb', 0)
745
fp.write(val.encode())
746
fp.flush()
747
fp.close()
748
except:
749
return False
750
return True
751
def fsetVal(self, val, path):
752
if not self.useftrace:
753
return False
754
return self.setVal(val, self.tpath+path)
755
def getVal(self, file):
756
res = ''
757
if not os.path.exists(file):
758
return res
759
try:
760
fp = open(file, 'r')
761
res = fp.read()
762
fp.close()
763
except:
764
pass
765
return res
766
def fgetVal(self, path):
767
if not self.useftrace:
768
return ''
769
return self.getVal(self.tpath+path)
770
def cleanupFtrace(self):
771
if self.useftrace:
772
self.fsetVal('0', 'events/kprobes/enable')
773
self.fsetVal('', 'kprobe_events')
774
self.fsetVal('1024', 'buffer_size_kb')
775
def setupAllKprobes(self):
776
for name in self.tracefuncs:
777
self.defaultKprobe(name, self.tracefuncs[name])
778
for name in self.dev_tracefuncs:
779
self.defaultKprobe(name, self.dev_tracefuncs[name])
780
def isCallgraphFunc(self, name):
781
if len(self.tracefuncs) < 1 and self.suspendmode == 'command':
782
return True
783
for i in self.tracefuncs:
784
if 'func' in self.tracefuncs[i]:
785
f = self.tracefuncs[i]['func']
786
else:
787
f = i
788
if name == f:
789
return True
790
return False
791
def initFtrace(self, quiet=False):
792
if not self.useftrace:
793
return
794
if not quiet:
795
sysvals.printSystemInfo(False)
796
pprint('INITIALIZING FTRACE')
797
# turn trace off
798
self.fsetVal('0', 'tracing_on')
799
self.cleanupFtrace()
800
# set the trace clock to global
801
self.fsetVal('global', 'trace_clock')
802
self.fsetVal('nop', 'current_tracer')
803
# set trace buffer to an appropriate value
804
cpus = max(1, self.cpucount)
805
if self.bufsize > 0:
806
tgtsize = self.bufsize
807
elif self.usecallgraph or self.usedevsrc:
808
bmax = (1*1024*1024) if self.suspendmode in ['disk', 'command'] \
809
else (3*1024*1024)
810
tgtsize = min(self.memfree, bmax)
811
else:
812
tgtsize = 65536
813
while not self.fsetVal('%d' % (tgtsize // cpus), 'buffer_size_kb'):
814
# if the size failed to set, lower it and keep trying
815
tgtsize -= 65536
816
if tgtsize < 65536:
817
tgtsize = int(self.fgetVal('buffer_size_kb')) * cpus
818
break
819
self.vprint('Setting trace buffers to %d kB (%d kB per cpu)' % (tgtsize, tgtsize/cpus))
820
# initialize the callgraph trace
821
if(self.usecallgraph):
822
# set trace type
823
self.fsetVal('function_graph', 'current_tracer')
824
self.fsetVal('', 'set_ftrace_filter')
825
# temporary hack to fix https://bugzilla.kernel.org/show_bug.cgi?id=212761
826
fp = open(self.tpath+'set_ftrace_notrace', 'w')
827
fp.write('native_queued_spin_lock_slowpath\ndev_driver_string')
828
fp.close()
829
# set trace format options
830
self.fsetVal('print-parent', 'trace_options')
831
self.fsetVal('funcgraph-abstime', 'trace_options')
832
self.fsetVal('funcgraph-cpu', 'trace_options')
833
self.fsetVal('funcgraph-duration', 'trace_options')
834
self.fsetVal('funcgraph-proc', 'trace_options')
835
self.fsetVal('funcgraph-tail', 'trace_options')
836
self.fsetVal('nofuncgraph-overhead', 'trace_options')
837
self.fsetVal('context-info', 'trace_options')
838
self.fsetVal('graph-time', 'trace_options')
839
self.fsetVal('%d' % self.max_graph_depth, 'max_graph_depth')
840
cf = ['dpm_run_callback']
841
if(self.usetraceevents):
842
cf += ['dpm_prepare', 'dpm_complete']
843
for fn in self.tracefuncs:
844
if 'func' in self.tracefuncs[fn]:
845
cf.append(self.tracefuncs[fn]['func'])
846
else:
847
cf.append(fn)
848
if self.ftop:
849
self.setFtraceFilterFunctions([self.ftopfunc])
850
else:
851
self.setFtraceFilterFunctions(cf)
852
# initialize the kprobe trace
853
elif self.usekprobes:
854
for name in self.tracefuncs:
855
self.defaultKprobe(name, self.tracefuncs[name])
856
if self.usedevsrc:
857
for name in self.dev_tracefuncs:
858
self.defaultKprobe(name, self.dev_tracefuncs[name])
859
if not quiet:
860
pprint('INITIALIZING KPROBES')
861
self.addKprobes(self.verbose)
862
if(self.usetraceevents):
863
# turn trace events on
864
events = iter(self.traceevents)
865
for e in events:
866
self.fsetVal('1', 'events/power/'+e+'/enable')
867
# clear the trace buffer
868
self.fsetVal('', 'trace')
869
def verifyFtrace(self):
870
# files needed for any trace data
871
files = ['buffer_size_kb', 'current_tracer', 'trace', 'trace_clock',
872
'trace_marker', 'trace_options', 'tracing_on']
873
# legacy check for old systems
874
if not os.path.exists(self.tpath+'trace'):
875
self.tpath = '/sys/kernel/debug/tracing/'
876
if not os.path.exists(self.epath):
877
self.epath = '/sys/kernel/debug/tracing/events/power/'
878
# files needed for callgraph trace data
879
tp = self.tpath
880
if(self.usecallgraph):
881
files += [
882
'available_filter_functions',
883
'set_ftrace_filter',
884
'set_graph_function'
885
]
886
for f in files:
887
if(os.path.exists(tp+f) == False):
888
return False
889
return True
890
def verifyKprobes(self):
891
# files needed for kprobes to work
892
files = ['kprobe_events', 'events']
893
tp = self.tpath
894
for f in files:
895
if(os.path.exists(tp+f) == False):
896
return False
897
return True
898
def colorText(self, str, color=31):
899
if not self.ansi:
900
return str
901
return '\x1B[%d;40m%s\x1B[m' % (color, str)
902
def writeDatafileHeader(self, filename, testdata):
903
fp = self.openlog(filename, 'w')
904
fp.write('%s\n%s\n# command | %s\n' % (self.teststamp, self.sysstamp, self.cmdline))
905
for test in testdata:
906
if 'fw' in test:
907
fw = test['fw']
908
if(fw):
909
fp.write('# fwsuspend %u fwresume %u\n' % (fw[0], fw[1]))
910
if 'turbo' in test:
911
fp.write('# turbostat %s\n' % test['turbo'])
912
if 'wifi' in test:
913
fp.write('# wifi %s\n' % test['wifi'])
914
if 'netfix' in test:
915
fp.write('# netfix %s\n' % test['netfix'])
916
if test['error'] or len(testdata) > 1:
917
fp.write('# enter_sleep_error %s\n' % test['error'])
918
return fp
919
def sudoUserchown(self, dir):
920
if os.path.exists(dir) and self.sudouser:
921
cmd = 'chown -R {0}:{0} {1} > /dev/null 2>&1'
922
call(cmd.format(self.sudouser, dir), shell=True)
923
def outputResult(self, testdata, num=0):
924
if not self.result:
925
return
926
n = ''
927
if num > 0:
928
n = '%d' % num
929
fp = open(self.result, 'a')
930
if 'stack' in testdata:
931
fp.write('Printing stack trace:\n')
932
for line in testdata['stack']:
933
fp.write(line)
934
fp.close()
935
self.sudoUserchown(self.result)
936
return
937
if 'error' in testdata:
938
fp.write('result%s: fail\n' % n)
939
fp.write('error%s: %s\n' % (n, testdata['error']))
940
else:
941
fp.write('result%s: pass\n' % n)
942
if 'mode' in testdata:
943
fp.write('mode%s: %s\n' % (n, testdata['mode']))
944
for v in ['suspend', 'resume', 'boot', 'lastinit']:
945
if v in testdata:
946
fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
947
for v in ['fwsuspend', 'fwresume']:
948
if v in testdata:
949
fp.write('%s%s: %.3f\n' % (v, n, testdata[v] / 1000000.0))
950
if 'bugurl' in testdata:
951
fp.write('url%s: %s\n' % (n, testdata['bugurl']))
952
fp.close()
953
self.sudoUserchown(self.result)
954
def configFile(self, file):
955
dir = os.path.dirname(os.path.realpath(__file__))
956
if os.path.exists(file):
957
return file
958
elif os.path.exists(dir+'/'+file):
959
return dir+'/'+file
960
elif os.path.exists(dir+'/config/'+file):
961
return dir+'/config/'+file
962
return ''
963
def openlog(self, filename, mode):
964
isgz = self.gzip
965
if mode == 'r':
966
try:
967
with gzip.open(filename, mode+'t') as fp:
968
test = fp.read(64)
969
isgz = True
970
except:
971
isgz = False
972
if isgz:
973
return gzip.open(filename, mode+'t')
974
return open(filename, mode)
975
def putlog(self, filename, text):
976
with self.openlog(filename, 'a') as fp:
977
fp.write(text)
978
fp.close()
979
def dlog(self, text):
980
if not self.dmesgfile:
981
return
982
self.putlog(self.dmesgfile, '# %s\n' % text)
983
def flog(self, text):
984
self.putlog(self.ftracefile, text)
985
def b64unzip(self, data):
986
try:
987
out = codecs.decode(base64.b64decode(data), 'zlib').decode()
988
except:
989
out = data
990
return out
991
def b64zip(self, data):
992
out = base64.b64encode(codecs.encode(data.encode(), 'zlib')).decode()
993
return out
994
def platforminfo(self, cmdafter):
995
# add platform info on to a completed ftrace file
996
if not os.path.exists(self.ftracefile):
997
return False
998
footer = '#\n'
999
1000
# add test command string line if need be
1001
if self.suspendmode == 'command' and self.testcommand:
1002
footer += '# platform-testcmd: %s\n' % (self.testcommand)
1003
1004
# get a list of target devices from the ftrace file
1005
props = dict()
1006
tp = TestProps()
1007
tf = self.openlog(self.ftracefile, 'r')
1008
for line in tf:
1009
if tp.stampInfo(line, self):
1010
continue
1011
# parse only valid lines, if this is not one move on
1012
m = re.match(tp.ftrace_line_fmt, line)
1013
if(not m or 'device_pm_callback_start' not in line):
1014
continue
1015
m = re.match(r'.*: (?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*', m.group('msg'));
1016
if(not m):
1017
continue
1018
dev = m.group('d')
1019
if dev not in props:
1020
props[dev] = DevProps()
1021
tf.close()
1022
1023
# now get the syspath for each target device
1024
for dirname, dirnames, filenames in os.walk('/sys/devices'):
1025
if(re.match(r'.*/power', dirname) and 'async' in filenames):
1026
dev = dirname.split('/')[-2]
1027
if dev in props and (not props[dev].syspath or len(dirname) < len(props[dev].syspath)):
1028
props[dev].syspath = dirname[:-6]
1029
1030
# now fill in the properties for our target devices
1031
for dev in sorted(props):
1032
dirname = props[dev].syspath
1033
if not dirname or not os.path.exists(dirname):
1034
continue
1035
props[dev].isasync = False
1036
if os.path.exists(dirname+'/power/async'):
1037
fp = open(dirname+'/power/async')
1038
if 'enabled' in fp.read():
1039
props[dev].isasync = True
1040
fp.close()
1041
fields = os.listdir(dirname)
1042
for file in ['product', 'name', 'model', 'description', 'id', 'idVendor']:
1043
if file not in fields:
1044
continue
1045
try:
1046
with open(os.path.join(dirname, file), 'rb') as fp:
1047
props[dev].altname = ascii(fp.read())
1048
except:
1049
continue
1050
if file == 'idVendor':
1051
idv, idp = props[dev].altname.strip(), ''
1052
try:
1053
with open(os.path.join(dirname, 'idProduct'), 'rb') as fp:
1054
idp = ascii(fp.read()).strip()
1055
except:
1056
props[dev].altname = ''
1057
break
1058
props[dev].altname = '%s:%s' % (idv, idp)
1059
break
1060
if props[dev].altname:
1061
out = props[dev].altname.strip().replace('\n', ' ')\
1062
.replace(',', ' ').replace(';', ' ')
1063
props[dev].altname = out
1064
1065
# add a devinfo line to the bottom of ftrace
1066
out = ''
1067
for dev in sorted(props):
1068
out += props[dev].out(dev)
1069
footer += '# platform-devinfo: %s\n' % self.b64zip(out)
1070
1071
# add a line for each of these commands with their outputs
1072
for name, cmdline, info in cmdafter:
1073
footer += '# platform-%s: %s | %s\n' % (name, cmdline, self.b64zip(info))
1074
self.flog(footer)
1075
return True
1076
def commonPrefix(self, list):
1077
if len(list) < 2:
1078
return ''
1079
prefix = list[0]
1080
for s in list[1:]:
1081
while s[:len(prefix)] != prefix and prefix:
1082
prefix = prefix[:len(prefix)-1]
1083
if not prefix:
1084
break
1085
if '/' in prefix and prefix[-1] != '/':
1086
prefix = prefix[0:prefix.rfind('/')+1]
1087
return prefix
1088
def dictify(self, text, format):
1089
out = dict()
1090
header = True if format == 1 else False
1091
delim = ' ' if format == 1 else ':'
1092
for line in text.split('\n'):
1093
if header:
1094
header, out['@'] = False, line
1095
continue
1096
line = line.strip()
1097
if delim in line:
1098
data = line.split(delim, 1)
1099
num = re.search(r'[\d]+', data[1])
1100
if format == 2 and num:
1101
out[data[0].strip()] = num.group()
1102
else:
1103
out[data[0].strip()] = data[1]
1104
return out
1105
def cmdinfovar(self, arg):
1106
if arg == 'ethdev':
1107
try:
1108
cmd = [self.getExec('ip'), '-4', '-o', '-br', 'addr']
1109
fp = Popen(cmd, stdout=PIPE, stderr=PIPE).stdout
1110
info = ascii(fp.read()).strip()
1111
fp.close()
1112
except:
1113
return 'iptoolcrash'
1114
for line in info.split('\n'):
1115
if line[0] == 'e' and 'UP' in line:
1116
return line.split()[0]
1117
return 'nodevicefound'
1118
return 'unknown'
1119
def cmdinfo(self, begin, debug=False):
1120
out = []
1121
if begin:
1122
self.cmd1 = dict()
1123
for cargs in self.infocmds:
1124
delta, name, args = cargs[0], cargs[1], cargs[2:]
1125
for i in range(len(args)):
1126
if args[i][0] == '{' and args[i][-1] == '}':
1127
args[i] = self.cmdinfovar(args[i][1:-1])
1128
cmdline, cmdpath = ' '.join(args[0:]), self.getExec(args[0])
1129
if not cmdpath or (begin and not delta):
1130
continue
1131
self.dlog('[%s]' % cmdline)
1132
try:
1133
fp = Popen([cmdpath]+args[1:], stdout=PIPE, stderr=PIPE).stdout
1134
info = ascii(fp.read()).strip()
1135
fp.close()
1136
except:
1137
continue
1138
if not debug and begin:
1139
self.cmd1[name] = self.dictify(info, delta)
1140
elif not debug and delta and name in self.cmd1:
1141
before, after = self.cmd1[name], self.dictify(info, delta)
1142
dinfo = ('\t%s\n' % before['@']) if '@' in before and len(before) > 1 else ''
1143
prefix = self.commonPrefix(list(before.keys()))
1144
for key in sorted(before):
1145
if key in after and before[key] != after[key]:
1146
title = key.replace(prefix, '')
1147
if delta == 2:
1148
dinfo += '\t%s : %s -> %s\n' % \
1149
(title, before[key].strip(), after[key].strip())
1150
else:
1151
dinfo += '%10s (start) : %s\n%10s (after) : %s\n' % \
1152
(title, before[key], title, after[key])
1153
dinfo = '\tnothing changed' if not dinfo else dinfo.rstrip()
1154
out.append((name, cmdline, dinfo))
1155
else:
1156
out.append((name, cmdline, '\tnothing' if not info else info))
1157
return out
1158
def testVal(self, file, fmt='basic', value=''):
1159
if file == 'restoreall':
1160
for f in self.cfgdef:
1161
if os.path.exists(f):
1162
fp = open(f, 'w')
1163
fp.write(self.cfgdef[f])
1164
fp.close()
1165
self.cfgdef = dict()
1166
elif value and os.path.exists(file):
1167
fp = open(file, 'r+')
1168
if fmt == 'radio':
1169
m = re.match(r'.*\[(?P<v>.*)\].*', fp.read())
1170
if m:
1171
self.cfgdef[file] = m.group('v')
1172
elif fmt == 'acpi':
1173
line = fp.read().strip().split('\n')[-1]
1174
m = re.match(r'.* (?P<v>[0-9A-Fx]*) .*', line)
1175
if m:
1176
self.cfgdef[file] = m.group('v')
1177
else:
1178
self.cfgdef[file] = fp.read().strip()
1179
fp.write(value)
1180
fp.close()
1181
def s0ixSupport(self):
1182
if not os.path.exists(self.s0ixres) or not os.path.exists(self.mempowerfile):
1183
return False
1184
fp = open(sysvals.mempowerfile, 'r')
1185
data = fp.read().strip()
1186
fp.close()
1187
if '[s2idle]' in data:
1188
return True
1189
return False
1190
def haveTurbostat(self):
1191
if not self.tstat:
1192
return False
1193
cmd = self.getExec('turbostat')
1194
if not cmd:
1195
return False
1196
fp = Popen([cmd, '-v'], stdout=PIPE, stderr=PIPE).stderr
1197
out = ascii(fp.read()).strip()
1198
fp.close()
1199
if re.match(r'turbostat version .*', out):
1200
self.vprint(out)
1201
return True
1202
return False
1203
def turbostat(self, s0ixready):
1204
cmd = self.getExec('turbostat')
1205
rawout = keyline = valline = ''
1206
fullcmd = '%s -q -S echo freeze > %s' % (cmd, self.powerfile)
1207
fp = Popen(['sh', '-c', fullcmd], stdout=PIPE, stderr=PIPE)
1208
for line in fp.stderr:
1209
line = ascii(line)
1210
rawout += line
1211
if keyline and valline:
1212
continue
1213
if re.match(r'(?i)Avg_MHz.*', line):
1214
keyline = line.strip().split()
1215
elif keyline:
1216
valline = line.strip().split()
1217
fp.wait()
1218
if not keyline or not valline or len(keyline) != len(valline):
1219
errmsg = 'unrecognized turbostat output:\n'+rawout.strip()
1220
self.vprint(errmsg)
1221
if not self.verbose:
1222
pprint(errmsg)
1223
return (fp.returncode, '')
1224
if self.verbose:
1225
pprint(rawout.strip())
1226
out = []
1227
for key in keyline:
1228
idx = keyline.index(key)
1229
val = valline[idx]
1230
if key == 'SYS%LPI' and not s0ixready and re.match(r'^[0\.]*$', val):
1231
continue
1232
out.append('%s=%s' % (key, val))
1233
return (fp.returncode, '|'.join(out))
1234
def netfixon(self, net='both'):
1235
cmd = self.getExec('netfix')
1236
if not cmd:
1237
return ''
1238
fp = Popen([cmd, '-s', net, 'on'], stdout=PIPE, stderr=PIPE).stdout
1239
out = ascii(fp.read()).strip()
1240
fp.close()
1241
return out
1242
def wifiDetails(self, dev):
1243
try:
1244
info = open('/sys/class/net/%s/device/uevent' % dev, 'r').read().strip()
1245
except:
1246
return dev
1247
vals = [dev]
1248
for prop in info.split('\n'):
1249
if prop.startswith('DRIVER=') or prop.startswith('PCI_ID='):
1250
vals.append(prop.split('=')[-1])
1251
return ':'.join(vals)
1252
def checkWifi(self, dev=''):
1253
try:
1254
w = open('/proc/net/wireless', 'r').read().strip()
1255
except:
1256
return ''
1257
for line in reversed(w.split('\n')):
1258
m = re.match(r' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', line)
1259
if not m or (dev and dev != m.group('dev')):
1260
continue
1261
return m.group('dev')
1262
return ''
1263
def pollWifi(self, dev, timeout=10):
1264
start = time.time()
1265
while (time.time() - start) < timeout:
1266
w = self.checkWifi(dev)
1267
if w:
1268
return '%s reconnected %.2f' % \
1269
(self.wifiDetails(dev), max(0, time.time() - start))
1270
time.sleep(0.01)
1271
return '%s timeout %d' % (self.wifiDetails(dev), timeout)
1272
def errorSummary(self, errinfo, msg):
1273
found = False
1274
for entry in errinfo:
1275
if re.match(entry['match'], msg):
1276
entry['count'] += 1
1277
if self.hostname not in entry['urls']:
1278
entry['urls'][self.hostname] = [self.htmlfile]
1279
elif self.htmlfile not in entry['urls'][self.hostname]:
1280
entry['urls'][self.hostname].append(self.htmlfile)
1281
found = True
1282
break
1283
if found:
1284
return
1285
arr = msg.split()
1286
for j in range(len(arr)):
1287
if re.match(r'^[0-9,\-\.]*$', arr[j]):
1288
arr[j] = r'[0-9,\-\.]*'
1289
else:
1290
arr[j] = arr[j]\
1291
.replace('\\', r'\\\\').replace(']', r'\]').replace('[', r'\[')\
1292
.replace('.', r'\.').replace('+', r'\+').replace('*', r'\*')\
1293
.replace('(', r'\(').replace(')', r'\)').replace('}', r'\}')\
1294
.replace('{', r'\{')
1295
mstr = ' *'.join(arr)
1296
entry = {
1297
'line': msg,
1298
'match': mstr,
1299
'count': 1,
1300
'urls': {self.hostname: [self.htmlfile]}
1301
}
1302
errinfo.append(entry)
1303
def multistat(self, start, idx, finish):
1304
if 'time' in self.multitest:
1305
id = '%d Duration=%dmin' % (idx+1, self.multitest['time'])
1306
else:
1307
id = '%d/%d' % (idx+1, self.multitest['count'])
1308
t = time.time()
1309
if 'start' not in self.multitest:
1310
self.multitest['start'] = self.multitest['last'] = t
1311
self.multitest['total'] = 0.0
1312
pprint('TEST (%s) START' % id)
1313
return
1314
dt = t - self.multitest['last']
1315
if not start:
1316
if idx == 0 and self.multitest['delay'] > 0:
1317
self.multitest['total'] += self.multitest['delay']
1318
pprint('TEST (%s) COMPLETE -- Duration %.1fs' % (id, dt))
1319
return
1320
self.multitest['total'] += dt
1321
self.multitest['last'] = t
1322
avg = self.multitest['total'] / idx
1323
if 'time' in self.multitest:
1324
left = finish - datetime.now()
1325
left -= timedelta(microseconds=left.microseconds)
1326
else:
1327
left = timedelta(seconds=((self.multitest['count'] - idx) * int(avg)))
1328
pprint('TEST (%s) START - Avg Duration %.1fs, Time left %s' % \
1329
(id, avg, str(left)))
1330
def multiinit(self, c, d):
1331
sz, unit = 'count', 'm'
1332
if c.endswith('d') or c.endswith('h') or c.endswith('m'):
1333
sz, unit, c = 'time', c[-1], c[:-1]
1334
self.multitest['run'] = True
1335
self.multitest[sz] = getArgInt('multi: n d (exec count)', c, 1, 1000000, False)
1336
self.multitest['delay'] = getArgInt('multi: n d (delay between tests)', d, 0, 3600, False)
1337
if unit == 'd':
1338
self.multitest[sz] *= 1440
1339
elif unit == 'h':
1340
self.multitest[sz] *= 60
1341
def displayControl(self, cmd):
1342
xset, ret = 'timeout 10 xset -d :0.0 {0}', 0
1343
if self.sudouser:
1344
xset = 'sudo -u %s %s' % (self.sudouser, xset)
1345
if cmd == 'init':
1346
ret = call(xset.format('dpms 0 0 0'), shell=True)
1347
if not ret:
1348
ret = call(xset.format('s off'), shell=True)
1349
elif cmd == 'reset':
1350
ret = call(xset.format('s reset'), shell=True)
1351
elif cmd in ['on', 'off', 'standby', 'suspend']:
1352
b4 = self.displayControl('stat')
1353
ret = call(xset.format('dpms force %s' % cmd), shell=True)
1354
if not ret:
1355
curr = self.displayControl('stat')
1356
self.vprint('Display Switched: %s -> %s' % (b4, curr))
1357
if curr != cmd:
1358
self.vprint('WARNING: Display failed to change to %s' % cmd)
1359
if ret:
1360
self.vprint('WARNING: Display failed to change to %s with xset' % cmd)
1361
return ret
1362
elif cmd == 'stat':
1363
fp = Popen(xset.format('q').split(' '), stdout=PIPE).stdout
1364
ret = 'unknown'
1365
for line in fp:
1366
m = re.match(r'[\s]*Monitor is (?P<m>.*)', ascii(line))
1367
if(m and len(m.group('m')) >= 2):
1368
out = m.group('m').lower()
1369
ret = out[3:] if out[0:2] == 'in' else out
1370
break
1371
fp.close()
1372
return ret
1373
def setRuntimeSuspend(self, before=True):
1374
if before:
1375
# runtime suspend disable or enable
1376
if self.rs > 0:
1377
self.rstgt, self.rsval, self.rsdir = 'on', 'auto', 'enabled'
1378
else:
1379
self.rstgt, self.rsval, self.rsdir = 'auto', 'on', 'disabled'
1380
pprint('CONFIGURING RUNTIME SUSPEND...')
1381
self.rslist = deviceInfo(self.rstgt)
1382
for i in self.rslist:
1383
self.setVal(self.rsval, i)
1384
pprint('runtime suspend %s on all devices (%d changed)' % (self.rsdir, len(self.rslist)))
1385
pprint('waiting 5 seconds...')
1386
time.sleep(5)
1387
else:
1388
# runtime suspend re-enable or re-disable
1389
for i in self.rslist:
1390
self.setVal(self.rstgt, i)
1391
pprint('runtime suspend settings restored on %d devices' % len(self.rslist))
1392
def start(self, pm):
1393
if self.useftrace:
1394
self.dlog('start ftrace tracing')
1395
self.fsetVal('1', 'tracing_on')
1396
if self.useprocmon:
1397
self.dlog('start the process monitor')
1398
pm.start()
1399
def stop(self, pm):
1400
if self.useftrace:
1401
if self.useprocmon:
1402
self.dlog('stop the process monitor')
1403
pm.stop()
1404
self.dlog('stop ftrace tracing')
1405
self.fsetVal('0', 'tracing_on')
1406
1407
sysvals = SystemValues()
1408
switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
1409
switchoff = ['disable', 'off', 'false', '0']
1410
suspendmodename = {
1411
'standby': 'standby (S1)',
1412
'freeze': 'freeze (S2idle)',
1413
'mem': 'suspend (S3)',
1414
'disk': 'hibernate (S4)'
1415
}
1416
1417
# Class: DevProps
1418
# Description:
1419
# Simple class which holds property values collected
1420
# for all the devices used in the timeline.
1421
class DevProps:
1422
def __init__(self):
1423
self.syspath = ''
1424
self.altname = ''
1425
self.isasync = True
1426
self.xtraclass = ''
1427
self.xtrainfo = ''
1428
def out(self, dev):
1429
return '%s,%s,%d;' % (dev, self.altname, self.isasync)
1430
def debug(self, dev):
1431
pprint('%s:\n\taltname = %s\n\t async = %s' % (dev, self.altname, self.isasync))
1432
def altName(self, dev):
1433
if not self.altname or self.altname == dev:
1434
return dev
1435
return '%s [%s]' % (self.altname, dev)
1436
def xtraClass(self):
1437
if self.xtraclass:
1438
return ' '+self.xtraclass
1439
if not self.isasync:
1440
return ' sync'
1441
return ''
1442
def xtraInfo(self):
1443
if self.xtraclass:
1444
return ' '+self.xtraclass
1445
if self.isasync:
1446
return ' (async)'
1447
return ' (sync)'
1448
1449
# Class: DeviceNode
1450
# Description:
1451
# A container used to create a device hierachy, with a single root node
1452
# and a tree of child nodes. Used by Data.deviceTopology()
1453
class DeviceNode:
1454
def __init__(self, nodename, nodedepth):
1455
self.name = nodename
1456
self.children = []
1457
self.depth = nodedepth
1458
1459
# Class: Data
1460
# Description:
1461
# The primary container for suspend/resume test data. There is one for
1462
# each test run. The data is organized into a cronological hierarchy:
1463
# Data.dmesg {
1464
# phases {
1465
# 10 sequential, non-overlapping phases of S/R
1466
# contents: times for phase start/end, order/color data for html
1467
# devlist {
1468
# device callback or action list for this phase
1469
# device {
1470
# a single device callback or generic action
1471
# contents: start/stop times, pid/cpu/driver info
1472
# parents/children, html id for timeline/callgraph
1473
# optionally includes an ftrace callgraph
1474
# optionally includes dev/ps data
1475
# }
1476
# }
1477
# }
1478
# }
1479
#
1480
class Data:
1481
phasedef = {
1482
'suspend_prepare': {'order': 0, 'color': '#CCFFCC'},
1483
'suspend': {'order': 1, 'color': '#88FF88'},
1484
'suspend_late': {'order': 2, 'color': '#00AA00'},
1485
'suspend_noirq': {'order': 3, 'color': '#008888'},
1486
'suspend_machine': {'order': 4, 'color': '#0000FF'},
1487
'resume_machine': {'order': 5, 'color': '#FF0000'},
1488
'resume_noirq': {'order': 6, 'color': '#FF9900'},
1489
'resume_early': {'order': 7, 'color': '#FFCC00'},
1490
'resume': {'order': 8, 'color': '#FFFF88'},
1491
'resume_complete': {'order': 9, 'color': '#FFFFCC'},
1492
}
1493
errlist = {
1494
'HWERROR' : r'.*\[ *Hardware Error *\].*',
1495
'FWBUG' : r'.*\[ *Firmware Bug *\].*',
1496
'TASKFAIL': r'.*Freezing .*after *.*',
1497
'BUG' : r'(?i).*\bBUG\b.*',
1498
'ERROR' : r'(?i).*\bERROR\b.*',
1499
'WARNING' : r'(?i).*\bWARNING\b.*',
1500
'FAULT' : r'(?i).*\bFAULT\b.*',
1501
'FAIL' : r'(?i).*\bFAILED\b.*',
1502
'INVALID' : r'(?i).*\bINVALID\b.*',
1503
'CRASH' : r'(?i).*\bCRASHED\b.*',
1504
'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*',
1505
'ABORT' : r'(?i).*\bABORT\b.*',
1506
'IRQ' : r'.*\bgenirq: .*',
1507
'ACPI' : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
1508
'DISKFULL': r'.*\bNo space left on device.*',
1509
'USBERR' : r'.*usb .*device .*, error [0-9-]*',
1510
'ATAERR' : r' *ata[0-9\.]*: .*failed.*',
1511
'MEIERR' : r' *mei.*: .*failed.*',
1512
'TPMERR' : r'(?i) *tpm *tpm[0-9]*: .*error.*',
1513
}
1514
def __init__(self, num):
1515
idchar = 'abcdefghij'
1516
self.start = 0.0 # test start
1517
self.end = 0.0 # test end
1518
self.hwstart = 0 # rtc test start
1519
self.hwend = 0 # rtc test end
1520
self.tSuspended = 0.0 # low-level suspend start
1521
self.tResumed = 0.0 # low-level resume start
1522
self.tKernSus = 0.0 # kernel level suspend start
1523
self.tKernRes = 0.0 # kernel level resume end
1524
self.fwValid = False # is firmware data available
1525
self.fwSuspend = 0 # time spent in firmware suspend
1526
self.fwResume = 0 # time spent in firmware resume
1527
self.html_device_id = 0
1528
self.stamp = 0
1529
self.outfile = ''
1530
self.kerror = False
1531
self.wifi = dict()
1532
self.turbostat = 0
1533
self.enterfail = ''
1534
self.currphase = ''
1535
self.pstl = dict() # process timeline
1536
self.testnumber = num
1537
self.idstr = idchar[num]
1538
self.dmesgtext = [] # dmesg text file in memory
1539
self.dmesg = dict() # root data structure
1540
self.errorinfo = {'suspend':[],'resume':[]}
1541
self.tLow = [] # time spent in low-level suspends (standby/freeze)
1542
self.devpids = []
1543
self.devicegroups = 0
1544
def sortedPhases(self):
1545
return sorted(self.dmesg, key=lambda k:self.dmesg[k]['order'])
1546
def initDevicegroups(self):
1547
# called when phases are all finished being added
1548
for phase in sorted(self.dmesg.keys()):
1549
if '*' in phase:
1550
p = phase.split('*')
1551
pnew = '%s%d' % (p[0], len(p))
1552
self.dmesg[pnew] = self.dmesg.pop(phase)
1553
self.devicegroups = []
1554
for phase in self.sortedPhases():
1555
self.devicegroups.append([phase])
1556
def nextPhase(self, phase, offset):
1557
order = self.dmesg[phase]['order'] + offset
1558
for p in self.dmesg:
1559
if self.dmesg[p]['order'] == order:
1560
return p
1561
return ''
1562
def lastPhase(self, depth=1):
1563
plist = self.sortedPhases()
1564
if len(plist) < depth:
1565
return ''
1566
return plist[-1*depth]
1567
def turbostatInfo(self):
1568
tp = TestProps()
1569
out = {'syslpi':'N/A','pkgpc10':'N/A'}
1570
for line in self.dmesgtext:
1571
m = re.match(tp.tstatfmt, line)
1572
if not m:
1573
continue
1574
for i in m.group('t').split('|'):
1575
if 'SYS%LPI' in i:
1576
out['syslpi'] = i.split('=')[-1]+'%'
1577
elif 'pc10' in i:
1578
out['pkgpc10'] = i.split('=')[-1]+'%'
1579
break
1580
return out
1581
def extractErrorInfo(self):
1582
lf = self.dmesgtext
1583
if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1584
lf = sysvals.openlog(sysvals.dmesgfile, 'r')
1585
i = 0
1586
tp = TestProps()
1587
list = []
1588
for line in lf:
1589
i += 1
1590
if tp.stampInfo(line, sysvals):
1591
continue
1592
m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
1593
if not m:
1594
continue
1595
t = float(m.group('ktime'))
1596
if t < self.start or t > self.end:
1597
continue
1598
dir = 'suspend' if t < self.tSuspended else 'resume'
1599
msg = m.group('msg')
1600
if re.match(r'capability: warning: .*', msg):
1601
continue
1602
for err in self.errlist:
1603
if re.match(self.errlist[err], msg):
1604
list.append((msg, err, dir, t, i, i))
1605
self.kerror = True
1606
break
1607
tp.msglist = []
1608
for msg, type, dir, t, idx1, idx2 in list:
1609
tp.msglist.append(msg)
1610
self.errorinfo[dir].append((type, t, idx1, idx2))
1611
if self.kerror:
1612
sysvals.dmesglog = True
1613
if len(self.dmesgtext) < 1 and sysvals.dmesgfile:
1614
lf.close()
1615
return tp
1616
def setStart(self, time, msg=''):
1617
self.start = time
1618
if msg:
1619
try:
1620
self.hwstart = datetime.strptime(msg, sysvals.tmstart)
1621
except:
1622
self.hwstart = 0
1623
def setEnd(self, time, msg=''):
1624
self.end = time
1625
if msg:
1626
try:
1627
self.hwend = datetime.strptime(msg, sysvals.tmend)
1628
except:
1629
self.hwend = 0
1630
def isTraceEventOutsideDeviceCalls(self, pid, time):
1631
for phase in self.sortedPhases():
1632
list = self.dmesg[phase]['list']
1633
for dev in list:
1634
d = list[dev]
1635
if(d['pid'] == pid and time >= d['start'] and
1636
time < d['end']):
1637
return False
1638
return True
1639
def sourcePhase(self, start):
1640
for phase in self.sortedPhases():
1641
if 'machine' in phase:
1642
continue
1643
pend = self.dmesg[phase]['end']
1644
if start <= pend:
1645
return phase
1646
return 'resume_complete' if 'resume_complete' in self.dmesg else ''
1647
def sourceDevice(self, phaselist, start, end, pid, type):
1648
tgtdev = ''
1649
for phase in phaselist:
1650
list = self.dmesg[phase]['list']
1651
for devname in list:
1652
dev = list[devname]
1653
# pid must match
1654
if dev['pid'] != pid:
1655
continue
1656
devS = dev['start']
1657
devE = dev['end']
1658
if type == 'device':
1659
# device target event is entirely inside the source boundary
1660
if(start < devS or start >= devE or end <= devS or end > devE):
1661
continue
1662
elif type == 'thread':
1663
# thread target event will expand the source boundary
1664
if start < devS:
1665
dev['start'] = start
1666
if end > devE:
1667
dev['end'] = end
1668
tgtdev = dev
1669
break
1670
return tgtdev
1671
def addDeviceFunctionCall(self, displayname, kprobename, proc, pid, start, end, cdata, rdata):
1672
# try to place the call in a device
1673
phases = self.sortedPhases()
1674
tgtdev = self.sourceDevice(phases, start, end, pid, 'device')
1675
# calls with device pids that occur outside device bounds are dropped
1676
# TODO: include these somehow
1677
if not tgtdev and pid in self.devpids:
1678
return False
1679
# try to place the call in a thread
1680
if not tgtdev:
1681
tgtdev = self.sourceDevice(phases, start, end, pid, 'thread')
1682
# create new thread blocks, expand as new calls are found
1683
if not tgtdev:
1684
if proc == '<...>':
1685
threadname = 'kthread-%d' % (pid)
1686
else:
1687
threadname = '%s-%d' % (proc, pid)
1688
tgtphase = self.sourcePhase(start)
1689
if not tgtphase:
1690
return False
1691
self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '')
1692
return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata)
1693
# this should not happen
1694
if not tgtdev:
1695
sysvals.vprint('[%f - %f] %s-%d %s %s %s' % \
1696
(start, end, proc, pid, kprobename, cdata, rdata))
1697
return False
1698
# place the call data inside the src element of the tgtdev
1699
if('src' not in tgtdev):
1700
tgtdev['src'] = []
1701
dtf = sysvals.dev_tracefuncs
1702
ubiquitous = False
1703
if kprobename in dtf and 'ub' in dtf[kprobename]:
1704
ubiquitous = True
1705
mc = re.match(r'\(.*\) *(?P<args>.*)', cdata)
1706
mr = re.match(r'\((?P<caller>\S*).* arg1=(?P<ret>.*)', rdata)
1707
if mc and mr:
1708
c = mr.group('caller').split('+')[0]
1709
a = mc.group('args').strip()
1710
r = mr.group('ret')
1711
if len(r) > 6:
1712
r = ''
1713
else:
1714
r = 'ret=%s ' % r
1715
if ubiquitous and c in dtf and 'ub' in dtf[c]:
1716
return False
1717
else:
1718
return False
1719
color = sysvals.kprobeColor(kprobename)
1720
e = DevFunction(displayname, a, c, r, start, end, ubiquitous, proc, pid, color)
1721
tgtdev['src'].append(e)
1722
return True
1723
def overflowDevices(self):
1724
# get a list of devices that extend beyond the end of this test run
1725
devlist = []
1726
for phase in self.sortedPhases():
1727
list = self.dmesg[phase]['list']
1728
for devname in list:
1729
dev = list[devname]
1730
if dev['end'] > self.end:
1731
devlist.append(dev)
1732
return devlist
1733
def mergeOverlapDevices(self, devlist):
1734
# merge any devices that overlap devlist
1735
for dev in devlist:
1736
devname = dev['name']
1737
for phase in self.sortedPhases():
1738
list = self.dmesg[phase]['list']
1739
if devname not in list:
1740
continue
1741
tdev = list[devname]
1742
o = min(dev['end'], tdev['end']) - max(dev['start'], tdev['start'])
1743
if o <= 0:
1744
continue
1745
dev['end'] = tdev['end']
1746
if 'src' not in dev or 'src' not in tdev:
1747
continue
1748
dev['src'] += tdev['src']
1749
del list[devname]
1750
def usurpTouchingThread(self, name, dev):
1751
# the caller test has priority of this thread, give it to him
1752
for phase in self.sortedPhases():
1753
list = self.dmesg[phase]['list']
1754
if name in list:
1755
tdev = list[name]
1756
if tdev['start'] - dev['end'] < 0.1:
1757
dev['end'] = tdev['end']
1758
if 'src' not in dev:
1759
dev['src'] = []
1760
if 'src' in tdev:
1761
dev['src'] += tdev['src']
1762
del list[name]
1763
break
1764
def stitchTouchingThreads(self, testlist):
1765
# merge any threads between tests that touch
1766
for phase in self.sortedPhases():
1767
list = self.dmesg[phase]['list']
1768
for devname in list:
1769
dev = list[devname]
1770
if 'htmlclass' not in dev or 'kth' not in dev['htmlclass']:
1771
continue
1772
for data in testlist:
1773
data.usurpTouchingThread(devname, dev)
1774
def optimizeDevSrc(self):
1775
# merge any src call loops to reduce timeline size
1776
for phase in self.sortedPhases():
1777
list = self.dmesg[phase]['list']
1778
for dev in list:
1779
if 'src' not in list[dev]:
1780
continue
1781
src = list[dev]['src']
1782
p = 0
1783
for e in sorted(src, key=lambda event: event.time):
1784
if not p or not e.repeat(p):
1785
p = e
1786
continue
1787
# e is another iteration of p, move it into p
1788
p.end = e.end
1789
p.length = p.end - p.time
1790
p.count += 1
1791
src.remove(e)
1792
def trimTimeVal(self, t, t0, dT, left):
1793
if left:
1794
if(t > t0):
1795
if(t - dT < t0):
1796
return t0
1797
return t - dT
1798
else:
1799
return t
1800
else:
1801
if(t < t0 + dT):
1802
if(t > t0):
1803
return t0 + dT
1804
return t + dT
1805
else:
1806
return t
1807
def trimTime(self, t0, dT, left):
1808
self.tSuspended = self.trimTimeVal(self.tSuspended, t0, dT, left)
1809
self.tResumed = self.trimTimeVal(self.tResumed, t0, dT, left)
1810
self.start = self.trimTimeVal(self.start, t0, dT, left)
1811
self.tKernSus = self.trimTimeVal(self.tKernSus, t0, dT, left)
1812
self.tKernRes = self.trimTimeVal(self.tKernRes, t0, dT, left)
1813
self.end = self.trimTimeVal(self.end, t0, dT, left)
1814
for phase in self.sortedPhases():
1815
p = self.dmesg[phase]
1816
p['start'] = self.trimTimeVal(p['start'], t0, dT, left)
1817
p['end'] = self.trimTimeVal(p['end'], t0, dT, left)
1818
list = p['list']
1819
for name in list:
1820
d = list[name]
1821
d['start'] = self.trimTimeVal(d['start'], t0, dT, left)
1822
d['end'] = self.trimTimeVal(d['end'], t0, dT, left)
1823
d['length'] = d['end'] - d['start']
1824
if('ftrace' in d):
1825
cg = d['ftrace']
1826
cg.start = self.trimTimeVal(cg.start, t0, dT, left)
1827
cg.end = self.trimTimeVal(cg.end, t0, dT, left)
1828
for line in cg.list:
1829
line.time = self.trimTimeVal(line.time, t0, dT, left)
1830
if('src' in d):
1831
for e in d['src']:
1832
e.time = self.trimTimeVal(e.time, t0, dT, left)
1833
e.end = self.trimTimeVal(e.end, t0, dT, left)
1834
e.length = e.end - e.time
1835
if('cpuexec' in d):
1836
cpuexec = dict()
1837
for e in d['cpuexec']:
1838
c0, cN = e
1839
c0 = self.trimTimeVal(c0, t0, dT, left)
1840
cN = self.trimTimeVal(cN, t0, dT, left)
1841
cpuexec[(c0, cN)] = d['cpuexec'][e]
1842
d['cpuexec'] = cpuexec
1843
for dir in ['suspend', 'resume']:
1844
list = []
1845
for e in self.errorinfo[dir]:
1846
type, tm, idx1, idx2 = e
1847
tm = self.trimTimeVal(tm, t0, dT, left)
1848
list.append((type, tm, idx1, idx2))
1849
self.errorinfo[dir] = list
1850
def trimFreezeTime(self, tZero):
1851
# trim out any standby or freeze clock time
1852
lp = ''
1853
for phase in self.sortedPhases():
1854
if 'resume_machine' in phase and 'suspend_machine' in lp:
1855
tS, tR = self.dmesg[lp]['end'], self.dmesg[phase]['start']
1856
tL = tR - tS
1857
if tL <= 0:
1858
continue
1859
left = True if tR > tZero else False
1860
self.trimTime(tS, tL, left)
1861
if 'waking' in self.dmesg[lp]:
1862
tCnt = self.dmesg[lp]['waking'][0]
1863
if self.dmesg[lp]['waking'][1] >= 0.001:
1864
tTry = '%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
1865
else:
1866
tTry = '%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
1867
text = '%.0f (%s ms waking %d times)' % (tL * 1000, tTry, tCnt)
1868
else:
1869
text = '%.0f' % (tL * 1000)
1870
self.tLow.append(text)
1871
lp = phase
1872
def getMemTime(self):
1873
if not self.hwstart or not self.hwend:
1874
return
1875
stime = (self.tSuspended - self.start) * 1000000
1876
rtime = (self.end - self.tResumed) * 1000000
1877
hws = self.hwstart + timedelta(microseconds=stime)
1878
hwr = self.hwend - timedelta(microseconds=rtime)
1879
self.tLow.append('%.0f'%((hwr - hws).total_seconds() * 1000))
1880
def getTimeValues(self):
1881
s = (self.tSuspended - self.tKernSus) * 1000
1882
r = (self.tKernRes - self.tResumed) * 1000
1883
return (max(s, 0), max(r, 0))
1884
def setPhase(self, phase, ktime, isbegin, order=-1):
1885
if(isbegin):
1886
# phase start over current phase
1887
if self.currphase:
1888
if 'resume_machine' not in self.currphase:
1889
sysvals.vprint('WARNING: phase %s failed to end' % self.currphase)
1890
self.dmesg[self.currphase]['end'] = ktime
1891
phases = self.dmesg.keys()
1892
color = self.phasedef[phase]['color']
1893
count = len(phases) if order < 0 else order
1894
# create unique name for every new phase
1895
while phase in phases:
1896
phase += '*'
1897
self.dmesg[phase] = {'list': dict(), 'start': -1.0, 'end': -1.0,
1898
'row': 0, 'color': color, 'order': count}
1899
self.dmesg[phase]['start'] = ktime
1900
self.currphase = phase
1901
else:
1902
# phase end without a start
1903
if phase not in self.currphase:
1904
if self.currphase:
1905
sysvals.vprint('WARNING: %s ended instead of %s, ftrace corruption?' % (phase, self.currphase))
1906
else:
1907
sysvals.vprint('WARNING: %s ended without a start, ftrace corruption?' % phase)
1908
return phase
1909
phase = self.currphase
1910
self.dmesg[phase]['end'] = ktime
1911
self.currphase = ''
1912
return phase
1913
def sortedDevices(self, phase):
1914
list = self.dmesg[phase]['list']
1915
return sorted(list, key=lambda k:list[k]['start'])
1916
def fixupInitcalls(self, phase):
1917
# if any calls never returned, clip them at system resume end
1918
phaselist = self.dmesg[phase]['list']
1919
for devname in phaselist:
1920
dev = phaselist[devname]
1921
if(dev['end'] < 0):
1922
for p in self.sortedPhases():
1923
if self.dmesg[p]['end'] > dev['start']:
1924
dev['end'] = self.dmesg[p]['end']
1925
break
1926
sysvals.vprint('%s (%s): callback didnt return' % (devname, phase))
1927
def deviceFilter(self, devicefilter):
1928
for phase in self.sortedPhases():
1929
list = self.dmesg[phase]['list']
1930
rmlist = []
1931
for name in list:
1932
keep = False
1933
for filter in devicefilter:
1934
if filter in name or \
1935
('drv' in list[name] and filter in list[name]['drv']):
1936
keep = True
1937
if not keep:
1938
rmlist.append(name)
1939
for name in rmlist:
1940
del list[name]
1941
def fixupInitcallsThatDidntReturn(self):
1942
# if any calls never returned, clip them at system resume end
1943
for phase in self.sortedPhases():
1944
self.fixupInitcalls(phase)
1945
def phaseOverlap(self, phases):
1946
rmgroups = []
1947
newgroup = []
1948
for group in self.devicegroups:
1949
for phase in phases:
1950
if phase not in group:
1951
continue
1952
for p in group:
1953
if p not in newgroup:
1954
newgroup.append(p)
1955
if group not in rmgroups:
1956
rmgroups.append(group)
1957
for group in rmgroups:
1958
self.devicegroups.remove(group)
1959
self.devicegroups.append(newgroup)
1960
def newActionGlobal(self, name, start, end, pid=-1, color=''):
1961
# which phase is this device callback or action in
1962
phases = self.sortedPhases()
1963
targetphase = 'none'
1964
htmlclass = ''
1965
overlap = 0.0
1966
myphases = []
1967
for phase in phases:
1968
pstart = self.dmesg[phase]['start']
1969
pend = self.dmesg[phase]['end']
1970
# see if the action overlaps this phase
1971
o = max(0, min(end, pend) - max(start, pstart))
1972
if o > 0:
1973
myphases.append(phase)
1974
# set the target phase to the one that overlaps most
1975
if o > overlap:
1976
if overlap > 0 and phase == 'post_resume':
1977
continue
1978
targetphase = phase
1979
overlap = o
1980
# if no target phase was found, pin it to the edge
1981
if targetphase == 'none':
1982
p0start = self.dmesg[phases[0]]['start']
1983
if start <= p0start:
1984
targetphase = phases[0]
1985
else:
1986
targetphase = phases[-1]
1987
if pid == -2:
1988
htmlclass = ' bg'
1989
elif pid == -3:
1990
htmlclass = ' ps'
1991
if len(myphases) > 1:
1992
htmlclass = ' bg'
1993
self.phaseOverlap(myphases)
1994
if targetphase in phases:
1995
newname = self.newAction(targetphase, name, pid, '', start, end, '', htmlclass, color)
1996
return (targetphase, newname)
1997
return False
1998
def newAction(self, phase, name, pid, parent, start, end, drv, htmlclass='', color=''):
1999
# new device callback for a specific phase
2000
self.html_device_id += 1
2001
devid = '%s%d' % (self.idstr, self.html_device_id)
2002
list = self.dmesg[phase]['list']
2003
length = -1.0
2004
if(start >= 0 and end >= 0):
2005
length = end - start
2006
if pid >= -2:
2007
i = 2
2008
origname = name
2009
while(name in list):
2010
name = '%s[%d]' % (origname, i)
2011
i += 1
2012
list[name] = {'name': name, 'start': start, 'end': end, 'pid': pid,
2013
'par': parent, 'length': length, 'row': 0, 'id': devid, 'drv': drv }
2014
if htmlclass:
2015
list[name]['htmlclass'] = htmlclass
2016
if color:
2017
list[name]['color'] = color
2018
return name
2019
def findDevice(self, phase, name):
2020
list = self.dmesg[phase]['list']
2021
mydev = ''
2022
for devname in sorted(list):
2023
if name == devname or re.match(r'^%s\[(?P<num>[0-9]*)\]$' % name, devname):
2024
mydev = devname
2025
if mydev:
2026
return list[mydev]
2027
return False
2028
def deviceChildren(self, devname, phase):
2029
devlist = []
2030
list = self.dmesg[phase]['list']
2031
for child in list:
2032
if(list[child]['par'] == devname):
2033
devlist.append(child)
2034
return devlist
2035
def maxDeviceNameSize(self, phase):
2036
size = 0
2037
for name in self.dmesg[phase]['list']:
2038
if len(name) > size:
2039
size = len(name)
2040
return size
2041
def printDetails(self):
2042
sysvals.vprint('Timeline Details:')
2043
sysvals.vprint(' test start: %f' % self.start)
2044
sysvals.vprint('kernel suspend start: %f' % self.tKernSus)
2045
tS = tR = False
2046
for phase in self.sortedPhases():
2047
devlist = self.dmesg[phase]['list']
2048
dc, ps, pe = len(devlist), self.dmesg[phase]['start'], self.dmesg[phase]['end']
2049
if not tS and ps >= self.tSuspended:
2050
sysvals.vprint(' machine suspended: %f' % self.tSuspended)
2051
tS = True
2052
if not tR and ps >= self.tResumed:
2053
sysvals.vprint(' machine resumed: %f' % self.tResumed)
2054
tR = True
2055
sysvals.vprint('%20s: %f - %f (%d devices)' % (phase, ps, pe, dc))
2056
if sysvals.devdump:
2057
sysvals.vprint(''.join('-' for i in range(80)))
2058
maxname = '%d' % self.maxDeviceNameSize(phase)
2059
fmt = '%3d) %'+maxname+'s - %f - %f'
2060
c = 1
2061
for name in sorted(devlist):
2062
s = devlist[name]['start']
2063
e = devlist[name]['end']
2064
sysvals.vprint(fmt % (c, name, s, e))
2065
c += 1
2066
sysvals.vprint(''.join('-' for i in range(80)))
2067
sysvals.vprint(' kernel resume end: %f' % self.tKernRes)
2068
sysvals.vprint(' test end: %f' % self.end)
2069
def deviceChildrenAllPhases(self, devname):
2070
devlist = []
2071
for phase in self.sortedPhases():
2072
list = self.deviceChildren(devname, phase)
2073
for dev in sorted(list):
2074
if dev not in devlist:
2075
devlist.append(dev)
2076
return devlist
2077
def masterTopology(self, name, list, depth):
2078
node = DeviceNode(name, depth)
2079
for cname in list:
2080
# avoid recursions
2081
if name == cname:
2082
continue
2083
clist = self.deviceChildrenAllPhases(cname)
2084
cnode = self.masterTopology(cname, clist, depth+1)
2085
node.children.append(cnode)
2086
return node
2087
def printTopology(self, node):
2088
html = ''
2089
if node.name:
2090
info = ''
2091
drv = ''
2092
for phase in self.sortedPhases():
2093
list = self.dmesg[phase]['list']
2094
if node.name in list:
2095
s = list[node.name]['start']
2096
e = list[node.name]['end']
2097
if list[node.name]['drv']:
2098
drv = ' {'+list[node.name]['drv']+'}'
2099
info += ('<li>%s: %.3fms</li>' % (phase, (e-s)*1000))
2100
html += '<li><b>'+node.name+drv+'</b>'
2101
if info:
2102
html += '<ul>'+info+'</ul>'
2103
html += '</li>'
2104
if len(node.children) > 0:
2105
html += '<ul>'
2106
for cnode in node.children:
2107
html += self.printTopology(cnode)
2108
html += '</ul>'
2109
return html
2110
def rootDeviceList(self):
2111
# list of devices graphed
2112
real = []
2113
for phase in self.sortedPhases():
2114
list = self.dmesg[phase]['list']
2115
for dev in sorted(list):
2116
if list[dev]['pid'] >= 0 and dev not in real:
2117
real.append(dev)
2118
# list of top-most root devices
2119
rootlist = []
2120
for phase in self.sortedPhases():
2121
list = self.dmesg[phase]['list']
2122
for dev in sorted(list):
2123
pdev = list[dev]['par']
2124
pid = list[dev]['pid']
2125
if(pid < 0 or re.match(r'[0-9]*-[0-9]*\.[0-9]*[\.0-9]*\:[\.0-9]*$', pdev)):
2126
continue
2127
if pdev and pdev not in real and pdev not in rootlist:
2128
rootlist.append(pdev)
2129
return rootlist
2130
def deviceTopology(self):
2131
rootlist = self.rootDeviceList()
2132
master = self.masterTopology('', rootlist, 0)
2133
return self.printTopology(master)
2134
def selectTimelineDevices(self, widfmt, tTotal, mindevlen):
2135
# only select devices that will actually show up in html
2136
self.tdevlist = dict()
2137
for phase in self.dmesg:
2138
devlist = []
2139
list = self.dmesg[phase]['list']
2140
for dev in list:
2141
length = (list[dev]['end'] - list[dev]['start']) * 1000
2142
width = widfmt % (((list[dev]['end']-list[dev]['start'])*100)/tTotal)
2143
if length >= mindevlen:
2144
devlist.append(dev)
2145
self.tdevlist[phase] = devlist
2146
def addHorizontalDivider(self, devname, devend):
2147
phase = 'suspend_prepare'
2148
self.newAction(phase, devname, -2, '', \
2149
self.start, devend, '', ' sec', '')
2150
if phase not in self.tdevlist:
2151
self.tdevlist[phase] = []
2152
self.tdevlist[phase].append(devname)
2153
d = DevItem(0, phase, self.dmesg[phase]['list'][devname])
2154
return d
2155
def addProcessUsageEvent(self, name, times):
2156
# get the start and end times for this process
2157
cpuexec = dict()
2158
tlast = start = end = -1
2159
for t in sorted(times):
2160
if tlast < 0:
2161
tlast = t
2162
continue
2163
if name in self.pstl[t] and self.pstl[t][name] > 0:
2164
if start < 0:
2165
start = tlast
2166
end, key = t, (tlast, t)
2167
maxj = (t - tlast) * 1024.0
2168
cpuexec[key] = min(1.0, float(self.pstl[t][name]) / maxj)
2169
tlast = t
2170
if start < 0 or end < 0:
2171
return
2172
# add a new action for this process and get the object
2173
out = self.newActionGlobal(name, start, end, -3)
2174
if out:
2175
phase, devname = out
2176
dev = self.dmesg[phase]['list'][devname]
2177
dev['cpuexec'] = cpuexec
2178
def createProcessUsageEvents(self):
2179
# get an array of process names and times
2180
proclist = {'sus': dict(), 'res': dict()}
2181
tdata = {'sus': [], 'res': []}
2182
for t in sorted(self.pstl):
2183
dir = 'sus' if t < self.tSuspended else 'res'
2184
for ps in sorted(self.pstl[t]):
2185
if ps not in proclist[dir]:
2186
proclist[dir][ps] = 0
2187
tdata[dir].append(t)
2188
# process the events for suspend and resume
2189
if len(proclist['sus']) > 0 or len(proclist['res']) > 0:
2190
sysvals.vprint('Process Execution:')
2191
for dir in ['sus', 'res']:
2192
for ps in sorted(proclist[dir]):
2193
self.addProcessUsageEvent(ps, tdata[dir])
2194
def handleEndMarker(self, time, msg=''):
2195
dm = self.dmesg
2196
self.setEnd(time, msg)
2197
self.initDevicegroups()
2198
# give suspend_prepare an end if needed
2199
if 'suspend_prepare' in dm and dm['suspend_prepare']['end'] < 0:
2200
dm['suspend_prepare']['end'] = time
2201
# assume resume machine ends at next phase start
2202
if 'resume_machine' in dm and dm['resume_machine']['end'] < 0:
2203
np = self.nextPhase('resume_machine', 1)
2204
if np:
2205
dm['resume_machine']['end'] = dm[np]['start']
2206
# if kernel resume end not found, assume its the end marker
2207
if self.tKernRes == 0.0:
2208
self.tKernRes = time
2209
# if kernel suspend start not found, assume its the end marker
2210
if self.tKernSus == 0.0:
2211
self.tKernSus = time
2212
# set resume complete to end at end marker
2213
if 'resume_complete' in dm:
2214
dm['resume_complete']['end'] = time
2215
def initcall_debug_call(self, line, quick=False):
2216
m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2217
r'PM: *calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
2218
if not m:
2219
m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2220
r'calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
2221
if not m:
2222
m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) calling '+\
2223
r'(?P<f>.*)\+ @ (?P<n>.*), parent: (?P<p>.*)', line)
2224
if m:
2225
return True if quick else m.group('t', 'f', 'n', 'p')
2226
return False if quick else ('', '', '', '')
2227
def initcall_debug_return(self, line, quick=False):
2228
m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: PM: '+\
2229
r'.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
2230
if not m:
2231
m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
2232
r'.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
2233
if not m:
2234
m = re.match(r'.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
2235
r'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', line)
2236
if m:
2237
return True if quick else m.group('t', 'f', 'dt')
2238
return False if quick else ('', '', '')
2239
def debugPrint(self):
2240
for p in self.sortedPhases():
2241
list = self.dmesg[p]['list']
2242
for devname in sorted(list):
2243
dev = list[devname]
2244
if 'ftrace' in dev:
2245
dev['ftrace'].debugPrint(' [%s]' % devname)
2246
2247
# Class: DevFunction
2248
# Description:
2249
# A container for kprobe function data we want in the dev timeline
2250
class DevFunction:
2251
def __init__(self, name, args, caller, ret, start, end, u, proc, pid, color):
2252
self.row = 0
2253
self.count = 1
2254
self.name = name
2255
self.args = args
2256
self.caller = caller
2257
self.ret = ret
2258
self.time = start
2259
self.length = end - start
2260
self.end = end
2261
self.ubiquitous = u
2262
self.proc = proc
2263
self.pid = pid
2264
self.color = color
2265
def title(self):
2266
cnt = ''
2267
if self.count > 1:
2268
cnt = '(x%d)' % self.count
2269
l = '%0.3fms' % (self.length * 1000)
2270
if self.ubiquitous:
2271
title = '%s(%s)%s <- %s, %s(%s)' % \
2272
(self.name, self.args, cnt, self.caller, self.ret, l)
2273
else:
2274
title = '%s(%s) %s%s(%s)' % (self.name, self.args, self.ret, cnt, l)
2275
return title.replace('"', '')
2276
def text(self):
2277
if self.count > 1:
2278
text = '%s(x%d)' % (self.name, self.count)
2279
else:
2280
text = self.name
2281
return text
2282
def repeat(self, tgt):
2283
# is the tgt call just a repeat of this call (e.g. are we in a loop)
2284
dt = self.time - tgt.end
2285
# only combine calls if -all- attributes are identical
2286
if tgt.caller == self.caller and \
2287
tgt.name == self.name and tgt.args == self.args and \
2288
tgt.proc == self.proc and tgt.pid == self.pid and \
2289
tgt.ret == self.ret and dt >= 0 and \
2290
dt <= sysvals.callloopmaxgap and \
2291
self.length < sysvals.callloopmaxlen:
2292
return True
2293
return False
2294
2295
# Class: FTraceLine
2296
# Description:
2297
# A container for a single line of ftrace data. There are six basic types:
2298
# callgraph line:
2299
# call: " dpm_run_callback() {"
2300
# return: " }"
2301
# leaf: " dpm_run_callback();"
2302
# trace event:
2303
# tracing_mark_write: SUSPEND START or RESUME COMPLETE
2304
# suspend_resume: phase or custom exec block data
2305
# device_pm_callback: device callback info
2306
class FTraceLine:
2307
def __init__(self, t, m='', d=''):
2308
self.length = 0.0
2309
self.fcall = False
2310
self.freturn = False
2311
self.fevent = False
2312
self.fkprobe = False
2313
self.depth = 0
2314
self.name = ''
2315
self.type = ''
2316
self.time = float(t)
2317
if not m and not d:
2318
return
2319
# is this a trace event
2320
if(d == 'traceevent' or re.match(r'^ *\/\* *(?P<msg>.*) \*\/ *$', m)):
2321
if(d == 'traceevent'):
2322
# nop format trace event
2323
msg = m
2324
else:
2325
# function_graph format trace event
2326
em = re.match(r'^ *\/\* *(?P<msg>.*) \*\/ *$', m)
2327
msg = em.group('msg')
2328
2329
emm = re.match(r'^(?P<call>.*?): (?P<msg>.*)', msg)
2330
if(emm):
2331
self.name = emm.group('msg')
2332
self.type = emm.group('call')
2333
else:
2334
self.name = msg
2335
km = re.match(r'^(?P<n>.*)_cal$', self.type)
2336
if km:
2337
self.fcall = True
2338
self.fkprobe = True
2339
self.type = km.group('n')
2340
return
2341
km = re.match(r'^(?P<n>.*)_ret$', self.type)
2342
if km:
2343
self.freturn = True
2344
self.fkprobe = True
2345
self.type = km.group('n')
2346
return
2347
self.fevent = True
2348
return
2349
# convert the duration to seconds
2350
if(d):
2351
self.length = float(d)/1000000
2352
# the indentation determines the depth
2353
match = re.match(r'^(?P<d> *)(?P<o>.*)$', m)
2354
if(not match):
2355
return
2356
self.depth = self.getDepth(match.group('d'))
2357
m = match.group('o')
2358
# function return
2359
if(m[0] == '}'):
2360
self.freturn = True
2361
if(len(m) > 1):
2362
# includes comment with function name
2363
match = re.match(r'^} *\/\* *(?P<n>.*) *\*\/$', m)
2364
if(match):
2365
self.name = match.group('n').strip()
2366
# function call
2367
else:
2368
self.fcall = True
2369
# function call with children
2370
if(m[-1] == '{'):
2371
match = re.match(r'^(?P<n>.*) *\(.*', m)
2372
if(match):
2373
self.name = match.group('n').strip()
2374
# function call with no children (leaf)
2375
elif(m[-1] == ';'):
2376
self.freturn = True
2377
match = re.match(r'^(?P<n>.*) *\(.*', m)
2378
if(match):
2379
self.name = match.group('n').strip()
2380
# something else (possibly a trace marker)
2381
else:
2382
self.name = m
2383
def isCall(self):
2384
return self.fcall and not self.freturn
2385
def isReturn(self):
2386
return self.freturn and not self.fcall
2387
def isLeaf(self):
2388
return self.fcall and self.freturn
2389
def getDepth(self, str):
2390
return len(str)/2
2391
def debugPrint(self, info=''):
2392
if self.isLeaf():
2393
pprint(' -- %12.6f (depth=%02d): %s(); (%.3f us) %s' % (self.time, \
2394
self.depth, self.name, self.length*1000000, info))
2395
elif self.freturn:
2396
pprint(' -- %12.6f (depth=%02d): %s} (%.3f us) %s' % (self.time, \
2397
self.depth, self.name, self.length*1000000, info))
2398
else:
2399
pprint(' -- %12.6f (depth=%02d): %s() { (%.3f us) %s' % (self.time, \
2400
self.depth, self.name, self.length*1000000, info))
2401
def startMarker(self):
2402
# Is this the starting line of a suspend?
2403
if not self.fevent:
2404
return False
2405
if sysvals.usetracemarkers:
2406
if(self.name.startswith('SUSPEND START')):
2407
return True
2408
return False
2409
else:
2410
if(self.type == 'suspend_resume' and
2411
re.match(r'suspend_enter\[.*\] begin', self.name)):
2412
return True
2413
return False
2414
def endMarker(self):
2415
# Is this the ending line of a resume?
2416
if not self.fevent:
2417
return False
2418
if sysvals.usetracemarkers:
2419
if(self.name.startswith('RESUME COMPLETE')):
2420
return True
2421
return False
2422
else:
2423
if(self.type == 'suspend_resume' and
2424
re.match(r'thaw_processes\[.*\] end', self.name)):
2425
return True
2426
return False
2427
2428
# Class: FTraceCallGraph
2429
# Description:
2430
# A container for the ftrace callgraph of a single recursive function.
2431
# This can be a dpm_run_callback, dpm_prepare, or dpm_complete callgraph
2432
# Each instance is tied to a single device in a single phase, and is
2433
# comprised of an ordered list of FTraceLine objects
2434
class FTraceCallGraph:
2435
vfname = 'missing_function_name'
2436
def __init__(self, pid, sv):
2437
self.id = ''
2438
self.invalid = False
2439
self.name = ''
2440
self.partial = False
2441
self.ignore = False
2442
self.start = -1.0
2443
self.end = -1.0
2444
self.list = []
2445
self.depth = 0
2446
self.pid = pid
2447
self.sv = sv
2448
def addLine(self, line):
2449
# if this is already invalid, just leave
2450
if(self.invalid):
2451
if(line.depth == 0 and line.freturn):
2452
return 1
2453
return 0
2454
# invalidate on bad depth
2455
if(self.depth < 0):
2456
self.invalidate(line)
2457
return 0
2458
# ignore data til we return to the current depth
2459
if self.ignore:
2460
if line.depth > self.depth:
2461
return 0
2462
else:
2463
self.list[-1].freturn = True
2464
self.list[-1].length = line.time - self.list[-1].time
2465
self.ignore = False
2466
# if this is a return at self.depth, no more work is needed
2467
if line.depth == self.depth and line.isReturn():
2468
if line.depth == 0:
2469
self.end = line.time
2470
return 1
2471
return 0
2472
# compare current depth with this lines pre-call depth
2473
prelinedep = line.depth
2474
if line.isReturn():
2475
prelinedep += 1
2476
last = 0
2477
lasttime = line.time
2478
if len(self.list) > 0:
2479
last = self.list[-1]
2480
lasttime = last.time
2481
if last.isLeaf():
2482
lasttime += last.length
2483
# handle low misalignments by inserting returns
2484
mismatch = prelinedep - self.depth
2485
warning = self.sv.verbose and abs(mismatch) > 1
2486
info = []
2487
if mismatch < 0:
2488
idx = 0
2489
# add return calls to get the depth down
2490
while prelinedep < self.depth:
2491
self.depth -= 1
2492
if idx == 0 and last and last.isCall():
2493
# special case, turn last call into a leaf
2494
last.depth = self.depth
2495
last.freturn = True
2496
last.length = line.time - last.time
2497
if warning:
2498
info.append(('[make leaf]', last))
2499
else:
2500
vline = FTraceLine(lasttime)
2501
vline.depth = self.depth
2502
vline.name = self.vfname
2503
vline.freturn = True
2504
self.list.append(vline)
2505
if warning:
2506
if idx == 0:
2507
info.append(('', last))
2508
info.append(('[add return]', vline))
2509
idx += 1
2510
if warning:
2511
info.append(('', line))
2512
# handle high misalignments by inserting calls
2513
elif mismatch > 0:
2514
idx = 0
2515
if warning:
2516
info.append(('', last))
2517
# add calls to get the depth up
2518
while prelinedep > self.depth:
2519
if idx == 0 and line.isReturn():
2520
# special case, turn this return into a leaf
2521
line.fcall = True
2522
prelinedep -= 1
2523
if warning:
2524
info.append(('[make leaf]', line))
2525
else:
2526
vline = FTraceLine(lasttime)
2527
vline.depth = self.depth
2528
vline.name = self.vfname
2529
vline.fcall = True
2530
self.list.append(vline)
2531
self.depth += 1
2532
if not last:
2533
self.start = vline.time
2534
if warning:
2535
info.append(('[add call]', vline))
2536
idx += 1
2537
if warning and ('[make leaf]', line) not in info:
2538
info.append(('', line))
2539
if warning:
2540
pprint('WARNING: ftrace data missing, corrections made:')
2541
for i in info:
2542
t, obj = i
2543
if obj:
2544
obj.debugPrint(t)
2545
# process the call and set the new depth
2546
skipadd = False
2547
md = self.sv.max_graph_depth
2548
if line.isCall():
2549
# ignore blacklisted/overdepth funcs
2550
if (md and self.depth >= md - 1) or (line.name in self.sv.cgblacklist):
2551
self.ignore = True
2552
else:
2553
self.depth += 1
2554
elif line.isReturn():
2555
self.depth -= 1
2556
# remove blacklisted/overdepth/empty funcs that slipped through
2557
if (last and last.isCall() and last.depth == line.depth) or \
2558
(md and last and last.depth >= md) or \
2559
(line.name in self.sv.cgblacklist):
2560
while len(self.list) > 0 and self.list[-1].depth > line.depth:
2561
self.list.pop(-1)
2562
if len(self.list) == 0:
2563
self.invalid = True
2564
return 1
2565
self.list[-1].freturn = True
2566
self.list[-1].length = line.time - self.list[-1].time
2567
self.list[-1].name = line.name
2568
skipadd = True
2569
if len(self.list) < 1:
2570
self.start = line.time
2571
# check for a mismatch that returned all the way to callgraph end
2572
res = 1
2573
if mismatch < 0 and self.list[-1].depth == 0 and self.list[-1].freturn:
2574
line = self.list[-1]
2575
skipadd = True
2576
res = -1
2577
if not skipadd:
2578
self.list.append(line)
2579
if(line.depth == 0 and line.freturn):
2580
if(self.start < 0):
2581
self.start = line.time
2582
self.end = line.time
2583
if line.fcall:
2584
self.end += line.length
2585
if self.list[0].name == self.vfname:
2586
self.invalid = True
2587
if res == -1:
2588
self.partial = True
2589
return res
2590
return 0
2591
def invalidate(self, line):
2592
if(len(self.list) > 0):
2593
first = self.list[0]
2594
self.list = []
2595
self.list.append(first)
2596
self.invalid = True
2597
id = 'task %s' % (self.pid)
2598
window = '(%f - %f)' % (self.start, line.time)
2599
if(self.depth < 0):
2600
pprint('Data misalignment for '+id+\
2601
' (buffer overflow), ignoring this callback')
2602
else:
2603
pprint('Too much data for '+id+\
2604
' '+window+', ignoring this callback')
2605
def slice(self, dev):
2606
minicg = FTraceCallGraph(dev['pid'], self.sv)
2607
minicg.name = self.name
2608
mydepth = -1
2609
good = False
2610
for l in self.list:
2611
if(l.time < dev['start'] or l.time > dev['end']):
2612
continue
2613
if mydepth < 0:
2614
if l.name == 'mutex_lock' and l.freturn:
2615
mydepth = l.depth
2616
continue
2617
elif l.depth == mydepth and l.name == 'mutex_unlock' and l.fcall:
2618
good = True
2619
break
2620
l.depth -= mydepth
2621
minicg.addLine(l)
2622
if not good or len(minicg.list) < 1:
2623
return 0
2624
return minicg
2625
def repair(self, enddepth):
2626
# bring the depth back to 0 with additional returns
2627
fixed = False
2628
last = self.list[-1]
2629
for i in reversed(range(enddepth)):
2630
t = FTraceLine(last.time)
2631
t.depth = i
2632
t.freturn = True
2633
fixed = self.addLine(t)
2634
if fixed != 0:
2635
self.end = last.time
2636
return True
2637
return False
2638
def postProcess(self):
2639
if len(self.list) > 0:
2640
self.name = self.list[0].name
2641
stack = dict()
2642
cnt = 0
2643
last = 0
2644
for l in self.list:
2645
# ftrace bug: reported duration is not reliable
2646
# check each leaf and clip it at max possible length
2647
if last and last.isLeaf():
2648
if last.length > l.time - last.time:
2649
last.length = l.time - last.time
2650
if l.isCall():
2651
stack[l.depth] = l
2652
cnt += 1
2653
elif l.isReturn():
2654
if(l.depth not in stack):
2655
if self.sv.verbose:
2656
pprint('Post Process Error: Depth missing')
2657
l.debugPrint()
2658
return False
2659
# calculate call length from call/return lines
2660
cl = stack[l.depth]
2661
cl.length = l.time - cl.time
2662
if cl.name == self.vfname:
2663
cl.name = l.name
2664
stack.pop(l.depth)
2665
l.length = 0
2666
cnt -= 1
2667
last = l
2668
if(cnt == 0):
2669
# trace caught the whole call tree
2670
return True
2671
elif(cnt < 0):
2672
if self.sv.verbose:
2673
pprint('Post Process Error: Depth is less than 0')
2674
return False
2675
# trace ended before call tree finished
2676
return self.repair(cnt)
2677
def deviceMatch(self, pid, data):
2678
found = ''
2679
# add the callgraph data to the device hierarchy
2680
borderphase = {
2681
'dpm_prepare': 'suspend_prepare',
2682
'dpm_complete': 'resume_complete'
2683
}
2684
if(self.name in borderphase):
2685
p = borderphase[self.name]
2686
list = data.dmesg[p]['list']
2687
for devname in list:
2688
dev = list[devname]
2689
if(pid == dev['pid'] and
2690
self.start <= dev['start'] and
2691
self.end >= dev['end']):
2692
cg = self.slice(dev)
2693
if cg:
2694
dev['ftrace'] = cg
2695
found = devname
2696
return found
2697
for p in data.sortedPhases():
2698
if(data.dmesg[p]['start'] <= self.start and
2699
self.start <= data.dmesg[p]['end']):
2700
list = data.dmesg[p]['list']
2701
for devname in sorted(list, key=lambda k:list[k]['start']):
2702
dev = list[devname]
2703
if(pid == dev['pid'] and
2704
self.start <= dev['start'] and
2705
self.end >= dev['end']):
2706
dev['ftrace'] = self
2707
found = devname
2708
break
2709
break
2710
return found
2711
def newActionFromFunction(self, data):
2712
name = self.name
2713
if name in ['dpm_run_callback', 'dpm_prepare', 'dpm_complete']:
2714
return
2715
fs = self.start
2716
fe = self.end
2717
if fs < data.start or fe > data.end:
2718
return
2719
phase = ''
2720
for p in data.sortedPhases():
2721
if(data.dmesg[p]['start'] <= self.start and
2722
self.start < data.dmesg[p]['end']):
2723
phase = p
2724
break
2725
if not phase:
2726
return
2727
out = data.newActionGlobal(name, fs, fe, -2)
2728
if out:
2729
phase, myname = out
2730
data.dmesg[phase]['list'][myname]['ftrace'] = self
2731
def debugPrint(self, info=''):
2732
pprint('%s pid=%d [%f - %f] %.3f us' % \
2733
(self.name, self.pid, self.start, self.end,
2734
(self.end - self.start)*1000000))
2735
for l in self.list:
2736
if l.isLeaf():
2737
pprint('%f (%02d): %s(); (%.3f us)%s' % (l.time, \
2738
l.depth, l.name, l.length*1000000, info))
2739
elif l.freturn:
2740
pprint('%f (%02d): %s} (%.3f us)%s' % (l.time, \
2741
l.depth, l.name, l.length*1000000, info))
2742
else:
2743
pprint('%f (%02d): %s() { (%.3f us)%s' % (l.time, \
2744
l.depth, l.name, l.length*1000000, info))
2745
pprint(' ')
2746
2747
class DevItem:
2748
def __init__(self, test, phase, dev):
2749
self.test = test
2750
self.phase = phase
2751
self.dev = dev
2752
def isa(self, cls):
2753
if 'htmlclass' in self.dev and cls in self.dev['htmlclass']:
2754
return True
2755
return False
2756
2757
# Class: Timeline
2758
# Description:
2759
# A container for a device timeline which calculates
2760
# all the html properties to display it correctly
2761
class Timeline:
2762
html_tblock = '<div id="block{0}" class="tblock" style="left:{1}%;width:{2}%;"><div class="tback" style="height:{3}px"></div>\n'
2763
html_device = '<div id="{0}" title="{1}" class="thread{7}" style="left:{2}%;top:{3}px;height:{4}px;width:{5}%;{8}">{6}</div>\n'
2764
html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}px;height:{3}px;background:{4}">{5}</div>\n'
2765
html_phaselet = '<div id="{0}" class="phaselet" style="left:{1}%;width:{2}%;background:{3}"></div>\n'
2766
html_legend = '<div id="p{3}" class="square" style="left:{0}%;background:{1}">&nbsp;{2}</div>\n'
2767
def __init__(self, rowheight, scaleheight):
2768
self.html = ''
2769
self.height = 0 # total timeline height
2770
self.scaleH = scaleheight # timescale (top) row height
2771
self.rowH = rowheight # device row height
2772
self.bodyH = 0 # body height
2773
self.rows = 0 # total timeline rows
2774
self.rowlines = dict()
2775
self.rowheight = dict()
2776
def createHeader(self, sv, stamp):
2777
if(not stamp['time']):
2778
return
2779
self.html += '<div class="version"><a href="https://www.intel.com/content/www/'+\
2780
'us/en/developer/topic-technology/open/pm-graph/overview.html">%s v%s</a></div>' \
2781
% (sv.title, sv.version)
2782
if sv.logmsg and sv.testlog:
2783
self.html += '<button id="showtest" class="logbtn btnfmt">log</button>'
2784
if sv.dmesglog:
2785
self.html += '<button id="showdmesg" class="logbtn btnfmt">dmesg</button>'
2786
if sv.ftracelog:
2787
self.html += '<button id="showftrace" class="logbtn btnfmt">ftrace</button>'
2788
headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
2789
self.html += headline_stamp.format(stamp['host'], stamp['kernel'],
2790
stamp['mode'], stamp['time'])
2791
if 'man' in stamp and 'plat' in stamp and 'cpu' in stamp and \
2792
stamp['man'] and stamp['plat'] and stamp['cpu']:
2793
headline_sysinfo = '<div class="stamp sysinfo">{0} {1} <i>with</i> {2}</div>\n'
2794
self.html += headline_sysinfo.format(stamp['man'], stamp['plat'], stamp['cpu'])
2795
2796
# Function: getDeviceRows
2797
# Description:
2798
# determine how may rows the device funcs will take
2799
# Arguments:
2800
# rawlist: the list of devices/actions for a single phase
2801
# Output:
2802
# The total number of rows needed to display this phase of the timeline
2803
def getDeviceRows(self, rawlist):
2804
# clear all rows and set them to undefined
2805
sortdict = dict()
2806
for item in rawlist:
2807
item.row = -1
2808
sortdict[item] = item.length
2809
sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2810
remaining = len(sortlist)
2811
rowdata = dict()
2812
row = 1
2813
# try to pack each row with as many ranges as possible
2814
while(remaining > 0):
2815
if(row not in rowdata):
2816
rowdata[row] = []
2817
for i in sortlist:
2818
if(i.row >= 0):
2819
continue
2820
s = i.time
2821
e = i.time + i.length
2822
valid = True
2823
for ritem in rowdata[row]:
2824
rs = ritem.time
2825
re = ritem.time + ritem.length
2826
if(not (((s <= rs) and (e <= rs)) or
2827
((s >= re) and (e >= re)))):
2828
valid = False
2829
break
2830
if(valid):
2831
rowdata[row].append(i)
2832
i.row = row
2833
remaining -= 1
2834
row += 1
2835
return row
2836
# Function: getPhaseRows
2837
# Description:
2838
# Organize the timeline entries into the smallest
2839
# number of rows possible, with no entry overlapping
2840
# Arguments:
2841
# devlist: the list of devices/actions in a group of contiguous phases
2842
# Output:
2843
# The total number of rows needed to display this phase of the timeline
2844
def getPhaseRows(self, devlist, row=0, sortby='length'):
2845
# clear all rows and set them to undefined
2846
remaining = len(devlist)
2847
rowdata = dict()
2848
sortdict = dict()
2849
myphases = []
2850
# initialize all device rows to -1 and calculate devrows
2851
for item in devlist:
2852
dev = item.dev
2853
tp = (item.test, item.phase)
2854
if tp not in myphases:
2855
myphases.append(tp)
2856
dev['row'] = -1
2857
if sortby == 'start':
2858
# sort by start 1st, then length 2nd
2859
sortdict[item] = (-1*float(dev['start']), float(dev['end']) - float(dev['start']))
2860
else:
2861
# sort by length 1st, then name 2nd
2862
sortdict[item] = (float(dev['end']) - float(dev['start']), item.dev['name'])
2863
if 'src' in dev:
2864
dev['devrows'] = self.getDeviceRows(dev['src'])
2865
# sort the devlist by length so that large items graph on top
2866
sortlist = sorted(sortdict, key=sortdict.get, reverse=True)
2867
orderedlist = []
2868
for item in sortlist:
2869
if item.dev['pid'] == -2:
2870
orderedlist.append(item)
2871
for item in sortlist:
2872
if item not in orderedlist:
2873
orderedlist.append(item)
2874
# try to pack each row with as many devices as possible
2875
while(remaining > 0):
2876
rowheight = 1
2877
if(row not in rowdata):
2878
rowdata[row] = []
2879
for item in orderedlist:
2880
dev = item.dev
2881
if(dev['row'] < 0):
2882
s = dev['start']
2883
e = dev['end']
2884
valid = True
2885
for ritem in rowdata[row]:
2886
rs = ritem.dev['start']
2887
re = ritem.dev['end']
2888
if(not (((s <= rs) and (e <= rs)) or
2889
((s >= re) and (e >= re)))):
2890
valid = False
2891
break
2892
if(valid):
2893
rowdata[row].append(item)
2894
dev['row'] = row
2895
remaining -= 1
2896
if 'devrows' in dev and dev['devrows'] > rowheight:
2897
rowheight = dev['devrows']
2898
for t, p in myphases:
2899
if t not in self.rowlines or t not in self.rowheight:
2900
self.rowlines[t] = dict()
2901
self.rowheight[t] = dict()
2902
if p not in self.rowlines[t] or p not in self.rowheight[t]:
2903
self.rowlines[t][p] = dict()
2904
self.rowheight[t][p] = dict()
2905
rh = self.rowH
2906
# section headers should use a different row height
2907
if len(rowdata[row]) == 1 and \
2908
'htmlclass' in rowdata[row][0].dev and \
2909
'sec' in rowdata[row][0].dev['htmlclass']:
2910
rh = 15
2911
self.rowlines[t][p][row] = rowheight
2912
self.rowheight[t][p][row] = rowheight * rh
2913
row += 1
2914
if(row > self.rows):
2915
self.rows = int(row)
2916
return row
2917
def phaseRowHeight(self, test, phase, row):
2918
return self.rowheight[test][phase][row]
2919
def phaseRowTop(self, test, phase, row):
2920
top = 0
2921
for i in sorted(self.rowheight[test][phase]):
2922
if i >= row:
2923
break
2924
top += self.rowheight[test][phase][i]
2925
return top
2926
def calcTotalRows(self):
2927
# Calculate the heights and offsets for the header and rows
2928
maxrows = 0
2929
standardphases = []
2930
for t in self.rowlines:
2931
for p in self.rowlines[t]:
2932
total = 0
2933
for i in sorted(self.rowlines[t][p]):
2934
total += self.rowlines[t][p][i]
2935
if total > maxrows:
2936
maxrows = total
2937
if total == len(self.rowlines[t][p]):
2938
standardphases.append((t, p))
2939
self.height = self.scaleH + (maxrows*self.rowH)
2940
self.bodyH = self.height - self.scaleH
2941
# if there is 1 line per row, draw them the standard way
2942
for t, p in standardphases:
2943
for i in sorted(self.rowheight[t][p]):
2944
self.rowheight[t][p][i] = float(self.bodyH)/len(self.rowlines[t][p])
2945
def createZoomBox(self, mode='command', testcount=1):
2946
# Create bounding box, add buttons
2947
html_zoombox = '<center><button id="zoomin">ZOOM IN +</button><button id="zoomout">ZOOM OUT -</button><button id="zoomdef">ZOOM 1:1</button></center>\n'
2948
html_timeline = '<div id="dmesgzoombox" class="zoombox">\n<div id="{0}" class="timeline" style="height:{1}px">\n'
2949
html_devlist1 = '<button id="devlist1" class="devlist" style="float:left;">Device Detail{0}</button>'
2950
html_devlist2 = '<button id="devlist2" class="devlist" style="float:right;">Device Detail2</button>\n'
2951
if mode != 'command':
2952
if testcount > 1:
2953
self.html += html_devlist2
2954
self.html += html_devlist1.format('1')
2955
else:
2956
self.html += html_devlist1.format('')
2957
self.html += html_zoombox
2958
self.html += html_timeline.format('dmesg', self.height)
2959
# Function: createTimeScale
2960
# Description:
2961
# Create the timescale for a timeline block
2962
# Arguments:
2963
# m0: start time (mode begin)
2964
# mMax: end time (mode end)
2965
# tTotal: total timeline time
2966
# mode: suspend or resume
2967
# Output:
2968
# The html code needed to display the time scale
2969
def createTimeScale(self, m0, mMax, tTotal, mode):
2970
timescale = '<div class="t" style="right:{0}%">{1}</div>\n'
2971
rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">{0}</div>\n'
2972
output = '<div class="timescale">\n'
2973
# set scale for timeline
2974
mTotal = mMax - m0
2975
tS = 0.1
2976
if(tTotal <= 0):
2977
return output+'</div>\n'
2978
if(tTotal > 4):
2979
tS = 1
2980
divTotal = int(mTotal/tS) + 1
2981
divEdge = (mTotal - tS*(divTotal-1))*100/mTotal
2982
for i in range(divTotal):
2983
htmlline = ''
2984
if(mode == 'suspend'):
2985
pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal) - divEdge)
2986
val = '%0.fms' % (float(i-divTotal+1)*tS*1000)
2987
if(i == divTotal - 1):
2988
val = mode
2989
htmlline = timescale.format(pos, val)
2990
else:
2991
pos = '%0.3f' % (100 - ((float(i)*tS*100)/mTotal))
2992
val = '%0.fms' % (float(i)*tS*1000)
2993
htmlline = timescale.format(pos, val)
2994
if(i == 0):
2995
htmlline = rline.format(mode)
2996
output += htmlline
2997
self.html += output+'</div>\n'
2998
2999
# Class: TestProps
3000
# Description:
3001
# A list of values describing the properties of these test runs
3002
class TestProps:
3003
stampfmt = r'# [a-z]*-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-'+\
3004
r'(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})'+\
3005
r' (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$'
3006
wififmt = r'^# wifi *(?P<d>\S*) *(?P<s>\S*) *(?P<t>[0-9\.]+).*'
3007
tstatfmt = r'^# turbostat (?P<t>\S*)'
3008
testerrfmt = r'^# enter_sleep_error (?P<e>.*)'
3009
sysinfofmt = r'^# sysinfo .*'
3010
cmdlinefmt = r'^# command \| (?P<cmd>.*)'
3011
kparamsfmt = r'^# kparams \| (?P<kp>.*)'
3012
devpropfmt = r'# Device Properties: .*'
3013
pinfofmt = r'# platform-(?P<val>[a-z,A-Z,0-9,_]*): (?P<info>.*)'
3014
tracertypefmt = r'# tracer: (?P<t>.*)'
3015
firmwarefmt = r'# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
3016
procexecfmt = r'ps - (?P<ps>.*)$'
3017
procmultifmt = r'@(?P<n>[0-9]*)\|(?P<ps>.*)$'
3018
ftrace_line_fmt_fg = \
3019
r'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
3020
r' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
3021
r'[ +!#\*@$]*(?P<dur>[0-9\.]*) .*\| (?P<msg>.*)'
3022
ftrace_line_fmt_nop = \
3023
r' *(?P<proc>.*)-(?P<pid>[0-9]*) *\[(?P<cpu>[0-9]*)\] *'+\
3024
r'(?P<flags>\S*) *(?P<time>[0-9\.]*): *'+\
3025
r'(?P<msg>.*)'
3026
machinesuspend = r'machine_suspend\[.*'
3027
multiproclist = dict()
3028
multiproctime = 0.0
3029
multiproccnt = 0
3030
def __init__(self):
3031
self.stamp = ''
3032
self.sysinfo = ''
3033
self.cmdline = ''
3034
self.testerror = []
3035
self.turbostat = []
3036
self.wifi = []
3037
self.fwdata = []
3038
self.ftrace_line_fmt = self.ftrace_line_fmt_nop
3039
self.cgformat = False
3040
self.data = 0
3041
self.ktemp = dict()
3042
def setTracerType(self, tracer):
3043
if(tracer == 'function_graph'):
3044
self.cgformat = True
3045
self.ftrace_line_fmt = self.ftrace_line_fmt_fg
3046
elif(tracer == 'nop'):
3047
self.ftrace_line_fmt = self.ftrace_line_fmt_nop
3048
else:
3049
doError('Invalid tracer format: [%s]' % tracer)
3050
def stampInfo(self, line, sv):
3051
if re.match(self.stampfmt, line):
3052
self.stamp = line
3053
return True
3054
elif re.match(self.sysinfofmt, line):
3055
self.sysinfo = line
3056
return True
3057
elif re.match(self.tstatfmt, line):
3058
self.turbostat.append(line)
3059
return True
3060
elif re.match(self.wififmt, line):
3061
self.wifi.append(line)
3062
return True
3063
elif re.match(self.testerrfmt, line):
3064
self.testerror.append(line)
3065
return True
3066
elif re.match(self.firmwarefmt, line):
3067
self.fwdata.append(line)
3068
return True
3069
elif(re.match(self.devpropfmt, line)):
3070
self.parseDevprops(line, sv)
3071
return True
3072
elif(re.match(self.pinfofmt, line)):
3073
self.parsePlatformInfo(line, sv)
3074
return True
3075
m = re.match(self.cmdlinefmt, line)
3076
if m:
3077
self.cmdline = m.group('cmd')
3078
return True
3079
m = re.match(self.tracertypefmt, line)
3080
if(m):
3081
self.setTracerType(m.group('t'))
3082
return True
3083
return False
3084
def parseStamp(self, data, sv):
3085
# global test data
3086
m = re.match(self.stampfmt, self.stamp)
3087
if not self.stamp or not m:
3088
doError('data does not include the expected stamp')
3089
data.stamp = {'time': '', 'host': '', 'mode': ''}
3090
dt = datetime(int(m.group('y'))+2000, int(m.group('m')),
3091
int(m.group('d')), int(m.group('H')), int(m.group('M')),
3092
int(m.group('S')))
3093
data.stamp['time'] = dt.strftime('%B %d %Y, %I:%M:%S %p')
3094
data.stamp['host'] = m.group('host')
3095
data.stamp['mode'] = m.group('mode')
3096
data.stamp['kernel'] = m.group('kernel')
3097
if re.match(self.sysinfofmt, self.sysinfo):
3098
for f in self.sysinfo.split('|'):
3099
if '#' in f:
3100
continue
3101
tmp = f.strip().split(':', 1)
3102
key = tmp[0]
3103
val = tmp[1]
3104
data.stamp[key] = val
3105
sv.hostname = data.stamp['host']
3106
sv.suspendmode = data.stamp['mode']
3107
if sv.suspendmode == 'freeze':
3108
self.machinesuspend = r'timekeeping_freeze\[.*'
3109
else:
3110
self.machinesuspend = r'machine_suspend\[.*'
3111
if sv.suspendmode == 'command' and sv.ftracefile != '':
3112
modes = ['on', 'freeze', 'standby', 'mem', 'disk']
3113
fp = sv.openlog(sv.ftracefile, 'r')
3114
for line in fp:
3115
m = re.match(r'.* machine_suspend\[(?P<mode>.*)\]', line)
3116
if m and m.group('mode') in ['1', '2', '3', '4']:
3117
sv.suspendmode = modes[int(m.group('mode'))]
3118
data.stamp['mode'] = sv.suspendmode
3119
break
3120
fp.close()
3121
sv.cmdline = self.cmdline
3122
if not sv.stamp:
3123
sv.stamp = data.stamp
3124
# firmware data
3125
if sv.suspendmode == 'mem' and len(self.fwdata) > data.testnumber:
3126
m = re.match(self.firmwarefmt, self.fwdata[data.testnumber])
3127
if m:
3128
data.fwSuspend, data.fwResume = int(m.group('s')), int(m.group('r'))
3129
if(data.fwSuspend > 0 or data.fwResume > 0):
3130
data.fwValid = True
3131
# turbostat data
3132
if len(self.turbostat) > data.testnumber:
3133
m = re.match(self.tstatfmt, self.turbostat[data.testnumber])
3134
if m:
3135
data.turbostat = m.group('t')
3136
# wifi data
3137
if len(self.wifi) > data.testnumber:
3138
m = re.match(self.wififmt, self.wifi[data.testnumber])
3139
if m:
3140
data.wifi = {'dev': m.group('d'), 'stat': m.group('s'),
3141
'time': float(m.group('t'))}
3142
data.stamp['wifi'] = m.group('d')
3143
# sleep mode enter errors
3144
if len(self.testerror) > data.testnumber:
3145
m = re.match(self.testerrfmt, self.testerror[data.testnumber])
3146
if m:
3147
data.enterfail = m.group('e')
3148
def devprops(self, data):
3149
props = dict()
3150
devlist = data.split(';')
3151
for dev in devlist:
3152
f = dev.split(',')
3153
if len(f) < 3:
3154
continue
3155
dev = f[0]
3156
props[dev] = DevProps()
3157
props[dev].altname = f[1]
3158
if int(f[2]):
3159
props[dev].isasync = True
3160
else:
3161
props[dev].isasync = False
3162
return props
3163
def parseDevprops(self, line, sv):
3164
idx = line.index(': ') + 2
3165
if idx >= len(line):
3166
return
3167
props = self.devprops(line[idx:])
3168
if sv.suspendmode == 'command' and 'testcommandstring' in props:
3169
sv.testcommand = props['testcommandstring'].altname
3170
sv.devprops = props
3171
def parsePlatformInfo(self, line, sv):
3172
m = re.match(self.pinfofmt, line)
3173
if not m:
3174
return
3175
name, info = m.group('val'), m.group('info')
3176
if name == 'devinfo':
3177
sv.devprops = self.devprops(sv.b64unzip(info))
3178
return
3179
elif name == 'testcmd':
3180
sv.testcommand = info
3181
return
3182
field = info.split('|')
3183
if len(field) < 2:
3184
return
3185
cmdline = field[0].strip()
3186
output = sv.b64unzip(field[1].strip())
3187
sv.platinfo.append([name, cmdline, output])
3188
3189
# Class: TestRun
3190
# Description:
3191
# A container for a suspend/resume test run. This is necessary as
3192
# there could be more than one, and they need to be separate.
3193
class TestRun:
3194
def __init__(self, dataobj):
3195
self.data = dataobj
3196
self.ftemp = dict()
3197
self.ttemp = dict()
3198
3199
class ProcessMonitor:
3200
maxchars = 512
3201
def __init__(self):
3202
self.proclist = dict()
3203
self.running = False
3204
def procstat(self):
3205
c = ['cat /proc/[1-9]*/stat 2>/dev/null']
3206
process = Popen(c, shell=True, stdout=PIPE)
3207
running = dict()
3208
for line in process.stdout:
3209
data = ascii(line).split()
3210
pid = data[0]
3211
name = re.sub('[()]', '', data[1])
3212
user = int(data[13])
3213
kern = int(data[14])
3214
kjiff = ujiff = 0
3215
if pid not in self.proclist:
3216
self.proclist[pid] = {'name' : name, 'user' : user, 'kern' : kern}
3217
else:
3218
val = self.proclist[pid]
3219
ujiff = user - val['user']
3220
kjiff = kern - val['kern']
3221
val['user'] = user
3222
val['kern'] = kern
3223
if ujiff > 0 or kjiff > 0:
3224
running[pid] = ujiff + kjiff
3225
process.wait()
3226
out = ['']
3227
for pid in running:
3228
jiffies = running[pid]
3229
val = self.proclist[pid]
3230
if len(out[-1]) > self.maxchars:
3231
out.append('')
3232
elif len(out[-1]) > 0:
3233
out[-1] += ','
3234
out[-1] += '%s-%s %d' % (val['name'], pid, jiffies)
3235
if len(out) > 1:
3236
for line in out:
3237
sysvals.fsetVal('ps - @%d|%s' % (len(out), line), 'trace_marker')
3238
else:
3239
sysvals.fsetVal('ps - %s' % out[0], 'trace_marker')
3240
def processMonitor(self, tid):
3241
while self.running:
3242
self.procstat()
3243
def start(self):
3244
self.thread = Thread(target=self.processMonitor, args=(0,))
3245
self.running = True
3246
self.thread.start()
3247
def stop(self):
3248
self.running = False
3249
3250
# ----------------- FUNCTIONS --------------------
3251
3252
# Function: doesTraceLogHaveTraceEvents
3253
# Description:
3254
# Quickly determine if the ftrace log has all of the trace events,
3255
# markers, and/or kprobes required for primary parsing.
3256
def doesTraceLogHaveTraceEvents():
3257
kpcheck = ['_cal: (', '_ret: (']
3258
techeck = ['suspend_resume', 'device_pm_callback', 'tracing_mark_write']
3259
tmcheck = ['SUSPEND START', 'RESUME COMPLETE']
3260
sysvals.usekprobes = False
3261
fp = sysvals.openlog(sysvals.ftracefile, 'r')
3262
for line in fp:
3263
# check for kprobes
3264
if not sysvals.usekprobes:
3265
for i in kpcheck:
3266
if i in line:
3267
sysvals.usekprobes = True
3268
# check for all necessary trace events
3269
check = techeck[:]
3270
for i in techeck:
3271
if i in line:
3272
check.remove(i)
3273
techeck = check
3274
# check for all necessary trace markers
3275
check = tmcheck[:]
3276
for i in tmcheck:
3277
if i in line:
3278
check.remove(i)
3279
tmcheck = check
3280
fp.close()
3281
sysvals.usetraceevents = True if len(techeck) < 3 else False
3282
sysvals.usetracemarkers = True if len(tmcheck) == 0 else False
3283
3284
# Function: appendIncompleteTraceLog
3285
# Description:
3286
# Adds callgraph data which lacks trace event data. This is only
3287
# for timelines generated from 3.15 or older
3288
# Arguments:
3289
# testruns: the array of Data objects obtained from parseKernelLog
3290
def appendIncompleteTraceLog(testruns):
3291
# create TestRun vessels for ftrace parsing
3292
testcnt = len(testruns)
3293
testidx = 0
3294
testrun = []
3295
for data in testruns:
3296
testrun.append(TestRun(data))
3297
3298
# extract the callgraph and traceevent data
3299
sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3300
os.path.basename(sysvals.ftracefile))
3301
tp = TestProps()
3302
tf = sysvals.openlog(sysvals.ftracefile, 'r')
3303
data = 0
3304
for line in tf:
3305
# remove any latent carriage returns
3306
line = line.replace('\r\n', '')
3307
if tp.stampInfo(line, sysvals):
3308
continue
3309
# parse only valid lines, if this is not one move on
3310
m = re.match(tp.ftrace_line_fmt, line)
3311
if(not m):
3312
continue
3313
# gather the basic message data from the line
3314
m_time = m.group('time')
3315
m_pid = m.group('pid')
3316
m_msg = m.group('msg')
3317
if(tp.cgformat):
3318
m_param3 = m.group('dur')
3319
else:
3320
m_param3 = 'traceevent'
3321
if(m_time and m_pid and m_msg):
3322
t = FTraceLine(m_time, m_msg, m_param3)
3323
pid = int(m_pid)
3324
else:
3325
continue
3326
# the line should be a call, return, or event
3327
if(not t.fcall and not t.freturn and not t.fevent):
3328
continue
3329
# look for the suspend start marker
3330
if(t.startMarker()):
3331
data = testrun[testidx].data
3332
tp.parseStamp(data, sysvals)
3333
data.setStart(t.time, t.name)
3334
continue
3335
if(not data):
3336
continue
3337
# find the end of resume
3338
if(t.endMarker()):
3339
data.setEnd(t.time, t.name)
3340
testidx += 1
3341
if(testidx >= testcnt):
3342
break
3343
continue
3344
# trace event processing
3345
if(t.fevent):
3346
continue
3347
# call/return processing
3348
elif sysvals.usecallgraph:
3349
# create a callgraph object for the data
3350
if(pid not in testrun[testidx].ftemp):
3351
testrun[testidx].ftemp[pid] = []
3352
testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3353
# when the call is finished, see which device matches it
3354
cg = testrun[testidx].ftemp[pid][-1]
3355
res = cg.addLine(t)
3356
if(res != 0):
3357
testrun[testidx].ftemp[pid].append(FTraceCallGraph(pid, sysvals))
3358
if(res == -1):
3359
testrun[testidx].ftemp[pid][-1].addLine(t)
3360
tf.close()
3361
3362
for test in testrun:
3363
# add the callgraph data to the device hierarchy
3364
for pid in test.ftemp:
3365
for cg in test.ftemp[pid]:
3366
if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3367
continue
3368
if(not cg.postProcess()):
3369
id = 'task %s cpu %s' % (pid, m.group('cpu'))
3370
sysvals.vprint('Sanity check failed for '+\
3371
id+', ignoring this callback')
3372
continue
3373
callstart = cg.start
3374
callend = cg.end
3375
for p in test.data.sortedPhases():
3376
if(test.data.dmesg[p]['start'] <= callstart and
3377
callstart <= test.data.dmesg[p]['end']):
3378
list = test.data.dmesg[p]['list']
3379
for devname in list:
3380
dev = list[devname]
3381
if(pid == dev['pid'] and
3382
callstart <= dev['start'] and
3383
callend >= dev['end']):
3384
dev['ftrace'] = cg
3385
break
3386
3387
# Function: loadTraceLog
3388
# Description:
3389
# load the ftrace file into memory and fix up any ordering issues
3390
# Output:
3391
# TestProps instance and an array of lines in proper order
3392
def loadTraceLog():
3393
tp, data, lines, trace = TestProps(), dict(), [], []
3394
tf = sysvals.openlog(sysvals.ftracefile, 'r')
3395
for line in tf:
3396
# remove any latent carriage returns
3397
line = line.replace('\r\n', '')
3398
if tp.stampInfo(line, sysvals):
3399
continue
3400
# ignore all other commented lines
3401
if line[0] == '#':
3402
continue
3403
# ftrace line: parse only valid lines
3404
m = re.match(tp.ftrace_line_fmt, line)
3405
if(not m):
3406
continue
3407
dur = m.group('dur') if tp.cgformat else 'traceevent'
3408
info = (m.group('time'), m.group('proc'), m.group('pid'),
3409
m.group('msg'), dur)
3410
# group the data by timestamp
3411
t = float(info[0])
3412
if t in data:
3413
data[t].append(info)
3414
else:
3415
data[t] = [info]
3416
# we only care about trace event ordering
3417
if (info[3].startswith('suspend_resume:') or \
3418
info[3].startswith('tracing_mark_write:')) and t not in trace:
3419
trace.append(t)
3420
tf.close()
3421
for t in sorted(data):
3422
first, last, blk = [], [], data[t]
3423
if len(blk) > 1 and t in trace:
3424
# move certain lines to the start or end of a timestamp block
3425
for i in range(len(blk)):
3426
if 'SUSPEND START' in blk[i][3]:
3427
first.append(i)
3428
elif re.match(r'.* timekeeping_freeze.*begin', blk[i][3]):
3429
last.append(i)
3430
elif re.match(r'.* timekeeping_freeze.*end', blk[i][3]):
3431
first.append(i)
3432
elif 'RESUME COMPLETE' in blk[i][3]:
3433
last.append(i)
3434
if len(first) == 1 and len(last) == 0:
3435
blk.insert(0, blk.pop(first[0]))
3436
elif len(last) == 1 and len(first) == 0:
3437
blk.append(blk.pop(last[0]))
3438
for info in blk:
3439
lines.append(info)
3440
return (tp, lines)
3441
3442
# Function: parseTraceLog
3443
# Description:
3444
# Analyze an ftrace log output file generated from this app during
3445
# the execution phase. Used when the ftrace log is the primary data source
3446
# and includes the suspend_resume and device_pm_callback trace events
3447
# The ftrace filename is taken from sysvals
3448
# Output:
3449
# An array of Data objects
3450
def parseTraceLog(live=False):
3451
sysvals.vprint('Analyzing the ftrace data (%s)...' % \
3452
os.path.basename(sysvals.ftracefile))
3453
if(os.path.exists(sysvals.ftracefile) == False):
3454
doError('%s does not exist' % sysvals.ftracefile)
3455
if not live:
3456
sysvals.setupAllKprobes()
3457
ksuscalls = ['ksys_sync', 'pm_prepare_console']
3458
krescalls = ['pm_restore_console']
3459
tracewatch = ['irq_wakeup']
3460
if sysvals.usekprobes:
3461
tracewatch += ['sync_filesystems', 'freeze_processes', 'syscore_suspend',
3462
'syscore_resume', 'console_resume_all', 'thaw_processes', 'CPU_ON',
3463
'CPU_OFF', 'acpi_suspend']
3464
3465
# extract the callgraph and traceevent data
3466
s2idle_enter = hwsus = False
3467
testruns, testdata = [], []
3468
testrun, data, limbo = 0, 0, True
3469
phase = 'suspend_prepare'
3470
tp, tf = loadTraceLog()
3471
for m_time, m_proc, m_pid, m_msg, m_param3 in tf:
3472
# gather the basic message data from the line
3473
if(m_time and m_pid and m_msg):
3474
t = FTraceLine(m_time, m_msg, m_param3)
3475
pid = int(m_pid)
3476
else:
3477
continue
3478
# the line should be a call, return, or event
3479
if(not t.fcall and not t.freturn and not t.fevent):
3480
continue
3481
# find the start of suspend
3482
if(t.startMarker()):
3483
data, limbo = Data(len(testdata)), False
3484
testdata.append(data)
3485
testrun = TestRun(data)
3486
testruns.append(testrun)
3487
tp.parseStamp(data, sysvals)
3488
data.setStart(t.time, t.name)
3489
data.first_suspend_prepare = True
3490
phase = data.setPhase('suspend_prepare', t.time, True)
3491
continue
3492
if(not data or limbo):
3493
continue
3494
# process cpu exec line
3495
if t.type == 'tracing_mark_write':
3496
if t.name == 'CMD COMPLETE' and data.tKernRes == 0:
3497
data.tKernRes = t.time
3498
m = re.match(tp.procexecfmt, t.name)
3499
if(m):
3500
parts, msg = 1, m.group('ps')
3501
m = re.match(tp.procmultifmt, msg)
3502
if(m):
3503
parts, msg = int(m.group('n')), m.group('ps')
3504
if tp.multiproccnt == 0:
3505
tp.multiproctime = t.time
3506
tp.multiproclist = dict()
3507
proclist = tp.multiproclist
3508
tp.multiproccnt += 1
3509
else:
3510
proclist = dict()
3511
tp.multiproccnt = 0
3512
for ps in msg.split(','):
3513
val = ps.split()
3514
if not val or len(val) != 2:
3515
continue
3516
name = val[0].replace('--', '-')
3517
proclist[name] = int(val[1])
3518
if parts == 1:
3519
data.pstl[t.time] = proclist
3520
elif parts == tp.multiproccnt:
3521
data.pstl[tp.multiproctime] = proclist
3522
tp.multiproccnt = 0
3523
continue
3524
# find the end of resume
3525
if(t.endMarker()):
3526
if data.tKernRes == 0:
3527
data.tKernRes = t.time
3528
data.handleEndMarker(t.time, t.name)
3529
if(not sysvals.usetracemarkers):
3530
# no trace markers? then quit and be sure to finish recording
3531
# the event we used to trigger resume end
3532
if('thaw_processes' in testrun.ttemp and len(testrun.ttemp['thaw_processes']) > 0):
3533
# if an entry exists, assume this is its end
3534
testrun.ttemp['thaw_processes'][-1]['end'] = t.time
3535
limbo = True
3536
continue
3537
# trace event processing
3538
if(t.fevent):
3539
if(t.type == 'suspend_resume'):
3540
# suspend_resume trace events have two types, begin and end
3541
if(re.match(r'(?P<name>.*) begin$', t.name)):
3542
isbegin = True
3543
elif(re.match(r'(?P<name>.*) end$', t.name)):
3544
isbegin = False
3545
else:
3546
continue
3547
if '[' in t.name:
3548
m = re.match(r'(?P<name>.*)\[.*', t.name)
3549
else:
3550
m = re.match(r'(?P<name>.*) .*', t.name)
3551
name = m.group('name')
3552
# ignore these events
3553
if(name.split('[')[0] in tracewatch):
3554
continue
3555
# -- phase changes --
3556
# start of kernel suspend
3557
if(re.match(r'suspend_enter\[.*', t.name)):
3558
if(isbegin and data.tKernSus == 0):
3559
data.tKernSus = t.time
3560
continue
3561
# suspend_prepare start
3562
elif(re.match(r'dpm_prepare\[.*', t.name)):
3563
if isbegin and data.first_suspend_prepare:
3564
data.first_suspend_prepare = False
3565
if data.tKernSus == 0:
3566
data.tKernSus = t.time
3567
continue
3568
phase = data.setPhase('suspend_prepare', t.time, isbegin)
3569
continue
3570
# suspend start
3571
elif(re.match(r'dpm_suspend\[.*', t.name)):
3572
phase = data.setPhase('suspend', t.time, isbegin)
3573
continue
3574
# suspend_late start
3575
elif(re.match(r'dpm_suspend_late\[.*', t.name)):
3576
phase = data.setPhase('suspend_late', t.time, isbegin)
3577
continue
3578
# suspend_noirq start
3579
elif(re.match(r'dpm_suspend_noirq\[.*', t.name)):
3580
phase = data.setPhase('suspend_noirq', t.time, isbegin)
3581
continue
3582
# suspend_machine/resume_machine
3583
elif(re.match(tp.machinesuspend, t.name)):
3584
lp = data.lastPhase()
3585
if(isbegin):
3586
hwsus = True
3587
if lp.startswith('resume_machine'):
3588
# trim out s2idle loops, track time trying to freeze
3589
llp = data.lastPhase(2)
3590
if llp.startswith('suspend_machine'):
3591
if 'waking' not in data.dmesg[llp]:
3592
data.dmesg[llp]['waking'] = [0, 0.0]
3593
data.dmesg[llp]['waking'][0] += 1
3594
data.dmesg[llp]['waking'][1] += \
3595
t.time - data.dmesg[lp]['start']
3596
data.currphase = ''
3597
del data.dmesg[lp]
3598
continue
3599
phase = data.setPhase('suspend_machine', data.dmesg[lp]['end'], True)
3600
data.setPhase(phase, t.time, False)
3601
if data.tSuspended == 0:
3602
data.tSuspended = t.time
3603
else:
3604
if lp.startswith('resume_machine'):
3605
data.dmesg[lp]['end'] = t.time
3606
continue
3607
phase = data.setPhase('resume_machine', t.time, True)
3608
if(sysvals.suspendmode in ['mem', 'disk']):
3609
susp = phase.replace('resume', 'suspend')
3610
if susp in data.dmesg:
3611
data.dmesg[susp]['end'] = t.time
3612
data.tSuspended = t.time
3613
data.tResumed = t.time
3614
continue
3615
# resume_noirq start
3616
elif(re.match(r'dpm_resume_noirq\[.*', t.name)):
3617
phase = data.setPhase('resume_noirq', t.time, isbegin)
3618
continue
3619
# resume_early start
3620
elif(re.match(r'dpm_resume_early\[.*', t.name)):
3621
phase = data.setPhase('resume_early', t.time, isbegin)
3622
continue
3623
# resume start
3624
elif(re.match(r'dpm_resume\[.*', t.name)):
3625
phase = data.setPhase('resume', t.time, isbegin)
3626
continue
3627
# resume complete start
3628
elif(re.match(r'dpm_complete\[.*', t.name)):
3629
phase = data.setPhase('resume_complete', t.time, isbegin)
3630
continue
3631
# skip trace events inside devices calls
3632
if(not data.isTraceEventOutsideDeviceCalls(pid, t.time)):
3633
continue
3634
# global events (outside device calls) are graphed
3635
if(name not in testrun.ttemp):
3636
testrun.ttemp[name] = []
3637
# special handling for s2idle_enter
3638
if name == 'machine_suspend':
3639
if hwsus:
3640
s2idle_enter = hwsus = False
3641
elif s2idle_enter and not isbegin:
3642
if(len(testrun.ttemp[name]) > 0):
3643
testrun.ttemp[name][-1]['end'] = t.time
3644
testrun.ttemp[name][-1]['loop'] += 1
3645
elif not s2idle_enter and isbegin:
3646
s2idle_enter = True
3647
testrun.ttemp[name].append({'begin': t.time,
3648
'end': t.time, 'pid': pid, 'loop': 0})
3649
continue
3650
if(isbegin):
3651
# create a new list entry
3652
testrun.ttemp[name].append(\
3653
{'begin': t.time, 'end': t.time, 'pid': pid})
3654
else:
3655
if(len(testrun.ttemp[name]) > 0):
3656
# if an entry exists, assume this is its end
3657
testrun.ttemp[name][-1]['end'] = t.time
3658
# device callback start
3659
elif(t.type == 'device_pm_callback_start'):
3660
if phase not in data.dmesg:
3661
continue
3662
m = re.match(r'(?P<drv>.*) (?P<d>.*), parent: *(?P<p>.*), .*',\
3663
t.name);
3664
if(not m):
3665
continue
3666
drv = m.group('drv')
3667
n = m.group('d')
3668
p = m.group('p')
3669
if(n and p):
3670
data.newAction(phase, n, pid, p, t.time, -1, drv)
3671
if pid not in data.devpids:
3672
data.devpids.append(pid)
3673
# device callback finish
3674
elif(t.type == 'device_pm_callback_end'):
3675
if phase not in data.dmesg:
3676
continue
3677
m = re.match(r'(?P<drv>.*) (?P<d>.*), err.*', t.name);
3678
if(not m):
3679
continue
3680
n = m.group('d')
3681
dev = data.findDevice(phase, n)
3682
if dev:
3683
dev['length'] = t.time - dev['start']
3684
dev['end'] = t.time
3685
# kprobe event processing
3686
elif(t.fkprobe):
3687
kprobename = t.type
3688
kprobedata = t.name
3689
key = (kprobename, pid)
3690
# displayname is generated from kprobe data
3691
displayname = ''
3692
if(t.fcall):
3693
displayname = sysvals.kprobeDisplayName(kprobename, kprobedata)
3694
if not displayname:
3695
continue
3696
if(key not in tp.ktemp):
3697
tp.ktemp[key] = []
3698
tp.ktemp[key].append({
3699
'pid': pid,
3700
'begin': t.time,
3701
'end': -1,
3702
'name': displayname,
3703
'cdata': kprobedata,
3704
'proc': m_proc,
3705
})
3706
# start of kernel resume
3707
if(data.tKernSus == 0 and phase == 'suspend_prepare' \
3708
and kprobename in ksuscalls):
3709
data.tKernSus = t.time
3710
elif(t.freturn):
3711
if(key not in tp.ktemp) or len(tp.ktemp[key]) < 1:
3712
continue
3713
e = next((x for x in reversed(tp.ktemp[key]) if x['end'] < 0), 0)
3714
if not e:
3715
continue
3716
if (t.time - e['begin']) * 1000 < sysvals.mindevlen:
3717
tp.ktemp[key].pop()
3718
continue
3719
e['end'] = t.time
3720
e['rdata'] = kprobedata
3721
# end of kernel resume
3722
if(phase != 'suspend_prepare' and kprobename in krescalls):
3723
if phase in data.dmesg:
3724
data.dmesg[phase]['end'] = t.time
3725
data.tKernRes = t.time
3726
3727
# callgraph processing
3728
elif sysvals.usecallgraph:
3729
# create a callgraph object for the data
3730
key = (m_proc, pid)
3731
if(key not in testrun.ftemp):
3732
testrun.ftemp[key] = []
3733
testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3734
# when the call is finished, see which device matches it
3735
cg = testrun.ftemp[key][-1]
3736
res = cg.addLine(t)
3737
if(res != 0):
3738
testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
3739
if(res == -1):
3740
testrun.ftemp[key][-1].addLine(t)
3741
if len(testdata) < 1:
3742
sysvals.vprint('WARNING: ftrace start marker is missing')
3743
if data and not data.devicegroups:
3744
sysvals.vprint('WARNING: ftrace end marker is missing')
3745
data.handleEndMarker(t.time, t.name)
3746
3747
if sysvals.suspendmode == 'command':
3748
for test in testruns:
3749
for p in test.data.sortedPhases():
3750
if p == 'suspend_prepare':
3751
test.data.dmesg[p]['start'] = test.data.start
3752
test.data.dmesg[p]['end'] = test.data.end
3753
else:
3754
test.data.dmesg[p]['start'] = test.data.end
3755
test.data.dmesg[p]['end'] = test.data.end
3756
test.data.tSuspended = test.data.end
3757
test.data.tResumed = test.data.end
3758
test.data.fwValid = False
3759
3760
# dev source and procmon events can be unreadable with mixed phase height
3761
if sysvals.usedevsrc or sysvals.useprocmon:
3762
sysvals.mixedphaseheight = False
3763
3764
# expand phase boundaries so there are no gaps
3765
for data in testdata:
3766
lp = data.sortedPhases()[0]
3767
for p in data.sortedPhases():
3768
if(p != lp and not ('machine' in p and 'machine' in lp)):
3769
data.dmesg[lp]['end'] = data.dmesg[p]['start']
3770
lp = p
3771
3772
for i in range(len(testruns)):
3773
test = testruns[i]
3774
data = test.data
3775
# find the total time range for this test (begin, end)
3776
tlb, tle = data.start, data.end
3777
if i < len(testruns) - 1:
3778
tle = testruns[i+1].data.start
3779
# add the process usage data to the timeline
3780
if sysvals.useprocmon:
3781
data.createProcessUsageEvents()
3782
# add the traceevent data to the device hierarchy
3783
if(sysvals.usetraceevents):
3784
# add actual trace funcs
3785
for name in sorted(test.ttemp):
3786
for event in test.ttemp[name]:
3787
if event['end'] - event['begin'] <= 0:
3788
continue
3789
title = name
3790
if name == 'machine_suspend' and 'loop' in event:
3791
title = 's2idle_enter_%dx' % event['loop']
3792
data.newActionGlobal(title, event['begin'], event['end'], event['pid'])
3793
# add the kprobe based virtual tracefuncs as actual devices
3794
for key in sorted(tp.ktemp):
3795
name, pid = key
3796
if name not in sysvals.tracefuncs:
3797
continue
3798
if pid not in data.devpids:
3799
data.devpids.append(pid)
3800
for e in tp.ktemp[key]:
3801
kb, ke = e['begin'], e['end']
3802
if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3803
continue
3804
color = sysvals.kprobeColor(name)
3805
data.newActionGlobal(e['name'], kb, ke, pid, color)
3806
# add config base kprobes and dev kprobes
3807
if sysvals.usedevsrc:
3808
for key in sorted(tp.ktemp):
3809
name, pid = key
3810
if name in sysvals.tracefuncs or name not in sysvals.dev_tracefuncs:
3811
continue
3812
for e in tp.ktemp[key]:
3813
kb, ke = e['begin'], e['end']
3814
if ke - kb < 0.000001 or tlb > kb or tle <= kb:
3815
continue
3816
data.addDeviceFunctionCall(e['name'], name, e['proc'], pid, kb,
3817
ke, e['cdata'], e['rdata'])
3818
if sysvals.usecallgraph:
3819
# add the callgraph data to the device hierarchy
3820
sortlist = dict()
3821
for key in sorted(test.ftemp):
3822
proc, pid = key
3823
for cg in test.ftemp[key]:
3824
if len(cg.list) < 1 or cg.invalid or (cg.end - cg.start == 0):
3825
continue
3826
if(not cg.postProcess()):
3827
id = 'task %s' % (pid)
3828
sysvals.vprint('Sanity check failed for '+\
3829
id+', ignoring this callback')
3830
continue
3831
# match cg data to devices
3832
devname = ''
3833
if sysvals.suspendmode != 'command':
3834
devname = cg.deviceMatch(pid, data)
3835
if not devname:
3836
sortkey = '%f%f%d' % (cg.start, cg.end, pid)
3837
sortlist[sortkey] = cg
3838
elif len(cg.list) > 1000000 and cg.name != sysvals.ftopfunc:
3839
sysvals.vprint('WARNING: the callgraph for %s is massive (%d lines)' %\
3840
(devname, len(cg.list)))
3841
# create blocks for orphan cg data
3842
for sortkey in sorted(sortlist):
3843
cg = sortlist[sortkey]
3844
name = cg.name
3845
if sysvals.isCallgraphFunc(name):
3846
sysvals.vprint('Callgraph found for task %d: %.3fms, %s' % (cg.pid, (cg.end - cg.start)*1000, name))
3847
cg.newActionFromFunction(data)
3848
if sysvals.suspendmode == 'command':
3849
return (testdata, '')
3850
3851
# fill in any missing phases
3852
error = []
3853
for data in testdata:
3854
tn = '' if len(testdata) == 1 else ('%d' % (data.testnumber + 1))
3855
terr = ''
3856
phasedef = data.phasedef
3857
lp = 'suspend_prepare'
3858
for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
3859
if p not in data.dmesg:
3860
if not terr:
3861
ph = p if 'machine' in p else lp
3862
if p == 'suspend_machine':
3863
sm = sysvals.suspendmode
3864
if sm in suspendmodename:
3865
sm = suspendmodename[sm]
3866
terr = 'test%s did not enter %s power mode' % (tn, sm)
3867
else:
3868
terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
3869
pprint('TEST%s FAILED: %s' % (tn, terr))
3870
error.append(terr)
3871
if data.tSuspended == 0:
3872
data.tSuspended = data.dmesg[lp]['end']
3873
if data.tResumed == 0:
3874
data.tResumed = data.dmesg[lp]['end']
3875
data.fwValid = False
3876
sysvals.vprint('WARNING: phase "%s" is missing!' % p)
3877
lp = p
3878
if not terr and 'dev' in data.wifi and data.wifi['stat'] == 'timeout':
3879
terr = '%s%s failed in wifi_resume <i>(%s %.0fs timeout)</i>' % \
3880
(sysvals.suspendmode, tn, data.wifi['dev'], data.wifi['time'])
3881
error.append(terr)
3882
if not terr and data.enterfail:
3883
pprint('test%s FAILED: enter %s failed with %s' % (tn, sysvals.suspendmode, data.enterfail))
3884
terr = 'test%s failed to enter %s mode' % (tn, sysvals.suspendmode)
3885
error.append(terr)
3886
if data.tSuspended == 0:
3887
data.tSuspended = data.tKernRes
3888
if data.tResumed == 0:
3889
data.tResumed = data.tSuspended
3890
3891
if(len(sysvals.devicefilter) > 0):
3892
data.deviceFilter(sysvals.devicefilter)
3893
data.fixupInitcallsThatDidntReturn()
3894
if sysvals.usedevsrc:
3895
data.optimizeDevSrc()
3896
3897
# x2: merge any overlapping devices between test runs
3898
if sysvals.usedevsrc and len(testdata) > 1:
3899
tc = len(testdata)
3900
for i in range(tc - 1):
3901
devlist = testdata[i].overflowDevices()
3902
for j in range(i + 1, tc):
3903
testdata[j].mergeOverlapDevices(devlist)
3904
testdata[0].stitchTouchingThreads(testdata[1:])
3905
return (testdata, ', '.join(error))
3906
3907
# Function: loadKernelLog
3908
# Description:
3909
# load the dmesg file into memory and fix up any ordering issues
3910
# Output:
3911
# An array of empty Data objects with only their dmesgtext attributes set
3912
def loadKernelLog():
3913
sysvals.vprint('Analyzing the dmesg data (%s)...' % \
3914
os.path.basename(sysvals.dmesgfile))
3915
if(os.path.exists(sysvals.dmesgfile) == False):
3916
doError('%s does not exist' % sysvals.dmesgfile)
3917
3918
# there can be multiple test runs in a single file
3919
tp = TestProps()
3920
tp.stamp = datetime.now().strftime('# suspend-%m%d%y-%H%M%S localhost mem unknown')
3921
testruns = []
3922
data = 0
3923
lf = sysvals.openlog(sysvals.dmesgfile, 'r')
3924
for line in lf:
3925
line = line.replace('\r\n', '')
3926
idx = line.find('[')
3927
if idx > 1:
3928
line = line[idx:]
3929
if tp.stampInfo(line, sysvals):
3930
continue
3931
m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
3932
if(not m):
3933
continue
3934
msg = m.group("msg")
3935
if re.match(r'PM: Syncing filesystems.*', msg) or \
3936
re.match(r'PM: suspend entry.*', msg):
3937
if(data):
3938
testruns.append(data)
3939
data = Data(len(testruns))
3940
tp.parseStamp(data, sysvals)
3941
if(not data):
3942
continue
3943
m = re.match(r'.* *(?P<k>[0-9]\.[0-9]{2}\.[0-9]-.*) .*', msg)
3944
if(m):
3945
sysvals.stamp['kernel'] = m.group('k')
3946
m = re.match(r'PM: Preparing system for (?P<m>.*) sleep', msg)
3947
if not m:
3948
m = re.match(r'PM: Preparing system for sleep \((?P<m>.*)\)', msg)
3949
if m:
3950
sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
3951
data.dmesgtext.append(line)
3952
lf.close()
3953
3954
if sysvals.suspendmode == 's2idle':
3955
sysvals.suspendmode = 'freeze'
3956
elif sysvals.suspendmode == 'deep':
3957
sysvals.suspendmode = 'mem'
3958
if data:
3959
testruns.append(data)
3960
if len(testruns) < 1:
3961
doError('dmesg log has no suspend/resume data: %s' \
3962
% sysvals.dmesgfile)
3963
3964
# fix lines with same timestamp/function with the call and return swapped
3965
for data in testruns:
3966
last = ''
3967
for line in data.dmesgtext:
3968
ct, cf, n, p = data.initcall_debug_call(line)
3969
rt, rf, l = data.initcall_debug_return(last)
3970
if ct and rt and ct == rt and cf == rf:
3971
i = data.dmesgtext.index(last)
3972
j = data.dmesgtext.index(line)
3973
data.dmesgtext[i] = line
3974
data.dmesgtext[j] = last
3975
last = line
3976
return testruns
3977
3978
# Function: parseKernelLog
3979
# Description:
3980
# Analyse a dmesg log output file generated from this app during
3981
# the execution phase. Create a set of device structures in memory
3982
# for subsequent formatting in the html output file
3983
# This call is only for legacy support on kernels where the ftrace
3984
# data lacks the suspend_resume or device_pm_callbacks trace events.
3985
# Arguments:
3986
# data: an empty Data object (with dmesgtext) obtained from loadKernelLog
3987
# Output:
3988
# The filled Data object
3989
def parseKernelLog(data):
3990
phase = 'suspend_runtime'
3991
3992
if(data.fwValid):
3993
sysvals.vprint('Firmware Suspend = %u ns, Firmware Resume = %u ns' % \
3994
(data.fwSuspend, data.fwResume))
3995
3996
# dmesg phase match table
3997
dm = {
3998
'suspend_prepare': ['PM: Syncing filesystems.*', 'PM: suspend entry.*'],
3999
'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*',
4000
'PM: Suspending system .*'],
4001
'suspend_late': ['PM: suspend of devices complete after.*',
4002
'PM: freeze of devices complete after.*'],
4003
'suspend_noirq': ['PM: late suspend of devices complete after.*',
4004
'PM: late freeze of devices complete after.*'],
4005
'suspend_machine': ['PM: suspend-to-idle',
4006
'PM: noirq suspend of devices complete after.*',
4007
'PM: noirq freeze of devices complete after.*'],
4008
'resume_machine': ['[PM: ]*Timekeeping suspended for.*',
4009
'ACPI: Low-level resume complete.*',
4010
'ACPI: resume from mwait',
4011
r'Suspended for [0-9\.]* seconds'],
4012
'resume_noirq': ['PM: resume from suspend-to-idle',
4013
'ACPI: Waking up from system sleep state.*'],
4014
'resume_early': ['PM: noirq resume of devices complete after.*',
4015
'PM: noirq restore of devices complete after.*'],
4016
'resume': ['PM: early resume of devices complete after.*',
4017
'PM: early restore of devices complete after.*'],
4018
'resume_complete': ['PM: resume of devices complete after.*',
4019
'PM: restore of devices complete after.*'],
4020
'post_resume': [r'.*Restarting tasks \.\.\..*',
4021
'Done restarting tasks.*'],
4022
}
4023
4024
# action table (expected events that occur and show up in dmesg)
4025
at = {
4026
'sync_filesystems': {
4027
'smsg': '.*[Ff]+ilesystems.*',
4028
'emsg': 'PM: Preparing system for[a-z]* sleep.*' },
4029
'freeze_user_processes': {
4030
'smsg': 'Freezing user space processes.*',
4031
'emsg': 'Freezing remaining freezable tasks.*' },
4032
'freeze_tasks': {
4033
'smsg': 'Freezing remaining freezable tasks.*',
4034
'emsg': 'PM: Suspending system.*' },
4035
'ACPI prepare': {
4036
'smsg': 'ACPI: Preparing to enter system sleep state.*',
4037
'emsg': 'PM: Saving platform NVS memory.*' },
4038
'PM vns': {
4039
'smsg': 'PM: Saving platform NVS memory.*',
4040
'emsg': 'Disabling non-boot CPUs .*' },
4041
}
4042
4043
t0 = -1.0
4044
cpu_start = -1.0
4045
prevktime = -1.0
4046
actions = dict()
4047
for line in data.dmesgtext:
4048
# parse each dmesg line into the time and message
4049
m = re.match(r'[ \t]*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)', line)
4050
if(m):
4051
val = m.group('ktime')
4052
try:
4053
ktime = float(val)
4054
except:
4055
continue
4056
msg = m.group('msg')
4057
# initialize data start to first line time
4058
if t0 < 0:
4059
data.setStart(ktime)
4060
t0 = ktime
4061
else:
4062
continue
4063
4064
# check for a phase change line
4065
phasechange = False
4066
for p in dm:
4067
for s in dm[p]:
4068
if(re.match(s, msg)):
4069
phasechange, phase = True, p
4070
dm[p] = [s]
4071
break
4072
4073
# hack for determining resume_machine end for freeze
4074
if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
4075
and phase == 'resume_machine' and \
4076
data.initcall_debug_call(line, True)):
4077
data.setPhase(phase, ktime, False)
4078
phase = 'resume_noirq'
4079
data.setPhase(phase, ktime, True)
4080
4081
if phasechange:
4082
if phase == 'suspend_prepare':
4083
data.setPhase(phase, ktime, True)
4084
data.setStart(ktime)
4085
data.tKernSus = ktime
4086
elif phase == 'suspend':
4087
lp = data.lastPhase()
4088
if lp:
4089
data.setPhase(lp, ktime, False)
4090
data.setPhase(phase, ktime, True)
4091
elif phase == 'suspend_late':
4092
lp = data.lastPhase()
4093
if lp:
4094
data.setPhase(lp, ktime, False)
4095
data.setPhase(phase, ktime, True)
4096
elif phase == 'suspend_noirq':
4097
lp = data.lastPhase()
4098
if lp:
4099
data.setPhase(lp, ktime, False)
4100
data.setPhase(phase, ktime, True)
4101
elif phase == 'suspend_machine':
4102
lp = data.lastPhase()
4103
if lp:
4104
data.setPhase(lp, ktime, False)
4105
data.setPhase(phase, ktime, True)
4106
elif phase == 'resume_machine':
4107
lp = data.lastPhase()
4108
if(sysvals.suspendmode in ['freeze', 'standby']):
4109
data.tSuspended = prevktime
4110
if lp:
4111
data.setPhase(lp, prevktime, False)
4112
else:
4113
data.tSuspended = ktime
4114
if lp:
4115
data.setPhase(lp, prevktime, False)
4116
data.tResumed = ktime
4117
data.setPhase(phase, ktime, True)
4118
elif phase == 'resume_noirq':
4119
lp = data.lastPhase()
4120
if lp:
4121
data.setPhase(lp, ktime, False)
4122
data.setPhase(phase, ktime, True)
4123
elif phase == 'resume_early':
4124
lp = data.lastPhase()
4125
if lp:
4126
data.setPhase(lp, ktime, False)
4127
data.setPhase(phase, ktime, True)
4128
elif phase == 'resume':
4129
lp = data.lastPhase()
4130
if lp:
4131
data.setPhase(lp, ktime, False)
4132
data.setPhase(phase, ktime, True)
4133
elif phase == 'resume_complete':
4134
lp = data.lastPhase()
4135
if lp:
4136
data.setPhase(lp, ktime, False)
4137
data.setPhase(phase, ktime, True)
4138
elif phase == 'post_resume':
4139
lp = data.lastPhase()
4140
if lp:
4141
data.setPhase(lp, ktime, False)
4142
data.setEnd(ktime)
4143
data.tKernRes = ktime
4144
break
4145
4146
# -- device callbacks --
4147
if(phase in data.sortedPhases()):
4148
# device init call
4149
t, f, n, p = data.initcall_debug_call(line)
4150
if t and f and n and p:
4151
data.newAction(phase, f, int(n), p, ktime, -1, '')
4152
else:
4153
# device init return
4154
t, f, l = data.initcall_debug_return(line)
4155
if t and f and l:
4156
list = data.dmesg[phase]['list']
4157
if(f in list):
4158
dev = list[f]
4159
dev['length'] = int(l)
4160
dev['end'] = ktime
4161
4162
# if trace events are not available, these are better than nothing
4163
if(not sysvals.usetraceevents):
4164
# look for known actions
4165
for a in sorted(at):
4166
if(re.match(at[a]['smsg'], msg)):
4167
if(a not in actions):
4168
actions[a] = [{'begin': ktime, 'end': ktime}]
4169
if(re.match(at[a]['emsg'], msg)):
4170
if(a in actions and actions[a][-1]['begin'] == actions[a][-1]['end']):
4171
actions[a][-1]['end'] = ktime
4172
# now look for CPU on/off events
4173
if(re.match(r'Disabling non-boot CPUs .*', msg)):
4174
# start of first cpu suspend
4175
cpu_start = ktime
4176
elif(re.match(r'Enabling non-boot CPUs .*', msg)):
4177
# start of first cpu resume
4178
cpu_start = ktime
4179
elif(re.match(r'smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg) \
4180
or re.match(r'psci: CPU(?P<cpu>[0-9]*) killed.*', msg)):
4181
# end of a cpu suspend, start of the next
4182
m = re.match(r'smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)
4183
if(not m):
4184
m = re.match(r'psci: CPU(?P<cpu>[0-9]*) killed.*', msg)
4185
cpu = 'CPU'+m.group('cpu')
4186
if(cpu not in actions):
4187
actions[cpu] = []
4188
actions[cpu].append({'begin': cpu_start, 'end': ktime})
4189
cpu_start = ktime
4190
elif(re.match(r'CPU(?P<cpu>[0-9]*) is up', msg)):
4191
# end of a cpu resume, start of the next
4192
m = re.match(r'CPU(?P<cpu>[0-9]*) is up', msg)
4193
cpu = 'CPU'+m.group('cpu')
4194
if(cpu not in actions):
4195
actions[cpu] = []
4196
actions[cpu].append({'begin': cpu_start, 'end': ktime})
4197
cpu_start = ktime
4198
prevktime = ktime
4199
data.initDevicegroups()
4200
4201
# fill in any missing phases
4202
phasedef = data.phasedef
4203
terr, lp = '', 'suspend_prepare'
4204
if lp not in data.dmesg:
4205
doError('dmesg log format has changed, could not find start of suspend')
4206
for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4207
if p not in data.dmesg:
4208
if not terr:
4209
pprint('TEST FAILED: %s failed in %s phase' % (sysvals.suspendmode, lp))
4210
terr = '%s failed in %s phase' % (sysvals.suspendmode, lp)
4211
if data.tSuspended == 0:
4212
data.tSuspended = data.dmesg[lp]['end']
4213
if data.tResumed == 0:
4214
data.tResumed = data.dmesg[lp]['end']
4215
sysvals.vprint('WARNING: phase "%s" is missing!' % p)
4216
lp = p
4217
lp = data.sortedPhases()[0]
4218
for p in data.sortedPhases():
4219
if(p != lp and not ('machine' in p and 'machine' in lp)):
4220
data.dmesg[lp]['end'] = data.dmesg[p]['start']
4221
lp = p
4222
if data.tSuspended == 0:
4223
data.tSuspended = data.tKernRes
4224
if data.tResumed == 0:
4225
data.tResumed = data.tSuspended
4226
4227
# fill in any actions we've found
4228
for name in sorted(actions):
4229
for event in actions[name]:
4230
data.newActionGlobal(name, event['begin'], event['end'])
4231
4232
if(len(sysvals.devicefilter) > 0):
4233
data.deviceFilter(sysvals.devicefilter)
4234
data.fixupInitcallsThatDidntReturn()
4235
return True
4236
4237
def callgraphHTML(sv, hf, num, cg, title, color, devid):
4238
html_func_top = '<article id="{0}" class="atop" style="background:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
4239
html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
4240
html_func_end = '</article>\n'
4241
html_func_leaf = '<article>{0} {1}</article>\n'
4242
4243
cgid = devid
4244
if cg.id:
4245
cgid += cg.id
4246
cglen = (cg.end - cg.start) * 1000
4247
if cglen < sv.mincglen:
4248
return num
4249
4250
fmt = '<r>(%.3f ms @ '+sv.timeformat+' to '+sv.timeformat+')</r>'
4251
flen = fmt % (cglen, cg.start, cg.end)
4252
hf.write(html_func_top.format(cgid, color, num, title, flen))
4253
num += 1
4254
for line in cg.list:
4255
if(line.length < 0.000000001):
4256
flen = ''
4257
else:
4258
fmt = '<n>(%.3f ms @ '+sv.timeformat+')</n>'
4259
flen = fmt % (line.length*1000, line.time)
4260
if line.isLeaf():
4261
if line.length * 1000 < sv.mincglen:
4262
continue
4263
hf.write(html_func_leaf.format(line.name, flen))
4264
elif line.freturn:
4265
hf.write(html_func_end)
4266
else:
4267
hf.write(html_func_start.format(num, line.name, flen))
4268
num += 1
4269
hf.write(html_func_end)
4270
return num
4271
4272
def addCallgraphs(sv, hf, data):
4273
hf.write('<section id="callgraphs" class="callgraph">\n')
4274
# write out the ftrace data converted to html
4275
num = 0
4276
for p in data.sortedPhases():
4277
if sv.cgphase and p != sv.cgphase:
4278
continue
4279
list = data.dmesg[p]['list']
4280
for d in data.sortedDevices(p):
4281
if len(sv.cgfilter) > 0 and d not in sv.cgfilter:
4282
continue
4283
dev = list[d]
4284
color = 'white'
4285
if 'color' in data.dmesg[p]:
4286
color = data.dmesg[p]['color']
4287
if 'color' in dev:
4288
color = dev['color']
4289
name = d if '[' not in d else d.split('[')[0]
4290
if(d in sv.devprops):
4291
name = sv.devprops[d].altName(d)
4292
if 'drv' in dev and dev['drv']:
4293
name += ' {%s}' % dev['drv']
4294
if sv.suspendmode in suspendmodename:
4295
name += ' '+p
4296
if('ftrace' in dev):
4297
cg = dev['ftrace']
4298
if cg.name == sv.ftopfunc:
4299
name = 'top level suspend/resume call'
4300
num = callgraphHTML(sv, hf, num, cg,
4301
name, color, dev['id'])
4302
if('ftraces' in dev):
4303
for cg in dev['ftraces']:
4304
num = callgraphHTML(sv, hf, num, cg,
4305
name+' &rarr; '+cg.name, color, dev['id'])
4306
hf.write('\n\n </section>\n')
4307
4308
def summaryCSS(title, center=True):
4309
tdcenter = 'text-align:center;' if center else ''
4310
out = '<!DOCTYPE html>\n<html>\n<head>\n\
4311
<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
4312
<title>'+title+'</title>\n\
4313
<style type=\'text/css\'>\n\
4314
.stamp {width: 100%;text-align:center;background:#888;line-height:30px;color:white;font: 25px Arial;}\n\
4315
table {width:100%;border-collapse: collapse;border:1px solid;}\n\
4316
th {border: 1px solid black;background:#222;color:white;}\n\
4317
td {font: 14px "Times New Roman";'+tdcenter+'}\n\
4318
tr.head td {border: 1px solid black;background:#aaa;}\n\
4319
tr.alt {background-color:#ddd;}\n\
4320
tr.notice {color:red;}\n\
4321
.minval {background-color:#BBFFBB;}\n\
4322
.medval {background-color:#BBBBFF;}\n\
4323
.maxval {background-color:#FFBBBB;}\n\
4324
.head a {color:#000;text-decoration: none;}\n\
4325
</style>\n</head>\n<body>\n'
4326
return out
4327
4328
# Function: createHTMLSummarySimple
4329
# Description:
4330
# Create summary html file for a series of tests
4331
# Arguments:
4332
# testruns: array of Data objects from parseTraceLog
4333
def createHTMLSummarySimple(testruns, htmlfile, title):
4334
# write the html header first (html head, css code, up to body start)
4335
html = summaryCSS('Summary - SleepGraph')
4336
4337
# extract the test data into list
4338
list = dict()
4339
tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4340
iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4341
num = 0
4342
useturbo = usewifi = False
4343
lastmode = ''
4344
cnt = dict()
4345
for data in sorted(testruns, key=lambda v:(v['mode'], v['host'], v['kernel'], v['time'])):
4346
mode = data['mode']
4347
if mode not in list:
4348
list[mode] = {'data': [], 'avg': [0,0], 'min': [0,0], 'max': [0,0], 'med': [0,0]}
4349
if lastmode and lastmode != mode and num > 0:
4350
for i in range(2):
4351
s = sorted(tMed[i])
4352
list[lastmode]['med'][i] = s[int(len(s)//2)]
4353
iMed[i] = tMed[i][list[lastmode]['med'][i]]
4354
list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4355
list[lastmode]['min'] = tMin
4356
list[lastmode]['max'] = tMax
4357
list[lastmode]['idx'] = (iMin, iMed, iMax)
4358
tAvg, tMin, tMax, tMed = [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [dict(), dict()]
4359
iMin, iMed, iMax = [0, 0], [0, 0], [0, 0]
4360
num = 0
4361
pkgpc10 = syslpi = wifi = ''
4362
if 'pkgpc10' in data and 'syslpi' in data:
4363
pkgpc10, syslpi, useturbo = data['pkgpc10'], data['syslpi'], True
4364
if 'wifi' in data:
4365
wifi, usewifi = data['wifi'], True
4366
res = data['result']
4367
tVal = [float(data['suspend']), float(data['resume'])]
4368
list[mode]['data'].append([data['host'], data['kernel'],
4369
data['time'], tVal[0], tVal[1], data['url'], res,
4370
data['issues'], data['sus_worst'], data['sus_worsttime'],
4371
data['res_worst'], data['res_worsttime'], pkgpc10, syslpi, wifi,
4372
(data['fullmode'] if 'fullmode' in data else mode)])
4373
idx = len(list[mode]['data']) - 1
4374
if res.startswith('fail in'):
4375
res = 'fail'
4376
if res not in cnt:
4377
cnt[res] = 1
4378
else:
4379
cnt[res] += 1
4380
if res == 'pass':
4381
for i in range(2):
4382
tMed[i][tVal[i]] = idx
4383
tAvg[i] += tVal[i]
4384
if tMin[i] == 0 or tVal[i] < tMin[i]:
4385
iMin[i] = idx
4386
tMin[i] = tVal[i]
4387
if tMax[i] == 0 or tVal[i] > tMax[i]:
4388
iMax[i] = idx
4389
tMax[i] = tVal[i]
4390
num += 1
4391
lastmode = mode
4392
if lastmode and num > 0:
4393
for i in range(2):
4394
s = sorted(tMed[i])
4395
list[lastmode]['med'][i] = s[int(len(s)//2)]
4396
iMed[i] = tMed[i][list[lastmode]['med'][i]]
4397
list[lastmode]['avg'] = [tAvg[0] / num, tAvg[1] / num]
4398
list[lastmode]['min'] = tMin
4399
list[lastmode]['max'] = tMax
4400
list[lastmode]['idx'] = (iMin, iMed, iMax)
4401
4402
# group test header
4403
desc = []
4404
for ilk in sorted(cnt, reverse=True):
4405
if cnt[ilk] > 0:
4406
desc.append('%d %s' % (cnt[ilk], ilk))
4407
html += '<div class="stamp">%s (%d tests: %s)</div>\n' % (title, len(testruns), ', '.join(desc))
4408
th = '\t<th>{0}</th>\n'
4409
td = '\t<td>{0}</td>\n'
4410
tdh = '\t<td{1}>{0}</td>\n'
4411
tdlink = '\t<td><a href="{0}">html</a></td>\n'
4412
cols = 12
4413
if useturbo:
4414
cols += 2
4415
if usewifi:
4416
cols += 1
4417
colspan = '%d' % cols
4418
4419
# table header
4420
html += '<table>\n<tr>\n' + th.format('#') +\
4421
th.format('Mode') + th.format('Host') + th.format('Kernel') +\
4422
th.format('Test Time') + th.format('Result') + th.format('Issues') +\
4423
th.format('Suspend') + th.format('Resume') +\
4424
th.format('Worst Suspend Device') + th.format('SD Time') +\
4425
th.format('Worst Resume Device') + th.format('RD Time')
4426
if useturbo:
4427
html += th.format('PkgPC10') + th.format('SysLPI')
4428
if usewifi:
4429
html += th.format('Wifi')
4430
html += th.format('Detail')+'</tr>\n'
4431
# export list into html
4432
head = '<tr class="head"><td>{0}</td><td>{1}</td>'+\
4433
'<td colspan='+colspan+' class="sus">Suspend Avg={2} '+\
4434
'<span class=minval><a href="#s{10}min">Min={3}</a></span> '+\
4435
'<span class=medval><a href="#s{10}med">Med={4}</a></span> '+\
4436
'<span class=maxval><a href="#s{10}max">Max={5}</a></span> '+\
4437
'Resume Avg={6} '+\
4438
'<span class=minval><a href="#r{10}min">Min={7}</a></span> '+\
4439
'<span class=medval><a href="#r{10}med">Med={8}</a></span> '+\
4440
'<span class=maxval><a href="#r{10}max">Max={9}</a></span></td>'+\
4441
'</tr>\n'
4442
headnone = '<tr class="head"><td>{0}</td><td>{1}</td><td colspan='+\
4443
colspan+'></td></tr>\n'
4444
for mode in sorted(list):
4445
# header line for each suspend mode
4446
num = 0
4447
tAvg, tMin, tMax, tMed = list[mode]['avg'], list[mode]['min'],\
4448
list[mode]['max'], list[mode]['med']
4449
count = len(list[mode]['data'])
4450
if 'idx' in list[mode]:
4451
iMin, iMed, iMax = list[mode]['idx']
4452
html += head.format('%d' % count, mode.upper(),
4453
'%.3f' % tAvg[0], '%.3f' % tMin[0], '%.3f' % tMed[0], '%.3f' % tMax[0],
4454
'%.3f' % tAvg[1], '%.3f' % tMin[1], '%.3f' % tMed[1], '%.3f' % tMax[1],
4455
mode.lower()
4456
)
4457
else:
4458
iMin = iMed = iMax = [-1, -1, -1]
4459
html += headnone.format('%d' % count, mode.upper())
4460
for d in list[mode]['data']:
4461
# row classes - alternate row color
4462
rcls = ['alt'] if num % 2 == 1 else []
4463
if d[6] != 'pass':
4464
rcls.append('notice')
4465
html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4466
# figure out if the line has sus or res highlighted
4467
idx = list[mode]['data'].index(d)
4468
tHigh = ['', '']
4469
for i in range(2):
4470
tag = 's%s' % mode if i == 0 else 'r%s' % mode
4471
if idx == iMin[i]:
4472
tHigh[i] = ' id="%smin" class=minval title="Minimum"' % tag
4473
elif idx == iMax[i]:
4474
tHigh[i] = ' id="%smax" class=maxval title="Maximum"' % tag
4475
elif idx == iMed[i]:
4476
tHigh[i] = ' id="%smed" class=medval title="Median"' % tag
4477
html += td.format("%d" % (list[mode]['data'].index(d) + 1)) # row
4478
html += td.format(d[15]) # mode
4479
html += td.format(d[0]) # host
4480
html += td.format(d[1]) # kernel
4481
html += td.format(d[2]) # time
4482
html += td.format(d[6]) # result
4483
html += td.format(d[7]) # issues
4484
html += tdh.format('%.3f ms' % d[3], tHigh[0]) if d[3] else td.format('') # suspend
4485
html += tdh.format('%.3f ms' % d[4], tHigh[1]) if d[4] else td.format('') # resume
4486
html += td.format(d[8]) # sus_worst
4487
html += td.format('%.3f ms' % d[9]) if d[9] else td.format('') # sus_worst time
4488
html += td.format(d[10]) # res_worst
4489
html += td.format('%.3f ms' % d[11]) if d[11] else td.format('') # res_worst time
4490
if useturbo:
4491
html += td.format(d[12]) # pkg_pc10
4492
html += td.format(d[13]) # syslpi
4493
if usewifi:
4494
html += td.format(d[14]) # wifi
4495
html += tdlink.format(d[5]) if d[5] else td.format('') # url
4496
html += '</tr>\n'
4497
num += 1
4498
4499
# flush the data to file
4500
hf = open(htmlfile, 'w')
4501
hf.write(html+'</table>\n</body>\n</html>\n')
4502
hf.close()
4503
4504
def createHTMLDeviceSummary(testruns, htmlfile, title):
4505
html = summaryCSS('Device Summary - SleepGraph', False)
4506
4507
# create global device list from all tests
4508
devall = dict()
4509
for data in testruns:
4510
host, url, devlist = data['host'], data['url'], data['devlist']
4511
for type in devlist:
4512
if type not in devall:
4513
devall[type] = dict()
4514
mdevlist, devlist = devall[type], data['devlist'][type]
4515
for name in devlist:
4516
length = devlist[name]
4517
if name not in mdevlist:
4518
mdevlist[name] = {'name': name, 'host': host,
4519
'worst': length, 'total': length, 'count': 1,
4520
'url': url}
4521
else:
4522
if length > mdevlist[name]['worst']:
4523
mdevlist[name]['worst'] = length
4524
mdevlist[name]['url'] = url
4525
mdevlist[name]['host'] = host
4526
mdevlist[name]['total'] += length
4527
mdevlist[name]['count'] += 1
4528
4529
# generate the html
4530
th = '\t<th>{0}</th>\n'
4531
td = '\t<td align=center>{0}</td>\n'
4532
tdr = '\t<td align=right>{0}</td>\n'
4533
tdlink = '\t<td align=center><a href="{0}">html</a></td>\n'
4534
limit = 1
4535
for type in sorted(devall, reverse=True):
4536
num = 0
4537
devlist = devall[type]
4538
# table header
4539
html += '<div class="stamp">%s (%s devices > %d ms)</div><table>\n' % \
4540
(title, type.upper(), limit)
4541
html += '<tr>\n' + '<th align=right>Device Name</th>' +\
4542
th.format('Average Time') + th.format('Count') +\
4543
th.format('Worst Time') + th.format('Host (worst time)') +\
4544
th.format('Link (worst time)') + '</tr>\n'
4545
for name in sorted(devlist, key=lambda k:(devlist[k]['worst'], \
4546
devlist[k]['total'], devlist[k]['name']), reverse=True):
4547
data = devall[type][name]
4548
data['average'] = data['total'] / data['count']
4549
if data['average'] < limit:
4550
continue
4551
# row classes - alternate row color
4552
rcls = ['alt'] if num % 2 == 1 else []
4553
html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4554
html += tdr.format(data['name']) # name
4555
html += td.format('%.3f ms' % data['average']) # average
4556
html += td.format(data['count']) # count
4557
html += td.format('%.3f ms' % data['worst']) # worst
4558
html += td.format(data['host']) # host
4559
html += tdlink.format(data['url']) # url
4560
html += '</tr>\n'
4561
num += 1
4562
html += '</table>\n'
4563
4564
# flush the data to file
4565
hf = open(htmlfile, 'w')
4566
hf.write(html+'</body>\n</html>\n')
4567
hf.close()
4568
return devall
4569
4570
def createHTMLIssuesSummary(testruns, issues, htmlfile, title, extra=''):
4571
multihost = len([e for e in issues if len(e['urls']) > 1]) > 0
4572
html = summaryCSS('Issues Summary - SleepGraph', False)
4573
total = len(testruns)
4574
4575
# generate the html
4576
th = '\t<th>{0}</th>\n'
4577
td = '\t<td align={0}>{1}</td>\n'
4578
tdlink = '<a href="{1}">{0}</a>'
4579
subtitle = '%d issues' % len(issues) if len(issues) > 0 else 'no issues'
4580
html += '<div class="stamp">%s (%s)</div><table>\n' % (title, subtitle)
4581
html += '<tr>\n' + th.format('Issue') + th.format('Count')
4582
if multihost:
4583
html += th.format('Hosts')
4584
html += th.format('Tests') + th.format('Fail Rate') +\
4585
th.format('First Instance') + '</tr>\n'
4586
4587
num = 0
4588
for e in sorted(issues, key=lambda v:v['count'], reverse=True):
4589
testtotal = 0
4590
links = []
4591
for host in sorted(e['urls']):
4592
links.append(tdlink.format(host, e['urls'][host][0]))
4593
testtotal += len(e['urls'][host])
4594
rate = '%d/%d (%.2f%%)' % (testtotal, total, 100*float(testtotal)/float(total))
4595
# row classes - alternate row color
4596
rcls = ['alt'] if num % 2 == 1 else []
4597
html += '<tr class="'+(' '.join(rcls))+'">\n' if len(rcls) > 0 else '<tr>\n'
4598
html += td.format('left', e['line']) # issue
4599
html += td.format('center', e['count']) # count
4600
if multihost:
4601
html += td.format('center', len(e['urls'])) # hosts
4602
html += td.format('center', testtotal) # test count
4603
html += td.format('center', rate) # test rate
4604
html += td.format('center nowrap', '<br>'.join(links)) # links
4605
html += '</tr>\n'
4606
num += 1
4607
4608
# flush the data to file
4609
hf = open(htmlfile, 'w')
4610
hf.write(html+'</table>\n'+extra+'</body>\n</html>\n')
4611
hf.close()
4612
return issues
4613
4614
def ordinal(value):
4615
suffix = 'th'
4616
if value < 10 or value > 19:
4617
if value % 10 == 1:
4618
suffix = 'st'
4619
elif value % 10 == 2:
4620
suffix = 'nd'
4621
elif value % 10 == 3:
4622
suffix = 'rd'
4623
return '%d%s' % (value, suffix)
4624
4625
# Function: createHTML
4626
# Description:
4627
# Create the output html file from the resident test data
4628
# Arguments:
4629
# testruns: array of Data objects from parseKernelLog or parseTraceLog
4630
# Output:
4631
# True if the html file was created, false if it failed
4632
def createHTML(testruns, testfail):
4633
if len(testruns) < 1:
4634
pprint('ERROR: Not enough test data to build a timeline')
4635
return
4636
4637
kerror = False
4638
for data in testruns:
4639
if data.kerror:
4640
kerror = True
4641
if(sysvals.suspendmode in ['freeze', 'standby']):
4642
data.trimFreezeTime(testruns[-1].tSuspended)
4643
else:
4644
data.getMemTime()
4645
4646
# html function templates
4647
html_error = '<div id="{1}" title="kernel error/warning" class="err" style="right:{0}%">{2}&rarr;</div>\n'
4648
html_traceevent = '<div title="{0}" class="traceevent{6}" style="left:{1}%;top:{2}px;height:{3}px;width:{4}%;line-height:{3}px;{7}">{5}</div>\n'
4649
html_cpuexec = '<div class="jiffie" style="left:{0}%;top:{1}px;height:{2}px;width:{3}%;background:{4};"></div>\n'
4650
html_timetotal = '<table class="time1">\n<tr>'\
4651
'<td class="green" title="{3}">{2} Suspend Time: <b>{0} ms</b></td>'\
4652
'<td class="yellow" title="{4}">{2} Resume Time: <b>{1} ms</b></td>'\
4653
'</tr>\n</table>\n'
4654
html_timetotal2 = '<table class="time1">\n<tr>'\
4655
'<td class="green" title="{4}">{3} Suspend Time: <b>{0} ms</b></td>'\
4656
'<td class="gray" title="time spent in low-power mode with clock running">'+sysvals.suspendmode+' time: <b>{1} ms</b></td>'\
4657
'<td class="yellow" title="{5}">{3} Resume Time: <b>{2} ms</b></td>'\
4658
'</tr>\n</table>\n'
4659
html_timetotal3 = '<table class="time1">\n<tr>'\
4660
'<td class="green">Execution Time: <b>{0} ms</b></td>'\
4661
'<td class="yellow">Command: <b>{1}</b></td>'\
4662
'</tr>\n</table>\n'
4663
html_fail = '<table class="testfail"><tr><td>{0}</td></tr></table>\n'
4664
html_kdesc = '<td class="{3}" title="time spent in kernel execution">{0}Kernel {2}: {1} ms</td>'
4665
html_fwdesc = '<td class="{3}" title="time spent in firmware">{0}Firmware {2}: {1} ms</td>'
4666
html_wifdesc = '<td class="yellow" title="time for wifi to reconnect after resume complete ({2})">{0}Wifi Resume: {1}</td>'
4667
4668
# html format variables
4669
scaleH = 20
4670
if kerror:
4671
scaleH = 40
4672
4673
# device timeline
4674
devtl = Timeline(30, scaleH)
4675
4676
# write the test title and general info header
4677
devtl.createHeader(sysvals, testruns[0].stamp)
4678
4679
# Generate the header for this timeline
4680
for data in testruns:
4681
tTotal = data.end - data.start
4682
if(tTotal == 0):
4683
doError('No timeline data')
4684
if sysvals.suspendmode == 'command':
4685
run_time = '%.0f' % (tTotal * 1000)
4686
if sysvals.testcommand:
4687
testdesc = sysvals.testcommand
4688
else:
4689
testdesc = 'unknown'
4690
if(len(testruns) > 1):
4691
testdesc = ordinal(data.testnumber+1)+' '+testdesc
4692
thtml = html_timetotal3.format(run_time, testdesc)
4693
devtl.html += thtml
4694
continue
4695
# typical full suspend/resume header
4696
stot, rtot = sktime, rktime = data.getTimeValues()
4697
ssrc, rsrc, testdesc, testdesc2 = ['kernel'], ['kernel'], 'Kernel', ''
4698
if data.fwValid:
4699
stot += (data.fwSuspend/1000000.0)
4700
rtot += (data.fwResume/1000000.0)
4701
ssrc.append('firmware')
4702
rsrc.append('firmware')
4703
testdesc = 'Total'
4704
if 'time' in data.wifi and data.wifi['stat'] != 'timeout':
4705
rtot += data.end - data.tKernRes + (data.wifi['time'] * 1000.0)
4706
rsrc.append('wifi')
4707
testdesc = 'Total'
4708
suspend_time, resume_time = '%.3f' % stot, '%.3f' % rtot
4709
stitle = 'time from kernel suspend start to %s mode [%s time]' % \
4710
(sysvals.suspendmode, ' & '.join(ssrc))
4711
rtitle = 'time from %s mode to kernel resume complete [%s time]' % \
4712
(sysvals.suspendmode, ' & '.join(rsrc))
4713
if(len(testruns) > 1):
4714
testdesc = testdesc2 = ordinal(data.testnumber+1)
4715
testdesc2 += ' '
4716
if(len(data.tLow) == 0):
4717
thtml = html_timetotal.format(suspend_time, \
4718
resume_time, testdesc, stitle, rtitle)
4719
else:
4720
low_time = '+'.join(data.tLow)
4721
thtml = html_timetotal2.format(suspend_time, low_time, \
4722
resume_time, testdesc, stitle, rtitle)
4723
devtl.html += thtml
4724
if not data.fwValid and 'dev' not in data.wifi:
4725
continue
4726
# extra detail when the times come from multiple sources
4727
thtml = '<table class="time2">\n<tr>'
4728
thtml += html_kdesc.format(testdesc2, '%.3f'%sktime, 'Suspend', 'green')
4729
if data.fwValid:
4730
sftime = '%.3f'%(data.fwSuspend / 1000000.0)
4731
rftime = '%.3f'%(data.fwResume / 1000000.0)
4732
thtml += html_fwdesc.format(testdesc2, sftime, 'Suspend', 'green')
4733
thtml += html_fwdesc.format(testdesc2, rftime, 'Resume', 'yellow')
4734
thtml += html_kdesc.format(testdesc2, '%.3f'%rktime, 'Resume', 'yellow')
4735
if 'time' in data.wifi:
4736
if data.wifi['stat'] != 'timeout':
4737
wtime = '%.0f ms'%(data.end - data.tKernRes + (data.wifi['time'] * 1000.0))
4738
else:
4739
wtime = 'TIMEOUT'
4740
thtml += html_wifdesc.format(testdesc2, wtime, data.wifi['dev'])
4741
thtml += '</tr>\n</table>\n'
4742
devtl.html += thtml
4743
if testfail:
4744
devtl.html += html_fail.format(testfail)
4745
4746
# time scale for potentially multiple datasets
4747
t0 = testruns[0].start
4748
tMax = testruns[-1].end
4749
tTotal = tMax - t0
4750
4751
# determine the maximum number of rows we need to draw
4752
fulllist = []
4753
threadlist = []
4754
pscnt = 0
4755
devcnt = 0
4756
for data in testruns:
4757
data.selectTimelineDevices('%f', tTotal, sysvals.mindevlen)
4758
for group in data.devicegroups:
4759
devlist = []
4760
for phase in group:
4761
for devname in sorted(data.tdevlist[phase]):
4762
d = DevItem(data.testnumber, phase, data.dmesg[phase]['list'][devname])
4763
devlist.append(d)
4764
if d.isa('kth'):
4765
threadlist.append(d)
4766
else:
4767
if d.isa('ps'):
4768
pscnt += 1
4769
else:
4770
devcnt += 1
4771
fulllist.append(d)
4772
if sysvals.mixedphaseheight:
4773
devtl.getPhaseRows(devlist)
4774
if not sysvals.mixedphaseheight:
4775
if len(threadlist) > 0 and len(fulllist) > 0:
4776
if pscnt > 0 and devcnt > 0:
4777
msg = 'user processes & device pm callbacks'
4778
elif pscnt > 0:
4779
msg = 'user processes'
4780
else:
4781
msg = 'device pm callbacks'
4782
d = testruns[0].addHorizontalDivider(msg, testruns[-1].end)
4783
fulllist.insert(0, d)
4784
devtl.getPhaseRows(fulllist)
4785
if len(threadlist) > 0:
4786
d = testruns[0].addHorizontalDivider('asynchronous kernel threads', testruns[-1].end)
4787
threadlist.insert(0, d)
4788
devtl.getPhaseRows(threadlist, devtl.rows)
4789
devtl.calcTotalRows()
4790
4791
# draw the full timeline
4792
devtl.createZoomBox(sysvals.suspendmode, len(testruns))
4793
for data in testruns:
4794
# draw each test run and block chronologically
4795
phases = {'suspend':[],'resume':[]}
4796
for phase in data.sortedPhases():
4797
if data.dmesg[phase]['start'] >= data.tSuspended:
4798
phases['resume'].append(phase)
4799
else:
4800
phases['suspend'].append(phase)
4801
# now draw the actual timeline blocks
4802
for dir in phases:
4803
# draw suspend and resume blocks separately
4804
bname = '%s%d' % (dir[0], data.testnumber)
4805
if dir == 'suspend':
4806
m0 = data.start
4807
mMax = data.tSuspended
4808
left = '%f' % (((m0-t0)*100.0)/tTotal)
4809
else:
4810
m0 = data.tSuspended
4811
mMax = data.end
4812
# in an x2 run, remove any gap between blocks
4813
if len(testruns) > 1 and data.testnumber == 0:
4814
mMax = testruns[1].start
4815
left = '%f' % ((((m0-t0)*100.0)+sysvals.srgap/2)/tTotal)
4816
mTotal = mMax - m0
4817
# if a timeline block is 0 length, skip altogether
4818
if mTotal == 0:
4819
continue
4820
width = '%f' % (((mTotal*100.0)-sysvals.srgap/2)/tTotal)
4821
devtl.html += devtl.html_tblock.format(bname, left, width, devtl.scaleH)
4822
for b in phases[dir]:
4823
# draw the phase color background
4824
phase = data.dmesg[b]
4825
length = phase['end']-phase['start']
4826
left = '%f' % (((phase['start']-m0)*100.0)/mTotal)
4827
width = '%f' % ((length*100.0)/mTotal)
4828
devtl.html += devtl.html_phase.format(left, width, \
4829
'%.3f'%devtl.scaleH, '%.3f'%devtl.bodyH, \
4830
data.dmesg[b]['color'], '')
4831
for e in data.errorinfo[dir]:
4832
# draw red lines for any kernel errors found
4833
type, t, idx1, idx2 = e
4834
id = '%d_%d' % (idx1, idx2)
4835
right = '%f' % (((mMax-t)*100.0)/mTotal)
4836
devtl.html += html_error.format(right, id, type)
4837
for b in phases[dir]:
4838
# draw the devices for this phase
4839
phaselist = data.dmesg[b]['list']
4840
for d in sorted(data.tdevlist[b]):
4841
dname = d if ('[' not in d or 'CPU' in d) else d.split('[')[0]
4842
name, dev = dname, phaselist[d]
4843
drv = xtraclass = xtrainfo = xtrastyle = ''
4844
if 'htmlclass' in dev:
4845
xtraclass = dev['htmlclass']
4846
if 'color' in dev:
4847
xtrastyle = 'background:%s;' % dev['color']
4848
if(d in sysvals.devprops):
4849
name = sysvals.devprops[d].altName(d)
4850
xtraclass = sysvals.devprops[d].xtraClass()
4851
xtrainfo = sysvals.devprops[d].xtraInfo()
4852
elif xtraclass == ' kth':
4853
xtrainfo = ' kernel_thread'
4854
if('drv' in dev and dev['drv']):
4855
drv = ' {%s}' % dev['drv']
4856
rowheight = devtl.phaseRowHeight(data.testnumber, b, dev['row'])
4857
rowtop = devtl.phaseRowTop(data.testnumber, b, dev['row'])
4858
top = '%.3f' % (rowtop + devtl.scaleH)
4859
left = '%f' % (((dev['start']-m0)*100)/mTotal)
4860
width = '%f' % (((dev['end']-dev['start'])*100)/mTotal)
4861
length = ' (%0.3f ms) ' % ((dev['end']-dev['start'])*1000)
4862
title = name+drv+xtrainfo+length
4863
if sysvals.suspendmode == 'command':
4864
title += sysvals.testcommand
4865
elif xtraclass == ' ps':
4866
if 'suspend' in b:
4867
title += 'pre_suspend_process'
4868
else:
4869
title += 'post_resume_process'
4870
else:
4871
title += b
4872
devtl.html += devtl.html_device.format(dev['id'], \
4873
title, left, top, '%.3f'%rowheight, width, \
4874
dname+drv, xtraclass, xtrastyle)
4875
if('cpuexec' in dev):
4876
for t in sorted(dev['cpuexec']):
4877
start, end = t
4878
height = '%.3f' % (rowheight/3)
4879
top = '%.3f' % (rowtop + devtl.scaleH + 2*rowheight/3)
4880
left = '%f' % (((start-m0)*100)/mTotal)
4881
width = '%f' % ((end-start)*100/mTotal)
4882
color = 'rgba(255, 0, 0, %f)' % dev['cpuexec'][t]
4883
devtl.html += \
4884
html_cpuexec.format(left, top, height, width, color)
4885
if('src' not in dev):
4886
continue
4887
# draw any trace events for this device
4888
for e in dev['src']:
4889
if e.length == 0:
4890
continue
4891
height = '%.3f' % devtl.rowH
4892
top = '%.3f' % (rowtop + devtl.scaleH + (e.row*devtl.rowH))
4893
left = '%f' % (((e.time-m0)*100)/mTotal)
4894
width = '%f' % (e.length*100/mTotal)
4895
xtrastyle = ''
4896
if e.color:
4897
xtrastyle = 'background:%s;' % e.color
4898
devtl.html += \
4899
html_traceevent.format(e.title(), \
4900
left, top, height, width, e.text(), '', xtrastyle)
4901
# draw the time scale, try to make the number of labels readable
4902
devtl.createTimeScale(m0, mMax, tTotal, dir)
4903
devtl.html += '</div>\n'
4904
4905
# timeline is finished
4906
devtl.html += '</div>\n</div>\n'
4907
4908
# draw a legend which describes the phases by color
4909
if sysvals.suspendmode != 'command':
4910
phasedef = testruns[-1].phasedef
4911
devtl.html += '<div class="legend">\n'
4912
pdelta = 100.0/len(phasedef.keys())
4913
pmargin = pdelta / 4.0
4914
for phase in sorted(phasedef, key=lambda k:phasedef[k]['order']):
4915
id, p = '', phasedef[phase]
4916
for word in phase.split('_'):
4917
id += word[0]
4918
order = '%.2f' % ((p['order'] * pdelta) + pmargin)
4919
name = phase.replace('_', ' &nbsp;')
4920
devtl.html += devtl.html_legend.format(order, p['color'], name, id)
4921
devtl.html += '</div>\n'
4922
4923
hf = open(sysvals.htmlfile, 'w')
4924
addCSS(hf, sysvals, len(testruns), kerror)
4925
4926
# write the device timeline
4927
hf.write(devtl.html)
4928
hf.write('<div id="devicedetailtitle"></div>\n')
4929
hf.write('<div id="devicedetail" style="display:none;">\n')
4930
# draw the colored boxes for the device detail section
4931
for data in testruns:
4932
hf.write('<div id="devicedetail%d">\n' % data.testnumber)
4933
pscolor = 'linear-gradient(to top left, #ccc, #eee)'
4934
hf.write(devtl.html_phaselet.format('pre_suspend_process', \
4935
'0', '0', pscolor))
4936
for b in data.sortedPhases():
4937
phase = data.dmesg[b]
4938
length = phase['end']-phase['start']
4939
left = '%.3f' % (((phase['start']-t0)*100.0)/tTotal)
4940
width = '%.3f' % ((length*100.0)/tTotal)
4941
hf.write(devtl.html_phaselet.format(b, left, width, \
4942
data.dmesg[b]['color']))
4943
hf.write(devtl.html_phaselet.format('post_resume_process', \
4944
'0', '0', pscolor))
4945
if sysvals.suspendmode == 'command':
4946
hf.write(devtl.html_phaselet.format('cmdexec', '0', '0', pscolor))
4947
hf.write('</div>\n')
4948
hf.write('</div>\n')
4949
4950
# write the ftrace data (callgraph)
4951
if sysvals.cgtest >= 0 and len(testruns) > sysvals.cgtest:
4952
data = testruns[sysvals.cgtest]
4953
else:
4954
data = testruns[-1]
4955
if sysvals.usecallgraph:
4956
addCallgraphs(sysvals, hf, data)
4957
4958
# add the test log as a hidden div
4959
if sysvals.testlog and sysvals.logmsg:
4960
hf.write('<div id="testlog" style="display:none;">\n'+sysvals.logmsg+'</div>\n')
4961
# add the dmesg log as a hidden div
4962
if sysvals.dmesglog and sysvals.dmesgfile:
4963
hf.write('<div id="dmesglog" style="display:none;">\n')
4964
lf = sysvals.openlog(sysvals.dmesgfile, 'r')
4965
for line in lf:
4966
line = line.replace('<', '&lt').replace('>', '&gt')
4967
hf.write(line)
4968
lf.close()
4969
hf.write('</div>\n')
4970
# add the ftrace log as a hidden div
4971
if sysvals.ftracelog and sysvals.ftracefile:
4972
hf.write('<div id="ftracelog" style="display:none;">\n')
4973
lf = sysvals.openlog(sysvals.ftracefile, 'r')
4974
for line in lf:
4975
hf.write(line)
4976
lf.close()
4977
hf.write('</div>\n')
4978
4979
# write the footer and close
4980
addScriptCode(hf, testruns)
4981
hf.write('</body>\n</html>\n')
4982
hf.close()
4983
return True
4984
4985
def addCSS(hf, sv, testcount=1, kerror=False, extra=''):
4986
kernel = sv.stamp['kernel']
4987
host = sv.hostname[0].upper()+sv.hostname[1:]
4988
mode = sv.suspendmode
4989
if sv.suspendmode in suspendmodename:
4990
mode = suspendmodename[sv.suspendmode]
4991
title = host+' '+mode+' '+kernel
4992
4993
# various format changes by flags
4994
cgchk = 'checked'
4995
cgnchk = 'not(:checked)'
4996
if sv.cgexp:
4997
cgchk = 'not(:checked)'
4998
cgnchk = 'checked'
4999
5000
hoverZ = 'z-index:8;'
5001
if sv.usedevsrc:
5002
hoverZ = ''
5003
5004
devlistpos = 'absolute'
5005
if testcount > 1:
5006
devlistpos = 'relative'
5007
5008
scaleTH = 20
5009
if kerror:
5010
scaleTH = 60
5011
5012
# write the html header first (html head, css code, up to body start)
5013
html_header = '<!DOCTYPE html>\n<html>\n<head>\n\
5014
<meta http-equiv="content-type" content="text/html; charset=UTF-8">\n\
5015
<title>'+title+'</title>\n\
5016
<style type=\'text/css\'>\n\
5017
body {overflow-y:scroll;}\n\
5018
.stamp {width:100%;text-align:center;background:gray;line-height:30px;color:white;font:25px Arial;}\n\
5019
.stamp.sysinfo {font:10px Arial;}\n\
5020
.callgraph {margin-top:30px;box-shadow:5px 5px 20px black;}\n\
5021
.callgraph article * {padding-left:28px;}\n\
5022
h1 {color:black;font:bold 30px Times;}\n\
5023
t0 {color:black;font:bold 30px Times;}\n\
5024
t1 {color:black;font:30px Times;}\n\
5025
t2 {color:black;font:25px Times;}\n\
5026
t3 {color:black;font:20px Times;white-space:nowrap;}\n\
5027
t4 {color:black;font:bold 30px Times;line-height:60px;white-space:nowrap;}\n\
5028
cS {font:bold 13px Times;}\n\
5029
table {width:100%;}\n\
5030
.gray {background:rgba(80,80,80,0.1);}\n\
5031
.green {background:rgba(204,255,204,0.4);}\n\
5032
.purple {background:rgba(128,0,128,0.2);}\n\
5033
.yellow {background:rgba(255,255,204,0.4);}\n\
5034
.blue {background:rgba(169,208,245,0.4);}\n\
5035
.time1 {font:22px Arial;border:1px solid;}\n\
5036
.time2 {font:15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
5037
.testfail {font:bold 22px Arial;color:red;border:1px dashed;}\n\
5038
td {text-align:center;}\n\
5039
r {color:#500000;font:15px Tahoma;}\n\
5040
n {color:#505050;font:15px Tahoma;}\n\
5041
.tdhl {color:red;}\n\
5042
.hide {display:none;}\n\
5043
.pf {display:none;}\n\
5044
.pf:'+cgchk+' + 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\
5045
.pf:'+cgnchk+' ~ 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\
5046
.pf:'+cgchk+' ~ *:not(:nth-child(2)) {display:none;}\n\
5047
.zoombox {position:relative;width:100%;overflow-x:scroll;-webkit-user-select:none;-moz-user-select:none;user-select:none;}\n\
5048
.timeline {position:relative;font-size:14px;cursor:pointer;width:100%; overflow:hidden;background:linear-gradient(#cccccc, white);}\n\
5049
.thread {position:absolute;height:0%;overflow:hidden;z-index:7;line-height:30px;font-size:14px;border:1px solid;text-align:center;white-space:nowrap;}\n\
5050
.thread.ps {border-radius:3px;background:linear-gradient(to top, #ccc, #eee);}\n\
5051
.thread:hover {background:white;border:1px solid red;'+hoverZ+'}\n\
5052
.thread.sec,.thread.sec:hover {background:black;border:0;color:white;line-height:15px;font-size:10px;}\n\
5053
.hover {background:white;border:1px solid red;'+hoverZ+'}\n\
5054
.hover.sync {background:white;}\n\
5055
.hover.bg,.hover.kth,.hover.sync,.hover.ps {background:white;}\n\
5056
.jiffie {position:absolute;pointer-events: none;z-index:8;}\n\
5057
.traceevent {position:absolute;font-size:10px;z-index:7;overflow:hidden;color:black;text-align:center;white-space:nowrap;border-radius:5px;border:1px solid black;background:linear-gradient(to bottom right,#CCC,#969696);}\n\
5058
.traceevent:hover {color:white;font-weight:bold;border:1px solid white;}\n\
5059
.phase {position:absolute;overflow:hidden;border:0px;text-align:center;}\n\
5060
.phaselet {float:left;overflow:hidden;border:0px;text-align:center;min-height:100px;font-size:24px;}\n\
5061
.t {position:absolute;line-height:'+('%d'%scaleTH)+'px;pointer-events:none;top:0;height:100%;border-right:1px solid black;z-index:6;}\n\
5062
.err {position:absolute;top:0%;height:100%;border-right:3px solid red;color:red;font:bold 14px Times;line-height:18px;}\n\
5063
.legend {position:relative; width:100%; height:40px; text-align:center;margin-bottom:20px}\n\
5064
.legend .square {position:absolute;cursor:pointer;top:10px; width:0px;height:20px;border:1px solid;padding-left:20px;}\n\
5065
button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
5066
.btnfmt {position:relative;float:right;height:25px;width:auto;margin-top:3px;margin-bottom:0;font-size:10px;text-align:center;}\n\
5067
.devlist {position:'+devlistpos+';width:190px;}\n\
5068
a:link {color:white;text-decoration:none;}\n\
5069
a:visited {color:white;}\n\
5070
a:hover {color:white;}\n\
5071
a:active {color:white;}\n\
5072
.version {position:relative;float:left;color:white;font-size:10px;line-height:30px;margin-left:10px;}\n\
5073
#devicedetail {min-height:100px;box-shadow:5px 5px 20px black;}\n\
5074
.tblock {position:absolute;height:100%;background:#ddd;}\n\
5075
.tback {position:absolute;width:100%;background:linear-gradient(#ccc, #ddd);}\n\
5076
.bg {z-index:1;}\n\
5077
'+extra+'\
5078
</style>\n</head>\n<body>\n'
5079
hf.write(html_header)
5080
5081
# Function: addScriptCode
5082
# Description:
5083
# Adds the javascript code to the output html
5084
# Arguments:
5085
# hf: the open html file pointer
5086
# testruns: array of Data objects from parseKernelLog or parseTraceLog
5087
def addScriptCode(hf, testruns):
5088
t0 = testruns[0].start * 1000
5089
tMax = testruns[-1].end * 1000
5090
hf.write('<script type="text/javascript">\n');
5091
# create an array in javascript memory with the device details
5092
detail = ' var devtable = [];\n'
5093
for data in testruns:
5094
topo = data.deviceTopology()
5095
detail += ' devtable[%d] = "%s";\n' % (data.testnumber, topo)
5096
detail += ' var bounds = [%f,%f];\n' % (t0, tMax)
5097
# add the code which will manipulate the data in the browser
5098
hf.write(detail);
5099
script_code = r""" var resolution = -1;
5100
var dragval = [0, 0];
5101
function redrawTimescale(t0, tMax, tS) {
5102
var rline = '<div class="t" style="left:0;border-left:1px solid black;border-right:0;">';
5103
var tTotal = tMax - t0;
5104
var list = document.getElementsByClassName("tblock");
5105
for (var i = 0; i < list.length; i++) {
5106
var timescale = list[i].getElementsByClassName("timescale")[0];
5107
var m0 = t0 + (tTotal*parseFloat(list[i].style.left)/100);
5108
var mTotal = tTotal*parseFloat(list[i].style.width)/100;
5109
var mMax = m0 + mTotal;
5110
var html = "";
5111
var divTotal = Math.floor(mTotal/tS) + 1;
5112
if(divTotal > 1000) continue;
5113
var divEdge = (mTotal - tS*(divTotal-1))*100/mTotal;
5114
var pos = 0.0, val = 0.0;
5115
for (var j = 0; j < divTotal; j++) {
5116
var htmlline = "";
5117
var mode = list[i].id[5];
5118
if(mode == "s") {
5119
pos = 100 - (((j)*tS*100)/mTotal) - divEdge;
5120
val = (j-divTotal+1)*tS;
5121
if(j == divTotal - 1)
5122
htmlline = '<div class="t" style="right:'+pos+'%"><cS>S&rarr;</cS></div>';
5123
else
5124
htmlline = '<div class="t" style="right:'+pos+'%">'+val+'ms</div>';
5125
} else {
5126
pos = 100 - (((j)*tS*100)/mTotal);
5127
val = (j)*tS;
5128
htmlline = '<div class="t" style="right:'+pos+'%">'+val+'ms</div>';
5129
if(j == 0)
5130
if(mode == "r")
5131
htmlline = rline+"<cS>&larr;R</cS></div>";
5132
else
5133
htmlline = rline+"<cS>0ms</div>";
5134
}
5135
html += htmlline;
5136
}
5137
timescale.innerHTML = html;
5138
}
5139
}
5140
function zoomTimeline() {
5141
var dmesg = document.getElementById("dmesg");
5142
var zoombox = document.getElementById("dmesgzoombox");
5143
var left = zoombox.scrollLeft;
5144
var val = parseFloat(dmesg.style.width);
5145
var newval = 100;
5146
var sh = window.outerWidth / 2;
5147
if(this.id == "zoomin") {
5148
newval = val * 1.2;
5149
if(newval > 910034) newval = 910034;
5150
dmesg.style.width = newval+"%";
5151
zoombox.scrollLeft = ((left + sh) * newval / val) - sh;
5152
} else if (this.id == "zoomout") {
5153
newval = val / 1.2;
5154
if(newval < 100) newval = 100;
5155
dmesg.style.width = newval+"%";
5156
zoombox.scrollLeft = ((left + sh) * newval / val) - sh;
5157
} else {
5158
zoombox.scrollLeft = 0;
5159
dmesg.style.width = "100%";
5160
}
5161
var tS = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1];
5162
var t0 = bounds[0];
5163
var tMax = bounds[1];
5164
var tTotal = tMax - t0;
5165
var wTotal = tTotal * 100.0 / newval;
5166
var idx = 7*window.innerWidth/1100;
5167
for(var i = 0; (i < tS.length)&&((wTotal / tS[i]) < idx); i++);
5168
if(i >= tS.length) i = tS.length - 1;
5169
if(tS[i] == resolution) return;
5170
resolution = tS[i];
5171
redrawTimescale(t0, tMax, tS[i]);
5172
}
5173
function deviceName(title) {
5174
var name = title.slice(0, title.indexOf(" ("));
5175
return name;
5176
}
5177
function deviceHover() {
5178
var name = deviceName(this.title);
5179
var dmesg = document.getElementById("dmesg");
5180
var dev = dmesg.getElementsByClassName("thread");
5181
var cpu = -1;
5182
if(name.match("CPU_ON\[[0-9]*\]"))
5183
cpu = parseInt(name.slice(7));
5184
else if(name.match("CPU_OFF\[[0-9]*\]"))
5185
cpu = parseInt(name.slice(8));
5186
for (var i = 0; i < dev.length; i++) {
5187
dname = deviceName(dev[i].title);
5188
var cname = dev[i].className.slice(dev[i].className.indexOf("thread"));
5189
if((cpu >= 0 && dname.match("CPU_O[NF]*\\[*"+cpu+"\\]")) ||
5190
(name == dname))
5191
{
5192
dev[i].className = "hover "+cname;
5193
} else {
5194
dev[i].className = cname;
5195
}
5196
}
5197
}
5198
function deviceUnhover() {
5199
var dmesg = document.getElementById("dmesg");
5200
var dev = dmesg.getElementsByClassName("thread");
5201
for (var i = 0; i < dev.length; i++) {
5202
dev[i].className = dev[i].className.slice(dev[i].className.indexOf("thread"));
5203
}
5204
}
5205
function deviceTitle(title, total, cpu) {
5206
var prefix = "Total";
5207
if(total.length > 3) {
5208
prefix = "Average";
5209
total[1] = (total[1]+total[3])/2;
5210
total[2] = (total[2]+total[4])/2;
5211
}
5212
var devtitle = document.getElementById("devicedetailtitle");
5213
var name = deviceName(title);
5214
if(cpu >= 0) name = "CPU"+cpu;
5215
var driver = "";
5216
var tS = "<t2>(</t2>";
5217
var tR = "<t2>)</t2>";
5218
if(total[1] > 0)
5219
tS = "<t2>("+prefix+" Suspend:</t2><t0> "+total[1].toFixed(3)+" ms</t0> ";
5220
if(total[2] > 0)
5221
tR = " <t2>"+prefix+" Resume:</t2><t0> "+total[2].toFixed(3)+" ms<t2>)</t2></t0>";
5222
var s = title.indexOf("{");
5223
var e = title.indexOf("}");
5224
if((s >= 0) && (e >= 0))
5225
driver = title.slice(s+1, e) + " <t1>@</t1> ";
5226
if(total[1] > 0 && total[2] > 0)
5227
devtitle.innerHTML = "<t0>"+driver+name+"</t0> "+tS+tR;
5228
else
5229
devtitle.innerHTML = "<t0>"+title+"</t0>";
5230
return name;
5231
}
5232
function deviceDetail() {
5233
var devinfo = document.getElementById("devicedetail");
5234
devinfo.style.display = "block";
5235
var name = deviceName(this.title);
5236
var cpu = -1;
5237
if(name.match("CPU_ON\[[0-9]*\]"))
5238
cpu = parseInt(name.slice(7));
5239
else if(name.match("CPU_OFF\[[0-9]*\]"))
5240
cpu = parseInt(name.slice(8));
5241
var dmesg = document.getElementById("dmesg");
5242
var dev = dmesg.getElementsByClassName("thread");
5243
var idlist = [];
5244
var pdata = [[]];
5245
if(document.getElementById("devicedetail1"))
5246
pdata = [[], []];
5247
var pd = pdata[0];
5248
var total = [0.0, 0.0, 0.0];
5249
for (var i = 0; i < dev.length; i++) {
5250
dname = deviceName(dev[i].title);
5251
if((cpu >= 0 && dname.match("CPU_O[NF]*\\[*"+cpu+"\\]")) ||
5252
(name == dname))
5253
{
5254
idlist[idlist.length] = dev[i].id;
5255
var tidx = 1;
5256
if(dev[i].id[0] == "a") {
5257
pd = pdata[0];
5258
} else {
5259
if(pdata.length == 1) pdata[1] = [];
5260
if(total.length == 3) total[3]=total[4]=0.0;
5261
pd = pdata[1];
5262
tidx = 3;
5263
}
5264
var info = dev[i].title.split(" ");
5265
var pname = info[info.length-1];
5266
var length = parseFloat(info[info.length-3].slice(1));
5267
if (pname in pd)
5268
pd[pname] += length;
5269
else
5270
pd[pname] = length;
5271
total[0] += length;
5272
if(pname.indexOf("suspend") >= 0)
5273
total[tidx] += length;
5274
else
5275
total[tidx+1] += length;
5276
}
5277
}
5278
var devname = deviceTitle(this.title, total, cpu);
5279
var left = 0.0;
5280
for (var t = 0; t < pdata.length; t++) {
5281
pd = pdata[t];
5282
devinfo = document.getElementById("devicedetail"+t);
5283
var phases = devinfo.getElementsByClassName("phaselet");
5284
for (var i = 0; i < phases.length; i++) {
5285
if(phases[i].id in pd) {
5286
var w = 100.0*pd[phases[i].id]/total[0];
5287
var fs = 32;
5288
if(w < 8) fs = 4*w | 0;
5289
var fs2 = fs*3/4;
5290
phases[i].style.width = w+"%";
5291
phases[i].style.left = left+"%";
5292
phases[i].title = phases[i].id+" "+pd[phases[i].id]+" ms";
5293
left += w;
5294
var time = "<t4 style=\"font-size:"+fs+"px\">"+pd[phases[i].id].toFixed(3)+" ms<br></t4>";
5295
var pname = "<t3 style=\"font-size:"+fs2+"px\">"+phases[i].id.replace(new RegExp("_", "g"), " ")+"</t3>";
5296
phases[i].innerHTML = time+pname;
5297
} else {
5298
phases[i].style.width = "0%";
5299
phases[i].style.left = left+"%";
5300
}
5301
}
5302
}
5303
if(typeof devstats !== 'undefined')
5304
callDetail(this.id, this.title);
5305
var cglist = document.getElementById("callgraphs");
5306
if(!cglist) return;
5307
var cg = cglist.getElementsByClassName("atop");
5308
if(cg.length < 10) return;
5309
for (var i = 0; i < cg.length; i++) {
5310
cgid = cg[i].id.split("x")[0]
5311
if(idlist.indexOf(cgid) >= 0) {
5312
cg[i].style.display = "block";
5313
} else {
5314
cg[i].style.display = "none";
5315
}
5316
}
5317
}
5318
function callDetail(devid, devtitle) {
5319
if(!(devid in devstats) || devstats[devid].length < 1)
5320
return;
5321
var list = devstats[devid];
5322
var tmp = devtitle.split(" ");
5323
var name = tmp[0], phase = tmp[tmp.length-1];
5324
var dd = document.getElementById(phase);
5325
var total = parseFloat(tmp[1].slice(1));
5326
var mlist = [];
5327
var maxlen = 0;
5328
var info = []
5329
for(var i in list) {
5330
if(list[i][0] == "@") {
5331
info = list[i].split("|");
5332
continue;
5333
}
5334
var tmp = list[i].split("|");
5335
var t = parseFloat(tmp[0]), f = tmp[1], c = parseInt(tmp[2]);
5336
var p = (t*100.0/total).toFixed(2);
5337
mlist[mlist.length] = [f, c, t.toFixed(2), p+"%"];
5338
if(f.length > maxlen)
5339
maxlen = f.length;
5340
}
5341
var pad = 5;
5342
if(mlist.length == 0) pad = 30;
5343
var html = '<div style="padding-top:'+pad+'px"><t3> <b>'+name+':</b>';
5344
if(info.length > 2)
5345
html += " start=<b>"+info[1]+"</b>, end=<b>"+info[2]+"</b>";
5346
if(info.length > 3)
5347
html += ", length<i>(w/o overhead)</i>=<b>"+info[3]+" ms</b>";
5348
if(info.length > 4)
5349
html += ", return=<b>"+info[4]+"</b>";
5350
html += "</t3></div>";
5351
if(mlist.length > 0) {
5352
html += '<table class=fstat style="padding-top:'+(maxlen*5)+'px;"><tr><th>Function</th>';
5353
for(var i in mlist)
5354
html += "<td class=vt>"+mlist[i][0]+"</td>";
5355
html += "</tr><tr><th>Calls</th>";
5356
for(var i in mlist)
5357
html += "<td>"+mlist[i][1]+"</td>";
5358
html += "</tr><tr><th>Time(ms)</th>";
5359
for(var i in mlist)
5360
html += "<td>"+mlist[i][2]+"</td>";
5361
html += "</tr><tr><th>Percent</th>";
5362
for(var i in mlist)
5363
html += "<td>"+mlist[i][3]+"</td>";
5364
html += "</tr></table>";
5365
}
5366
dd.innerHTML = html;
5367
var height = (maxlen*5)+100;
5368
dd.style.height = height+"px";
5369
document.getElementById("devicedetail").style.height = height+"px";
5370
}
5371
function callSelect() {
5372
var cglist = document.getElementById("callgraphs");
5373
if(!cglist) return;
5374
var cg = cglist.getElementsByClassName("atop");
5375
for (var i = 0; i < cg.length; i++) {
5376
if(this.id == cg[i].id) {
5377
cg[i].style.display = "block";
5378
} else {
5379
cg[i].style.display = "none";
5380
}
5381
}
5382
}
5383
function devListWindow(e) {
5384
var win = window.open();
5385
var html = "<title>"+e.target.innerHTML+"</title>"+
5386
"<style type=\"text/css\">"+
5387
" ul {list-style-type:circle;padding-left:10px;margin-left:10px;}"+
5388
"</style>"
5389
var dt = devtable[0];
5390
if(e.target.id != "devlist1")
5391
dt = devtable[1];
5392
win.document.write(html+dt);
5393
}
5394
function errWindow() {
5395
var range = this.id.split("_");
5396
var idx1 = parseInt(range[0]);
5397
var idx2 = parseInt(range[1]);
5398
var win = window.open();
5399
var log = document.getElementById("dmesglog");
5400
var title = "<title>dmesg log</title>";
5401
var text = log.innerHTML.split("\n");
5402
var html = "";
5403
for(var i = 0; i < text.length; i++) {
5404
if(i == idx1) {
5405
html += "<e id=target>"+text[i]+"</e>\n";
5406
} else if(i > idx1 && i <= idx2) {
5407
html += "<e>"+text[i]+"</e>\n";
5408
} else {
5409
html += text[i]+"\n";
5410
}
5411
}
5412
win.document.write("<style>e{color:red}</style>"+title+"<pre>"+html+"</pre>");
5413
win.location.hash = "#target";
5414
win.document.close();
5415
}
5416
function logWindow(e) {
5417
var name = e.target.id.slice(4);
5418
var win = window.open();
5419
var log = document.getElementById(name+"log");
5420
var title = "<title>"+document.title.split(" ")[0]+" "+name+" log</title>";
5421
win.document.write(title+"<pre>"+log.innerHTML+"</pre>");
5422
win.document.close();
5423
}
5424
function onMouseDown(e) {
5425
dragval[0] = e.clientX;
5426
dragval[1] = document.getElementById("dmesgzoombox").scrollLeft;
5427
document.onmousemove = onMouseMove;
5428
}
5429
function onMouseMove(e) {
5430
var zoombox = document.getElementById("dmesgzoombox");
5431
zoombox.scrollLeft = dragval[1] + dragval[0] - e.clientX;
5432
}
5433
function onMouseUp(e) {
5434
document.onmousemove = null;
5435
}
5436
function onKeyPress(e) {
5437
var c = e.charCode;
5438
if(c != 42 && c != 43 && c != 45) return;
5439
var click = document.createEvent("Events");
5440
click.initEvent("click", true, false);
5441
if(c == 43)
5442
document.getElementById("zoomin").dispatchEvent(click);
5443
else if(c == 45)
5444
document.getElementById("zoomout").dispatchEvent(click);
5445
else if(c == 42)
5446
document.getElementById("zoomdef").dispatchEvent(click);
5447
}
5448
window.addEventListener("resize", function () {zoomTimeline();});
5449
window.addEventListener("load", function () {
5450
var dmesg = document.getElementById("dmesg");
5451
dmesg.style.width = "100%"
5452
dmesg.onmousedown = onMouseDown;
5453
document.onmouseup = onMouseUp;
5454
document.onkeypress = onKeyPress;
5455
document.getElementById("zoomin").onclick = zoomTimeline;
5456
document.getElementById("zoomout").onclick = zoomTimeline;
5457
document.getElementById("zoomdef").onclick = zoomTimeline;
5458
var list = document.getElementsByClassName("err");
5459
for (var i = 0; i < list.length; i++)
5460
list[i].onclick = errWindow;
5461
var list = document.getElementsByClassName("logbtn");
5462
for (var i = 0; i < list.length; i++)
5463
list[i].onclick = logWindow;
5464
list = document.getElementsByClassName("devlist");
5465
for (var i = 0; i < list.length; i++)
5466
list[i].onclick = devListWindow;
5467
var dev = dmesg.getElementsByClassName("thread");
5468
for (var i = 0; i < dev.length; i++) {
5469
dev[i].onclick = deviceDetail;
5470
dev[i].onmouseover = deviceHover;
5471
dev[i].onmouseout = deviceUnhover;
5472
}
5473
var dev = dmesg.getElementsByClassName("srccall");
5474
for (var i = 0; i < dev.length; i++)
5475
dev[i].onclick = callSelect;
5476
zoomTimeline();
5477
});
5478
</script> """
5479
hf.write(script_code);
5480
5481
# Function: executeSuspend
5482
# Description:
5483
# Execute system suspend through the sysfs interface, then copy the output
5484
# dmesg and ftrace files to the test output directory.
5485
def executeSuspend(quiet=False):
5486
sv, tp, pm = sysvals, sysvals.tpath, ProcessMonitor()
5487
if sv.wifi:
5488
wifi = sv.checkWifi()
5489
sv.dlog('wifi check, connected device is "%s"' % wifi)
5490
testdata = []
5491
# run these commands to prepare the system for suspend
5492
if sv.display:
5493
if not quiet:
5494
pprint('SET DISPLAY TO %s' % sv.display.upper())
5495
ret = sv.displayControl(sv.display)
5496
sv.dlog('xset display %s, ret = %d' % (sv.display, ret))
5497
time.sleep(1)
5498
if sv.sync:
5499
if not quiet:
5500
pprint('SYNCING FILESYSTEMS')
5501
sv.dlog('syncing filesystems')
5502
call('sync', shell=True)
5503
sv.dlog('read dmesg')
5504
sv.initdmesg()
5505
sv.dlog('cmdinfo before')
5506
sv.cmdinfo(True)
5507
sv.start(pm)
5508
# execute however many s/r runs requested
5509
for count in range(1,sv.execcount+1):
5510
# x2delay in between test runs
5511
if(count > 1 and sv.x2delay > 0):
5512
sv.fsetVal('WAIT %d' % sv.x2delay, 'trace_marker')
5513
time.sleep(sv.x2delay/1000.0)
5514
sv.fsetVal('WAIT END', 'trace_marker')
5515
# start message
5516
if sv.testcommand != '':
5517
pprint('COMMAND START')
5518
else:
5519
if(sv.rtcwake):
5520
pprint('SUSPEND START')
5521
else:
5522
pprint('SUSPEND START (press a key to resume)')
5523
# set rtcwake
5524
if(sv.rtcwake):
5525
if not quiet:
5526
pprint('will issue an rtcwake in %d seconds' % sv.rtcwaketime)
5527
sv.dlog('enable RTC wake alarm')
5528
sv.rtcWakeAlarmOn()
5529
# start of suspend trace marker
5530
sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
5531
# predelay delay
5532
if(count == 1 and sv.predelay > 0):
5533
sv.fsetVal('WAIT %d' % sv.predelay, 'trace_marker')
5534
time.sleep(sv.predelay/1000.0)
5535
sv.fsetVal('WAIT END', 'trace_marker')
5536
# initiate suspend or command
5537
sv.dlog('system executing a suspend')
5538
tdata = {'error': ''}
5539
if sv.testcommand != '':
5540
res = call(sv.testcommand+' 2>&1', shell=True);
5541
if res != 0:
5542
tdata['error'] = 'cmd returned %d' % res
5543
else:
5544
s0ixready = sv.s0ixSupport()
5545
mode = sv.suspendmode
5546
if sv.memmode and os.path.exists(sv.mempowerfile):
5547
mode = 'mem'
5548
sv.testVal(sv.mempowerfile, 'radio', sv.memmode)
5549
if sv.diskmode and os.path.exists(sv.diskpowerfile):
5550
mode = 'disk'
5551
sv.testVal(sv.diskpowerfile, 'radio', sv.diskmode)
5552
if sv.acpidebug:
5553
sv.testVal(sv.acpipath, 'acpi', '0xe')
5554
if ((mode == 'freeze') or (sv.memmode == 's2idle')) \
5555
and sv.haveTurbostat():
5556
# execution will pause here
5557
retval, turbo = sv.turbostat(s0ixready)
5558
if retval != 0:
5559
tdata['error'] ='turbostat returned %d' % retval
5560
if turbo:
5561
tdata['turbo'] = turbo
5562
else:
5563
pf = open(sv.powerfile, 'w')
5564
pf.write(mode)
5565
# execution will pause here
5566
try:
5567
pf.flush()
5568
pf.close()
5569
except Exception as e:
5570
tdata['error'] = str(e)
5571
sv.fsetVal('CMD COMPLETE', 'trace_marker')
5572
sv.dlog('system returned')
5573
# reset everything
5574
sv.testVal('restoreall')
5575
if(sv.rtcwake):
5576
sv.dlog('disable RTC wake alarm')
5577
sv.rtcWakeAlarmOff()
5578
# postdelay delay
5579
if(count == sv.execcount and sv.postdelay > 0):
5580
sv.fsetVal('WAIT %d' % sv.postdelay, 'trace_marker')
5581
time.sleep(sv.postdelay/1000.0)
5582
sv.fsetVal('WAIT END', 'trace_marker')
5583
# return from suspend
5584
pprint('RESUME COMPLETE')
5585
if(count < sv.execcount):
5586
sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5587
elif(not sv.wifitrace):
5588
sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5589
sv.stop(pm)
5590
if sv.wifi and wifi:
5591
tdata['wifi'] = sv.pollWifi(wifi)
5592
sv.dlog('wifi check, %s' % tdata['wifi'])
5593
if(count == sv.execcount and sv.wifitrace):
5594
sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
5595
sv.stop(pm)
5596
if sv.netfix:
5597
tdata['netfix'] = sv.netfixon()
5598
sv.dlog('netfix, %s' % tdata['netfix'])
5599
if(sv.suspendmode == 'mem' or sv.suspendmode == 'command'):
5600
sv.dlog('read the ACPI FPDT')
5601
tdata['fw'] = getFPDT(False)
5602
testdata.append(tdata)
5603
sv.dlog('cmdinfo after')
5604
cmdafter = sv.cmdinfo(False)
5605
# grab a copy of the dmesg output
5606
if not quiet:
5607
pprint('CAPTURING DMESG')
5608
sv.getdmesg(testdata)
5609
# grab a copy of the ftrace output
5610
if sv.useftrace:
5611
if not quiet:
5612
pprint('CAPTURING TRACE')
5613
op = sv.writeDatafileHeader(sv.ftracefile, testdata)
5614
fp = open(tp+'trace', 'rb')
5615
op.write(ascii(fp.read()))
5616
op.close()
5617
sv.fsetVal('', 'trace')
5618
sv.platforminfo(cmdafter)
5619
5620
def readFile(file):
5621
if os.path.islink(file):
5622
return os.readlink(file).split('/')[-1]
5623
else:
5624
return sysvals.getVal(file).strip()
5625
5626
# Function: ms2nice
5627
# Description:
5628
# Print out a very concise time string in minutes and seconds
5629
# Output:
5630
# The time string, e.g. "1901m16s"
5631
def ms2nice(val):
5632
val = int(val)
5633
h = val // 3600000
5634
m = (val // 60000) % 60
5635
s = (val // 1000) % 60
5636
if h > 0:
5637
return '%d:%02d:%02d' % (h, m, s)
5638
if m > 0:
5639
return '%02d:%02d' % (m, s)
5640
return '%ds' % s
5641
5642
def yesno(val):
5643
list = {'enabled':'A', 'disabled':'S', 'auto':'E', 'on':'D',
5644
'active':'A', 'suspended':'S', 'suspending':'S'}
5645
if val not in list:
5646
return ' '
5647
return list[val]
5648
5649
# Function: deviceInfo
5650
# Description:
5651
# Detect all the USB hosts and devices currently connected and add
5652
# a list of USB device names to sysvals for better timeline readability
5653
def deviceInfo(output=''):
5654
if not output:
5655
pprint('LEGEND\n'\
5656
'---------------------------------------------------------------------------------------------\n'\
5657
' A = async/sync PM queue (A/S) C = runtime active children\n'\
5658
' R = runtime suspend enabled/disabled (E/D) rACTIVE = runtime active (min/sec)\n'\
5659
' S = runtime status active/suspended (A/S) rSUSPEND = runtime suspend (min/sec)\n'\
5660
' U = runtime usage count\n'\
5661
'---------------------------------------------------------------------------------------------\n'\
5662
'DEVICE NAME A R S U C rACTIVE rSUSPEND\n'\
5663
'---------------------------------------------------------------------------------------------')
5664
5665
res = []
5666
tgtval = 'runtime_status'
5667
lines = dict()
5668
for dirname, dirnames, filenames in os.walk('/sys/devices'):
5669
if(not re.match(r'.*/power', dirname) or
5670
'control' not in filenames or
5671
tgtval not in filenames):
5672
continue
5673
name = ''
5674
dirname = dirname[:-6]
5675
device = dirname.split('/')[-1]
5676
power = dict()
5677
power[tgtval] = readFile('%s/power/%s' % (dirname, tgtval))
5678
# only list devices which support runtime suspend
5679
if power[tgtval] not in ['active', 'suspended', 'suspending']:
5680
continue
5681
for i in ['product', 'driver', 'subsystem']:
5682
file = '%s/%s' % (dirname, i)
5683
if os.path.exists(file):
5684
name = readFile(file)
5685
break
5686
for i in ['async', 'control', 'runtime_status', 'runtime_usage',
5687
'runtime_active_kids', 'runtime_active_time',
5688
'runtime_suspended_time']:
5689
if i in filenames:
5690
power[i] = readFile('%s/power/%s' % (dirname, i))
5691
if output:
5692
if power['control'] == output:
5693
res.append('%s/power/control' % dirname)
5694
continue
5695
lines[dirname] = '%-26s %-26s %1s %1s %1s %1s %1s %10s %10s' % \
5696
(device[:26], name[:26],
5697
yesno(power['async']), \
5698
yesno(power['control']), \
5699
yesno(power['runtime_status']), \
5700
power['runtime_usage'], \
5701
power['runtime_active_kids'], \
5702
ms2nice(power['runtime_active_time']), \
5703
ms2nice(power['runtime_suspended_time']))
5704
for i in sorted(lines):
5705
print(lines[i])
5706
return res
5707
5708
# Function: getModes
5709
# Description:
5710
# Determine the supported power modes on this system
5711
# Output:
5712
# A string list of the available modes
5713
def getModes():
5714
modes = []
5715
if(os.path.exists(sysvals.powerfile)):
5716
fp = open(sysvals.powerfile, 'r')
5717
modes = fp.read().split()
5718
fp.close()
5719
if(os.path.exists(sysvals.mempowerfile)):
5720
deep = False
5721
fp = open(sysvals.mempowerfile, 'r')
5722
for m in fp.read().split():
5723
memmode = m.strip('[]')
5724
if memmode == 'deep':
5725
deep = True
5726
else:
5727
modes.append('mem-%s' % memmode)
5728
fp.close()
5729
if 'mem' in modes and not deep:
5730
modes.remove('mem')
5731
if('disk' in modes and os.path.exists(sysvals.diskpowerfile)):
5732
fp = open(sysvals.diskpowerfile, 'r')
5733
for m in fp.read().split():
5734
modes.append('disk-%s' % m.strip('[]'))
5735
fp.close()
5736
return modes
5737
5738
def dmidecode_backup(out, fatal=False):
5739
cpath, spath, info = '/proc/cpuinfo', '/sys/class/dmi/id', {
5740
'bios-vendor': 'bios_vendor',
5741
'bios-version': 'bios_version',
5742
'bios-release-date': 'bios_date',
5743
'system-manufacturer': 'sys_vendor',
5744
'system-product-name': 'product_name',
5745
'system-version': 'product_version',
5746
'system-serial-number': 'product_serial',
5747
'baseboard-manufacturer': 'board_vendor',
5748
'baseboard-product-name': 'board_name',
5749
'baseboard-version': 'board_version',
5750
'baseboard-serial-number': 'board_serial',
5751
'chassis-manufacturer': 'chassis_vendor',
5752
'chassis-version': 'chassis_version',
5753
'chassis-serial-number': 'chassis_serial',
5754
}
5755
for key in info:
5756
if key not in out:
5757
val = sysvals.getVal(os.path.join(spath, info[key])).strip()
5758
if val and val.lower() != 'to be filled by o.e.m.':
5759
out[key] = val
5760
if 'processor-version' not in out and os.path.exists(cpath):
5761
with open(cpath, 'r') as fp:
5762
for line in fp:
5763
m = re.match(r'^model\s*name\s*\:\s*(?P<c>.*)', line)
5764
if m:
5765
out['processor-version'] = m.group('c').strip()
5766
break
5767
if fatal and len(out) < 1:
5768
doError('dmidecode failed to get info from %s or %s' % \
5769
(sysvals.mempath, spath))
5770
return out
5771
5772
# Function: dmidecode
5773
# Description:
5774
# Read the bios tables and pull out system info
5775
# Arguments:
5776
# mempath: /dev/mem or custom mem path
5777
# fatal: True to exit on error, False to return empty dict
5778
# Output:
5779
# A dict object with all available key/values
5780
def dmidecode(mempath, fatal=False):
5781
out = dict()
5782
if(not (os.path.exists(mempath) and os.access(mempath, os.R_OK))):
5783
return dmidecode_backup(out, fatal)
5784
5785
# the list of values to retrieve, with hardcoded (type, idx)
5786
info = {
5787
'bios-vendor': (0, 4),
5788
'bios-version': (0, 5),
5789
'bios-release-date': (0, 8),
5790
'system-manufacturer': (1, 4),
5791
'system-product-name': (1, 5),
5792
'system-version': (1, 6),
5793
'system-serial-number': (1, 7),
5794
'baseboard-manufacturer': (2, 4),
5795
'baseboard-product-name': (2, 5),
5796
'baseboard-version': (2, 6),
5797
'baseboard-serial-number': (2, 7),
5798
'chassis-manufacturer': (3, 4),
5799
'chassis-version': (3, 6),
5800
'chassis-serial-number': (3, 7),
5801
'processor-manufacturer': (4, 7),
5802
'processor-version': (4, 16),
5803
}
5804
5805
# by default use legacy scan, but try to use EFI first
5806
memaddr, memsize = 0xf0000, 0x10000
5807
for ep in ['/sys/firmware/efi/systab', '/proc/efi/systab']:
5808
if not os.path.exists(ep) or not os.access(ep, os.R_OK):
5809
continue
5810
fp = open(ep, 'r')
5811
buf = fp.read()
5812
fp.close()
5813
i = buf.find('SMBIOS=')
5814
if i >= 0:
5815
try:
5816
memaddr = int(buf[i+7:], 16)
5817
memsize = 0x20
5818
except:
5819
continue
5820
5821
# read in the memory for scanning
5822
try:
5823
fp = open(mempath, 'rb')
5824
fp.seek(memaddr)
5825
buf = fp.read(memsize)
5826
except:
5827
return dmidecode_backup(out, fatal)
5828
fp.close()
5829
5830
# search for either an SM table or DMI table
5831
i = base = length = num = 0
5832
while(i < memsize):
5833
if buf[i:i+4] == b'_SM_' and i < memsize - 16:
5834
length = struct.unpack('H', buf[i+22:i+24])[0]
5835
base, num = struct.unpack('IH', buf[i+24:i+30])
5836
break
5837
elif buf[i:i+5] == b'_DMI_':
5838
length = struct.unpack('H', buf[i+6:i+8])[0]
5839
base, num = struct.unpack('IH', buf[i+8:i+14])
5840
break
5841
i += 16
5842
if base == 0 and length == 0 and num == 0:
5843
return dmidecode_backup(out, fatal)
5844
5845
# read in the SM or DMI table
5846
try:
5847
fp = open(mempath, 'rb')
5848
fp.seek(base)
5849
buf = fp.read(length)
5850
except:
5851
return dmidecode_backup(out, fatal)
5852
fp.close()
5853
5854
# scan the table for the values we want
5855
count = i = 0
5856
while(count < num and i <= len(buf) - 4):
5857
type, size, handle = struct.unpack('BBH', buf[i:i+4])
5858
n = i + size
5859
while n < len(buf) - 1:
5860
if 0 == struct.unpack('H', buf[n:n+2])[0]:
5861
break
5862
n += 1
5863
data = buf[i+size:n+2].split(b'\0')
5864
for name in info:
5865
itype, idxadr = info[name]
5866
if itype == type:
5867
idx = struct.unpack('B', buf[i+idxadr:i+idxadr+1])[0]
5868
if idx > 0 and idx < len(data) - 1:
5869
s = data[idx-1].decode('utf-8')
5870
if s.strip() and s.strip().lower() != 'to be filled by o.e.m.':
5871
out[name] = s
5872
i = n + 2
5873
count += 1
5874
return out
5875
5876
# Function: getFPDT
5877
# Description:
5878
# Read the acpi bios tables and pull out FPDT, the firmware data
5879
# Arguments:
5880
# output: True to output the info to stdout, False otherwise
5881
def getFPDT(output):
5882
rectype = {}
5883
rectype[0] = 'Firmware Basic Boot Performance Record'
5884
rectype[1] = 'S3 Performance Table Record'
5885
prectype = {}
5886
prectype[0] = 'Basic S3 Resume Performance Record'
5887
prectype[1] = 'Basic S3 Suspend Performance Record'
5888
5889
sysvals.rootCheck(True)
5890
if(not os.path.exists(sysvals.fpdtpath)):
5891
if(output):
5892
doError('file does not exist: %s' % sysvals.fpdtpath)
5893
return False
5894
if(not os.access(sysvals.fpdtpath, os.R_OK)):
5895
if(output):
5896
doError('file is not readable: %s' % sysvals.fpdtpath)
5897
return False
5898
if(not os.path.exists(sysvals.mempath)):
5899
if(output):
5900
doError('file does not exist: %s' % sysvals.mempath)
5901
return False
5902
if(not os.access(sysvals.mempath, os.R_OK)):
5903
if(output):
5904
doError('file is not readable: %s' % sysvals.mempath)
5905
return False
5906
5907
fp = open(sysvals.fpdtpath, 'rb')
5908
buf = fp.read()
5909
fp.close()
5910
5911
if(len(buf) < 36):
5912
if(output):
5913
doError('Invalid FPDT table data, should '+\
5914
'be at least 36 bytes')
5915
return False
5916
5917
table = struct.unpack('4sIBB6s8sI4sI', buf[0:36])
5918
if(output):
5919
pprint('\n'\
5920
'Firmware Performance Data Table (%s)\n'\
5921
' Signature : %s\n'\
5922
' Table Length : %u\n'\
5923
' Revision : %u\n'\
5924
' Checksum : 0x%x\n'\
5925
' OEM ID : %s\n'\
5926
' OEM Table ID : %s\n'\
5927
' OEM Revision : %u\n'\
5928
' Creator ID : %s\n'\
5929
' Creator Revision : 0x%x\n'\
5930
'' % (ascii(table[0]), ascii(table[0]), table[1], table[2],
5931
table[3], ascii(table[4]), ascii(table[5]), table[6],
5932
ascii(table[7]), table[8]))
5933
5934
if(table[0] != b'FPDT'):
5935
if(output):
5936
doError('Invalid FPDT table')
5937
return False
5938
if(len(buf) <= 36):
5939
return False
5940
i = 0
5941
fwData = [0, 0]
5942
records = buf[36:]
5943
try:
5944
fp = open(sysvals.mempath, 'rb')
5945
except:
5946
pprint('WARNING: /dev/mem is not readable, ignoring the FPDT data')
5947
return False
5948
while(i < len(records)):
5949
header = struct.unpack('HBB', records[i:i+4])
5950
if(header[0] not in rectype):
5951
i += header[1]
5952
continue
5953
if(header[1] != 16):
5954
i += header[1]
5955
continue
5956
addr = struct.unpack('Q', records[i+8:i+16])[0]
5957
try:
5958
fp.seek(addr)
5959
first = fp.read(8)
5960
except:
5961
if(output):
5962
pprint('Bad address 0x%x in %s' % (addr, sysvals.mempath))
5963
return [0, 0]
5964
rechead = struct.unpack('4sI', first)
5965
recdata = fp.read(rechead[1]-8)
5966
if(rechead[0] == b'FBPT'):
5967
record = struct.unpack('HBBIQQQQQ', recdata[:48])
5968
if(output):
5969
pprint('%s (%s)\n'\
5970
' Reset END : %u ns\n'\
5971
' OS Loader LoadImage Start : %u ns\n'\
5972
' OS Loader StartImage Start : %u ns\n'\
5973
' ExitBootServices Entry : %u ns\n'\
5974
' ExitBootServices Exit : %u ns'\
5975
'' % (rectype[header[0]], ascii(rechead[0]), record[4], record[5],
5976
record[6], record[7], record[8]))
5977
elif(rechead[0] == b'S3PT'):
5978
if(output):
5979
pprint('%s (%s)' % (rectype[header[0]], ascii(rechead[0])))
5980
j = 0
5981
while(j < len(recdata)):
5982
prechead = struct.unpack('HBB', recdata[j:j+4])
5983
if(prechead[0] not in prectype):
5984
continue
5985
if(prechead[0] == 0):
5986
record = struct.unpack('IIQQ', recdata[j:j+prechead[1]])
5987
fwData[1] = record[2]
5988
if(output):
5989
pprint(' %s\n'\
5990
' Resume Count : %u\n'\
5991
' FullResume : %u ns\n'\
5992
' AverageResume : %u ns'\
5993
'' % (prectype[prechead[0]], record[1],
5994
record[2], record[3]))
5995
elif(prechead[0] == 1):
5996
record = struct.unpack('QQ', recdata[j+4:j+prechead[1]])
5997
fwData[0] = record[1] - record[0]
5998
if(output):
5999
pprint(' %s\n'\
6000
' SuspendStart : %u ns\n'\
6001
' SuspendEnd : %u ns\n'\
6002
' SuspendTime : %u ns'\
6003
'' % (prectype[prechead[0]], record[0],
6004
record[1], fwData[0]))
6005
6006
j += prechead[1]
6007
if(output):
6008
pprint('')
6009
i += header[1]
6010
fp.close()
6011
return fwData
6012
6013
# Function: statusCheck
6014
# Description:
6015
# Verify that the requested command and options will work, and
6016
# print the results to the terminal
6017
# Output:
6018
# True if the test will work, False if not
6019
def statusCheck(probecheck=False):
6020
status = ''
6021
6022
pprint('Checking this system (%s)...' % platform.node())
6023
6024
# check we have root access
6025
res = sysvals.colorText('NO (No features of this tool will work!)')
6026
if(sysvals.rootCheck(False)):
6027
res = 'YES'
6028
pprint(' have root access: %s' % res)
6029
if(res != 'YES'):
6030
pprint(' Try running this script with sudo')
6031
return 'missing root access'
6032
6033
# check sysfs is mounted
6034
res = sysvals.colorText('NO (No features of this tool will work!)')
6035
if(os.path.exists(sysvals.powerfile)):
6036
res = 'YES'
6037
pprint(' is sysfs mounted: %s' % res)
6038
if(res != 'YES'):
6039
return 'sysfs is missing'
6040
6041
# check target mode is a valid mode
6042
if sysvals.suspendmode != 'command':
6043
res = sysvals.colorText('NO')
6044
modes = getModes()
6045
if(sysvals.suspendmode in modes):
6046
res = 'YES'
6047
else:
6048
status = '%s mode is not supported' % sysvals.suspendmode
6049
pprint(' is "%s" a valid power mode: %s' % (sysvals.suspendmode, res))
6050
if(res == 'NO'):
6051
pprint(' valid power modes are: %s' % modes)
6052
pprint(' please choose one with -m')
6053
6054
# check if ftrace is available
6055
if sysvals.useftrace:
6056
res = sysvals.colorText('NO')
6057
sysvals.useftrace = sysvals.verifyFtrace()
6058
efmt = '"{0}" uses ftrace, and it is not properly supported'
6059
if sysvals.useftrace:
6060
res = 'YES'
6061
elif sysvals.usecallgraph:
6062
status = efmt.format('-f')
6063
elif sysvals.usedevsrc:
6064
status = efmt.format('-dev')
6065
elif sysvals.useprocmon:
6066
status = efmt.format('-proc')
6067
pprint(' is ftrace supported: %s' % res)
6068
6069
# check if kprobes are available
6070
if sysvals.usekprobes:
6071
res = sysvals.colorText('NO')
6072
sysvals.usekprobes = sysvals.verifyKprobes()
6073
if(sysvals.usekprobes):
6074
res = 'YES'
6075
else:
6076
sysvals.usedevsrc = False
6077
pprint(' are kprobes supported: %s' % res)
6078
6079
# what data source are we using
6080
res = 'DMESG (very limited, ftrace is preferred)'
6081
if sysvals.useftrace:
6082
sysvals.usetraceevents = True
6083
for e in sysvals.traceevents:
6084
if not os.path.exists(sysvals.epath+e):
6085
sysvals.usetraceevents = False
6086
if(sysvals.usetraceevents):
6087
res = 'FTRACE (all trace events found)'
6088
pprint(' timeline data source: %s' % res)
6089
6090
# check if rtcwake
6091
res = sysvals.colorText('NO')
6092
if(sysvals.rtcpath != ''):
6093
res = 'YES'
6094
elif(sysvals.rtcwake):
6095
status = 'rtcwake is not properly supported'
6096
pprint(' is rtcwake supported: %s' % res)
6097
6098
# check info commands
6099
pprint(' optional commands this tool may use for info:')
6100
no = sysvals.colorText('MISSING')
6101
yes = sysvals.colorText('FOUND', 32)
6102
for c in ['turbostat', 'mcelog', 'lspci', 'lsusb', 'netfix']:
6103
if c == 'turbostat':
6104
res = yes if sysvals.haveTurbostat() else no
6105
else:
6106
res = yes if sysvals.getExec(c) else no
6107
pprint(' %s: %s' % (c, res))
6108
6109
if not probecheck:
6110
return status
6111
6112
# verify kprobes
6113
if sysvals.usekprobes:
6114
for name in sysvals.tracefuncs:
6115
sysvals.defaultKprobe(name, sysvals.tracefuncs[name])
6116
if sysvals.usedevsrc:
6117
for name in sysvals.dev_tracefuncs:
6118
sysvals.defaultKprobe(name, sysvals.dev_tracefuncs[name])
6119
sysvals.addKprobes(True)
6120
6121
return status
6122
6123
# Function: doError
6124
# Description:
6125
# generic error function for catastrphic failures
6126
# Arguments:
6127
# msg: the error message to print
6128
# help: True if printHelp should be called after, False otherwise
6129
def doError(msg, help=False):
6130
if(help == True):
6131
printHelp()
6132
pprint('ERROR: %s\n' % msg)
6133
sysvals.outputResult({'error':msg})
6134
sys.exit(1)
6135
6136
# Function: getArgInt
6137
# Description:
6138
# pull out an integer argument from the command line with checks
6139
def getArgInt(name, args, min, max, main=True):
6140
if main:
6141
try:
6142
arg = next(args)
6143
except:
6144
doError(name+': no argument supplied', True)
6145
else:
6146
arg = args
6147
try:
6148
val = int(arg)
6149
except:
6150
doError(name+': non-integer value given', True)
6151
if(val < min or val > max):
6152
doError(name+': value should be between %d and %d' % (min, max), True)
6153
return val
6154
6155
# Function: getArgFloat
6156
# Description:
6157
# pull out a float argument from the command line with checks
6158
def getArgFloat(name, args, min, max, main=True):
6159
if main:
6160
try:
6161
arg = next(args)
6162
except:
6163
doError(name+': no argument supplied', True)
6164
else:
6165
arg = args
6166
try:
6167
val = float(arg)
6168
except:
6169
doError(name+': non-numerical value given', True)
6170
if(val < min or val > max):
6171
doError(name+': value should be between %f and %f' % (min, max), True)
6172
return val
6173
6174
def processData(live=False, quiet=False):
6175
if not quiet:
6176
pprint('PROCESSING: %s' % sysvals.htmlfile)
6177
sysvals.vprint('usetraceevents=%s, usetracemarkers=%s, usekprobes=%s' % \
6178
(sysvals.usetraceevents, sysvals.usetracemarkers, sysvals.usekprobes))
6179
error = ''
6180
if(sysvals.usetraceevents):
6181
testruns, error = parseTraceLog(live)
6182
if sysvals.dmesgfile:
6183
for data in testruns:
6184
data.extractErrorInfo()
6185
else:
6186
testruns = loadKernelLog()
6187
for data in testruns:
6188
parseKernelLog(data)
6189
if(sysvals.ftracefile and (sysvals.usecallgraph or sysvals.usetraceevents)):
6190
appendIncompleteTraceLog(testruns)
6191
if not sysvals.stamp:
6192
pprint('ERROR: data does not include the expected stamp')
6193
return (testruns, {'error': 'timeline generation failed'})
6194
shown = ['os', 'bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
6195
'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
6196
sysvals.vprint('System Info:')
6197
for key in sorted(sysvals.stamp):
6198
if key in shown:
6199
sysvals.vprint(' %-8s : %s' % (key.upper(), sysvals.stamp[key]))
6200
sysvals.vprint('Command:\n %s' % sysvals.cmdline)
6201
for data in testruns:
6202
if data.turbostat:
6203
idx, s = 0, 'Turbostat:\n '
6204
for val in data.turbostat.split('|'):
6205
idx += len(val) + 1
6206
if idx >= 80:
6207
idx = 0
6208
s += '\n '
6209
s += val + ' '
6210
sysvals.vprint(s)
6211
data.printDetails()
6212
if len(sysvals.platinfo) > 0:
6213
sysvals.vprint('\nPlatform Info:')
6214
for info in sysvals.platinfo:
6215
sysvals.vprint('[%s - %s]' % (info[0], info[1]))
6216
sysvals.vprint(info[2])
6217
sysvals.vprint('')
6218
if sysvals.cgdump:
6219
for data in testruns:
6220
data.debugPrint()
6221
sys.exit(0)
6222
if len(testruns) < 1:
6223
pprint('ERROR: Not enough test data to build a timeline')
6224
return (testruns, {'error': 'timeline generation failed'})
6225
sysvals.vprint('Creating the html timeline (%s)...' % sysvals.htmlfile)
6226
createHTML(testruns, error)
6227
if not quiet:
6228
pprint('DONE: %s' % sysvals.htmlfile)
6229
data = testruns[0]
6230
stamp = data.stamp
6231
stamp['suspend'], stamp['resume'] = data.getTimeValues()
6232
if data.fwValid:
6233
stamp['fwsuspend'], stamp['fwresume'] = data.fwSuspend, data.fwResume
6234
if error:
6235
stamp['error'] = error
6236
return (testruns, stamp)
6237
6238
# Function: rerunTest
6239
# Description:
6240
# generate an output from an existing set of ftrace/dmesg logs
6241
def rerunTest(htmlfile=''):
6242
if sysvals.ftracefile:
6243
doesTraceLogHaveTraceEvents()
6244
if not sysvals.dmesgfile and not sysvals.usetraceevents:
6245
doError('recreating this html output requires a dmesg file')
6246
if htmlfile:
6247
sysvals.htmlfile = htmlfile
6248
else:
6249
sysvals.setOutputFile()
6250
if os.path.exists(sysvals.htmlfile):
6251
if not os.path.isfile(sysvals.htmlfile):
6252
doError('a directory already exists with this name: %s' % sysvals.htmlfile)
6253
elif not os.access(sysvals.htmlfile, os.W_OK):
6254
doError('missing permission to write to %s' % sysvals.htmlfile)
6255
testruns, stamp = processData()
6256
sysvals.resetlog()
6257
return stamp
6258
6259
# Function: runTest
6260
# Description:
6261
# execute a suspend/resume, gather the logs, and generate the output
6262
def runTest(n=0, quiet=False):
6263
# prepare for the test
6264
sysvals.initTestOutput('suspend')
6265
op = sysvals.writeDatafileHeader(sysvals.dmesgfile, [])
6266
op.write('# EXECUTION TRACE START\n')
6267
op.close()
6268
if n <= 1:
6269
if sysvals.rs != 0:
6270
sysvals.dlog('%sabling runtime suspend' % ('en' if sysvals.rs > 0 else 'dis'))
6271
sysvals.setRuntimeSuspend(True)
6272
if sysvals.display:
6273
ret = sysvals.displayControl('init')
6274
sysvals.dlog('xset display init, ret = %d' % ret)
6275
sysvals.testVal(sysvals.pmdpath, 'basic', '1')
6276
sysvals.testVal(sysvals.s0ixpath, 'basic', 'Y')
6277
sysvals.dlog('initialize ftrace')
6278
sysvals.initFtrace(quiet)
6279
6280
# execute the test
6281
executeSuspend(quiet)
6282
sysvals.cleanupFtrace()
6283
if sysvals.skiphtml:
6284
sysvals.outputResult({}, n)
6285
sysvals.sudoUserchown(sysvals.testdir)
6286
return
6287
testruns, stamp = processData(True, quiet)
6288
for data in testruns:
6289
del data
6290
sysvals.sudoUserchown(sysvals.testdir)
6291
sysvals.outputResult(stamp, n)
6292
if 'error' in stamp:
6293
return 2
6294
return 0
6295
6296
def find_in_html(html, start, end, firstonly=True):
6297
cnt, out, list = len(html), [], []
6298
if firstonly:
6299
m = re.search(start, html)
6300
if m:
6301
list.append(m)
6302
else:
6303
list = re.finditer(start, html)
6304
for match in list:
6305
s = match.end()
6306
e = cnt if (len(out) < 1 or s + 10000 > cnt) else s + 10000
6307
m = re.search(end, html[s:e])
6308
if not m:
6309
break
6310
e = s + m.start()
6311
str = html[s:e]
6312
if end == 'ms':
6313
num = re.search(r'[-+]?\d*\.\d+|\d+', str)
6314
str = num.group() if num else 'NaN'
6315
if firstonly:
6316
return str
6317
out.append(str)
6318
if firstonly:
6319
return ''
6320
return out
6321
6322
def data_from_html(file, outpath, issues, fulldetail=False):
6323
try:
6324
html = open(file, 'r').read()
6325
except:
6326
html = ascii(open(file, 'rb').read())
6327
sysvals.htmlfile = os.path.relpath(file, outpath)
6328
# extract general info
6329
suspend = find_in_html(html, 'Kernel Suspend', 'ms')
6330
resume = find_in_html(html, 'Kernel Resume', 'ms')
6331
sysinfo = find_in_html(html, '<div class="stamp sysinfo">', '</div>')
6332
line = find_in_html(html, '<div class="stamp">', '</div>')
6333
stmp = line.split()
6334
if not suspend or not resume or len(stmp) != 8:
6335
return False
6336
try:
6337
dt = datetime.strptime(' '.join(stmp[3:]), '%B %d %Y, %I:%M:%S %p')
6338
except:
6339
return False
6340
sysvals.hostname = stmp[0]
6341
tstr = dt.strftime('%Y/%m/%d %H:%M:%S')
6342
error = find_in_html(html, '<table class="testfail"><tr><td>', '</td>')
6343
if error:
6344
m = re.match(r'[a-z0-9]* failed in (?P<p>\S*).*', error)
6345
if m:
6346
result = 'fail in %s' % m.group('p')
6347
else:
6348
result = 'fail'
6349
else:
6350
result = 'pass'
6351
# extract error info
6352
tp, ilist = False, []
6353
extra = dict()
6354
log = find_in_html(html, '<div id="dmesglog" style="display:none;">',
6355
'</div>').strip()
6356
if log:
6357
d = Data(0)
6358
d.end = 999999999
6359
d.dmesgtext = log.split('\n')
6360
tp = d.extractErrorInfo()
6361
if len(issues) < 100:
6362
for msg in tp.msglist:
6363
sysvals.errorSummary(issues, msg)
6364
if stmp[2] == 'freeze':
6365
extra = d.turbostatInfo()
6366
elist = dict()
6367
for dir in d.errorinfo:
6368
for err in d.errorinfo[dir]:
6369
if err[0] not in elist:
6370
elist[err[0]] = 0
6371
elist[err[0]] += 1
6372
for i in elist:
6373
ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
6374
line = find_in_html(log, '# wifi ', '\n')
6375
if line:
6376
extra['wifi'] = line
6377
line = find_in_html(log, '# netfix ', '\n')
6378
if line:
6379
extra['netfix'] = line
6380
line = find_in_html(log, '# command ', '\n')
6381
if line:
6382
m = re.match(r'.* -m (?P<m>\S*).*', line)
6383
if m:
6384
extra['fullmode'] = m.group('m')
6385
low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
6386
for lowstr in ['waking', '+']:
6387
if not low:
6388
break
6389
if lowstr not in low:
6390
continue
6391
if lowstr == '+':
6392
issue = 'S2LOOPx%d' % len(low.split('+'))
6393
else:
6394
m = re.match(r'.*waking *(?P<n>[0-9]*) *times.*', low)
6395
issue = 'S2WAKEx%s' % m.group('n') if m else 'S2WAKExNaN'
6396
match = [i for i in issues if i['match'] == issue]
6397
if len(match) > 0:
6398
match[0]['count'] += 1
6399
if sysvals.hostname not in match[0]['urls']:
6400
match[0]['urls'][sysvals.hostname] = [sysvals.htmlfile]
6401
elif sysvals.htmlfile not in match[0]['urls'][sysvals.hostname]:
6402
match[0]['urls'][sysvals.hostname].append(sysvals.htmlfile)
6403
else:
6404
issues.append({
6405
'match': issue, 'count': 1, 'line': issue,
6406
'urls': {sysvals.hostname: [sysvals.htmlfile]},
6407
})
6408
ilist.append(issue)
6409
# extract device info
6410
devices = dict()
6411
for line in html.split('\n'):
6412
m = re.match(r' *<div id=\"[a,0-9]*\" *title=\"(?P<title>.*)\" class=\"thread.*', line)
6413
if not m or 'thread kth' in line or 'thread sec' in line:
6414
continue
6415
m = re.match(r'(?P<n>.*) \((?P<t>[0-9,\.]*) ms\) (?P<p>.*)', m.group('title'))
6416
if not m:
6417
continue
6418
name, time, phase = m.group('n'), m.group('t'), m.group('p')
6419
if name == 'async_synchronize_full':
6420
continue
6421
if ' async' in name or ' sync' in name:
6422
name = ' '.join(name.split(' ')[:-1])
6423
if phase.startswith('suspend'):
6424
d = 'suspend'
6425
elif phase.startswith('resume'):
6426
d = 'resume'
6427
else:
6428
continue
6429
if d not in devices:
6430
devices[d] = dict()
6431
if name not in devices[d]:
6432
devices[d][name] = 0.0
6433
devices[d][name] += float(time)
6434
# create worst device info
6435
worst = dict()
6436
for d in ['suspend', 'resume']:
6437
worst[d] = {'name':'', 'time': 0.0}
6438
dev = devices[d] if d in devices else 0
6439
if dev and len(dev.keys()) > 0:
6440
n = sorted(dev, key=lambda k:(dev[k], k), reverse=True)[0]
6441
worst[d]['name'], worst[d]['time'] = n, dev[n]
6442
data = {
6443
'mode': stmp[2],
6444
'host': stmp[0],
6445
'kernel': stmp[1],
6446
'sysinfo': sysinfo,
6447
'time': tstr,
6448
'result': result,
6449
'issues': ' '.join(ilist),
6450
'suspend': suspend,
6451
'resume': resume,
6452
'devlist': devices,
6453
'sus_worst': worst['suspend']['name'],
6454
'sus_worsttime': worst['suspend']['time'],
6455
'res_worst': worst['resume']['name'],
6456
'res_worsttime': worst['resume']['time'],
6457
'url': sysvals.htmlfile,
6458
}
6459
for key in extra:
6460
data[key] = extra[key]
6461
if fulldetail:
6462
data['funclist'] = find_in_html(html, '<div title="', '" class="traceevent"', False)
6463
if tp:
6464
for arg in ['-multi ', '-info ']:
6465
if arg in tp.cmdline:
6466
data['target'] = tp.cmdline[tp.cmdline.find(arg):].split()[1]
6467
break
6468
return data
6469
6470
def genHtml(subdir, force=False):
6471
for dirname, dirnames, filenames in os.walk(subdir):
6472
sysvals.dmesgfile = sysvals.ftracefile = sysvals.htmlfile = ''
6473
for filename in filenames:
6474
file = os.path.join(dirname, filename)
6475
if sysvals.usable(file):
6476
if(re.match(r'.*_dmesg.txt', filename)):
6477
sysvals.dmesgfile = file
6478
elif(re.match(r'.*_ftrace.txt', filename)):
6479
sysvals.ftracefile = file
6480
sysvals.setOutputFile()
6481
if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
6482
(force or not sysvals.usable(sysvals.htmlfile, True)):
6483
pprint('FTRACE: %s' % sysvals.ftracefile)
6484
if sysvals.dmesgfile:
6485
pprint('DMESG : %s' % sysvals.dmesgfile)
6486
rerunTest()
6487
6488
# Function: runSummary
6489
# Description:
6490
# create a summary of tests in a sub-directory
6491
def runSummary(subdir, local=True, genhtml=False):
6492
inpath = os.path.abspath(subdir)
6493
outpath = os.path.abspath('.') if local else inpath
6494
pprint('Generating a summary of folder:\n %s' % inpath)
6495
if genhtml:
6496
genHtml(subdir)
6497
target, issues, testruns = '', [], []
6498
desc = {'host':[],'mode':[],'kernel':[]}
6499
for dirname, dirnames, filenames in os.walk(subdir):
6500
for filename in filenames:
6501
if(not re.match(r'.*.html', filename)):
6502
continue
6503
data = data_from_html(os.path.join(dirname, filename), outpath, issues)
6504
if(not data):
6505
continue
6506
if 'target' in data:
6507
target = data['target']
6508
testruns.append(data)
6509
for key in desc:
6510
if data[key] not in desc[key]:
6511
desc[key].append(data[key])
6512
pprint('Summary files:')
6513
if len(desc['host']) == len(desc['mode']) == len(desc['kernel']) == 1:
6514
title = '%s %s %s' % (desc['host'][0], desc['kernel'][0], desc['mode'][0])
6515
if target:
6516
title += ' %s' % target
6517
else:
6518
title = inpath
6519
createHTMLSummarySimple(testruns, os.path.join(outpath, 'summary.html'), title)
6520
pprint(' summary.html - tabular list of test data found')
6521
createHTMLDeviceSummary(testruns, os.path.join(outpath, 'summary-devices.html'), title)
6522
pprint(' summary-devices.html - kernel device list sorted by total execution time')
6523
createHTMLIssuesSummary(testruns, issues, os.path.join(outpath, 'summary-issues.html'), title)
6524
pprint(' summary-issues.html - kernel issues found sorted by frequency')
6525
6526
# Function: checkArgBool
6527
# Description:
6528
# check if a boolean string value is true or false
6529
def checkArgBool(name, value):
6530
if value in switchvalues:
6531
if value in switchoff:
6532
return False
6533
return True
6534
doError('invalid boolean --> (%s: %s), use "true/false" or "1/0"' % (name, value), True)
6535
return False
6536
6537
# Function: configFromFile
6538
# Description:
6539
# Configure the script via the info in a config file
6540
def configFromFile(file):
6541
Config = configparser.ConfigParser()
6542
6543
Config.read(file)
6544
sections = Config.sections()
6545
overridekprobes = False
6546
overridedevkprobes = False
6547
if 'Settings' in sections:
6548
for opt in Config.options('Settings'):
6549
value = Config.get('Settings', opt).lower()
6550
option = opt.lower()
6551
if(option == 'verbose'):
6552
sysvals.verbose = checkArgBool(option, value)
6553
elif(option == 'addlogs'):
6554
sysvals.dmesglog = sysvals.ftracelog = checkArgBool(option, value)
6555
elif(option == 'dev'):
6556
sysvals.usedevsrc = checkArgBool(option, value)
6557
elif(option == 'proc'):
6558
sysvals.useprocmon = checkArgBool(option, value)
6559
elif(option == 'x2'):
6560
if checkArgBool(option, value):
6561
sysvals.execcount = 2
6562
elif(option == 'callgraph'):
6563
sysvals.usecallgraph = checkArgBool(option, value)
6564
elif(option == 'override-timeline-functions'):
6565
overridekprobes = checkArgBool(option, value)
6566
elif(option == 'override-dev-timeline-functions'):
6567
overridedevkprobes = checkArgBool(option, value)
6568
elif(option == 'skiphtml'):
6569
sysvals.skiphtml = checkArgBool(option, value)
6570
elif(option == 'sync'):
6571
sysvals.sync = checkArgBool(option, value)
6572
elif(option == 'rs' or option == 'runtimesuspend'):
6573
if value in switchvalues:
6574
if value in switchoff:
6575
sysvals.rs = -1
6576
else:
6577
sysvals.rs = 1
6578
else:
6579
doError('invalid value --> (%s: %s), use "enable/disable"' % (option, value), True)
6580
elif(option == 'display'):
6581
disopt = ['on', 'off', 'standby', 'suspend']
6582
if value not in disopt:
6583
doError('invalid value --> (%s: %s), use %s' % (option, value, disopt), True)
6584
sysvals.display = value
6585
elif(option == 'gzip'):
6586
sysvals.gzip = checkArgBool(option, value)
6587
elif(option == 'cgfilter'):
6588
sysvals.setCallgraphFilter(value)
6589
elif(option == 'cgskip'):
6590
if value in switchoff:
6591
sysvals.cgskip = ''
6592
else:
6593
sysvals.cgskip = sysvals.configFile(val)
6594
if(not sysvals.cgskip):
6595
doError('%s does not exist' % sysvals.cgskip)
6596
elif(option == 'cgtest'):
6597
sysvals.cgtest = getArgInt('cgtest', value, 0, 1, False)
6598
elif(option == 'cgphase'):
6599
d = Data(0)
6600
if value not in d.phasedef:
6601
doError('invalid phase --> (%s: %s), valid phases are %s'\
6602
% (option, value, d.phasedef.keys()), True)
6603
sysvals.cgphase = value
6604
elif(option == 'fadd'):
6605
file = sysvals.configFile(value)
6606
if(not file):
6607
doError('%s does not exist' % value)
6608
sysvals.addFtraceFilterFunctions(file)
6609
elif(option == 'result'):
6610
sysvals.result = value
6611
elif(option == 'multi'):
6612
nums = value.split()
6613
if len(nums) != 2:
6614
doError('multi requires 2 integers (exec_count and delay)', True)
6615
sysvals.multiinit(nums[0], nums[1])
6616
elif(option == 'devicefilter'):
6617
sysvals.setDeviceFilter(value)
6618
elif(option == 'expandcg'):
6619
sysvals.cgexp = checkArgBool(option, value)
6620
elif(option == 'srgap'):
6621
if checkArgBool(option, value):
6622
sysvals.srgap = 5
6623
elif(option == 'mode'):
6624
sysvals.suspendmode = value
6625
elif(option == 'command' or option == 'cmd'):
6626
sysvals.testcommand = value
6627
elif(option == 'x2delay'):
6628
sysvals.x2delay = getArgInt('x2delay', value, 0, 60000, False)
6629
elif(option == 'predelay'):
6630
sysvals.predelay = getArgInt('predelay', value, 0, 60000, False)
6631
elif(option == 'postdelay'):
6632
sysvals.postdelay = getArgInt('postdelay', value, 0, 60000, False)
6633
elif(option == 'maxdepth'):
6634
sysvals.max_graph_depth = getArgInt('maxdepth', value, 0, 1000, False)
6635
elif(option == 'rtcwake'):
6636
if value in switchoff:
6637
sysvals.rtcwake = False
6638
else:
6639
sysvals.rtcwake = True
6640
sysvals.rtcwaketime = getArgInt('rtcwake', value, 0, 3600, False)
6641
elif(option == 'timeprec'):
6642
sysvals.setPrecision(getArgInt('timeprec', value, 0, 6, False))
6643
elif(option == 'mindev'):
6644
sysvals.mindevlen = getArgFloat('mindev', value, 0.0, 10000.0, False)
6645
elif(option == 'callloop-maxgap'):
6646
sysvals.callloopmaxgap = getArgFloat('callloop-maxgap', value, 0.0, 1.0, False)
6647
elif(option == 'callloop-maxlen'):
6648
sysvals.callloopmaxgap = getArgFloat('callloop-maxlen', value, 0.0, 1.0, False)
6649
elif(option == 'mincg'):
6650
sysvals.mincglen = getArgFloat('mincg', value, 0.0, 10000.0, False)
6651
elif(option == 'bufsize'):
6652
sysvals.bufsize = getArgInt('bufsize', value, 1, 1024*1024*8, False)
6653
elif(option == 'output-dir'):
6654
sysvals.outdir = sysvals.setOutputFolder(value)
6655
6656
if sysvals.suspendmode == 'command' and not sysvals.testcommand:
6657
doError('No command supplied for mode "command"')
6658
6659
# compatibility errors
6660
if sysvals.usedevsrc and sysvals.usecallgraph:
6661
doError('-dev is not compatible with -f')
6662
if sysvals.usecallgraph and sysvals.useprocmon:
6663
doError('-proc is not compatible with -f')
6664
6665
if overridekprobes:
6666
sysvals.tracefuncs = dict()
6667
if overridedevkprobes:
6668
sysvals.dev_tracefuncs = dict()
6669
6670
kprobes = dict()
6671
kprobesec = 'dev_timeline_functions_'+platform.machine()
6672
if kprobesec in sections:
6673
for name in Config.options(kprobesec):
6674
text = Config.get(kprobesec, name)
6675
kprobes[name] = (text, True)
6676
kprobesec = 'timeline_functions_'+platform.machine()
6677
if kprobesec in sections:
6678
for name in Config.options(kprobesec):
6679
if name in kprobes:
6680
doError('Duplicate timeline function found "%s"' % (name))
6681
text = Config.get(kprobesec, name)
6682
kprobes[name] = (text, False)
6683
6684
for name in kprobes:
6685
function = name
6686
format = name
6687
color = ''
6688
args = dict()
6689
text, dev = kprobes[name]
6690
data = text.split()
6691
i = 0
6692
for val in data:
6693
# bracketted strings are special formatting, read them separately
6694
if val[0] == '[' and val[-1] == ']':
6695
for prop in val[1:-1].split(','):
6696
p = prop.split('=')
6697
if p[0] == 'color':
6698
try:
6699
color = int(p[1], 16)
6700
color = '#'+p[1]
6701
except:
6702
color = p[1]
6703
continue
6704
# first real arg should be the format string
6705
if i == 0:
6706
format = val
6707
# all other args are actual function args
6708
else:
6709
d = val.split('=')
6710
args[d[0]] = d[1]
6711
i += 1
6712
if not function or not format:
6713
doError('Invalid kprobe: %s' % name)
6714
for arg in re.findall('{(?P<n>[a-z,A-Z,0-9]*)}', format):
6715
if arg not in args:
6716
doError('Kprobe "%s" is missing argument "%s"' % (name, arg))
6717
if (dev and name in sysvals.dev_tracefuncs) or (not dev and name in sysvals.tracefuncs):
6718
doError('Duplicate timeline function found "%s"' % (name))
6719
6720
kp = {
6721
'name': name,
6722
'func': function,
6723
'format': format,
6724
sysvals.archargs: args
6725
}
6726
if color:
6727
kp['color'] = color
6728
if dev:
6729
sysvals.dev_tracefuncs[name] = kp
6730
else:
6731
sysvals.tracefuncs[name] = kp
6732
6733
# Function: printHelp
6734
# Description:
6735
# print out the help text
6736
def printHelp():
6737
pprint('\n%s v%s\n'\
6738
'Usage: sudo sleepgraph <options> <commands>\n'\
6739
'\n'\
6740
'Description:\n'\
6741
' This tool is designed to assist kernel and OS developers in optimizing\n'\
6742
' their linux stack\'s suspend/resume time. Using a kernel image built\n'\
6743
' with a few extra options enabled, the tool will execute a suspend and\n'\
6744
' capture dmesg and ftrace data until resume is complete. This data is\n'\
6745
' transformed into a device timeline and an optional callgraph to give\n'\
6746
' a detailed view of which devices/subsystems are taking the most\n'\
6747
' time in suspend/resume.\n'\
6748
'\n'\
6749
' If no specific command is given, the default behavior is to initiate\n'\
6750
' a suspend/resume and capture the dmesg/ftrace output as an html timeline.\n'\
6751
'\n'\
6752
' Generates output files in subdirectory: suspend-yymmdd-HHMMSS\n'\
6753
' HTML output: <hostname>_<mode>.html\n'\
6754
' raw dmesg output: <hostname>_<mode>_dmesg.txt\n'\
6755
' raw ftrace output: <hostname>_<mode>_ftrace.txt\n'\
6756
'\n'\
6757
'Options:\n'\
6758
' -h Print this help text\n'\
6759
' -v Print the current tool version\n'\
6760
' -config fn Pull arguments and config options from file fn\n'\
6761
' -verbose Print extra information during execution and analysis\n'\
6762
' -m mode Mode to initiate for suspend (default: %s)\n'\
6763
' -o name Overrides the output subdirectory name when running a new test\n'\
6764
' default: suspend-{date}-{time}\n'\
6765
' -rtcwake t Wakeup t seconds after suspend, set t to "off" to disable (default: 15)\n'\
6766
' -addlogs Add the dmesg and ftrace logs to the html output\n'\
6767
' -noturbostat Dont use turbostat in freeze mode (default: disabled)\n'\
6768
' -srgap Add a visible gap in the timeline between sus/res (default: disabled)\n'\
6769
' -skiphtml Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
6770
' -result fn Export a results table to a text file for parsing.\n'\
6771
' -wifi If a wifi connection is available, check that it reconnects after resume.\n'\
6772
' -wifitrace Trace kernel execution through wifi reconnect.\n'\
6773
' -netfix Use netfix to reset the network in the event it fails to resume.\n'\
6774
' -debugtiming Add timestamp to each printed line\n'\
6775
' [testprep]\n'\
6776
' -sync Sync the filesystems before starting the test\n'\
6777
' -rs on/off Enable/disable runtime suspend for all devices, restore all after test\n'\
6778
' -display m Change the display mode to m for the test (on/off/standby/suspend)\n'\
6779
' [advanced]\n'\
6780
' -gzip Gzip the trace and dmesg logs to save space\n'\
6781
' -cmd {s} Run the timeline over a custom command, e.g. "sync -d"\n'\
6782
' -proc Add usermode process info into the timeline (default: disabled)\n'\
6783
' -dev Add kernel function calls and threads to the timeline (default: disabled)\n'\
6784
' -x2 Run two suspend/resumes back to back (default: disabled)\n'\
6785
' -x2delay t Include t ms delay between multiple test runs (default: 0 ms)\n'\
6786
' -predelay t Include t ms delay before 1st suspend (default: 0 ms)\n'\
6787
' -postdelay t Include t ms delay after last resume (default: 0 ms)\n'\
6788
' -mindev ms Discard all device blocks shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6789
' -multi n d Execute <n> consecutive tests at <d> seconds intervals. If <n> is followed\n'\
6790
' by a "d", "h", or "m" execute for <n> days, hours, or mins instead.\n'\
6791
' The outputs will be created in a new subdirectory with a summary page.\n'\
6792
' -maxfail n Abort a -multi run after n consecutive fails (default is 0 = never abort)\n'\
6793
' [debug]\n'\
6794
' -f Use ftrace to create device callgraphs (default: disabled)\n'\
6795
' -ftop Use ftrace on the top level call: "%s" (default: disabled)\n'\
6796
' -maxdepth N limit the callgraph data to N call levels (default: 0=all)\n'\
6797
' -expandcg pre-expand the callgraph data in the html output (default: disabled)\n'\
6798
' -fadd file Add functions to be graphed in the timeline from a list in a text file\n'\
6799
' -filter "d1,d2,..." Filter out all but this comma-delimited list of device names\n'\
6800
' -mincg ms Discard all callgraphs shorter than ms milliseconds (e.g. 0.001 for us)\n'\
6801
' -cgphase P Only show callgraph data for phase P (e.g. suspend_late)\n'\
6802
' -cgtest N Only show callgraph data for test N (e.g. 0 or 1 in an x2 run)\n'\
6803
' -timeprec N Number of significant digits in timestamps (0:S, [3:ms], 6:us)\n'\
6804
' -cgfilter S Filter the callgraph output in the timeline\n'\
6805
' -cgskip file Callgraph functions to skip, off to disable (default: cgskip.txt)\n'\
6806
' -bufsize N Set trace buffer size to N kilo-bytes (default: all of free memory)\n'\
6807
' -devdump Print out all the raw device data for each phase\n'\
6808
' -cgdump Print out all the raw callgraph data\n'\
6809
'\n'\
6810
'Other commands:\n'\
6811
' -modes List available suspend modes\n'\
6812
' -status Test to see if the system is enabled to run this tool\n'\
6813
' -fpdt Print out the contents of the ACPI Firmware Performance Data Table\n'\
6814
' -wificheck Print out wifi connection info\n'\
6815
' -x<mode> Test xset by toggling the given mode (on/off/standby/suspend)\n'\
6816
' -sysinfo Print out system info extracted from BIOS\n'\
6817
' -devinfo Print out the pm settings of all devices which support runtime suspend\n'\
6818
' -cmdinfo Print out all the platform info collected before and after suspend/resume\n'\
6819
' -flist Print the list of functions currently being captured in ftrace\n'\
6820
' -flistall Print all functions capable of being captured in ftrace\n'\
6821
' -summary dir Create a summary of tests in this dir [-genhtml builds missing html]\n'\
6822
' [redo]\n'\
6823
' -ftrace ftracefile Create HTML output using ftrace input (used with -dmesg)\n'\
6824
' -dmesg dmesgfile Create HTML output using dmesg (used with -ftrace)\n'\
6825
'' % (sysvals.title, sysvals.version, sysvals.suspendmode, sysvals.ftopfunc))
6826
return True
6827
6828
# ----------------- MAIN --------------------
6829
# exec start (skipped if script is loaded as library)
6830
if __name__ == '__main__':
6831
genhtml = False
6832
cmd = ''
6833
simplecmds = ['-sysinfo', '-modes', '-fpdt', '-flist', '-flistall',
6834
'-devinfo', '-status', '-xon', '-xoff', '-xstandby', '-xsuspend',
6835
'-xinit', '-xreset', '-xstat', '-wificheck', '-cmdinfo']
6836
if '-f' in sys.argv:
6837
sysvals.cgskip = sysvals.configFile('cgskip.txt')
6838
# loop through the command line arguments
6839
args = iter(sys.argv[1:])
6840
for arg in args:
6841
if(arg == '-m'):
6842
try:
6843
val = next(args)
6844
except:
6845
doError('No mode supplied', True)
6846
if val == 'command' and not sysvals.testcommand:
6847
doError('No command supplied for mode "command"', True)
6848
sysvals.suspendmode = val
6849
elif(arg in simplecmds):
6850
cmd = arg[1:]
6851
elif(arg == '-h'):
6852
printHelp()
6853
sys.exit(0)
6854
elif(arg == '-v'):
6855
pprint("Version %s" % sysvals.version)
6856
sys.exit(0)
6857
elif(arg == '-debugtiming'):
6858
debugtiming = True
6859
elif(arg == '-x2'):
6860
sysvals.execcount = 2
6861
elif(arg == '-x2delay'):
6862
sysvals.x2delay = getArgInt('-x2delay', args, 0, 60000)
6863
elif(arg == '-predelay'):
6864
sysvals.predelay = getArgInt('-predelay', args, 0, 60000)
6865
elif(arg == '-postdelay'):
6866
sysvals.postdelay = getArgInt('-postdelay', args, 0, 60000)
6867
elif(arg == '-f'):
6868
sysvals.usecallgraph = True
6869
elif(arg == '-ftop'):
6870
sysvals.usecallgraph = True
6871
sysvals.ftop = True
6872
sysvals.usekprobes = False
6873
elif(arg == '-skiphtml'):
6874
sysvals.skiphtml = True
6875
elif(arg == '-cgdump'):
6876
sysvals.cgdump = True
6877
elif(arg == '-devdump'):
6878
sysvals.devdump = True
6879
elif(arg == '-genhtml'):
6880
genhtml = True
6881
elif(arg == '-addlogs'):
6882
sysvals.dmesglog = sysvals.ftracelog = True
6883
elif(arg == '-nologs'):
6884
sysvals.dmesglog = sysvals.ftracelog = False
6885
elif(arg == '-addlogdmesg'):
6886
sysvals.dmesglog = True
6887
elif(arg == '-addlogftrace'):
6888
sysvals.ftracelog = True
6889
elif(arg == '-noturbostat'):
6890
sysvals.tstat = False
6891
elif(arg == '-verbose'):
6892
sysvals.verbose = True
6893
elif(arg == '-proc'):
6894
sysvals.useprocmon = True
6895
elif(arg == '-dev'):
6896
sysvals.usedevsrc = True
6897
elif(arg == '-sync'):
6898
sysvals.sync = True
6899
elif(arg == '-wifi'):
6900
sysvals.wifi = True
6901
elif(arg == '-wifitrace'):
6902
sysvals.wifitrace = True
6903
elif(arg == '-netfix'):
6904
sysvals.netfix = True
6905
elif(arg == '-gzip'):
6906
sysvals.gzip = True
6907
elif(arg == '-info'):
6908
try:
6909
val = next(args)
6910
except:
6911
doError('-info requires one string argument', True)
6912
elif(arg == '-desc'):
6913
try:
6914
val = next(args)
6915
except:
6916
doError('-desc requires one string argument', True)
6917
elif(arg == '-rs'):
6918
try:
6919
val = next(args)
6920
except:
6921
doError('-rs requires "enable" or "disable"', True)
6922
if val.lower() in switchvalues:
6923
if val.lower() in switchoff:
6924
sysvals.rs = -1
6925
else:
6926
sysvals.rs = 1
6927
else:
6928
doError('invalid option: %s, use "enable/disable" or "on/off"' % val, True)
6929
elif(arg == '-display'):
6930
try:
6931
val = next(args)
6932
except:
6933
doError('-display requires an mode value', True)
6934
disopt = ['on', 'off', 'standby', 'suspend']
6935
if val.lower() not in disopt:
6936
doError('valid display mode values are %s' % disopt, True)
6937
sysvals.display = val.lower()
6938
elif(arg == '-maxdepth'):
6939
sysvals.max_graph_depth = getArgInt('-maxdepth', args, 0, 1000)
6940
elif(arg == '-rtcwake'):
6941
try:
6942
val = next(args)
6943
except:
6944
doError('No rtcwake time supplied', True)
6945
if val.lower() in switchoff:
6946
sysvals.rtcwake = False
6947
else:
6948
sysvals.rtcwake = True
6949
sysvals.rtcwaketime = getArgInt('-rtcwake', val, 0, 3600, False)
6950
elif(arg == '-timeprec'):
6951
sysvals.setPrecision(getArgInt('-timeprec', args, 0, 6))
6952
elif(arg == '-mindev'):
6953
sysvals.mindevlen = getArgFloat('-mindev', args, 0.0, 10000.0)
6954
elif(arg == '-mincg'):
6955
sysvals.mincglen = getArgFloat('-mincg', args, 0.0, 10000.0)
6956
elif(arg == '-bufsize'):
6957
sysvals.bufsize = getArgInt('-bufsize', args, 1, 1024*1024*8)
6958
elif(arg == '-cgtest'):
6959
sysvals.cgtest = getArgInt('-cgtest', args, 0, 1)
6960
elif(arg == '-cgphase'):
6961
try:
6962
val = next(args)
6963
except:
6964
doError('No phase name supplied', True)
6965
d = Data(0)
6966
if val not in d.phasedef:
6967
doError('invalid phase --> (%s: %s), valid phases are %s'\
6968
% (arg, val, d.phasedef.keys()), True)
6969
sysvals.cgphase = val
6970
elif(arg == '-cgfilter'):
6971
try:
6972
val = next(args)
6973
except:
6974
doError('No callgraph functions supplied', True)
6975
sysvals.setCallgraphFilter(val)
6976
elif(arg == '-skipkprobe'):
6977
try:
6978
val = next(args)
6979
except:
6980
doError('No kprobe functions supplied', True)
6981
sysvals.skipKprobes(val)
6982
elif(arg == '-cgskip'):
6983
try:
6984
val = next(args)
6985
except:
6986
doError('No file supplied', True)
6987
if val.lower() in switchoff:
6988
sysvals.cgskip = ''
6989
else:
6990
sysvals.cgskip = sysvals.configFile(val)
6991
if(not sysvals.cgskip):
6992
doError('%s does not exist' % sysvals.cgskip)
6993
elif(arg == '-callloop-maxgap'):
6994
sysvals.callloopmaxgap = getArgFloat('-callloop-maxgap', args, 0.0, 1.0)
6995
elif(arg == '-callloop-maxlen'):
6996
sysvals.callloopmaxlen = getArgFloat('-callloop-maxlen', args, 0.0, 1.0)
6997
elif(arg == '-cmd'):
6998
try:
6999
val = next(args)
7000
except:
7001
doError('No command string supplied', True)
7002
sysvals.testcommand = val
7003
sysvals.suspendmode = 'command'
7004
elif(arg == '-expandcg'):
7005
sysvals.cgexp = True
7006
elif(arg == '-srgap'):
7007
sysvals.srgap = 5
7008
elif(arg == '-maxfail'):
7009
sysvals.maxfail = getArgInt('-maxfail', args, 0, 1000000)
7010
elif(arg == '-multi'):
7011
try:
7012
c, d = next(args), next(args)
7013
except:
7014
doError('-multi requires two values', True)
7015
sysvals.multiinit(c, d)
7016
elif(arg == '-o'):
7017
try:
7018
val = next(args)
7019
except:
7020
doError('No subdirectory name supplied', True)
7021
sysvals.outdir = sysvals.setOutputFolder(val)
7022
elif(arg == '-config'):
7023
try:
7024
val = next(args)
7025
except:
7026
doError('No text file supplied', True)
7027
file = sysvals.configFile(val)
7028
if(not file):
7029
doError('%s does not exist' % val)
7030
configFromFile(file)
7031
elif(arg == '-fadd'):
7032
try:
7033
val = next(args)
7034
except:
7035
doError('No text file supplied', True)
7036
file = sysvals.configFile(val)
7037
if(not file):
7038
doError('%s does not exist' % val)
7039
sysvals.addFtraceFilterFunctions(file)
7040
elif(arg == '-dmesg'):
7041
try:
7042
val = next(args)
7043
except:
7044
doError('No dmesg file supplied', True)
7045
sysvals.notestrun = True
7046
sysvals.dmesgfile = val
7047
if(os.path.exists(sysvals.dmesgfile) == False):
7048
doError('%s does not exist' % sysvals.dmesgfile)
7049
elif(arg == '-ftrace'):
7050
try:
7051
val = next(args)
7052
except:
7053
doError('No ftrace file supplied', True)
7054
sysvals.notestrun = True
7055
sysvals.ftracefile = val
7056
if(os.path.exists(sysvals.ftracefile) == False):
7057
doError('%s does not exist' % sysvals.ftracefile)
7058
elif(arg == '-summary'):
7059
try:
7060
val = next(args)
7061
except:
7062
doError('No directory supplied', True)
7063
cmd = 'summary'
7064
sysvals.outdir = val
7065
sysvals.notestrun = True
7066
if(os.path.isdir(val) == False):
7067
doError('%s is not accesible' % val)
7068
elif(arg == '-filter'):
7069
try:
7070
val = next(args)
7071
except:
7072
doError('No devnames supplied', True)
7073
sysvals.setDeviceFilter(val)
7074
elif(arg == '-result'):
7075
try:
7076
val = next(args)
7077
except:
7078
doError('No result file supplied', True)
7079
sysvals.result = val
7080
else:
7081
doError('Invalid argument: '+arg, True)
7082
7083
# compatibility errors
7084
if(sysvals.usecallgraph and sysvals.usedevsrc):
7085
doError('-dev is not compatible with -f')
7086
if(sysvals.usecallgraph and sysvals.useprocmon):
7087
doError('-proc is not compatible with -f')
7088
7089
sysvals.signalHandlerInit()
7090
if sysvals.usecallgraph and sysvals.cgskip:
7091
sysvals.vprint('Using cgskip file: %s' % sysvals.cgskip)
7092
sysvals.setCallgraphBlacklist(sysvals.cgskip)
7093
7094
# callgraph size cannot exceed device size
7095
if sysvals.mincglen < sysvals.mindevlen:
7096
sysvals.mincglen = sysvals.mindevlen
7097
7098
# remove existing buffers before calculating memory
7099
if(sysvals.usecallgraph or sysvals.usedevsrc):
7100
sysvals.fsetVal('16', 'buffer_size_kb')
7101
sysvals.cpuInfo()
7102
7103
# just run a utility command and exit
7104
if(cmd != ''):
7105
ret = 0
7106
if(cmd == 'status'):
7107
if not statusCheck(True):
7108
ret = 1
7109
elif(cmd == 'fpdt'):
7110
if not getFPDT(True):
7111
ret = 1
7112
elif(cmd == 'sysinfo'):
7113
sysvals.printSystemInfo(True)
7114
elif(cmd == 'devinfo'):
7115
deviceInfo()
7116
elif(cmd == 'modes'):
7117
pprint(getModes())
7118
elif(cmd == 'flist'):
7119
sysvals.getFtraceFilterFunctions(True)
7120
elif(cmd == 'flistall'):
7121
sysvals.getFtraceFilterFunctions(False)
7122
elif(cmd == 'summary'):
7123
runSummary(sysvals.outdir, True, genhtml)
7124
elif(cmd in ['xon', 'xoff', 'xstandby', 'xsuspend', 'xinit', 'xreset']):
7125
sysvals.verbose = True
7126
ret = sysvals.displayControl(cmd[1:])
7127
elif(cmd == 'xstat'):
7128
pprint('Display Status: %s' % sysvals.displayControl('stat').upper())
7129
elif(cmd == 'wificheck'):
7130
dev = sysvals.checkWifi()
7131
if dev:
7132
print('%s is connected' % sysvals.wifiDetails(dev))
7133
else:
7134
print('No wifi connection found')
7135
elif(cmd == 'cmdinfo'):
7136
for out in sysvals.cmdinfo(False, True):
7137
print('[%s - %s]\n%s\n' % out)
7138
sys.exit(ret)
7139
7140
# if instructed, re-analyze existing data files
7141
if(sysvals.notestrun):
7142
stamp = rerunTest(sysvals.outdir)
7143
sysvals.outputResult(stamp)
7144
sys.exit(0)
7145
7146
# verify that we can run a test
7147
error = statusCheck()
7148
if(error):
7149
doError(error)
7150
7151
# extract mem/disk extra modes and convert
7152
mode = sysvals.suspendmode
7153
if mode.startswith('mem'):
7154
memmode = mode.split('-', 1)[-1] if '-' in mode else 'deep'
7155
if memmode == 'shallow':
7156
mode = 'standby'
7157
elif memmode == 's2idle':
7158
mode = 'freeze'
7159
else:
7160
mode = 'mem'
7161
sysvals.memmode = memmode
7162
sysvals.suspendmode = mode
7163
if mode.startswith('disk-'):
7164
sysvals.diskmode = mode.split('-', 1)[-1]
7165
sysvals.suspendmode = 'disk'
7166
sysvals.systemInfo(dmidecode(sysvals.mempath))
7167
7168
failcnt, ret = 0, 0
7169
if sysvals.multitest['run']:
7170
# run multiple tests in a separate subdirectory
7171
if not sysvals.outdir:
7172
if 'time' in sysvals.multitest:
7173
s = '-%dm' % sysvals.multitest['time']
7174
else:
7175
s = '-x%d' % sysvals.multitest['count']
7176
sysvals.outdir = datetime.now().strftime('suspend-%y%m%d-%H%M%S'+s)
7177
if not os.path.isdir(sysvals.outdir):
7178
os.makedirs(sysvals.outdir)
7179
sysvals.sudoUserchown(sysvals.outdir)
7180
finish = datetime.now()
7181
if 'time' in sysvals.multitest:
7182
finish += timedelta(minutes=sysvals.multitest['time'])
7183
for i in range(sysvals.multitest['count']):
7184
sysvals.multistat(True, i, finish)
7185
if i != 0 and sysvals.multitest['delay'] > 0:
7186
pprint('Waiting %d seconds...' % (sysvals.multitest['delay']))
7187
time.sleep(sysvals.multitest['delay'])
7188
fmt = 'suspend-%y%m%d-%H%M%S'
7189
sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
7190
ret = runTest(i+1, not sysvals.verbose)
7191
failcnt = 0 if not ret else failcnt + 1
7192
if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
7193
pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
7194
break
7195
sysvals.resetlog()
7196
sysvals.multistat(False, i, finish)
7197
if 'time' in sysvals.multitest and datetime.now() >= finish:
7198
break
7199
if not sysvals.skiphtml:
7200
runSummary(sysvals.outdir, False, False)
7201
sysvals.sudoUserchown(sysvals.outdir)
7202
else:
7203
if sysvals.outdir:
7204
sysvals.testdir = sysvals.outdir
7205
# run the test in the current directory
7206
ret = runTest()
7207
7208
# reset to default values after testing
7209
if sysvals.display:
7210
sysvals.displayControl('reset')
7211
if sysvals.rs != 0:
7212
sysvals.setRuntimeSuspend(False)
7213
sys.exit(ret)
7214
7215