exported-sql-viewer.py 154 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030
  1. #!/usr/bin/env python
  2. # SPDX-License-Identifier: GPL-2.0
  3. # exported-sql-viewer.py: view data from sql database
  4. # Copyright (c) 2014-2018, Intel Corporation.
  5. # To use this script you will need to have exported data using either the
  6. # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
  7. # scripts for details.
  8. #
  9. # Following on from the example in the export scripts, a
  10. # call-graph can be displayed for the pt_example database like this:
  11. #
  12. # python tools/perf/scripts/python/exported-sql-viewer.py pt_example
  13. #
  14. # Note that for PostgreSQL, this script supports connecting to remote databases
  15. # by setting hostname, port, username, password, and dbname e.g.
  16. #
  17. # python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
  18. #
  19. # The result is a GUI window with a tree representing a context-sensitive
  20. # call-graph. Expanding a couple of levels of the tree and adjusting column
  21. # widths to suit will display something like:
  22. #
  23. # Call Graph: pt_example
  24. # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
  25. # v- ls
  26. # v- 2638:2638
  27. # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
  28. # |- unknown unknown 1 13198 0.1 1 0.0
  29. # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
  30. # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
  31. # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
  32. # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
  33. # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
  34. # >- __libc_csu_init ls 1 10354 0.1 10 0.0
  35. # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
  36. # v- main ls 1 8182043 99.6 180254 99.9
  37. #
  38. # Points to note:
  39. # The top level is a command name (comm)
  40. # The next level is a thread (pid:tid)
  41. # Subsequent levels are functions
  42. # 'Count' is the number of calls
  43. # 'Time' is the elapsed time until the function returns
  44. # Percentages are relative to the level above
  45. # 'Branch Count' is the total number of branches for that function and all
  46. # functions that it calls
  47. # There is also a "All branches" report, which displays branches and
  48. # possibly disassembly. However, presently, the only supported disassembler is
  49. # Intel XED, and additionally the object code must be present in perf build ID
  50. # cache. To use Intel XED, libxed.so must be present. To build and install
  51. # libxed.so:
  52. # git clone https://github.com/intelxed/mbuild.git mbuild
  53. # git clone https://github.com/intelxed/xed
  54. # cd xed
  55. # ./mfile.py --share
  56. # sudo ./mfile.py --prefix=/usr/local install
  57. # sudo ldconfig
  58. #
  59. # Example report:
  60. #
  61. # Time CPU Command PID TID Branch Type In Tx Branch
  62. # 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
  63. # 7fab593ea260 48 89 e7 mov %rsp, %rdi
  64. # 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
  65. # 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
  66. # 7fab593ea260 48 89 e7 mov %rsp, %rdi
  67. # 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930
  68. # 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
  69. # 7fab593ea930 55 pushq %rbp
  70. # 7fab593ea931 48 89 e5 mov %rsp, %rbp
  71. # 7fab593ea934 41 57 pushq %r15
  72. # 7fab593ea936 41 56 pushq %r14
  73. # 7fab593ea938 41 55 pushq %r13
  74. # 7fab593ea93a 41 54 pushq %r12
  75. # 7fab593ea93c 53 pushq %rbx
  76. # 7fab593ea93d 48 89 fb mov %rdi, %rbx
  77. # 7fab593ea940 48 83 ec 68 sub $0x68, %rsp
  78. # 7fab593ea944 0f 31 rdtsc
  79. # 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx
  80. # 7fab593ea94a 89 c0 mov %eax, %eax
  81. # 7fab593ea94c 48 09 c2 or %rax, %rdx
  82. # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
  83. # 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
  84. # 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
  85. # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
  86. # 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip)
  87. # 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
  88. from __future__ import print_function
  89. import sys
  90. # Only change warnings if the python -W option was not used
  91. if not sys.warnoptions:
  92. import warnings
  93. # PySide2 causes deprecation warnings, ignore them.
  94. warnings.filterwarnings("ignore", category=DeprecationWarning)
  95. import argparse
  96. import weakref
  97. import threading
  98. import string
  99. try:
  100. # Python2
  101. import cPickle as pickle
  102. # size of pickled integer big enough for record size
  103. glb_nsz = 8
  104. except ImportError:
  105. import pickle
  106. glb_nsz = 16
  107. import re
  108. import os
  109. import random
  110. import copy
  111. import math
  112. from libxed import LibXED
  113. pyside_version_1 = True
  114. if not "--pyside-version-1" in sys.argv:
  115. try:
  116. from PySide2.QtCore import *
  117. from PySide2.QtGui import *
  118. from PySide2.QtSql import *
  119. from PySide2.QtWidgets import *
  120. pyside_version_1 = False
  121. except:
  122. pass
  123. if pyside_version_1:
  124. from PySide.QtCore import *
  125. from PySide.QtGui import *
  126. from PySide.QtSql import *
  127. from decimal import Decimal, ROUND_HALF_UP
  128. from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \
  129. c_void_p, c_bool, c_byte, c_char, c_int, c_uint, c_longlong, c_ulonglong
  130. from multiprocessing import Process, Array, Value, Event
  131. # xrange is range in Python3
  132. try:
  133. xrange
  134. except NameError:
  135. xrange = range
  136. def printerr(*args, **keyword_args):
  137. print(*args, file=sys.stderr, **keyword_args)
  138. # Data formatting helpers
  139. def tohex(ip):
  140. if ip < 0:
  141. ip += 1 << 64
  142. return "%x" % ip
  143. def offstr(offset):
  144. if offset:
  145. return "+0x%x" % offset
  146. return ""
  147. def dsoname(name):
  148. if name == "[kernel.kallsyms]":
  149. return "[kernel]"
  150. return name
  151. def findnth(s, sub, n, offs=0):
  152. pos = s.find(sub)
  153. if pos < 0:
  154. return pos
  155. if n <= 1:
  156. return offs + pos
  157. return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
  158. # Percent to one decimal place
  159. def PercentToOneDP(n, d):
  160. if not d:
  161. return "0.0"
  162. x = (n * Decimal(100)) / d
  163. return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
  164. # Helper for queries that must not fail
  165. def QueryExec(query, stmt):
  166. ret = query.exec_(stmt)
  167. if not ret:
  168. raise Exception("Query failed: " + query.lastError().text())
  169. # Background thread
  170. class Thread(QThread):
  171. done = Signal(object)
  172. def __init__(self, task, param=None, parent=None):
  173. super(Thread, self).__init__(parent)
  174. self.task = task
  175. self.param = param
  176. def run(self):
  177. while True:
  178. if self.param is None:
  179. done, result = self.task()
  180. else:
  181. done, result = self.task(self.param)
  182. self.done.emit(result)
  183. if done:
  184. break
  185. # Tree data model
  186. class TreeModel(QAbstractItemModel):
  187. def __init__(self, glb, params, parent=None):
  188. super(TreeModel, self).__init__(parent)
  189. self.glb = glb
  190. self.params = params
  191. self.root = self.GetRoot()
  192. self.last_row_read = 0
  193. def Item(self, parent):
  194. if parent.isValid():
  195. return parent.internalPointer()
  196. else:
  197. return self.root
  198. def rowCount(self, parent):
  199. result = self.Item(parent).childCount()
  200. if result < 0:
  201. result = 0
  202. self.dataChanged.emit(parent, parent)
  203. return result
  204. def hasChildren(self, parent):
  205. return self.Item(parent).hasChildren()
  206. def headerData(self, section, orientation, role):
  207. if role == Qt.TextAlignmentRole:
  208. return self.columnAlignment(section)
  209. if role != Qt.DisplayRole:
  210. return None
  211. if orientation != Qt.Horizontal:
  212. return None
  213. return self.columnHeader(section)
  214. def parent(self, child):
  215. child_item = child.internalPointer()
  216. if child_item is self.root:
  217. return QModelIndex()
  218. parent_item = child_item.getParentItem()
  219. return self.createIndex(parent_item.getRow(), 0, parent_item)
  220. def index(self, row, column, parent):
  221. child_item = self.Item(parent).getChildItem(row)
  222. return self.createIndex(row, column, child_item)
  223. def DisplayData(self, item, index):
  224. return item.getData(index.column())
  225. def FetchIfNeeded(self, row):
  226. if row > self.last_row_read:
  227. self.last_row_read = row
  228. if row + 10 >= self.root.child_count:
  229. self.fetcher.Fetch(glb_chunk_sz)
  230. def columnAlignment(self, column):
  231. return Qt.AlignLeft
  232. def columnFont(self, column):
  233. return None
  234. def data(self, index, role):
  235. if role == Qt.TextAlignmentRole:
  236. return self.columnAlignment(index.column())
  237. if role == Qt.FontRole:
  238. return self.columnFont(index.column())
  239. if role != Qt.DisplayRole:
  240. return None
  241. item = index.internalPointer()
  242. return self.DisplayData(item, index)
  243. # Table data model
  244. class TableModel(QAbstractTableModel):
  245. def __init__(self, parent=None):
  246. super(TableModel, self).__init__(parent)
  247. self.child_count = 0
  248. self.child_items = []
  249. self.last_row_read = 0
  250. def Item(self, parent):
  251. if parent.isValid():
  252. return parent.internalPointer()
  253. else:
  254. return self
  255. def rowCount(self, parent):
  256. return self.child_count
  257. def headerData(self, section, orientation, role):
  258. if role == Qt.TextAlignmentRole:
  259. return self.columnAlignment(section)
  260. if role != Qt.DisplayRole:
  261. return None
  262. if orientation != Qt.Horizontal:
  263. return None
  264. return self.columnHeader(section)
  265. def index(self, row, column, parent):
  266. return self.createIndex(row, column, self.child_items[row])
  267. def DisplayData(self, item, index):
  268. return item.getData(index.column())
  269. def FetchIfNeeded(self, row):
  270. if row > self.last_row_read:
  271. self.last_row_read = row
  272. if row + 10 >= self.child_count:
  273. self.fetcher.Fetch(glb_chunk_sz)
  274. def columnAlignment(self, column):
  275. return Qt.AlignLeft
  276. def columnFont(self, column):
  277. return None
  278. def data(self, index, role):
  279. if role == Qt.TextAlignmentRole:
  280. return self.columnAlignment(index.column())
  281. if role == Qt.FontRole:
  282. return self.columnFont(index.column())
  283. if role != Qt.DisplayRole:
  284. return None
  285. item = index.internalPointer()
  286. return self.DisplayData(item, index)
  287. # Model cache
  288. model_cache = weakref.WeakValueDictionary()
  289. model_cache_lock = threading.Lock()
  290. def LookupCreateModel(model_name, create_fn):
  291. model_cache_lock.acquire()
  292. try:
  293. model = model_cache[model_name]
  294. except:
  295. model = None
  296. if model is None:
  297. model = create_fn()
  298. model_cache[model_name] = model
  299. model_cache_lock.release()
  300. return model
  301. def LookupModel(model_name):
  302. model_cache_lock.acquire()
  303. try:
  304. model = model_cache[model_name]
  305. except:
  306. model = None
  307. model_cache_lock.release()
  308. return model
  309. # Find bar
  310. class FindBar():
  311. def __init__(self, parent, finder, is_reg_expr=False):
  312. self.finder = finder
  313. self.context = []
  314. self.last_value = None
  315. self.last_pattern = None
  316. label = QLabel("Find:")
  317. label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  318. self.textbox = QComboBox()
  319. self.textbox.setEditable(True)
  320. self.textbox.currentIndexChanged.connect(self.ValueChanged)
  321. self.progress = QProgressBar()
  322. self.progress.setRange(0, 0)
  323. self.progress.hide()
  324. if is_reg_expr:
  325. self.pattern = QCheckBox("Regular Expression")
  326. else:
  327. self.pattern = QCheckBox("Pattern")
  328. self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  329. self.next_button = QToolButton()
  330. self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
  331. self.next_button.released.connect(lambda: self.NextPrev(1))
  332. self.prev_button = QToolButton()
  333. self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
  334. self.prev_button.released.connect(lambda: self.NextPrev(-1))
  335. self.close_button = QToolButton()
  336. self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
  337. self.close_button.released.connect(self.Deactivate)
  338. self.hbox = QHBoxLayout()
  339. self.hbox.setContentsMargins(0, 0, 0, 0)
  340. self.hbox.addWidget(label)
  341. self.hbox.addWidget(self.textbox)
  342. self.hbox.addWidget(self.progress)
  343. self.hbox.addWidget(self.pattern)
  344. self.hbox.addWidget(self.next_button)
  345. self.hbox.addWidget(self.prev_button)
  346. self.hbox.addWidget(self.close_button)
  347. self.bar = QWidget()
  348. self.bar.setLayout(self.hbox)
  349. self.bar.hide()
  350. def Widget(self):
  351. return self.bar
  352. def Activate(self):
  353. self.bar.show()
  354. self.textbox.lineEdit().selectAll()
  355. self.textbox.setFocus()
  356. def Deactivate(self):
  357. self.bar.hide()
  358. def Busy(self):
  359. self.textbox.setEnabled(False)
  360. self.pattern.hide()
  361. self.next_button.hide()
  362. self.prev_button.hide()
  363. self.progress.show()
  364. def Idle(self):
  365. self.textbox.setEnabled(True)
  366. self.progress.hide()
  367. self.pattern.show()
  368. self.next_button.show()
  369. self.prev_button.show()
  370. def Find(self, direction):
  371. value = self.textbox.currentText()
  372. pattern = self.pattern.isChecked()
  373. self.last_value = value
  374. self.last_pattern = pattern
  375. self.finder.Find(value, direction, pattern, self.context)
  376. def ValueChanged(self):
  377. value = self.textbox.currentText()
  378. pattern = self.pattern.isChecked()
  379. index = self.textbox.currentIndex()
  380. data = self.textbox.itemData(index)
  381. # Store the pattern in the combo box to keep it with the text value
  382. if data == None:
  383. self.textbox.setItemData(index, pattern)
  384. else:
  385. self.pattern.setChecked(data)
  386. self.Find(0)
  387. def NextPrev(self, direction):
  388. value = self.textbox.currentText()
  389. pattern = self.pattern.isChecked()
  390. if value != self.last_value:
  391. index = self.textbox.findText(value)
  392. # Allow for a button press before the value has been added to the combo box
  393. if index < 0:
  394. index = self.textbox.count()
  395. self.textbox.addItem(value, pattern)
  396. self.textbox.setCurrentIndex(index)
  397. return
  398. else:
  399. self.textbox.setItemData(index, pattern)
  400. elif pattern != self.last_pattern:
  401. # Keep the pattern recorded in the combo box up to date
  402. index = self.textbox.currentIndex()
  403. self.textbox.setItemData(index, pattern)
  404. self.Find(direction)
  405. def NotFound(self):
  406. QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
  407. # Context-sensitive call graph data model item base
  408. class CallGraphLevelItemBase(object):
  409. def __init__(self, glb, params, row, parent_item):
  410. self.glb = glb
  411. self.params = params
  412. self.row = row
  413. self.parent_item = parent_item
  414. self.query_done = False
  415. self.child_count = 0
  416. self.child_items = []
  417. if parent_item:
  418. self.level = parent_item.level + 1
  419. else:
  420. self.level = 0
  421. def getChildItem(self, row):
  422. return self.child_items[row]
  423. def getParentItem(self):
  424. return self.parent_item
  425. def getRow(self):
  426. return self.row
  427. def childCount(self):
  428. if not self.query_done:
  429. self.Select()
  430. if not self.child_count:
  431. return -1
  432. return self.child_count
  433. def hasChildren(self):
  434. if not self.query_done:
  435. return True
  436. return self.child_count > 0
  437. def getData(self, column):
  438. return self.data[column]
  439. # Context-sensitive call graph data model level 2+ item base
  440. class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
  441. def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
  442. super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
  443. self.comm_id = comm_id
  444. self.thread_id = thread_id
  445. self.call_path_id = call_path_id
  446. self.insn_cnt = insn_cnt
  447. self.cyc_cnt = cyc_cnt
  448. self.branch_count = branch_count
  449. self.time = time
  450. def Select(self):
  451. self.query_done = True
  452. query = QSqlQuery(self.glb.db)
  453. if self.params.have_ipc:
  454. ipc_str = ", SUM(insn_count), SUM(cyc_count)"
  455. else:
  456. ipc_str = ""
  457. QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
  458. " FROM calls"
  459. " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
  460. " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
  461. " INNER JOIN dsos ON symbols.dso_id = dsos.id"
  462. " WHERE parent_call_path_id = " + str(self.call_path_id) +
  463. " AND comm_id = " + str(self.comm_id) +
  464. " AND thread_id = " + str(self.thread_id) +
  465. " GROUP BY call_path_id, name, short_name"
  466. " ORDER BY call_path_id")
  467. while query.next():
  468. if self.params.have_ipc:
  469. insn_cnt = int(query.value(5))
  470. cyc_cnt = int(query.value(6))
  471. branch_count = int(query.value(7))
  472. else:
  473. insn_cnt = 0
  474. cyc_cnt = 0
  475. branch_count = int(query.value(5))
  476. child_item = CallGraphLevelThreeItem(self.glb, self.params, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), insn_cnt, cyc_cnt, branch_count, self)
  477. self.child_items.append(child_item)
  478. self.child_count += 1
  479. # Context-sensitive call graph data model level three item
  480. class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
  481. def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
  482. super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
  483. dso = dsoname(dso)
  484. if self.params.have_ipc:
  485. insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
  486. cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
  487. br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
  488. ipc = CalcIPC(cyc_cnt, insn_cnt)
  489. self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
  490. else:
  491. self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
  492. self.dbid = call_path_id
  493. # Context-sensitive call graph data model level two item
  494. class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
  495. def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
  496. super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
  497. if self.params.have_ipc:
  498. self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
  499. else:
  500. self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
  501. self.dbid = thread_id
  502. def Select(self):
  503. super(CallGraphLevelTwoItem, self).Select()
  504. for child_item in self.child_items:
  505. self.time += child_item.time
  506. self.insn_cnt += child_item.insn_cnt
  507. self.cyc_cnt += child_item.cyc_cnt
  508. self.branch_count += child_item.branch_count
  509. for child_item in self.child_items:
  510. child_item.data[4] = PercentToOneDP(child_item.time, self.time)
  511. if self.params.have_ipc:
  512. child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
  513. child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
  514. child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
  515. else:
  516. child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
  517. # Context-sensitive call graph data model level one item
  518. class CallGraphLevelOneItem(CallGraphLevelItemBase):
  519. def __init__(self, glb, params, row, comm_id, comm, parent_item):
  520. super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
  521. if self.params.have_ipc:
  522. self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
  523. else:
  524. self.data = [comm, "", "", "", "", "", ""]
  525. self.dbid = comm_id
  526. def Select(self):
  527. self.query_done = True
  528. query = QSqlQuery(self.glb.db)
  529. QueryExec(query, "SELECT thread_id, pid, tid"
  530. " FROM comm_threads"
  531. " INNER JOIN threads ON thread_id = threads.id"
  532. " WHERE comm_id = " + str(self.dbid))
  533. while query.next():
  534. child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
  535. self.child_items.append(child_item)
  536. self.child_count += 1
  537. # Context-sensitive call graph data model root item
  538. class CallGraphRootItem(CallGraphLevelItemBase):
  539. def __init__(self, glb, params):
  540. super(CallGraphRootItem, self).__init__(glb, params, 0, None)
  541. self.dbid = 0
  542. self.query_done = True
  543. if_has_calls = ""
  544. if IsSelectable(glb.db, "comms", columns = "has_calls"):
  545. if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
  546. query = QSqlQuery(glb.db)
  547. QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
  548. while query.next():
  549. if not query.value(0):
  550. continue
  551. child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
  552. self.child_items.append(child_item)
  553. self.child_count += 1
  554. # Call graph model parameters
  555. class CallGraphModelParams():
  556. def __init__(self, glb, parent=None):
  557. self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
  558. # Context-sensitive call graph data model base
  559. class CallGraphModelBase(TreeModel):
  560. def __init__(self, glb, parent=None):
  561. super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
  562. def FindSelect(self, value, pattern, query):
  563. if pattern:
  564. # postgresql and sqlite pattern patching differences:
  565. # postgresql LIKE is case sensitive but sqlite LIKE is not
  566. # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
  567. # postgresql supports ILIKE which is case insensitive
  568. # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
  569. if not self.glb.dbref.is_sqlite3:
  570. # Escape % and _
  571. s = value.replace("%", "\\%")
  572. s = s.replace("_", "\\_")
  573. # Translate * and ? into SQL LIKE pattern characters % and _
  574. if sys.version_info[0] == 3:
  575. trans = str.maketrans("*?", "%_")
  576. else:
  577. trans = string.maketrans("*?", "%_")
  578. match = " LIKE '" + str(s).translate(trans) + "'"
  579. else:
  580. match = " GLOB '" + str(value) + "'"
  581. else:
  582. match = " = '" + str(value) + "'"
  583. self.DoFindSelect(query, match)
  584. def Found(self, query, found):
  585. if found:
  586. return self.FindPath(query)
  587. return []
  588. def FindValue(self, value, pattern, query, last_value, last_pattern):
  589. if last_value == value and pattern == last_pattern:
  590. found = query.first()
  591. else:
  592. self.FindSelect(value, pattern, query)
  593. found = query.next()
  594. return self.Found(query, found)
  595. def FindNext(self, query):
  596. found = query.next()
  597. if not found:
  598. found = query.first()
  599. return self.Found(query, found)
  600. def FindPrev(self, query):
  601. found = query.previous()
  602. if not found:
  603. found = query.last()
  604. return self.Found(query, found)
  605. def FindThread(self, c):
  606. if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
  607. ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
  608. elif c.direction > 0:
  609. ids = self.FindNext(c.query)
  610. else:
  611. ids = self.FindPrev(c.query)
  612. return (True, ids)
  613. def Find(self, value, direction, pattern, context, callback):
  614. class Context():
  615. def __init__(self, *x):
  616. self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
  617. def Update(self, *x):
  618. self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
  619. if len(context):
  620. context[0].Update(value, direction, pattern)
  621. else:
  622. context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
  623. # Use a thread so the UI is not blocked during the SELECT
  624. thread = Thread(self.FindThread, context[0])
  625. thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
  626. thread.start()
  627. def FindDone(self, thread, callback, ids):
  628. callback(ids)
  629. # Context-sensitive call graph data model
  630. class CallGraphModel(CallGraphModelBase):
  631. def __init__(self, glb, parent=None):
  632. super(CallGraphModel, self).__init__(glb, parent)
  633. def GetRoot(self):
  634. return CallGraphRootItem(self.glb, self.params)
  635. def columnCount(self, parent=None):
  636. if self.params.have_ipc:
  637. return 12
  638. else:
  639. return 7
  640. def columnHeader(self, column):
  641. if self.params.have_ipc:
  642. headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
  643. else:
  644. headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
  645. return headers[column]
  646. def columnAlignment(self, column):
  647. if self.params.have_ipc:
  648. alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
  649. else:
  650. alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
  651. return alignment[column]
  652. def DoFindSelect(self, query, match):
  653. QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
  654. " FROM calls"
  655. " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
  656. " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
  657. " WHERE calls.id <> 0"
  658. " AND symbols.name" + match +
  659. " GROUP BY comm_id, thread_id, call_path_id"
  660. " ORDER BY comm_id, thread_id, call_path_id")
  661. def FindPath(self, query):
  662. # Turn the query result into a list of ids that the tree view can walk
  663. # to open the tree at the right place.
  664. ids = []
  665. parent_id = query.value(0)
  666. while parent_id:
  667. ids.insert(0, parent_id)
  668. q2 = QSqlQuery(self.glb.db)
  669. QueryExec(q2, "SELECT parent_id"
  670. " FROM call_paths"
  671. " WHERE id = " + str(parent_id))
  672. if not q2.next():
  673. break
  674. parent_id = q2.value(0)
  675. # The call path root is not used
  676. if ids[0] == 1:
  677. del ids[0]
  678. ids.insert(0, query.value(2))
  679. ids.insert(0, query.value(1))
  680. return ids
  681. # Call tree data model level 2+ item base
  682. class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
  683. def __init__(self, glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
  684. super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
  685. self.comm_id = comm_id
  686. self.thread_id = thread_id
  687. self.calls_id = calls_id
  688. self.call_time = call_time
  689. self.time = time
  690. self.insn_cnt = insn_cnt
  691. self.cyc_cnt = cyc_cnt
  692. self.branch_count = branch_count
  693. def Select(self):
  694. self.query_done = True
  695. if self.calls_id == 0:
  696. comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
  697. else:
  698. comm_thread = ""
  699. if self.params.have_ipc:
  700. ipc_str = ", insn_count, cyc_count"
  701. else:
  702. ipc_str = ""
  703. query = QSqlQuery(self.glb.db)
  704. QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
  705. " FROM calls"
  706. " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
  707. " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
  708. " INNER JOIN dsos ON symbols.dso_id = dsos.id"
  709. " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
  710. " ORDER BY call_time, calls.id")
  711. while query.next():
  712. if self.params.have_ipc:
  713. insn_cnt = int(query.value(5))
  714. cyc_cnt = int(query.value(6))
  715. branch_count = int(query.value(7))
  716. else:
  717. insn_cnt = 0
  718. cyc_cnt = 0
  719. branch_count = int(query.value(5))
  720. child_item = CallTreeLevelThreeItem(self.glb, self.params, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), insn_cnt, cyc_cnt, branch_count, self)
  721. self.child_items.append(child_item)
  722. self.child_count += 1
  723. # Call tree data model level three item
  724. class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
  725. def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
  726. super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item)
  727. dso = dsoname(dso)
  728. if self.params.have_ipc:
  729. insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
  730. cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
  731. br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
  732. ipc = CalcIPC(cyc_cnt, insn_cnt)
  733. self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
  734. else:
  735. self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
  736. self.dbid = calls_id
  737. # Call tree data model level two item
  738. class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
  739. def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
  740. super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, 0, parent_item)
  741. if self.params.have_ipc:
  742. self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
  743. else:
  744. self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
  745. self.dbid = thread_id
  746. def Select(self):
  747. super(CallTreeLevelTwoItem, self).Select()
  748. for child_item in self.child_items:
  749. self.time += child_item.time
  750. self.insn_cnt += child_item.insn_cnt
  751. self.cyc_cnt += child_item.cyc_cnt
  752. self.branch_count += child_item.branch_count
  753. for child_item in self.child_items:
  754. child_item.data[4] = PercentToOneDP(child_item.time, self.time)
  755. if self.params.have_ipc:
  756. child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
  757. child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
  758. child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
  759. else:
  760. child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
  761. # Call tree data model level one item
  762. class CallTreeLevelOneItem(CallGraphLevelItemBase):
  763. def __init__(self, glb, params, row, comm_id, comm, parent_item):
  764. super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
  765. if self.params.have_ipc:
  766. self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
  767. else:
  768. self.data = [comm, "", "", "", "", "", ""]
  769. self.dbid = comm_id
  770. def Select(self):
  771. self.query_done = True
  772. query = QSqlQuery(self.glb.db)
  773. QueryExec(query, "SELECT thread_id, pid, tid"
  774. " FROM comm_threads"
  775. " INNER JOIN threads ON thread_id = threads.id"
  776. " WHERE comm_id = " + str(self.dbid))
  777. while query.next():
  778. child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
  779. self.child_items.append(child_item)
  780. self.child_count += 1
  781. # Call tree data model root item
  782. class CallTreeRootItem(CallGraphLevelItemBase):
  783. def __init__(self, glb, params):
  784. super(CallTreeRootItem, self).__init__(glb, params, 0, None)
  785. self.dbid = 0
  786. self.query_done = True
  787. if_has_calls = ""
  788. if IsSelectable(glb.db, "comms", columns = "has_calls"):
  789. if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
  790. query = QSqlQuery(glb.db)
  791. QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
  792. while query.next():
  793. if not query.value(0):
  794. continue
  795. child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
  796. self.child_items.append(child_item)
  797. self.child_count += 1
  798. # Call Tree data model
  799. class CallTreeModel(CallGraphModelBase):
  800. def __init__(self, glb, parent=None):
  801. super(CallTreeModel, self).__init__(glb, parent)
  802. def GetRoot(self):
  803. return CallTreeRootItem(self.glb, self.params)
  804. def columnCount(self, parent=None):
  805. if self.params.have_ipc:
  806. return 12
  807. else:
  808. return 7
  809. def columnHeader(self, column):
  810. if self.params.have_ipc:
  811. headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
  812. else:
  813. headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
  814. return headers[column]
  815. def columnAlignment(self, column):
  816. if self.params.have_ipc:
  817. alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
  818. else:
  819. alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
  820. return alignment[column]
  821. def DoFindSelect(self, query, match):
  822. QueryExec(query, "SELECT calls.id, comm_id, thread_id"
  823. " FROM calls"
  824. " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
  825. " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
  826. " WHERE calls.id <> 0"
  827. " AND symbols.name" + match +
  828. " ORDER BY comm_id, thread_id, call_time, calls.id")
  829. def FindPath(self, query):
  830. # Turn the query result into a list of ids that the tree view can walk
  831. # to open the tree at the right place.
  832. ids = []
  833. parent_id = query.value(0)
  834. while parent_id:
  835. ids.insert(0, parent_id)
  836. q2 = QSqlQuery(self.glb.db)
  837. QueryExec(q2, "SELECT parent_id"
  838. " FROM calls"
  839. " WHERE id = " + str(parent_id))
  840. if not q2.next():
  841. break
  842. parent_id = q2.value(0)
  843. ids.insert(0, query.value(2))
  844. ids.insert(0, query.value(1))
  845. return ids
  846. # Vertical layout
  847. class HBoxLayout(QHBoxLayout):
  848. def __init__(self, *children):
  849. super(HBoxLayout, self).__init__()
  850. self.layout().setContentsMargins(0, 0, 0, 0)
  851. for child in children:
  852. if child.isWidgetType():
  853. self.layout().addWidget(child)
  854. else:
  855. self.layout().addLayout(child)
  856. # Horizontal layout
  857. class VBoxLayout(QVBoxLayout):
  858. def __init__(self, *children):
  859. super(VBoxLayout, self).__init__()
  860. self.layout().setContentsMargins(0, 0, 0, 0)
  861. for child in children:
  862. if child.isWidgetType():
  863. self.layout().addWidget(child)
  864. else:
  865. self.layout().addLayout(child)
  866. # Vertical layout widget
  867. class VBox():
  868. def __init__(self, *children):
  869. self.vbox = QWidget()
  870. self.vbox.setLayout(VBoxLayout(*children))
  871. def Widget(self):
  872. return self.vbox
  873. # Tree window base
  874. class TreeWindowBase(QMdiSubWindow):
  875. def __init__(self, parent=None):
  876. super(TreeWindowBase, self).__init__(parent)
  877. self.model = None
  878. self.find_bar = None
  879. self.view = QTreeView()
  880. self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
  881. self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
  882. self.context_menu = TreeContextMenu(self.view)
  883. def DisplayFound(self, ids):
  884. if not len(ids):
  885. return False
  886. parent = QModelIndex()
  887. for dbid in ids:
  888. found = False
  889. n = self.model.rowCount(parent)
  890. for row in xrange(n):
  891. child = self.model.index(row, 0, parent)
  892. if child.internalPointer().dbid == dbid:
  893. found = True
  894. self.view.setExpanded(parent, True)
  895. self.view.setCurrentIndex(child)
  896. parent = child
  897. break
  898. if not found:
  899. break
  900. return found
  901. def Find(self, value, direction, pattern, context):
  902. self.view.setFocus()
  903. self.find_bar.Busy()
  904. self.model.Find(value, direction, pattern, context, self.FindDone)
  905. def FindDone(self, ids):
  906. found = True
  907. if not self.DisplayFound(ids):
  908. found = False
  909. self.find_bar.Idle()
  910. if not found:
  911. self.find_bar.NotFound()
  912. # Context-sensitive call graph window
  913. class CallGraphWindow(TreeWindowBase):
  914. def __init__(self, glb, parent=None):
  915. super(CallGraphWindow, self).__init__(parent)
  916. self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
  917. self.view.setModel(self.model)
  918. for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
  919. self.view.setColumnWidth(c, w)
  920. self.find_bar = FindBar(self, self)
  921. self.vbox = VBox(self.view, self.find_bar.Widget())
  922. self.setWidget(self.vbox.Widget())
  923. AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
  924. # Call tree window
  925. class CallTreeWindow(TreeWindowBase):
  926. def __init__(self, glb, parent=None, thread_at_time=None):
  927. super(CallTreeWindow, self).__init__(parent)
  928. self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
  929. self.view.setModel(self.model)
  930. for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
  931. self.view.setColumnWidth(c, w)
  932. self.find_bar = FindBar(self, self)
  933. self.vbox = VBox(self.view, self.find_bar.Widget())
  934. self.setWidget(self.vbox.Widget())
  935. AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
  936. if thread_at_time:
  937. self.DisplayThreadAtTime(*thread_at_time)
  938. def DisplayThreadAtTime(self, comm_id, thread_id, time):
  939. parent = QModelIndex()
  940. for dbid in (comm_id, thread_id):
  941. found = False
  942. n = self.model.rowCount(parent)
  943. for row in xrange(n):
  944. child = self.model.index(row, 0, parent)
  945. if child.internalPointer().dbid == dbid:
  946. found = True
  947. self.view.setExpanded(parent, True)
  948. self.view.setCurrentIndex(child)
  949. parent = child
  950. break
  951. if not found:
  952. return
  953. found = False
  954. while True:
  955. n = self.model.rowCount(parent)
  956. if not n:
  957. return
  958. last_child = None
  959. for row in xrange(n):
  960. self.view.setExpanded(parent, True)
  961. child = self.model.index(row, 0, parent)
  962. child_call_time = child.internalPointer().call_time
  963. if child_call_time < time:
  964. last_child = child
  965. elif child_call_time == time:
  966. self.view.setCurrentIndex(child)
  967. return
  968. elif child_call_time > time:
  969. break
  970. if not last_child:
  971. if not found:
  972. child = self.model.index(0, 0, parent)
  973. self.view.setExpanded(parent, True)
  974. self.view.setCurrentIndex(child)
  975. return
  976. found = True
  977. self.view.setExpanded(parent, True)
  978. self.view.setCurrentIndex(last_child)
  979. parent = last_child
  980. # ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
  981. def ExecComm(db, thread_id, time):
  982. query = QSqlQuery(db)
  983. QueryExec(query, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag"
  984. " FROM comm_threads"
  985. " INNER JOIN comms ON comms.id = comm_threads.comm_id"
  986. " WHERE comm_threads.thread_id = " + str(thread_id) +
  987. " ORDER BY comms.c_time, comms.id")
  988. first = None
  989. last = None
  990. while query.next():
  991. if first is None:
  992. first = query.value(0)
  993. if query.value(2) and Decimal(query.value(1)) <= Decimal(time):
  994. last = query.value(0)
  995. if not(last is None):
  996. return last
  997. return first
  998. # Container for (x, y) data
  999. class XY():
  1000. def __init__(self, x=0, y=0):
  1001. self.x = x
  1002. self.y = y
  1003. def __str__(self):
  1004. return "XY({}, {})".format(str(self.x), str(self.y))
  1005. # Container for sub-range data
  1006. class Subrange():
  1007. def __init__(self, lo=0, hi=0):
  1008. self.lo = lo
  1009. self.hi = hi
  1010. def __str__(self):
  1011. return "Subrange({}, {})".format(str(self.lo), str(self.hi))
  1012. # Graph data region base class
  1013. class GraphDataRegion(object):
  1014. def __init__(self, key, title = "", ordinal = ""):
  1015. self.key = key
  1016. self.title = title
  1017. self.ordinal = ordinal
  1018. # Function to sort GraphDataRegion
  1019. def GraphDataRegionOrdinal(data_region):
  1020. return data_region.ordinal
  1021. # Attributes for a graph region
  1022. class GraphRegionAttribute():
  1023. def __init__(self, colour):
  1024. self.colour = colour
  1025. # Switch graph data region represents a task
  1026. class SwitchGraphDataRegion(GraphDataRegion):
  1027. def __init__(self, key, exec_comm_id, pid, tid, comm, thread_id, comm_id):
  1028. super(SwitchGraphDataRegion, self).__init__(key)
  1029. self.title = str(pid) + " / " + str(tid) + " " + comm
  1030. # Order graph legend within exec comm by pid / tid / time
  1031. self.ordinal = str(pid).rjust(16) + str(exec_comm_id).rjust(8) + str(tid).rjust(16)
  1032. self.exec_comm_id = exec_comm_id
  1033. self.pid = pid
  1034. self.tid = tid
  1035. self.comm = comm
  1036. self.thread_id = thread_id
  1037. self.comm_id = comm_id
  1038. # Graph data point
  1039. class GraphDataPoint():
  1040. def __init__(self, data, index, x, y, altx=None, alty=None, hregion=None, vregion=None):
  1041. self.data = data
  1042. self.index = index
  1043. self.x = x
  1044. self.y = y
  1045. self.altx = altx
  1046. self.alty = alty
  1047. self.hregion = hregion
  1048. self.vregion = vregion
  1049. # Graph data (single graph) base class
  1050. class GraphData(object):
  1051. def __init__(self, collection, xbase=Decimal(0), ybase=Decimal(0)):
  1052. self.collection = collection
  1053. self.points = []
  1054. self.xbase = xbase
  1055. self.ybase = ybase
  1056. self.title = ""
  1057. def AddPoint(self, x, y, altx=None, alty=None, hregion=None, vregion=None):
  1058. index = len(self.points)
  1059. x = float(Decimal(x) - self.xbase)
  1060. y = float(Decimal(y) - self.ybase)
  1061. self.points.append(GraphDataPoint(self, index, x, y, altx, alty, hregion, vregion))
  1062. def XToData(self, x):
  1063. return Decimal(x) + self.xbase
  1064. def YToData(self, y):
  1065. return Decimal(y) + self.ybase
  1066. # Switch graph data (for one CPU)
  1067. class SwitchGraphData(GraphData):
  1068. def __init__(self, db, collection, cpu, xbase):
  1069. super(SwitchGraphData, self).__init__(collection, xbase)
  1070. self.cpu = cpu
  1071. self.title = "CPU " + str(cpu)
  1072. self.SelectSwitches(db)
  1073. def SelectComms(self, db, thread_id, last_comm_id, start_time, end_time):
  1074. query = QSqlQuery(db)
  1075. QueryExec(query, "SELECT id, c_time"
  1076. " FROM comms"
  1077. " WHERE c_thread_id = " + str(thread_id) +
  1078. " AND exec_flag = " + self.collection.glb.dbref.TRUE +
  1079. " AND c_time >= " + str(start_time) +
  1080. " AND c_time <= " + str(end_time) +
  1081. " ORDER BY c_time, id")
  1082. while query.next():
  1083. comm_id = query.value(0)
  1084. if comm_id == last_comm_id:
  1085. continue
  1086. time = query.value(1)
  1087. hregion = self.HRegion(db, thread_id, comm_id, time)
  1088. self.AddPoint(time, 1000, None, None, hregion)
  1089. def SelectSwitches(self, db):
  1090. last_time = None
  1091. last_comm_id = None
  1092. last_thread_id = None
  1093. query = QSqlQuery(db)
  1094. QueryExec(query, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags"
  1095. " FROM context_switches"
  1096. " WHERE machine_id = " + str(self.collection.machine_id) +
  1097. " AND cpu = " + str(self.cpu) +
  1098. " ORDER BY time, id")
  1099. while query.next():
  1100. flags = int(query.value(5))
  1101. if flags & 1:
  1102. # Schedule-out: detect and add exec's
  1103. if last_thread_id == query.value(1) and last_comm_id is not None and last_comm_id != query.value(3):
  1104. self.SelectComms(db, last_thread_id, last_comm_id, last_time, query.value(0))
  1105. continue
  1106. # Schedule-in: add data point
  1107. if len(self.points) == 0:
  1108. start_time = self.collection.glb.StartTime(self.collection.machine_id)
  1109. hregion = self.HRegion(db, query.value(1), query.value(3), start_time)
  1110. self.AddPoint(start_time, 1000, None, None, hregion)
  1111. time = query.value(0)
  1112. comm_id = query.value(4)
  1113. thread_id = query.value(2)
  1114. hregion = self.HRegion(db, thread_id, comm_id, time)
  1115. self.AddPoint(time, 1000, None, None, hregion)
  1116. last_time = time
  1117. last_comm_id = comm_id
  1118. last_thread_id = thread_id
  1119. def NewHRegion(self, db, key, thread_id, comm_id, time):
  1120. exec_comm_id = ExecComm(db, thread_id, time)
  1121. query = QSqlQuery(db)
  1122. QueryExec(query, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id))
  1123. if query.next():
  1124. pid = query.value(0)
  1125. tid = query.value(1)
  1126. else:
  1127. pid = -1
  1128. tid = -1
  1129. query = QSqlQuery(db)
  1130. QueryExec(query, "SELECT comm FROM comms WHERE id = " + str(comm_id))
  1131. if query.next():
  1132. comm = query.value(0)
  1133. else:
  1134. comm = ""
  1135. return SwitchGraphDataRegion(key, exec_comm_id, pid, tid, comm, thread_id, comm_id)
  1136. def HRegion(self, db, thread_id, comm_id, time):
  1137. key = str(thread_id) + ":" + str(comm_id)
  1138. hregion = self.collection.LookupHRegion(key)
  1139. if hregion is None:
  1140. hregion = self.NewHRegion(db, key, thread_id, comm_id, time)
  1141. self.collection.AddHRegion(key, hregion)
  1142. return hregion
  1143. # Graph data collection (multiple related graphs) base class
  1144. class GraphDataCollection(object):
  1145. def __init__(self, glb):
  1146. self.glb = glb
  1147. self.data = []
  1148. self.hregions = {}
  1149. self.xrangelo = None
  1150. self.xrangehi = None
  1151. self.yrangelo = None
  1152. self.yrangehi = None
  1153. self.dp = XY(0, 0)
  1154. def AddGraphData(self, data):
  1155. self.data.append(data)
  1156. def LookupHRegion(self, key):
  1157. if key in self.hregions:
  1158. return self.hregions[key]
  1159. return None
  1160. def AddHRegion(self, key, hregion):
  1161. self.hregions[key] = hregion
  1162. # Switch graph data collection (SwitchGraphData for each CPU)
  1163. class SwitchGraphDataCollection(GraphDataCollection):
  1164. def __init__(self, glb, db, machine_id):
  1165. super(SwitchGraphDataCollection, self).__init__(glb)
  1166. self.machine_id = machine_id
  1167. self.cpus = self.SelectCPUs(db)
  1168. self.xrangelo = glb.StartTime(machine_id)
  1169. self.xrangehi = glb.FinishTime(machine_id)
  1170. self.yrangelo = Decimal(0)
  1171. self.yrangehi = Decimal(1000)
  1172. for cpu in self.cpus:
  1173. self.AddGraphData(SwitchGraphData(db, self, cpu, self.xrangelo))
  1174. def SelectCPUs(self, db):
  1175. cpus = []
  1176. query = QSqlQuery(db)
  1177. QueryExec(query, "SELECT DISTINCT cpu"
  1178. " FROM context_switches"
  1179. " WHERE machine_id = " + str(self.machine_id))
  1180. while query.next():
  1181. cpus.append(int(query.value(0)))
  1182. return sorted(cpus)
  1183. # Switch graph data graphics item displays the graphed data
  1184. class SwitchGraphDataGraphicsItem(QGraphicsItem):
  1185. def __init__(self, data, graph_width, graph_height, attrs, event_handler, parent=None):
  1186. super(SwitchGraphDataGraphicsItem, self).__init__(parent)
  1187. self.data = data
  1188. self.graph_width = graph_width
  1189. self.graph_height = graph_height
  1190. self.attrs = attrs
  1191. self.event_handler = event_handler
  1192. self.setAcceptHoverEvents(True)
  1193. def boundingRect(self):
  1194. return QRectF(0, 0, self.graph_width, self.graph_height)
  1195. def PaintPoint(self, painter, last, x):
  1196. if not(last is None or last.hregion.pid == 0 or x < self.attrs.subrange.x.lo):
  1197. if last.x < self.attrs.subrange.x.lo:
  1198. x0 = self.attrs.subrange.x.lo
  1199. else:
  1200. x0 = last.x
  1201. if x > self.attrs.subrange.x.hi:
  1202. x1 = self.attrs.subrange.x.hi
  1203. else:
  1204. x1 = x - 1
  1205. x0 = self.attrs.XToPixel(x0)
  1206. x1 = self.attrs.XToPixel(x1)
  1207. y0 = self.attrs.YToPixel(last.y)
  1208. colour = self.attrs.region_attributes[last.hregion.key].colour
  1209. width = x1 - x0 + 1
  1210. if width < 2:
  1211. painter.setPen(colour)
  1212. painter.drawLine(x0, self.graph_height - y0, x0, self.graph_height)
  1213. else:
  1214. painter.fillRect(x0, self.graph_height - y0, width, self.graph_height - 1, colour)
  1215. def paint(self, painter, option, widget):
  1216. last = None
  1217. for point in self.data.points:
  1218. self.PaintPoint(painter, last, point.x)
  1219. if point.x > self.attrs.subrange.x.hi:
  1220. break;
  1221. last = point
  1222. self.PaintPoint(painter, last, self.attrs.subrange.x.hi + 1)
  1223. def BinarySearchPoint(self, target):
  1224. lower_pos = 0
  1225. higher_pos = len(self.data.points)
  1226. while True:
  1227. pos = int((lower_pos + higher_pos) / 2)
  1228. val = self.data.points[pos].x
  1229. if target >= val:
  1230. lower_pos = pos
  1231. else:
  1232. higher_pos = pos
  1233. if higher_pos <= lower_pos + 1:
  1234. return lower_pos
  1235. def XPixelToData(self, x):
  1236. x = self.attrs.PixelToX(x)
  1237. if x < self.data.points[0].x:
  1238. x = 0
  1239. pos = 0
  1240. low = True
  1241. else:
  1242. pos = self.BinarySearchPoint(x)
  1243. low = False
  1244. return (low, pos, self.data.XToData(x))
  1245. def EventToData(self, event):
  1246. no_data = (None,) * 4
  1247. if len(self.data.points) < 1:
  1248. return no_data
  1249. x = event.pos().x()
  1250. if x < 0:
  1251. return no_data
  1252. low0, pos0, time_from = self.XPixelToData(x)
  1253. low1, pos1, time_to = self.XPixelToData(x + 1)
  1254. hregions = set()
  1255. hregion_times = []
  1256. if not low1:
  1257. for i in xrange(pos0, pos1 + 1):
  1258. hregion = self.data.points[i].hregion
  1259. hregions.add(hregion)
  1260. if i == pos0:
  1261. time = time_from
  1262. else:
  1263. time = self.data.XToData(self.data.points[i].x)
  1264. hregion_times.append((hregion, time))
  1265. return (time_from, time_to, hregions, hregion_times)
  1266. def hoverMoveEvent(self, event):
  1267. time_from, time_to, hregions, hregion_times = self.EventToData(event)
  1268. if time_from is not None:
  1269. self.event_handler.PointEvent(self.data.cpu, time_from, time_to, hregions)
  1270. def hoverLeaveEvent(self, event):
  1271. self.event_handler.NoPointEvent()
  1272. def mousePressEvent(self, event):
  1273. if event.button() != Qt.RightButton:
  1274. super(SwitchGraphDataGraphicsItem, self).mousePressEvent(event)
  1275. return
  1276. time_from, time_to, hregions, hregion_times = self.EventToData(event)
  1277. if hregion_times:
  1278. self.event_handler.RightClickEvent(self.data.cpu, hregion_times, event.screenPos())
  1279. # X-axis graphics item
  1280. class XAxisGraphicsItem(QGraphicsItem):
  1281. def __init__(self, width, parent=None):
  1282. super(XAxisGraphicsItem, self).__init__(parent)
  1283. self.width = width
  1284. self.max_mark_sz = 4
  1285. self.height = self.max_mark_sz + 1
  1286. def boundingRect(self):
  1287. return QRectF(0, 0, self.width, self.height)
  1288. def Step(self):
  1289. attrs = self.parentItem().attrs
  1290. subrange = attrs.subrange.x
  1291. t = subrange.hi - subrange.lo
  1292. s = (3.0 * t) / self.width
  1293. n = 1.0
  1294. while s > n:
  1295. n = n * 10.0
  1296. return n
  1297. def PaintMarks(self, painter, at_y, lo, hi, step, i):
  1298. attrs = self.parentItem().attrs
  1299. x = lo
  1300. while x <= hi:
  1301. xp = attrs.XToPixel(x)
  1302. if i % 10:
  1303. if i % 5:
  1304. sz = 1
  1305. else:
  1306. sz = 2
  1307. else:
  1308. sz = self.max_mark_sz
  1309. i = 0
  1310. painter.drawLine(xp, at_y, xp, at_y + sz)
  1311. x += step
  1312. i += 1
  1313. def paint(self, painter, option, widget):
  1314. # Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
  1315. painter.drawLine(0, 0, self.width - 1, 0)
  1316. n = self.Step()
  1317. attrs = self.parentItem().attrs
  1318. subrange = attrs.subrange.x
  1319. if subrange.lo:
  1320. x_offset = n - (subrange.lo % n)
  1321. else:
  1322. x_offset = 0.0
  1323. x = subrange.lo + x_offset
  1324. i = (x / n) % 10
  1325. self.PaintMarks(painter, 0, x, subrange.hi, n, i)
  1326. def ScaleDimensions(self):
  1327. n = self.Step()
  1328. attrs = self.parentItem().attrs
  1329. lo = attrs.subrange.x.lo
  1330. hi = (n * 10.0) + lo
  1331. width = attrs.XToPixel(hi)
  1332. if width > 500:
  1333. width = 0
  1334. return (n, lo, hi, width)
  1335. def PaintScale(self, painter, at_x, at_y):
  1336. n, lo, hi, width = self.ScaleDimensions()
  1337. if not width:
  1338. return
  1339. painter.drawLine(at_x, at_y, at_x + width, at_y)
  1340. self.PaintMarks(painter, at_y, lo, hi, n, 0)
  1341. def ScaleWidth(self):
  1342. n, lo, hi, width = self.ScaleDimensions()
  1343. return width
  1344. def ScaleHeight(self):
  1345. return self.height
  1346. def ScaleUnit(self):
  1347. return self.Step() * 10
  1348. # Scale graphics item base class
  1349. class ScaleGraphicsItem(QGraphicsItem):
  1350. def __init__(self, axis, parent=None):
  1351. super(ScaleGraphicsItem, self).__init__(parent)
  1352. self.axis = axis
  1353. def boundingRect(self):
  1354. scale_width = self.axis.ScaleWidth()
  1355. if not scale_width:
  1356. return QRectF()
  1357. return QRectF(0, 0, self.axis.ScaleWidth() + 100, self.axis.ScaleHeight())
  1358. def paint(self, painter, option, widget):
  1359. scale_width = self.axis.ScaleWidth()
  1360. if not scale_width:
  1361. return
  1362. self.axis.PaintScale(painter, 0, 5)
  1363. x = scale_width + 4
  1364. painter.drawText(QPointF(x, 10), self.Text())
  1365. def Unit(self):
  1366. return self.axis.ScaleUnit()
  1367. def Text(self):
  1368. return ""
  1369. # Switch graph scale graphics item
  1370. class SwitchScaleGraphicsItem(ScaleGraphicsItem):
  1371. def __init__(self, axis, parent=None):
  1372. super(SwitchScaleGraphicsItem, self).__init__(axis, parent)
  1373. def Text(self):
  1374. unit = self.Unit()
  1375. if unit >= 1000000000:
  1376. unit = int(unit / 1000000000)
  1377. us = "s"
  1378. elif unit >= 1000000:
  1379. unit = int(unit / 1000000)
  1380. us = "ms"
  1381. elif unit >= 1000:
  1382. unit = int(unit / 1000)
  1383. us = "us"
  1384. else:
  1385. unit = int(unit)
  1386. us = "ns"
  1387. return " = " + str(unit) + " " + us
  1388. # Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
  1389. class SwitchGraphGraphicsItem(QGraphicsItem):
  1390. def __init__(self, collection, data, attrs, event_handler, first, parent=None):
  1391. super(SwitchGraphGraphicsItem, self).__init__(parent)
  1392. self.collection = collection
  1393. self.data = data
  1394. self.attrs = attrs
  1395. self.event_handler = event_handler
  1396. margin = 20
  1397. title_width = 50
  1398. self.title_graphics = QGraphicsSimpleTextItem(data.title, self)
  1399. self.title_graphics.setPos(margin, margin)
  1400. graph_width = attrs.XToPixel(attrs.subrange.x.hi) + 1
  1401. graph_height = attrs.YToPixel(attrs.subrange.y.hi) + 1
  1402. self.graph_origin_x = margin + title_width + margin
  1403. self.graph_origin_y = graph_height + margin
  1404. x_axis_size = 1
  1405. y_axis_size = 1
  1406. self.yline = QGraphicsLineItem(0, 0, 0, graph_height, self)
  1407. self.x_axis = XAxisGraphicsItem(graph_width, self)
  1408. self.x_axis.setPos(self.graph_origin_x, self.graph_origin_y + 1)
  1409. if first:
  1410. self.scale_item = SwitchScaleGraphicsItem(self.x_axis, self)
  1411. self.scale_item.setPos(self.graph_origin_x, self.graph_origin_y + 10)
  1412. self.yline.setPos(self.graph_origin_x - y_axis_size, self.graph_origin_y - graph_height)
  1413. self.axis_point = QGraphicsLineItem(0, 0, 0, 0, self)
  1414. self.axis_point.setPos(self.graph_origin_x - 1, self.graph_origin_y +1)
  1415. self.width = self.graph_origin_x + graph_width + margin
  1416. self.height = self.graph_origin_y + margin
  1417. self.graph = SwitchGraphDataGraphicsItem(data, graph_width, graph_height, attrs, event_handler, self)
  1418. self.graph.setPos(self.graph_origin_x, self.graph_origin_y - graph_height)
  1419. if parent and 'EnableRubberBand' in dir(parent):
  1420. parent.EnableRubberBand(self.graph_origin_x, self.graph_origin_x + graph_width - 1, self)
  1421. def boundingRect(self):
  1422. return QRectF(0, 0, self.width, self.height)
  1423. def paint(self, painter, option, widget):
  1424. pass
  1425. def RBXToPixel(self, x):
  1426. return self.attrs.PixelToX(x - self.graph_origin_x)
  1427. def RBXRangeToPixel(self, x0, x1):
  1428. return (self.RBXToPixel(x0), self.RBXToPixel(x1 + 1))
  1429. def RBPixelToTime(self, x):
  1430. if x < self.data.points[0].x:
  1431. return self.data.XToData(0)
  1432. return self.data.XToData(x)
  1433. def RBEventTimes(self, x0, x1):
  1434. x0, x1 = self.RBXRangeToPixel(x0, x1)
  1435. time_from = self.RBPixelToTime(x0)
  1436. time_to = self.RBPixelToTime(x1)
  1437. return (time_from, time_to)
  1438. def RBEvent(self, x0, x1):
  1439. time_from, time_to = self.RBEventTimes(x0, x1)
  1440. self.event_handler.RangeEvent(time_from, time_to)
  1441. def RBMoveEvent(self, x0, x1):
  1442. if x1 < x0:
  1443. x0, x1 = x1, x0
  1444. self.RBEvent(x0, x1)
  1445. def RBReleaseEvent(self, x0, x1, selection_state):
  1446. if x1 < x0:
  1447. x0, x1 = x1, x0
  1448. x0, x1 = self.RBXRangeToPixel(x0, x1)
  1449. self.event_handler.SelectEvent(x0, x1, selection_state)
  1450. # Graphics item to draw a vertical bracket (used to highlight "forward" sub-range)
  1451. class VerticalBracketGraphicsItem(QGraphicsItem):
  1452. def __init__(self, parent=None):
  1453. super(VerticalBracketGraphicsItem, self).__init__(parent)
  1454. self.width = 0
  1455. self.height = 0
  1456. self.hide()
  1457. def SetSize(self, width, height):
  1458. self.width = width + 1
  1459. self.height = height + 1
  1460. def boundingRect(self):
  1461. return QRectF(0, 0, self.width, self.height)
  1462. def paint(self, painter, option, widget):
  1463. colour = QColor(255, 255, 0, 32)
  1464. painter.fillRect(0, 0, self.width, self.height, colour)
  1465. x1 = self.width - 1
  1466. y1 = self.height - 1
  1467. painter.drawLine(0, 0, x1, 0)
  1468. painter.drawLine(0, 0, 0, 3)
  1469. painter.drawLine(x1, 0, x1, 3)
  1470. painter.drawLine(0, y1, x1, y1)
  1471. painter.drawLine(0, y1, 0, y1 - 3)
  1472. painter.drawLine(x1, y1, x1, y1 - 3)
  1473. # Graphics item to contain graphs arranged vertically
  1474. class VertcalGraphSetGraphicsItem(QGraphicsItem):
  1475. def __init__(self, collection, attrs, event_handler, child_class, parent=None):
  1476. super(VertcalGraphSetGraphicsItem, self).__init__(parent)
  1477. self.collection = collection
  1478. self.top = 10
  1479. self.width = 0
  1480. self.height = self.top
  1481. self.rubber_band = None
  1482. self.rb_enabled = False
  1483. first = True
  1484. for data in collection.data:
  1485. child = child_class(collection, data, attrs, event_handler, first, self)
  1486. child.setPos(0, self.height + 1)
  1487. rect = child.boundingRect()
  1488. if rect.right() > self.width:
  1489. self.width = rect.right()
  1490. self.height = self.height + rect.bottom() + 1
  1491. first = False
  1492. self.bracket = VerticalBracketGraphicsItem(self)
  1493. def EnableRubberBand(self, xlo, xhi, rb_event_handler):
  1494. if self.rb_enabled:
  1495. return
  1496. self.rb_enabled = True
  1497. self.rb_in_view = False
  1498. self.setAcceptedMouseButtons(Qt.LeftButton)
  1499. self.rb_xlo = xlo
  1500. self.rb_xhi = xhi
  1501. self.rb_event_handler = rb_event_handler
  1502. self.mousePressEvent = self.MousePressEvent
  1503. self.mouseMoveEvent = self.MouseMoveEvent
  1504. self.mouseReleaseEvent = self.MouseReleaseEvent
  1505. def boundingRect(self):
  1506. return QRectF(0, 0, self.width, self.height)
  1507. def paint(self, painter, option, widget):
  1508. pass
  1509. def RubberBandParent(self):
  1510. scene = self.scene()
  1511. view = scene.views()[0]
  1512. viewport = view.viewport()
  1513. return viewport
  1514. def RubberBandSetGeometry(self, rect):
  1515. scene_rectf = self.mapRectToScene(QRectF(rect))
  1516. scene = self.scene()
  1517. view = scene.views()[0]
  1518. poly = view.mapFromScene(scene_rectf)
  1519. self.rubber_band.setGeometry(poly.boundingRect())
  1520. def SetSelection(self, selection_state):
  1521. if self.rubber_band:
  1522. if selection_state:
  1523. self.RubberBandSetGeometry(selection_state)
  1524. self.rubber_band.show()
  1525. else:
  1526. self.rubber_band.hide()
  1527. def SetBracket(self, rect):
  1528. if rect:
  1529. x, y, width, height = rect.x(), rect.y(), rect.width(), rect.height()
  1530. self.bracket.setPos(x, y)
  1531. self.bracket.SetSize(width, height)
  1532. self.bracket.show()
  1533. else:
  1534. self.bracket.hide()
  1535. def RubberBandX(self, event):
  1536. x = event.pos().toPoint().x()
  1537. if x < self.rb_xlo:
  1538. x = self.rb_xlo
  1539. elif x > self.rb_xhi:
  1540. x = self.rb_xhi
  1541. else:
  1542. self.rb_in_view = True
  1543. return x
  1544. def RubberBandRect(self, x):
  1545. if self.rb_origin.x() <= x:
  1546. width = x - self.rb_origin.x()
  1547. rect = QRect(self.rb_origin, QSize(width, self.height))
  1548. else:
  1549. width = self.rb_origin.x() - x
  1550. top_left = QPoint(self.rb_origin.x() - width, self.rb_origin.y())
  1551. rect = QRect(top_left, QSize(width, self.height))
  1552. return rect
  1553. def MousePressEvent(self, event):
  1554. self.rb_in_view = False
  1555. x = self.RubberBandX(event)
  1556. self.rb_origin = QPoint(x, self.top)
  1557. if self.rubber_band is None:
  1558. self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.RubberBandParent())
  1559. self.RubberBandSetGeometry(QRect(self.rb_origin, QSize(0, self.height)))
  1560. if self.rb_in_view:
  1561. self.rubber_band.show()
  1562. self.rb_event_handler.RBMoveEvent(x, x)
  1563. else:
  1564. self.rubber_band.hide()
  1565. def MouseMoveEvent(self, event):
  1566. x = self.RubberBandX(event)
  1567. rect = self.RubberBandRect(x)
  1568. self.RubberBandSetGeometry(rect)
  1569. if self.rb_in_view:
  1570. self.rubber_band.show()
  1571. self.rb_event_handler.RBMoveEvent(self.rb_origin.x(), x)
  1572. def MouseReleaseEvent(self, event):
  1573. x = self.RubberBandX(event)
  1574. if self.rb_in_view:
  1575. selection_state = self.RubberBandRect(x)
  1576. else:
  1577. selection_state = None
  1578. self.rb_event_handler.RBReleaseEvent(self.rb_origin.x(), x, selection_state)
  1579. # Switch graph legend data model
  1580. class SwitchGraphLegendModel(QAbstractTableModel):
  1581. def __init__(self, collection, region_attributes, parent=None):
  1582. super(SwitchGraphLegendModel, self).__init__(parent)
  1583. self.region_attributes = region_attributes
  1584. self.child_items = sorted(collection.hregions.values(), key=GraphDataRegionOrdinal)
  1585. self.child_count = len(self.child_items)
  1586. self.highlight_set = set()
  1587. self.column_headers = ("pid", "tid", "comm")
  1588. def rowCount(self, parent):
  1589. return self.child_count
  1590. def headerData(self, section, orientation, role):
  1591. if role != Qt.DisplayRole:
  1592. return None
  1593. if orientation != Qt.Horizontal:
  1594. return None
  1595. return self.columnHeader(section)
  1596. def index(self, row, column, parent):
  1597. return self.createIndex(row, column, self.child_items[row])
  1598. def columnCount(self, parent=None):
  1599. return len(self.column_headers)
  1600. def columnHeader(self, column):
  1601. return self.column_headers[column]
  1602. def data(self, index, role):
  1603. if role == Qt.BackgroundRole:
  1604. child = self.child_items[index.row()]
  1605. if child in self.highlight_set:
  1606. return self.region_attributes[child.key].colour
  1607. return None
  1608. if role == Qt.ForegroundRole:
  1609. child = self.child_items[index.row()]
  1610. if child in self.highlight_set:
  1611. return QColor(255, 255, 255)
  1612. return self.region_attributes[child.key].colour
  1613. if role != Qt.DisplayRole:
  1614. return None
  1615. hregion = self.child_items[index.row()]
  1616. col = index.column()
  1617. if col == 0:
  1618. return hregion.pid
  1619. if col == 1:
  1620. return hregion.tid
  1621. if col == 2:
  1622. return hregion.comm
  1623. return None
  1624. def SetHighlight(self, row, set_highlight):
  1625. child = self.child_items[row]
  1626. top_left = self.createIndex(row, 0, child)
  1627. bottom_right = self.createIndex(row, len(self.column_headers) - 1, child)
  1628. self.dataChanged.emit(top_left, bottom_right)
  1629. def Highlight(self, highlight_set):
  1630. for row in xrange(self.child_count):
  1631. child = self.child_items[row]
  1632. if child in self.highlight_set:
  1633. if child not in highlight_set:
  1634. self.SetHighlight(row, False)
  1635. elif child in highlight_set:
  1636. self.SetHighlight(row, True)
  1637. self.highlight_set = highlight_set
  1638. # Switch graph legend is a table
  1639. class SwitchGraphLegend(QWidget):
  1640. def __init__(self, collection, region_attributes, parent=None):
  1641. super(SwitchGraphLegend, self).__init__(parent)
  1642. self.data_model = SwitchGraphLegendModel(collection, region_attributes)
  1643. self.model = QSortFilterProxyModel()
  1644. self.model.setSourceModel(self.data_model)
  1645. self.view = QTableView()
  1646. self.view.setModel(self.model)
  1647. self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
  1648. self.view.verticalHeader().setVisible(False)
  1649. self.view.sortByColumn(-1, Qt.AscendingOrder)
  1650. self.view.setSortingEnabled(True)
  1651. self.view.resizeColumnsToContents()
  1652. self.view.resizeRowsToContents()
  1653. self.vbox = VBoxLayout(self.view)
  1654. self.setLayout(self.vbox)
  1655. sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2
  1656. sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width()
  1657. self.saved_size = sz1
  1658. def resizeEvent(self, event):
  1659. self.saved_size = self.size().width()
  1660. super(SwitchGraphLegend, self).resizeEvent(event)
  1661. def Highlight(self, highlight_set):
  1662. self.data_model.Highlight(highlight_set)
  1663. self.update()
  1664. def changeEvent(self, event):
  1665. if event.type() == QEvent.FontChange:
  1666. self.view.resizeRowsToContents()
  1667. self.view.resizeColumnsToContents()
  1668. # Need to resize rows again after column resize
  1669. self.view.resizeRowsToContents()
  1670. super(SwitchGraphLegend, self).changeEvent(event)
  1671. # Random colour generation
  1672. def RGBColourTooLight(r, g, b):
  1673. if g > 230:
  1674. return True
  1675. if g <= 160:
  1676. return False
  1677. if r <= 180 and g <= 180:
  1678. return False
  1679. if r < 60:
  1680. return False
  1681. return True
  1682. def GenerateColours(x):
  1683. cs = [0]
  1684. for i in xrange(1, x):
  1685. cs.append(int((255.0 / i) + 0.5))
  1686. colours = []
  1687. for r in cs:
  1688. for g in cs:
  1689. for b in cs:
  1690. # Exclude black and colours that look too light against a white background
  1691. if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b):
  1692. continue
  1693. colours.append(QColor(r, g, b))
  1694. return colours
  1695. def GenerateNColours(n):
  1696. for x in xrange(2, n + 2):
  1697. colours = GenerateColours(x)
  1698. if len(colours) >= n:
  1699. return colours
  1700. return []
  1701. def GenerateNRandomColours(n, seed):
  1702. colours = GenerateNColours(n)
  1703. random.seed(seed)
  1704. random.shuffle(colours)
  1705. return colours
  1706. # Graph attributes, in particular the scale and subrange that change when zooming
  1707. class GraphAttributes():
  1708. def __init__(self, scale, subrange, region_attributes, dp):
  1709. self.scale = scale
  1710. self.subrange = subrange
  1711. self.region_attributes = region_attributes
  1712. # Rounding avoids errors due to finite floating point precision
  1713. self.dp = dp # data decimal places
  1714. self.Update()
  1715. def XToPixel(self, x):
  1716. return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x))
  1717. def YToPixel(self, y):
  1718. return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y))
  1719. def PixelToXRounded(self, px):
  1720. return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo
  1721. def PixelToYRounded(self, py):
  1722. return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo
  1723. def PixelToX(self, px):
  1724. x = self.PixelToXRounded(px)
  1725. if self.pdp.x == 0:
  1726. rt = self.XToPixel(x)
  1727. if rt > px:
  1728. return x - 1
  1729. return x
  1730. def PixelToY(self, py):
  1731. y = self.PixelToYRounded(py)
  1732. if self.pdp.y == 0:
  1733. rt = self.YToPixel(y)
  1734. if rt > py:
  1735. return y - 1
  1736. return y
  1737. def ToPDP(self, dp, scale):
  1738. # Calculate pixel decimal places:
  1739. # (10 ** dp) is the minimum delta in the data
  1740. # scale it to get the minimum delta in pixels
  1741. # log10 gives the number of decimals places negatively
  1742. # subtrace 1 to divide by 10
  1743. # round to the lower negative number
  1744. # change the sign to get the number of decimals positively
  1745. x = math.log10((10 ** dp) * scale)
  1746. if x < 0:
  1747. x -= 1
  1748. x = -int(math.floor(x) - 0.1)
  1749. else:
  1750. x = 0
  1751. return x
  1752. def Update(self):
  1753. x = self.ToPDP(self.dp.x, self.scale.x)
  1754. y = self.ToPDP(self.dp.y, self.scale.y)
  1755. self.pdp = XY(x, y) # pixel decimal places
  1756. # Switch graph splitter which divides the CPU graphs from the legend
  1757. class SwitchGraphSplitter(QSplitter):
  1758. def __init__(self, parent=None):
  1759. super(SwitchGraphSplitter, self).__init__(parent)
  1760. self.first_time = False
  1761. def resizeEvent(self, ev):
  1762. if self.first_time:
  1763. self.first_time = False
  1764. sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2
  1765. sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width()
  1766. sz0 = self.size().width() - self.handleWidth() - sz1
  1767. self.setSizes([sz0, sz1])
  1768. elif not(self.widget(1).saved_size is None):
  1769. sz1 = self.widget(1).saved_size
  1770. sz0 = self.size().width() - self.handleWidth() - sz1
  1771. self.setSizes([sz0, sz1])
  1772. super(SwitchGraphSplitter, self).resizeEvent(ev)
  1773. # Graph widget base class
  1774. class GraphWidget(QWidget):
  1775. graph_title_changed = Signal(object)
  1776. def __init__(self, parent=None):
  1777. super(GraphWidget, self).__init__(parent)
  1778. def GraphTitleChanged(self, title):
  1779. self.graph_title_changed.emit(title)
  1780. def Title(self):
  1781. return ""
  1782. # Display time in s, ms, us or ns
  1783. def ToTimeStr(val):
  1784. val = Decimal(val)
  1785. if val >= 1000000000:
  1786. return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001")))
  1787. if val >= 1000000:
  1788. return "{} ms".format((val / 1000000).quantize(Decimal("0.000001")))
  1789. if val >= 1000:
  1790. return "{} us".format((val / 1000).quantize(Decimal("0.001")))
  1791. return "{} ns".format(val.quantize(Decimal("1")))
  1792. # Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons
  1793. class SwitchGraphWidget(GraphWidget):
  1794. def __init__(self, glb, collection, parent=None):
  1795. super(SwitchGraphWidget, self).__init__(parent)
  1796. self.glb = glb
  1797. self.collection = collection
  1798. self.back_state = []
  1799. self.forward_state = []
  1800. self.selection_state = (None, None)
  1801. self.fwd_rect = None
  1802. self.start_time = self.glb.StartTime(collection.machine_id)
  1803. i = 0
  1804. hregions = collection.hregions.values()
  1805. colours = GenerateNRandomColours(len(hregions), 1013)
  1806. region_attributes = {}
  1807. for hregion in hregions:
  1808. if hregion.pid == 0 and hregion.tid == 0:
  1809. region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0))
  1810. else:
  1811. region_attributes[hregion.key] = GraphRegionAttribute(colours[i])
  1812. i = i + 1
  1813. # Default to entire range
  1814. xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0)
  1815. ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0)
  1816. subrange = XY(xsubrange, ysubrange)
  1817. scale = self.GetScaleForRange(subrange)
  1818. self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp)
  1819. self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem)
  1820. self.scene = QGraphicsScene()
  1821. self.scene.addItem(self.item)
  1822. self.view = QGraphicsView(self.scene)
  1823. self.view.centerOn(0, 0)
  1824. self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop)
  1825. self.legend = SwitchGraphLegend(collection, region_attributes)
  1826. self.splitter = SwitchGraphSplitter()
  1827. self.splitter.addWidget(self.view)
  1828. self.splitter.addWidget(self.legend)
  1829. self.point_label = QLabel("")
  1830. self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
  1831. self.back_button = QToolButton()
  1832. self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft))
  1833. self.back_button.setDisabled(True)
  1834. self.back_button.released.connect(lambda: self.Back())
  1835. self.forward_button = QToolButton()
  1836. self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight))
  1837. self.forward_button.setDisabled(True)
  1838. self.forward_button.released.connect(lambda: self.Forward())
  1839. self.zoom_button = QToolButton()
  1840. self.zoom_button.setText("Zoom")
  1841. self.zoom_button.setDisabled(True)
  1842. self.zoom_button.released.connect(lambda: self.Zoom())
  1843. self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label)
  1844. self.vbox = VBoxLayout(self.splitter, self.hbox)
  1845. self.setLayout(self.vbox)
  1846. def GetScaleForRangeX(self, xsubrange):
  1847. # Default graph 1000 pixels wide
  1848. dflt = 1000.0
  1849. r = xsubrange.hi - xsubrange.lo
  1850. return dflt / r
  1851. def GetScaleForRangeY(self, ysubrange):
  1852. # Default graph 50 pixels high
  1853. dflt = 50.0
  1854. r = ysubrange.hi - ysubrange.lo
  1855. return dflt / r
  1856. def GetScaleForRange(self, subrange):
  1857. # Default graph 1000 pixels wide, 50 pixels high
  1858. xscale = self.GetScaleForRangeX(subrange.x)
  1859. yscale = self.GetScaleForRangeY(subrange.y)
  1860. return XY(xscale, yscale)
  1861. def PointEvent(self, cpu, time_from, time_to, hregions):
  1862. text = "CPU: " + str(cpu)
  1863. time_from = time_from.quantize(Decimal(1))
  1864. rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id)
  1865. text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")"
  1866. self.point_label.setText(text)
  1867. self.legend.Highlight(hregions)
  1868. def RightClickEvent(self, cpu, hregion_times, pos):
  1869. if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"):
  1870. return
  1871. menu = QMenu(self.view)
  1872. for hregion, time in hregion_times:
  1873. thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time)
  1874. menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time)
  1875. menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view))
  1876. menu.exec_(pos)
  1877. def RightClickSelect(self, args):
  1878. CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args)
  1879. def NoPointEvent(self):
  1880. self.point_label.setText("")
  1881. self.legend.Highlight({})
  1882. def RangeEvent(self, time_from, time_to):
  1883. time_from = time_from.quantize(Decimal(1))
  1884. time_to = time_to.quantize(Decimal(1))
  1885. if time_to <= time_from:
  1886. self.point_label.setText("")
  1887. return
  1888. rel_time_from = time_from - self.start_time
  1889. rel_time_to = time_to - self.start_time
  1890. text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")"
  1891. text = text + " duration: " + ToTimeStr(time_to - time_from)
  1892. self.point_label.setText(text)
  1893. def BackState(self):
  1894. return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect)
  1895. def PushBackState(self):
  1896. state = copy.deepcopy(self.BackState())
  1897. self.back_state.append(state)
  1898. self.back_button.setEnabled(True)
  1899. def PopBackState(self):
  1900. self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop()
  1901. self.attrs.Update()
  1902. if not self.back_state:
  1903. self.back_button.setDisabled(True)
  1904. def PushForwardState(self):
  1905. state = copy.deepcopy(self.BackState())
  1906. self.forward_state.append(state)
  1907. self.forward_button.setEnabled(True)
  1908. def PopForwardState(self):
  1909. self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop()
  1910. self.attrs.Update()
  1911. if not self.forward_state:
  1912. self.forward_button.setDisabled(True)
  1913. def Title(self):
  1914. time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo)
  1915. time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi)
  1916. rel_time_from = time_from - self.start_time
  1917. rel_time_to = time_to - self.start_time
  1918. title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to)
  1919. title = title + " (" + ToTimeStr(time_to - time_from) + ")"
  1920. return title
  1921. def Update(self):
  1922. selected_subrange, selection_state = self.selection_state
  1923. self.item.SetSelection(selection_state)
  1924. self.item.SetBracket(self.fwd_rect)
  1925. self.zoom_button.setDisabled(selected_subrange is None)
  1926. self.GraphTitleChanged(self.Title())
  1927. self.item.update(self.item.boundingRect())
  1928. def Back(self):
  1929. if not self.back_state:
  1930. return
  1931. self.PushForwardState()
  1932. self.PopBackState()
  1933. self.Update()
  1934. def Forward(self):
  1935. if not self.forward_state:
  1936. return
  1937. self.PushBackState()
  1938. self.PopForwardState()
  1939. self.Update()
  1940. def SelectEvent(self, x0, x1, selection_state):
  1941. if selection_state is None:
  1942. selected_subrange = None
  1943. else:
  1944. if x1 - x0 < 1.0:
  1945. x1 += 1.0
  1946. selected_subrange = Subrange(x0, x1)
  1947. self.selection_state = (selected_subrange, selection_state)
  1948. self.zoom_button.setDisabled(selected_subrange is None)
  1949. def Zoom(self):
  1950. selected_subrange, selection_state = self.selection_state
  1951. if selected_subrange is None:
  1952. return
  1953. self.fwd_rect = selection_state
  1954. self.item.SetSelection(None)
  1955. self.PushBackState()
  1956. self.attrs.subrange.x = selected_subrange
  1957. self.forward_state = []
  1958. self.forward_button.setDisabled(True)
  1959. self.selection_state = (None, None)
  1960. self.fwd_rect = None
  1961. self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x)
  1962. self.attrs.Update()
  1963. self.Update()
  1964. # Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting
  1965. class SlowInitClass():
  1966. def __init__(self, glb, title, init_fn):
  1967. self.init_fn = init_fn
  1968. self.done = False
  1969. self.result = None
  1970. self.msg_box = QMessageBox(glb.mainwindow)
  1971. self.msg_box.setText("Initializing " + title + ". Please wait.")
  1972. self.msg_box.setWindowTitle("Initializing " + title)
  1973. self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation))
  1974. self.init_thread = Thread(self.ThreadFn, glb)
  1975. self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection)
  1976. self.init_thread.start()
  1977. def Done(self):
  1978. self.msg_box.done(0)
  1979. def ThreadFn(self, glb):
  1980. conn_name = "SlowInitClass" + str(os.getpid())
  1981. db, dbname = glb.dbref.Open(conn_name)
  1982. self.result = self.init_fn(db)
  1983. self.done = True
  1984. return (True, 0)
  1985. def Result(self):
  1986. while not self.done:
  1987. self.msg_box.exec_()
  1988. self.init_thread.wait()
  1989. return self.result
  1990. def SlowInit(glb, title, init_fn):
  1991. init = SlowInitClass(glb, title, init_fn)
  1992. return init.Result()
  1993. # Time chart by CPU window
  1994. class TimeChartByCPUWindow(QMdiSubWindow):
  1995. def __init__(self, glb, parent=None):
  1996. super(TimeChartByCPUWindow, self).__init__(parent)
  1997. self.glb = glb
  1998. self.machine_id = glb.HostMachineId()
  1999. self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id)
  2000. collection = LookupModel(self.collection_name)
  2001. if collection is None:
  2002. collection = SlowInit(glb, "Time Chart", self.Init)
  2003. self.widget = SwitchGraphWidget(glb, collection, self)
  2004. self.view = self.widget
  2005. self.base_title = "Time Chart by CPU"
  2006. self.setWindowTitle(self.base_title + self.widget.Title())
  2007. self.widget.graph_title_changed.connect(self.GraphTitleChanged)
  2008. self.setWidget(self.widget)
  2009. AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle())
  2010. def Init(self, db):
  2011. return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id))
  2012. def GraphTitleChanged(self, title):
  2013. self.setWindowTitle(self.base_title + " : " + title)
  2014. # Child data item finder
  2015. class ChildDataItemFinder():
  2016. def __init__(self, root):
  2017. self.root = root
  2018. self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
  2019. self.rows = []
  2020. self.pos = 0
  2021. def FindSelect(self):
  2022. self.rows = []
  2023. if self.pattern:
  2024. pattern = re.compile(self.value)
  2025. for child in self.root.child_items:
  2026. for column_data in child.data:
  2027. if re.search(pattern, str(column_data)) is not None:
  2028. self.rows.append(child.row)
  2029. break
  2030. else:
  2031. for child in self.root.child_items:
  2032. for column_data in child.data:
  2033. if self.value in str(column_data):
  2034. self.rows.append(child.row)
  2035. break
  2036. def FindValue(self):
  2037. self.pos = 0
  2038. if self.last_value != self.value or self.pattern != self.last_pattern:
  2039. self.FindSelect()
  2040. if not len(self.rows):
  2041. return -1
  2042. return self.rows[self.pos]
  2043. def FindThread(self):
  2044. if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
  2045. row = self.FindValue()
  2046. elif len(self.rows):
  2047. if self.direction > 0:
  2048. self.pos += 1
  2049. if self.pos >= len(self.rows):
  2050. self.pos = 0
  2051. else:
  2052. self.pos -= 1
  2053. if self.pos < 0:
  2054. self.pos = len(self.rows) - 1
  2055. row = self.rows[self.pos]
  2056. else:
  2057. row = -1
  2058. return (True, row)
  2059. def Find(self, value, direction, pattern, context, callback):
  2060. self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
  2061. # Use a thread so the UI is not blocked
  2062. thread = Thread(self.FindThread)
  2063. thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
  2064. thread.start()
  2065. def FindDone(self, thread, callback, row):
  2066. callback(row)
  2067. # Number of database records to fetch in one go
  2068. glb_chunk_sz = 10000
  2069. # Background process for SQL data fetcher
  2070. class SQLFetcherProcess():
  2071. def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
  2072. # Need a unique connection name
  2073. conn_name = "SQLFetcher" + str(os.getpid())
  2074. self.db, dbname = dbref.Open(conn_name)
  2075. self.sql = sql
  2076. self.buffer = buffer
  2077. self.head = head
  2078. self.tail = tail
  2079. self.fetch_count = fetch_count
  2080. self.fetching_done = fetching_done
  2081. self.process_target = process_target
  2082. self.wait_event = wait_event
  2083. self.fetched_event = fetched_event
  2084. self.prep = prep
  2085. self.query = QSqlQuery(self.db)
  2086. self.query_limit = 0 if "$$last_id$$" in sql else 2
  2087. self.last_id = -1
  2088. self.fetched = 0
  2089. self.more = True
  2090. self.local_head = self.head.value
  2091. self.local_tail = self.tail.value
  2092. def Select(self):
  2093. if self.query_limit:
  2094. if self.query_limit == 1:
  2095. return
  2096. self.query_limit -= 1
  2097. stmt = self.sql.replace("$$last_id$$", str(self.last_id))
  2098. QueryExec(self.query, stmt)
  2099. def Next(self):
  2100. if not self.query.next():
  2101. self.Select()
  2102. if not self.query.next():
  2103. return None
  2104. self.last_id = self.query.value(0)
  2105. return self.prep(self.query)
  2106. def WaitForTarget(self):
  2107. while True:
  2108. self.wait_event.clear()
  2109. target = self.process_target.value
  2110. if target > self.fetched or target < 0:
  2111. break
  2112. self.wait_event.wait()
  2113. return target
  2114. def HasSpace(self, sz):
  2115. if self.local_tail <= self.local_head:
  2116. space = len(self.buffer) - self.local_head
  2117. if space > sz:
  2118. return True
  2119. if space >= glb_nsz:
  2120. # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
  2121. nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
  2122. self.buffer[self.local_head : self.local_head + len(nd)] = nd
  2123. self.local_head = 0
  2124. if self.local_tail - self.local_head > sz:
  2125. return True
  2126. return False
  2127. def WaitForSpace(self, sz):
  2128. if self.HasSpace(sz):
  2129. return
  2130. while True:
  2131. self.wait_event.clear()
  2132. self.local_tail = self.tail.value
  2133. if self.HasSpace(sz):
  2134. return
  2135. self.wait_event.wait()
  2136. def AddToBuffer(self, obj):
  2137. d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
  2138. n = len(d)
  2139. nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
  2140. sz = n + glb_nsz
  2141. self.WaitForSpace(sz)
  2142. pos = self.local_head
  2143. self.buffer[pos : pos + len(nd)] = nd
  2144. self.buffer[pos + glb_nsz : pos + sz] = d
  2145. self.local_head += sz
  2146. def FetchBatch(self, batch_size):
  2147. fetched = 0
  2148. while batch_size > fetched:
  2149. obj = self.Next()
  2150. if obj is None:
  2151. self.more = False
  2152. break
  2153. self.AddToBuffer(obj)
  2154. fetched += 1
  2155. if fetched:
  2156. self.fetched += fetched
  2157. with self.fetch_count.get_lock():
  2158. self.fetch_count.value += fetched
  2159. self.head.value = self.local_head
  2160. self.fetched_event.set()
  2161. def Run(self):
  2162. while self.more:
  2163. target = self.WaitForTarget()
  2164. if target < 0:
  2165. break
  2166. batch_size = min(glb_chunk_sz, target - self.fetched)
  2167. self.FetchBatch(batch_size)
  2168. self.fetching_done.value = True
  2169. self.fetched_event.set()
  2170. def SQLFetcherFn(*x):
  2171. process = SQLFetcherProcess(*x)
  2172. process.Run()
  2173. # SQL data fetcher
  2174. class SQLFetcher(QObject):
  2175. done = Signal(object)
  2176. def __init__(self, glb, sql, prep, process_data, parent=None):
  2177. super(SQLFetcher, self).__init__(parent)
  2178. self.process_data = process_data
  2179. self.more = True
  2180. self.target = 0
  2181. self.last_target = 0
  2182. self.fetched = 0
  2183. self.buffer_size = 16 * 1024 * 1024
  2184. self.buffer = Array(c_char, self.buffer_size, lock=False)
  2185. self.head = Value(c_longlong)
  2186. self.tail = Value(c_longlong)
  2187. self.local_tail = 0
  2188. self.fetch_count = Value(c_longlong)
  2189. self.fetching_done = Value(c_bool)
  2190. self.last_count = 0
  2191. self.process_target = Value(c_longlong)
  2192. self.wait_event = Event()
  2193. self.fetched_event = Event()
  2194. glb.AddInstanceToShutdownOnExit(self)
  2195. self.process = Process(target=SQLFetcherFn, args=(glb.dbref, sql, self.buffer, self.head, self.tail, self.fetch_count, self.fetching_done, self.process_target, self.wait_event, self.fetched_event, prep))
  2196. self.process.start()
  2197. self.thread = Thread(self.Thread)
  2198. self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
  2199. self.thread.start()
  2200. def Shutdown(self):
  2201. # Tell the thread and process to exit
  2202. self.process_target.value = -1
  2203. self.wait_event.set()
  2204. self.more = False
  2205. self.fetching_done.value = True
  2206. self.fetched_event.set()
  2207. def Thread(self):
  2208. if not self.more:
  2209. return True, 0
  2210. while True:
  2211. self.fetched_event.clear()
  2212. fetch_count = self.fetch_count.value
  2213. if fetch_count != self.last_count:
  2214. break
  2215. if self.fetching_done.value:
  2216. self.more = False
  2217. return True, 0
  2218. self.fetched_event.wait()
  2219. count = fetch_count - self.last_count
  2220. self.last_count = fetch_count
  2221. self.fetched += count
  2222. return False, count
  2223. def Fetch(self, nr):
  2224. if not self.more:
  2225. # -1 inidcates there are no more
  2226. return -1
  2227. result = self.fetched
  2228. extra = result + nr - self.target
  2229. if extra > 0:
  2230. self.target += extra
  2231. # process_target < 0 indicates shutting down
  2232. if self.process_target.value >= 0:
  2233. self.process_target.value = self.target
  2234. self.wait_event.set()
  2235. return result
  2236. def RemoveFromBuffer(self):
  2237. pos = self.local_tail
  2238. if len(self.buffer) - pos < glb_nsz:
  2239. pos = 0
  2240. n = pickle.loads(self.buffer[pos : pos + glb_nsz])
  2241. if n == 0:
  2242. pos = 0
  2243. n = pickle.loads(self.buffer[0 : glb_nsz])
  2244. pos += glb_nsz
  2245. obj = pickle.loads(self.buffer[pos : pos + n])
  2246. self.local_tail = pos + n
  2247. return obj
  2248. def ProcessData(self, count):
  2249. for i in xrange(count):
  2250. obj = self.RemoveFromBuffer()
  2251. self.process_data(obj)
  2252. self.tail.value = self.local_tail
  2253. self.wait_event.set()
  2254. self.done.emit(count)
  2255. # Fetch more records bar
  2256. class FetchMoreRecordsBar():
  2257. def __init__(self, model, parent):
  2258. self.model = model
  2259. self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
  2260. self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  2261. self.fetch_count = QSpinBox()
  2262. self.fetch_count.setRange(1, 1000000)
  2263. self.fetch_count.setValue(10)
  2264. self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  2265. self.fetch = QPushButton("Go!")
  2266. self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  2267. self.fetch.released.connect(self.FetchMoreRecords)
  2268. self.progress = QProgressBar()
  2269. self.progress.setRange(0, 100)
  2270. self.progress.hide()
  2271. self.done_label = QLabel("All records fetched")
  2272. self.done_label.hide()
  2273. self.spacer = QLabel("")
  2274. self.close_button = QToolButton()
  2275. self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
  2276. self.close_button.released.connect(self.Deactivate)
  2277. self.hbox = QHBoxLayout()
  2278. self.hbox.setContentsMargins(0, 0, 0, 0)
  2279. self.hbox.addWidget(self.label)
  2280. self.hbox.addWidget(self.fetch_count)
  2281. self.hbox.addWidget(self.fetch)
  2282. self.hbox.addWidget(self.spacer)
  2283. self.hbox.addWidget(self.progress)
  2284. self.hbox.addWidget(self.done_label)
  2285. self.hbox.addWidget(self.close_button)
  2286. self.bar = QWidget()
  2287. self.bar.setLayout(self.hbox)
  2288. self.bar.show()
  2289. self.in_progress = False
  2290. self.model.progress.connect(self.Progress)
  2291. self.done = False
  2292. if not model.HasMoreRecords():
  2293. self.Done()
  2294. def Widget(self):
  2295. return self.bar
  2296. def Activate(self):
  2297. self.bar.show()
  2298. self.fetch.setFocus()
  2299. def Deactivate(self):
  2300. self.bar.hide()
  2301. def Enable(self, enable):
  2302. self.fetch.setEnabled(enable)
  2303. self.fetch_count.setEnabled(enable)
  2304. def Busy(self):
  2305. self.Enable(False)
  2306. self.fetch.hide()
  2307. self.spacer.hide()
  2308. self.progress.show()
  2309. def Idle(self):
  2310. self.in_progress = False
  2311. self.Enable(True)
  2312. self.progress.hide()
  2313. self.fetch.show()
  2314. self.spacer.show()
  2315. def Target(self):
  2316. return self.fetch_count.value() * glb_chunk_sz
  2317. def Done(self):
  2318. self.done = True
  2319. self.Idle()
  2320. self.label.hide()
  2321. self.fetch_count.hide()
  2322. self.fetch.hide()
  2323. self.spacer.hide()
  2324. self.done_label.show()
  2325. def Progress(self, count):
  2326. if self.in_progress:
  2327. if count:
  2328. percent = ((count - self.start) * 100) / self.Target()
  2329. if percent >= 100:
  2330. self.Idle()
  2331. else:
  2332. self.progress.setValue(percent)
  2333. if not count:
  2334. # Count value of zero means no more records
  2335. self.Done()
  2336. def FetchMoreRecords(self):
  2337. if self.done:
  2338. return
  2339. self.progress.setValue(0)
  2340. self.Busy()
  2341. self.in_progress = True
  2342. self.start = self.model.FetchMoreRecords(self.Target())
  2343. # Brance data model level two item
  2344. class BranchLevelTwoItem():
  2345. def __init__(self, row, col, text, parent_item):
  2346. self.row = row
  2347. self.parent_item = parent_item
  2348. self.data = [""] * (col + 1)
  2349. self.data[col] = text
  2350. self.level = 2
  2351. def getParentItem(self):
  2352. return self.parent_item
  2353. def getRow(self):
  2354. return self.row
  2355. def childCount(self):
  2356. return 0
  2357. def hasChildren(self):
  2358. return False
  2359. def getData(self, column):
  2360. return self.data[column]
  2361. # Brance data model level one item
  2362. class BranchLevelOneItem():
  2363. def __init__(self, glb, row, data, parent_item):
  2364. self.glb = glb
  2365. self.row = row
  2366. self.parent_item = parent_item
  2367. self.child_count = 0
  2368. self.child_items = []
  2369. self.data = data[1:]
  2370. self.dbid = data[0]
  2371. self.level = 1
  2372. self.query_done = False
  2373. self.br_col = len(self.data) - 1
  2374. def getChildItem(self, row):
  2375. return self.child_items[row]
  2376. def getParentItem(self):
  2377. return self.parent_item
  2378. def getRow(self):
  2379. return self.row
  2380. def Select(self):
  2381. self.query_done = True
  2382. if not self.glb.have_disassembler:
  2383. return
  2384. query = QSqlQuery(self.glb.db)
  2385. QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
  2386. " FROM samples"
  2387. " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
  2388. " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
  2389. " WHERE samples.id = " + str(self.dbid))
  2390. if not query.next():
  2391. return
  2392. cpu = query.value(0)
  2393. dso = query.value(1)
  2394. sym = query.value(2)
  2395. if dso == 0 or sym == 0:
  2396. return
  2397. off = query.value(3)
  2398. short_name = query.value(4)
  2399. long_name = query.value(5)
  2400. build_id = query.value(6)
  2401. sym_start = query.value(7)
  2402. ip = query.value(8)
  2403. QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
  2404. " FROM samples"
  2405. " INNER JOIN symbols ON samples.symbol_id = symbols.id"
  2406. " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
  2407. " ORDER BY samples.id"
  2408. " LIMIT 1")
  2409. if not query.next():
  2410. return
  2411. if query.value(0) != dso:
  2412. # Cannot disassemble from one dso to another
  2413. return
  2414. bsym = query.value(1)
  2415. boff = query.value(2)
  2416. bsym_start = query.value(3)
  2417. if bsym == 0:
  2418. return
  2419. tot = bsym_start + boff + 1 - sym_start - off
  2420. if tot <= 0 or tot > 16384:
  2421. return
  2422. inst = self.glb.disassembler.Instruction()
  2423. f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
  2424. if not f:
  2425. return
  2426. mode = 0 if Is64Bit(f) else 1
  2427. self.glb.disassembler.SetMode(inst, mode)
  2428. buf_sz = tot + 16
  2429. buf = create_string_buffer(tot + 16)
  2430. f.seek(sym_start + off)
  2431. buf.value = f.read(buf_sz)
  2432. buf_ptr = addressof(buf)
  2433. i = 0
  2434. while tot > 0:
  2435. cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
  2436. if cnt:
  2437. byte_str = tohex(ip).rjust(16)
  2438. for k in xrange(cnt):
  2439. byte_str += " %02x" % ord(buf[i])
  2440. i += 1
  2441. while k < 15:
  2442. byte_str += " "
  2443. k += 1
  2444. self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
  2445. self.child_count += 1
  2446. else:
  2447. return
  2448. buf_ptr += cnt
  2449. tot -= cnt
  2450. buf_sz -= cnt
  2451. ip += cnt
  2452. def childCount(self):
  2453. if not self.query_done:
  2454. self.Select()
  2455. if not self.child_count:
  2456. return -1
  2457. return self.child_count
  2458. def hasChildren(self):
  2459. if not self.query_done:
  2460. return True
  2461. return self.child_count > 0
  2462. def getData(self, column):
  2463. return self.data[column]
  2464. # Brance data model root item
  2465. class BranchRootItem():
  2466. def __init__(self):
  2467. self.child_count = 0
  2468. self.child_items = []
  2469. self.level = 0
  2470. def getChildItem(self, row):
  2471. return self.child_items[row]
  2472. def getParentItem(self):
  2473. return None
  2474. def getRow(self):
  2475. return 0
  2476. def childCount(self):
  2477. return self.child_count
  2478. def hasChildren(self):
  2479. return self.child_count > 0
  2480. def getData(self, column):
  2481. return ""
  2482. # Calculate instructions per cycle
  2483. def CalcIPC(cyc_cnt, insn_cnt):
  2484. if cyc_cnt and insn_cnt:
  2485. ipc = Decimal(float(insn_cnt) / cyc_cnt)
  2486. ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
  2487. else:
  2488. ipc = "0"
  2489. return ipc
  2490. # Branch data preparation
  2491. def BranchDataPrepBr(query, data):
  2492. data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
  2493. " (" + dsoname(query.value(11)) + ")" + " -> " +
  2494. tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
  2495. " (" + dsoname(query.value(15)) + ")")
  2496. def BranchDataPrepIPC(query, data):
  2497. insn_cnt = query.value(16)
  2498. cyc_cnt = query.value(17)
  2499. ipc = CalcIPC(cyc_cnt, insn_cnt)
  2500. data.append(insn_cnt)
  2501. data.append(cyc_cnt)
  2502. data.append(ipc)
  2503. def BranchDataPrep(query):
  2504. data = []
  2505. for i in xrange(0, 8):
  2506. data.append(query.value(i))
  2507. BranchDataPrepBr(query, data)
  2508. return data
  2509. def BranchDataPrepWA(query):
  2510. data = []
  2511. data.append(query.value(0))
  2512. # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
  2513. data.append("{:>19}".format(query.value(1)))
  2514. for i in xrange(2, 8):
  2515. data.append(query.value(i))
  2516. BranchDataPrepBr(query, data)
  2517. return data
  2518. def BranchDataWithIPCPrep(query):
  2519. data = []
  2520. for i in xrange(0, 8):
  2521. data.append(query.value(i))
  2522. BranchDataPrepIPC(query, data)
  2523. BranchDataPrepBr(query, data)
  2524. return data
  2525. def BranchDataWithIPCPrepWA(query):
  2526. data = []
  2527. data.append(query.value(0))
  2528. # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
  2529. data.append("{:>19}".format(query.value(1)))
  2530. for i in xrange(2, 8):
  2531. data.append(query.value(i))
  2532. BranchDataPrepIPC(query, data)
  2533. BranchDataPrepBr(query, data)
  2534. return data
  2535. # Branch data model
  2536. class BranchModel(TreeModel):
  2537. progress = Signal(object)
  2538. def __init__(self, glb, event_id, where_clause, parent=None):
  2539. super(BranchModel, self).__init__(glb, None, parent)
  2540. self.event_id = event_id
  2541. self.more = True
  2542. self.populated = 0
  2543. self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
  2544. if self.have_ipc:
  2545. select_ipc = ", insn_count, cyc_count"
  2546. prep_fn = BranchDataWithIPCPrep
  2547. prep_wa_fn = BranchDataWithIPCPrepWA
  2548. else:
  2549. select_ipc = ""
  2550. prep_fn = BranchDataPrep
  2551. prep_wa_fn = BranchDataPrepWA
  2552. sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
  2553. " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
  2554. " ip, symbols.name, sym_offset, dsos.short_name,"
  2555. " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
  2556. + select_ipc +
  2557. " FROM samples"
  2558. " INNER JOIN comms ON comm_id = comms.id"
  2559. " INNER JOIN threads ON thread_id = threads.id"
  2560. " INNER JOIN branch_types ON branch_type = branch_types.id"
  2561. " INNER JOIN symbols ON symbol_id = symbols.id"
  2562. " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
  2563. " INNER JOIN dsos ON samples.dso_id = dsos.id"
  2564. " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
  2565. " WHERE samples.id > $$last_id$$" + where_clause +
  2566. " AND evsel_id = " + str(self.event_id) +
  2567. " ORDER BY samples.id"
  2568. " LIMIT " + str(glb_chunk_sz))
  2569. if pyside_version_1 and sys.version_info[0] == 3:
  2570. prep = prep_fn
  2571. else:
  2572. prep = prep_wa_fn
  2573. self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
  2574. self.fetcher.done.connect(self.Update)
  2575. self.fetcher.Fetch(glb_chunk_sz)
  2576. def GetRoot(self):
  2577. return BranchRootItem()
  2578. def columnCount(self, parent=None):
  2579. if self.have_ipc:
  2580. return 11
  2581. else:
  2582. return 8
  2583. def columnHeader(self, column):
  2584. if self.have_ipc:
  2585. return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
  2586. else:
  2587. return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
  2588. def columnFont(self, column):
  2589. if self.have_ipc:
  2590. br_col = 10
  2591. else:
  2592. br_col = 7
  2593. if column != br_col:
  2594. return None
  2595. return QFont("Monospace")
  2596. def DisplayData(self, item, index):
  2597. if item.level == 1:
  2598. self.FetchIfNeeded(item.row)
  2599. return item.getData(index.column())
  2600. def AddSample(self, data):
  2601. child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
  2602. self.root.child_items.append(child)
  2603. self.populated += 1
  2604. def Update(self, fetched):
  2605. if not fetched:
  2606. self.more = False
  2607. self.progress.emit(0)
  2608. child_count = self.root.child_count
  2609. count = self.populated - child_count
  2610. if count > 0:
  2611. parent = QModelIndex()
  2612. self.beginInsertRows(parent, child_count, child_count + count - 1)
  2613. self.insertRows(child_count, count, parent)
  2614. self.root.child_count += count
  2615. self.endInsertRows()
  2616. self.progress.emit(self.root.child_count)
  2617. def FetchMoreRecords(self, count):
  2618. current = self.root.child_count
  2619. if self.more:
  2620. self.fetcher.Fetch(count)
  2621. else:
  2622. self.progress.emit(0)
  2623. return current
  2624. def HasMoreRecords(self):
  2625. return self.more
  2626. # Report Variables
  2627. class ReportVars():
  2628. def __init__(self, name = "", where_clause = "", limit = ""):
  2629. self.name = name
  2630. self.where_clause = where_clause
  2631. self.limit = limit
  2632. def UniqueId(self):
  2633. return str(self.where_clause + ";" + self.limit)
  2634. # Branch window
  2635. class BranchWindow(QMdiSubWindow):
  2636. def __init__(self, glb, event_id, report_vars, parent=None):
  2637. super(BranchWindow, self).__init__(parent)
  2638. model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
  2639. self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
  2640. self.view = QTreeView()
  2641. self.view.setUniformRowHeights(True)
  2642. self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
  2643. self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
  2644. self.view.setModel(self.model)
  2645. self.ResizeColumnsToContents()
  2646. self.context_menu = TreeContextMenu(self.view)
  2647. self.find_bar = FindBar(self, self, True)
  2648. self.finder = ChildDataItemFinder(self.model.root)
  2649. self.fetch_bar = FetchMoreRecordsBar(self.model, self)
  2650. self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
  2651. self.setWidget(self.vbox.Widget())
  2652. AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
  2653. def ResizeColumnToContents(self, column, n):
  2654. # Using the view's resizeColumnToContents() here is extrememly slow
  2655. # so implement a crude alternative
  2656. mm = "MM" if column else "MMMM"
  2657. font = self.view.font()
  2658. metrics = QFontMetrics(font)
  2659. max = 0
  2660. for row in xrange(n):
  2661. val = self.model.root.child_items[row].data[column]
  2662. len = metrics.width(str(val) + mm)
  2663. max = len if len > max else max
  2664. val = self.model.columnHeader(column)
  2665. len = metrics.width(str(val) + mm)
  2666. max = len if len > max else max
  2667. self.view.setColumnWidth(column, max)
  2668. def ResizeColumnsToContents(self):
  2669. n = min(self.model.root.child_count, 100)
  2670. if n < 1:
  2671. # No data yet, so connect a signal to notify when there is
  2672. self.model.rowsInserted.connect(self.UpdateColumnWidths)
  2673. return
  2674. columns = self.model.columnCount()
  2675. for i in xrange(columns):
  2676. self.ResizeColumnToContents(i, n)
  2677. def UpdateColumnWidths(self, *x):
  2678. # This only needs to be done once, so disconnect the signal now
  2679. self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
  2680. self.ResizeColumnsToContents()
  2681. def Find(self, value, direction, pattern, context):
  2682. self.view.setFocus()
  2683. self.find_bar.Busy()
  2684. self.finder.Find(value, direction, pattern, context, self.FindDone)
  2685. def FindDone(self, row):
  2686. self.find_bar.Idle()
  2687. if row >= 0:
  2688. self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
  2689. else:
  2690. self.find_bar.NotFound()
  2691. # Line edit data item
  2692. class LineEditDataItem(object):
  2693. def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
  2694. self.glb = glb
  2695. self.label = label
  2696. self.placeholder_text = placeholder_text
  2697. self.parent = parent
  2698. self.id = id
  2699. self.value = default
  2700. self.widget = QLineEdit(default)
  2701. self.widget.editingFinished.connect(self.Validate)
  2702. self.widget.textChanged.connect(self.Invalidate)
  2703. self.red = False
  2704. self.error = ""
  2705. self.validated = True
  2706. if placeholder_text:
  2707. self.widget.setPlaceholderText(placeholder_text)
  2708. def TurnTextRed(self):
  2709. if not self.red:
  2710. palette = QPalette()
  2711. palette.setColor(QPalette.Text,Qt.red)
  2712. self.widget.setPalette(palette)
  2713. self.red = True
  2714. def TurnTextNormal(self):
  2715. if self.red:
  2716. palette = QPalette()
  2717. self.widget.setPalette(palette)
  2718. self.red = False
  2719. def InvalidValue(self, value):
  2720. self.value = ""
  2721. self.TurnTextRed()
  2722. self.error = self.label + " invalid value '" + value + "'"
  2723. self.parent.ShowMessage(self.error)
  2724. def Invalidate(self):
  2725. self.validated = False
  2726. def DoValidate(self, input_string):
  2727. self.value = input_string.strip()
  2728. def Validate(self):
  2729. self.validated = True
  2730. self.error = ""
  2731. self.TurnTextNormal()
  2732. self.parent.ClearMessage()
  2733. input_string = self.widget.text()
  2734. if not len(input_string.strip()):
  2735. self.value = ""
  2736. return
  2737. self.DoValidate(input_string)
  2738. def IsValid(self):
  2739. if not self.validated:
  2740. self.Validate()
  2741. if len(self.error):
  2742. self.parent.ShowMessage(self.error)
  2743. return False
  2744. return True
  2745. def IsNumber(self, value):
  2746. try:
  2747. x = int(value)
  2748. except:
  2749. x = 0
  2750. return str(x) == value
  2751. # Non-negative integer ranges dialog data item
  2752. class NonNegativeIntegerRangesDataItem(LineEditDataItem):
  2753. def __init__(self, glb, label, placeholder_text, column_name, parent):
  2754. super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
  2755. self.column_name = column_name
  2756. def DoValidate(self, input_string):
  2757. singles = []
  2758. ranges = []
  2759. for value in [x.strip() for x in input_string.split(",")]:
  2760. if "-" in value:
  2761. vrange = value.split("-")
  2762. if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
  2763. return self.InvalidValue(value)
  2764. ranges.append(vrange)
  2765. else:
  2766. if not self.IsNumber(value):
  2767. return self.InvalidValue(value)
  2768. singles.append(value)
  2769. ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
  2770. if len(singles):
  2771. ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
  2772. self.value = " OR ".join(ranges)
  2773. # Positive integer dialog data item
  2774. class PositiveIntegerDataItem(LineEditDataItem):
  2775. def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
  2776. super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
  2777. def DoValidate(self, input_string):
  2778. if not self.IsNumber(input_string.strip()):
  2779. return self.InvalidValue(input_string)
  2780. value = int(input_string.strip())
  2781. if value <= 0:
  2782. return self.InvalidValue(input_string)
  2783. self.value = str(value)
  2784. # Dialog data item converted and validated using a SQL table
  2785. class SQLTableDataItem(LineEditDataItem):
  2786. def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
  2787. super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
  2788. self.table_name = table_name
  2789. self.match_column = match_column
  2790. self.column_name1 = column_name1
  2791. self.column_name2 = column_name2
  2792. def ValueToIds(self, value):
  2793. ids = []
  2794. query = QSqlQuery(self.glb.db)
  2795. stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
  2796. ret = query.exec_(stmt)
  2797. if ret:
  2798. while query.next():
  2799. ids.append(str(query.value(0)))
  2800. return ids
  2801. def DoValidate(self, input_string):
  2802. all_ids = []
  2803. for value in [x.strip() for x in input_string.split(",")]:
  2804. ids = self.ValueToIds(value)
  2805. if len(ids):
  2806. all_ids.extend(ids)
  2807. else:
  2808. return self.InvalidValue(value)
  2809. self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
  2810. if self.column_name2:
  2811. self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
  2812. # Sample time ranges dialog data item converted and validated using 'samples' SQL table
  2813. class SampleTimeRangesDataItem(LineEditDataItem):
  2814. def __init__(self, glb, label, placeholder_text, column_name, parent):
  2815. self.column_name = column_name
  2816. self.last_id = 0
  2817. self.first_time = 0
  2818. self.last_time = 2 ** 64
  2819. query = QSqlQuery(glb.db)
  2820. QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
  2821. if query.next():
  2822. self.last_id = int(query.value(0))
  2823. self.first_time = int(glb.HostStartTime())
  2824. self.last_time = int(glb.HostFinishTime())
  2825. if placeholder_text:
  2826. placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
  2827. super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
  2828. def IdBetween(self, query, lower_id, higher_id, order):
  2829. QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
  2830. if query.next():
  2831. return True, int(query.value(0))
  2832. else:
  2833. return False, 0
  2834. def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
  2835. query = QSqlQuery(self.glb.db)
  2836. while True:
  2837. next_id = int((lower_id + higher_id) / 2)
  2838. QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
  2839. if not query.next():
  2840. ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
  2841. if not ok:
  2842. ok, dbid = self.IdBetween(query, next_id, higher_id, "")
  2843. if not ok:
  2844. return str(higher_id)
  2845. next_id = dbid
  2846. QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
  2847. next_time = int(query.value(0))
  2848. if get_floor:
  2849. if target_time > next_time:
  2850. lower_id = next_id
  2851. else:
  2852. higher_id = next_id
  2853. if higher_id <= lower_id + 1:
  2854. return str(higher_id)
  2855. else:
  2856. if target_time >= next_time:
  2857. lower_id = next_id
  2858. else:
  2859. higher_id = next_id
  2860. if higher_id <= lower_id + 1:
  2861. return str(lower_id)
  2862. def ConvertRelativeTime(self, val):
  2863. mult = 1
  2864. suffix = val[-2:]
  2865. if suffix == "ms":
  2866. mult = 1000000
  2867. elif suffix == "us":
  2868. mult = 1000
  2869. elif suffix == "ns":
  2870. mult = 1
  2871. else:
  2872. return val
  2873. val = val[:-2].strip()
  2874. if not self.IsNumber(val):
  2875. return val
  2876. val = int(val) * mult
  2877. if val >= 0:
  2878. val += self.first_time
  2879. else:
  2880. val += self.last_time
  2881. return str(val)
  2882. def ConvertTimeRange(self, vrange):
  2883. if vrange[0] == "":
  2884. vrange[0] = str(self.first_time)
  2885. if vrange[1] == "":
  2886. vrange[1] = str(self.last_time)
  2887. vrange[0] = self.ConvertRelativeTime(vrange[0])
  2888. vrange[1] = self.ConvertRelativeTime(vrange[1])
  2889. if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
  2890. return False
  2891. beg_range = max(int(vrange[0]), self.first_time)
  2892. end_range = min(int(vrange[1]), self.last_time)
  2893. if beg_range > self.last_time or end_range < self.first_time:
  2894. return False
  2895. vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
  2896. vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
  2897. return True
  2898. def AddTimeRange(self, value, ranges):
  2899. n = value.count("-")
  2900. if n == 1:
  2901. pass
  2902. elif n == 2:
  2903. if value.split("-")[1].strip() == "":
  2904. n = 1
  2905. elif n == 3:
  2906. n = 2
  2907. else:
  2908. return False
  2909. pos = findnth(value, "-", n)
  2910. vrange = [value[:pos].strip() ,value[pos+1:].strip()]
  2911. if self.ConvertTimeRange(vrange):
  2912. ranges.append(vrange)
  2913. return True
  2914. return False
  2915. def DoValidate(self, input_string):
  2916. ranges = []
  2917. for value in [x.strip() for x in input_string.split(",")]:
  2918. if not self.AddTimeRange(value, ranges):
  2919. return self.InvalidValue(value)
  2920. ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
  2921. self.value = " OR ".join(ranges)
  2922. # Report Dialog Base
  2923. class ReportDialogBase(QDialog):
  2924. def __init__(self, glb, title, items, partial, parent=None):
  2925. super(ReportDialogBase, self).__init__(parent)
  2926. self.glb = glb
  2927. self.report_vars = ReportVars()
  2928. self.setWindowTitle(title)
  2929. self.setMinimumWidth(600)
  2930. self.data_items = [x(glb, self) for x in items]
  2931. self.partial = partial
  2932. self.grid = QGridLayout()
  2933. for row in xrange(len(self.data_items)):
  2934. self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
  2935. self.grid.addWidget(self.data_items[row].widget, row, 1)
  2936. self.status = QLabel()
  2937. self.ok_button = QPushButton("Ok", self)
  2938. self.ok_button.setDefault(True)
  2939. self.ok_button.released.connect(self.Ok)
  2940. self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  2941. self.cancel_button = QPushButton("Cancel", self)
  2942. self.cancel_button.released.connect(self.reject)
  2943. self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
  2944. self.hbox = QHBoxLayout()
  2945. #self.hbox.addStretch()
  2946. self.hbox.addWidget(self.status)
  2947. self.hbox.addWidget(self.ok_button)
  2948. self.hbox.addWidget(self.cancel_button)
  2949. self.vbox = QVBoxLayout()
  2950. self.vbox.addLayout(self.grid)
  2951. self.vbox.addLayout(self.hbox)
  2952. self.setLayout(self.vbox)
  2953. def Ok(self):
  2954. vars = self.report_vars
  2955. for d in self.data_items:
  2956. if d.id == "REPORTNAME":
  2957. vars.name = d.value
  2958. if not vars.name:
  2959. self.ShowMessage("Report name is required")
  2960. return
  2961. for d in self.data_items:
  2962. if not d.IsValid():
  2963. return
  2964. for d in self.data_items[1:]:
  2965. if d.id == "LIMIT":
  2966. vars.limit = d.value
  2967. elif len(d.value):
  2968. if len(vars.where_clause):
  2969. vars.where_clause += " AND "
  2970. vars.where_clause += d.value
  2971. if len(vars.where_clause):
  2972. if self.partial:
  2973. vars.where_clause = " AND ( " + vars.where_clause + " ) "
  2974. else:
  2975. vars.where_clause = " WHERE " + vars.where_clause + " "
  2976. self.accept()
  2977. def ShowMessage(self, msg):
  2978. self.status.setText("<font color=#FF0000>" + msg)
  2979. def ClearMessage(self):
  2980. self.status.setText("")
  2981. # Selected branch report creation dialog
  2982. class SelectedBranchDialog(ReportDialogBase):
  2983. def __init__(self, glb, parent=None):
  2984. title = "Selected Branches"
  2985. items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
  2986. lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
  2987. lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
  2988. lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
  2989. lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
  2990. lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
  2991. lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
  2992. lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
  2993. lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
  2994. super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
  2995. # Event list
  2996. def GetEventList(db):
  2997. events = []
  2998. query = QSqlQuery(db)
  2999. QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
  3000. while query.next():
  3001. events.append(query.value(0))
  3002. return events
  3003. # Is a table selectable
  3004. def IsSelectable(db, table, sql = "", columns = "*"):
  3005. query = QSqlQuery(db)
  3006. try:
  3007. QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
  3008. except:
  3009. return False
  3010. return True
  3011. # SQL table data model item
  3012. class SQLTableItem():
  3013. def __init__(self, row, data):
  3014. self.row = row
  3015. self.data = data
  3016. def getData(self, column):
  3017. return self.data[column]
  3018. # SQL table data model
  3019. class SQLTableModel(TableModel):
  3020. progress = Signal(object)
  3021. def __init__(self, glb, sql, column_headers, parent=None):
  3022. super(SQLTableModel, self).__init__(parent)
  3023. self.glb = glb
  3024. self.more = True
  3025. self.populated = 0
  3026. self.column_headers = column_headers
  3027. self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
  3028. self.fetcher.done.connect(self.Update)
  3029. self.fetcher.Fetch(glb_chunk_sz)
  3030. def DisplayData(self, item, index):
  3031. self.FetchIfNeeded(item.row)
  3032. return item.getData(index.column())
  3033. def AddSample(self, data):
  3034. child = SQLTableItem(self.populated, data)
  3035. self.child_items.append(child)
  3036. self.populated += 1
  3037. def Update(self, fetched):
  3038. if not fetched:
  3039. self.more = False
  3040. self.progress.emit(0)
  3041. child_count = self.child_count
  3042. count = self.populated - child_count
  3043. if count > 0:
  3044. parent = QModelIndex()
  3045. self.beginInsertRows(parent, child_count, child_count + count - 1)
  3046. self.insertRows(child_count, count, parent)
  3047. self.child_count += count
  3048. self.endInsertRows()
  3049. self.progress.emit(self.child_count)
  3050. def FetchMoreRecords(self, count):
  3051. current = self.child_count
  3052. if self.more:
  3053. self.fetcher.Fetch(count)
  3054. else:
  3055. self.progress.emit(0)
  3056. return current
  3057. def HasMoreRecords(self):
  3058. return self.more
  3059. def columnCount(self, parent=None):
  3060. return len(self.column_headers)
  3061. def columnHeader(self, column):
  3062. return self.column_headers[column]
  3063. def SQLTableDataPrep(self, query, count):
  3064. data = []
  3065. for i in xrange(count):
  3066. data.append(query.value(i))
  3067. return data
  3068. # SQL automatic table data model
  3069. class SQLAutoTableModel(SQLTableModel):
  3070. def __init__(self, glb, table_name, parent=None):
  3071. sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
  3072. if table_name == "comm_threads_view":
  3073. # For now, comm_threads_view has no id column
  3074. sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
  3075. column_headers = []
  3076. query = QSqlQuery(glb.db)
  3077. if glb.dbref.is_sqlite3:
  3078. QueryExec(query, "PRAGMA table_info(" + table_name + ")")
  3079. while query.next():
  3080. column_headers.append(query.value(1))
  3081. if table_name == "sqlite_master":
  3082. sql = "SELECT * FROM " + table_name
  3083. else:
  3084. if table_name[:19] == "information_schema.":
  3085. sql = "SELECT * FROM " + table_name
  3086. select_table_name = table_name[19:]
  3087. schema = "information_schema"
  3088. else:
  3089. select_table_name = table_name
  3090. schema = "public"
  3091. QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
  3092. while query.next():
  3093. column_headers.append(query.value(0))
  3094. if pyside_version_1 and sys.version_info[0] == 3:
  3095. if table_name == "samples_view":
  3096. self.SQLTableDataPrep = self.samples_view_DataPrep
  3097. if table_name == "samples":
  3098. self.SQLTableDataPrep = self.samples_DataPrep
  3099. super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
  3100. def samples_view_DataPrep(self, query, count):
  3101. data = []
  3102. data.append(query.value(0))
  3103. # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
  3104. data.append("{:>19}".format(query.value(1)))
  3105. for i in xrange(2, count):
  3106. data.append(query.value(i))
  3107. return data
  3108. def samples_DataPrep(self, query, count):
  3109. data = []
  3110. for i in xrange(9):
  3111. data.append(query.value(i))
  3112. # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
  3113. data.append("{:>19}".format(query.value(9)))
  3114. for i in xrange(10, count):
  3115. data.append(query.value(i))
  3116. return data
  3117. # Base class for custom ResizeColumnsToContents
  3118. class ResizeColumnsToContentsBase(QObject):
  3119. def __init__(self, parent=None):
  3120. super(ResizeColumnsToContentsBase, self).__init__(parent)
  3121. def ResizeColumnToContents(self, column, n):
  3122. # Using the view's resizeColumnToContents() here is extrememly slow
  3123. # so implement a crude alternative
  3124. font = self.view.font()
  3125. metrics = QFontMetrics(font)
  3126. max = 0
  3127. for row in xrange(n):
  3128. val = self.data_model.child_items[row].data[column]
  3129. len = metrics.width(str(val) + "MM")
  3130. max = len if len > max else max
  3131. val = self.data_model.columnHeader(column)
  3132. len = metrics.width(str(val) + "MM")
  3133. max = len if len > max else max
  3134. self.view.setColumnWidth(column, max)
  3135. def ResizeColumnsToContents(self):
  3136. n = min(self.data_model.child_count, 100)
  3137. if n < 1:
  3138. # No data yet, so connect a signal to notify when there is
  3139. self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
  3140. return
  3141. columns = self.data_model.columnCount()
  3142. for i in xrange(columns):
  3143. self.ResizeColumnToContents(i, n)
  3144. def UpdateColumnWidths(self, *x):
  3145. # This only needs to be done once, so disconnect the signal now
  3146. self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
  3147. self.ResizeColumnsToContents()
  3148. # Convert value to CSV
  3149. def ToCSValue(val):
  3150. if '"' in val:
  3151. val = val.replace('"', '""')
  3152. if "," in val or '"' in val:
  3153. val = '"' + val + '"'
  3154. return val
  3155. # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
  3156. glb_max_cols = 1000
  3157. def RowColumnKey(a):
  3158. return a.row() * glb_max_cols + a.column()
  3159. # Copy selected table cells to clipboard
  3160. def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
  3161. indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
  3162. idx_cnt = len(indexes)
  3163. if not idx_cnt:
  3164. return
  3165. if idx_cnt == 1:
  3166. with_hdr=False
  3167. min_row = indexes[0].row()
  3168. max_row = indexes[0].row()
  3169. min_col = indexes[0].column()
  3170. max_col = indexes[0].column()
  3171. for i in indexes:
  3172. min_row = min(min_row, i.row())
  3173. max_row = max(max_row, i.row())
  3174. min_col = min(min_col, i.column())
  3175. max_col = max(max_col, i.column())
  3176. if max_col > glb_max_cols:
  3177. raise RuntimeError("glb_max_cols is too low")
  3178. max_width = [0] * (1 + max_col - min_col)
  3179. for i in indexes:
  3180. c = i.column() - min_col
  3181. max_width[c] = max(max_width[c], len(str(i.data())))
  3182. text = ""
  3183. pad = ""
  3184. sep = ""
  3185. if with_hdr:
  3186. model = indexes[0].model()
  3187. for col in range(min_col, max_col + 1):
  3188. val = model.headerData(col, Qt.Horizontal, Qt.DisplayRole)
  3189. if as_csv:
  3190. text += sep + ToCSValue(val)
  3191. sep = ","
  3192. else:
  3193. c = col - min_col
  3194. max_width[c] = max(max_width[c], len(val))
  3195. width = max_width[c]
  3196. align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
  3197. if align & Qt.AlignRight:
  3198. val = val.rjust(width)
  3199. text += pad + sep + val
  3200. pad = " " * (width - len(val))
  3201. sep = " "
  3202. text += "\n"
  3203. pad = ""
  3204. sep = ""
  3205. last_row = min_row
  3206. for i in indexes:
  3207. if i.row() > last_row:
  3208. last_row = i.row()
  3209. text += "\n"
  3210. pad = ""
  3211. sep = ""
  3212. if as_csv:
  3213. text += sep + ToCSValue(str(i.data()))
  3214. sep = ","
  3215. else:
  3216. width = max_width[i.column() - min_col]
  3217. if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
  3218. val = str(i.data()).rjust(width)
  3219. else:
  3220. val = str(i.data())
  3221. text += pad + sep + val
  3222. pad = " " * (width - len(val))
  3223. sep = " "
  3224. QApplication.clipboard().setText(text)
  3225. def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
  3226. indexes = view.selectedIndexes()
  3227. if not len(indexes):
  3228. return
  3229. selection = view.selectionModel()
  3230. first = None
  3231. for i in indexes:
  3232. above = view.indexAbove(i)
  3233. if not selection.isSelected(above):
  3234. first = i
  3235. break
  3236. if first is None:
  3237. raise RuntimeError("CopyTreeCellsToClipboard internal error")
  3238. model = first.model()
  3239. row_cnt = 0
  3240. col_cnt = model.columnCount(first)
  3241. max_width = [0] * col_cnt
  3242. indent_sz = 2
  3243. indent_str = " " * indent_sz
  3244. expanded_mark_sz = 2
  3245. if sys.version_info[0] == 3:
  3246. expanded_mark = "\u25BC "
  3247. not_expanded_mark = "\u25B6 "
  3248. else:
  3249. expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
  3250. not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
  3251. leaf_mark = " "
  3252. if not as_csv:
  3253. pos = first
  3254. while True:
  3255. row_cnt += 1
  3256. row = pos.row()
  3257. for c in range(col_cnt):
  3258. i = pos.sibling(row, c)
  3259. if c:
  3260. n = len(str(i.data()))
  3261. else:
  3262. n = len(str(i.data()).strip())
  3263. n += (i.internalPointer().level - 1) * indent_sz
  3264. n += expanded_mark_sz
  3265. max_width[c] = max(max_width[c], n)
  3266. pos = view.indexBelow(pos)
  3267. if not selection.isSelected(pos):
  3268. break
  3269. text = ""
  3270. pad = ""
  3271. sep = ""
  3272. if with_hdr:
  3273. for c in range(col_cnt):
  3274. val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
  3275. if as_csv:
  3276. text += sep + ToCSValue(val)
  3277. sep = ","
  3278. else:
  3279. max_width[c] = max(max_width[c], len(val))
  3280. width = max_width[c]
  3281. align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
  3282. if align & Qt.AlignRight:
  3283. val = val.rjust(width)
  3284. text += pad + sep + val
  3285. pad = " " * (width - len(val))
  3286. sep = " "
  3287. text += "\n"
  3288. pad = ""
  3289. sep = ""
  3290. pos = first
  3291. while True:
  3292. row = pos.row()
  3293. for c in range(col_cnt):
  3294. i = pos.sibling(row, c)
  3295. val = str(i.data())
  3296. if not c:
  3297. if model.hasChildren(i):
  3298. if view.isExpanded(i):
  3299. mark = expanded_mark
  3300. else:
  3301. mark = not_expanded_mark
  3302. else:
  3303. mark = leaf_mark
  3304. val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
  3305. if as_csv:
  3306. text += sep + ToCSValue(val)
  3307. sep = ","
  3308. else:
  3309. width = max_width[c]
  3310. if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
  3311. val = val.rjust(width)
  3312. text += pad + sep + val
  3313. pad = " " * (width - len(val))
  3314. sep = " "
  3315. pos = view.indexBelow(pos)
  3316. if not selection.isSelected(pos):
  3317. break
  3318. text = text.rstrip() + "\n"
  3319. pad = ""
  3320. sep = ""
  3321. QApplication.clipboard().setText(text)
  3322. def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
  3323. view.CopyCellsToClipboard(view, as_csv, with_hdr)
  3324. def CopyCellsToClipboardHdr(view):
  3325. CopyCellsToClipboard(view, False, True)
  3326. def CopyCellsToClipboardCSV(view):
  3327. CopyCellsToClipboard(view, True, True)
  3328. # Context menu
  3329. class ContextMenu(object):
  3330. def __init__(self, view):
  3331. self.view = view
  3332. self.view.setContextMenuPolicy(Qt.CustomContextMenu)
  3333. self.view.customContextMenuRequested.connect(self.ShowContextMenu)
  3334. def ShowContextMenu(self, pos):
  3335. menu = QMenu(self.view)
  3336. self.AddActions(menu)
  3337. menu.exec_(self.view.mapToGlobal(pos))
  3338. def AddCopy(self, menu):
  3339. menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
  3340. menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
  3341. def AddActions(self, menu):
  3342. self.AddCopy(menu)
  3343. class TreeContextMenu(ContextMenu):
  3344. def __init__(self, view):
  3345. super(TreeContextMenu, self).__init__(view)
  3346. def AddActions(self, menu):
  3347. i = self.view.currentIndex()
  3348. text = str(i.data()).strip()
  3349. if len(text):
  3350. menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
  3351. self.AddCopy(menu)
  3352. # Table window
  3353. class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
  3354. def __init__(self, glb, table_name, parent=None):
  3355. super(TableWindow, self).__init__(parent)
  3356. self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
  3357. self.model = QSortFilterProxyModel()
  3358. self.model.setSourceModel(self.data_model)
  3359. self.view = QTableView()
  3360. self.view.setModel(self.model)
  3361. self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
  3362. self.view.verticalHeader().setVisible(False)
  3363. self.view.sortByColumn(-1, Qt.AscendingOrder)
  3364. self.view.setSortingEnabled(True)
  3365. self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
  3366. self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
  3367. self.ResizeColumnsToContents()
  3368. self.context_menu = ContextMenu(self.view)
  3369. self.find_bar = FindBar(self, self, True)
  3370. self.finder = ChildDataItemFinder(self.data_model)
  3371. self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
  3372. self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
  3373. self.setWidget(self.vbox.Widget())
  3374. AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
  3375. def Find(self, value, direction, pattern, context):
  3376. self.view.setFocus()
  3377. self.find_bar.Busy()
  3378. self.finder.Find(value, direction, pattern, context, self.FindDone)
  3379. def FindDone(self, row):
  3380. self.find_bar.Idle()
  3381. if row >= 0:
  3382. self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
  3383. else:
  3384. self.find_bar.NotFound()
  3385. # Table list
  3386. def GetTableList(glb):
  3387. tables = []
  3388. query = QSqlQuery(glb.db)
  3389. if glb.dbref.is_sqlite3:
  3390. QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
  3391. else:
  3392. QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
  3393. while query.next():
  3394. tables.append(query.value(0))
  3395. if glb.dbref.is_sqlite3:
  3396. tables.append("sqlite_master")
  3397. else:
  3398. tables.append("information_schema.tables")
  3399. tables.append("information_schema.views")
  3400. tables.append("information_schema.columns")
  3401. return tables
  3402. # Top Calls data model
  3403. class TopCallsModel(SQLTableModel):
  3404. def __init__(self, glb, report_vars, parent=None):
  3405. text = ""
  3406. if not glb.dbref.is_sqlite3:
  3407. text = "::text"
  3408. limit = ""
  3409. if len(report_vars.limit):
  3410. limit = " LIMIT " + report_vars.limit
  3411. sql = ("SELECT comm, pid, tid, name,"
  3412. " CASE"
  3413. " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
  3414. " ELSE short_name"
  3415. " END AS dso,"
  3416. " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
  3417. " CASE"
  3418. " WHEN (calls.flags = 1) THEN 'no call'" + text +
  3419. " WHEN (calls.flags = 2) THEN 'no return'" + text +
  3420. " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
  3421. " ELSE ''" + text +
  3422. " END AS flags"
  3423. " FROM calls"
  3424. " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
  3425. " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
  3426. " INNER JOIN dsos ON symbols.dso_id = dsos.id"
  3427. " INNER JOIN comms ON calls.comm_id = comms.id"
  3428. " INNER JOIN threads ON calls.thread_id = threads.id" +
  3429. report_vars.where_clause +
  3430. " ORDER BY elapsed_time DESC" +
  3431. limit
  3432. )
  3433. column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
  3434. self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
  3435. super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
  3436. def columnAlignment(self, column):
  3437. return self.alignment[column]
  3438. # Top Calls report creation dialog
  3439. class TopCallsDialog(ReportDialogBase):
  3440. def __init__(self, glb, parent=None):
  3441. title = "Top Calls by Elapsed Time"
  3442. items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
  3443. lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
  3444. lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
  3445. lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
  3446. lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
  3447. lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
  3448. lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
  3449. lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
  3450. super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
  3451. # Top Calls window
  3452. class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
  3453. def __init__(self, glb, report_vars, parent=None):
  3454. super(TopCallsWindow, self).__init__(parent)
  3455. self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
  3456. self.model = self.data_model
  3457. self.view = QTableView()
  3458. self.view.setModel(self.model)
  3459. self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
  3460. self.view.verticalHeader().setVisible(False)
  3461. self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
  3462. self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
  3463. self.context_menu = ContextMenu(self.view)
  3464. self.ResizeColumnsToContents()
  3465. self.find_bar = FindBar(self, self, True)
  3466. self.finder = ChildDataItemFinder(self.model)
  3467. self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
  3468. self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
  3469. self.setWidget(self.vbox.Widget())
  3470. AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
  3471. def Find(self, value, direction, pattern, context):
  3472. self.view.setFocus()
  3473. self.find_bar.Busy()
  3474. self.finder.Find(value, direction, pattern, context, self.FindDone)
  3475. def FindDone(self, row):
  3476. self.find_bar.Idle()
  3477. if row >= 0:
  3478. self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
  3479. else:
  3480. self.find_bar.NotFound()
  3481. # Action Definition
  3482. def CreateAction(label, tip, callback, parent=None, shortcut=None):
  3483. action = QAction(label, parent)
  3484. if shortcut != None:
  3485. action.setShortcuts(shortcut)
  3486. action.setStatusTip(tip)
  3487. action.triggered.connect(callback)
  3488. return action
  3489. # Typical application actions
  3490. def CreateExitAction(app, parent=None):
  3491. return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
  3492. # Typical MDI actions
  3493. def CreateCloseActiveWindowAction(mdi_area):
  3494. return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
  3495. def CreateCloseAllWindowsAction(mdi_area):
  3496. return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
  3497. def CreateTileWindowsAction(mdi_area):
  3498. return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
  3499. def CreateCascadeWindowsAction(mdi_area):
  3500. return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
  3501. def CreateNextWindowAction(mdi_area):
  3502. return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
  3503. def CreatePreviousWindowAction(mdi_area):
  3504. return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
  3505. # Typical MDI window menu
  3506. class WindowMenu():
  3507. def __init__(self, mdi_area, menu):
  3508. self.mdi_area = mdi_area
  3509. self.window_menu = menu.addMenu("&Windows")
  3510. self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
  3511. self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
  3512. self.tile_windows = CreateTileWindowsAction(mdi_area)
  3513. self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
  3514. self.next_window = CreateNextWindowAction(mdi_area)
  3515. self.previous_window = CreatePreviousWindowAction(mdi_area)
  3516. self.window_menu.aboutToShow.connect(self.Update)
  3517. def Update(self):
  3518. self.window_menu.clear()
  3519. sub_window_count = len(self.mdi_area.subWindowList())
  3520. have_sub_windows = sub_window_count != 0
  3521. self.close_active_window.setEnabled(have_sub_windows)
  3522. self.close_all_windows.setEnabled(have_sub_windows)
  3523. self.tile_windows.setEnabled(have_sub_windows)
  3524. self.cascade_windows.setEnabled(have_sub_windows)
  3525. self.next_window.setEnabled(have_sub_windows)
  3526. self.previous_window.setEnabled(have_sub_windows)
  3527. self.window_menu.addAction(self.close_active_window)
  3528. self.window_menu.addAction(self.close_all_windows)
  3529. self.window_menu.addSeparator()
  3530. self.window_menu.addAction(self.tile_windows)
  3531. self.window_menu.addAction(self.cascade_windows)
  3532. self.window_menu.addSeparator()
  3533. self.window_menu.addAction(self.next_window)
  3534. self.window_menu.addAction(self.previous_window)
  3535. if sub_window_count == 0:
  3536. return
  3537. self.window_menu.addSeparator()
  3538. nr = 1
  3539. for sub_window in self.mdi_area.subWindowList():
  3540. label = str(nr) + " " + sub_window.name
  3541. if nr < 10:
  3542. label = "&" + label
  3543. action = self.window_menu.addAction(label)
  3544. action.setCheckable(True)
  3545. action.setChecked(sub_window == self.mdi_area.activeSubWindow())
  3546. action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
  3547. self.window_menu.addAction(action)
  3548. nr += 1
  3549. def setActiveSubWindow(self, nr):
  3550. self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
  3551. # Help text
  3552. glb_help_text = """
  3553. <h1>Contents</h1>
  3554. <style>
  3555. p.c1 {
  3556. text-indent: 40px;
  3557. }
  3558. p.c2 {
  3559. text-indent: 80px;
  3560. }
  3561. }
  3562. </style>
  3563. <p class=c1><a href=#reports>1. Reports</a></p>
  3564. <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
  3565. <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
  3566. <p class=c2><a href=#allbranches>1.3 All branches</a></p>
  3567. <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
  3568. <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
  3569. <p class=c1><a href=#charts>2. Charts</a></p>
  3570. <p class=c2><a href=#timechartbycpu>2.1 Time chart by CPU</a></p>
  3571. <p class=c1><a href=#tables>3. Tables</a></p>
  3572. <h1 id=reports>1. Reports</h1>
  3573. <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
  3574. The result is a GUI window with a tree representing a context-sensitive
  3575. call-graph. Expanding a couple of levels of the tree and adjusting column
  3576. widths to suit will display something like:
  3577. <pre>
  3578. Call Graph: pt_example
  3579. Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
  3580. v- ls
  3581. v- 2638:2638
  3582. v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
  3583. |- unknown unknown 1 13198 0.1 1 0.0
  3584. >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
  3585. >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
  3586. v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
  3587. >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
  3588. >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
  3589. >- __libc_csu_init ls 1 10354 0.1 10 0.0
  3590. |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
  3591. v- main ls 1 8182043 99.6 180254 99.9
  3592. </pre>
  3593. <h3>Points to note:</h3>
  3594. <ul>
  3595. <li>The top level is a command name (comm)</li>
  3596. <li>The next level is a thread (pid:tid)</li>
  3597. <li>Subsequent levels are functions</li>
  3598. <li>'Count' is the number of calls</li>
  3599. <li>'Time' is the elapsed time until the function returns</li>
  3600. <li>Percentages are relative to the level above</li>
  3601. <li>'Branch Count' is the total number of branches for that function and all functions that it calls
  3602. </ul>
  3603. <h3>Find</h3>
  3604. Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
  3605. The pattern matching symbols are ? for any character and * for zero or more characters.
  3606. <h2 id=calltree>1.2 Call Tree</h2>
  3607. The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
  3608. Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
  3609. <h2 id=allbranches>1.3 All branches</h2>
  3610. The All branches report displays all branches in chronological order.
  3611. Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
  3612. <h3>Disassembly</h3>
  3613. Open a branch to display disassembly. This only works if:
  3614. <ol>
  3615. <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
  3616. <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
  3617. The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
  3618. One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
  3619. or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
  3620. </ol>
  3621. <h4 id=xed>Intel XED Setup</h4>
  3622. To use Intel XED, libxed.so must be present. To build and install libxed.so:
  3623. <pre>
  3624. git clone https://github.com/intelxed/mbuild.git mbuild
  3625. git clone https://github.com/intelxed/xed
  3626. cd xed
  3627. ./mfile.py --share
  3628. sudo ./mfile.py --prefix=/usr/local install
  3629. sudo ldconfig
  3630. </pre>
  3631. <h3>Instructions per Cycle (IPC)</h3>
  3632. If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
  3633. <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
  3634. Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
  3635. In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
  3636. since the previous displayed 'IPC'.
  3637. <h3>Find</h3>
  3638. Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
  3639. Refer to Python documentation for the regular expression syntax.
  3640. All columns are searched, but only currently fetched rows are searched.
  3641. <h2 id=selectedbranches>1.4 Selected branches</h2>
  3642. This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
  3643. by various selection criteria. A dialog box displays available criteria which are AND'ed together.
  3644. <h3>1.4.1 Time ranges</h3>
  3645. The time ranges hint text shows the total time range. Relative time ranges can also be entered in
  3646. ms, us or ns. Also, negative values are relative to the end of trace. Examples:
  3647. <pre>
  3648. 81073085947329-81073085958238 From 81073085947329 to 81073085958238
  3649. 100us-200us From 100us to 200us
  3650. 10ms- From 10ms to the end
  3651. -100ns The first 100ns
  3652. -10ms- The last 10ms
  3653. </pre>
  3654. N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
  3655. <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
  3656. The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
  3657. The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
  3658. If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
  3659. <h1 id=charts>2. Charts</h1>
  3660. <h2 id=timechartbycpu>2.1 Time chart by CPU</h2>
  3661. This chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu.
  3662. <h3>Features</h3>
  3663. <ol>
  3664. <li>Mouse over to highight the task and show the time</li>
  3665. <li>Drag the mouse to select a region and zoom by pushing the Zoom button</li>
  3666. <li>Go back and forward by pressing the arrow buttons</li>
  3667. <li>If call information is available, right-click to show a call tree opened to that task and time.
  3668. Note, the call tree may take some time to appear, and there may not be call information for the task or time selected.
  3669. </li>
  3670. </ol>
  3671. <h3>Important</h3>
  3672. The graph can be misleading in the following respects:
  3673. <ol>
  3674. <li>The graph shows the first task on each CPU as running from the beginning of the time range.
  3675. Because tracing might start on different CPUs at different times, that is not necessarily the case.
  3676. Refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
  3677. <li>Similarly, the last task on each CPU can be showing running longer than it really was.
  3678. Again, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
  3679. <li>When the mouse is over a task, the highlighted task might not be visible on the legend without scrolling if the legend does not fit fully in the window</li>
  3680. </ol>
  3681. <h1 id=tables>3. Tables</h1>
  3682. The Tables menu shows all tables and views in the database. Most tables have an associated view
  3683. which displays the information in a more friendly way. Not all data for large tables is fetched
  3684. immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
  3685. but that can be slow for large tables.
  3686. <p>There are also tables of database meta-information.
  3687. For SQLite3 databases, the sqlite_master table is included.
  3688. For PostgreSQL databases, information_schema.tables/views/columns are included.
  3689. <h3>Find</h3>
  3690. Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
  3691. Refer to Python documentation for the regular expression syntax.
  3692. All columns are searched, but only currently fetched rows are searched.
  3693. <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
  3694. will go to the next/previous result in id order, instead of display order.
  3695. """
  3696. # Help window
  3697. class HelpWindow(QMdiSubWindow):
  3698. def __init__(self, glb, parent=None):
  3699. super(HelpWindow, self).__init__(parent)
  3700. self.text = QTextBrowser()
  3701. self.text.setHtml(glb_help_text)
  3702. self.text.setReadOnly(True)
  3703. self.text.setOpenExternalLinks(True)
  3704. self.setWidget(self.text)
  3705. AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
  3706. # Main window that only displays the help text
  3707. class HelpOnlyWindow(QMainWindow):
  3708. def __init__(self, parent=None):
  3709. super(HelpOnlyWindow, self).__init__(parent)
  3710. self.setMinimumSize(200, 100)
  3711. self.resize(800, 600)
  3712. self.setWindowTitle("Exported SQL Viewer Help")
  3713. self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
  3714. self.text = QTextBrowser()
  3715. self.text.setHtml(glb_help_text)
  3716. self.text.setReadOnly(True)
  3717. self.text.setOpenExternalLinks(True)
  3718. self.setCentralWidget(self.text)
  3719. # PostqreSQL server version
  3720. def PostqreSQLServerVersion(db):
  3721. query = QSqlQuery(db)
  3722. QueryExec(query, "SELECT VERSION()")
  3723. if query.next():
  3724. v_str = query.value(0)
  3725. v_list = v_str.strip().split(" ")
  3726. if v_list[0] == "PostgreSQL" and v_list[2] == "on":
  3727. return v_list[1]
  3728. return v_str
  3729. return "Unknown"
  3730. # SQLite version
  3731. def SQLiteVersion(db):
  3732. query = QSqlQuery(db)
  3733. QueryExec(query, "SELECT sqlite_version()")
  3734. if query.next():
  3735. return query.value(0)
  3736. return "Unknown"
  3737. # About dialog
  3738. class AboutDialog(QDialog):
  3739. def __init__(self, glb, parent=None):
  3740. super(AboutDialog, self).__init__(parent)
  3741. self.setWindowTitle("About Exported SQL Viewer")
  3742. self.setMinimumWidth(300)
  3743. pyside_version = "1" if pyside_version_1 else "2"
  3744. text = "<pre>"
  3745. text += "Python version: " + sys.version.split(" ")[0] + "\n"
  3746. text += "PySide version: " + pyside_version + "\n"
  3747. text += "Qt version: " + qVersion() + "\n"
  3748. if glb.dbref.is_sqlite3:
  3749. text += "SQLite version: " + SQLiteVersion(glb.db) + "\n"
  3750. else:
  3751. text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
  3752. text += "</pre>"
  3753. self.text = QTextBrowser()
  3754. self.text.setHtml(text)
  3755. self.text.setReadOnly(True)
  3756. self.text.setOpenExternalLinks(True)
  3757. self.vbox = QVBoxLayout()
  3758. self.vbox.addWidget(self.text)
  3759. self.setLayout(self.vbox)
  3760. # Font resize
  3761. def ResizeFont(widget, diff):
  3762. font = widget.font()
  3763. sz = font.pointSize()
  3764. font.setPointSize(sz + diff)
  3765. widget.setFont(font)
  3766. def ShrinkFont(widget):
  3767. ResizeFont(widget, -1)
  3768. def EnlargeFont(widget):
  3769. ResizeFont(widget, 1)
  3770. # Unique name for sub-windows
  3771. def NumberedWindowName(name, nr):
  3772. if nr > 1:
  3773. name += " <" + str(nr) + ">"
  3774. return name
  3775. def UniqueSubWindowName(mdi_area, name):
  3776. nr = 1
  3777. while True:
  3778. unique_name = NumberedWindowName(name, nr)
  3779. ok = True
  3780. for sub_window in mdi_area.subWindowList():
  3781. if sub_window.name == unique_name:
  3782. ok = False
  3783. break
  3784. if ok:
  3785. return unique_name
  3786. nr += 1
  3787. # Add a sub-window
  3788. def AddSubWindow(mdi_area, sub_window, name):
  3789. unique_name = UniqueSubWindowName(mdi_area, name)
  3790. sub_window.setMinimumSize(200, 100)
  3791. sub_window.resize(800, 600)
  3792. sub_window.setWindowTitle(unique_name)
  3793. sub_window.setAttribute(Qt.WA_DeleteOnClose)
  3794. sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
  3795. sub_window.name = unique_name
  3796. mdi_area.addSubWindow(sub_window)
  3797. sub_window.show()
  3798. # Main window
  3799. class MainWindow(QMainWindow):
  3800. def __init__(self, glb, parent=None):
  3801. super(MainWindow, self).__init__(parent)
  3802. self.glb = glb
  3803. self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
  3804. self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
  3805. self.setMinimumSize(200, 100)
  3806. self.mdi_area = QMdiArea()
  3807. self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
  3808. self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
  3809. self.setCentralWidget(self.mdi_area)
  3810. menu = self.menuBar()
  3811. file_menu = menu.addMenu("&File")
  3812. file_menu.addAction(CreateExitAction(glb.app, self))
  3813. edit_menu = menu.addMenu("&Edit")
  3814. edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
  3815. edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
  3816. edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
  3817. edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
  3818. edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
  3819. edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
  3820. reports_menu = menu.addMenu("&Reports")
  3821. if IsSelectable(glb.db, "calls"):
  3822. reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
  3823. if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
  3824. reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
  3825. self.EventMenu(GetEventList(glb.db), reports_menu)
  3826. if IsSelectable(glb.db, "calls"):
  3827. reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
  3828. if IsSelectable(glb.db, "context_switches"):
  3829. charts_menu = menu.addMenu("&Charts")
  3830. charts_menu.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self.TimeChartByCPU, self))
  3831. self.TableMenu(GetTableList(glb), menu)
  3832. self.window_menu = WindowMenu(self.mdi_area, menu)
  3833. help_menu = menu.addMenu("&Help")
  3834. help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
  3835. help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
  3836. def Try(self, fn):
  3837. win = self.mdi_area.activeSubWindow()
  3838. if win:
  3839. try:
  3840. fn(win.view)
  3841. except:
  3842. pass
  3843. def CopyToClipboard(self):
  3844. self.Try(CopyCellsToClipboardHdr)
  3845. def CopyToClipboardCSV(self):
  3846. self.Try(CopyCellsToClipboardCSV)
  3847. def Find(self):
  3848. win = self.mdi_area.activeSubWindow()
  3849. if win:
  3850. try:
  3851. win.find_bar.Activate()
  3852. except:
  3853. pass
  3854. def FetchMoreRecords(self):
  3855. win = self.mdi_area.activeSubWindow()
  3856. if win:
  3857. try:
  3858. win.fetch_bar.Activate()
  3859. except:
  3860. pass
  3861. def ShrinkFont(self):
  3862. self.Try(ShrinkFont)
  3863. def EnlargeFont(self):
  3864. self.Try(EnlargeFont)
  3865. def EventMenu(self, events, reports_menu):
  3866. branches_events = 0
  3867. for event in events:
  3868. event = event.split(":")[0]
  3869. if event == "branches":
  3870. branches_events += 1
  3871. dbid = 0
  3872. for event in events:
  3873. dbid += 1
  3874. event = event.split(":")[0]
  3875. if event == "branches":
  3876. label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
  3877. reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
  3878. label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
  3879. reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
  3880. def TimeChartByCPU(self):
  3881. TimeChartByCPUWindow(self.glb, self)
  3882. def TableMenu(self, tables, menu):
  3883. table_menu = menu.addMenu("&Tables")
  3884. for table in tables:
  3885. table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
  3886. def NewCallGraph(self):
  3887. CallGraphWindow(self.glb, self)
  3888. def NewCallTree(self):
  3889. CallTreeWindow(self.glb, self)
  3890. def NewTopCalls(self):
  3891. dialog = TopCallsDialog(self.glb, self)
  3892. ret = dialog.exec_()
  3893. if ret:
  3894. TopCallsWindow(self.glb, dialog.report_vars, self)
  3895. def NewBranchView(self, event_id):
  3896. BranchWindow(self.glb, event_id, ReportVars(), self)
  3897. def NewSelectedBranchView(self, event_id):
  3898. dialog = SelectedBranchDialog(self.glb, self)
  3899. ret = dialog.exec_()
  3900. if ret:
  3901. BranchWindow(self.glb, event_id, dialog.report_vars, self)
  3902. def NewTableView(self, table_name):
  3903. TableWindow(self.glb, table_name, self)
  3904. def Help(self):
  3905. HelpWindow(self.glb, self)
  3906. def About(self):
  3907. dialog = AboutDialog(self.glb, self)
  3908. dialog.exec_()
  3909. def TryOpen(file_name):
  3910. try:
  3911. return open(file_name, "rb")
  3912. except:
  3913. return None
  3914. def Is64Bit(f):
  3915. result = sizeof(c_void_p)
  3916. # ELF support only
  3917. pos = f.tell()
  3918. f.seek(0)
  3919. header = f.read(7)
  3920. f.seek(pos)
  3921. magic = header[0:4]
  3922. if sys.version_info[0] == 2:
  3923. eclass = ord(header[4])
  3924. encoding = ord(header[5])
  3925. version = ord(header[6])
  3926. else:
  3927. eclass = header[4]
  3928. encoding = header[5]
  3929. version = header[6]
  3930. if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
  3931. result = True if eclass == 2 else False
  3932. return result
  3933. # Global data
  3934. class Glb():
  3935. def __init__(self, dbref, db, dbname):
  3936. self.dbref = dbref
  3937. self.db = db
  3938. self.dbname = dbname
  3939. self.home_dir = os.path.expanduser("~")
  3940. self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
  3941. if self.buildid_dir:
  3942. self.buildid_dir += "/.build-id/"
  3943. else:
  3944. self.buildid_dir = self.home_dir + "/.debug/.build-id/"
  3945. self.app = None
  3946. self.mainwindow = None
  3947. self.instances_to_shutdown_on_exit = weakref.WeakSet()
  3948. try:
  3949. self.disassembler = LibXED()
  3950. self.have_disassembler = True
  3951. except:
  3952. self.have_disassembler = False
  3953. self.host_machine_id = 0
  3954. self.host_start_time = 0
  3955. self.host_finish_time = 0
  3956. def FileFromBuildId(self, build_id):
  3957. file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
  3958. return TryOpen(file_name)
  3959. def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
  3960. # Assume current machine i.e. no support for virtualization
  3961. if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
  3962. file_name = os.getenv("PERF_KCORE")
  3963. f = TryOpen(file_name) if file_name else None
  3964. if f:
  3965. return f
  3966. # For now, no special handling if long_name is /proc/kcore
  3967. f = TryOpen(long_name)
  3968. if f:
  3969. return f
  3970. f = self.FileFromBuildId(build_id)
  3971. if f:
  3972. return f
  3973. return None
  3974. def AddInstanceToShutdownOnExit(self, instance):
  3975. self.instances_to_shutdown_on_exit.add(instance)
  3976. # Shutdown any background processes or threads
  3977. def ShutdownInstances(self):
  3978. for x in self.instances_to_shutdown_on_exit:
  3979. try:
  3980. x.Shutdown()
  3981. except:
  3982. pass
  3983. def GetHostMachineId(self):
  3984. query = QSqlQuery(self.db)
  3985. QueryExec(query, "SELECT id FROM machines WHERE pid = -1")
  3986. if query.next():
  3987. self.host_machine_id = query.value(0)
  3988. else:
  3989. self.host_machine_id = 0
  3990. return self.host_machine_id
  3991. def HostMachineId(self):
  3992. if self.host_machine_id:
  3993. return self.host_machine_id
  3994. return self.GetHostMachineId()
  3995. def SelectValue(self, sql):
  3996. query = QSqlQuery(self.db)
  3997. try:
  3998. QueryExec(query, sql)
  3999. except:
  4000. return None
  4001. if query.next():
  4002. return Decimal(query.value(0))
  4003. return None
  4004. def SwitchesMinTime(self, machine_id):
  4005. return self.SelectValue("SELECT time"
  4006. " FROM context_switches"
  4007. " WHERE time != 0 AND machine_id = " + str(machine_id) +
  4008. " ORDER BY id LIMIT 1")
  4009. def SwitchesMaxTime(self, machine_id):
  4010. return self.SelectValue("SELECT time"
  4011. " FROM context_switches"
  4012. " WHERE time != 0 AND machine_id = " + str(machine_id) +
  4013. " ORDER BY id DESC LIMIT 1")
  4014. def SamplesMinTime(self, machine_id):
  4015. return self.SelectValue("SELECT time"
  4016. " FROM samples"
  4017. " WHERE time != 0 AND machine_id = " + str(machine_id) +
  4018. " ORDER BY id LIMIT 1")
  4019. def SamplesMaxTime(self, machine_id):
  4020. return self.SelectValue("SELECT time"
  4021. " FROM samples"
  4022. " WHERE time != 0 AND machine_id = " + str(machine_id) +
  4023. " ORDER BY id DESC LIMIT 1")
  4024. def CallsMinTime(self, machine_id):
  4025. return self.SelectValue("SELECT calls.call_time"
  4026. " FROM calls"
  4027. " INNER JOIN threads ON threads.thread_id = calls.thread_id"
  4028. " WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id) +
  4029. " ORDER BY calls.id LIMIT 1")
  4030. def CallsMaxTime(self, machine_id):
  4031. return self.SelectValue("SELECT calls.return_time"
  4032. " FROM calls"
  4033. " INNER JOIN threads ON threads.thread_id = calls.thread_id"
  4034. " WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id) +
  4035. " ORDER BY calls.return_time DESC LIMIT 1")
  4036. def GetStartTime(self, machine_id):
  4037. t0 = self.SwitchesMinTime(machine_id)
  4038. t1 = self.SamplesMinTime(machine_id)
  4039. t2 = self.CallsMinTime(machine_id)
  4040. if t0 is None or (not(t1 is None) and t1 < t0):
  4041. t0 = t1
  4042. if t0 is None or (not(t2 is None) and t2 < t0):
  4043. t0 = t2
  4044. return t0
  4045. def GetFinishTime(self, machine_id):
  4046. t0 = self.SwitchesMaxTime(machine_id)
  4047. t1 = self.SamplesMaxTime(machine_id)
  4048. t2 = self.CallsMaxTime(machine_id)
  4049. if t0 is None or (not(t1 is None) and t1 > t0):
  4050. t0 = t1
  4051. if t0 is None or (not(t2 is None) and t2 > t0):
  4052. t0 = t2
  4053. return t0
  4054. def HostStartTime(self):
  4055. if self.host_start_time:
  4056. return self.host_start_time
  4057. self.host_start_time = self.GetStartTime(self.HostMachineId())
  4058. return self.host_start_time
  4059. def HostFinishTime(self):
  4060. if self.host_finish_time:
  4061. return self.host_finish_time
  4062. self.host_finish_time = self.GetFinishTime(self.HostMachineId())
  4063. return self.host_finish_time
  4064. def StartTime(self, machine_id):
  4065. if machine_id == self.HostMachineId():
  4066. return self.HostStartTime()
  4067. return self.GetStartTime(machine_id)
  4068. def FinishTime(self, machine_id):
  4069. if machine_id == self.HostMachineId():
  4070. return self.HostFinishTime()
  4071. return self.GetFinishTime(machine_id)
  4072. # Database reference
  4073. class DBRef():
  4074. def __init__(self, is_sqlite3, dbname):
  4075. self.is_sqlite3 = is_sqlite3
  4076. self.dbname = dbname
  4077. self.TRUE = "TRUE"
  4078. self.FALSE = "FALSE"
  4079. # SQLite prior to version 3.23 does not support TRUE and FALSE
  4080. if self.is_sqlite3:
  4081. self.TRUE = "1"
  4082. self.FALSE = "0"
  4083. def Open(self, connection_name):
  4084. dbname = self.dbname
  4085. if self.is_sqlite3:
  4086. db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
  4087. else:
  4088. db = QSqlDatabase.addDatabase("QPSQL", connection_name)
  4089. opts = dbname.split()
  4090. for opt in opts:
  4091. if "=" in opt:
  4092. opt = opt.split("=")
  4093. if opt[0] == "hostname":
  4094. db.setHostName(opt[1])
  4095. elif opt[0] == "port":
  4096. db.setPort(int(opt[1]))
  4097. elif opt[0] == "username":
  4098. db.setUserName(opt[1])
  4099. elif opt[0] == "password":
  4100. db.setPassword(opt[1])
  4101. elif opt[0] == "dbname":
  4102. dbname = opt[1]
  4103. else:
  4104. dbname = opt
  4105. db.setDatabaseName(dbname)
  4106. if not db.open():
  4107. raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
  4108. return db, dbname
  4109. # Main
  4110. def Main():
  4111. usage_str = "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
  4112. " or: exported-sql-viewer.py --help-only"
  4113. ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
  4114. ap.add_argument("--pyside-version-1", action='store_true')
  4115. ap.add_argument("dbname", nargs="?")
  4116. ap.add_argument("--help-only", action='store_true')
  4117. args = ap.parse_args()
  4118. if args.help_only:
  4119. app = QApplication(sys.argv)
  4120. mainwindow = HelpOnlyWindow()
  4121. mainwindow.show()
  4122. err = app.exec_()
  4123. sys.exit(err)
  4124. dbname = args.dbname
  4125. if dbname is None:
  4126. ap.print_usage()
  4127. print("Too few arguments")
  4128. sys.exit(1)
  4129. is_sqlite3 = False
  4130. try:
  4131. f = open(dbname, "rb")
  4132. if f.read(15) == b'SQLite format 3':
  4133. is_sqlite3 = True
  4134. f.close()
  4135. except:
  4136. pass
  4137. dbref = DBRef(is_sqlite3, dbname)
  4138. db, dbname = dbref.Open("main")
  4139. glb = Glb(dbref, db, dbname)
  4140. app = QApplication(sys.argv)
  4141. glb.app = app
  4142. mainwindow = MainWindow(glb)
  4143. glb.mainwindow = mainwindow
  4144. mainwindow.show()
  4145. err = app.exec_()
  4146. glb.ShutdownInstances()
  4147. db.close()
  4148. sys.exit(err)
  4149. if __name__ == "__main__":
  4150. Main()