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: /usr/bin
Viewing File: /usr/bin/pmrep
#!/usr/bin/pmpython # # Copyright (C) 2015-2021 Marko Myllynen <myllynen@redhat.com> # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # pylint: disable=superfluous-parens # pylint: disable=invalid-name, line-too-long, no-self-use # pylint: disable=too-many-boolean-expressions, too-many-statements # pylint: disable=too-many-instance-attributes, too-many-locals # pylint: disable=too-many-branches, too-many-nested-blocks # pylint: disable=broad-except, too-many-arguments # pylint: disable=too-many-lines, too-many-public-methods """ Performance Metrics Reporter """ # Common imports from collections import OrderedDict import errno import sys # Our imports from datetime import datetime, timedelta import signal import shutil import time import math import re import os # PCP Python PMAPI from pcp import pmapi, pmi, pmconfig from cpmapi import PM_CONTEXT_ARCHIVE, PM_CONTEXT_LOCAL from cpmapi import PM_INDOM_NULL, PM_IN_NULL, PM_DEBUG_APPL1, PM_TIME_SEC from cpmapi import PM_SEM_DISCRETE, PM_TYPE_STRING from cpmapi import PM_TEXT_PMID, PM_TEXT_INDOM, PM_TEXT_ONELINE, PM_TEXT_HELP from cpmapi import PM_LABEL_INDOM, PM_LABEL_INSTANCES from cpmapi import PM_LABEL_DOMAIN, PM_LABEL_CLUSTER, PM_LABEL_ITEM from cpmi import PMI_ERR_DUPINSTNAME, PMI_ERR_DUPTEXT if sys.version_info[0] >= 3: long = int # pylint: disable=redefined-builtin # Default config DEFAULT_CONFIG = ["./pmrep.conf", "$HOME/.pmrep.conf", "$HOME/.pcp/pmrep.conf", "$PCP_SYSCONF_DIR/pmrep/pmrep.conf", "$PCP_SYSCONF_DIR/pmrep"] # Defaults CONFVER = 1 CSVSEP = "," CSVTIME = "%Y-%m-%d %H:%M:%S" OUTSEP = " " OUTTIME = "%H:%M:%S" NO_VAL = "N/A" NO_INST = "~" SINGULR = "=" # pmrep output targets OUTPUT_ARCHIVE = "archive" OUTPUT_CSV = "csv" OUTPUT_STDOUT = "stdout" class PMReporter(object): """ Report PCP metrics """ def __init__(self): """ Construct object, prepare for command line handling """ self.context = None self.daemonize = 0 self.pmconfig = pmconfig.pmConfig(self) self.opts = self.options() # Configuration directives self.keys = ('source', 'output', 'derived', 'header', 'globals', 'samples', 'interval', 'type', 'precision', 'daemonize', 'timestamp', 'unitinfo', 'colxrow', 'separate_header', 'fixed_header', 'delay', 'width', 'delimiter', 'extcsv', 'width_force', 'extheader', 'repeat_header', 'timefmt', 'interpol', 'dynamic_header', 'overall_rank', 'overall_rank_alt', 'sort_metric', 'count_scale', 'space_scale', 'time_scale', 'version', 'count_scale_force', 'space_scale_force', 'time_scale_force', 'type_prefer', 'precision_force', 'limit_filter', 'limit_filter_force', 'live_filter', 'rank', 'invert_filter', 'predicate', 'names_change', 'speclocal', 'instances', 'ignore_incompat', 'ignore_unknown', 'omit_flat', 'instinfo', 'include_labels', 'include_texts') # The order of preference for options (as present): # 1 - command line options # 2 - options from configuration file(s) # 3 - built-in defaults defined below self.check = 0 self.version = CONFVER self.source = "local:" self.output = OUTPUT_STDOUT self.speclocal = None self.derived = None self.header = 1 self.instinfo = 1 self.unitinfo = 1 self.globals = 1 self.timestamp = 0 self.samples = None # forever self.interval = pmapi.timeval(1) # 1 sec self.opts.pmSetOptionInterval(str(1)) # 1 sec self.delay = 0 self.type = 0 self.type_prefer = self.type self.ignore_incompat = 0 self.ignore_unknown = 0 self.names_change = 0 # ignore self.instances = [] self.live_filter = 0 self.rank = 0 self.overall_rank = 0 self.overall_rank_alt = 0 self.limit_filter = 0 self.limit_filter_force = 0 self.invert_filter = 0 self.predicate = None self.sort_metric = None self.omit_flat = 0 self.include_labels = 0 self.include_texts = 0 self.colxrow = None self.width = 0 self.width_force = None self.precision = 3 # .3f self.precision_force = None self.delimiter = None self.extcsv = 0 self.extheader = 0 self.fixed_header = 0 self.repeat_header = 0 self.dynamic_header = 0 self.separate_header = 0 self.timefmt = None self.interpol = 1 self.count_scale = None self.count_scale_force = None self.space_scale = None self.space_scale_force = None self.time_scale = None self.time_scale_force = None # Not in pmrep.conf, won't overwrite self.outfile = None # Internal self.format = None # stdout format self.writer = None self.pmi = None self.lines = 0 self.localtz = None self.prev_ts = None self.runtime = -1 self.found_insts = [] self.prev_insts = None self.static_header = 1 self.repeat_header_auto = 0 # Performance metrics store # key - metric name # values - 0:txt label, 1:instance(s), 2:unit/scale, 3:type, # 4:width, 5:pmfg item, 6:precision, 7:limit self.metrics = OrderedDict() self.pmfg = None self.pmfg_ts = None # Read configuration and prepare to connect self.config = self.pmconfig.set_config_path(DEFAULT_CONFIG) self.pmconfig.read_options() self.pmconfig.read_cmd_line() self.pmconfig.prepare_metrics() self.pmconfig.set_signal_handler() def options(self): """ Setup default command line argument option handling """ opts = pmapi.pmOptions() opts.pmSetOptionCallback(self.option) opts.pmSetOverrideCallback(self.option_override) opts.pmSetShortOptions("a:h:LK:c:Co:F:e:D:V?HUGpA:S:T:O:s:t:Z:zdrRIi:jJ:234:578:9:nN:6:vmX:W:w:P:0:l:kxE:1gf:uq:b:y:Q:B:Y:") opts.pmSetShortUsage("[option...] metricspec [...]") opts.pmSetLongOptionHeader("General options") opts.pmSetLongOptionArchive() # -a/--archive opts.pmSetLongOptionArchiveFolio() # --archive-folio opts.pmSetLongOptionContainer() # --container opts.pmSetLongOptionHost() # -h/--host opts.pmSetLongOptionLocalPMDA() # -L/--local-PMDA opts.pmSetLongOptionSpecLocal() # -K/--spec-local opts.pmSetLongOption("config", 1, "c", "FILE", "config file path") opts.pmSetLongOption("check", 0, "C", "", "check config and metrics and exit") opts.pmSetLongOption("output", 1, "o", "OUTPUT", "output target: archive, csv, stdout (default)") opts.pmSetLongOption("output-file", 1, "F", "OUTFILE", "output file") opts.pmSetLongOption("derived", 1, "e", "FILE|DFNT", "derived metrics definitions") opts.pmSetLongOption("daemonize", 0, "", "", "daemonize on startup") opts.pmSetLongOptionDebug() # -D/--debug opts.pmSetLongOptionVersion() # -V/--version opts.pmSetLongOptionHelp() # -?/--help opts.pmSetLongOptionHeader("Reporting options") opts.pmSetLongOption("no-header", 0, "H", "", "omit headers") opts.pmSetLongOption("no-unit-info", 0, "U", "", "omit unit info from headers") opts.pmSetLongOption("no-inst-info", 0, "", "", "omit instance info from headers") opts.pmSetLongOption("no-globals", 0, "G", "", "omit global metrics") opts.pmSetLongOption("timestamps", 0, "p", "", "print timestamps") opts.pmSetLongOptionAlign() # -A/--align opts.pmSetLongOptionStart() # -S/--start opts.pmSetLongOptionFinish() # -T/--finish opts.pmSetLongOptionOrigin() # -O/--origin opts.pmSetLongOptionSamples() # -s/--samples opts.pmSetLongOptionInterval() # -t/--interval opts.pmSetLongOptionTimeZone() # -Z/--timezone opts.pmSetLongOptionHostZone() # -z/--hostzone opts.pmSetLongOption("delay", 0, "d", "", "delay, pause between updates for archive replay") opts.pmSetLongOption("raw", 0, "r", "", "output raw counter values (no rate conversion)") opts.pmSetLongOption("raw-prefer", 0, "R", "", "prefer output raw counter values (no rate conversion)") opts.pmSetLongOption("ignore-incompat", 0, "I", "", "ignore incompatible instances (default: abort)") opts.pmSetLongOption("ignore-unknown", 0, "5", "", "ignore unknown metrics (default: abort)") opts.pmSetLongOption("instances", 1, "i", "STR", "instances to report (default: all current)") opts.pmSetLongOption("live-filter", 0, "j", "", "perform instance live filtering") opts.pmSetLongOption("rank", 1, "J", "COUNT", "limit results to COUNT highest/lowest valued instances") opts.pmSetLongOption("overall-rank", 0, "2", "", "report overall ranking from archive") opts.pmSetLongOption("overall-rank-alt", 0, "3", "", "report overall ranking from archive in pmrep format") opts.pmSetLongOption("names-change", 1, "4", "ACTION", "update/ignore/abort on PMNS changes (default: ignore)") opts.pmSetLongOption("limit-filter", 1, "8", "LIMIT", "default limit for value filtering") opts.pmSetLongOption("limit-filter-force", 1, "9", "LIMIT", "forced limit for value filtering") opts.pmSetLongOption("invert-filter", 0, "n", "", "perform ranking before live filtering") opts.pmSetLongOption("predicate", 1, "N", "METRIC", "set predicate filter reference metric") opts.pmSetLongOption("sort-metric", 1, "6", "METRIC", "set sort reference metric for colxrow output") opts.pmSetLongOption("omit-flat", 0, "v", "", "omit single-valued metrics") opts.pmSetLongOption("include-labels", 0, "m", "", "include metric label info") opts.pmSetLongOption("include-texts", 0, "", "", "include metric help texts in archive output") opts.pmSetLongOption("colxrow", 1, "X", "STR", "swap stdout columns and rows using STR as header label") opts.pmSetLongOption("width", 1, "w", "N", "default column width") opts.pmSetLongOption("width-force", 1, "W", "N", "forced column width") opts.pmSetLongOption("precision", 1, "P", "N", "prefer N digits after decimal separator (default: 3)") opts.pmSetLongOption("precision-force", 1, "0", "N", "force N digits after decimal separator") opts.pmSetLongOption("delimiter", 1, "l", "STR", "delimiter to separate csv/stdout columns") opts.pmSetLongOption("extended-csv", 0, "k", "", "write extended CSV") opts.pmSetLongOption("extended-header", 0, "x", "", "display extended header") opts.pmSetLongOption("fixed-header", 0, "7", "", "fixed header and dynamic instances with colxrow output") opts.pmSetLongOption("repeat-header", 1, "E", "N", "repeat stdout headers every N lines") opts.pmSetLongOption("dynamic-header", 0, "1", "", "update header dynamically on metric/instance changes") opts.pmSetLongOption("separate-header", 0, "g", "", "write separated header before metrics") opts.pmSetLongOption("timestamp-format", 1, "f", "STR", "strftime string for timestamp format") opts.pmSetLongOption("no-interpol", 0, "u", "", "disable interpolation mode with archives") opts.pmSetLongOption("count-scale", 1, "q", "SCALE", "default count unit") opts.pmSetLongOption("count-scale-force", 1, "Q", "SCALE", "forced count unit") opts.pmSetLongOption("space-scale", 1, "b", "SCALE", "default space unit") opts.pmSetLongOption("space-scale-force", 1, "B", "SCALE", "forced space unit") opts.pmSetLongOption("time-scale", 1, "y", "SCALE", "default time unit") opts.pmSetLongOption("time-scale-force", 1, "Y", "SCALE", "forced time unit") return opts def option_override(self, opt): """ Override standard PCP options """ if opt in ('g', 'H', 'K', 'n', 'N', 'p'): return 1 return 0 def option(self, opt, optarg, _index): """ Perform setup for individual command line option """ if opt == 'daemonize': self.daemonize = 1 elif opt == 'include-texts': self.include_texts = 1 elif opt == 'no-inst-info': self.instinfo = 0 elif opt == 'K': if not self.speclocal or not self.speclocal.startswith(";"): self.speclocal = ";" + optarg else: self.speclocal = self.speclocal + ";" + optarg elif opt == 'c': self.config = optarg elif opt == 'C': self.check = 1 elif opt == 'o': self.output = optarg elif opt == 'F': if os.path.exists(optarg + ".index"): sys.stderr.write("Archive %s already exists.\n" % optarg) sys.exit(1) if os.path.exists(optarg): kind = "File" if os.path.isfile(optarg) else "Directory" sys.stderr.write("%s %s already exists.\n" % (kind, optarg)) sys.exit(1) self.outfile = optarg elif opt == 'e': if not self.derived or not self.derived.startswith(";"): self.derived = ";" + optarg else: self.derived = self.derived + ";" + optarg elif opt == 'H': self.header = 0 elif opt == 'U': self.unitinfo = 0 elif opt == 'G': self.globals = 0 elif opt == 'p': self.timestamp = 1 elif opt == 'd': self.delay = 1 elif opt == 'r': self.type = 1 elif opt == 'R': self.type_prefer = 1 elif opt == 'I': self.ignore_incompat = 1 elif opt == '5': self.ignore_unknown = 1 elif opt == 'i': self.instances = self.instances + self.pmconfig.parse_instances(optarg) elif opt == 'j': self.live_filter = 1 elif opt == 'J': self.rank = optarg elif opt == '2': self.overall_rank = 1 elif opt == '3': self.overall_rank_alt = 1 elif opt == '4': if optarg == 'ignore': self.names_change = 0 elif optarg == 'abort': self.names_change = 1 elif optarg == 'update': self.names_change = 2 else: sys.stderr.write("Unknown names-change action '%s' specified.\n" % optarg) sys.exit(1) elif opt == '8': self.limit_filter = optarg elif opt == '9': self.limit_filter_force = optarg elif opt == 'n': self.invert_filter = 1 elif opt == 'N': self.predicate = optarg elif opt == '6': self.sort_metric = optarg elif opt == 'v': self.omit_flat = 1 elif opt == 'm': self.include_labels = 1 elif opt == 'X': self.colxrow = optarg elif opt == 'w': self.width = optarg elif opt == 'W': self.width_force = optarg elif opt == 'P': self.precision = optarg elif opt == '0': self.precision_force = optarg elif opt == 'l': self.delimiter = optarg elif opt == 'k': self.extcsv = 1 elif opt == 'x': self.extheader = 1 elif opt == 'E': self.repeat_header = optarg elif opt == '7': self.fixed_header = 1 elif opt == '1': self.dynamic_header = 1 elif opt == 'g': self.separate_header = 1 elif opt == 'f': self.timefmt = optarg elif opt == 'u': self.interpol = 0 elif opt == 'q': self.count_scale = optarg elif opt == 'Q': self.count_scale_force = optarg elif opt == 'b': self.space_scale = optarg elif opt == 'B': self.space_scale_force = optarg elif opt == 'y': self.time_scale = optarg elif opt == 'Y': self.time_scale_force = optarg else: raise pmapi.pmUsageErr() def connect(self): """ Establish PMAPI context """ context, self.source = pmapi.pmContext.set_connect_options(self.opts, self.source, self.speclocal) self.pmfg = pmapi.fetchgroup(context, self.source) self.pmfg_ts = self.pmfg.extend_timestamp() self.context = self.pmfg.get_context() if pmapi.c_api.pmSetContextOptions(self.context.ctx, self.opts.mode, self.opts.delta): raise pmapi.pmUsageErr() def validate_config(self): """ Validate configuration """ if self.version != CONFVER: sys.stderr.write("Incompatible configuration file version (read v%s, need v%d).\n" % (self.version, CONFVER)) sys.exit(1) self.pmconfig.validate_common_options() if self.output != OUTPUT_ARCHIVE and \ self.output != OUTPUT_CSV and \ self.output != OUTPUT_STDOUT: sys.stderr.write("Error while parsing options: Invalid output target specified.\n") sys.exit(1) # Check how we were invoked and adjust output if sys.argv[0].endswith("pcp2csv"): self.output = OUTPUT_CSV if self.output == OUTPUT_ARCHIVE and not self.outfile: sys.stderr.write("Output archive must be defined with archive output.\n") sys.exit(1) if self.output == OUTPUT_ARCHIVE: outdir = os.path.dirname(self.outfile) if os.path.dirname(self.outfile) else "." if not os.access(outdir, os.W_OK|os.X_OK): sys.stderr.write("Output directory %s not accessible.\n" % outdir) sys.exit(1) # Set default width when needed if self.separate_header and not self.width: self.width = 8 # Adjustments and checks for overall rankings if not self.rank and (self.overall_rank or self.overall_rank_alt): sys.stderr.write("Overall ranking requires ranking enabled.\n") sys.exit(1) if self.overall_rank_alt: self.overall_rank = 1 if self.overall_rank and \ (self.context.type != PM_CONTEXT_ARCHIVE or self.output != OUTPUT_STDOUT): sys.stderr.write("Overall ranking supported only with archive input and stdout output.\n") sys.exit(1) if self.overall_rank: self.header = 0 self.colxrow = None self.predicate = None # Adjust header selection if self.output == OUTPUT_ARCHIVE: self.dynamic_header = 0 self.fixed_header = 0 if self.colxrow is None or self.output != OUTPUT_STDOUT: self.fixed_header = 0 if self.dynamic_header: self.fixed_header = 0 if self.fixed_header: self.dynamic_header = 0 if self.names_change == 2: self.fixed_header = 0 self.dynamic_header = 1 self.static_header = 1 not in (self.fixed_header, self.dynamic_header) self.pmconfig.validate_metrics(curr_insts=not self.live_filter) self.pmconfig.finalize_options() if self.sort_metric: for sort_metric in self.sort_metric.split(","): sort_metric = sort_metric[1:] if sort_metric[:1] == "-" else sort_metric if sort_metric not in self.metrics: sys.stderr.write("Sort reference metric %s not part of metrics.\n" % sort_metric) sys.exit(1) i = list(self.metrics.keys()).index(sort_metric) if self.pmconfig.insts[i][0][0] == PM_IN_NULL: sys.stderr.write("Sort reference metric must have instances.\n") sys.exit(1) def execute(self): """ Fetch and report """ # Debug if self.context.pmDebug(PM_DEBUG_APPL1): sys.stdout.write("Known config file keywords: " + str(self.keys) + "\n") sys.stdout.write("Known metric spec keywords: " + str(self.pmconfig.metricspec) + "\n") # Set delay mode, interpolation if self.context.type != PM_CONTEXT_ARCHIVE: self.delay = 1 self.interpol = 1 # Time self.localtz = self.context.get_current_tz() # Common preparations self.context.prepare_execute(self.opts, self.output == OUTPUT_ARCHIVE, self.interpol, self.interval) # Set output primitives if self.delimiter is None: if self.output == OUTPUT_CSV: self.delimiter = CSVSEP else: self.delimiter = OUTSEP if self.timefmt is None: if self.output == OUTPUT_CSV: self.timefmt = CSVTIME else: self.timefmt = OUTTIME if not self.timefmt: self.timestamp = 0 # Print preparation self.prepare_writer() if self.output == OUTPUT_STDOUT: self.prepare_stdout() # Headers if self.extheader == 1: self.write_extheader() if self.header == 1 and not self.dynamic_header: self.write_header() if self.header == 0: self.repeat_header = 0 if self.repeat_header == "auto" and (self.dynamic_header or self.fixed_header): self.repeat_header = 0 if self.repeat_header == "auto": self.set_auto_repeat_header() if self.repeat_header != "auto": try: signum = getattr(signal, "SIGWINCH") signal.signal(signum, self.set_auto_repeat_header) except Exception: pass else: adjust = 2 if not self.unitinfo else 3 if [x for x in self.pmconfig.descs if x.contents.indom != PM_INDOM_NULL]: adjust += 1 # Best guess with no terminal info available self.repeat_header = 24 - adjust # Just checking if self.check == 1: return # Daemonize when requested if self.daemonize == 1: self.opts.daemonize() # Align poll interval to host clock if self.context.type != PM_CONTEXT_ARCHIVE and self.opts.pmGetOptionAlignment(): align = float(self.opts.pmGetOptionAlignment()) - (time.time() % float(self.opts.pmGetOptionAlignment())) time.sleep(align) # Main loop refresh_metrics = 0 while self.samples != 0: # Refresh metrics as needed if refresh_metrics: refresh_metrics = 0 self.pmconfig.update_metrics(curr_insts=not self.live_filter) # Fetch values refresh_metrics = self.pmconfig.fetch() if refresh_metrics < 0: break # Repeat header if needed if self.output == OUTPUT_STDOUT and not self.dynamic_header: if (self.lines > 0 and self.repeat_header == self.lines) or \ (self.repeat_header_auto and self.lines >= self.repeat_header): self.write_header(True) self.lines = 0 self.lines += 1 # Report and prepare for the next round self.report(self.pmfg_ts()) if self.samples and self.samples > 0: self.samples -= 1 if self.delay and self.interpol and self.samples != 0: self.pmconfig.pause() # Allow to flush buffered values / say goodbye self.report(None) def report(self, tstamp): """ Report metric values """ if tstamp is not None: tstamp = tstamp.strftime(self.timefmt) if self.overall_rank: self.overall_ranking(tstamp) elif self.output == OUTPUT_ARCHIVE: self.write_archive(tstamp) elif self.output == OUTPUT_CSV: self.write_csv(tstamp) elif self.output == OUTPUT_STDOUT: self.write_stdout(tstamp) def prepare_writer(self): """ Prepare generic stdout writer """ if not self.writer: if self.output == OUTPUT_ARCHIVE or self.outfile is None: self.writer = sys.stdout else: self.writer = open(self.outfile, 'wt') def prepare_stdout(self): """ Prepare stdout output format """ if self.colxrow is None: self.prepare_stdout_std() else: self.prepare_stdout_colxrow() def prepare_stdout_std(self, results=()): """ Prepare standard/default stdout output format """ index = 0 if self.timestamp == 0: #self.format = "{:}{}" self.format = "{0:}{1}" index += 2 else: tstamp = datetime.fromtimestamp(time.time()).strftime(self.timefmt) #self.format = "{:<" + str(len(tstamp)) + "}{}" self.format = "{" + str(index) + ":<" + str(len(tstamp)) + "}" index += 1 self.format += "{" + str(index) + "}" index += 1 def prepare_line(index, l): """ Line prepare helper """ #self.format += "{:>" + l + "." + l + "}{}" self.format += "{" + str(index) + ":>" + l + "." + l + "}" index += 1 self.format += "{" + str(index) + "}" index += 1 if results: for i, metric in enumerate(results): for _ in range(len(results[metric])): prepare_line(index, str(self.metrics[metric][4])) index += 2 else: for i, metric in enumerate(self.metrics): for _ in range(len(self.pmconfig.insts[i][0])): prepare_line(index, str(self.metrics[metric][4])) index += 2 #self.format = self.format[:-2] l = len(str(index-1)) + 2 self.format = self.format[:-l] def prepare_stdout_colxrow(self, results=()): """ Prepare columns and rows swapped stdout output """ index = 0 # Timestamp if self.timestamp == 0: self.format = "{0:}{1}" index += 2 else: tstamp = datetime.fromtimestamp(time.time()).strftime(self.timefmt) self.format = "{0:<" + str(len(tstamp)) + "." + str(len(tstamp)) + "}{1}" index += 2 # Instance name if self.colxrow: self.format += "{2:>" + str(len(self.colxrow)) + "." + str(len(self.colxrow)) + "}{3}" else: self.format += "{2:>" + str(8) + "." + str(8) + "}{3}" index += 2 # Metrics / text labels self.labels = OrderedDict() # pylint: disable=attribute-defined-outside-init for i, metric in enumerate(self.metrics): if self.dynamic_header and results and not results[metric]: continue l = str(self.metrics[metric][4]) label = self.metrics[metric][0] if label in self.labels: self.labels[label].append((metric, i)) continue self.labels[label] = [(metric, i)] # Value truncated and aligned self.format += "{" + str(index) + ":>" + l + "." + l + "}" index += 1 # Dummy self.format += "{" + str(index) + "}" index += 1 # Drop the last dummy l = len(str(index-1)) + 2 self.format = self.format[:-l] # Collect the instances in play if self.static_header: for i in range(len(self.metrics)): for instance in self.pmconfig.insts[i][1]: if instance not in self.found_insts: self.found_insts.append(instance) else: seen = set() self.found_insts = [i[1] for metric in results for i in results[metric]] self.found_insts = [i for i in self.found_insts if not (i in seen or seen.add(i))] def set_auto_repeat_header(self, *_args): """ Set auto repeat header """ try: if hasattr(shutil, 'get_terminal_size'): lines = shutil.get_terminal_size().lines else: lines = int(os.popen('stty size', 'r').read().split()[0]) if self.colxrow is None: header = 2 if not self.unitinfo else 3 if [x for x in self.pmconfig.descs if x.contents.indom != PM_INDOM_NULL]: header += 1 self.repeat_header = lines - header else: header = 1 if not self.unitinfo else 2 instances = len(set([j for i in self.pmconfig.insts for j in i[0]])) # pylint: disable=consider-using-set-comprehension self.repeat_header = int(lines / instances) - header self.repeat_header_auto = 1 except Exception: pass def write_extheader(self): """ Write extended header """ if self.context.type == PM_CONTEXT_LOCAL: host = "localhost, using DSO PMDAs" else: host = self.context.pmGetContextHostName() timezone = self.context.posix_tz_to_utc_offset(self.context.get_current_tz(self.opts)) if timezone != self.context.posix_tz_to_utc_offset(self.localtz): timezone += " (reporting, current is " + self.context.posix_tz_to_utc_offset(self.localtz) + ")" origin = float(self.opts.pmGetOptionOrigin()) if self.opts.pmGetOptionOrigin() is not None else 0 if self.runtime != -1: duration = self.runtime samples = self.samples else: if self.samples: duration = (self.samples - 1) * float(self.interval) samples = self.samples else: duration = float(self.opts.pmGetOptionFinish()) - origin samples = int(duration / float(self.interval) + 1) samples = max(0, samples) duration = (samples - 1) * float(self.interval) duration = max(0, duration) endtime = origin + duration instances = sum([len(x[0]) for x in self.pmconfig.insts]) insts_txt = "instances" if instances != 1 else "instance" if not self.static_header: if self.context.type == PM_CONTEXT_ARCHIVE: insts_txt += " present in archive" else: insts_txt += " initially" if self.context.type == PM_CONTEXT_ARCHIVE and not self.interpol: duration = float(self.opts.pmGetOptionFinish()) - origin duration = max(0, duration) def secs_to_readable(seconds): """ Convert seconds to easily readable format """ seconds = float(math.floor((seconds) + math.copysign(0.5, seconds))) parts = str(timedelta(seconds=int(round(seconds)))).split(":") if len(parts[0]) == 1: parts[0] = "0" + parts[0] elif parts[0][-2] == " ": parts[0] = parts[0].rsplit(" ", 1)[0] + " 0" + parts[0].rsplit(" ", 1)[1] return ":".join(parts) if self.context.type == PM_CONTEXT_ARCHIVE: endtime = float(self.context.pmGetArchiveEnd()) if not self.interpol and self.opts.pmGetOptionSamples(): samples = str(samples) + " (requested)" elif not self.interpol: samples = "N/A" comm = "#" if self.output == OUTPUT_CSV else "" self.writer.write(comm + "\n") if self.context.type == PM_CONTEXT_ARCHIVE: self.writer.write(comm + " archive: " + self.source + "\n") self.writer.write(comm + " host: " + host + "\n") self.writer.write(comm + " timezone: " + timezone + "\n") self.writer.write(comm + " start: " + time.asctime(time.localtime(origin)) + "\n") self.writer.write(comm + " end: " + time.asctime(time.localtime(endtime)) + "\n") self.writer.write(comm + " metrics: " + str(len(self.metrics)) + " (" + str(instances) + " " + insts_txt + ")\n") self.writer.write(comm + " samples: " + str(samples) + "\n") if not (self.context.type == PM_CONTEXT_ARCHIVE and not self.interpol): self.writer.write(comm + " interval: " + str(float(self.interval)) + " sec\n") else: self.writer.write(comm + " interval: N/A\n") self.writer.write(comm + " duration: " + secs_to_readable(duration) + "\n") self.writer.write(comm + "\n") def get_results_iter(self, i, metric, results): """ Helper to get results iterators """ l = len(self.pmconfig.insts[i][0]) if not self.dynamic_header else len(results[metric]) r = self.pmconfig.insts[i][0] if not self.dynamic_header else results[metric] return zip(range(l), r) def get_instance_count(self, results): """ Helper to get number of instances of current results """ if self.static_header: if self.colxrow is None: c = len(str(sum([len(i[0]) for i in self.pmconfig.insts]))) else: c = len(str(len(self.metrics))) else: if self.colxrow is None: c = len(str(sum([len(results[i]) for i in results]))) else: c = len(str(len(results))) return c def get_labels_inst(self, i, j, n): """ Helper to get labels instance id reference """ if j is None: return None if self.dynamic_header: return None if n[0] == PM_IN_NULL else j return None if self.pmconfig.insts[i][0][0] == PM_IN_NULL else j def write_separate_header(self, results=()): """ Write separate header """ c = self.get_instance_count(results) + 1 def write_labels(metric, k, i, j, n, metric_only=False): """ Labels writer helper """ if self.include_labels: ins = None if metric_only else self.get_labels_inst(i, j, n) labels = self.pmconfig.get_labels_str(metric, ins, self.dynamic_header, True) write_line(metric, k, labels, True) def write_line(metric, k, name, label=False): """ Line writer helper """ line = "[" + str(k).rjust(c) + "] - " if label: self.writer.write(line + name + "\n") return line += metric if name: line += "[\"" + name + "\"]" if self.unitinfo: if self.metrics[metric][2][0]: line += " - " + self.metrics[metric][2][0] else: line += " - none" line += "\n" self.writer.write(line.format(str(k))) k = 0 if self.colxrow is None: for i, metric in enumerate(self.metrics): for j, n in self.get_results_iter(i, metric, results): k += 1 name = self.pmconfig.insts[i][1][j] if self.static_header else n[1] name = name if name is None else str(name) write_line(metric, k, name) write_labels(metric, k, i, j, n) else: for label in self.labels: k += 1 for metric, i in self.labels[label]: if self.fixed_header: write_line(metric, k, None) write_labels(metric, k, None, None, None, True) else: for j, n in self.get_results_iter(i, metric, results): name = self.pmconfig.insts[i][1][j] if not self.dynamic_header else n[1] name = name if name is None else str(name) write_line(metric, k, name) write_labels(metric, k, i, j, n) self.writer.write("\n") names = ["", self.delimiter] # no timestamp on header line if self.colxrow is not None: names.extend(["", self.delimiter]) # nothing for the instance column k = 0 for i, metric in enumerate(self.metrics): l = len(self.pmconfig.insts[i][0]) if not self.dynamic_header else len(results[metric]) for _ in range(l): k += 1 names.extend([str(k), self.delimiter]) del names[-1] self.writer.write(self.format.format(*names) + "\n") def write_header(self, repeat=False): """ Write info header """ if self.output == OUTPUT_ARCHIVE: self.write_header_archive() if self.output == OUTPUT_CSV: self.write_header_csv() if self.output == OUTPUT_STDOUT: self.write_header_stdout(repeat) def write_header_archive(self): """ Write info header for archive output """ self.writer.write("Recording %d metrics to %s" % (len(self.metrics), self.outfile)) if self.runtime != -1: self.writer.write(":\n%s samples(s) with %.1f sec interval ~ %d sec duration.\n" % (self.samples, float(self.interval), self.runtime)) elif self.samples: duration = (self.samples - 1) * float(self.interval) self.writer.write(":\n%s samples(s) with %.1f sec interval ~ %d sec duration.\n" % (self.samples, float(self.interval), duration)) else: self.writer.write("...") if self.context.type != PM_CONTEXT_ARCHIVE: self.writer.write(" (Ctrl-C to stop)") self.writer.write("\n") def write_header_csv(self, results=()): """ Write info header for CSV output """ if not self.header: return if self.extcsv: self.writer.write("Host,Interval,") self.writer.write("Time") for i, metric in enumerate(self.metrics): for j, n in self.get_results_iter(i, metric, results): name = metric if not self.dynamic_header: if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL: # Always mark metrics with instance domain name += "-" if self.pmconfig.insts[i][1][j]: # Append instance name when present name += self.pmconfig.insts[i][1][j] else: if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL: name += "-" + n[1] if self.delimiter: name = name.replace(self.delimiter, " ") name = name.replace("\n", " ").replace("\"", " ") self.writer.write(self.delimiter + "\"" + name + "\"") if self.include_labels: ins = j if not self.dynamic_header else n[0] labels = self.pmconfig.get_labels_str(metric, ins, self.dynamic_header, True) if self.delimiter: repl = ";" if self.delimiter == "," else "," labels = labels.replace(self.delimiter, repl) labels = labels.replace("\n", " ").replace("\"", " ") self.writer.write(self.delimiter + "\"" + labels + "\"") self.writer.write("\n") def write_header_stdout(self, repeat=False, results=()): """ Write info header for stdout output """ if not self.header: return if repeat: self.writer.write("\n") if self.separate_header: self.write_separate_header(results) return names = ["", self.delimiter] # no timestamp on header line insts = ["", self.delimiter] # no timestamp on instances line units = ["", self.delimiter] # no timestamp on units line mlabels = ["", self.delimiter] # no timestamp on metric labels line if self.colxrow is not None: names += [self.colxrow, self.delimiter] units += ["", self.delimiter] mlabels += ["", self.delimiter] prnti = 0 hlabels = [] # header labels def add_header_items(metric, name, i, j, n=[PM_IN_NULL]): # pylint: disable=dangerous-default-value """ Helper to add items to header """ names.extend([self.metrics[metric][0], self.delimiter]) insts.extend([name, self.delimiter]) units.extend([self.metrics[metric][2][0], self.delimiter]) if self.include_labels: ins = self.get_labels_inst(i, j, n) mlabels.append(self.pmconfig.get_labels_str(metric, ins, self.dynamic_header, True)) mlabels.append(self.delimiter) hlabels.append(self.metrics[metric][0]) for i, metric in enumerate(self.metrics): if self.colxrow is not None: if self.metrics[metric][0] in hlabels or \ (self.dynamic_header and results and not results[metric]): continue add_header_items(metric, None, i, None) continue prnti = 1 if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL else prnti if results: for inst, name, _ in results[metric]: name = name if prnti and name else self.delimiter j = None if not self.include_labels else list(self.metrics.keys()).index(metric) n = None if not self.include_labels else [x for x in results[metric] if x[0] == inst] add_header_items(metric, name, i, j, n[0] if n else None) # pylint: disable=unsubscriptable-object else: for j, n in self.get_results_iter(i, metric, results): name = self.pmconfig.insts[i][1][j] if prnti and self.pmconfig.insts[i][1][j] else self.delimiter add_header_items(metric, name, i, j, n) del names[-1] del units[-1] del insts[-1] del mlabels[-1] self.writer.write(self.format.format(*names) + "\n") if self.instinfo and prnti: self.writer.write(self.format.format(*insts) + "\n") if self.include_labels: self.writer.write(self.format.format(*mlabels) + "\n") if self.unitinfo: self.writer.write(self.format.format(*units) + "\n") def write_archive(self, timestamp): """ Write archive record """ if timestamp is None: # Complete and close self.pmi.pmiEnd() self.pmi = None return def record_metric_info(metric, i, inst=None): """ Helper to record metric info """ def record_labels(lid, i, inst, name, value): """ Helper to record labels """ try: pmid = self.pmconfig.pmids[i] if lid is PM_LABEL_DOMAIN: ident = pmapi.pmContext.pmID_domain(pmid) elif lid is PM_LABEL_INDOM: ident = self.pmconfig.descs[i].contents.indom elif lid is PM_LABEL_CLUSTER: ident = pmapi.pmContext.pmID_cluster(pmid) elif lid is PM_LABEL_ITEM: ident = pmapi.pmContext.pmID_item(pmid) else: ident = 0 self.pmi.pmiPutLabel(lid, ident, inst, name, str(value)) except Exception as pmierror: sys.stderr.write("pmiPutLabel failed: %s\n" % str(pmierror)) sys.exit(1) if inst in (None, PM_IN_NULL): self.pmi.pmiAddMetric(metric, self.pmconfig.pmids[i], self.pmconfig.descs[i].contents.type, self.pmconfig.descs[i].contents.indom, self.pmconfig.descs[i].contents.sem, self.pmconfig.descs[i].contents.units) if self.include_labels: for lid in self.pmconfig.labels[i][0]: for name, value in self.pmconfig.labels[i][0][lid].items(): record_labels(lid, i, PM_IN_NULL, name, value) if self.include_labels and inst not in (None, PM_IN_NULL): if inst in self.pmconfig.res_labels[metric][1]: for name, value in self.pmconfig.res_labels[metric][1][inst].items(): record_labels(PM_LABEL_INSTANCES, i, inst, name, value) if self.include_texts: try: if self.pmconfig.texts[i][0]: self.pmi.pmiPutText(PM_TEXT_PMID, PM_TEXT_ONELINE, self.pmconfig.pmids[i], self.pmconfig.texts[i][0]) if self.pmconfig.texts[i][1]: self.pmi.pmiPutText(PM_TEXT_PMID, PM_TEXT_HELP, self.pmconfig.pmids[i], self.pmconfig.texts[i][1]) if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL: if self.pmconfig.texts[i][2]: self.pmi.pmiPutText(PM_TEXT_INDOM, PM_TEXT_ONELINE, self.pmconfig.descs[i].contents.indom, self.pmconfig.texts[i][2]) if self.pmconfig.texts[i][3]: self.pmi.pmiPutText(PM_TEXT_INDOM, PM_TEXT_HELP, self.pmconfig.descs[i].contents.indom, self.pmconfig.texts[i][3]) except pmi.pmiErr as pmierror: if pmierror.errno() == PMI_ERR_DUPTEXT: # Ignore duplicate help texts pass if self.pmi is None: # Create a new archive self.pmi = pmi.pmiLogImport(self.outfile) self.prev_res = OrderedDict() # pylint: disable=attribute-defined-outside-init self.recorded = {} # pylint: disable=attribute-defined-outside-init if self.context.type == PM_CONTEXT_ARCHIVE: self.pmi.pmiSetHostname(self.context.pmGetArchiveLabel().hostname) self.pmi.pmiSetTimezone(self.context.get_current_tz(self.opts)) for i, metric in enumerate(self.metrics): self.recorded[metric] = [] record_metric_info(metric, i) # Add current values data = 0 # NB. We use valid_only=False to make sure that for every metric # requested their metadata will be recorded in the archive even # if their values are not available for whatever reason. results = self.pmconfig.get_ranked_results(valid_only=False) for i, metric in enumerate(results): if metric not in self.recorded: self.recorded[metric] = [] record_metric_info(metric, i) for inst, name, value in results[metric]: if inst != PM_IN_NULL and inst not in self.recorded[metric]: self.recorded[metric].append(inst) record_metric_info(metric, i, inst) try: self.pmi.pmiAddInstance(self.pmconfig.descs[i].contents.indom, name, inst) except pmi.pmiErr as pmierror: if pmierror.errno() == PMI_ERR_DUPINSTNAME: # Already added pass if self.pmconfig.descs[i].contents.sem == PM_SEM_DISCRETE and metric in self.prev_res: index = [idx for idx, (x, _, _) in enumerate(self.prev_res[metric]) if x == inst] if index and value == self.prev_res[metric][index[0]][2]: continue try: self.pmi.pmiPutValue(metric, name, str(value)) except pmi.pmiErr as pmierror: pass data = 1 self.prev_res = results # pylint: disable=attribute-defined-outside-init # Flush if data: self.pmi.pmiWrite(int(self.pmfg_ts().strftime('%s')), self.pmfg_ts().microsecond) def dynamic_header_update(self, results, line=None): """ Update dynamic header as needed """ if self.rank: for metric in results: results[metric] = sorted(results[metric], key=lambda x: x[0]) insts = [(metric, list(zip(*results[metric]))[0]) for metric in results if results[metric]] if self.fixed_header: self.prepare_stdout_colxrow(results) elif (insts and (self.repeat_header == self.lines or insts != self.prev_insts)) or \ (self.repeat_header_auto and self.lines >= self.repeat_header): if self.output == OUTPUT_CSV: self.write_header_csv(results) if self.output == OUTPUT_STDOUT: if self.colxrow is None: self.prepare_stdout_std(results) else: self.prepare_stdout_colxrow(results) self.write_header_stdout(self.prev_insts is not None, results) self.lines = 0 if not self.fixed_header: self.lines += 1 if not insts and line: self.format = "{0:}{1}{2:>" + str(self.width) + "}" line.extend([NO_VAL, self.delimiter]) self.prev_insts = insts def parse_non_number(self, value, width=8): """ Check and handle float inf, -inf, and NaN """ if math.isinf(value): if value > 0: value = "inf" if width >= 3 else pmconfig.TRUNC else: value = "-inf" if width >= 4 else pmconfig.TRUNC elif math.isnan(value): value = "NaN" if width >= 3 else pmconfig.TRUNC return value def remove_delimiter(self, value): """ Remove delimiter if needed in string values """ if isinstance(value, str) and self.delimiter and not self.delimiter.isspace(): if self.delimiter != "_": value = value.replace(self.delimiter, "_") else: value = value.replace(self.delimiter, " ") return value def write_csv(self, timestamp): """ Write results in CSV format """ if timestamp is None: # Silent goodbye return ts = self.context.datetime_to_secs(self.pmfg_ts(), PM_TIME_SEC) if self.prev_ts is None: self.prev_ts = ts if self.context.type == PM_CONTEXT_LOCAL: host = "localhost" else: host = self.context.pmGetContextHostName() self.csv_host = host + self.delimiter # pylint: disable=attribute-defined-outside-init self.csv_tz = " " + self.context.posix_tz_to_utc_offset(self.context.get_current_tz(self.opts)) # pylint: disable=attribute-defined-outside-init # Construct the results line = "" if self.extcsv: line += self.csv_host line += str(int(ts - self.prev_ts + 0.5)) + self.delimiter self.prev_ts = ts line += timestamp if self.extcsv: line += self.csv_tz results = self.pmconfig.get_ranked_results() if self.dynamic_header: self.dynamic_header_update(results) res = {} for i, metric in enumerate(results): for inst, _, value in results[metric]: res[metric + "+" + str(inst)] = value # Add corresponding values for each header column for i, metric in enumerate(self.metrics): fmt = "." + str(self.metrics[metric][6]) + "f" for j, n in self.get_results_iter(i, metric, results): line += self.delimiter try: ref = str(self.pmconfig.insts[i][0][j]) if not self.dynamic_header else str(n[0]) value = res[metric + "+" + ref] except Exception: continue if isinstance(value, str): value = self.remove_delimiter(value) value = value.replace("\n", " ").replace('"', " ") line += '"' + value + '"' if self.include_labels: line += self.delimiter line += '"' + value + '"' else: if isinstance(value, float): value = self.parse_non_number(value) if isinstance(value, float): value = format(value, fmt) line += str(value) if self.include_labels: line += self.delimiter line += str(value) self.writer.write(line + "\n") def format_stdout_value(self, value, width, precision, fmt, k): """ Format value for stdout output """ if isinstance(value, (int, long)): if len(str(value)) > width: value = pmconfig.TRUNC else: #fmt[k] = "{:" + str(width) + "d}" fmt[k] = "{X:" + str(width) + "d}" elif isinstance(value, float) and \ not math.isinf(value) and \ not math.isnan(value): s = len(str(int(value))) if s > width: value = pmconfig.TRUNC elif s + 2 > width: fmt[k] = "{X:" + str(width) + "d}" value = int(value) else: c = precision for _ in reversed(range(c+1)): t = "{0:" + str(width) + "." + str(c) + "f}" if len(t.format(value)) > width: c -= 1 else: #fmt[k] = t.replace("0:", ":") fmt[k] = t.replace("0:", "X:") break elif isinstance(value, str): value = self.remove_delimiter(value) value = value.replace("\n", "\\n") else: value = self.parse_non_number(value, width) return value def write_stdout(self, timestamp): """ Write line to stdout """ if self.colxrow is None: self.write_stdout_std(timestamp) else: self.write_stdout_colxrow(timestamp) def write_stdout_std(self, timestamp): """ Write line to standard formatted stdout """ if timestamp is None: # Silent goodbye return line = [] if self.timestamp == 0: line.append("") else: line.append(timestamp) line.append(self.delimiter) results = self.pmconfig.get_ranked_results() if self.dynamic_header: self.dynamic_header_update(results, line) #fmt = self.format.split("{}") fmt = re.split("{\\d+}", self.format) res = {} for i, metric in enumerate(results): for inst, _, value in results[metric]: res[metric + "+" + str(inst)] = value # Add corresponding values for each header column k = 0 for i, metric in enumerate(self.metrics): for j, n in self.get_results_iter(i, metric, results): k += 1 try: ref = str(self.pmconfig.insts[i][0][j]) if not self.dynamic_header else str(n[0]) value = res[metric + "+" + ref] value = self.format_stdout_value(value, self.metrics[metric][4], self.metrics[metric][6], fmt, k) except Exception: value = NO_VAL line.extend([value, self.delimiter]) del line[-1] #self.writer.write('{}'.join(fmt).format(*line) + "\n") index = 0 nfmt = "" for f in fmt: nfmt += f.replace("{X:", "{" + str(index) + ":") index += 1 nfmt += "{" + str(index) + "}" index += 1 l = len(str(index-1)) + 2 nfmt = nfmt[:-l] self.writer.write(nfmt.format(*line) + "\n") def write_stdout_colxrow(self, timestamp): """ Write line to columns and rows swapped stdout """ if timestamp is None: # Silent goodbye return # Avoid per-line I/O output = "" results = self.pmconfig.get_ranked_results() res = {} for i, metric in enumerate(results): for inst, name, value in results[metric]: if self.static_header: res[metric + "+" + str(inst)] = value else: res[metric + "+" + str(name)] = value if not self.static_header: self.dynamic_header_update(results) if self.sort_metric: found_insts = self.found_insts self.found_insts = [] for sort_metric in self.sort_metric.split(","): revs = sort_metric[:1] != "-" sort_metric = sort_metric if revs else sort_metric[1:] for r in sorted(results[sort_metric], key=lambda x: x[2], reverse=revs): if r[1] not in self.found_insts: self.found_insts.append(r[1]) self.found_insts.extend([i for i in found_insts if i not in self.found_insts]) # We need to construct each line independently for instance in self.found_insts: # Split on dummies fmt = re.split("{\\d+}", self.format) # Start a new line line = [] k = 0 # Add timestamp if wanted if self.timestamp == 0: line.append("") else: line.append(timestamp) line.append(self.delimiter) k += 1 # Add instance if instance: line.append(instance) else: line.append(SINGULR) line.append(self.delimiter) k += 1 for label in self.labels: found = 0 for metric, i in self.labels[label]: if found: break insts = self.pmconfig.insts[i][1] if self.static_header else self.found_insts if label == self.metrics[metric][0] and instance in insts: found = 1 try: if self.static_header: ref = self.pmconfig.insts[i][0][self.pmconfig.insts[i][1].index(instance)] else: ref = instance value = res[metric + "+" + str(ref)] value = self.format_stdout_value(value, self.metrics[metric][4], self.metrics[metric][6], fmt, k) except Exception: value = NO_VAL if self.static_header else NO_INST line.extend([value, self.delimiter]) k += 1 if not found: # Not an instance for this label, # add a placeholder and move on line.extend([NO_INST, self.delimiter]) k += 1 continue # Skip metric output when only unavailable instances if self.dynamic_header: values = set(line[4::2]) if len(values) == 1 and NO_INST in values: continue # Print the line in a Python 2.6 compatible manner del line[-1] index = 0 nfmt = "" for f in fmt: nfmt += f.replace("{X:", "{" + str(index) + ":") index += 1 nfmt += "{" + str(index) + "}" index += 1 l = len(str(index-1)) + 2 nfmt = nfmt[:-l] output += nfmt.format(*line) + "\n" if not output: line = [""] if self.timestamp == 0 else [timestamp] if self.dynamic_header: self.format = "{0:}{1}{2:>" + str(len(self.colxrow)) + "}" line.extend([self.delimiter, NO_VAL, self.delimiter]) else: line.extend([self.delimiter, NO_VAL]) for _ in range(len(self.metrics)): line.extend([self.delimiter, NO_INST]) output = self.format.format(*line) + "\n" self.writer.write(output) def overall_ranking(self, timestamp): """ Perform overall ranking """ if not hasattr(self, 'all_ranked'): self.all_ranked = OrderedDict() # pylint: disable=attribute-defined-outside-init if timestamp is None: # All results available, pretty print results in requested format m_len = i_len = u_len = v_len = 3 for metric in self.all_ranked: values = False for _, name, value in self.all_ranked[metric]: values = True name = name.replace("\n", " ") if name else name if name: i_len = i_len if len(name) < i_len else len(name) p = self.metrics[metric][6] if self.metrics[metric][4] > self.metrics[metric][6] else self.metrics[metric][4] numfmt = "." + str(p) + "f" value = format(value, numfmt) if isinstance(value, float) else str(value) v_len = v_len if len(value) < v_len else len(value) if values: m_len = m_len if len(metric) < m_len else len(metric) u_len = u_len if len(self.metrics[metric][2][0]) < u_len else len(self.metrics[metric][2][0]) d = self.delimiter for metric in self.all_ranked: alt_line = [] for _, name, value in self.all_ranked[metric]: name = name.replace("\n", " ") if name else name if not self.overall_rank_alt: line = [metric, d, "", d] if not name else [metric, d, name, d] line.append(self.metrics[metric][2][0]) p = self.metrics[metric][6] if self.metrics[metric][4] > self.metrics[metric][6] else self.metrics[metric][4] numfmt = "." + str(p) + "f" value = format(value, numfmt) if isinstance(value, float) else str(value) line.append(value) output = "{0:<" + str(m_len+1) + "}{1:<2}{2:<" + str(i_len+1) + "}" output += "{3:<2}{4:>" + str(u_len) + "} " + d + "{5:>" + str(v_len+1) + "}" else: if not alt_line: alt_line = [metric, ",,", ""] if not name else [metric, ",,\"'", name + "'\""] output = "{0}{1}{2}" else: alt_line[2] = alt_line[2][:-1] + ",'" + name + "'\"" if not self.overall_rank_alt: self.writer.write(output.format(*line) + "\n") if self.overall_rank_alt and alt_line: self.writer.write(output.format(*alt_line) + "\n") return results = self.pmconfig.get_ranked_results() if self.prev_insts is None: for i, metric in enumerate(results): if self.pmconfig.descs[i].contents.type != PM_TYPE_STRING: self.all_ranked[metric] = results[metric] self.prev_insts = [] revs = self.rank > 0 for i, metric in enumerate(results): if self.pmconfig.descs[i].contents.type == PM_TYPE_STRING: continue rank = abs(self.rank) if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL else 1 c, r, t = (0, [], []) for j in sorted(results[metric] + self.all_ranked[metric], key=lambda x: x[2], reverse=revs): if j[0] not in t and c < rank: c += 1 r.append(j) t.append(j[0]) self.all_ranked[metric] = r def finalize(self): """ Finalize and clean up """ if self.writer: try: self.writer.flush() except IOError as ioerror: if ioerror.errno != errno.EPIPE: raise error try: self.writer.close() except Exception: pass self.writer = None if self.pmi: self.pmi.pmiEnd() self.pmi = None if __name__ == '__main__': try: P = PMReporter() P.connect() P.validate_config() P.execute() P.finalize() except pmapi.pmErr as error: sys.stderr.write("%s: %s" % (error.progname(), error.message())) if error.message() == "Connection refused": sys.stderr.write("; is pmcd running?") sys.stderr.write("\n") sys.exit(1) except pmapi.pmUsageErr as usage: usage.message() sys.exit(1) except IOError as error: if error.errno != errno.EPIPE: sys.stderr.write("%s\n" % str(error)) sys.exit(1) except KeyboardInterrupt: sys.stdout.write("\n") P.finalize()