analyze_suspend.py 118 KB

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