diff mbox series

pm-graph v5.9

Message ID 20220707225451.32738-1-todd.e.brandt@intel.com (mailing list archive)
State Mainlined, archived
Headers show
Series pm-graph v5.9 | expand

Commit Message

Todd Brandt July 7, 2022, 10:54 p.m. UTC
bootgraph:
- fix parsing of /proc/version to be much more flexible
- check kernel version to disallow ftrace on anything older than 4.10
sleepgraph:
- include fix to bugzilla 212761 in case it regresses
- fix for -proc bug: https://github.com/intel/pm-graph/pull/20
- add -debugtiming arg to get timestamps on prints
- allow use of the netfix tool hosted in the github repo
- read s0ix data from pmc_core for better debug
- include more system data in the output log
- Do a better job testing input files useability
- flag more error data from dmesg in the timeline
- pre-parse the trace log to fix any ordering issues
- add new parser to process dmesg only timelines
- remove superflous sleep(5) in multitest mode
config/custom-timeline-functions.cfg:
- change some names to keep up to date
README:
- new version, small wording changes

Signed-off-by: Todd Brandt <todd.e.brandt@intel.com>
---
 tools/power/pm-graph/README                   |   6 +-
 tools/power/pm-graph/bootgraph.py             |  20 +-
 .../config/custom-timeline-functions.cfg      |   2 +-
 tools/power/pm-graph/sleepgraph.py            | 518 ++++++++++++------
 4 files changed, 360 insertions(+), 186 deletions(-)

Comments

Rafael J. Wysocki July 15, 2022, 5:33 p.m. UTC | #1
On Fri, Jul 8, 2022 at 12:54 AM Todd Brandt <todd.e.brandt@intel.com> wrote:
>
> bootgraph:
> - fix parsing of /proc/version to be much more flexible
> - check kernel version to disallow ftrace on anything older than 4.10
> sleepgraph:
> - include fix to bugzilla 212761 in case it regresses
> - fix for -proc bug: https://github.com/intel/pm-graph/pull/20
> - add -debugtiming arg to get timestamps on prints
> - allow use of the netfix tool hosted in the github repo
> - read s0ix data from pmc_core for better debug
> - include more system data in the output log
> - Do a better job testing input files useability
> - flag more error data from dmesg in the timeline
> - pre-parse the trace log to fix any ordering issues
> - add new parser to process dmesg only timelines
> - remove superflous sleep(5) in multitest mode
> config/custom-timeline-functions.cfg:
> - change some names to keep up to date
> README:
> - new version, small wording changes
>
> Signed-off-by: Todd Brandt <todd.e.brandt@intel.com>

Applied as 5.20 material, thanks!

