analyze_suspend.py 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447
  1. #!/usr/bin/python
  2. #
  3. # Tool for analyzing suspend/resume timing
  4. # Copyright (c) 2013, Intel Corporation.
  5. #
  6. # This program is free software; you can redistribute it and/or modify it
  7. # under the terms and conditions of the GNU General Public License,
  8. # version 2, as published by the Free Software Foundation.
  9. #
  10. # This program is distributed in the hope it will be useful, but WITHOUT
  11. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  13. # more details.
  14. #
  15. # You should have received a copy of the GNU General Public License along with
  16. # this program; if not, write to the Free Software Foundation, Inc.,
  17. # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
  18. #
  19. # Authors:
  20. # Todd Brandt <todd.e.brandt@linux.intel.com>
  21. #
  22. # Description:
  23. # This tool is designed to assist kernel and OS developers in optimizing
  24. # their linux stack's suspend/resume time. Using a kernel image built
  25. # with a few extra options enabled, the tool will execute a suspend and
  26. # will capture dmesg and ftrace data until resume is complete. This data
  27. # is transformed into a device timeline and a callgraph to give a quick
  28. # and detailed view of which devices and callbacks are taking the most
  29. # time in suspend/resume. The output is a single html file which can be
  30. # viewed in firefox or chrome.
  31. #
  32. # The following kernel build options are required:
  33. # CONFIG_PM_DEBUG=y
  34. # CONFIG_PM_SLEEP_DEBUG=y
  35. # CONFIG_FTRACE=y
  36. # CONFIG_FUNCTION_TRACER=y
  37. # CONFIG_FUNCTION_GRAPH_TRACER=y
  38. #
  39. # The following additional kernel parameters are required:
  40. # (e.g. in file /etc/default/grub)
  41. # GRUB_CMDLINE_LINUX_DEFAULT="... initcall_debug log_buf_len=16M ..."
  42. #
  43. import sys
  44. import time
  45. import os
  46. import string
  47. import re
  48. import array
  49. import platform
  50. import datetime
  51. import struct
  52. # -- classes --
  53. class SystemValues:
  54. testdir = "."
  55. tpath = "/sys/kernel/debug/tracing/"
  56. mempath = "/dev/mem"
  57. powerfile = "/sys/power/state"
  58. suspendmode = "mem"
  59. prefix = "test"
  60. teststamp = ""
  61. dmesgfile = ""
  62. ftracefile = ""
  63. htmlfile = ""
  64. rtcwake = False
  65. def setOutputFile(self):
  66. if((self.htmlfile == "") and (self.dmesgfile != "")):
  67. m = re.match(r"(?P<name>.*)_dmesg\.txt$", self.dmesgfile)
  68. if(m):
  69. self.htmlfile = m.group("name")+".html"
  70. if((self.htmlfile == "") and (self.ftracefile != "")):
  71. m = re.match(r"(?P<name>.*)_ftrace\.txt$", self.ftracefile)
  72. if(m):
  73. self.htmlfile = m.group("name")+".html"
  74. if(self.htmlfile == ""):
  75. self.htmlfile = "output.html"
  76. def initTestOutput(self):
  77. hostname = platform.node()
  78. if(hostname != ""):
  79. self.prefix = hostname
  80. v = os.popen("cat /proc/version").read().strip()
  81. kver = string.split(v)[2]
  82. self.testdir = os.popen("date \"+suspend-%m%d%y-%H%M%S\"").read().strip()
  83. self.teststamp = "# "+self.testdir+" "+self.prefix+" "+self.suspendmode+" "+kver
  84. self.dmesgfile = self.testdir+"/"+self.prefix+"_"+self.suspendmode+"_dmesg.txt"
  85. self.ftracefile = self.testdir+"/"+self.prefix+"_"+self.suspendmode+"_ftrace.txt"
  86. self.htmlfile = self.testdir+"/"+self.prefix+"_"+self.suspendmode+".html"
  87. os.mkdir(self.testdir)
  88. class Data:
  89. altdevname = dict()
  90. usedmesg = False
  91. useftrace = False
  92. notestrun = False
  93. verbose = False
  94. phases = []
  95. dmesg = {} # root data structure
  96. start = 0.0
  97. end = 0.0
  98. stamp = {'time': "", 'host': "", 'mode': ""}
  99. id = 0
  100. tSuspended = 0.0
  101. fwValid = False
  102. fwSuspend = 0
  103. fwResume = 0
  104. def initialize(self):
  105. self.dmesg = { # dmesg log data
  106. 'suspend_general': {'list': dict(), 'start': -1.0, 'end': -1.0,
  107. 'row': 0, 'color': "#CCFFCC", 'order': 0},
  108. 'suspend_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
  109. 'row': 0, 'color': "green", 'order': 1},
  110. 'suspend_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
  111. 'row': 0, 'color': "#00FFFF", 'order': 2},
  112. 'suspend_cpu': {'list': dict(), 'start': -1.0, 'end': -1.0,
  113. 'row': 0, 'color': "blue", 'order': 3},
  114. 'resume_cpu': {'list': dict(), 'start': -1.0, 'end': -1.0,
  115. 'row': 0, 'color': "red", 'order': 4},
  116. 'resume_noirq': {'list': dict(), 'start': -1.0, 'end': -1.0,
  117. 'row': 0, 'color': "orange", 'order': 5},
  118. 'resume_early': {'list': dict(), 'start': -1.0, 'end': -1.0,
  119. 'row': 0, 'color': "yellow", 'order': 6},
  120. 'resume_general': {'list': dict(), 'start': -1.0, 'end': -1.0,
  121. 'row': 0, 'color': "#FFFFCC", 'order': 7}
  122. }
  123. self.phases = self.sortedPhases()
  124. def normalizeTime(self):
  125. tSus = tRes = self.tSuspended
  126. if self.fwValid:
  127. tSus -= -self.fwSuspend / 1000000000.0
  128. tRes -= self.fwResume / 1000000000.0
  129. self.tSuspended = 0.0
  130. self.start -= tSus
  131. self.end -= tRes
  132. for phase in self.phases:
  133. zero = tRes
  134. if "suspend" in phase:
  135. zero = tSus
  136. p = self.dmesg[phase]
  137. p['start'] -= zero
  138. p['end'] -= zero
  139. list = p['list']
  140. for name in list:
  141. d = list[name]
  142. d['start'] -= zero
  143. d['end'] -= zero
  144. if('ftrace' in d):
  145. cg = d['ftrace']
  146. cg.start -= zero
  147. cg.end -= zero
  148. for line in cg.list:
  149. line.time -= zero
  150. if self.fwValid:
  151. fws = -self.fwSuspend / 1000000000.0
  152. fwr = self.fwResume / 1000000000.0
  153. list = dict()
  154. self.id += 1
  155. devid = "dc%d" % self.id
  156. list["firmware-suspend"] = \
  157. {'start': fws, 'end': 0, 'pid': 0, 'par': "",
  158. 'length': -fws, 'row': 0, 'id': devid };
  159. self.id += 1
  160. devid = "dc%d" % self.id
  161. list["firmware-resume"] = \
  162. {'start': 0, 'end': fwr, 'pid': 0, 'par': "",
  163. 'length': fwr, 'row': 0, 'id': devid };
  164. self.dmesg['BIOS'] = \
  165. {'list': list, 'start': fws, 'end': fwr,
  166. 'row': 0, 'color': "purple", 'order': 4}
  167. self.dmesg['resume_cpu']['order'] += 1
  168. self.dmesg['resume_noirq']['order'] += 1
  169. self.dmesg['resume_early']['order'] += 1
  170. self.dmesg['resume_general']['order'] += 1
  171. self.phases = self.sortedPhases()
  172. def vprint(self, msg):
  173. if(self.verbose):
  174. print(msg)
  175. def dmesgSortVal(self, phase):
  176. return self.dmesg[phase]['order']
  177. def sortedPhases(self):
  178. return sorted(self.dmesg, key=self.dmesgSortVal)
  179. def sortedDevices(self, phase):
  180. list = self.dmesg[phase]['list']
  181. slist = []
  182. tmp = dict()
  183. for devname in list:
  184. dev = list[devname]
  185. tmp[dev['start']] = devname
  186. for t in sorted(tmp):
  187. slist.append(tmp[t])
  188. return slist
  189. def fixupInitcalls(self, phase, end):
  190. # if any calls never returned, clip them at system resume end
  191. phaselist = self.dmesg[phase]['list']
  192. for devname in phaselist:
  193. dev = phaselist[devname]
  194. if(dev['end'] < 0):
  195. dev['end'] = end
  196. self.vprint("%s (%s): callback didn't return" % (devname, phase))
  197. def fixupInitcallsThatDidntReturn(self):
  198. # if any calls never returned, clip them at system resume end
  199. for phase in self.phases:
  200. self.fixupInitcalls(phase, self.dmesg['resume_general']['end'])
  201. if(phase == "resume_general"):
  202. break
  203. def newAction(self, phase, name, pid, parent, start, end):
  204. self.id += 1
  205. devid = "dc%d" % self.id
  206. list = self.dmesg[phase]['list']
  207. length = -1.0
  208. if(start >= 0 and end >= 0):
  209. length = end - start
  210. list[name] = {'start': start, 'end': end, 'pid': pid, 'par': parent,
  211. 'length': length, 'row': 0, 'id': devid }
  212. def deviceIDs(self, devlist, phase):
  213. idlist = []
  214. for p in self.phases:
  215. if(p[0] != phase[0]):
  216. continue
  217. list = data.dmesg[p]['list']
  218. for devname in list:
  219. if devname in devlist:
  220. idlist.append(list[devname]['id'])
  221. return idlist
  222. def deviceParentID(self, devname, phase):
  223. pdev = ""
  224. pdevid = ""
  225. for p in self.phases:
  226. if(p[0] != phase[0]):
  227. continue
  228. list = data.dmesg[p]['list']
  229. if devname in list:
  230. pdev = list[devname]['par']
  231. for p in self.phases:
  232. if(p[0] != phase[0]):
  233. continue
  234. list = data.dmesg[p]['list']
  235. if pdev in list:
  236. return list[pdev]['id']
  237. return pdev
  238. def deviceChildrenIDs(self, devname, phase):
  239. devlist = []
  240. for p in self.phases:
  241. if(p[0] != phase[0]):
  242. continue
  243. list = data.dmesg[p]['list']
  244. for child in list:
  245. if(list[child]['par'] == devname):
  246. devlist.append(child)
  247. return self.deviceIDs(devlist, phase)
  248. class FTraceLine:
  249. time = 0.0
  250. length = 0.0
  251. fcall = False
  252. freturn = False
  253. fevent = False
  254. depth = 0
  255. name = ""
  256. def __init__(self, t, m, d):
  257. self.time = float(t)
  258. # check to see if this is a trace event
  259. em = re.match(r"^ *\/\* *(?P<msg>.*) \*\/ *$", m)
  260. if(em):
  261. self.name = em.group("msg")
  262. self.fevent = True
  263. return
  264. # convert the duration to seconds
  265. if(d):
  266. self.length = float(d)/1000000
  267. # the indentation determines the depth
  268. match = re.match(r"^(?P<d> *)(?P<o>.*)$", m)
  269. if(not match):
  270. return
  271. self.depth = self.getDepth(match.group('d'))
  272. m = match.group('o')
  273. # function return
  274. if(m[0] == '}'):
  275. self.freturn = True
  276. if(len(m) > 1):
  277. # includes comment with function name
  278. match = re.match(r"^} *\/\* *(?P<n>.*) *\*\/$", m)
  279. if(match):
  280. self.name = match.group('n')
  281. # function call
  282. else:
  283. self.fcall = True
  284. # function call with children
  285. if(m[-1] == '{'):
  286. match = re.match(r"^(?P<n>.*) *\(.*", m)
  287. if(match):
  288. self.name = match.group('n')
  289. # function call with no children (leaf)
  290. elif(m[-1] == ';'):
  291. self.freturn = True
  292. match = re.match(r"^(?P<n>.*) *\(.*", m)
  293. if(match):
  294. self.name = match.group('n')
  295. # something else (possibly a trace marker)
  296. else:
  297. self.name = m
  298. def getDepth(self, str):
  299. return len(str)/2
  300. class FTraceCallGraph:
  301. start = -1.0
  302. end = -1.0
  303. list = []
  304. invalid = False
  305. depth = 0
  306. def __init__(self):
  307. self.start = -1.0
  308. self.end = -1.0
  309. self.list = []
  310. self.depth = 0
  311. def setDepth(self, line):
  312. if(line.fcall and not line.freturn):
  313. line.depth = self.depth
  314. self.depth += 1
  315. elif(line.freturn and not line.fcall):
  316. self.depth -= 1
  317. line.depth = self.depth
  318. else:
  319. line.depth = self.depth
  320. def addLine(self, line, match):
  321. if(not self.invalid):
  322. self.setDepth(line)
  323. if(line.depth == 0 and line.freturn):
  324. self.end = line.time
  325. self.list.append(line)
  326. return True
  327. if(self.invalid):
  328. return False
  329. if(len(self.list) >= 1000000 or self.depth < 0):
  330. first = self.list[0]
  331. self.list = []
  332. self.list.append(first)
  333. self.invalid = True
  334. id = "task %s cpu %s" % (match.group("pid"), match.group("cpu"))
  335. window = "(%f - %f)" % (self.start, line.time)
  336. data.vprint("Too much data for "+id+" "+window+", ignoring this callback")
  337. return False
  338. self.list.append(line)
  339. if(self.start < 0):
  340. self.start = line.time
  341. return False
  342. def sanityCheck(self):
  343. stack = dict()
  344. cnt = 0
  345. for l in self.list:
  346. if(l.fcall and not l.freturn):
  347. stack[l.depth] = l
  348. cnt += 1
  349. elif(l.freturn and not l.fcall):
  350. if(not stack[l.depth]):
  351. return False
  352. stack[l.depth].length = l.length
  353. stack[l.depth] = 0
  354. l.length = 0
  355. cnt -= 1
  356. if(cnt == 0):
  357. return True
  358. return False
  359. def debugPrint(self, filename):
  360. if(filename == "stdout"):
  361. print("[%f - %f]") % (self.start, self.end)
  362. for l in self.list:
  363. if(l.freturn and l.fcall):
  364. print("%f (%02d): %s(); (%.3f us)" % (l.time, l.depth, l.name, l.length*1000000))
  365. elif(l.freturn):
  366. print("%f (%02d): %s} (%.3f us)" % (l.time, l.depth, l.name, l.length*1000000))
  367. else:
  368. print("%f (%02d): %s() { (%.3f us)" % (l.time, l.depth, l.name, l.length*1000000))
  369. print(" ")
  370. else:
  371. fp = open(filename, 'w')
  372. print(filename)
  373. for l in self.list:
  374. if(l.freturn and l.fcall):
  375. fp.write("%f (%02d): %s(); (%.3f us)\n" % (l.time, l.depth, l.name, l.length*1000000))
  376. elif(l.freturn):
  377. fp.write("%f (%02d): %s} (%.3f us)\n" % (l.time, l.depth, l.name, l.length*1000000))
  378. else:
  379. fp.write("%f (%02d): %s() { (%.3f us)\n" % (l.time, l.depth, l.name, l.length*1000000))
  380. fp.close()
  381. class Timeline:
  382. html = {}
  383. scaleH = 0.0 # height of the timescale row as a percent of the timeline height
  384. rowH = 0.0 # height of each row in percent of the timeline height
  385. row_height_pixels = 30
  386. maxrows = 0
  387. height = 0
  388. def __init__(self):
  389. self.html = {
  390. 'timeline': "",
  391. 'legend': "",
  392. 'scale': ""
  393. }
  394. def setRows(self, rows):
  395. self.maxrows = int(rows)
  396. self.scaleH = 100.0/float(self.maxrows)
  397. self.height = self.maxrows*self.row_height_pixels
  398. r = float(self.maxrows - 1)
  399. if(r < 1.0):
  400. r = 1.0
  401. self.rowH = (100.0 - self.scaleH)/r
  402. # -- global objects --
  403. sysvals = SystemValues()
  404. data = Data()
  405. # -- functions --
  406. # Function: initFtrace
  407. # Description:
  408. # Configure ftrace to capture a function trace during suspend/resume
  409. def initFtrace():
  410. global sysvals
  411. print("INITIALIZING FTRACE...")
  412. # turn trace off
  413. os.system("echo 0 > "+sysvals.tpath+"tracing_on")
  414. # set the trace clock to global
  415. os.system("echo global > "+sysvals.tpath+"trace_clock")
  416. # set trace buffer to a huge value
  417. os.system("echo nop > "+sysvals.tpath+"current_tracer")
  418. os.system("echo 100000 > "+sysvals.tpath+"buffer_size_kb")
  419. # clear the trace buffer
  420. os.system("echo \"\" > "+sysvals.tpath+"trace")
  421. # set trace type
  422. os.system("echo function_graph > "+sysvals.tpath+"current_tracer")
  423. os.system("echo \"\" > "+sysvals.tpath+"set_ftrace_filter")
  424. # set trace format options
  425. os.system("echo funcgraph-abstime > "+sysvals.tpath+"trace_options")
  426. os.system("echo funcgraph-proc > "+sysvals.tpath+"trace_options")
  427. # focus only on device suspend and resume
  428. os.system("cat "+sysvals.tpath+"available_filter_functions | grep dpm_run_callback > "+sysvals.tpath+"set_graph_function")
  429. # Function: verifyFtrace
  430. # Description:
  431. # Check that ftrace is working on the system
  432. def verifyFtrace():
  433. global sysvals
  434. files = ["available_filter_functions", "buffer_size_kb",
  435. "current_tracer", "set_ftrace_filter",
  436. "trace", "trace_marker"]
  437. for f in files:
  438. if(os.path.exists(sysvals.tpath+f) == False):
  439. return False
  440. return True
  441. def parseStamp(line):
  442. global data, sysvals
  443. stampfmt = r"# suspend-(?P<m>[0-9]{2})(?P<d>[0-9]{2})(?P<y>[0-9]{2})-"+\
  444. "(?P<H>[0-9]{2})(?P<M>[0-9]{2})(?P<S>[0-9]{2})"+\
  445. " (?P<host>.*) (?P<mode>.*) (?P<kernel>.*)$"
  446. m = re.match(stampfmt, line)
  447. if(m):
  448. dt = datetime.datetime(int(m.group("y"))+2000, int(m.group("m")),
  449. int(m.group("d")), int(m.group("H")), int(m.group("M")),
  450. int(m.group("S")))
  451. data.stamp['time'] = dt.strftime("%B %d %Y, %I:%M:%S %p")
  452. data.stamp['host'] = m.group("host")
  453. data.stamp['mode'] = m.group("mode")
  454. data.stamp['kernel'] = m.group("kernel")
  455. sysvals.suspendmode = data.stamp['mode']
  456. # Function: analyzeTraceLog
  457. # Description:
  458. # Analyse an ftrace log output file generated from this app during
  459. # the execution phase. Create an "ftrace" structure in memory for
  460. # subsequent formatting in the html output file
  461. def analyzeTraceLog():
  462. global sysvals, data
  463. # the ftrace data is tied to the dmesg data
  464. if(not data.usedmesg):
  465. return
  466. # read through the ftrace and parse the data
  467. data.vprint("Analyzing the ftrace data...")
  468. ftrace_line_fmt = r"^ *(?P<time>[0-9\.]*) *\| *(?P<cpu>[0-9]*)\)"+\
  469. " *(?P<proc>.*)-(?P<pid>[0-9]*) *\|"+\
  470. "[ +!]*(?P<dur>[0-9\.]*) .*\| (?P<msg>.*)"
  471. ftemp = dict()
  472. inthepipe = False
  473. tf = open(sysvals.ftracefile, 'r')
  474. count = 0
  475. for line in tf:
  476. count = count + 1
  477. # grab the time stamp if it's valid
  478. if(count == 1):
  479. parseStamp(line)
  480. continue
  481. # parse only valid lines
  482. m = re.match(ftrace_line_fmt, line)
  483. if(not m):
  484. continue
  485. m_time = m.group("time")
  486. m_pid = m.group("pid")
  487. m_msg = m.group("msg")
  488. m_dur = m.group("dur")
  489. if(m_time and m_pid and m_msg):
  490. t = FTraceLine(m_time, m_msg, m_dur)
  491. pid = int(m_pid)
  492. else:
  493. continue
  494. # the line should be a call, return, or event
  495. if(not t.fcall and not t.freturn and not t.fevent):
  496. continue
  497. # only parse the ftrace data during suspend/resume
  498. if(not inthepipe):
  499. # look for the suspend start marker
  500. if(t.fevent):
  501. if(t.name == "SUSPEND START"):
  502. data.vprint("SUSPEND START %f %s:%d" % (t.time, sysvals.ftracefile, count))
  503. inthepipe = True
  504. continue
  505. else:
  506. # look for the resume end marker
  507. if(t.fevent):
  508. if(t.name == "RESUME COMPLETE"):
  509. data.vprint("RESUME COMPLETE %f %s:%d" % (t.time, sysvals.ftracefile, count))
  510. inthepipe = False
  511. break
  512. continue
  513. # create a callgraph object for the data
  514. if(pid not in ftemp):
  515. ftemp[pid] = FTraceCallGraph()
  516. # when the call is finished, see which device matches it
  517. if(ftemp[pid].addLine(t, m)):
  518. if(not ftemp[pid].sanityCheck()):
  519. id = "task %s cpu %s" % (pid, m.group("cpu"))
  520. data.vprint("Sanity check failed for "+id+", ignoring this callback")
  521. continue
  522. callstart = ftemp[pid].start
  523. callend = ftemp[pid].end
  524. for p in data.phases:
  525. if(data.dmesg[p]['start'] <= callstart and callstart <= data.dmesg[p]['end']):
  526. list = data.dmesg[p]['list']
  527. for devname in list:
  528. dev = list[devname]
  529. if(pid == dev['pid'] and callstart <= dev['start'] and callend >= dev['end']):
  530. data.vprint("%15s [%f - %f] %s(%d)" % (p, callstart, callend, devname, pid))
  531. dev['ftrace'] = ftemp[pid]
  532. break
  533. ftemp[pid] = FTraceCallGraph()
  534. tf.close()
  535. # Function: sortKernelLog
  536. # Description:
  537. # The dmesg output log sometimes comes with with lines that have
  538. # timestamps out of order. This could cause issues since a call
  539. # could accidentally end up in the wrong phase
  540. def sortKernelLog():
  541. global sysvals, data
  542. lf = open(sysvals.dmesgfile, 'r')
  543. dmesglist = []
  544. count = 0
  545. for line in lf:
  546. line = line.replace("\r\n", "")
  547. if(count == 0):
  548. parseStamp(line)
  549. elif(count == 1):
  550. m = re.match(r"# fwsuspend (?P<s>[0-9]*) fwresume (?P<r>[0-9]*)$", line)
  551. if(m):
  552. data.fwSuspend = int(m.group("s"))
  553. data.fwResume = int(m.group("r"))
  554. if(data.fwSuspend > 0 or data.fwResume > 0):
  555. data.fwValid = True
  556. if(re.match(r".*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)", line)):
  557. dmesglist.append(line)
  558. count += 1
  559. lf.close()
  560. last = ""
  561. # fix lines with the same time stamp and function with the call and return swapped
  562. for line in dmesglist:
  563. mc = re.match(r".*(\[ *)(?P<t>[0-9\.]*)(\]) calling (?P<f>.*)\+ @ .*, parent: .*", line)
  564. mr = re.match(r".*(\[ *)(?P<t>[0-9\.]*)(\]) call (?P<f>.*)\+ returned .* after (?P<dt>.*) usecs", last)
  565. if(mc and mr and (mc.group("t") == mr.group("t")) and (mc.group("f") == mr.group("f"))):
  566. i = dmesglist.index(last)
  567. j = dmesglist.index(line)
  568. dmesglist[i] = line
  569. dmesglist[j] = last
  570. last = line
  571. return dmesglist
  572. # Function: analyzeKernelLog
  573. # Description:
  574. # Analyse a dmesg log output file generated from this app during
  575. # the execution phase. Create a set of device structures in memory
  576. # for subsequent formatting in the html output file
  577. def analyzeKernelLog():
  578. global sysvals, data
  579. print("PROCESSING DATA")
  580. data.vprint("Analyzing the dmesg data...")
  581. if(os.path.exists(sysvals.dmesgfile) == False):
  582. print("ERROR: %s doesn't exist") % sysvals.dmesgfile
  583. return False
  584. lf = sortKernelLog()
  585. phase = "suspend_runtime"
  586. dm = {
  587. 'suspend_general': r"PM: Syncing filesystems.*",
  588. 'suspend_early': r"PM: suspend of devices complete after.*",
  589. 'suspend_noirq': r"PM: late suspend of devices complete after.*",
  590. 'suspend_cpu': r"PM: noirq suspend of devices complete after.*",
  591. 'resume_cpu': r"ACPI: Low-level resume complete.*",
  592. 'resume_noirq': r"ACPI: Waking up from system sleep state.*",
  593. 'resume_early': r"PM: noirq resume of devices complete after.*",
  594. 'resume_general': r"PM: early resume of devices complete after.*",
  595. 'resume_complete': r".*Restarting tasks \.\.\..*",
  596. }
  597. if(sysvals.suspendmode == "standby"):
  598. dm['resume_cpu'] = r"PM: Restoring platform NVS memory"
  599. elif(sysvals.suspendmode == "disk"):
  600. dm['suspend_early'] = r"PM: freeze of devices complete after.*"
  601. dm['suspend_noirq'] = r"PM: late freeze of devices complete after.*"
  602. dm['suspend_cpu'] = r"PM: noirq freeze of devices complete after.*"
  603. dm['resume_cpu'] = r"PM: Restoring platform NVS memory"
  604. dm['resume_early'] = r"PM: noirq restore of devices complete after.*"
  605. dm['resume_general'] = r"PM: early restore of devices complete after.*"
  606. action_start = 0.0
  607. for line in lf:
  608. # -- preprocessing --
  609. # parse each dmesg line into the time and message
  610. m = re.match(r".*(\[ *)(?P<ktime>[0-9\.]*)(\]) (?P<msg>.*)", line)
  611. if(m):
  612. ktime = float(m.group("ktime"))
  613. msg = m.group("msg")
  614. else:
  615. print line
  616. continue
  617. # -- phase changes --
  618. # suspend_general start
  619. if(re.match(dm['suspend_general'], msg)):
  620. phase = "suspend_general"
  621. data.dmesg[phase]['start'] = ktime
  622. data.start = ktime
  623. # action start: syncing filesystems
  624. action_start = ktime
  625. # suspend_early start
  626. elif(re.match(dm['suspend_early'], msg)):
  627. data.dmesg["suspend_general"]['end'] = ktime
  628. phase = "suspend_early"
  629. data.dmesg[phase]['start'] = ktime
  630. # suspend_noirq start
  631. elif(re.match(dm['suspend_noirq'], msg)):
  632. data.dmesg["suspend_early"]['end'] = ktime
  633. phase = "suspend_noirq"
  634. data.dmesg[phase]['start'] = ktime
  635. # suspend_cpu start
  636. elif(re.match(dm['suspend_cpu'], msg)):
  637. data.dmesg["suspend_noirq"]['end'] = ktime
  638. phase = "suspend_cpu"
  639. data.dmesg[phase]['start'] = ktime
  640. # resume_cpu start
  641. elif(re.match(dm['resume_cpu'], msg)):
  642. data.tSuspended = ktime
  643. data.dmesg["suspend_cpu"]['end'] = ktime
  644. phase = "resume_cpu"
  645. data.dmesg[phase]['start'] = ktime
  646. # resume_noirq start
  647. elif(re.match(dm['resume_noirq'], msg)):
  648. data.dmesg["resume_cpu"]['end'] = ktime
  649. phase = "resume_noirq"
  650. data.dmesg[phase]['start'] = ktime
  651. # action end: ACPI resume
  652. data.newAction("resume_cpu", "ACPI", -1, "", action_start, ktime)
  653. # resume_early start
  654. elif(re.match(dm['resume_early'], msg)):
  655. data.dmesg["resume_noirq"]['end'] = ktime
  656. phase = "resume_early"
  657. data.dmesg[phase]['start'] = ktime
  658. # resume_general start
  659. elif(re.match(dm['resume_general'], msg)):
  660. data.dmesg["resume_early"]['end'] = ktime
  661. phase = "resume_general"
  662. data.dmesg[phase]['start'] = ktime
  663. # resume complete start
  664. elif(re.match(dm['resume_complete'], msg)):
  665. data.dmesg["resume_general"]['end'] = ktime
  666. data.end = ktime
  667. phase = "resume_runtime"
  668. break
  669. # -- device callbacks --
  670. if(phase in data.phases):
  671. # device init call
  672. if(re.match(r"calling (?P<f>.*)\+ @ .*, parent: .*", msg)):
  673. sm = re.match(r"calling (?P<f>.*)\+ @ (?P<n>.*), parent: (?P<p>.*)", msg);
  674. f = sm.group("f")
  675. n = sm.group("n")
  676. p = sm.group("p")
  677. if(f and n and p):
  678. data.newAction(phase, f, int(n), p, ktime, -1)
  679. # device init return
  680. elif(re.match(r"call (?P<f>.*)\+ returned .* after (?P<t>.*) usecs", msg)):
  681. sm = re.match(r"call (?P<f>.*)\+ returned .* after (?P<t>.*) usecs(?P<a>.*)", msg);
  682. f = sm.group("f")
  683. t = sm.group("t")
  684. list = data.dmesg[phase]['list']
  685. if(f in list):
  686. dev = list[f]
  687. dev['length'] = int(t)
  688. dev['end'] = ktime
  689. data.vprint("%15s [%f - %f] %s(%d) %s" %
  690. (phase, dev['start'], dev['end'], f, dev['pid'], dev['par']))
  691. # -- phase specific actions --
  692. if(phase == "suspend_general"):
  693. if(re.match(r"PM: Preparing system for mem sleep.*", msg)):
  694. data.newAction(phase, "filesystem-sync", -1, "", action_start, ktime)
  695. elif(re.match(r"Freezing user space processes .*", msg)):
  696. action_start = ktime
  697. elif(re.match(r"Freezing remaining freezable tasks.*", msg)):
  698. data.newAction(phase, "freeze-user-processes", -1, "", action_start, ktime)
  699. action_start = ktime
  700. elif(re.match(r"PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*", msg)):
  701. data.newAction(phase, "freeze-tasks", -1, "", action_start, ktime)
  702. elif(phase == "suspend_cpu"):
  703. m = re.match(r"smpboot: CPU (?P<cpu>[0-9]*) is now offline", msg)
  704. if(m):
  705. cpu = "CPU"+m.group("cpu")
  706. data.newAction(phase, cpu, -1, "", action_start, ktime)
  707. action_start = ktime
  708. elif(re.match(r"ACPI: Preparing to enter system sleep state.*", msg)):
  709. action_start = ktime
  710. elif(re.match(r"Disabling non-boot CPUs .*", msg)):
  711. data.newAction(phase, "ACPI", -1, "", action_start, ktime)
  712. action_start = ktime
  713. elif(phase == "resume_cpu"):
  714. m = re.match(r"CPU(?P<cpu>[0-9]*) is up", msg)
  715. if(m):
  716. cpu = "CPU"+m.group("cpu")
  717. data.newAction(phase, cpu, -1, "", action_start, ktime)
  718. action_start = ktime
  719. elif(re.match(r"Enabling non-boot CPUs .*", msg)):
  720. action_start = ktime
  721. # fill in any missing phases
  722. lp = "suspend_general"
  723. for p in data.phases:
  724. if(p == "suspend_general"):
  725. continue
  726. if(data.dmesg[p]['start'] < 0):
  727. data.dmesg[p]['start'] = data.dmesg[lp]['end']
  728. if(p == "resume_cpu"):
  729. data.tSuspended = data.dmesg[lp]['end']
  730. if(data.dmesg[p]['end'] < 0):
  731. data.dmesg[p]['end'] = data.dmesg[p]['start']
  732. lp = p
  733. data.fixupInitcallsThatDidntReturn()
  734. return True
  735. # Function: setTimelineRows
  736. # Description:
  737. # Organize the device or thread lists into the smallest
  738. # number of rows possible, with no entry overlapping
  739. # Arguments:
  740. # list: the list to sort (dmesg or ftrace)
  741. # sortedkeys: sorted key list to use
  742. def setTimelineRows(list, sortedkeys):
  743. global data
  744. # clear all rows and set them to undefined
  745. remaining = len(list)
  746. rowdata = dict()
  747. row = 0
  748. for item in list:
  749. list[item]['row'] = -1
  750. # try to pack each row with as many ranges as possible
  751. while(remaining > 0):
  752. if(row not in rowdata):
  753. rowdata[row] = []
  754. for item in sortedkeys:
  755. if(list[item]['row'] < 0):
  756. s = list[item]['start']
  757. e = list[item]['end']
  758. valid = True
  759. for ritem in rowdata[row]:
  760. rs = ritem['start']
  761. re = ritem['end']
  762. if(not (((s <= rs) and (e <= rs)) or ((s >= re) and (e >= re)))):
  763. valid = False
  764. break
  765. if(valid):
  766. rowdata[row].append(list[item])
  767. list[item]['row'] = row
  768. remaining -= 1
  769. row += 1
  770. return row
  771. # Function: createTimeScale
  772. # Description:
  773. # Create timescale lines for the dmesg and ftrace timelines
  774. # Arguments:
  775. # t0: start time (suspend begin)
  776. # tMax: end time (resume end)
  777. # tSuspend: time when suspend occurs
  778. def createTimeScale(t0, tMax, tSuspended):
  779. global data
  780. timescale = "<div class=\"t\" style=\"right:{0}%\">{1}</div>\n"
  781. output = '<div id="timescale">\n'
  782. # set scale for timeline
  783. tTotal = tMax - t0
  784. tS = 0.1
  785. if(tTotal <= 0):
  786. return output
  787. if(tTotal > 4):
  788. tS = 1
  789. if(tSuspended < 0):
  790. for i in range(int(tTotal/tS)+1):
  791. pos = "%0.3f" % (100 - ((float(i)*tS*100)/tTotal))
  792. if(i > 0):
  793. val = "%0.f" % (float(i)*tS*1000)
  794. else:
  795. val = ""
  796. output += timescale.format(pos, val)
  797. else:
  798. tSuspend = tSuspended - t0
  799. divTotal = int(tTotal/tS) + 1
  800. divSuspend = int(tSuspend/tS)
  801. s0 = (tSuspend - tS*divSuspend)*100/tTotal
  802. for i in range(divTotal):
  803. pos = "%0.3f" % (100 - ((float(i)*tS*100)/tTotal) - s0)
  804. if((i == 0) and (s0 < 3)):
  805. val = ""
  806. elif(i == divSuspend):
  807. val = "S/R"
  808. else:
  809. val = "%0.f" % (float(i-divSuspend)*tS*1000)
  810. output += timescale.format(pos, val)
  811. output += '</div>\n'
  812. return output
  813. # Function: createHTML
  814. # Description:
  815. # Create the output html file.
  816. def createHTML():
  817. global sysvals, data
  818. data.normalizeTime()
  819. # html function templates
  820. headline_stamp = '<div class="stamp">{0} {1} {2} {3}</div>\n'
  821. html_zoombox = '<center><button id="zoomin">ZOOM IN</button><button id="zoomout">ZOOM OUT</button><button id="zoomdef">ZOOM 1:1</button></center>\n<div id="dmesgzoombox" class="zoombox">\n'
  822. html_timeline = '<div id="{0}" class="timeline" style="height:{1}px">\n'
  823. html_device = '<div id="{0}" title="{1}" class="thread" style="left:{2}%;top:{3}%;height:{4}%;width:{5}%;">{6}</div>\n'
  824. html_phase = '<div class="phase" style="left:{0}%;width:{1}%;top:{2}%;height:{3}%;background-color:{4}">{5}</div>\n'
  825. html_legend = '<div class="square" style="left:{0}%;background-color:{1}">&nbsp;{2}</div>\n'
  826. html_timetotal = '<table class="time1">\n<tr>'\
  827. '<td class="gray">{2} Suspend Time: <b>{0} ms</b></td>'\
  828. '<td class="gray">{2} Resume Time: <b>{1} ms</b></td>'\
  829. '</tr>\n</table>\n'
  830. html_timegroups = '<table class="time2">\n<tr>'\
  831. '<td class="green">Kernel Suspend: {0} ms</td>'\
  832. '<td class="purple">Firmware Suspend: {1} ms</td>'\
  833. '<td class="purple">Firmware Resume: {2} ms</td>'\
  834. '<td class="yellow">Kernel Resume: {3} ms</td>'\
  835. '</tr>\n</table>\n'
  836. # device timeline (dmesg)
  837. if(data.usedmesg):
  838. data.vprint("Creating Device Timeline...")
  839. devtl = Timeline()
  840. # Generate the header for this timeline
  841. t0 = data.start
  842. tMax = data.end
  843. tTotal = tMax - t0
  844. if(tTotal == 0):
  845. print("ERROR: No timeline data")
  846. sys.exit()
  847. suspend_time = "%.0f"%(-data.start*1000)
  848. resume_time = "%.0f"%(data.end*1000)
  849. if data.fwValid:
  850. devtl.html['timeline'] = html_timetotal.format(suspend_time, resume_time, "Total")
  851. sktime = "%.3f"%((data.dmesg['suspend_cpu']['end'] - data.dmesg['suspend_general']['start'])*1000)
  852. sftime = "%.3f"%(data.fwSuspend / 1000000.0)
  853. rftime = "%.3f"%(data.fwResume / 1000000.0)
  854. rktime = "%.3f"%((data.dmesg['resume_general']['end'] - data.dmesg['resume_cpu']['start'])*1000)
  855. devtl.html['timeline'] += html_timegroups.format(sktime, sftime, rftime, rktime)
  856. else:
  857. devtl.html['timeline'] = html_timetotal.format(suspend_time, resume_time, "Kernel")
  858. # determine the maximum number of rows we need to draw
  859. timelinerows = 0
  860. for phase in data.dmesg:
  861. list = data.dmesg[phase]['list']
  862. rows = setTimelineRows(list, list)
  863. data.dmesg[phase]['row'] = rows
  864. if(rows > timelinerows):
  865. timelinerows = rows
  866. # calculate the timeline height and create its bounding box
  867. devtl.setRows(timelinerows + 1)
  868. devtl.html['timeline'] += html_zoombox;
  869. devtl.html['timeline'] += html_timeline.format("dmesg", devtl.height);
  870. # draw the colored boxes for each of the phases
  871. for b in data.dmesg:
  872. phase = data.dmesg[b]
  873. left = "%.3f" % (((phase['start']-data.start)*100)/tTotal)
  874. width = "%.3f" % (((phase['end']-phase['start'])*100)/tTotal)
  875. devtl.html['timeline'] += html_phase.format(left, width, "%.3f"%devtl.scaleH, "%.3f"%(100-devtl.scaleH), data.dmesg[b]['color'], "")
  876. # draw the time scale, try to make the number of labels readable
  877. devtl.html['scale'] = createTimeScale(t0, tMax, data.tSuspended)
  878. devtl.html['timeline'] += devtl.html['scale']
  879. for b in data.dmesg:
  880. phaselist = data.dmesg[b]['list']
  881. for d in phaselist:
  882. name = d
  883. if(d in data.altdevname):
  884. name = data.altdevname[d]
  885. dev = phaselist[d]
  886. height = (100.0 - devtl.scaleH)/data.dmesg[b]['row']
  887. top = "%.3f" % ((dev['row']*height) + devtl.scaleH)
  888. left = "%.3f" % (((dev['start']-data.start)*100)/tTotal)
  889. width = "%.3f" % (((dev['end']-dev['start'])*100)/tTotal)
  890. len = " (%0.3f ms) " % ((dev['end']-dev['start'])*1000)
  891. color = "rgba(204,204,204,0.5)"
  892. devtl.html['timeline'] += html_device.format(dev['id'], name+len+b, left, top, "%.3f"%height, width, name)
  893. # timeline is finished
  894. devtl.html['timeline'] += "</div>\n</div>\n"
  895. # draw a legend which describes the phases by color
  896. devtl.html['legend'] = "<div class=\"legend\">\n"
  897. pdelta = 100.0/data.phases.__len__()
  898. pmargin = pdelta / 4.0
  899. for phase in data.phases:
  900. order = "%.2f" % ((data.dmesg[phase]['order'] * pdelta) + pmargin)
  901. name = string.replace(phase, "_", " &nbsp;")
  902. devtl.html['legend'] += html_legend.format(order, data.dmesg[phase]['color'], name)
  903. devtl.html['legend'] += "</div>\n"
  904. hf = open(sysvals.htmlfile, 'w')
  905. thread_height = 0
  906. # write the html header first (html head, css code, everything up to the start of body)
  907. html_header = "<!DOCTYPE html>\n<html>\n<head>\n\
  908. <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n\
  909. <title>AnalyzeSuspend</title>\n\
  910. <style type='text/css'>\n\
  911. body {overflow-y: scroll;}\n\
  912. .stamp {width: 100%;text-align:center;background-color:gray;line-height:30px;color:white;font: 25px Arial;}\n\
  913. .callgraph {margin-top: 30px;box-shadow: 5px 5px 20px black;}\n\
  914. .callgraph article * {padding-left: 28px;}\n\
  915. h1 {color:black;font: bold 30px Times;}\n\
  916. table {width:100%;}\n\
  917. .gray {background-color:rgba(80,80,80,0.1);}\n\
  918. .green {background-color:rgba(204,255,204,0.4);}\n\
  919. .purple {background-color:rgba(128,0,128,0.2);}\n\
  920. .yellow {background-color:rgba(255,255,204,0.4);}\n\
  921. .time1 {font: 22px Arial;border:1px solid;}\n\
  922. .time2 {font: 15px Arial;border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}\n\
  923. td {text-align: center;}\n\
  924. .tdhl {color: red;}\n\
  925. .hide {display: none;}\n\
  926. .pf {display: none;}\n\
  927. .pf:checked + label {background: url(\'data:image/svg+xml;utf,<?xml version=\"1.0\" standalone=\"no\"?><svg xmlns=\"http://www.w3.org/2000/svg\" height=\"18\" width=\"18\" version=\"1.1\"><circle cx=\"9\" cy=\"9\" r=\"8\" stroke=\"black\" stroke-width=\"1\" fill=\"white\"/><rect x=\"4\" y=\"8\" width=\"10\" height=\"2\" style=\"fill:black;stroke-width:0\"/><rect x=\"8\" y=\"4\" width=\"2\" height=\"10\" style=\"fill:black;stroke-width:0\"/></svg>\') no-repeat left center;}\n\
  928. .pf:not(:checked) ~ label {background: url(\'data:image/svg+xml;utf,<?xml version=\"1.0\" standalone=\"no\"?><svg xmlns=\"http://www.w3.org/2000/svg\" height=\"18\" width=\"18\" version=\"1.1\"><circle cx=\"9\" cy=\"9\" r=\"8\" stroke=\"black\" stroke-width=\"1\" fill=\"white\"/><rect x=\"4\" y=\"8\" width=\"10\" height=\"2\" style=\"fill:black;stroke-width:0\"/></svg>\') no-repeat left center;}\n\
  929. .pf:checked ~ *:not(:nth-child(2)) {display: none;}\n\
  930. .zoombox {position: relative; width: 100%; overflow-x: scroll;}\n\
  931. .timeline {position: relative; font-size: 14px;cursor: pointer;width: 100%; overflow: hidden; background-color:#dddddd;}\n\
  932. .thread {position: absolute; height: "+"%.3f"%thread_height+"%; overflow: hidden; line-height: 30px; border:1px solid;text-align:center;white-space:nowrap;background-color:rgba(204,204,204,0.5);}\n\
  933. .thread:hover {background-color:white;border:1px solid red;z-index:10;}\n\
  934. .phase {position: absolute;overflow: hidden;border:0px;text-align:center;}\n\
  935. .t {position: absolute; top: 0%; height: 100%; border-right:1px solid black;}\n\
  936. .legend {position: relative; width: 100%; height: 40px; text-align: center;margin-bottom:20px}\n\
  937. .legend .square {position:absolute;top:10px; width: 0px;height: 20px;border:1px solid;padding-left:20px;}\n\
  938. button {height:40px;width:200px;margin-bottom:20px;margin-top:20px;font-size:24px;}\n\
  939. </style>\n</head>\n<body>\n"
  940. hf.write(html_header)
  941. # write the test title and general info header
  942. if(data.stamp['time'] != ""):
  943. hf.write(headline_stamp.format(data.stamp['host'],
  944. data.stamp['kernel'], data.stamp['mode'], data.stamp['time']))
  945. # write the dmesg data (device timeline)
  946. if(data.usedmesg):
  947. hf.write(devtl.html['timeline'])
  948. hf.write(devtl.html['legend'])
  949. hf.write('<div id="devicedetail"></div>\n')
  950. hf.write('<div id="devicetree"></div>\n')
  951. # write the ftrace data (callgraph)
  952. if(data.useftrace):
  953. hf.write('<section id="callgraphs" class="callgraph">\n')
  954. # write out the ftrace data converted to html
  955. html_func_top = '<article id="{0}" class="atop" style="background-color:{1}">\n<input type="checkbox" class="pf" id="f{2}" checked/><label for="f{2}">{3} {4}</label>\n'
  956. html_func_start = '<article>\n<input type="checkbox" class="pf" id="f{0}" checked/><label for="f{0}">{1} {2}</label>\n'
  957. html_func_end = '</article>\n'
  958. html_func_leaf = '<article>{0} {1}</article>\n'
  959. num = 0
  960. for p in data.phases:
  961. list = data.dmesg[p]['list']
  962. for devname in data.sortedDevices(p):
  963. if('ftrace' not in list[devname]):
  964. continue
  965. name = devname
  966. if(devname in data.altdevname):
  967. name = data.altdevname[devname]
  968. devid = list[devname]['id']
  969. cg = list[devname]['ftrace']
  970. flen = "(%.3f ms)" % ((cg.end - cg.start)*1000)
  971. hf.write(html_func_top.format(devid, data.dmesg[p]['color'], num, name+" "+p, flen))
  972. num += 1
  973. for line in cg.list:
  974. if(line.length < 0.000000001):
  975. flen = ""
  976. else:
  977. flen = "(%.3f ms)" % (line.length*1000)
  978. if(line.freturn and line.fcall):
  979. hf.write(html_func_leaf.format(line.name, flen))
  980. elif(line.freturn):
  981. hf.write(html_func_end)
  982. else:
  983. hf.write(html_func_start.format(num, line.name, flen))
  984. num += 1
  985. hf.write(html_func_end)
  986. hf.write("\n\n </section>\n")
  987. # write the footer and close
  988. addScriptCode(hf)
  989. hf.write("</body>\n</html>\n")
  990. hf.close()
  991. return True
  992. def addScriptCode(hf):
  993. global data
  994. t0 = (data.start - data.tSuspended) * 1000
  995. tMax = (data.end - data.tSuspended) * 1000
  996. # create an array in javascript memory with the device details
  997. detail = ' var bounds = [%f,%f];\n' % (t0, tMax)
  998. detail += ' var d = [];\n'
  999. dfmt = ' d["%s"] = { n:"%s", p:"%s", c:[%s] };\n';
  1000. for p in data.dmesg:
  1001. list = data.dmesg[p]['list']
  1002. for d in list:
  1003. parent = data.deviceParentID(d, p)
  1004. idlist = data.deviceChildrenIDs(d, p)
  1005. idstr = ""
  1006. for i in idlist:
  1007. if(idstr == ""):
  1008. idstr += '"'+i+'"'
  1009. else:
  1010. idstr += ', '+'"'+i+'"'
  1011. detail += dfmt % (list[d]['id'], d, parent, idstr)
  1012. # add the code which will manipulate the data in the browser
  1013. script_code = \
  1014. '<script type="text/javascript">\n'+detail+\
  1015. ' var filter = [];\n'\
  1016. ' var table = [];\n'\
  1017. ' function deviceParent(devid) {\n'\
  1018. ' var devlist = [];\n'\
  1019. ' if(filter.indexOf(devid) < 0) filter[filter.length] = devid;\n'\
  1020. ' if(d[devid].p in d)\n'\
  1021. ' devlist = deviceParent(d[devid].p);\n'\
  1022. ' else if(d[devid].p != "")\n'\
  1023. ' devlist = [d[devid].p];\n'\
  1024. ' devlist[devlist.length] = d[devid].n;\n'\
  1025. ' return devlist;\n'\
  1026. ' }\n'\
  1027. ' function deviceChildren(devid, column, row) {\n'\
  1028. ' if(!(devid in d)) return;\n'\
  1029. ' if(filter.indexOf(devid) < 0) filter[filter.length] = devid;\n'\
  1030. ' var cell = {name: d[devid].n, span: 1};\n'\
  1031. ' var span = 0;\n'\
  1032. ' if(column >= table.length) table[column] = [];\n'\
  1033. ' table[column][row] = cell;\n'\
  1034. ' for(var i = 0; i < d[devid].c.length; i++) {\n'\
  1035. ' var cid = d[devid].c[i];\n'\
  1036. ' span += deviceChildren(cid, column+1, row+span);\n'\
  1037. ' }\n'\
  1038. ' if(span == 0) span = 1;\n'\
  1039. ' table[column][row].span = span;\n'\
  1040. ' return span;\n'\
  1041. ' }\n'\
  1042. ' function deviceTree(devid, resume) {\n'\
  1043. ' var html = "<table border=1>";\n'\
  1044. ' filter = [];\n'\
  1045. ' table = [];\n'\
  1046. ' plist = deviceParent(devid);\n'\
  1047. ' var devidx = plist.length - 1;\n'\
  1048. ' for(var i = 0; i < devidx; i++)\n'\
  1049. ' table[i] = [{name: plist[i], span: 1}];\n'\
  1050. ' deviceChildren(devid, devidx, 0);\n'\
  1051. ' for(var i = 0; i < devidx; i++)\n'\
  1052. ' table[i][0].span = table[devidx][0].span;\n'\
  1053. ' for(var row = 0; row < table[0][0].span; row++) {\n'\
  1054. ' html += "<tr>";\n'\
  1055. ' for(var col = 0; col < table.length; col++)\n'\
  1056. ' if(row in table[col]) {\n'\
  1057. ' var cell = table[col][row];\n'\
  1058. ' var args = "";\n'\
  1059. ' if(cell.span > 1)\n'\
  1060. ' args += " rowspan="+cell.span;\n'\
  1061. ' if((col == devidx) && (row == 0))\n'\
  1062. ' args += " class=tdhl";\n'\
  1063. ' if(resume)\n'\
  1064. ' html += "<td"+args+">"+cell.name+" &rarr;</td>";\n'\
  1065. ' else\n'\
  1066. ' html += "<td"+args+">&larr; "+cell.name+"</td>";\n'\
  1067. ' }\n'\
  1068. ' html += "</tr>";\n'\
  1069. ' }\n'\
  1070. ' html += "</table>";\n'\
  1071. ' return html;\n'\
  1072. ' }\n'\
  1073. ' function zoomTimeline() {\n'\
  1074. ' var timescale = document.getElementById("timescale");\n'\
  1075. ' var dmesg = document.getElementById("dmesg");\n'\
  1076. ' var zoombox = document.getElementById("dmesgzoombox");\n'\
  1077. ' var val = parseFloat(dmesg.style.width);\n'\
  1078. ' var newval = 100;\n'\
  1079. ' var sh = window.outerWidth / 2;\n'\
  1080. ' if(this.id == "zoomin") {\n'\
  1081. ' newval = val * 1.2;\n'\
  1082. ' if(newval > 40000) newval = 40000;\n'\
  1083. ' dmesg.style.width = newval+"%";\n'\
  1084. ' zoombox.scrollLeft = ((zoombox.scrollLeft + sh) * newval / val) - sh;\n'\
  1085. ' } else if (this.id == "zoomout") {\n'\
  1086. ' newval = val / 1.2;\n'\
  1087. ' if(newval < 100) newval = 100;\n'\
  1088. ' dmesg.style.width = newval+"%";\n'\
  1089. ' zoombox.scrollLeft = ((zoombox.scrollLeft + sh) * newval / val) - sh;\n'\
  1090. ' } else {\n'\
  1091. ' zoombox.scrollLeft = 0;\n'\
  1092. ' dmesg.style.width = "100%";\n'\
  1093. ' }\n'\
  1094. ' var html = "";\n'\
  1095. ' var t0 = bounds[0];\n'\
  1096. ' var tMax = bounds[1];\n'\
  1097. ' var tTotal = tMax - t0;\n'\
  1098. ' var wTotal = tTotal * 100.0 / newval;\n'\
  1099. ' for(var tS = 1000; (wTotal / tS) < 3; tS /= 10);\n'\
  1100. ' if(tS < 1) tS = 1;\n'\
  1101. ' for(var s = ((t0 / tS)|0) * tS; s < tMax; s += tS) {\n'\
  1102. ' var pos = (tMax - s) * 100.0 / tTotal;\n'\
  1103. ' var name = (s == 0)?"S/R":(s+"ms");\n'\
  1104. ' html += \"<div class=\\\"t\\\" style=\\\"right:\"+pos+\"%\\\">\"+name+\"</div>\";\n'\
  1105. ' }\n'\
  1106. ' timescale.innerHTML = html;\n'\
  1107. ' }\n'\
  1108. ' function deviceDetail() {\n'\
  1109. ' var devtitle = document.getElementById("devicedetail");\n'\
  1110. ' devtitle.innerHTML = "<h1>"+this.title+"</h1>";\n'\
  1111. ' var devtree = document.getElementById("devicetree");\n'\
  1112. ' devtree.innerHTML = deviceTree(this.id, (this.title.indexOf("resume") >= 0));\n'\
  1113. ' var cglist = document.getElementById("callgraphs");\n'\
  1114. ' if(!cglist) return;\n'\
  1115. ' var cg = cglist.getElementsByClassName("atop");\n'\
  1116. ' for (var i = 0; i < cg.length; i++) {\n'\
  1117. ' if(filter.indexOf(cg[i].id) >= 0) {\n'\
  1118. ' cg[i].style.display = "block";\n'\
  1119. ' } else {\n'\
  1120. ' cg[i].style.display = "none";\n'\
  1121. ' }\n'\
  1122. ' }\n'\
  1123. ' }\n'\
  1124. ' window.addEventListener("load", function () {\n'\
  1125. ' var dmesg = document.getElementById("dmesg");\n'\
  1126. ' dmesg.style.width = "100%"\n'\
  1127. ' document.getElementById("zoomin").onclick = zoomTimeline;\n'\
  1128. ' document.getElementById("zoomout").onclick = zoomTimeline;\n'\
  1129. ' document.getElementById("zoomdef").onclick = zoomTimeline;\n'\
  1130. ' var dev = dmesg.getElementsByClassName("thread");\n'\
  1131. ' for (var i = 0; i < dev.length; i++) {\n'\
  1132. ' dev[i].onclick = deviceDetail;\n'\
  1133. ' }\n'\
  1134. ' zoomTimeline();\n'\
  1135. ' });\n'\
  1136. '</script>\n'
  1137. hf.write(script_code);
  1138. # Function: executeSuspend
  1139. # Description:
  1140. # Execute system suspend through the sysfs interface
  1141. def executeSuspend():
  1142. global sysvals, data
  1143. detectUSB()
  1144. pf = open(sysvals.powerfile, 'w')
  1145. # clear the kernel ring buffer just as we start
  1146. os.system("dmesg -C")
  1147. # start ftrace
  1148. if(data.useftrace):
  1149. print("START TRACING")
  1150. os.system("echo 1 > "+sysvals.tpath+"tracing_on")
  1151. os.system("echo SUSPEND START > "+sysvals.tpath+"trace_marker")
  1152. # initiate suspend
  1153. if(sysvals.rtcwake):
  1154. print("SUSPEND START")
  1155. os.system("rtcwake -s 10 -m "+sysvals.suspendmode)
  1156. else:
  1157. print("SUSPEND START (press a key to resume)")
  1158. pf.write(sysvals.suspendmode)
  1159. # execution will pause here
  1160. pf.close()
  1161. # return from suspend
  1162. print("RESUME COMPLETE")
  1163. # stop ftrace
  1164. if(data.useftrace):
  1165. os.system("echo RESUME COMPLETE > "+sysvals.tpath+"trace_marker")
  1166. os.system("echo 0 > "+sysvals.tpath+"tracing_on")
  1167. print("CAPTURING FTRACE")
  1168. os.system("echo \""+sysvals.teststamp+"\" > "+sysvals.ftracefile)
  1169. os.system("cat "+sysvals.tpath+"trace >> "+sysvals.ftracefile)
  1170. # grab a copy of the dmesg output
  1171. print("CAPTURING DMESG")
  1172. os.system("echo \""+sysvals.teststamp+"\" > "+sysvals.dmesgfile)
  1173. os.system("dmesg -c >> "+sysvals.dmesgfile)
  1174. # Function: detectUSB
  1175. # Description:
  1176. # Detect all the USB hosts and devices currently connected
  1177. def detectUSB():
  1178. global sysvals, data
  1179. for dirname, dirnames, filenames in os.walk("/sys/devices"):
  1180. if(re.match(r".*/usb[0-9]*.*", dirname) and
  1181. "idVendor" in filenames and "idProduct" in filenames):
  1182. vid = os.popen("cat %s/idVendor 2>/dev/null" % dirname).read().replace('\n', '')
  1183. pid = os.popen("cat %s/idProduct 2>/dev/null" % dirname).read().replace('\n', '')
  1184. product = os.popen("cat %s/product 2>/dev/null" % dirname).read().replace('\n', '')
  1185. name = dirname.split('/')[-1]
  1186. if(len(product) > 0):
  1187. data.altdevname[name] = "%s [%s]" % (product, name)
  1188. else:
  1189. data.altdevname[name] = "%s:%s [%s]" % (vid, pid, name)
  1190. def getModes():
  1191. global sysvals
  1192. modes = ""
  1193. if(os.path.exists(sysvals.powerfile)):
  1194. fp = open(sysvals.powerfile, 'r')
  1195. modes = string.split(fp.read())
  1196. fp.close()
  1197. return modes
  1198. # Function: statusCheck
  1199. # Description:
  1200. # Verify that the requested command and options will work
  1201. def statusCheck(dryrun):
  1202. global sysvals, data
  1203. res = dict()
  1204. if(data.notestrun):
  1205. print("SUCCESS: The command should run!")
  1206. return
  1207. # check we have root access
  1208. check = "YES"
  1209. if(os.environ['USER'] != "root"):
  1210. if(not dryrun):
  1211. doError("root access is required", False)
  1212. check = "NO"
  1213. res[" have root access: "] = check
  1214. # check sysfs is mounted
  1215. check = "YES"
  1216. if(not os.path.exists(sysvals.powerfile)):
  1217. if(not dryrun):
  1218. doError("sysfs must be mounted", False)
  1219. check = "NO"
  1220. res[" is sysfs mounted: "] = check
  1221. # check target mode is a valid mode
  1222. check = "YES"
  1223. modes = getModes()
  1224. if(sysvals.suspendmode not in modes):
  1225. if(not dryrun):
  1226. doError("%s is not a value power mode" % sysvals.suspendmode, False)
  1227. check = "NO"
  1228. res[" is "+sysvals.suspendmode+" a power mode: "] = check
  1229. # check if ftrace is available
  1230. if(data.useftrace):
  1231. check = "YES"
  1232. if(not verifyFtrace()):
  1233. if(not dryrun):
  1234. doError("ftrace is not configured", False)
  1235. check = "NO"
  1236. res[" is ftrace usable: "] = check
  1237. # check if rtcwake
  1238. if(sysvals.rtcwake):
  1239. check = "YES"
  1240. version = os.popen("rtcwake -V 2>/dev/null").read()
  1241. if(not version.startswith("rtcwake")):
  1242. if(not dryrun):
  1243. doError("rtcwake is not installed", False)
  1244. check = "NO"
  1245. res[" is rtcwake usable: "] = check
  1246. if(dryrun):
  1247. status = True
  1248. print("Checking if system can run the current command:")
  1249. for r in res:
  1250. print("%s\t%s" % (r, res[r]))
  1251. if(res[r] != "YES"):
  1252. status = False
  1253. if(status):
  1254. print("SUCCESS: The command should run!")
  1255. else:
  1256. print("FAILURE: The command won't run!")
  1257. def printHelp():
  1258. global sysvals
  1259. modes = getModes()
  1260. print("")
  1261. print("AnalyzeSuspend")
  1262. print("Usage: sudo analyze_suspend.py <options>")
  1263. print("")
  1264. print("Description:")
  1265. print(" Initiates a system suspend/resume while capturing dmesg")
  1266. print(" and (optionally) ftrace data to analyze device timing")
  1267. print("")
  1268. print(" Generates output files in subdirectory: suspend-mmddyy-HHMMSS")
  1269. print(" HTML output: <hostname>_<mode>.html")
  1270. print(" raw dmesg output: <hostname>_<mode>_dmesg.txt")
  1271. print(" raw ftrace output (with -f): <hostname>_<mode>_ftrace.txt")
  1272. print("")
  1273. print("Options:")
  1274. print(" [general]")
  1275. print(" -h Print this help text")
  1276. print(" -verbose Print extra information during execution and analysis")
  1277. print(" -status Test to see if the system is enabled to run this tool")
  1278. print(" -modes List available suspend modes")
  1279. print(" -m mode Mode to initiate for suspend %s (default: %s)") % (modes, sysvals.suspendmode)
  1280. print(" -rtcwake Use rtcwake to autoresume after 10 seconds (default: disabled)")
  1281. print(" -f Use ftrace to create device callgraphs (default: disabled)")
  1282. print(" [re-analyze data from previous runs]")
  1283. print(" -dmesg dmesgfile Create HTML timeline from dmesg file")
  1284. print(" -ftrace ftracefile Create HTML callgraph from ftrace file")
  1285. print("")
  1286. return True
  1287. def doError(msg, help):
  1288. print("ERROR: %s") % msg
  1289. if(help == True):
  1290. printHelp()
  1291. sys.exit()
  1292. # -- script main --
  1293. # loop through the command line arguments
  1294. cmd = ""
  1295. args = iter(sys.argv[1:])
  1296. for arg in args:
  1297. if(arg == "-m"):
  1298. try:
  1299. val = args.next()
  1300. except:
  1301. doError("No mode supplied", True)
  1302. sysvals.suspendmode = val
  1303. elif(arg == "-f"):
  1304. data.useftrace = True
  1305. elif(arg == "-modes"):
  1306. cmd = "modes"
  1307. elif(arg == "-status"):
  1308. cmd = "status"
  1309. elif(arg == "-verbose"):
  1310. data.verbose = True
  1311. elif(arg == "-rtcwake"):
  1312. sysvals.rtcwake = True
  1313. elif(arg == "-dmesg"):
  1314. try:
  1315. val = args.next()
  1316. except:
  1317. doError("No dmesg file supplied", True)
  1318. data.notestrun = True
  1319. data.usedmesg = True
  1320. sysvals.dmesgfile = val
  1321. elif(arg == "-ftrace"):
  1322. try:
  1323. val = args.next()
  1324. except:
  1325. doError("No ftrace file supplied", True)
  1326. data.notestrun = True
  1327. data.useftrace = True
  1328. sysvals.ftracefile = val
  1329. elif(arg == "-h"):
  1330. printHelp()
  1331. sys.exit()
  1332. else:
  1333. doError("Invalid argument: "+arg, True)
  1334. # just run a utility command and exit
  1335. if(cmd != ""):
  1336. if(cmd == "status"):
  1337. statusCheck(True)
  1338. elif(cmd == "modes"):
  1339. modes = getModes()
  1340. print modes
  1341. sys.exit()
  1342. data.initialize()
  1343. # if instructed, re-analyze existing data files
  1344. if(data.notestrun):
  1345. sysvals.setOutputFile()
  1346. data.vprint("Output file: %s" % sysvals.htmlfile)
  1347. if(sysvals.dmesgfile != ""):
  1348. analyzeKernelLog()
  1349. if(sysvals.ftracefile != ""):
  1350. analyzeTraceLog()
  1351. createHTML()
  1352. sys.exit()
  1353. # verify that we can run a test
  1354. data.usedmesg = True
  1355. statusCheck(False)
  1356. # prepare for the test
  1357. if(data.useftrace):
  1358. initFtrace()
  1359. sysvals.initTestOutput()
  1360. data.vprint("Output files:\n %s" % sysvals.dmesgfile)
  1361. if(data.useftrace):
  1362. data.vprint(" %s" % sysvals.ftracefile)
  1363. data.vprint(" %s" % sysvals.htmlfile)
  1364. # execute the test
  1365. executeSuspend()
  1366. analyzeKernelLog()
  1367. if(data.useftrace):
  1368. analyzeTraceLog()
  1369. createHTML()