PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/golang/1.22.0/src/internal/profile
Viewing File: /opt/golang/1.22.0/src/internal/profile/legacy_profile.go
// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file implements parsers to convert legacy profiles into the // profile.proto format. package profile import ( "bufio" "bytes" "fmt" "internal/lazyregexp" "io" "math" "strconv" "strings" ) var ( countStartRE = lazyregexp.New(`\A(\w+) profile: total \d+\n\z`) countRE = lazyregexp.New(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`) heapHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`) heapSampleRE = lazyregexp.New(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`) contentionSampleRE = lazyregexp.New(`(\d+) *(\d+) @([ x0-9a-f]*)`) hexNumberRE = lazyregexp.New(`0x[0-9a-f]+`) growthHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`) fragmentationHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`) threadzStartRE = lazyregexp.New(`--- threadz \d+ ---`) threadStartRE = lazyregexp.New(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`) procMapsRE = lazyregexp.New(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`) briefMapsRE = lazyregexp.New(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`) // LegacyHeapAllocated instructs the heapz parsers to use the // allocated memory stats instead of the default in-use memory. Note // that tcmalloc doesn't provide all allocated memory, only in-use // stats. LegacyHeapAllocated bool ) func isSpaceOrComment(line string) bool { trimmed := strings.TrimSpace(line) return len(trimmed) == 0 || trimmed[0] == '#' } // parseGoCount parses a Go count profile (e.g., threadcreate or // goroutine) and returns a new Profile. func parseGoCount(b []byte) (*Profile, error) { r := bytes.NewBuffer(b) var line string var err error for { // Skip past comments and empty lines seeking a real header. line, err = r.ReadString('\n') if err != nil { return nil, err } if !isSpaceOrComment(line) { break } } m := countStartRE.FindStringSubmatch(line) if m == nil { return nil, errUnrecognized } profileType := m[1] p := &Profile{ PeriodType: &ValueType{Type: profileType, Unit: "count"}, Period: 1, SampleType: []*ValueType{{Type: profileType, Unit: "count"}}, } locations := make(map[uint64]*Location) for { line, err = r.ReadString('\n') if err != nil { if err == io.EOF { break } return nil, err } if isSpaceOrComment(line) { continue } if strings.HasPrefix(line, "---") { break } m := countRE.FindStringSubmatch(line) if m == nil { return nil, errMalformed } n, err := strconv.ParseInt(m[1], 0, 64) if err != nil { return nil, errMalformed } fields := strings.Fields(m[2]) locs := make([]*Location, 0, len(fields)) for _, stk := range fields { addr, err := strconv.ParseUint(stk, 0, 64) if err != nil { return nil, errMalformed } // Adjust all frames by -1 to land on the call instruction. addr-- loc := locations[addr] if loc == nil { loc = &Location{ Address: addr, } locations[addr] = loc p.Location = append(p.Location, loc) } locs = append(locs, loc) } p.Sample = append(p.Sample, &Sample{ Location: locs, Value: []int64{n}, }) } if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil { return nil, err } return p, nil } // remapLocationIDs ensures there is a location for each address // referenced by a sample, and remaps the samples to point to the new // location ids. func (p *Profile) remapLocationIDs() { seen := make(map[*Location]bool, len(p.Location)) var locs []*Location for _, s := range p.Sample { for _, l := range s.Location { if seen[l] { continue } l.ID = uint64(len(locs) + 1) locs = append(locs, l) seen[l] = true } } p.Location = locs } func (p *Profile) remapFunctionIDs() { seen := make(map[*Function]bool, len(p.Function)) var fns []*Function for _, l := range p.Location { for _, ln := range l.Line { fn := ln.Function if fn == nil || seen[fn] { continue } fn.ID = uint64(len(fns) + 1) fns = append(fns, fn) seen[fn] = true } } p.Function = fns } // remapMappingIDs matches location addresses with existing mappings // and updates them appropriately. This is O(N*M), if this ever shows // up as a bottleneck, evaluate sorting the mappings and doing a // binary search, which would make it O(N*log(M)). func (p *Profile) remapMappingIDs() { if len(p.Mapping) == 0 { return } // Some profile handlers will incorrectly set regions for the main // executable if its section is remapped. Fix them through heuristics. // Remove the initial mapping if named '/anon_hugepage' and has a // consecutive adjacent mapping. if m := p.Mapping[0]; strings.HasPrefix(m.File, "/anon_hugepage") { if len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start { p.Mapping = p.Mapping[1:] } } for _, l := range p.Location { if a := l.Address; a != 0 { for _, m := range p.Mapping { if m.Start <= a && a < m.Limit { l.Mapping = m break } } } } // Reset all mapping IDs. for i, m := range p.Mapping { m.ID = uint64(i + 1) } } var cpuInts = []func([]byte) (uint64, []byte){ get32l, get32b, get64l, get64b, } func get32l(b []byte) (uint64, []byte) { if len(b) < 4 { return 0, nil } return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:] } func get32b(b []byte) (uint64, []byte) { if len(b) < 4 { return 0, nil } return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:] } func get64l(b []byte) (uint64, []byte) { if len(b) < 8 { return 0, nil } return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:] } func get64b(b []byte) (uint64, []byte) { if len(b) < 8 { return 0, nil } return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:] } // ParseTracebacks parses a set of tracebacks and returns a newly // populated profile. It will accept any text file and generate a // Profile out of it with any hex addresses it can identify, including // a process map if it can recognize one. Each sample will include a // tag "source" with the addresses recognized in string format. func ParseTracebacks(b []byte) (*Profile, error) { r := bytes.NewBuffer(b) p := &Profile{ PeriodType: &ValueType{Type: "trace", Unit: "count"}, Period: 1, SampleType: []*ValueType{ {Type: "trace", Unit: "count"}, }, } var sources []string var sloc []*Location locs := make(map[uint64]*Location) for { l, err := r.ReadString('\n') if err != nil { if err != io.EOF { return nil, err } if l == "" { break } } if sectionTrigger(l) == memoryMapSection { break } if s, addrs := extractHexAddresses(l); len(s) > 0 { for _, addr := range addrs { // Addresses from stack traces point to the next instruction after // each call. Adjust by -1 to land somewhere on the actual call. addr-- loc := locs[addr] if locs[addr] == nil { loc = &Location{ Address: addr, } p.Location = append(p.Location, loc) locs[addr] = loc } sloc = append(sloc, loc) } sources = append(sources, s...) } else { if len(sources) > 0 || len(sloc) > 0 { addTracebackSample(sloc, sources, p) sloc, sources = nil, nil } } } // Add final sample to save any leftover data. if len(sources) > 0 || len(sloc) > 0 { addTracebackSample(sloc, sources, p) } if err := p.ParseMemoryMap(r); err != nil { return nil, err } return p, nil } func addTracebackSample(l []*Location, s []string, p *Profile) { p.Sample = append(p.Sample, &Sample{ Value: []int64{1}, Location: l, Label: map[string][]string{"source": s}, }) } // parseCPU parses a profilez legacy profile and returns a newly // populated Profile. // // The general format for profilez samples is a sequence of words in // binary format. The first words are a header with the following data: // // 1st word -- 0 // 2nd word -- 3 // 3rd word -- 0 if a c++ application, 1 if a java application. // 4th word -- Sampling period (in microseconds). // 5th word -- Padding. func parseCPU(b []byte) (*Profile, error) { var parse func([]byte) (uint64, []byte) var n1, n2, n3, n4, n5 uint64 for _, parse = range cpuInts { var tmp []byte n1, tmp = parse(b) n2, tmp = parse(tmp) n3, tmp = parse(tmp) n4, tmp = parse(tmp) n5, tmp = parse(tmp) if tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 { b = tmp return cpuProfile(b, int64(n4), parse) } } return nil, errUnrecognized } // cpuProfile returns a new Profile from C++ profilez data. // b is the profile bytes after the header, period is the profiling // period, and parse is a function to parse 8-byte chunks from the // profile in its native endianness. func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) { p := &Profile{ Period: period * 1000, PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"}, SampleType: []*ValueType{ {Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}, }, } var err error if b, _, err = parseCPUSamples(b, parse, true, p); err != nil { return nil, err } // If all samples have the same second-to-the-bottom frame, it // strongly suggests that it is an uninteresting artifact of // measurement -- a stack frame pushed by the signal handler. The // bottom frame is always correct as it is picked up from the signal // structure, not the stack. Check if this is the case and if so, // remove. if len(p.Sample) > 1 && len(p.Sample[0].Location) > 1 { allSame := true id1 := p.Sample[0].Location[1].Address for _, s := range p.Sample { if len(s.Location) < 2 || id1 != s.Location[1].Address { allSame = false break } } if allSame { for _, s := range p.Sample { s.Location = append(s.Location[:1], s.Location[2:]...) } } } if err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil { return nil, err } return p, nil } // parseCPUSamples parses a collection of profilez samples from a // profile. // // profilez samples are a repeated sequence of stack frames of the // form: // // 1st word -- The number of times this stack was encountered. // 2nd word -- The size of the stack (StackSize). // 3rd word -- The first address on the stack. // ... // StackSize + 2 -- The last address on the stack // // The last stack trace is of the form: // // 1st word -- 0 // 2nd word -- 1 // 3rd word -- 0 // // Addresses from stack traces may point to the next instruction after // each call. Optionally adjust by -1 to land somewhere on the actual // call (except for the leaf, which is not a call). func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) { locs := make(map[uint64]*Location) for len(b) > 0 { var count, nstk uint64 count, b = parse(b) nstk, b = parse(b) if b == nil || nstk > uint64(len(b)/4) { return nil, nil, errUnrecognized } var sloc []*Location addrs := make([]uint64, nstk) for i := 0; i < int(nstk); i++ { addrs[i], b = parse(b) } if count == 0 && nstk == 1 && addrs[0] == 0 { // End of data marker break } for i, addr := range addrs { if adjust && i > 0 { addr-- } loc := locs[addr] if loc == nil { loc = &Location{ Address: addr, } locs[addr] = loc p.Location = append(p.Location, loc) } sloc = append(sloc, loc) } p.Sample = append(p.Sample, &Sample{ Value: []int64{int64(count), int64(count) * p.Period}, Location: sloc, }) } // Reached the end without finding the EOD marker. return b, locs, nil } // parseHeap parses a heapz legacy or a growthz profile and // returns a newly populated Profile. func parseHeap(b []byte) (p *Profile, err error) { r := bytes.NewBuffer(b) l, err := r.ReadString('\n') if err != nil { return nil, errUnrecognized } sampling := "" if header := heapHeaderRE.FindStringSubmatch(l); header != nil { p = &Profile{ SampleType: []*ValueType{ {Type: "objects", Unit: "count"}, {Type: "space", Unit: "bytes"}, }, PeriodType: &ValueType{Type: "objects", Unit: "bytes"}, } var period int64 if len(header[6]) > 0 { if period, err = strconv.ParseInt(header[6], 10, 64); err != nil { return nil, errUnrecognized } } switch header[5] { case "heapz_v2", "heap_v2": sampling, p.Period = "v2", period case "heapprofile": sampling, p.Period = "", 1 case "heap": sampling, p.Period = "v2", period/2 default: return nil, errUnrecognized } } else if header = growthHeaderRE.FindStringSubmatch(l); header != nil { p = &Profile{ SampleType: []*ValueType{ {Type: "objects", Unit: "count"}, {Type: "space", Unit: "bytes"}, }, PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"}, Period: 1, } } else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil { p = &Profile{ SampleType: []*ValueType{ {Type: "objects", Unit: "count"}, {Type: "space", Unit: "bytes"}, }, PeriodType: &ValueType{Type: "allocations", Unit: "count"}, Period: 1, } } else { return nil, errUnrecognized } if LegacyHeapAllocated { for _, st := range p.SampleType { st.Type = "alloc_" + st.Type } } else { for _, st := range p.SampleType { st.Type = "inuse_" + st.Type } } locs := make(map[uint64]*Location) for { l, err = r.ReadString('\n') if err != nil { if err != io.EOF { return nil, err } if l == "" { break } } if isSpaceOrComment(l) { continue } l = strings.TrimSpace(l) if sectionTrigger(l) != unrecognizedSection { break } value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling) if err != nil { return nil, err } var sloc []*Location for _, addr := range addrs { // Addresses from stack traces point to the next instruction after // each call. Adjust by -1 to land somewhere on the actual call. addr-- loc := locs[addr] if locs[addr] == nil { loc = &Location{ Address: addr, } p.Location = append(p.Location, loc) locs[addr] = loc } sloc = append(sloc, loc) } p.Sample = append(p.Sample, &Sample{ Value: value, Location: sloc, NumLabel: map[string][]int64{"bytes": {blocksize}}, }) } if err = parseAdditionalSections(l, r, p); err != nil { return nil, err } return p, nil } // parseHeapSample parses a single row from a heap profile into a new Sample. func parseHeapSample(line string, rate int64, sampling string) (value []int64, blocksize int64, addrs []uint64, err error) { sampleData := heapSampleRE.FindStringSubmatch(line) if len(sampleData) != 6 { return value, blocksize, addrs, fmt.Errorf("unexpected number of sample values: got %d, want 6", len(sampleData)) } // Use first two values by default; tcmalloc sampling generates the // same value for both, only the older heap-profile collect separate // stats for in-use and allocated objects. valueIndex := 1 if LegacyHeapAllocated { valueIndex = 3 } var v1, v2 int64 if v1, err = strconv.ParseInt(sampleData[valueIndex], 10, 64); err != nil { return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) } if v2, err = strconv.ParseInt(sampleData[valueIndex+1], 10, 64); err != nil { return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) } if v1 == 0 { if v2 != 0 { return value, blocksize, addrs, fmt.Errorf("allocation count was 0 but allocation bytes was %d", v2) } } else { blocksize = v2 / v1 if sampling == "v2" { v1, v2 = scaleHeapSample(v1, v2, rate) } } value = []int64{v1, v2} addrs = parseHexAddresses(sampleData[5]) return value, blocksize, addrs, nil } // extractHexAddresses extracts hex numbers from a string and returns // them, together with their numeric value, in a slice. func extractHexAddresses(s string) ([]string, []uint64) { hexStrings := hexNumberRE.FindAllString(s, -1) var ids []uint64 for _, s := range hexStrings { if id, err := strconv.ParseUint(s, 0, 64); err == nil { ids = append(ids, id) } else { // Do not expect any parsing failures due to the regexp matching. panic("failed to parse hex value:" + s) } } return hexStrings, ids } // parseHexAddresses parses hex numbers from a string and returns them // in a slice. func parseHexAddresses(s string) []uint64 { _, ids := extractHexAddresses(s) return ids } // scaleHeapSample adjusts the data from a heapz Sample to // account for its probability of appearing in the collected // data. heapz profiles are a sampling of the memory allocations // requests in a program. We estimate the unsampled value by dividing // each collected sample by its probability of appearing in the // profile. heapz v2 profiles rely on a poisson process to determine // which samples to collect, based on the desired average collection // rate R. The probability of a sample of size S to appear in that // profile is 1-exp(-S/R). func scaleHeapSample(count, size, rate int64) (int64, int64) { if count == 0 || size == 0 { return 0, 0 } if rate <= 1 { // if rate==1 all samples were collected so no adjustment is needed. // if rate<1 treat as unknown and skip scaling. return count, size } avgSize := float64(size) / float64(count) scale := 1 / (1 - math.Exp(-avgSize/float64(rate))) return int64(float64(count) * scale), int64(float64(size) * scale) } // parseContention parses a mutex or contention profile. There are 2 cases: // "--- contentionz " for legacy C++ profiles (and backwards compatibility) // "--- mutex:" or "--- contention:" for profiles generated by the Go runtime. // This code converts the text output from runtime into a *Profile. (In the future // the runtime might write a serialized Profile directly making this unnecessary.) func parseContention(b []byte) (*Profile, error) { r := bytes.NewBuffer(b) var l string var err error for { // Skip past comments and empty lines seeking a real header. l, err = r.ReadString('\n') if err != nil { return nil, err } if !isSpaceOrComment(l) { break } } if strings.HasPrefix(l, "--- contentionz ") { return parseCppContention(r) } else if strings.HasPrefix(l, "--- mutex:") { return parseCppContention(r) } else if strings.HasPrefix(l, "--- contention:") { return parseCppContention(r) } return nil, errUnrecognized } // parseCppContention parses the output from synchronization_profiling.cc // for backward compatibility, and the compatible (non-debug) block profile // output from the Go runtime. func parseCppContention(r *bytes.Buffer) (*Profile, error) { p := &Profile{ PeriodType: &ValueType{Type: "contentions", Unit: "count"}, Period: 1, SampleType: []*ValueType{ {Type: "contentions", Unit: "count"}, {Type: "delay", Unit: "nanoseconds"}, }, } var cpuHz int64 var l string var err error // Parse text of the form "attribute = value" before the samples. const delimiter = '=' for { l, err = r.ReadString('\n') if err != nil { if err != io.EOF { return nil, err } if l == "" { break } } if isSpaceOrComment(l) { continue } if l = strings.TrimSpace(l); l == "" { continue } if strings.HasPrefix(l, "---") { break } index := strings.IndexByte(l, delimiter) if index < 0 { break } key := l[:index] val := l[index+1:] key, val = strings.TrimSpace(key), strings.TrimSpace(val) var err error switch key { case "cycles/second": if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil { return nil, errUnrecognized } case "sampling period": if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil { return nil, errUnrecognized } case "ms since reset": ms, err := strconv.ParseInt(val, 0, 64) if err != nil { return nil, errUnrecognized } p.DurationNanos = ms * 1000 * 1000 case "format": // CPP contentionz profiles don't have format. return nil, errUnrecognized case "resolution": // CPP contentionz profiles don't have resolution. return nil, errUnrecognized case "discarded samples": default: return nil, errUnrecognized } } locs := make(map[uint64]*Location) for { if !isSpaceOrComment(l) { if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") { break } value, addrs, err := parseContentionSample(l, p.Period, cpuHz) if err != nil { return nil, err } var sloc []*Location for _, addr := range addrs { // Addresses from stack traces point to the next instruction after // each call. Adjust by -1 to land somewhere on the actual call. addr-- loc := locs[addr] if locs[addr] == nil { loc = &Location{ Address: addr, } p.Location = append(p.Location, loc) locs[addr] = loc } sloc = append(sloc, loc) } p.Sample = append(p.Sample, &Sample{ Value: value, Location: sloc, }) } if l, err = r.ReadString('\n'); err != nil { if err != io.EOF { return nil, err } if l == "" { break } } } if err = parseAdditionalSections(l, r, p); err != nil { return nil, err } return p, nil } // parseContentionSample parses a single row from a contention profile // into a new Sample. func parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) { sampleData := contentionSampleRE.FindStringSubmatch(line) if sampleData == nil { return value, addrs, errUnrecognized } v1, err := strconv.ParseInt(sampleData[1], 10, 64) if err != nil { return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) } v2, err := strconv.ParseInt(sampleData[2], 10, 64) if err != nil { return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) } // Unsample values if period and cpuHz are available. // - Delays are scaled to cycles and then to nanoseconds. // - Contentions are scaled to cycles. if period > 0 { if cpuHz > 0 { cpuGHz := float64(cpuHz) / 1e9 v1 = int64(float64(v1) * float64(period) / cpuGHz) } v2 = v2 * period } value = []int64{v2, v1} addrs = parseHexAddresses(sampleData[3]) return value, addrs, nil } // parseThread parses a Threadz profile and returns a new Profile. func parseThread(b []byte) (*Profile, error) { r := bytes.NewBuffer(b) var line string var err error for { // Skip past comments and empty lines seeking a real header. line, err = r.ReadString('\n') if err != nil { return nil, err } if !isSpaceOrComment(line) { break } } if m := threadzStartRE.FindStringSubmatch(line); m != nil { // Advance over initial comments until first stack trace. for { line, err = r.ReadString('\n') if err != nil { if err != io.EOF { return nil, err } if line == "" { break } } if sectionTrigger(line) != unrecognizedSection || line[0] == '-' { break } } } else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { return nil, errUnrecognized } p := &Profile{ SampleType: []*ValueType{{Type: "thread", Unit: "count"}}, PeriodType: &ValueType{Type: "thread", Unit: "count"}, Period: 1, } locs := make(map[uint64]*Location) // Recognize each thread and populate profile samples. for sectionTrigger(line) == unrecognizedSection { if strings.HasPrefix(line, "---- no stack trace for") { line = "" break } if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { return nil, errUnrecognized } var addrs []uint64 line, addrs, err = parseThreadSample(r) if err != nil { return nil, errUnrecognized } if len(addrs) == 0 { // We got a --same as previous threads--. Bump counters. if len(p.Sample) > 0 { s := p.Sample[len(p.Sample)-1] s.Value[0]++ } continue } var sloc []*Location for _, addr := range addrs { // Addresses from stack traces point to the next instruction after // each call. Adjust by -1 to land somewhere on the actual call. addr-- loc := locs[addr] if locs[addr] == nil { loc = &Location{ Address: addr, } p.Location = append(p.Location, loc) locs[addr] = loc } sloc = append(sloc, loc) } p.Sample = append(p.Sample, &Sample{ Value: []int64{1}, Location: sloc, }) } if err = parseAdditionalSections(line, r, p); err != nil { return nil, err } return p, nil } // parseThreadSample parses a symbolized or unsymbolized stack trace. // Returns the first line after the traceback, the sample (or nil if // it hits a 'same-as-previous' marker) and an error. func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) { var l string sameAsPrevious := false for { if l, err = b.ReadString('\n'); err != nil { if err != io.EOF { return "", nil, err } if l == "" { break } } if l = strings.TrimSpace(l); l == "" { continue } if strings.HasPrefix(l, "---") { break } if strings.Contains(l, "same as previous thread") { sameAsPrevious = true continue } addrs = append(addrs, parseHexAddresses(l)...) } if sameAsPrevious { return l, nil, nil } return l, addrs, nil } // parseAdditionalSections parses any additional sections in the // profile, ignoring any unrecognized sections. func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) (err error) { for { if sectionTrigger(l) == memoryMapSection { break } // Ignore any unrecognized sections. if l, err := b.ReadString('\n'); err != nil { if err != io.EOF { return err } if l == "" { break } } } return p.ParseMemoryMap(b) } // ParseMemoryMap parses a memory map in the format of // /proc/self/maps, and overrides the mappings in the current profile. // It renumbers the samples and locations in the profile correspondingly. func (p *Profile) ParseMemoryMap(rd io.Reader) error { b := bufio.NewReader(rd) var attrs []string var r *strings.Replacer const delimiter = '=' for { l, err := b.ReadString('\n') if err != nil { if err != io.EOF { return err } if l == "" { break } } if l = strings.TrimSpace(l); l == "" { continue } if r != nil { l = r.Replace(l) } m, err := parseMappingEntry(l) if err != nil { if err == errUnrecognized { // Recognize assignments of the form: attr=value, and replace // $attr with value on subsequent mappings. idx := strings.IndexByte(l, delimiter) if idx >= 0 { attr := l[:idx] value := l[idx+1:] attrs = append(attrs, "$"+strings.TrimSpace(attr), strings.TrimSpace(value)) r = strings.NewReplacer(attrs...) } // Ignore any unrecognized entries continue } return err } if m == nil || (m.File == "" && len(p.Mapping) != 0) { // In some cases the first entry may include the address range // but not the name of the file. It should be followed by // another entry with the name. continue } if len(p.Mapping) == 1 && p.Mapping[0].File == "" { // Update the name if this is the entry following that empty one. p.Mapping[0].File = m.File continue } p.Mapping = append(p.Mapping, m) } p.remapLocationIDs() p.remapFunctionIDs() p.remapMappingIDs() return nil } func parseMappingEntry(l string) (*Mapping, error) { mapping := &Mapping{} var err error if me := procMapsRE.FindStringSubmatch(l); len(me) == 9 { if !strings.Contains(me[3], "x") { // Skip non-executable entries. return nil, nil } if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { return nil, errUnrecognized } if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { return nil, errUnrecognized } if me[4] != "" { if mapping.Offset, err = strconv.ParseUint(me[4], 16, 64); err != nil { return nil, errUnrecognized } } mapping.File = me[8] return mapping, nil } if me := briefMapsRE.FindStringSubmatch(l); len(me) == 6 { if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { return nil, errUnrecognized } if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { return nil, errUnrecognized } mapping.File = me[3] if me[5] != "" { if mapping.Offset, err = strconv.ParseUint(me[5], 16, 64); err != nil { return nil, errUnrecognized } } return mapping, nil } return nil, errUnrecognized } type sectionType int const ( unrecognizedSection sectionType = iota memoryMapSection ) var memoryMapTriggers = []string{ "--- Memory map: ---", "MAPPED_LIBRARIES:", } func sectionTrigger(line string) sectionType { for _, trigger := range memoryMapTriggers { if strings.Contains(line, trigger) { return memoryMapSection } } return unrecognizedSection } func (p *Profile) addLegacyFrameInfo() { switch { case isProfileType(p, heapzSampleTypes) || isProfileType(p, heapzInUseSampleTypes) || isProfileType(p, heapzAllocSampleTypes): p.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr case isProfileType(p, contentionzSampleTypes): p.DropFrames, p.KeepFrames = lockRxStr, "" default: p.DropFrames, p.KeepFrames = cpuProfilerRxStr, "" } } var heapzSampleTypes = []string{"allocations", "size"} // early Go pprof profiles var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"} var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"} var contentionzSampleTypes = []string{"contentions", "delay"} func isProfileType(p *Profile, t []string) bool { st := p.SampleType if len(st) != len(t) { return false } for i := range st { if st[i].Type != t[i] { return false } } return true } var allocRxStr = strings.Join([]string{ // POSIX entry points. `calloc`, `cfree`, `malloc`, `free`, `memalign`, `do_memalign`, `(__)?posix_memalign`, `pvalloc`, `valloc`, `realloc`, // TC malloc. `tcmalloc::.*`, `tc_calloc`, `tc_cfree`, `tc_malloc`, `tc_free`, `tc_memalign`, `tc_posix_memalign`, `tc_pvalloc`, `tc_valloc`, `tc_realloc`, `tc_new`, `tc_delete`, `tc_newarray`, `tc_deletearray`, `tc_new_nothrow`, `tc_newarray_nothrow`, // Memory-allocation routines on OS X. `malloc_zone_malloc`, `malloc_zone_calloc`, `malloc_zone_valloc`, `malloc_zone_realloc`, `malloc_zone_memalign`, `malloc_zone_free`, // Go runtime `runtime\..*`, // Other misc. memory allocation routines `BaseArena::.*`, `(::)?do_malloc_no_errno`, `(::)?do_malloc_pages`, `(::)?do_malloc`, `DoSampledAllocation`, `MallocedMemBlock::MallocedMemBlock`, `_M_allocate`, `__builtin_(vec_)?delete`, `__builtin_(vec_)?new`, `__gnu_cxx::new_allocator::allocate`, `__libc_malloc`, `__malloc_alloc_template::allocate`, `allocate`, `cpp_alloc`, `operator new(\[\])?`, `simple_alloc::allocate`, }, `|`) var allocSkipRxStr = strings.Join([]string{ // Preserve Go runtime frames that appear in the middle/bottom of // the stack. `runtime\.panic`, `runtime\.reflectcall`, `runtime\.call[0-9]*`, }, `|`) var cpuProfilerRxStr = strings.Join([]string{ `ProfileData::Add`, `ProfileData::prof_handler`, `CpuProfiler::prof_handler`, `__pthread_sighandler`, `__restore`, }, `|`) var lockRxStr = strings.Join([]string{ `RecordLockProfileData`, `(base::)?RecordLockProfileData.*`, `(base::)?SubmitMutexProfileData.*`, `(base::)?SubmitSpinLockProfileData.*`, `(Mutex::)?AwaitCommon.*`, `(Mutex::)?Unlock.*`, `(Mutex::)?UnlockSlow.*`, `(Mutex::)?ReaderUnlock.*`, `(MutexLock::)?~MutexLock.*`, `(SpinLock::)?Unlock.*`, `(SpinLock::)?SlowUnlock.*`, `(SpinLockHolder::)?~SpinLockHolder.*`, }, `|`)