> ---
>  tools/power/pm-graph/README                   |   6 +-
>  tools/power/pm-graph/bootgraph.py             |  20 +-
>  .../config/custom-timeline-functions.cfg      |   2 +-
>  tools/power/pm-graph/sleepgraph.py            | 518 ++++++++++++------
>  4 files changed, 360 insertions(+), 186 deletions(-)
>
> diff --git a/tools/power/pm-graph/README b/tools/power/pm-graph/README
> index da468bd510ca..e6020c0d59ec 100644
> --- a/tools/power/pm-graph/README
> +++ b/tools/power/pm-graph/README
> @@ -6,7 +6,7 @@
>     |_|                    |___/          |_|
>
>     pm-graph: suspend/resume/boot timing analysis tools
> -    Version: 5.8
> +    Version: 5.9
>       Author: Todd Brandt <todd.e.brandt@intel.com>
>    Home Page: https://01.org/pm-graph
>
> @@ -97,8 +97,8 @@
>          (kernel/pre-3.15/enable_trace_events_suspend_resume.patch)
>          (kernel/pre-3.15/enable_trace_events_device_pm_callback.patch)
>
> -       If you're using a kernel older than 3.15.0, the following
> -        additional kernel parameters are required:
> +       If you're using bootgraph, or sleepgraph with a kernel older than 3.15.0,
> +               the following additional kernel parameters are required:
>          (e.g. in file /etc/default/grub)
>          GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=32M ..."
>
> diff --git a/tools/power/pm-graph/bootgraph.py b/tools/power/pm-graph/bootgraph.py
> index 2823cd3122f7..f96f50e0c336 100755
> --- a/tools/power/pm-graph/bootgraph.py
> +++ b/tools/power/pm-graph/bootgraph.py
> @@ -69,22 +69,24 @@ class SystemValues(aslib.SystemValues):
>         bootloader = 'grub'
>         blexec = []
>         def __init__(self):
> -               self.hostname = platform.node()
> +               self.kernel, self.hostname = 'unknown', platform.node()
>                 self.testtime = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
>                 if os.path.exists('/proc/version'):
>                         fp = open('/proc/version', 'r')
> -                       val = fp.read().strip()
> +                       self.kernel = self.kernelVersion(fp.read().strip())
>                         fp.close()
> -                       self.kernel = self.kernelVersion(val)
> -               else:
> -                       self.kernel = 'unknown'
>                 self.testdir = datetime.now().strftime('boot-%y%m%d-%H%M%S')
>         def kernelVersion(self, msg):
> -               return msg.split()[2]
> +               m = re.match('^[Ll]inux *[Vv]ersion *(?P<v>\S*) .*', msg)
> +               if m:
> +                       return m.group('v')
> +               return 'unknown'
>         def checkFtraceKernelVersion(self):
> -               val = tuple(map(int, self.kernel.split('-')[0].split('.')))
> -               if val >= (4, 10, 0):
> -                       return True
> +               m = re.match('^(?P<x>[0-9]*)\.(?P<y>[0-9]*)\.(?P<z>[0-9]*).*', self.kernel)
> +               if m:
> +                       val = tuple(map(int, m.groups()))
> +                       if val >= (4, 10, 0):
> +                               return True
>                 return False
>         def kernelParams(self):
>                 cmdline = 'initcall_debug log_buf_len=32M'
> diff --git a/tools/power/pm-graph/config/custom-timeline-functions.cfg b/tools/power/pm-graph/config/custom-timeline-functions.cfg
> index 962e5768681c..4f80ad7d7275 100644
> --- a/tools/power/pm-graph/config/custom-timeline-functions.cfg
> +++ b/tools/power/pm-graph/config/custom-timeline-functions.cfg
> @@ -125,7 +125,7 @@ acpi_suspend_begin:
>  suspend_console:
>  acpi_pm_prepare:
>  syscore_suspend:
> -arch_thaw_secondary_cpus_end:
> +arch_enable_nonboot_cpus_end:
>  syscore_resume:
>  acpi_pm_finish:
>  resume_console:
> diff --git a/tools/power/pm-graph/sleepgraph.py b/tools/power/pm-graph/sleepgraph.py
> index ffd50953a024..33981adcdd68 100755
> --- a/tools/power/pm-graph/sleepgraph.py
> +++ b/tools/power/pm-graph/sleepgraph.py
> @@ -66,8 +66,13 @@ from threading import Thread
>  from subprocess import call, Popen, PIPE
>  import base64
>
> +debugtiming = False
> +mystarttime = time.time()
>  def pprint(msg):
> -       print(msg)
> +       if debugtiming:
> +               print('[%09.3f] %s' % (time.time()-mystarttime, msg))
> +       else:
> +               print(msg)
>         sys.stdout.flush()
>
>  def ascii(text):
> @@ -81,13 +86,14 @@ def ascii(text):
>  #       store system values and test parameters
>  class SystemValues:
>         title = 'SleepGraph'
> -       version = '5.8'
> +       version = '5.9'
>         ansi = False
>         rs = 0
>         display = ''
>         gzip = False
>         sync = False
>         wifi = False
> +       netfix = False
>         verbose = False
>         testlog = True
>         dmesglog = True
> @@ -108,6 +114,7 @@ class SystemValues:
>         cpucount = 0
>         memtotal = 204800
>         memfree = 204800
> +       osversion = ''
>         srgap = 0
>         cgexp = False
>         testdir = ''
> @@ -116,6 +123,7 @@ class SystemValues:
>         fpdtpath = '/sys/firmware/acpi/tables/FPDT'
>         epath = '/sys/kernel/debug/tracing/events/power/'
>         pmdpath = '/sys/power/pm_debug_messages'
> +       s0ixpath = '/sys/module/intel_pmc_core/parameters/warn_on_s0ix_failures'
>         acpipath='/sys/module/acpi/parameters/debug_level'
>         traceevents = [
>                 'suspend_resume',
> @@ -156,6 +164,7 @@ class SystemValues:
>         ftop = False
>         usetraceevents = False
>         usetracemarkers = True
> +       useftrace = True
>         usekprobes = True
>         usedevsrc = False
>         useprocmon = False
> @@ -279,10 +288,16 @@ class SystemValues:
>                 'intel_fbdev_set_suspend': {},
>         }
>         infocmds = [
> +               [0, 'sysinfo', 'uname', '-a'],
> +               [0, 'cpuinfo', 'head', '-7', '/proc/cpuinfo'],
>                 [0, 'kparams', 'cat', '/proc/cmdline'],
>                 [0, 'mcelog', 'mcelog'],
>                 [0, 'pcidevices', 'lspci', '-tv'],
> -               [0, 'usbdevices', 'lsusb', '-t'],
> +               [0, 'usbdevices', 'lsusb', '-tv'],
> +               [0, 'acpidevices', 'sh', '-c', 'ls -l /sys/bus/acpi/devices/*/physical_node'],
> +               [0, 's0ix_require', 'cat', '/sys/kernel/debug/pmc_core/substate_requirements'],
> +               [0, 's0ix_debug', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_debug_status'],
> +               [1, 's0ix_residency', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_residency_usec'],
>                 [1, 'interrupts', 'cat', '/proc/interrupts'],
>                 [1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'],
>                 [2, 'gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/*'],
> @@ -358,8 +373,19 @@ class SystemValues:
>                         self.outputResult({'error':msg})
>                         sys.exit(1)
>                 return False
> -       def usable(self, file):
> -               return (os.path.exists(file) and os.path.getsize(file) > 0)
> +       def usable(self, file, ishtml=False):
> +               if not os.path.exists(file) or os.path.getsize(file) < 1:
> +                       return False
> +               if ishtml:
> +                       try:
> +                               fp = open(file, 'r')
> +                               res = fp.read(1000)
> +                               fp.close()
> +                       except:
> +                               return False
> +                       if '<html>' not in res:
> +                               return False
> +               return True
>         def getExec(self, cmd):
>                 try:
>                         fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
> @@ -413,12 +439,16 @@ class SystemValues:
>                 r = info['bios-release-date'] if 'bios-release-date' in info else ''
>                 self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
>                         (m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
> +               if self.osversion:
> +                       self.sysstamp += ' | os:%s' % self.osversion
>         def printSystemInfo(self, fatal=False):
>                 self.rootCheck(True)
>                 out = dmidecode(self.mempath, fatal)
>                 if len(out) < 1:
>                         return
>                 fmt = '%-24s: %s'
> +               if self.osversion:
> +                       print(fmt % ('os-version', self.osversion))
>                 for name in sorted(out):
>                         print(fmt % (name, out[name]))
>                 print(fmt % ('cpucount', ('%d' % self.cpucount)))
> @@ -426,20 +456,25 @@ class SystemValues:
>                 print(fmt % ('memfree', ('%d kB' % self.memfree)))
>         def cpuInfo(self):
>                 self.cpucount = 0
> -               fp = open('/proc/cpuinfo', 'r')
> -               for line in fp:
> -                       if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
> -                               self.cpucount += 1
> -               fp.close()
> -               fp = open('/proc/meminfo', 'r')
> -               for line in fp:
> -                       m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
> -                       if m:
> -                               self.memtotal = int(m.group('sz'))
> -                       m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
> -                       if m:
> -                               self.memfree = int(m.group('sz'))
> -               fp.close()
> +               if os.path.exists('/proc/cpuinfo'):
> +                       with open('/proc/cpuinfo', 'r') as fp:
> +                               for line in fp:
> +                                       if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
> +                                               self.cpucount += 1
> +               if os.path.exists('/proc/meminfo'):
> +                       with open('/proc/meminfo', 'r') as fp:
> +                               for line in fp:
> +                                       m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
> +                                       if m:
> +                                               self.memtotal = int(m.group('sz'))
> +                                       m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
> +                                       if m:
> +                                               self.memfree = int(m.group('sz'))
> +               if os.path.exists('/etc/os-release'):
> +                       with open('/etc/os-release', 'r') as fp:
> +                               for line in fp:
> +                                       if line.startswith('PRETTY_NAME='):
> +                                               self.osversion = line[12:].strip().replace('"', '')
>         def initTestOutput(self, name):
>                 self.prefix = self.hostname
>                 v = open('/proc/version', 'r').read().strip()
> @@ -698,6 +733,8 @@ class SystemValues:
>                         return False
>                 return True
>         def fsetVal(self, val, path):
> +               if not self.useftrace:
> +                       return False
>                 return self.setVal(val, self.tpath+path)
>         def getVal(self, file):
>                 res = ''
> @@ -711,9 +748,11 @@ class SystemValues:
>                         pass
>                 return res
>         def fgetVal(self, path):
> +               if not self.useftrace:
> +                       return ''
>                 return self.getVal(self.tpath+path)
>         def cleanupFtrace(self):
> -               if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
> +               if self.useftrace:
>                         self.fsetVal('0', 'events/kprobes/enable')
>                         self.fsetVal('', 'kprobe_events')
>                         self.fsetVal('1024', 'buffer_size_kb')
> @@ -734,13 +773,14 @@ class SystemValues:
>                                 return True
>                 return False
>         def initFtrace(self, quiet=False):
> +               if not self.useftrace:
> +                       return
>                 if not quiet:
>                         sysvals.printSystemInfo(False)
>                         pprint('INITIALIZING FTRACE...')
>                 # turn trace off
>                 self.fsetVal('0', 'tracing_on')
>                 self.cleanupFtrace()
> -               self.testVal(self.pmdpath, 'basic', '1')
>                 # set the trace clock to global
>                 self.fsetVal('global', 'trace_clock')
>                 self.fsetVal('nop', 'current_tracer')
> @@ -766,6 +806,10 @@ class SystemValues:
>                         # set trace type
>                         self.fsetVal('function_graph', 'current_tracer')
>                         self.fsetVal('', 'set_ftrace_filter')
> +                       # temporary hack to fix https://bugzilla.kernel.org/show_bug.cgi?id=212761
> +                       fp = open(self.tpath+'set_ftrace_notrace', 'w')
> +                       fp.write('native_queued_spin_lock_slowpath\ndev_driver_string')
> +                       fp.close()
>                         # set trace format options
>                         self.fsetVal('print-parent', 'trace_options')
>                         self.fsetVal('funcgraph-abstime', 'trace_options')
> @@ -846,6 +890,8 @@ class SystemValues:
>                                 fp.write('# turbostat %s\n' % test['turbo'])
>                         if 'wifi' in test:
>                                 fp.write('# wifi %s\n' % test['wifi'])
> +                       if 'netfix' in test:
> +                               fp.write('# netfix %s\n' % test['netfix'])
>                         if test['error'] or len(testdata) > 1:
>                                 fp.write('# enter_sleep_error %s\n' % test['error'])
>                 return fp
> @@ -865,6 +911,8 @@ class SystemValues:
>                         fp.write('error%s: %s\n' % (n, testdata['error']))
>                 else:
>                         fp.write('result%s: pass\n' % n)
> +               if 'mode' in testdata:
> +                       fp.write('mode%s: %s\n' % (n, testdata['mode']))
>                 for v in ['suspend', 'resume', 'boot', 'lastinit']:
>                         if v in testdata:
>                                 fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
> @@ -901,6 +949,8 @@ class SystemValues:
>                         fp.write(text)
>                         fp.close()
>         def dlog(self, text):
> +               if not self.dmesgfile:
> +                       return
>                 self.putlog(self.dmesgfile, '# %s\n' % text)
>         def flog(self, text):
>                 self.putlog(self.ftracefile, text)
> @@ -954,34 +1004,31 @@ class SystemValues:
>                         dirname = props[dev].syspath
>                         if not dirname or not os.path.exists(dirname):
>                                 continue
> -                       with open(dirname+'/power/async') as fp:
> -                               text = fp.read()
> -                               props[dev].isasync = False
> -                               if 'enabled' in text:
> +                       props[dev].isasync = False
> +                       if os.path.exists(dirname+'/power/async'):
> +                               fp = open(dirname+'/power/async')
> +                               if 'enabled' in fp.read():
>                                         props[dev].isasync = True
> +                               fp.close()
>                         fields = os.listdir(dirname)
> -                       if 'product' in fields:
> -                               with open(dirname+'/product', 'rb') as fp:
> -                                       props[dev].altname = ascii(fp.read())
> -                       elif 'name' in fields:
> -                               with open(dirname+'/name', 'rb') as fp:
> -                                       props[dev].altname = ascii(fp.read())
> -                       elif 'model' in fields:
> -                               with open(dirname+'/model', 'rb') as fp:
> -                                       props[dev].altname = ascii(fp.read())
> -                       elif 'description' in fields:
> -                               with open(dirname+'/description', 'rb') as fp:
> -                                       props[dev].altname = ascii(fp.read())
> -                       elif 'id' in fields:
> -                               with open(dirname+'/id', 'rb') as fp:
> -                                       props[dev].altname = ascii(fp.read())
> -                       elif 'idVendor' in fields and 'idProduct' in fields:
> -                               idv, idp = '', ''
> -                               with open(dirname+'/idVendor', 'rb') as fp:
> -                                       idv = ascii(fp.read()).strip()
> -                               with open(dirname+'/idProduct', 'rb') as fp:
> -                                       idp = ascii(fp.read()).strip()
> -                               props[dev].altname = '%s:%s' % (idv, idp)
> +                       for file in ['product', 'name', 'model', 'description', 'id', 'idVendor']:
> +                               if file not in fields:
> +                                       continue
> +                               try:
> +                                       with open(os.path.join(dirname, file), 'rb') as fp:
> +                                               props[dev].altname = ascii(fp.read())
> +                               except:
> +                                       continue
> +                               if file == 'idVendor':
> +                                       idv, idp = props[dev].altname.strip(), ''
> +                                       try:
> +                                               with open(os.path.join(dirname, 'idProduct'), 'rb') as fp:
> +                                                       idp = ascii(fp.read()).strip()
> +                                       except:
> +                                               props[dev].altname = ''
> +                                               break
> +                                       props[dev].altname = '%s:%s' % (idv, idp)
> +                               break
>                         if props[dev].altname:
>                                 out = props[dev].altname.strip().replace('\n', ' ')\
>                                         .replace(',', ' ').replace(';', ' ')
> @@ -1047,7 +1094,7 @@ class SystemValues:
>                                 self.cmd1[name] = self.dictify(info, delta)
>                         elif not debug and delta and name in self.cmd1:
>                                 before, after = self.cmd1[name], self.dictify(info, delta)
> -                               dinfo = ('\t%s\n' % before['@']) if '@' in before else ''
> +                               dinfo = ('\t%s\n' % before['@']) if '@' in before and len(before) > 1 else ''
>                                 prefix = self.commonPrefix(list(before.keys()))
>                                 for key in sorted(before):
>                                         if key in after and before[key] != after[key]:
> @@ -1128,6 +1175,22 @@ class SystemValues:
>                         val = valline[idx]
>                         out.append('%s=%s' % (key, val))
>                 return '|'.join(out)
> +       def netfixon(self, net='both'):
> +               cmd = self.getExec('netfix')
> +               if not cmd:
> +                       return ''
> +               fp = Popen([cmd, '-s', net, 'on'], stdout=PIPE, stderr=PIPE).stdout
> +               out = ascii(fp.read()).strip()
> +               fp.close()
> +               return out
> +       def wifiRepair(self):
> +               out = self.netfixon('wifi')
> +               if not out or 'error' in out.lower():
> +                       return ''
> +               m = re.match('WIFI \S* ONLINE (?P<action>\S*)', out)
> +               if not m:
> +                       return 'dead'
> +               return m.group('action')
>         def wifiDetails(self, dev):
>                 try:
>                         info = open('/sys/class/net/%s/device/uevent' % dev, 'r').read().strip()
> @@ -1144,12 +1207,12 @@ class SystemValues:
>                 except:
>                         return ''
>                 for line in reversed(w.split('\n')):
> -                       m = re.match(' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', w.split('\n')[-1])
> +                       m = re.match(' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', line)
>                         if not m or (dev and dev != m.group('dev')):
>                                 continue
>                         return m.group('dev')
>                 return ''
> -       def pollWifi(self, dev, timeout=60):
> +       def pollWifi(self, dev, timeout=10):
>                 start = time.time()
>                 while (time.time() - start) < timeout:
>                         w = self.checkWifi(dev)
> @@ -1157,6 +1220,11 @@ class SystemValues:
>                                 return '%s reconnected %.2f' % \
>                                         (self.wifiDetails(dev), max(0, time.time() - start))
>                         time.sleep(0.01)
> +               if self.netfix:
> +                       res = self.wifiRepair()
> +                       if res:
> +                               timeout = max(0, time.time() - start)
> +                               return '%s %s %d' % (self.wifiDetails(dev), res, timeout)
>                 return '%s timeout %d' % (self.wifiDetails(dev), timeout)
>         def errorSummary(self, errinfo, msg):
>                 found = False
> @@ -1283,10 +1351,10 @@ sysvals = SystemValues()
>  switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
>  switchoff = ['disable', 'off', 'false', '0']
>  suspendmodename = {
> -       'freeze': 'Freeze (S0)',
> -       'standby': 'Standby (S1)',
> -       'mem': 'Suspend (S3)',
> -       'disk': 'Hibernate (S4)'
> +       'standby': 'standby (S1)',
> +       'freeze': 'freeze (S2idle)',
> +       'mem': 'suspend (S3)',
> +       'disk': 'hibernate (S4)'
>  }
>
>  # Class: DevProps
> @@ -1376,6 +1444,7 @@ class Data:
>                 'INVALID' : r'(?i).*\bINVALID\b.*',
>                 'CRASH'   : r'(?i).*\bCRASHED\b.*',
>                 'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*',
> +               'ABORT'   : r'(?i).*\bABORT\b.*',
>                 'IRQ'     : r'.*\bgenirq: .*',
>                 'TASKFAIL': r'.*Freezing of tasks *.*',
>                 'ACPI'    : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
> @@ -1724,9 +1793,9 @@ class Data:
>                                 if 'waking' in self.dmesg[lp]:
>                                         tCnt = self.dmesg[lp]['waking'][0]
>                                         if self.dmesg[lp]['waking'][1] >= 0.001:
> -                                               tTry = '-%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
> +                                               tTry = '%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
>                                         else:
> -                                               tTry = '-%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
> +                                               tTry = '%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
>                                         text = '%.0f (%s ms waking %d times)' % (tL * 1000, tTry, tCnt)
>                                 else:
>                                         text = '%.0f' % (tL * 1000)
> @@ -2107,6 +2176,30 @@ class Data:
>                 # set resume complete to end at end marker
>                 if 'resume_complete' in dm:
>                         dm['resume_complete']['end'] = time
> +       def initcall_debug_call(self, line, quick=False):
> +               m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
> +                       'PM: *calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
> +               if not m:
> +                       m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
> +                               'calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
> +               if not m:
> +                       m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
> +                               '(?P<f>.*)\+ @ (?P<n>.*), parent: (?P<p>.*)', line)
> +               if m:
> +                       return True if quick else m.group('t', 'f', 'n', 'p')
> +               return False if quick else ('', '', '', '')
> +       def initcall_debug_return(self, line, quick=False):
> +               m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: PM: '+\
> +                       '.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
> +               if not m:
> +                       m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
> +                               '.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
> +               if not m:
> +                       m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
> +                               '(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', line)
> +               if m:
> +                       return True if quick else m.group('t', 'f', 'dt')
> +               return False if quick else ('', '', '')
>         def debugPrint(self):
>                 for p in self.sortedPhases():
>                         list = self.dmesg[p]['list']
> @@ -2880,10 +2973,11 @@ class TestProps:
>         cmdlinefmt = '^# command \| (?P<cmd>.*)'
>         kparamsfmt = '^# kparams \| (?P<kp>.*)'
>         devpropfmt = '# Device Properties: .*'
> -       pinfofmt   = '# platform-(?P<val>[a-z,A-Z,0-9]*): (?P<info>.*)'
> +       pinfofmt   = '# platform-(?P<val>[a-z,A-Z,0-9,_]*): (?P<info>.*)'
>         tracertypefmt = '# tracer: (?P<t>.*)'
>         firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
>         procexecfmt = 'ps - (?P<ps>.*)$'
> +       procmultifmt = '@(?P<n>[0-9]*)\|(?P<ps>.*)$'
>         ftrace_line_fmt_fg = \
>                 '^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
>                 ' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
> @@ -2893,6 +2987,9 @@ class TestProps:
>                 '(?P<flags>\S*) *(?P<time>[0-9\.]*): *'+\
>                 '(?P<msg>.*)'
>         machinesuspend = 'machine_suspend\[.*'
> +       multiproclist = dict()
> +       multiproctime = 0.0
> +       multiproccnt = 0
>         def __init__(self):
>                 self.stamp = ''
>                 self.sysinfo = ''
> @@ -3063,6 +3160,7 @@ class TestRun:
>                 self.ttemp = dict()
>
>  class ProcessMonitor:
> +       maxchars = 512
>         def __init__(self):
>                 self.proclist = dict()
>                 self.running = False
> @@ -3088,19 +3186,23 @@ class ProcessMonitor:
>                         if ujiff > 0 or kjiff > 0:
>                                 running[pid] = ujiff + kjiff
>                 process.wait()
> -               out = ''
> +               out = ['']
>                 for pid in running:
>                         jiffies = running[pid]
>                         val = self.proclist[pid]
> -                       if out:
> -                               out += ','
> -                       out += '%s-%s %d' % (val['name'], pid, jiffies)
> -               return 'ps - '+out
> +                       if len(out[-1]) > self.maxchars:
> +                               out.append('')
> +                       elif len(out[-1]) > 0:
> +                               out[-1] += ','
> +                       out[-1] += '%s-%s %d' % (val['name'], pid, jiffies)
> +               if len(out) > 1:
> +                       for line in out:
> +                               sysvals.fsetVal('ps - @%d|%s' % (len(out), line), 'trace_marker')
> +               else:
> +                       sysvals.fsetVal('ps - %s' % out[0], 'trace_marker')
>         def processMonitor(self, tid):
>                 while self.running:
> -                       out = self.procstat()
> -                       if out:
> -                               sysvals.fsetVal(out, 'trace_marker')
> +                       self.procstat()
>         def start(self):
>                 self.thread = Thread(target=self.processMonitor, args=(0,))
>                 self.running = True
> @@ -3144,7 +3246,6 @@ def doesTraceLogHaveTraceEvents():
>
>  # Function: appendIncompleteTraceLog
>  # Description:
> -#       [deprecated for kernel 3.15 or newer]
>  #       Adds callgraph data which lacks trace event data. This is only
>  #       for timelines generated from 3.15 or older
>  # Arguments:
> @@ -3246,6 +3347,61 @@ def appendIncompleteTraceLog(testruns):
>                                                                 dev['ftrace'] = cg
>                                                 break
>
> +# Function: loadTraceLog
> +# Description:
> +#       load the ftrace file into memory and fix up any ordering issues
> +# Output:
> +#       TestProps instance and an array of lines in proper order
> +def loadTraceLog():
> +       tp, data, lines, trace = TestProps(), dict(), [], []
> +       tf = sysvals.openlog(sysvals.ftracefile, 'r')
> +       for line in tf:
> +               # remove any latent carriage returns
> +               line = line.replace('\r\n', '')
> +               if tp.stampInfo(line, sysvals):
> +                       continue
> +               # ignore all other commented lines
> +               if line[0] == '#':
> +                       continue
> +               # ftrace line: parse only valid lines
> +               m = re.match(tp.ftrace_line_fmt, line)
> +               if(not m):
> +                       continue
> +               dur = m.group('dur') if tp.cgformat else 'traceevent'
> +               info = (m.group('time'), m.group('proc'), m.group('pid'),
> +                       m.group('msg'), dur)
> +               # group the data by timestamp
> +               t = float(info[0])
> +               if t in data:
> +                       data[t].append(info)
> +               else:
> +                       data[t] = [info]
> +               # we only care about trace event ordering
> +               if (info[3].startswith('suspend_resume:') or \
> +                       info[3].startswith('tracing_mark_write:')) and t not in trace:
> +                               trace.append(t)
> +       tf.close()
> +       for t in sorted(data):
> +               first, last, blk = [], [], data[t]
> +               if len(blk) > 1 and t in trace:
> +                       # move certain lines to the start or end of a timestamp block
> +                       for i in range(len(blk)):
> +                               if 'SUSPEND START' in blk[i][3]:
> +                                       first.append(i)
> +                               elif re.match('.* timekeeping_freeze.*begin', blk[i][3]):
> +                                       last.append(i)
> +                               elif re.match('.* timekeeping_freeze.*end', blk[i][3]):
> +                                       first.append(i)
> +                               elif 'RESUME COMPLETE' in blk[i][3]:
> +                                       last.append(i)
> +                       if len(first) == 1 and len(last) == 0:
> +                               blk.insert(0, blk.pop(first[0]))
> +                       elif len(last) == 1 and len(first) == 0:
> +                               blk.append(blk.pop(last[0]))
> +               for info in blk:
> +                       lines.append(info)
> +       return (tp, lines)
> +
>  # Function: parseTraceLog
>  # Description:
>  #       Analyze an ftrace log output file generated from this app during
> @@ -3271,32 +3427,12 @@ def parseTraceLog(live=False):
>
>         # extract the callgraph and traceevent data
>         s2idle_enter = hwsus = False
> -       tp = TestProps()
>         testruns, testdata = [], []
>         testrun, data, limbo = 0, 0, True
> -       tf = sysvals.openlog(sysvals.ftracefile, 'r')
>         phase = 'suspend_prepare'
> -       for line in tf:
> -               # remove any latent carriage returns
> -               line = line.replace('\r\n', '')
> -               if tp.stampInfo(line, sysvals):
> -                       continue
> -               # ignore all other commented lines
> -               if line[0] == '#':
> -                       continue
> -               # ftrace line: parse only valid lines
> -               m = re.match(tp.ftrace_line_fmt, line)
> -               if(not m):
> -                       continue
> +       tp, tf = loadTraceLog()
> +       for m_time, m_proc, m_pid, m_msg, m_param3 in tf:
>                 # gather the basic message data from the line
> -               m_time = m.group('time')
> -               m_proc = m.group('proc')
> -               m_pid = m.group('pid')
> -               m_msg = m.group('msg')
> -               if(tp.cgformat):
> -                       m_param3 = m.group('dur')
> -               else:
> -                       m_param3 = 'traceevent'
>                 if(m_time and m_pid and m_msg):
>                         t = FTraceLine(m_time, m_msg, m_param3)
>                         pid = int(m_pid)
> @@ -3322,14 +3458,29 @@ def parseTraceLog(live=False):
>                 if t.type == 'tracing_mark_write':
>                         m = re.match(tp.procexecfmt, t.name)
>                         if(m):
> -                               proclist = dict()
> -                               for ps in m.group('ps').split(','):
> +                               parts, msg = 1, m.group('ps')
> +                               m = re.match(tp.procmultifmt, msg)
> +                               if(m):
> +                                       parts, msg = int(m.group('n')), m.group('ps')
> +                                       if tp.multiproccnt == 0:
> +                                               tp.multiproctime = t.time
> +                                               tp.multiproclist = dict()
> +                                       proclist = tp.multiproclist
> +                                       tp.multiproccnt += 1
> +                               else:
> +                                       proclist = dict()
> +                                       tp.multiproccnt = 0
> +                               for ps in msg.split(','):
>                                         val = ps.split()
> -                                       if not val:
> +                                       if not val or len(val) != 2:
>                                                 continue
>                                         name = val[0].replace('--', '-')
>                                         proclist[name] = int(val[1])
> -                               data.pstl[t.time] = proclist
> +                               if parts == 1:
> +                                       data.pstl[t.time] = proclist
> +                               elif parts == tp.multiproccnt:
> +                                       data.pstl[tp.multiproctime] = proclist
> +                                       tp.multiproccnt = 0
>                                 continue
>                 # find the end of resume
>                 if(t.endMarker()):
> @@ -3545,7 +3696,6 @@ def parseTraceLog(live=False):
>                                 testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
>                         if(res == -1):
>                                 testrun.ftemp[key][-1].addLine(t)
> -       tf.close()
>         if len(testdata) < 1:
>                 sysvals.vprint('WARNING: ftrace start marker is missing')
>         if data and not data.devicegroups:
> @@ -3667,7 +3817,13 @@ def parseTraceLog(live=False):
>                         if p not in data.dmesg:
>                                 if not terr:
>                                         ph = p if 'machine' in p else lp
> -                                       terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
> +                                       if p == 'suspend_machine':
> +                                               sm = sysvals.suspendmode
> +                                               if sm in suspendmodename:
> +                                                       sm = suspendmodename[sm]
> +                                               terr = 'test%s did not enter %s power mode' % (tn, sm)
> +                                       else:
> +                                               terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
>                                         pprint('TEST%s FAILED: %s' % (tn, terr))
>                                         error.append(terr)
>                                         if data.tSuspended == 0:
> @@ -3708,9 +3864,7 @@ def parseTraceLog(live=False):
>
>  # Function: loadKernelLog
>  # Description:
> -#       [deprecated for kernel 3.15.0 or newer]
>  #       load the dmesg file into memory and fix up any ordering issues
> -#       The dmesg filename is taken from sysvals
>  # Output:
>  #       An array of empty Data objects with only their dmesgtext attributes set
>  def loadKernelLog():
> @@ -3736,7 +3890,8 @@ def loadKernelLog():
>                 if(not m):
>                         continue
>                 msg = m.group("msg")
> -               if(re.match('PM: Syncing filesystems.*', msg)):
> +               if re.match('PM: Syncing filesystems.*', msg) or \
> +                       re.match('PM: suspend entry.*', msg):
>                         if(data):
>                                 testruns.append(data)
>                         data = Data(len(testruns))
> @@ -3747,11 +3902,17 @@ def loadKernelLog():
>                 if(m):
>                         sysvals.stamp['kernel'] = m.group('k')
>                 m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
> -               if(m):
> +               if not m:
> +                       m = re.match('PM: Preparing system for sleep \((?P<m>.*)\)', msg)
> +               if m:
>                         sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
>                 data.dmesgtext.append(line)
>         lf.close()
>
> +       if sysvals.suspendmode == 's2idle':
> +               sysvals.suspendmode = 'freeze'
> +       elif sysvals.suspendmode == 'deep':
> +               sysvals.suspendmode = 'mem'
>         if data:
>                 testruns.append(data)
>         if len(testruns) < 1:
> @@ -3762,12 +3923,9 @@ def loadKernelLog():
>         for data in testruns:
>                 last = ''
>                 for line in data.dmesgtext:
> -                       mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
> -                               '(?P<f>.*)\+ @ .*, parent: .*', line)
> -                       mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
> -                               '(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
> -                       if(mc and mr and (mc.group('t') == mr.group('t')) and
> -                               (mc.group('f') == mr.group('f'))):
> +                       ct, cf, n, p = data.initcall_debug_call(line)
> +                       rt, rf, l = data.initcall_debug_return(last)
> +                       if ct and rt and ct == rt and cf == rf:
>                                 i = data.dmesgtext.index(last)
>                                 j = data.dmesgtext.index(line)
>                                 data.dmesgtext[i] = line
> @@ -3777,7 +3935,6 @@ def loadKernelLog():
>
>  # Function: parseKernelLog
>  # Description:
> -#       [deprecated for kernel 3.15.0 or newer]
>  #       Analyse a dmesg log output file generated from this app during
>  #       the execution phase. Create a set of device structures in memory
>  #       for subsequent formatting in the html output file
> @@ -3796,30 +3953,30 @@ def parseKernelLog(data):
>
>         # dmesg phase match table
>         dm = {
> -               'suspend_prepare': ['PM: Syncing filesystems.*'],
> -                       'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*'],
> -                  'suspend_late': ['PM: suspend of devices complete after.*'],
> -                 'suspend_noirq': ['PM: late suspend of devices complete after.*'],
> -               'suspend_machine': ['PM: noirq suspend of devices complete after.*'],
> -                'resume_machine': ['ACPI: Low-level resume complete.*'],
> -                  'resume_noirq': ['ACPI: Waking up from system sleep state.*'],
> -                  'resume_early': ['PM: noirq resume of devices complete after.*'],
> -                        'resume': ['PM: early resume of devices complete after.*'],
> -               'resume_complete': ['PM: resume of devices complete after.*'],
> +               'suspend_prepare': ['PM: Syncing filesystems.*', 'PM: suspend entry.*'],
> +                       'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*',
> +                                   'PM: Suspending system .*'],
> +                  'suspend_late': ['PM: suspend of devices complete after.*',
> +                                                       'PM: freeze of devices complete after.*'],
> +                 'suspend_noirq': ['PM: late suspend of devices complete after.*',
> +                                                       'PM: late freeze of devices complete after.*'],
> +               'suspend_machine': ['PM: suspend-to-idle',
> +                                                       'PM: noirq suspend of devices complete after.*',
> +                                                       'PM: noirq freeze of devices complete after.*'],
> +                'resume_machine': ['PM: Timekeeping suspended for.*',
> +                                                       'ACPI: Low-level resume complete.*',
> +                                                       'ACPI: resume from mwait',
> +                                                       'Suspended for [0-9\.]* seconds'],
> +                  'resume_noirq': ['PM: resume from suspend-to-idle',
> +                                                       'ACPI: Waking up from system sleep state.*'],
> +                  'resume_early': ['PM: noirq resume of devices complete after.*',
> +                                                       'PM: noirq restore of devices complete after.*'],
> +                        'resume': ['PM: early resume of devices complete after.*',
> +                                                       'PM: early restore of devices complete after.*'],
> +               'resume_complete': ['PM: resume of devices complete after.*',
> +                                                       'PM: restore of devices complete after.*'],
>                     'post_resume': ['.*Restarting tasks \.\.\..*'],
>         }
> -       if(sysvals.suspendmode == 'standby'):
> -               dm['resume_machine'] = ['PM: Restoring platform NVS memory']
> -       elif(sysvals.suspendmode == 'disk'):
> -               dm['suspend_late'] = ['PM: freeze of devices complete after.*']
> -               dm['suspend_noirq'] = ['PM: late freeze of devices complete after.*']
> -               dm['suspend_machine'] = ['PM: noirq freeze of devices complete after.*']
> -               dm['resume_machine'] = ['PM: Restoring platform NVS memory']
> -               dm['resume_early'] = ['PM: noirq restore of devices complete after.*']
> -               dm['resume'] = ['PM: early restore of devices complete after.*']
> -               dm['resume_complete'] = ['PM: restore of devices complete after.*']
> -       elif(sysvals.suspendmode == 'freeze'):
> -               dm['resume_machine'] = ['ACPI: resume from mwait']
>
>         # action table (expected events that occur and show up in dmesg)
>         at = {
> @@ -3867,12 +4024,13 @@ def parseKernelLog(data):
>                         for s in dm[p]:
>                                 if(re.match(s, msg)):
>                                         phasechange, phase = True, p
> +                                       dm[p] = [s]
>                                         break
>
>                 # hack for determining resume_machine end for freeze
>                 if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
>                         and phase == 'resume_machine' and \
> -                       re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
> +                       data.initcall_debug_call(line, True)):
>                         data.setPhase(phase, ktime, False)
>                         phase = 'resume_noirq'
>                         data.setPhase(phase, ktime, True)
> @@ -3945,26 +4103,18 @@ def parseKernelLog(data):
>                 # -- device callbacks --
>                 if(phase in data.sortedPhases()):
>                         # device init call
> -                       if(re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
> -                               sm = re.match('calling  (?P<f>.*)\+ @ '+\
> -                                       '(?P<n>.*), parent: (?P<p>.*)', msg);
> -                               f = sm.group('f')
> -                               n = sm.group('n')
> -                               p = sm.group('p')
> -                               if(f and n and p):
> -                                       data.newAction(phase, f, int(n), p, ktime, -1, '')
> -                       # device init return
> -                       elif(re.match('call (?P<f>.*)\+ returned .* after '+\
> -                               '(?P<t>.*) usecs', msg)):
> -                               sm = re.match('call (?P<f>.*)\+ returned .* after '+\
> -                                       '(?P<t>.*) usecs(?P<a>.*)', msg);
> -                               f = sm.group('f')
> -                               t = sm.group('t')
> -                               list = data.dmesg[phase]['list']
> -                               if(f in list):
> -                                       dev = list[f]
> -                                       dev['length'] = int(t)
> -                                       dev['end'] = ktime
> +                       t, f, n, p = data.initcall_debug_call(line)
> +                       if t and f and n and p:
> +                               data.newAction(phase, f, int(n), p, ktime, -1, '')
> +                       else:
> +                               # device init return
> +                               t, f, l = data.initcall_debug_return(line)
> +                               if t and f and l:
> +                                       list = data.dmesg[phase]['list']
> +                                       if(f in list):
> +                                               dev = list[f]
> +                                               dev['length'] = int(l)
> +                                               dev['end'] = ktime
>
>                 # if trace events are not available, these are better than nothing
>                 if(not sysvals.usetraceevents):
> @@ -4006,6 +4156,8 @@ def parseKernelLog(data):
>         # fill in any missing phases
>         phasedef = data.phasedef
>         terr, lp = '', 'suspend_prepare'
> +       if lp not in data.dmesg:
> +               doError('dmesg log format has changed, could not find start of suspend')
>         for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
>                 if p not in data.dmesg:
>                         if not terr:
> @@ -5302,7 +5454,7 @@ def executeSuspend(quiet=False):
>         sv.dlog('read dmesg')
>         sv.initdmesg()
>         # start ftrace
> -       if(sv.usecallgraph or sv.usetraceevents):
> +       if sv.useftrace:
>                 if not quiet:
>                         pprint('START TRACING')
>                 sv.dlog('start ftrace tracing')
> @@ -5334,8 +5486,7 @@ def executeSuspend(quiet=False):
>                         sv.dlog('enable RTC wake alarm')
>                         sv.rtcWakeAlarmOn()
>                 # start of suspend trace marker
> -               if(sv.usecallgraph or sv.usetraceevents):
> -                       sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
> +               sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
>                 # predelay delay
>                 if(count == 1 and sv.predelay > 0):
>                         sv.fsetVal('WAIT %d' % sv.predelay, 'trace_marker')
> @@ -5384,11 +5535,17 @@ def executeSuspend(quiet=False):
>                         sv.fsetVal('WAIT END', 'trace_marker')
>                 # return from suspend
>                 pprint('RESUME COMPLETE')
> -               if(sv.usecallgraph or sv.usetraceevents):
> -                       sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
> +               sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
>                 if sv.wifi and wifi:
>                         tdata['wifi'] = sv.pollWifi(wifi)
>                         sv.dlog('wifi check, %s' % tdata['wifi'])
> +                       if sv.netfix:
> +                               netfixout = sv.netfixon('wired')
> +               elif sv.netfix:
> +                       netfixout = sv.netfixon()
> +               if sv.netfix and netfixout:
> +                       tdata['netfix'] = netfixout
> +                       sv.dlog('netfix, %s' % tdata['netfix'])
>                 if(sv.suspendmode == 'mem' or sv.suspendmode == 'command'):
>                         sv.dlog('read the ACPI FPDT')
>                         tdata['fw'] = getFPDT(False)
> @@ -5396,7 +5553,7 @@ def executeSuspend(quiet=False):
>         sv.dlog('run the cmdinfo list after')
>         cmdafter = sv.cmdinfo(False)
>         # stop ftrace
> -       if(sv.usecallgraph or sv.usetraceevents):
> +       if sv.useftrace:
>                 if sv.useprocmon:
>                         sv.dlog('stop the process monitor')
>                         pm.stop()
> @@ -5407,7 +5564,7 @@ def executeSuspend(quiet=False):
>         sysvals.dlog('EXECUTION TRACE END')
>         sv.getdmesg(testdata)
>         # grab a copy of the ftrace output
> -       if(sv.usecallgraph or sv.usetraceevents):
> +       if sv.useftrace:
>                 if not quiet:
>                         pprint('CAPTURING TRACE')
>                 op = sv.writeDatafileHeader(sv.ftracefile, testdata)
> @@ -5838,13 +5995,19 @@ def statusCheck(probecheck=False):
>                         pprint('      please choose one with -m')
>
>         # check if ftrace is available
> -       res = sysvals.colorText('NO')
> -       ftgood = sysvals.verifyFtrace()
> -       if(ftgood):
> -               res = 'YES'
> -       elif(sysvals.usecallgraph):
> -               status = 'ftrace is not properly supported'
> -       pprint('    is ftrace supported: %s' % res)
> +       if sysvals.useftrace:
> +               res = sysvals.colorText('NO')
> +               sysvals.useftrace = sysvals.verifyFtrace()
> +               efmt = '"{0}" uses ftrace, and it is not properly supported'
> +               if sysvals.useftrace:
> +                       res = 'YES'
> +               elif sysvals.usecallgraph:
> +                       status = efmt.format('-f')
> +               elif sysvals.usedevsrc:
> +                       status = efmt.format('-dev')
> +               elif sysvals.useprocmon:
> +                       status = efmt.format('-proc')
> +               pprint('    is ftrace supported: %s' % res)
>
>         # check if kprobes are available
>         if sysvals.usekprobes:
> @@ -5857,8 +6020,8 @@ def statusCheck(probecheck=False):
>                 pprint('    are kprobes supported: %s' % res)
>
>         # what data source are we using
> -       res = 'DMESG'
> -       if(ftgood):
> +       res = 'DMESG (very limited, ftrace is preferred)'
> +       if sysvals.useftrace:
>                 sysvals.usetraceevents = True
>                 for e in sysvals.traceevents:
>                         if not os.path.exists(sysvals.epath+e):
> @@ -5879,7 +6042,7 @@ def statusCheck(probecheck=False):
>         pprint('    optional commands this tool may use for info:')
>         no = sysvals.colorText('MISSING')
>         yes = sysvals.colorText('FOUND', 32)
> -       for c in ['turbostat', 'mcelog', 'lspci', 'lsusb']:
> +       for c in ['turbostat', 'mcelog', 'lspci', 'lsusb', 'netfix']:
>                 if c == 'turbostat':
>                         res = yes if sysvals.haveTurbostat() else no
>                 else:
> @@ -5971,7 +6134,7 @@ def processData(live=False, quiet=False):
>         if not sysvals.stamp:
>                 pprint('ERROR: data does not include the expected stamp')
>                 return (testruns, {'error': 'timeline generation failed'})
> -       shown = ['bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
> +       shown = ['os', 'bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
>                         'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
>         sysvals.vprint('System Info:')
>         for key in sorted(sysvals.stamp):
> @@ -6052,6 +6215,8 @@ def runTest(n=0, quiet=False):
>                 if sysvals.display:
>                         ret = sysvals.displayControl('init')
>                         sysvals.dlog('xset display init, ret = %d' % ret)
> +       sysvals.testVal(sysvals.pmdpath, 'basic', '1')
> +       sysvals.testVal(sysvals.s0ixpath, 'basic', 'Y')
>         sysvals.dlog('initialize ftrace')
>         sysvals.initFtrace(quiet)
>
> @@ -6145,9 +6310,12 @@ def data_from_html(file, outpath, issues, fulldetail=False):
>                                 elist[err[0]] += 1
>                 for i in elist:
>                         ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
> -       wifi = find_in_html(html, 'Wifi Resume: ', '</td>')
> -       if wifi:
> -               extra['wifi'] = wifi
> +               line = find_in_html(log, '# wifi ', '\n')
> +               if line:
> +                       extra['wifi'] = line
> +               line = find_in_html(log, '# netfix ', '\n')
> +               if line:
> +                       extra['netfix'] = line
>         low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
>         for lowstr in ['waking', '+']:
>                 if not low:
> @@ -6243,7 +6411,7 @@ def genHtml(subdir, force=False):
>                                         sysvals.ftracefile = file
>                 sysvals.setOutputFile()
>                 if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
> -                       (force or not sysvals.usable(sysvals.htmlfile)):
> +                       (force or not sysvals.usable(sysvals.htmlfile, True)):
>                         pprint('FTRACE: %s' % sysvals.ftracefile)
>                         if sysvals.dmesgfile:
>                                 pprint('DMESG : %s' % sysvals.dmesgfile)
> @@ -6533,6 +6701,7 @@ def printHelp():
>         '   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
>         '   -result fn   Export a results table to a text file for parsing.\n'\
>         '   -wifi        If a wifi connection is available, check that it reconnects after resume.\n'\
> +       '   -netfix      Use netfix to reset the network in the event it fails to resume.\n'\
>         '  [testprep]\n'\
>         '   -sync        Sync the filesystems before starting the test\n'\
>         '   -rs on/off   Enable/disable runtime suspend for all devices, restore all after test\n'\
> @@ -6615,6 +6784,8 @@ if __name__ == '__main__':
>                 elif(arg == '-v'):
>                         pprint("Version %s" % sysvals.version)
>                         sys.exit(0)
> +               elif(arg == '-debugtiming'):
> +                       debugtiming = True
>                 elif(arg == '-x2'):
>                         sysvals.execcount = 2
>                 elif(arg == '-x2delay'):
> @@ -6657,6 +6828,8 @@ if __name__ == '__main__':
>                         sysvals.sync = True
>                 elif(arg == '-wifi'):
>                         sysvals.wifi = True
> +               elif(arg == '-netfix'):
> +                       sysvals.netfix = True
>                 elif(arg == '-gzip'):
>                         sysvals.gzip = True
>                 elif(arg == '-info'):
> @@ -6819,7 +6992,7 @@ if __name__ == '__main__':
>                         sysvals.outdir = val
>                         sysvals.notestrun = True
>                         if(os.path.isdir(val) == False):
> -                               doError('%s is not accessible' % val)
> +                               doError('%s is not accesible' % val)
>                 elif(arg == '-filter'):
>                         try:
>                                 val = next(args)
> @@ -6942,12 +7115,11 @@ if __name__ == '__main__':
>                                 time.sleep(sysvals.multitest['delay'])
>                         fmt = 'suspend-%y%m%d-%H%M%S'
>                         sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
> -                       ret = runTest(i+1, True)
> +                       ret = runTest(i+1, not sysvals.verbose)
>                         failcnt = 0 if not ret else failcnt + 1
>                         if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
>                                 pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
>                                 break
> -                       time.sleep(5)
>                         sysvals.resetlog()
>                         sysvals.multistat(False, i, finish)
>                         if 'time' in sysvals.multitest and datetime.now() >= finish:
> --
> 2.17.1
>
diff mbox series

Patch

diff --git a/tools/power/pm-graph/README b/tools/power/pm-graph/README
index da468bd510ca..e6020c0d59ec 100644
--- a/tools/power/pm-graph/README
+++ b/tools/power/pm-graph/README
@@ -6,7 +6,7 @@ 
    |_|                    |___/          |_|
 
    pm-graph: suspend/resume/boot timing analysis tools
-    Version: 5.8
+    Version: 5.9
      Author: Todd Brandt <todd.e.brandt@intel.com>
   Home Page: https://01.org/pm-graph
 
@@ -97,8 +97,8 @@ 
         (kernel/pre-3.15/enable_trace_events_suspend_resume.patch)
         (kernel/pre-3.15/enable_trace_events_device_pm_callback.patch)
 
-	If you're using a kernel older than 3.15.0, the following
-        additional kernel parameters are required:
+	If you're using bootgraph, or sleepgraph with a kernel older than 3.15.0,
+		the following additional kernel parameters are required:
         (e.g. in file /etc/default/grub)
         GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=32M ..."
 
diff --git a/tools/power/pm-graph/bootgraph.py b/tools/power/pm-graph/bootgraph.py
index 2823cd3122f7..f96f50e0c336 100755
--- a/tools/power/pm-graph/bootgraph.py
+++ b/tools/power/pm-graph/bootgraph.py
@@ -69,22 +69,24 @@  class SystemValues(aslib.SystemValues):
 	bootloader = 'grub'
 	blexec = []
 	def __init__(self):
-		self.hostname = platform.node()
+		self.kernel, self.hostname = 'unknown', platform.node()
 		self.testtime = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
 		if os.path.exists('/proc/version'):
 			fp = open('/proc/version', 'r')
-			val = fp.read().strip()
+			self.kernel = self.kernelVersion(fp.read().strip())
 			fp.close()
-			self.kernel = self.kernelVersion(val)
-		else:
-			self.kernel = 'unknown'
 		self.testdir = datetime.now().strftime('boot-%y%m%d-%H%M%S')
 	def kernelVersion(self, msg):
-		return msg.split()[2]
+		m = re.match('^[Ll]inux *[Vv]ersion *(?P<v>\S*) .*', msg)
+		if m:
+			return m.group('v')
+		return 'unknown'
 	def checkFtraceKernelVersion(self):
-		val = tuple(map(int, self.kernel.split('-')[0].split('.')))
-		if val >= (4, 10, 0):
-			return True
+		m = re.match('^(?P<x>[0-9]*)\.(?P<y>[0-9]*)\.(?P<z>[0-9]*).*', self.kernel)
+		if m:
+			val = tuple(map(int, m.groups()))
+			if val >= (4, 10, 0):
+				return True
 		return False
 	def kernelParams(self):
 		cmdline = 'initcall_debug log_buf_len=32M'
diff --git a/tools/power/pm-graph/config/custom-timeline-functions.cfg b/tools/power/pm-graph/config/custom-timeline-functions.cfg
index 962e5768681c..4f80ad7d7275 100644
--- a/tools/power/pm-graph/config/custom-timeline-functions.cfg
+++ b/tools/power/pm-graph/config/custom-timeline-functions.cfg
@@ -125,7 +125,7 @@  acpi_suspend_begin:
 suspend_console:
 acpi_pm_prepare:
 syscore_suspend:
-arch_thaw_secondary_cpus_end:
+arch_enable_nonboot_cpus_end:
 syscore_resume:
 acpi_pm_finish:
 resume_console:
diff --git a/tools/power/pm-graph/sleepgraph.py b/tools/power/pm-graph/sleepgraph.py
index ffd50953a024..33981adcdd68 100755
--- a/tools/power/pm-graph/sleepgraph.py
+++ b/tools/power/pm-graph/sleepgraph.py
@@ -66,8 +66,13 @@  from threading import Thread
 from subprocess import call, Popen, PIPE
 import base64
 
+debugtiming = False
+mystarttime = time.time()
 def pprint(msg):
-	print(msg)
+	if debugtiming:
+		print('[%09.3f] %s' % (time.time()-mystarttime, msg))
+	else:
+		print(msg)
 	sys.stdout.flush()
 
 def ascii(text):
@@ -81,13 +86,14 @@  def ascii(text):
 #	 store system values and test parameters
 class SystemValues:
 	title = 'SleepGraph'
-	version = '5.8'
+	version = '5.9'
 	ansi = False
 	rs = 0
 	display = ''
 	gzip = False
 	sync = False
 	wifi = False
+	netfix = False
 	verbose = False
 	testlog = True
 	dmesglog = True
@@ -108,6 +114,7 @@  class SystemValues:
 	cpucount = 0
 	memtotal = 204800
 	memfree = 204800
+	osversion = ''
 	srgap = 0
 	cgexp = False
 	testdir = ''
@@ -116,6 +123,7 @@  class SystemValues:
 	fpdtpath = '/sys/firmware/acpi/tables/FPDT'
 	epath = '/sys/kernel/debug/tracing/events/power/'
 	pmdpath = '/sys/power/pm_debug_messages'
+	s0ixpath = '/sys/module/intel_pmc_core/parameters/warn_on_s0ix_failures'
 	acpipath='/sys/module/acpi/parameters/debug_level'
 	traceevents = [
 		'suspend_resume',
@@ -156,6 +164,7 @@  class SystemValues:
 	ftop = False
 	usetraceevents = False
 	usetracemarkers = True
+	useftrace = True
 	usekprobes = True
 	usedevsrc = False
 	useprocmon = False
@@ -279,10 +288,16 @@  class SystemValues:
 		'intel_fbdev_set_suspend': {},
 	}
 	infocmds = [
+		[0, 'sysinfo', 'uname', '-a'],
+		[0, 'cpuinfo', 'head', '-7', '/proc/cpuinfo'],
 		[0, 'kparams', 'cat', '/proc/cmdline'],
 		[0, 'mcelog', 'mcelog'],
 		[0, 'pcidevices', 'lspci', '-tv'],
-		[0, 'usbdevices', 'lsusb', '-t'],
+		[0, 'usbdevices', 'lsusb', '-tv'],
+		[0, 'acpidevices', 'sh', '-c', 'ls -l /sys/bus/acpi/devices/*/physical_node'],
+		[0, 's0ix_require', 'cat', '/sys/kernel/debug/pmc_core/substate_requirements'],
+		[0, 's0ix_debug', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_debug_status'],
+		[1, 's0ix_residency', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_residency_usec'],
 		[1, 'interrupts', 'cat', '/proc/interrupts'],
 		[1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'],
 		[2, 'gpecounts', 'sh', '-c', 'grep -v invalid /sys/firmware/acpi/interrupts/*'],
@@ -358,8 +373,19 @@  class SystemValues:
 			self.outputResult({'error':msg})
 			sys.exit(1)
 		return False
-	def usable(self, file):
-		return (os.path.exists(file) and os.path.getsize(file) > 0)
+	def usable(self, file, ishtml=False):
+		if not os.path.exists(file) or os.path.getsize(file) < 1:
+			return False
+		if ishtml:
+			try:
+				fp = open(file, 'r')
+				res = fp.read(1000)
+				fp.close()
+			except:
+				return False
+			if '<html>' not in res:
+				return False
+		return True
 	def getExec(self, cmd):
 		try:
 			fp = Popen(['which', cmd], stdout=PIPE, stderr=PIPE).stdout
@@ -413,12 +439,16 @@  class SystemValues:
 		r = info['bios-release-date'] if 'bios-release-date' in info else ''
 		self.sysstamp = '# sysinfo | man:%s | plat:%s | cpu:%s | bios:%s | biosdate:%s | numcpu:%d | memsz:%d | memfr:%d' % \
 			(m, p, c, b, r, self.cpucount, self.memtotal, self.memfree)
+		if self.osversion:
+			self.sysstamp += ' | os:%s' % self.osversion
 	def printSystemInfo(self, fatal=False):
 		self.rootCheck(True)
 		out = dmidecode(self.mempath, fatal)
 		if len(out) < 1:
 			return
 		fmt = '%-24s: %s'
+		if self.osversion:
+			print(fmt % ('os-version', self.osversion))
 		for name in sorted(out):
 			print(fmt % (name, out[name]))
 		print(fmt % ('cpucount', ('%d' % self.cpucount)))
@@ -426,20 +456,25 @@  class SystemValues:
 		print(fmt % ('memfree', ('%d kB' % self.memfree)))
 	def cpuInfo(self):
 		self.cpucount = 0
-		fp = open('/proc/cpuinfo', 'r')
-		for line in fp:
-			if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
-				self.cpucount += 1
-		fp.close()
-		fp = open('/proc/meminfo', 'r')
-		for line in fp:
-			m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
-			if m:
-				self.memtotal = int(m.group('sz'))
-			m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
-			if m:
-				self.memfree = int(m.group('sz'))
-		fp.close()
+		if os.path.exists('/proc/cpuinfo'):
+			with open('/proc/cpuinfo', 'r') as fp:
+				for line in fp:
+					if re.match('^processor[ \t]*:[ \t]*[0-9]*', line):
+						self.cpucount += 1
+		if os.path.exists('/proc/meminfo'):
+			with open('/proc/meminfo', 'r') as fp:
+				for line in fp:
+					m = re.match('^MemTotal:[ \t]*(?P<sz>[0-9]*) *kB', line)
+					if m:
+						self.memtotal = int(m.group('sz'))
+					m = re.match('^MemFree:[ \t]*(?P<sz>[0-9]*) *kB', line)
+					if m:
+						self.memfree = int(m.group('sz'))
+		if os.path.exists('/etc/os-release'):
+			with open('/etc/os-release', 'r') as fp:
+				for line in fp:
+					if line.startswith('PRETTY_NAME='):
+						self.osversion = line[12:].strip().replace('"', '')
 	def initTestOutput(self, name):
 		self.prefix = self.hostname
 		v = open('/proc/version', 'r').read().strip()
@@ -698,6 +733,8 @@  class SystemValues:
 			return False
 		return True
 	def fsetVal(self, val, path):
+		if not self.useftrace:
+			return False
 		return self.setVal(val, self.tpath+path)
 	def getVal(self, file):
 		res = ''
@@ -711,9 +748,11 @@  class SystemValues:
 			pass
 		return res
 	def fgetVal(self, path):
+		if not self.useftrace:
+			return ''
 		return self.getVal(self.tpath+path)
 	def cleanupFtrace(self):
-		if(self.usecallgraph or self.usetraceevents or self.usedevsrc):
+		if self.useftrace:
 			self.fsetVal('0', 'events/kprobes/enable')
 			self.fsetVal('', 'kprobe_events')
 			self.fsetVal('1024', 'buffer_size_kb')
@@ -734,13 +773,14 @@  class SystemValues:
 				return True
 		return False
 	def initFtrace(self, quiet=False):
+		if not self.useftrace:
+			return
 		if not quiet:
 			sysvals.printSystemInfo(False)
 			pprint('INITIALIZING FTRACE...')
 		# turn trace off
 		self.fsetVal('0', 'tracing_on')
 		self.cleanupFtrace()
-		self.testVal(self.pmdpath, 'basic', '1')
 		# set the trace clock to global
 		self.fsetVal('global', 'trace_clock')
 		self.fsetVal('nop', 'current_tracer')
@@ -766,6 +806,10 @@  class SystemValues:
 			# set trace type
 			self.fsetVal('function_graph', 'current_tracer')
 			self.fsetVal('', 'set_ftrace_filter')
+			# temporary hack to fix https://bugzilla.kernel.org/show_bug.cgi?id=212761
+			fp = open(self.tpath+'set_ftrace_notrace', 'w')
+			fp.write('native_queued_spin_lock_slowpath\ndev_driver_string')
+			fp.close()
 			# set trace format options
 			self.fsetVal('print-parent', 'trace_options')
 			self.fsetVal('funcgraph-abstime', 'trace_options')
@@ -846,6 +890,8 @@  class SystemValues:
 				fp.write('# turbostat %s\n' % test['turbo'])
 			if 'wifi' in test:
 				fp.write('# wifi %s\n' % test['wifi'])
+			if 'netfix' in test:
+				fp.write('# netfix %s\n' % test['netfix'])
 			if test['error'] or len(testdata) > 1:
 				fp.write('# enter_sleep_error %s\n' % test['error'])
 		return fp
@@ -865,6 +911,8 @@  class SystemValues:
 			fp.write('error%s: %s\n' % (n, testdata['error']))
 		else:
 			fp.write('result%s: pass\n' % n)
+		if 'mode' in testdata:
+			fp.write('mode%s: %s\n' % (n, testdata['mode']))
 		for v in ['suspend', 'resume', 'boot', 'lastinit']:
 			if v in testdata:
 				fp.write('%s%s: %.3f\n' % (v, n, testdata[v]))
@@ -901,6 +949,8 @@  class SystemValues:
 			fp.write(text)
 			fp.close()
 	def dlog(self, text):
+		if not self.dmesgfile:
+			return
 		self.putlog(self.dmesgfile, '# %s\n' % text)
 	def flog(self, text):
 		self.putlog(self.ftracefile, text)
@@ -954,34 +1004,31 @@  class SystemValues:
 			dirname = props[dev].syspath
 			if not dirname or not os.path.exists(dirname):
 				continue
-			with open(dirname+'/power/async') as fp:
-				text = fp.read()
-				props[dev].isasync = False
-				if 'enabled' in text:
+			props[dev].isasync = False
+			if os.path.exists(dirname+'/power/async'):
+				fp = open(dirname+'/power/async')
+				if 'enabled' in fp.read():
 					props[dev].isasync = True
+				fp.close()
 			fields = os.listdir(dirname)
-			if 'product' in fields:
-				with open(dirname+'/product', 'rb') as fp:
-					props[dev].altname = ascii(fp.read())
-			elif 'name' in fields:
-				with open(dirname+'/name', 'rb') as fp:
-					props[dev].altname = ascii(fp.read())
-			elif 'model' in fields:
-				with open(dirname+'/model', 'rb') as fp:
-					props[dev].altname = ascii(fp.read())
-			elif 'description' in fields:
-				with open(dirname+'/description', 'rb') as fp:
-					props[dev].altname = ascii(fp.read())
-			elif 'id' in fields:
-				with open(dirname+'/id', 'rb') as fp:
-					props[dev].altname = ascii(fp.read())
-			elif 'idVendor' in fields and 'idProduct' in fields:
-				idv, idp = '', ''
-				with open(dirname+'/idVendor', 'rb') as fp:
-					idv = ascii(fp.read()).strip()
-				with open(dirname+'/idProduct', 'rb') as fp:
-					idp = ascii(fp.read()).strip()
-				props[dev].altname = '%s:%s' % (idv, idp)
+			for file in ['product', 'name', 'model', 'description', 'id', 'idVendor']:
+				if file not in fields:
+					continue
+				try:
+					with open(os.path.join(dirname, file), 'rb') as fp:
+						props[dev].altname = ascii(fp.read())
+				except:
+					continue
+				if file == 'idVendor':
+					idv, idp = props[dev].altname.strip(), ''
+					try:
+						with open(os.path.join(dirname, 'idProduct'), 'rb') as fp:
+							idp = ascii(fp.read()).strip()
+					except:
+						props[dev].altname = ''
+						break
+					props[dev].altname = '%s:%s' % (idv, idp)
+				break
 			if props[dev].altname:
 				out = props[dev].altname.strip().replace('\n', ' ')\
 					.replace(',', ' ').replace(';', ' ')
@@ -1047,7 +1094,7 @@  class SystemValues:
 				self.cmd1[name] = self.dictify(info, delta)
 			elif not debug and delta and name in self.cmd1:
 				before, after = self.cmd1[name], self.dictify(info, delta)
-				dinfo = ('\t%s\n' % before['@']) if '@' in before else ''
+				dinfo = ('\t%s\n' % before['@']) if '@' in before and len(before) > 1 else ''
 				prefix = self.commonPrefix(list(before.keys()))
 				for key in sorted(before):
 					if key in after and before[key] != after[key]:
@@ -1128,6 +1175,22 @@  class SystemValues:
 			val = valline[idx]
 			out.append('%s=%s' % (key, val))
 		return '|'.join(out)
+	def netfixon(self, net='both'):
+		cmd = self.getExec('netfix')
+		if not cmd:
+			return ''
+		fp = Popen([cmd, '-s', net, 'on'], stdout=PIPE, stderr=PIPE).stdout
+		out = ascii(fp.read()).strip()
+		fp.close()
+		return out
+	def wifiRepair(self):
+		out = self.netfixon('wifi')
+		if not out or 'error' in out.lower():
+			return ''
+		m = re.match('WIFI \S* ONLINE (?P<action>\S*)', out)
+		if not m:
+			return 'dead'
+		return m.group('action')
 	def wifiDetails(self, dev):
 		try:
 			info = open('/sys/class/net/%s/device/uevent' % dev, 'r').read().strip()
@@ -1144,12 +1207,12 @@  class SystemValues:
 		except:
 			return ''
 		for line in reversed(w.split('\n')):
-			m = re.match(' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', w.split('\n')[-1])
+			m = re.match(' *(?P<dev>.*): (?P<stat>[0-9a-f]*) .*', line)
 			if not m or (dev and dev != m.group('dev')):
 				continue
 			return m.group('dev')
 		return ''
-	def pollWifi(self, dev, timeout=60):
+	def pollWifi(self, dev, timeout=10):
 		start = time.time()
 		while (time.time() - start) < timeout:
 			w = self.checkWifi(dev)
@@ -1157,6 +1220,11 @@  class SystemValues:
 				return '%s reconnected %.2f' % \
 					(self.wifiDetails(dev), max(0, time.time() - start))
 			time.sleep(0.01)
+		if self.netfix:
+			res = self.wifiRepair()
+			if res:
+				timeout = max(0, time.time() - start)
+				return '%s %s %d' % (self.wifiDetails(dev), res, timeout)
 		return '%s timeout %d' % (self.wifiDetails(dev), timeout)
 	def errorSummary(self, errinfo, msg):
 		found = False
@@ -1283,10 +1351,10 @@  sysvals = SystemValues()
 switchvalues = ['enable', 'disable', 'on', 'off', 'true', 'false', '1', '0']
 switchoff = ['disable', 'off', 'false', '0']
 suspendmodename = {
-	'freeze': 'Freeze (S0)',
-	'standby': 'Standby (S1)',
-	'mem': 'Suspend (S3)',
-	'disk': 'Hibernate (S4)'
+	'standby': 'standby (S1)',
+	'freeze': 'freeze (S2idle)',
+	'mem': 'suspend (S3)',
+	'disk': 'hibernate (S4)'
 }
 
 # Class: DevProps
@@ -1376,6 +1444,7 @@  class Data:
 		'INVALID' : r'(?i).*\bINVALID\b.*',
 		'CRASH'   : r'(?i).*\bCRASHED\b.*',
 		'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*',
+		'ABORT'   : r'(?i).*\bABORT\b.*',
 		'IRQ'     : r'.*\bgenirq: .*',
 		'TASKFAIL': r'.*Freezing of tasks *.*',
 		'ACPI'    : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*',
@@ -1724,9 +1793,9 @@  class Data:
 				if 'waking' in self.dmesg[lp]:
 					tCnt = self.dmesg[lp]['waking'][0]
 					if self.dmesg[lp]['waking'][1] >= 0.001:
-						tTry = '-%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
+						tTry = '%.0f' % (round(self.dmesg[lp]['waking'][1] * 1000))
 					else:
-						tTry = '-%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
+						tTry = '%.3f' % (self.dmesg[lp]['waking'][1] * 1000)
 					text = '%.0f (%s ms waking %d times)' % (tL * 1000, tTry, tCnt)
 				else:
 					text = '%.0f' % (tL * 1000)
@@ -2107,6 +2176,30 @@  class Data:
 		# set resume complete to end at end marker
 		if 'resume_complete' in dm:
 			dm['resume_complete']['end'] = time
+	def initcall_debug_call(self, line, quick=False):
+		m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
+			'PM: *calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
+		if not m:
+			m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
+				'calling .* @ (?P<n>.*), parent: (?P<p>.*)', line)
+		if not m:
+			m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
+				'(?P<f>.*)\+ @ (?P<n>.*), parent: (?P<p>.*)', line)
+		if m:
+			return True if quick else m.group('t', 'f', 'n', 'p')
+		return False if quick else ('', '', '', '')
+	def initcall_debug_return(self, line, quick=False):
+		m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: PM: '+\
+			'.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
+		if not m:
+			m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) .* (?P<f>.*)\: '+\
+				'.* returned (?P<r>[0-9]*) after (?P<dt>[0-9]*) usecs', line)
+		if not m:
+			m = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
+				'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', line)
+		if m:
+			return True if quick else m.group('t', 'f', 'dt')
+		return False if quick else ('', '', '')
 	def debugPrint(self):
 		for p in self.sortedPhases():
 			list = self.dmesg[p]['list']
@@ -2880,10 +2973,11 @@  class TestProps:
 	cmdlinefmt = '^# command \| (?P<cmd>.*)'
 	kparamsfmt = '^# kparams \| (?P<kp>.*)'
 	devpropfmt = '# Device Properties: .*'
-	pinfofmt   = '# platform-(?P<val>[a-z,A-Z,0-9]*): (?P<info>.*)'
+	pinfofmt   = '# platform-(?P<val>[a-z,A-Z,0-9,_]*): (?P<info>.*)'
 	tracertypefmt = '# tracer: (?P<t>.*)'
 	firmwarefmt = '# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$'
 	procexecfmt = 'ps - (?P<ps>.*)$'
+	procmultifmt = '@(?P<n>[0-9]*)\|(?P<ps>.*)$'
 	ftrace_line_fmt_fg = \
 		'^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)'+\
 		' *(?P<proc>.*)-(?P<pid>[0-9]*) *\|'+\
@@ -2893,6 +2987,9 @@  class TestProps:
 		'(?P<flags>\S*) *(?P<time>[0-9\.]*): *'+\
 		'(?P<msg>.*)'
 	machinesuspend = 'machine_suspend\[.*'
+	multiproclist = dict()
+	multiproctime = 0.0
+	multiproccnt = 0
 	def __init__(self):
 		self.stamp = ''
 		self.sysinfo = ''
@@ -3063,6 +3160,7 @@  class TestRun:
 		self.ttemp = dict()
 
 class ProcessMonitor:
+	maxchars = 512
 	def __init__(self):
 		self.proclist = dict()
 		self.running = False
@@ -3088,19 +3186,23 @@  class ProcessMonitor:
 			if ujiff > 0 or kjiff > 0:
 				running[pid] = ujiff + kjiff
 		process.wait()
-		out = ''
+		out = ['']
 		for pid in running:
 			jiffies = running[pid]
 			val = self.proclist[pid]
-			if out:
-				out += ','
-			out += '%s-%s %d' % (val['name'], pid, jiffies)
-		return 'ps - '+out
+			if len(out[-1]) > self.maxchars:
+				out.append('')
+			elif len(out[-1]) > 0:
+				out[-1] += ','
+			out[-1] += '%s-%s %d' % (val['name'], pid, jiffies)
+		if len(out) > 1:
+			for line in out:
+				sysvals.fsetVal('ps - @%d|%s' % (len(out), line), 'trace_marker')
+		else:
+			sysvals.fsetVal('ps - %s' % out[0], 'trace_marker')
 	def processMonitor(self, tid):
 		while self.running:
-			out = self.procstat()
-			if out:
-				sysvals.fsetVal(out, 'trace_marker')
+			self.procstat()
 	def start(self):
 		self.thread = Thread(target=self.processMonitor, args=(0,))
 		self.running = True
@@ -3144,7 +3246,6 @@  def doesTraceLogHaveTraceEvents():
 
 # Function: appendIncompleteTraceLog
 # Description:
-#	 [deprecated for kernel 3.15 or newer]
 #	 Adds callgraph data which lacks trace event data. This is only
 #	 for timelines generated from 3.15 or older
 # Arguments:
@@ -3246,6 +3347,61 @@  def appendIncompleteTraceLog(testruns):
 								dev['ftrace'] = cg
 						break
 
+# Function: loadTraceLog
+# Description:
+#	 load the ftrace file into memory and fix up any ordering issues
+# Output:
+#	 TestProps instance and an array of lines in proper order
+def loadTraceLog():
+	tp, data, lines, trace = TestProps(), dict(), [], []
+	tf = sysvals.openlog(sysvals.ftracefile, 'r')
+	for line in tf:
+		# remove any latent carriage returns
+		line = line.replace('\r\n', '')
+		if tp.stampInfo(line, sysvals):
+			continue
+		# ignore all other commented lines
+		if line[0] == '#':
+			continue
+		# ftrace line: parse only valid lines
+		m = re.match(tp.ftrace_line_fmt, line)
+		if(not m):
+			continue
+		dur = m.group('dur') if tp.cgformat else 'traceevent'
+		info = (m.group('time'), m.group('proc'), m.group('pid'),
+			m.group('msg'), dur)
+		# group the data by timestamp
+		t = float(info[0])
+		if t in data:
+			data[t].append(info)
+		else:
+			data[t] = [info]
+		# we only care about trace event ordering
+		if (info[3].startswith('suspend_resume:') or \
+			info[3].startswith('tracing_mark_write:')) and t not in trace:
+				trace.append(t)
+	tf.close()
+	for t in sorted(data):
+		first, last, blk = [], [], data[t]
+		if len(blk) > 1 and t in trace:
+			# move certain lines to the start or end of a timestamp block
+			for i in range(len(blk)):
+				if 'SUSPEND START' in blk[i][3]:
+					first.append(i)
+				elif re.match('.* timekeeping_freeze.*begin', blk[i][3]):
+					last.append(i)
+				elif re.match('.* timekeeping_freeze.*end', blk[i][3]):
+					first.append(i)
+				elif 'RESUME COMPLETE' in blk[i][3]:
+					last.append(i)
+			if len(first) == 1 and len(last) == 0:
+				blk.insert(0, blk.pop(first[0]))
+			elif len(last) == 1 and len(first) == 0:
+				blk.append(blk.pop(last[0]))
+		for info in blk:
+			lines.append(info)
+	return (tp, lines)
+
 # Function: parseTraceLog
 # Description:
 #	 Analyze an ftrace log output file generated from this app during
@@ -3271,32 +3427,12 @@  def parseTraceLog(live=False):
 
 	# extract the callgraph and traceevent data
 	s2idle_enter = hwsus = False
-	tp = TestProps()
 	testruns, testdata = [], []
 	testrun, data, limbo = 0, 0, True
-	tf = sysvals.openlog(sysvals.ftracefile, 'r')
 	phase = 'suspend_prepare'
-	for line in tf:
-		# remove any latent carriage returns
-		line = line.replace('\r\n', '')
-		if tp.stampInfo(line, sysvals):
-			continue
-		# ignore all other commented lines
-		if line[0] == '#':
-			continue
-		# ftrace line: parse only valid lines
-		m = re.match(tp.ftrace_line_fmt, line)
-		if(not m):
-			continue
+	tp, tf = loadTraceLog()
+	for m_time, m_proc, m_pid, m_msg, m_param3 in tf:
 		# gather the basic message data from the line
-		m_time = m.group('time')
-		m_proc = m.group('proc')
-		m_pid = m.group('pid')
-		m_msg = m.group('msg')
-		if(tp.cgformat):
-			m_param3 = m.group('dur')
-		else:
-			m_param3 = 'traceevent'
 		if(m_time and m_pid and m_msg):
 			t = FTraceLine(m_time, m_msg, m_param3)
 			pid = int(m_pid)
@@ -3322,14 +3458,29 @@  def parseTraceLog(live=False):
 		if t.type == 'tracing_mark_write':
 			m = re.match(tp.procexecfmt, t.name)
 			if(m):
-				proclist = dict()
-				for ps in m.group('ps').split(','):
+				parts, msg = 1, m.group('ps')
+				m = re.match(tp.procmultifmt, msg)
+				if(m):
+					parts, msg = int(m.group('n')), m.group('ps')
+					if tp.multiproccnt == 0:
+						tp.multiproctime = t.time
+						tp.multiproclist = dict()
+					proclist = tp.multiproclist
+					tp.multiproccnt += 1
+				else:
+					proclist = dict()
+					tp.multiproccnt = 0
+				for ps in msg.split(','):
 					val = ps.split()
-					if not val:
+					if not val or len(val) != 2:
 						continue
 					name = val[0].replace('--', '-')
 					proclist[name] = int(val[1])
-				data.pstl[t.time] = proclist
+				if parts == 1:
+					data.pstl[t.time] = proclist
+				elif parts == tp.multiproccnt:
+					data.pstl[tp.multiproctime] = proclist
+					tp.multiproccnt = 0
 				continue
 		# find the end of resume
 		if(t.endMarker()):
@@ -3545,7 +3696,6 @@  def parseTraceLog(live=False):
 				testrun.ftemp[key].append(FTraceCallGraph(pid, sysvals))
 			if(res == -1):
 				testrun.ftemp[key][-1].addLine(t)
-	tf.close()
 	if len(testdata) < 1:
 		sysvals.vprint('WARNING: ftrace start marker is missing')
 	if data and not data.devicegroups:
@@ -3667,7 +3817,13 @@  def parseTraceLog(live=False):
 			if p not in data.dmesg:
 				if not terr:
 					ph = p if 'machine' in p else lp
-					terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
+					if p == 'suspend_machine':
+						sm = sysvals.suspendmode
+						if sm in suspendmodename:
+							sm = suspendmodename[sm]
+						terr = 'test%s did not enter %s power mode' % (tn, sm)
+					else:
+						terr = '%s%s failed in %s phase' % (sysvals.suspendmode, tn, ph)
 					pprint('TEST%s FAILED: %s' % (tn, terr))
 					error.append(terr)
 					if data.tSuspended == 0:
@@ -3708,9 +3864,7 @@  def parseTraceLog(live=False):
 
 # Function: loadKernelLog
 # Description:
-#	 [deprecated for kernel 3.15.0 or newer]
 #	 load the dmesg file into memory and fix up any ordering issues
-#	 The dmesg filename is taken from sysvals
 # Output:
 #	 An array of empty Data objects with only their dmesgtext attributes set
 def loadKernelLog():
@@ -3736,7 +3890,8 @@  def loadKernelLog():
 		if(not m):
 			continue
 		msg = m.group("msg")
-		if(re.match('PM: Syncing filesystems.*', msg)):
+		if re.match('PM: Syncing filesystems.*', msg) or \
+			re.match('PM: suspend entry.*', msg):
 			if(data):
 				testruns.append(data)
 			data = Data(len(testruns))
@@ -3747,11 +3902,17 @@  def loadKernelLog():
 		if(m):
 			sysvals.stamp['kernel'] = m.group('k')
 		m = re.match('PM: Preparing system for (?P<m>.*) sleep', msg)
-		if(m):
+		if not m:
+			m = re.match('PM: Preparing system for sleep \((?P<m>.*)\)', msg)
+		if m:
 			sysvals.stamp['mode'] = sysvals.suspendmode = m.group('m')
 		data.dmesgtext.append(line)
 	lf.close()
 
+	if sysvals.suspendmode == 's2idle':
+		sysvals.suspendmode = 'freeze'
+	elif sysvals.suspendmode == 'deep':
+		sysvals.suspendmode = 'mem'
 	if data:
 		testruns.append(data)
 	if len(testruns) < 1:
@@ -3762,12 +3923,9 @@  def loadKernelLog():
 	for data in testruns:
 		last = ''
 		for line in data.dmesgtext:
-			mc = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) calling  '+\
-				'(?P<f>.*)\+ @ .*, parent: .*', line)
-			mr = re.match('.*(\[ *)(?P<t>[0-9\.]*)(\]) call '+\
-				'(?P<f>.*)\+ returned .* after (?P<dt>.*) usecs', last)
-			if(mc and mr and (mc.group('t') == mr.group('t')) and
-				(mc.group('f') == mr.group('f'))):
+			ct, cf, n, p = data.initcall_debug_call(line)
+			rt, rf, l = data.initcall_debug_return(last)
+			if ct and rt and ct == rt and cf == rf:
 				i = data.dmesgtext.index(last)
 				j = data.dmesgtext.index(line)
 				data.dmesgtext[i] = line
@@ -3777,7 +3935,6 @@  def loadKernelLog():
 
 # Function: parseKernelLog
 # Description:
-#	 [deprecated for kernel 3.15.0 or newer]
 #	 Analyse a dmesg log output file generated from this app during
 #	 the execution phase. Create a set of device structures in memory
 #	 for subsequent formatting in the html output file
@@ -3796,30 +3953,30 @@  def parseKernelLog(data):
 
 	# dmesg phase match table
 	dm = {
-		'suspend_prepare': ['PM: Syncing filesystems.*'],
-		        'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*'],
-		   'suspend_late': ['PM: suspend of devices complete after.*'],
-		  'suspend_noirq': ['PM: late suspend of devices complete after.*'],
-		'suspend_machine': ['PM: noirq suspend of devices complete after.*'],
-		 'resume_machine': ['ACPI: Low-level resume complete.*'],
-		   'resume_noirq': ['ACPI: Waking up from system sleep state.*'],
-		   'resume_early': ['PM: noirq resume of devices complete after.*'],
-		         'resume': ['PM: early resume of devices complete after.*'],
-		'resume_complete': ['PM: resume of devices complete after.*'],
+		'suspend_prepare': ['PM: Syncing filesystems.*', 'PM: suspend entry.*'],
+		        'suspend': ['PM: Entering [a-z]* sleep.*', 'Suspending console.*',
+		                    'PM: Suspending system .*'],
+		   'suspend_late': ['PM: suspend of devices complete after.*',
+							'PM: freeze of devices complete after.*'],
+		  'suspend_noirq': ['PM: late suspend of devices complete after.*',
+							'PM: late freeze of devices complete after.*'],
+		'suspend_machine': ['PM: suspend-to-idle',
+							'PM: noirq suspend of devices complete after.*',
+							'PM: noirq freeze of devices complete after.*'],
+		 'resume_machine': ['PM: Timekeeping suspended for.*',
+							'ACPI: Low-level resume complete.*',
+							'ACPI: resume from mwait',
+							'Suspended for [0-9\.]* seconds'],
+		   'resume_noirq': ['PM: resume from suspend-to-idle',
+							'ACPI: Waking up from system sleep state.*'],
+		   'resume_early': ['PM: noirq resume of devices complete after.*',
+							'PM: noirq restore of devices complete after.*'],
+		         'resume': ['PM: early resume of devices complete after.*',
+							'PM: early restore of devices complete after.*'],
+		'resume_complete': ['PM: resume of devices complete after.*',
+							'PM: restore of devices complete after.*'],
 		    'post_resume': ['.*Restarting tasks \.\.\..*'],
 	}
-	if(sysvals.suspendmode == 'standby'):
-		dm['resume_machine'] = ['PM: Restoring platform NVS memory']
-	elif(sysvals.suspendmode == 'disk'):
-		dm['suspend_late'] = ['PM: freeze of devices complete after.*']
-		dm['suspend_noirq'] = ['PM: late freeze of devices complete after.*']
-		dm['suspend_machine'] = ['PM: noirq freeze of devices complete after.*']
-		dm['resume_machine'] = ['PM: Restoring platform NVS memory']
-		dm['resume_early'] = ['PM: noirq restore of devices complete after.*']
-		dm['resume'] = ['PM: early restore of devices complete after.*']
-		dm['resume_complete'] = ['PM: restore of devices complete after.*']
-	elif(sysvals.suspendmode == 'freeze'):
-		dm['resume_machine'] = ['ACPI: resume from mwait']
 
 	# action table (expected events that occur and show up in dmesg)
 	at = {
@@ -3867,12 +4024,13 @@  def parseKernelLog(data):
 			for s in dm[p]:
 				if(re.match(s, msg)):
 					phasechange, phase = True, p
+					dm[p] = [s]
 					break
 
 		# hack for determining resume_machine end for freeze
 		if(not sysvals.usetraceevents and sysvals.suspendmode == 'freeze' \
 			and phase == 'resume_machine' and \
-			re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
+			data.initcall_debug_call(line, True)):
 			data.setPhase(phase, ktime, False)
 			phase = 'resume_noirq'
 			data.setPhase(phase, ktime, True)
@@ -3945,26 +4103,18 @@  def parseKernelLog(data):
 		# -- device callbacks --
 		if(phase in data.sortedPhases()):
 			# device init call
-			if(re.match('calling  (?P<f>.*)\+ @ .*, parent: .*', msg)):
-				sm = re.match('calling  (?P<f>.*)\+ @ '+\
-					'(?P<n>.*), parent: (?P<p>.*)', msg);
-				f = sm.group('f')
-				n = sm.group('n')
-				p = sm.group('p')
-				if(f and n and p):
-					data.newAction(phase, f, int(n), p, ktime, -1, '')
-			# device init return
-			elif(re.match('call (?P<f>.*)\+ returned .* after '+\
-				'(?P<t>.*) usecs', msg)):
-				sm = re.match('call (?P<f>.*)\+ returned .* after '+\
-					'(?P<t>.*) usecs(?P<a>.*)', msg);
-				f = sm.group('f')
-				t = sm.group('t')
-				list = data.dmesg[phase]['list']
-				if(f in list):
-					dev = list[f]
-					dev['length'] = int(t)
-					dev['end'] = ktime
+			t, f, n, p = data.initcall_debug_call(line)
+			if t and f and n and p:
+				data.newAction(phase, f, int(n), p, ktime, -1, '')
+			else:
+				# device init return
+				t, f, l = data.initcall_debug_return(line)
+				if t and f and l:
+					list = data.dmesg[phase]['list']
+					if(f in list):
+						dev = list[f]
+						dev['length'] = int(l)
+						dev['end'] = ktime
 
 		# if trace events are not available, these are better than nothing
 		if(not sysvals.usetraceevents):
@@ -4006,6 +4156,8 @@  def parseKernelLog(data):
 	# fill in any missing phases
 	phasedef = data.phasedef
 	terr, lp = '', 'suspend_prepare'
+	if lp not in data.dmesg:
+		doError('dmesg log format has changed, could not find start of suspend')
 	for p in sorted(phasedef, key=lambda k:phasedef[k]['order']):
 		if p not in data.dmesg:
 			if not terr:
@@ -5302,7 +5454,7 @@  def executeSuspend(quiet=False):
 	sv.dlog('read dmesg')
 	sv.initdmesg()
 	# start ftrace
-	if(sv.usecallgraph or sv.usetraceevents):
+	if sv.useftrace:
 		if not quiet:
 			pprint('START TRACING')
 		sv.dlog('start ftrace tracing')
@@ -5334,8 +5486,7 @@  def executeSuspend(quiet=False):
 			sv.dlog('enable RTC wake alarm')
 			sv.rtcWakeAlarmOn()
 		# start of suspend trace marker
-		if(sv.usecallgraph or sv.usetraceevents):
-			sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
+		sv.fsetVal(datetime.now().strftime(sv.tmstart), 'trace_marker')
 		# predelay delay
 		if(count == 1 and sv.predelay > 0):
 			sv.fsetVal('WAIT %d' % sv.predelay, 'trace_marker')
@@ -5384,11 +5535,17 @@  def executeSuspend(quiet=False):
 			sv.fsetVal('WAIT END', 'trace_marker')
 		# return from suspend
 		pprint('RESUME COMPLETE')
-		if(sv.usecallgraph or sv.usetraceevents):
-			sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
+		sv.fsetVal(datetime.now().strftime(sv.tmend), 'trace_marker')
 		if sv.wifi and wifi:
 			tdata['wifi'] = sv.pollWifi(wifi)
 			sv.dlog('wifi check, %s' % tdata['wifi'])
+			if sv.netfix:
+				netfixout = sv.netfixon('wired')
+		elif sv.netfix:
+			netfixout = sv.netfixon()
+		if sv.netfix and netfixout:
+			tdata['netfix'] = netfixout
+			sv.dlog('netfix, %s' % tdata['netfix'])
 		if(sv.suspendmode == 'mem' or sv.suspendmode == 'command'):
 			sv.dlog('read the ACPI FPDT')
 			tdata['fw'] = getFPDT(False)
@@ -5396,7 +5553,7 @@  def executeSuspend(quiet=False):
 	sv.dlog('run the cmdinfo list after')
 	cmdafter = sv.cmdinfo(False)
 	# stop ftrace
-	if(sv.usecallgraph or sv.usetraceevents):
+	if sv.useftrace:
 		if sv.useprocmon:
 			sv.dlog('stop the process monitor')
 			pm.stop()
@@ -5407,7 +5564,7 @@  def executeSuspend(quiet=False):
 	sysvals.dlog('EXECUTION TRACE END')
 	sv.getdmesg(testdata)
 	# grab a copy of the ftrace output
-	if(sv.usecallgraph or sv.usetraceevents):
+	if sv.useftrace:
 		if not quiet:
 			pprint('CAPTURING TRACE')
 		op = sv.writeDatafileHeader(sv.ftracefile, testdata)
@@ -5838,13 +5995,19 @@  def statusCheck(probecheck=False):
 			pprint('      please choose one with -m')
 
 	# check if ftrace is available
-	res = sysvals.colorText('NO')
-	ftgood = sysvals.verifyFtrace()
-	if(ftgood):
-		res = 'YES'
-	elif(sysvals.usecallgraph):
-		status = 'ftrace is not properly supported'
-	pprint('    is ftrace supported: %s' % res)
+	if sysvals.useftrace:
+		res = sysvals.colorText('NO')
+		sysvals.useftrace = sysvals.verifyFtrace()
+		efmt = '"{0}" uses ftrace, and it is not properly supported'
+		if sysvals.useftrace:
+			res = 'YES'
+		elif sysvals.usecallgraph:
+			status = efmt.format('-f')
+		elif sysvals.usedevsrc:
+			status = efmt.format('-dev')
+		elif sysvals.useprocmon:
+			status = efmt.format('-proc')
+		pprint('    is ftrace supported: %s' % res)
 
 	# check if kprobes are available
 	if sysvals.usekprobes:
@@ -5857,8 +6020,8 @@  def statusCheck(probecheck=False):
 		pprint('    are kprobes supported: %s' % res)
 
 	# what data source are we using
-	res = 'DMESG'
-	if(ftgood):
+	res = 'DMESG (very limited, ftrace is preferred)'
+	if sysvals.useftrace:
 		sysvals.usetraceevents = True
 		for e in sysvals.traceevents:
 			if not os.path.exists(sysvals.epath+e):
@@ -5879,7 +6042,7 @@  def statusCheck(probecheck=False):
 	pprint('    optional commands this tool may use for info:')
 	no = sysvals.colorText('MISSING')
 	yes = sysvals.colorText('FOUND', 32)
-	for c in ['turbostat', 'mcelog', 'lspci', 'lsusb']:
+	for c in ['turbostat', 'mcelog', 'lspci', 'lsusb', 'netfix']:
 		if c == 'turbostat':
 			res = yes if sysvals.haveTurbostat() else no
 		else:
@@ -5971,7 +6134,7 @@  def processData(live=False, quiet=False):
 	if not sysvals.stamp:
 		pprint('ERROR: data does not include the expected stamp')
 		return (testruns, {'error': 'timeline generation failed'})
-	shown = ['bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
+	shown = ['os', 'bios', 'biosdate', 'cpu', 'host', 'kernel', 'man', 'memfr',
 			'memsz', 'mode', 'numcpu', 'plat', 'time', 'wifi']
 	sysvals.vprint('System Info:')
 	for key in sorted(sysvals.stamp):
@@ -6052,6 +6215,8 @@  def runTest(n=0, quiet=False):
 		if sysvals.display:
 			ret = sysvals.displayControl('init')
 			sysvals.dlog('xset display init, ret = %d' % ret)
+	sysvals.testVal(sysvals.pmdpath, 'basic', '1')
+	sysvals.testVal(sysvals.s0ixpath, 'basic', 'Y')
 	sysvals.dlog('initialize ftrace')
 	sysvals.initFtrace(quiet)
 
@@ -6145,9 +6310,12 @@  def data_from_html(file, outpath, issues, fulldetail=False):
 				elist[err[0]] += 1
 		for i in elist:
 			ilist.append('%sx%d' % (i, elist[i]) if elist[i] > 1 else i)
-	wifi = find_in_html(html, 'Wifi Resume: ', '</td>')
-	if wifi:
-		extra['wifi'] = wifi
+		line = find_in_html(log, '# wifi ', '\n')
+		if line:
+			extra['wifi'] = line
+		line = find_in_html(log, '# netfix ', '\n')
+		if line:
+			extra['netfix'] = line
 	low = find_in_html(html, 'freeze time: <b>', ' ms</b>')
 	for lowstr in ['waking', '+']:
 		if not low:
@@ -6243,7 +6411,7 @@  def genHtml(subdir, force=False):
 					sysvals.ftracefile = file
 		sysvals.setOutputFile()
 		if (sysvals.dmesgfile or sysvals.ftracefile) and sysvals.htmlfile and \
-			(force or not sysvals.usable(sysvals.htmlfile)):
+			(force or not sysvals.usable(sysvals.htmlfile, True)):
 			pprint('FTRACE: %s' % sysvals.ftracefile)
 			if sysvals.dmesgfile:
 				pprint('DMESG : %s' % sysvals.dmesgfile)
@@ -6533,6 +6701,7 @@  def printHelp():
 	'   -skiphtml    Run the test and capture the trace logs, but skip the timeline (default: disabled)\n'\
 	'   -result fn   Export a results table to a text file for parsing.\n'\
 	'   -wifi        If a wifi connection is available, check that it reconnects after resume.\n'\
+	'   -netfix      Use netfix to reset the network in the event it fails to resume.\n'\
 	'  [testprep]\n'\
 	'   -sync        Sync the filesystems before starting the test\n'\
 	'   -rs on/off   Enable/disable runtime suspend for all devices, restore all after test\n'\
@@ -6615,6 +6784,8 @@  if __name__ == '__main__':
 		elif(arg == '-v'):
 			pprint("Version %s" % sysvals.version)
 			sys.exit(0)
+		elif(arg == '-debugtiming'):
+			debugtiming = True
 		elif(arg == '-x2'):
 			sysvals.execcount = 2
 		elif(arg == '-x2delay'):
@@ -6657,6 +6828,8 @@  if __name__ == '__main__':
 			sysvals.sync = True
 		elif(arg == '-wifi'):
 			sysvals.wifi = True
+		elif(arg == '-netfix'):
+			sysvals.netfix = True
 		elif(arg == '-gzip'):
 			sysvals.gzip = True
 		elif(arg == '-info'):
@@ -6819,7 +6992,7 @@  if __name__ == '__main__':
 			sysvals.outdir = val
 			sysvals.notestrun = True
 			if(os.path.isdir(val) == False):
-				doError('%s is not accessible' % val)
+				doError('%s is not accesible' % val)
 		elif(arg == '-filter'):
 			try:
 				val = next(args)
@@ -6942,12 +7115,11 @@  if __name__ == '__main__':
 				time.sleep(sysvals.multitest['delay'])
 			fmt = 'suspend-%y%m%d-%H%M%S'
 			sysvals.testdir = os.path.join(sysvals.outdir, datetime.now().strftime(fmt))
-			ret = runTest(i+1, True)
+			ret = runTest(i+1, not sysvals.verbose)
 			failcnt = 0 if not ret else failcnt + 1
 			if sysvals.maxfail > 0 and failcnt >= sysvals.maxfail:
 				pprint('Maximum fail count of %d reached, aborting multitest' % (sysvals.maxfail))
 				break
-			time.sleep(5)
 			sysvals.resetlog()
 			sysvals.multistat(False, i, finish)
 			if 'time' in sysvals.multitest and datetime.now() >= finish: