PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB`Mini Shell

HOME


Mini Shell 1.0
DIR:/proc/self/root/lib/Acronis/PyShell/site-tools/
Upload File :
Current File : //proc/self/root/lib/Acronis/PyShell/site-tools/cep.py
from datetime import datetime, timedelta
from urllib import parse
import acrort
import argparse
import itertools
import json
import pprint
import prettytable
import requests


def fmt_sizeof(num, suffix='B'):
    for unit in ['','K','M','G','T','P','E','Z']:
        if abs(num) < 1024.0:
            return "%3.1f%s%s" % (num, unit, suffix)
        num /= 1024.0
    return "%.1f%s%s" % (num, 'Y', suffix)


class PrettyTable(prettytable.PrettyTable):
    PrettyFormat = 'default'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        if self.PrettyFormat == 'json':
            return self.get_json_string()
        else:
            return self.get_string()

    def get_json_string(self, **kwargs):
        options = self._get_options(kwargs)
        result = [self._field_names]
        for row in self._get_rows(options):
            result.append(row)

        return json.dumps(result)


class DistributionTable:
    def build(self, map, name):
        t = PrettyTable([name]+["VALUE", '%%'])
        total = sum([v for _, v in map.items()])
        for k, v in map.items():
            t.add_row([k, v, '{:1.1f}'.format(v / total * 100)])
        t.align[name] = 'l'
        t.align["VALUE"] = 'r'
        t.align['%%'] = 'r'
        return t

class MachineReport:
    def build_ams_pretty(self, cn):
        rep = self.build_ams_report(cn)
        t = PrettyTable(["ACRONIS MANAGEMENT SERVER", "VALUE"])
        t.add_row(["Report date", str(datetime.now().date())])
        t.add_row(["Version", rep['version']])
        t.add_row(["CPU model", rep['cpu']])
        t.add_row(["RAM", rep['memory']])
        t.add_row(["OS", rep['os']])
        t.align["ACRONIS MANAGEMENT SERVER"] = 'l'
        t.align["VALUE"] = 'r'
        return t

    def build_ams_report(self, cn):
        pt = acrort.plain.Unit(flat=[
            ('^Is', 'string', 'MachineManagement::Machine'),
            ('.Info.Role', 'dword', 1)
        ])
        resp = cn.dml.select(acrort.dml.ViewSpec(pt))
        assert len(resp) == 1, "MachineManagement::Machine role=1 exists"
        return self.parse_machine(resp[0])

    def build_agent_pretty(self, cn):
        rep = self.build_agent_report(cn)
        tt = []
        tt.append(DistributionTable().build(rep['agent_version'], "AGENT VERSIONS"))
        tt.append(DistributionTable().build(rep['cpu'], "AGENT CPUs"))
        tt.append(DistributionTable().build(rep['os'], "AGENT OS"))
        tt.append(DistributionTable().build(rep['memory'], "AGENT RAM"))
        return tt

    def build_agent_report(self, cn):
        reader = DmlReader(cn)
        pt = acrort.plain.Unit(flat=[
            ('^Is', 'string', 'MachineManagement::Machine'),
            ('.Info.Role', 'dword', 0)
        ])
        opts = acrort.plain.Unit(flat=[
            ('.Mask.Agents', 'nil', ''),
            ('.Mask.Info', 'nil', '')
        ])
        stat = [self.parse_machine(m) for m in reader.read(pt, opts)]
        return {
            'os' : self.count_values([m['os'] for m in stat]),
            'cpu' : self.count_values([m['cpu'] for m in stat]),
            'memory' : self.count_values([m['memory'] for m in stat]),
            'agent_version' : self.count_values([m['version'] for m in stat]),
        }

    def count_values(self, values):
        stat = {}
        for v in values:
            stat[v] = stat.get(v, 0) + 1
        return stat

    def parse_machine(self, m):
        return {
            'version' : [v.Version.ref for _, v in m.Info.Agents if v.Id.ref == ''][0],
            'cpu_freq' :  m.Info.Hardware.ProcessorFrequency.ref,
            'cpu' :  m.Info.Hardware.ProcessorName.ref,
            'memory' : fmt_sizeof(m.Info.Hardware.MemorySize.ref),
            'os' : m.Info.OS.Name.ref,
        }


class ApiGwReader:
    def __init__(self, url, user, pswd):
        self.url = url
        self.user = user
        self.pswd = pswd
        self.token = self.get_token(url, user, pswd)

    def get_token(self, url, user, pswd):
        resp = requests.post(
            url=url+"/idp/token",
            data={
                'grant_type' : 'password',
                'username' : user,
                'password' : pswd,
            },
        )
        assert resp.status_code == 200, "Get token success"
        return resp.json()['access_token']


class ActivityByDateReport:
    def build_pretty(self, token, url, days_limit):
        report = self.build(token, url, days_limit)
        t = PrettyTable(["ACTIVITY BY DATE", "TOTAL", "SUCCESS", "FAILED"])
        fmt = lambda x : '-' if x == 0 else x
        for r in report:
            t.add_row([str(r[0]), fmt(r[1]+r[2]), fmt(r[1]), fmt(r[2])])
        return t

    def build(self, token, url, days_limit):
        now = datetime.now()
        round4 = lambda x : x.replace(hour=x.hour // 4 * 4, minute=0, second=0, microsecond=0)
        top = round4(now)
        bottom = top - timedelta(days=days_limit)
        buckets = {}
        for a in self.activity_reader(url, token=token):
            time, status = self.strptime(a['finishTime']), a['status']
            if time < bottom:
                break
            b = buckets.get(round4(time), [0, 0, []])
            b[2].append(time)
            if status in ['ok', 'warning']:
                b[0] += 1
            else:
                b[1] += 1
            buckets[round4(time)] = b
        res = []
        while top >= bottom:
            b = buckets.get(top, [0, 0, []])
            res.append((top, b[0], b[1], b[2]))
            top -= timedelta(hours=4)
        return sorted(res, key=lambda x: -x[0].timestamp())

    def activity_reader(self, tm, token):
        usn = None
        limit = 100
        while True:
            url = tm+"/api/task_manager/x/activities?state=completed&order=usn.desc&limit={}".format(limit)
            if usn:
                url = url + "&usn_ls={}".format(usn)
            resp = requests.get(
                url=url,
                headers={
                    'Authorization': 'Bearer ' + token,
                }
            )
            assert resp.status_code == 200, "Get task successful"
            activities = resp.json()
            for a in activities:
                if usn:
                    assert a['usn'] < usn, "usn is decreasing"
                usn = a['usn']
                yield a
            if len(activities) < limit:
                break


    def strptime(self, t):
        return datetime.strptime(t, '%Y-%m-%dT%H:%M:%SZ')


class DmlReader:
    def __init__(self, cn):
        self.cn = cn

    def read(self, pt, opts):
        limit = 100
        if not opts:
            opts = acrort.plain.Unit(flat=[('.LimitOptions', 'dword', limit)])
        opts = opts.consolidate(acrort.plain.Unit(flat=[('.LimitOptions', 'dword', limit)]))
        if opts.get_branch('.Mask', None):
            opts = opts.consolidate(acrort.plain.Unit(flat=[('.Mask.DmlTimeStamp', 'nil', '')]))
        stamp = 0
        while True:
            spec = acrort.dml.ViewSpec(pt.consolidate(self.stamp_greater(stamp)), opts.consolidate(self.sort_stamp_asc()))
            stampmax = stamp
            for obj in self.cn.dml.select(spec):
                yield obj
                stampmax = obj.DmlTimeStamp.ref
            if stampmax - stamp < limit:
                break
            stamp = stampmax

    def sort_stamp_asc(self):
        return acrort.plain.Unit(flat=[
            ('.SortingOptions.DmlTimeStamp', 'sqword', 0)])

    def stamp_greater(self, ts):
        return acrort.plain.Unit(flat=[('.DmlTimeStamp', 'sqword', 0), ('.DmlTimeStamp^Greater', 'sqword', ts)])


class BackupPlanScheduleReport:
    def __init__(self):
        pass

    def build_pretty(self, cn):
        rep = self.build(cn)
        tt = []
        tt.append(DistributionTable().build(rep['schemes'], "BACKUP PLAN SCHEMES"))
        dow = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
        str_hour = lambda x : '{:02}-{:02}'.format(x, x+2)
        dow_stat = { dow[k] : v for k, v in rep['days'].items() }
        tt.append(DistributionTable().build(dow_stat, "DAY OF WEEK"))
        dow_time_stat = PrettyTable(["DOW AND TIME", "COUNT"])
        for i in [1, 2, 3, 4, 5, 6, 0]:
            for j in range(12):
                v = rep['schedules'].get((i, j * 2), 0)
                dow_time_stat.add_row(['{}, {}'.format(dow[i], str_hour(j * 2)), v])
        dow_time_stat.align["DOW AND TIME"] = 'l'
        dow_time_stat.align["COUNT"] = 'r'
        tt.append(dow_time_stat)
        return tt

    def build(self, cn):
        schedules = {}
        schemes = {}
        names = {}
        days_stat = {}
        for name, scheme, days, start in self.parse_schedules(cn):
            #print(name, scheme, days, start)
            if not scheme:
                schemes['others'] = schemes.get('others', 0) + 1
                continue
            schemes[scheme] = schemes.get(scheme, 0) + 1
            if not days:
                continue
            for d in days:
                buck_id = d, start[0] // 2 * 2
                schedules[buck_id] = schedules.get(buck_id, 0) + 1
                names[buck_id] = names.get(buck_id, []) + [name]
                days_stat[d] = days_stat.get(d, 0) + 1
        return {
            'schemes' : schemes,
            'schedules' : schedules,
            'days' : days_stat,
        }

    def parse_schedules(self, cn):
        reader = DmlReader(cn)
        pt = acrort.plain.Unit(flat=[
            ('^Is', 'string', 'Gtob::Dto::ProtectionPlan'),
        ])
        opts = acrort.plain.Unit(flat=[
            ('.Mask.Scheme', 'nil', ''),
            ('.Mask.Name', 'nil', ''),
            ('.Mask.Enabled', 'nil', ''),
        ])
        days_of_week = [0] * 7
        for obj in reader.read(pt, opts):
            if obj.Scheme.Type.ref == 5:
                for _, item in obj.Scheme.Parameters.Items:
                    for days, startAt in self.process_schedule(item.Schedule):
                        yield obj.Name.ref, 'custom', days, startAt
                continue
            if obj.Scheme.Type.ref == 9:
                yield obj.Name.ref, 'replication_once', None, None
                continue
            if obj.Scheme.Type.ref in [10, 20, 21, 22, 23]:
                names = {
                    10 : 'replication_simple',
                    20 : 'always_full',
                    21 : 'always_incr',
                    22 : 'full_daily_incr',
                    23 : 'mwd',
                }
                for days, startAt in self.process_schedule(obj.Scheme.Parameters.BackupSchedule.Schedule):
                    yield obj.Name.ref, names[obj.Scheme.Type.ref], days, startAt
                continue
            yield obj.Name.ref, None, None, None

    def from_array(self, ar):
        return [x.ref for _, x in ar]

    def from_mask_days(self, m):
        forb = [i for i, ch in enumerate(bin(m)[2:][::-1]) if ch == '1']
        return [i for i in range(7) if i not in forb]

    def process_schedule(self, sch):
        assert 'ScheduleManagement::Schedule' in [v for n, v in sch.traits if n == 'Is']
        alarms = [al for _, al in sch.Alarms if al.Alarm.polyType.ref == 1]
        if not alarms:
            return
        for al in alarms:
            impl = al.Alarm.impl
            if impl.Calendar.Calendar.polyType.ref != 2:
                continue
            days = self.from_mask_days(impl.Calendar.Calendar.impl.Days.ref)
            startAt = impl.StartTime.Hour.ref, impl.StartTime.Minute.ref
            if impl.RepeatAtDay.TimeInterval.ref > 0:
                step = impl.RepeatAtDay.TimeInterval.ref // 60
                endAt = (impl.RepeatAtDay.EndTime.Hour.ref, impl.RepeatAtDay.EndTime.Minute.ref)
                while startAt <= endAt:
                    yield days, startAt
                    min = startAt[1] + step
                    startAt = (startAt[0] + min // 60, min % 60)
                continue
            yield days, startAt


class AgentOnlineReport:
    def build_pretty(self, cn):
        rep = self.build(cn)
        return DistributionTable().build(rep, "AGENT AVAILABILITY")

    def build(self, cn):
        reader = DmlReader(cn)
        pt = acrort.plain.Unit(flat=[
            ('^Is', 'string', 'MachineManagement::Machine'),
            ('.Info.Role', 'dword', 0),
        ])
        opts = acrort.plain.Unit(flat=[
            ('.Mask.Status', 'nil', ''),
        ])
        status = {}
        for m in reader.read(pt, opts):
            st = m.Status.ref
            status[st] = status.get(st, 0) + 1
        return { { 1 : 'offline', 0 : 'online' }[k] : v for k, v in status.items() }


class BackupPlanDataTypeReport:
    def build_pretty(self, cn):
        rep = self.build(cn)
        return DistributionTable().build(rep, "BACKUP PLAN DATA TYPES")

    def build(self, cn):
        mm = {
            'ams::instances::physical_instance' : 'Machines/Disks/Volumes',
            'ams::instances::virtual_instance' : 'VMs',
            'ams::resources::group' : 'Groups',
            'arx::ams::gct::mailbox' : 'MS Exchange mailboxes',
            'mms::disk::disk' : 'Machines/Disks/Volumes',
            'mms::disk::volume' : 'Machines/Disks/Volumes',
            'mms::file::dir' : 'Files/Folders',
            'mms::file::file' : 'Files/Folders',
            'mms::smb::dir' : 'Files/Folders',
        }
        stat = {}
        for t, host, resource in self.parse_inclusions(cn):
            key = mm[t]
            counter = stat.get(key, 0)
            counter += 1
            stat[key] = counter
        return stat

    def parse_inclusions(self, cn):
        reader = DmlReader(cn)
        pt = acrort.plain.Unit(flat=[
            ('^Is', 'string', 'Gtob::Dto::ProtectionPlan'),
        ])
        opts = acrort.plain.Unit(flat=[
            ('.Mask.Target', 'nil', ''),
            ('.Mask.Name', 'nil', ''),
        ])
        for p in reader.read(pt, opts):
            for _, item in p.Target.Inclusions:
                it = item.Key.ItemType.ref
                id = item.Key.LocalID.ref
                if it in ['ams::instances::virtual_instance', 'ams::instances::physical_instance']:
                    id = id.split('@')
                    yield it, id[1], id[0]
                    continue
                if it == 'ams::resources::group':
                    yield it, None, id
                    continue
                if it == 'arx::ams::gct::mailbox':
                    url = parse.unquote(id.split("ArxUri=")[1])
                    pp = parse.urlsplit(url)
                    assert pp.scheme == 'arx'
                    instance, host = pp.path[1:].split('@')
                    yield it, host, instance
                    continue
                if not item.get_branch('.Key.0B781614-5AED-4A10-9B79-0A607CB7EEAE', None) is None:
                    yield it, item.get_branch('.Key.0B781614-5AED-4A10-9B79-0A607CB7EEAE').ref, None
                    continue
                raise Exception("Please add support of new type here")


class ProtectedResourcesReport:
    def build_pretty(self, cn):
        rep = self.build(cn)
        tt = []
        tt.append(DistributionTable().build(rep['virtual'], "PROTECTED VIRTUAL MACHINES"))
        tt.append(DistributionTable().build(rep['physical'], "PROTECTED PHYSICAL MACHINES"))
        tt.append(DistributionTable().build(rep['mailbox'], "PROTECTED MAILBOXES"))
        tt.append(DistributionTable().build(rep['db'], "PROTECTED DATABASE SERVERS"))
        tt.append(DistributionTable().build(rep['exchange'], "PROTECTED EXCHANGE SERVERS"))

        pr = PrettyTable(["PROTECTED RESOURCES", "COUNT"])
        nm = {
            'ams::instances::physical_instance::gct::disks' : 'Physical Machines',
            'arx::ams::gct::mailbox' : 'Mailbox',
            'ams::instances::virtual_instance' : 'Virtual Machines',
            'ams::instances::physical_instance::gct::files' : 'Files/Folders',
            'ams::instances::sql_server' : 'Database Servers',
            'arx::ams::gct::exchange_instance' : 'MS Exchange Servers',
        }
        mapped = { nm[k] : v for k, v in rep['protected_resources'].items() }
        for k, v in mapped.items():
            pr.add_row([k, v])
        pr.align["PROTECTED RESOURCES"] = 'l'
        pr.align["COUNT"] = 'r'
        tt.append(pr)
        return tt

    def build(self, cn):
        stat = {}
        protected = {}
        for _, typ, os, gtob in self.protected_instance_reader(cn):
            protected[gtob] = protected.get(gtob, 0) + 1
            subgroup = stat.get(typ, {})
            subgroup[os] = subgroup.get(os, 0) + 1
            stat[typ] = subgroup
        return {
            'protected_resources' : protected,
            'physical' : stat.get('physical', {}),
            'virtual' : stat.get('virtual', {}),
            'mailbox' : stat.get('mailbox', {}),
            'db' : stat.get('db', {}),
            'exchange' : stat.get('exchange', {}),
        }

    def protected_instance_reader(self, cn):
        for group in self.splitter(100, self.enum_protected_instances(cn)):
            ids = list(set([id for id, _ in group]))
            ii = {}
            for i in self.get_instance_os_type(cn, ids):
                id = str(i.ID.ref)
                if i.Type.ref == 1:
                    ii[id] = 'physical', i.Parameters.OperatingSystem[0].ref
                    continue
                if i.Type.ref in [4, 5]:
                    os = i.Parameters.OperatingSystem[0].ref
                    if not os and i.Parameters.Type[0].ref == 'mshyperv':
                        os = '!HyperV!'
                    ii[id] = 'virtual', os
                    continue
                if i.Type.ref == 24:
                    ii[id] = 'mailbox', i.Parameters.OperatingSystem[0].ref
                    continue
                if i.Type.ref == 2:
                    ii[id] = 'db', i.Parameters.OperatingSystem[0].ref
                    continue
                if i.Type.ref == 6:
                    ii[id] = 'exchange', i.Parameters.OperatingSystem[0].ref
                    continue
            for id, type in group:
                found = ii.get(id, None)
                if found is None:
                    continue
                yield id, found[0], found[1], type

    def splitter(self, size, ids):
        group = []
        for x in ids:
            group.append(x)
            if len(group) == size:
                yield group
                group = []
        if group:
            yield group

    def get_instance_os_type(self, cn, ids):
        por_value_in = [acrort.plain.Unit(flat=[('', 'guid', id)]) for id in ids]
        pt = acrort.plain.Unit(flat=[
            ('^Is', 'string', 'InstanceManagement::Instance'),
            ('.ID', 'guid', str(acrort.common.Guid())),
            ('.ID^ValueIn', 'array', por_value_in),
            #('.ID', 'guid', 'C9743CCD-6FAB-4CD8-ADEA-A3B6DED1E375')
        ])
        opts = acrort.plain.Unit(flat=[
            ('.Mask.Parameters.OperatingSystem', 'nil', ''),
            ('.Mask.Parameters.Type', 'nil', ''),
            ('.Mask.Type', 'nil', ''),
        ])
        ii = [i for i in cn.dml.select(acrort.dml.ViewSpec(pt, opts))]
        return ii

    def enum_protected_instances(self, cn):
        reader = DmlReader(cn)
        pt = acrort.plain.Unit(flat=[
            ('^Is', 'string', 'Gtob::Dto::ItemProtection')
        ])
        opts = acrort.plain.Unit(flat=[
            ('.Mask.InstanceID', 'nil', ''),
            ('.Mask.Centralized.Subject', 'nil', ''),
        ])
        for ip in reader.read(pt, opts):
            it = ip.Centralized.Subject.ItemType.ref
            if it == 'ams::instances::physical_instance':
                proj = ip.get_branch('.Centralized.Subject.4B2A7A93-A44F-4155-BDE3-A023C57C9431', '')
                it += '::' + proj.ref
            yield str(ip.InstanceID.ref), it


class ConsumedStorageReport:
    def build_pretty(self, cn):
        rep = self.build(cn)
        t = PrettyTable(["CONSUMED STORAGE", "PHYSICAL SIZE", "LOGICAL SIZE"])
        for k, v in rep.items():
            t.add_row([k, v, 'N/A'])
        t.align["CONSUMED STORAGE"] = 'l'
        t.align["PHYSICAL SIZE"] = 'r'
        return t

    def build(self, cn):
        reader = DmlReader(cn)
        pt = acrort.plain.Unit(flat=[
            ('^Is', 'string', 'DMS::BackupLocation'),
        ])
        opts = acrort.plain.Unit(flat=[
            ('.Mask.Info.DisplayName', 'nil', ''),
            ('.Mask.OccupiedSpace', 'nil', ''),
            ('.Mask.TotalSpace', 'nil', ''),
            ('.Mask.Info.Kind.LocationKind', 'nil', ''),
        ])
        stats = {}
        for x in reader.read(pt, opts):
            kind = x.Info.Kind.LocationKind.ref
            sum = stats.get(kind, 0)
            occup = x.get_branch('.OccupiedSpace', None)
            if not occup is None:
                sum += occup.ref
            stats[kind] = sum
        kinds = {
            1 : 'Local folder',
            2 : 'Network share',
            3 : 'FTP Location',
            4 : 'SFTP Location',
            5 : 'CD Location',
            6 : 'Tape Location',
            7 : 'Acronis Storage Node',
            8 : 'Acronis Secure Zone',
            9 : 'Removable drive',
            10 : 'Cloud Storage',
            11 : 'NFS Location',
            12 : 'ESX Location'
        }
        return { kinds[k] : fmt_sizeof(v) for k, v in stats.items() }


class TenantsHierarchyReport:
    def __init__(self, token, url):
        self._url = url
        self._token = token

    def build_pretty(self):
        rep = self.build()
        tt = []
        pt1 = PrettyTable(["TENANTS HIERARCHY", "COUNT"])
        pt1.add_row(['Organizations', rep['customer']])
        pt1.add_row(['Units', rep['unit']])
        pt1.add_row(['Folders', rep['folder']])
        pt1.align["TENANTS HIERARCHY"] = 'l'
        pt1.align["COUNT"] = 'r'
        tt.append(pt1)

        pt2 = PrettyTable(["UNITS PER ORGANIZATION", "COUNT"])
        pt2.align["UNITS PER ORGANIZATION"] = 'l'
        pt2.align["COUNT"] = 'r'
        tt.append(pt2)

        return tt

    def build(self):
        headers = {
            'Authorization': 'Bearer ' + self._token
        }

        resp = requests.get(self._url + '/api/2/users/me', headers=headers)
        assert resp.status_code == 200, "Get users/me successful"
        tenant_id = resp.json()['tenant_id']

        resp = requests.get(self._url + '/api/2/tenants/%s/children' % tenant_id, headers=headers)
        assert resp.status_code == 200, "Get tenants/children successful"
        tenants_ids = resp.json()['items']

        tenants = {
            'customer': 0,
            'partner': 0,
            'unit': 0,
            'folder': 0,
        }
        for uuids in [tenants_ids[i:i+1000] for i in range(0, len(tenants_ids), 1000)]:
            resp = requests.get(self._url + '/api/2/tenants?uuids=' + ','.join(uuids), headers=headers)
            assert resp.status_code == 200, "Get tenants data successful"
            for item in resp.json()['items']:
                kind = item['kind']
                tenants[kind] = tenants[kind] + 1

        return tenants


def main():
    parser = argparse.ArgumentParser(description='CEP report')
    parser.add_argument('--activity', nargs=1, help='Activity by date report', dest='activity')
    parser.add_argument('--agents', action='store_true', help='Agent machine report', dest='agents')
    parser.add_argument('--ams', action='store_true', help='Ams machine report', dest='ams')
    parser.add_argument('--consumed', action='store_true', help='Plan report', dest='consumed')
    parser.add_argument('--online', action='store_true', help='Ams machine report', dest='online')
    parser.add_argument('--plans', action='store_true', help='Plan report', dest='plans')
    parser.add_argument('--protected', action='store_true', help='Plan report', dest='protected')
    parser.add_argument('--remote', nargs=3, help='Specify computer user pass', dest ='remote', metavar=('computer','user','pass'), required=True)
    parser.add_argument('--schedule', action='store_true', help='Schedule report', dest='schedule')
    parser.add_argument('--hierarchy', action='store_true', help='Tenants hierarchy report', dest='hierarchy')
    parser.add_argument('--all', action='store_true', help='All report', dest='all')
    parser.add_argument('--fast', action='store_true', help='Fast reports only', dest='fast')
    parser.add_argument('--json', action='store_true', help='JSON output format', dest='json')
    args = parser.parse_args()

    result = []

    if args.json:
        PrettyTable.PrettyFormat = 'json'

    if args.all:
        args.activity = [1]
        args.agents = args.ams = args.consumed = args.online = args.plans = args.protected = True
        args.schedule = args.hierarchy = True

    if args.fast:
        args.agents = args.ams = args.consumed = args.online = args.plans = args.protected = True
        args.schedule = True

    def connector():
        return acrort.connectivity.Connection(service='ams', computer=args.remote[0],
                cred=(args.remote[1], args.remote[2]), client_session_data={'identity_disabled':True})

    if args.activity or args.hierarchy:
        apigw = ApiGwReader('http://' + args.remote[0]+':9877', args.remote[1], args.remote[2])

    if args.ams:
        result.append(MachineReport().build_ams_pretty(connector()))

    if args.activity:
        period = int(args.activity[0])
        result.append(ActivityByDateReport().build_pretty(apigw.token, url='http://' + args.remote[0] + ':9877',
                    days_limit=period))

    if args.schedule:
        for t in BackupPlanScheduleReport().build_pretty(connector()):
            result.append(t)

    if args.online:
        result.append(AgentOnlineReport().build_pretty(connector()))

    if args.agents:
        for t in MachineReport().build_agent_pretty(connector()):
            result.append(t)

    if args.plans:
        result.append(BackupPlanDataTypeReport().build_pretty(connector()))

    if args.protected:
        for t in ProtectedResourcesReport().build_pretty(connector()):
            result.append(t)

    if args.consumed:
        result.append(ConsumedStorageReport().build_pretty(connector()))

    if args.hierarchy:
        for t in TenantsHierarchyReport(apigw.token, 'http://' + args.remote[0]+':30678').build_pretty():
            result.append(t)

    if args.json:
        print(result)
    else:
       for r in result: print(r)


if __name__ == '__main__':
    main()