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