From e73633cd8129b18e706e7ee8bdb05d802111c7e3 Mon Sep 17 00:00:00 2001 From: nitsanw Date: Thu, 1 Sep 2016 01:27:55 +0200 Subject: [PATCH] Fix Cassandra Stress reporting thread model and precision Patch by Nitsan Wakart; reviewed by tjake for CASSANDRA-12585 --- CHANGES.txt | 1 + NOTICE.txt | 3 + build.xml | 2 + lib/jctools-core-1.2.1.jar | Bin 0 -> 106714 bytes lib/licenses/jctools-core-1.2.1.txt | 202 +++++++++++++ .../apache/cassandra/stress/Operation.java | 9 +- .../apache/cassandra/stress/StressAction.java | 67 ++++- .../apache/cassandra/stress/StressGraph.java | 2 +- .../cassandra/stress/StressProfile.java | 2 +- .../operations/FixedOpDistribution.java | 5 - .../stress/operations/OpDistribution.java | 3 - .../operations/OpDistributionFactory.java | 4 +- .../stress/operations/PartitionOperation.java | 2 +- .../operations/SampledOpDistribution.java | 8 - .../SampledOpDistributionFactory.java | 24 +- .../predefined/CqlCounterAdder.java | 2 +- .../predefined/CqlCounterGetter.java | 2 +- .../operations/predefined/CqlInserter.java | 2 +- .../operations/predefined/CqlOperation.java | 2 +- .../operations/predefined/CqlReader.java | 2 +- .../predefined/PredefinedOperation.java | 2 +- .../predefined/ThriftCounterAdder.java | 2 +- .../predefined/ThriftCounterGetter.java | 2 +- .../operations/predefined/ThriftInserter.java | 2 +- .../operations/predefined/ThriftReader.java | 2 +- .../operations/userdefined/SchemaInsert.java | 2 +- .../operations/userdefined/SchemaQuery.java | 2 +- .../userdefined/SchemaStatement.java | 2 +- .../userdefined/TokenRangeQuery.java | 2 +- .../userdefined/ValidatingSchemaQuery.java | 2 +- .../stress/{ => report}/StressMetrics.java | 280 ++++++++++++------ .../apache/cassandra/stress/report/Timer.java | 63 ++++ .../{util => report}/TimingInterval.java | 150 +++++----- .../{util => report}/TimingIntervals.java | 38 +-- .../settings/SettingsCommandPreDefined.java | 9 +- .../SettingsCommandPreDefinedMixed.java | 2 +- .../stress/settings/SettingsCommandUser.java | 2 +- .../apache/cassandra/stress/util/Timer.java | 167 ----------- .../apache/cassandra/stress/util/Timing.java | 147 --------- 39 files changed, 645 insertions(+), 577 deletions(-) create mode 100644 lib/jctools-core-1.2.1.jar create mode 100644 lib/licenses/jctools-core-1.2.1.txt rename tools/stress/src/org/apache/cassandra/stress/{ => report}/StressMetrics.java (57%) create mode 100644 tools/stress/src/org/apache/cassandra/stress/report/Timer.java rename tools/stress/src/org/apache/cassandra/stress/{util => report}/TimingInterval.java (64%) rename tools/stress/src/org/apache/cassandra/stress/{util => report}/TimingIntervals.java (72%) delete mode 100644 tools/stress/src/org/apache/cassandra/stress/util/Timer.java delete mode 100644 tools/stress/src/org/apache/cassandra/stress/util/Timing.java diff --git a/CHANGES.txt b/CHANGES.txt index 2e8fa4e26e..54ffcf1bcb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.10 + * Fix Cassandra Stress reporting thread model and precision (CASSANDRA-12585) * Add JMH benchmarks.jar (CASSANDRA-12586) * Add row offset support to SASI (CASSANDRA-11990) * Cleanup uses of AlterTableStatementColumn (CASSANDRA-12567) diff --git a/NOTICE.txt b/NOTICE.txt index 1c552fc972..dcdf03dc41 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -86,3 +86,6 @@ Copyright (c) 2000-2011 INRIA, France Telecom HdrHistogram http://hdrhistogram.org + +JCTools +http://jctools.github.io/JCTools/ diff --git a/build.xml b/build.xml index 858030a928..6954e04b58 100644 --- a/build.xml +++ b/build.xml @@ -452,6 +452,7 @@ + @@ -618,6 +619,7 @@ + #wR^Rclp5R_2b( zh$r?FTTTiX1QOt{?N|AW_}^~+af0}Bl@U=ApplRjrIY)I7&yS&AF*&hjC%7wkNy69 zkpHKcjDW0!sEDExt&HfS%+#c`6bj1&#!%+ze70{s%x-mwF<hzCEod``h- zaso_4zu+wX&O%sfWGWQEKf4A1@cJJk0Qu83TL;sBb^iY=0sRjN3qvPcTPw%^A^F!Z z|2+KHhVR&Gb|Jyfn%lmO8@TYYwHUI$H{}BJjXk_dh4Ta2{Z7hwA1RNan-R1upB(0&9zN2He z29%f5a{Bj7HrH)t5C|hZ|ClgHLO8rQe*YbGoH#u{KH$;(0ew3}VPZOax_Dm&%8)|! zc1JOs_42#)szIa@cv4N1O=Y$7rVZXjOUtIUb>}v36P)$shROGCyX%Z}NFB`0t*XyV z`)Q8njBC&BH_yy#_S=>&-zzf0as-$60Ld2bK7tOOH4-nLHKI=|J$FH$n5_I6f(R}{ zKUsuGINC0m15%Eo{fI5?(UBzd3IP@h3NXb|#~Z$=#5{O>mac@5Q(3 zfJ`YbgOau+&&Ia&>1k}b5z6-RgIawZ-Dy19LlauI$F}l)lb*~!tpJ@)6Rq&!Rys#= z#;Tnaf)>nCp@w^u%9_=97q&!?Q+N3;opg@mF@_8#W-(OOa;s>iwPh-}a zq0(%14*cXpvi$uNVRpRg?BgM$HQvqIs&@g&55hsUIXse#Pq;lhX+ zaTil!_-z|^Y0=a0cff%4xYEyQmU$;ejN96Fn?7v%56hL;XED`V9C0m67nWj$_}`gs z=v?ofM1hq=-tlJC-FW29kdRTN-Mw@|JtkXDsV=N^>N5vQEiE^C91&_&YQ#C)<#k-F zW-jB&IQ77JGZ_!h%c9HB-E0fKG-g*AXE&^h0i-wBPz>H=FNjKW@iD@++IZ)QU4p*? zxa*tJBZ?B$Fw3eMR$z+^?=w_9u_wE5>T`sNdE$V87e}&^5Qfv6+Q_T9b0aD1ZY0xx z1K%^VpG%Q+{wOf1pjp8h>;y1Z)wL{`#1;lqdY6gf0UX3LQM>*I9T`4cEc4K$ zHeKAdN!?M!0JduwqcQB#d)!THt_YgD@cxx#s{e^nQ)Fg>vK*#+mN~y80GFwo;M7t1 zSPfA*x_p(ego=i8Ftu0VLD!fJ36g_OSJlBZ$;)!^b$eJijbZYFAnM=!IT7zxqshM^1D`WC4Om;y!Lj!kA{wWMhg z*R&eSaP90z!1LwN+r|6@-Nnz7X(OvWrvnc)QRao*Uf|1AnicFT;7&c11ncM_Flh;@ zT#ppE%{)hm4IvKdH6o1jhcfqu8|$JeHxL57LSrnSk7b2IkHopl?RvK@8W#iaQGQ)r z?h&Y{6=9$koY<$=OmL4#%F!rU6JOt=QLE7+wF0q@v0@BFDGhZzQD)rm=L~JTF6Obf z7vY8ZKZ^wj7>YK7x*&qsAhbCx?>(|u9~6he{!nDnm?mYwUM@jP%f(->!6r(tMVT|# z`qJ8@Aha-EHTsBsX2cDx$B&=dCL2CD9lbFo5EDE(CIVnx&k3;CeEJ=xL$S`S*;w?~ zC7MoG3kj~3IB8nXr*(nG@&#(A9`hz)y^|OwSkQXoF3pa=**rJ8q2cWSL#i-&`}Y9` z_HDpc_h`2nvZn&emzwI9RyMZym>m&35L!14>|V|Ze&=UfwHGs80IpBw#2Xjz$(5vt z0Ys}CSrjwU3a#jTI*YGukH`~YL7Kq>;ZwSLqD1mY?d&|&_e@(9UkJGzdq@g60G}t%tsk{iPAkw?Dmyuf$j*GiEClbP*CjKZ<2|I`&8)$345T%P2ye_E92uhvZNoM~S~PcChJDE$&=-YRZNDn>q#6!d4)o5$ zhcXp-fU~5jtmw;^E4&5Mx5$>b=5k@L8HAeijVPBQt>BN^q|@qo8Qs3Bg45LlHfD)X zqKOg5oyLY-OjN;*`Kw&>RK07q5~Xipis6qs@0|1Kl4L^&^LvhXnNTEMLMJuQ;KNXjkbft?cshP*vvXEU6kzv7mN#m5@`GLs_pfcD7Zr78R5uFiLp z&gA%1CD?>e2Tdvt>5jjgmLtZA$VBKOK{6R@iXd$j$1>p8vYl&gTBLcwFp#@q3)@Em z8-fXOOKqFa<5KU@v9T%Tg0@F0LSS3;{_)$-$865r zi4lx=h9mL@*^VnhRJ4opP}4nKlf zEfceav&yyJN--;)?hCoLhxY@tyv&-O1zxlD|zRVn9gDLn42J>k=f#Uo3n(7>C-BRH@uvhldzKWiUx`Y{ zv|JKM^qkVe)XB8P8ep^#UKeo#P!Oa6-xPcAh#qLu#aQM3Hu=U*VH#)j)))tE-SNFy@J!TZfU-UEF zWGNO%MsE|YG$J0>u5R#H1~hT^`OE~A`rQ*n)Valc(1GXU%6yVOP0bO(wZ5{;Pi@7XQ@UHjahbUGNZAuj+;`*@@kFS?6&M)@xtK4MK_ zKPT>>_Sukn)rFY7&5qnU8}BRB=m%>b=4#y`Z@E2AR~KH>I^+##A6}g0s}wypYcz1gh<2WSHFr?56`b=C z4SN3gQHVfWKVH#FxTu-C2}p`VdW}$L2I)>wxUE9H$>nR{TRwE%^Jai`Bs;!9dj1c( z!|gq|RIty|vV#0R`CAA#V9_x!%=r;C}iY z=iFkF+Q?9?H1&&dixS*-eBy1|cS`6x4$9)%eyH3~h)*h-yxKviUXjYP&AS@3J%2i& z^jm_M9%}hr&?*iO6ZjZ$t>7z$8rXG1E$4N^>^A;x<%{vaFIie&{$T)XB`x|}1&hMi z0@b*~V1!^_;ewmEGB61|Ku;ZhtHVmBD6`XYMH`L2Ep*J2FU(g|ksz48pSZ+-DpDjd;Zp`xkz^(vFzj`+024(#fL?D4_!G z>_!|K%5#od<<=-N{2!EVZZKM(sJ6Xeb-sT6J+Pr3fhY6d{LQxgAYx|4b7s|cbENr; zF8c+Q!I=gFg#jm|Wd}ZGk;xdX$>%{I^^l-txAE*|Ylf*q0;96o3ldsuwK5&aV(pP4 z*Kq|d{PJ8N7FBH&Rc-D^2|a+_=L@D0L&%!8xqV$=$TK8r(@iZ=V5jC7SyEN=*U{+0 zR_}o@4?%U&i~z8Bv!G76RqA12TP>fV5$1+fiCXtOI9q=2ats{Xdc)-fWy=Jn&kYzKWWieB~U&&!1q`>wly8jGf0?!oDq!-O0_~n9rU}gWCSCLNIK21l3ZQU*t=o+LHE?j zw!uA<_)`oKTRruw04PVyW%6-l6|!`XQqm#(3s~0i3BvEb`uzCZlXL|#^lix1O;OMT zN=k%H1S=PSckKvsHrO^Ky-EK zu?OLA2n}>zfk;_-@18>K?8MCX*-4q-6Wt+n!cF?n1=@$ie?Z`xaH~Oa6a;JOajBWT z4iE85g6QAF9wvZ|GjttypgT7-|SoZZcD4zieAg^X>n92(dF*aaOQ z8e6xBU)Q1io*cQxwLe<{Ke61eNW4agctMk50E% zO6}50?aGwcgOFhrXC}4LsLn4Pis(of0)*LqzGLtA1`sMj)>ii;$c5df9*Kmg<`mTU zSw4x3P`bzO3kP31$nJWwJ7lD=ytRXfHr$SueV>h%(`X?$J)rB_A}678lftB87MrTE zVK0U+DilQI#}~#Ju6Q{&+lQn;HQcgk0rpm9NCB-4C-G8Dp617E$9U8R+WwT}fmI)T>K8vJFtgB z-V*|XSvMzn|IDj3>Vzp?p{|}C!8<4QxEt#H1;{gYRF8i*VU>Pe>5c?NB<-eEV70&= zoOw-6#UHMR|6bDaS4^DWI8~A)3#p}-d;J4yFz9Zf^e-gPT|ycBc>xSV^%d89Pk)`J z!v!L{k8pH8^c<`^=*0*R-}2mB>S(*UJ+4WG-fSgI!0s#*HTb#pwKh#;W*`v(yeD=x zt(XYPu=xi+vb;g;sgMKy`q#5=FR)g<5Bh3uv>*Qb3{i;km2Nfwr6*P~hJf3UMl$P` zv&UY8L_Q)q_m=FZmE!HXg^>4L6^Om1!3WOB>9%f8;ZHdl!n9uON3gWHNvx9DtTe-QBN z5z3UH8baxa2>HUv;_EzBiU!Q46O#syneL>T(=Cv8xYZj{Gu32f zB)+nXl53WXqkjLynlZhysMy#<<`t;~Fn@;7@Qzqn;*!b^Yse=W6)c_^j4zQZ_jx<> zCgOv@85g9OMYiTN*T8UZa^S}(7}o%mSvFA+?@Xd>RL~af~ ztUQlj#ZfoE&DS$o2%409W?Cvhu(CKB6(Z90A^f@1ezbftPh9<@f!438nfYgBA&CYL z%_?S6mtuK<55X}2u;dO3rB_YfcZsg#xyoGa*=oxS(2Ka}4hqKuD!+UV5UiCgkhbQ* z?%rYL6W3qY@r|lLGYt2Nyj3d9Y=P58_X2%c=Q;O~6$@tOhwQ2(eb`?chz@2_<8vtj zm6HVLkQH+(0}?9p*w!q|dv-O;8OkGaK&mHw{dTJq=9g!$Phb?**$FK(DJ%Yc_fW;A zEoPA;+7TmwB8Sdc{)8?hV92Le^dpbw*&{crC4qzT_Y1_^*}JMyHFwrqCieVzT>$+S%|B@i93@+S0}rc zCxb0bK!-d4@gpr-tM3Fzd5pF8GC8;mNnc!%bL?g=r}LRXpUXoyqYYd7}uZ zaL8RxK3`BcW=12Z<@Km6E9E0qe7Ef_ZNVqkN-$|j^pK%*$BPoWiS~k}wE^v?S~?z} zv$+Sqn7DmoPA%QOR*rw`{*$KpI~n1*2{8}^2mk;K3;;m=-^mEYKV-zeSqNzcIenwQ zQxVY#>QaOJ$icTw^BWyXl`F+JEh3;Yr8as}0>UB?v`SvNCHb=95;92ne3NATV7`8Q zl3k8Cpt)A~rn#e@OLQee_SPq8ZT%s@GN*Q051p? zcL`5OjL-gapXXCc&Vjasj*5<)_8bb|bwV$qIp zf@_%68+-^%S#J^Ab?rP+JQ9{&t#a*nv4;=asm{>+6lJD@4zZd?Xo2>8lSG4V@KyB8 z&ON&3C8~5SO~yPSy=*uRr-!t=b$@;uvxqd+?-;O5UyW`t3{Bqn4kzo`C8`<;&lv~X+5p)!1Do2IVG z+uuz&ts>!yB?AAok-(C`B!O+1Nz!=;LL-aZ0ACjsNGPRONHgYwPhG-eL1h?gIy4ow z=K2Lf_5~0cH#&H0zh{3kdk5!tF&|(JV^V~~sr2lm>fP+5-0buHn5ql7CEyK>G&W)f z8*4D8FH!SgNWKRQiZ{_{ETKaDOFW@~m_%YCL5a9r@`q~rnWMJMhYa3)3N{-Yu47NJ{&4*L{*=Ta#l8J$!AU zli|i8o`IJVEX$}rc9EVZR#K+UmAZO45r1OHIuBI{UeQaBAo++eYqYl8P`)MAga}a8?59`NqOA8AXRC>tH^0PPsIq3Q*^WATv|^Bao8sog{WiSE=LXZn&!AuQcW%MJ5kqQAs+4NnYW1 zS{J5KKCi2Ij=cmA0kDHP;Y>MuBmwQx@T%O3sv8qHiVn`Kajd?`|qCGGuckj98?&G9@QAxcnRxD`vV?^DPW;4BG-z@jXte5+$_Q z`0s-dev){34&Zk#V0Q={G(?faieO>cL1^cLkxzGKjBoqVFp*+HBc&>`-RLDM;4ve~ z-_d1*JK)p2yI34NSJ89W&y#!U`b=r}(|hasOyYyvU^bt`w_n}vLfIzjLqf5=u^{&t z9cgU!A#TJW#P`Ve90zRSy)vHy#4l|Bjsbts>l+9mv!j1F`YLb$0N(%ZHH2(!44oYu zjBT70jsI}eHipJVLgo&J&Q|&k{|*g*cO}_Mv#MCi$X`DappioGL?jtNF)fL9$7;bx9cfEIAZ?kW^b-zEq$N*I9aK!LxsMw<^Ksk@k@cq$BsW_cIZM zeuUV|}$wTlycLC4)N=HCE2es4X{2_$UY% z6|l8Q6zwLo1XahIetCJDMs`kGO%ne6InI#QJfWpnB~)s(0O7m&L>!fh-I~#3_!eX6 zZf;QFNT+RQRd~~}_3|9orA4n@Xv4sE+F^BD0zN5j#-tXFQPL}B&Nx`hRB3m0_Q7?p za~<~i?#N`4+B`)(?{db=bTd=0;U|7w%osk&&ew?3PeW{Izf8Pc}Gt4_z@O?}hwm4%1?B`n+L)KRbNer{y!rR+A#TN8zw!gslCDrjc}L6+h@ z=DV?1o&gL5is>-Dp8!hjK|;i;y~xx8zmdli`z6Cj^@Bz#I&oW5)B<0&1g-{uB=1ktz3h|>}OD9)CA+0iZHG+ zpvSd)GXcSGJ}gRtk0>OM=yqIPqvHo8Y8ZJGag(D59K^5+x|0;A>$4YF*1~1)MakMV z+{(KweY;p24J%`vS((LUKIMlC7mv}WDEfdei&Z4r4!P!z-o2Fe$w`un>>wthpOs|< z>ot_GB2yJY>7X5Fo+?#xR%l4g$iu;NSPIwHurz8@kBo&+u-P^)mpzsn%Taq5z7-Se zo?IfG2k@ZX`s5PcAR>nOFjXW>F>(1WXOUf>B1E!MNii z_z?XIX+_I)O)&}C;@RFH6wfdk2Um^ZOMSc=uCEB;Hnv}DUo-`hK5lzoXu1V_A_VhG zcLWAc3*NcAi#zKazO-q$Y%Z?1dA@dClaXJ-^@{yNCnPaJ()3P{?@ zc8AZ;VIeVO08!W#RlBGF#GQ6-#&}^7d3q)kfq@cc|@n2B+@8Ig+UQy1$*2vk=*g@3X*viQ9?>@3v zVOkc50X^%13cR2UTUwgC0*vjxBoZs0zhM0h2^`GUxV3+vWB8^fe)PfUpdaimKT21X z+wLyFd?8)7cZN;oS;2wH3W5XJ?L1mbA$b}LW-+fRIwD493Ko{@4v3 zG`fTJF;qwfm5_ZlH*E0DqdvdnwlXf<4=t(2oXGY!0a56d%BL|*&w2#`BOVe$gm~wE zqWMfwTn?F_-Gz+Vo2i*=ZXwl(zF4b}k%C97_e-CiF6+kKdYlK^=OgkX`-1VCo^=lA z(a&^EKsd3LX*=q!HJ23g1r`6^wX!Y6@E2j#jJ;0ZY(B`f_m9ux9Y+1U@fdPm%-?3L5$Y*a>D;AILgM=rUSz%OH%U1$P`Qij`i# ze+RC=%C(lOr};hpScU!%1>3Qmgy-jT$$l-_&xXx!(XkK%>UsK=53e?qy~U=4@-O0WDvXBQvbU7>fDq&7 zctrCoIPwIN@E|mbP~`C5adoT=o8Sn%xb7`w2d|vST0U()lSW3pR?UNa02)&SihfAq z9jaDsxG^f+h8h~YJ)dx|YhR@K3z&@(wwzDHN<3xxVg4!9(Q)+U@$8S49pE%VKtA!t z14;e1Q5+Wv`^^Pl&Waoc&|9#;a1MIj<|ODL&q>iu+)bUGAS+cXZd#;Tyc&dnbp=ds zTc;kGu1a`%>$kH;44qlPqBGT>!meWFXT7Gs^ILhGAyn_D9p1wL7~lW4=H)vZW8k0l zWd{lX!1&*-`H!MKfxp%6{VjhH)&Em5=-~d3Wu^2jizER5m6D?4$)iC|8PSVUj66XA zL|BbN3@@P(O+g^=wV^}jR@_bXDl}Mlhswf`AbtS+2F@F0@8XF-h$3jUkezLNnz^2y z-HzAW;|)e1iGs39UqcuPQGbLiO)4&19!5wPXAasC;ij4{e8JP9X|t+mY( zf(NAV3vALbjh5@CW{2Ku)ZF9UNAE{TspGD>dPoo9FqeO|V}l(WB((kJCow_Du@zmz zDl3fla(#54v&?2{i}C;@((#Y)vFEODh;Fp!yyNPmhlUDqBpK zr58@Lg$tSST<*|^vxeCot+XV}IUUkmOweE3l+cN(XUG>klb**QF56X;3|>DK&D{x| zGmryY+x34Thgi?`8M6%~E!d0TN_D3Af3!*JLfl^hBUKAs!Ynu?L>c+sfL}Fo;})}! z_H(sKRIae}(QJ{X<3G`=Lp=NxK1$F0i0BdOB1Pa3en+3b-a>oABUm92kC)>PLIhGE zNrEP*c8v13%e`>QCXS8Y;T_OB^IIZNU^3#6evk|;5IvGXJHz@R`LkcBh+cG!-CX^M zA)G^6K5`~cx|8yxK2T)IOb2u6jV!&rO$>Su@b_U@v0S*8iT; zP+@X;R$u@Cx$po0%>U<2cjWj7o>tj)#8O4!Roz(Dz~Zovs$P@89wuRL zSP*6|vA5{7xf+T-<_uRX4r%*5^JsQiQ8!oqZFdz1L{pUXP&hZE)*T)aR6Im*>^&`{5|om#|>*DPI znUG3S%q)DGF%t`Oozr~h2{JR2Gis1@wxn;wSkdT_G!rqCF%vSAGEtn+wn9r98;=H3 zaYDUMGMYfmG;>6Vx#|jNcvRk-%G0v}&%k465N;8l zL3;9hKPWxjW-B0_$z9^Q^6V)yg z#&$Zi>c@=io1|iNL|r+~l_ZNE>nELyn->9D1`Z7GKrrB#YwC3Rx3$A%xjEtUr|Z~- zrXV=xM(*tw?2a|se$a}RCCXK6HUy%kX6&M-=SESeX0)Qag`Er@sXcgwt8dRd?*dUe zrB+cj50{nP8KrcMyB`ei1RuY{w0DZ}M$gEu9soN(leB$C3TqilYZuu-vNyCN;hCS1 zO9{xE9~0r1=Cy#&5<0t&x45B4vmC0pxoh8efQz&CHBp^aexnGWxjwYcE#))Pbn-W8 zLCclq{%98I%zmvYKicAtP1)Al%C?#&?V|21n!@@H`c)v`KnmGT-D??R?}@tMrSTG#5urY5_9y5Tk2LSB@S^&_G zGs12^97uyG!*7WQap$)^?kFce228yuz(bKK+#gg^q#ZZPOGG5fNcJdn6>dopl`I$1B#6`tr@R|)nw0W| zHB6Mb{o=&SSmz@iuVJKO1)_iQV0ru_3=lF96$F)13gqEQ@}Nj@XG~+pz>H21j8ps} zW#af@$PB!9E$t!p>%;^*bz^S?=LrCF@bNMy3^$=T(==GAex*G6FZ!a+um*$rW*I^g zZv>v&`aVl!9clx(;9NU;<3JAy|AYua5+!elQUn&o)VMGI-&$FTS`mg-hai{iRYS>z z@U^Ox<-^HsDya&ANrb@>TtN=i^Zu7a@gG>e*U`+#QUrX0hpO{^&K|%K807IHR}AMyTj-Eo9#}+$;<0 z=q6_1zv#{d-33EY6uz28!WY>EY7IYsq+xgORu)Rsh8#)x9_-LjV5%amc*5aXee#Gu zg>A_J?oo=i%8^@^ojGIoTUTwzj~5eP0eXnwI&fW=s`sM52QtGI28Fad#eeO^L39dz z6DXhe4;XhGGB%uX0h9SQl42b$C6j5X>G`gHctJ{`(p> zy>~~!3dI1=0`-Hp#k+y4o{RLYl7XCUKXucP^V1w@H;{{ht#ALM5malpu+(1&Rp-(j zm?l0q@*DVnNAFy3I^AvdWpPYdz+D z=Xw{+i)Yuh5rN{di8r5vzyb2{kz`HH0P49vYV9S+;lnXVx~ z{ms53fSw}^tx3YtsH$rrE!B0W7Z!3RusmEuTBUzoV<>x0Q&XaEgiafGlWU36VuYfZ zJ1HM_lUo_)JVTw7l_|#g^{aQ{Iu2V7uvPNTZnT-Vrl(DbA)!clmDFV_$qBSP$WX5zz@PQ$j;+pxFg# z)iTkX`*Ib;qu^-BRi?qL3wb&rJ9z>>YYnPz7~MJul|SdBH@*^G+UU{WuI6=x95i*X z#%9ybxKQ_KxV0|8<{cIPU`am)XzbgIf(|OhF z@CQjy2+^Ckz}DzD5@v^;xKJxL?p}UC(d-kmSGX9aQx)Y|WsytfFa`2M3eCbJ=z69J zFWd7LJZSqIeUKuihrOgg(E(F-qll;)CUkq|h!70WZ%om-Ub@Vi>T%%g`R!|BE!aKu zQI|4oIL9kdDuE7&M+TYYWbSzR_Q<>f#O81P`S!N@+SLO_+2)lHNUyA(3c{s{y;42r0#hr| z51{QDENV-;Qps0M{RqI~p9FVa1ub;OPB0;!ufxici7Y$`Esn!9l!tzz+n1FD^lw0p zcu}%nfqBi5y@^jhfLJD*h8<&k?H-5gA)9u^sd7kZ$k-yB7@PmHR%JAGAzo}EOE0M~ znnC=f80EhDpsNv(HENJKr+^JR^@H>v{1!g?+kdCa`fxQp}Y+*8u~D>l$;D&VenK)QFXp`*RUChp-wpuc2bM{RDNYlwQ)Jt%W_7{hA)mJEWH3v)sxRg9>BL!0Dp zDoo|Kwo|0z$MA<=mfW{5BhFurs%-SvQ*8g~TT5T_O#J?^l(V=106hPvZ~f~xHDwzE zTW1@iKmUgNn|uAw6scOx(@tp_zjA1qs`9yZ6qDiyuo{AWXc>8tw%zR!@TRcd2mddcuAq` z70=-GtCm^MY@um8$H=rJB@60|ms@o8|R zmts=HaW*j&d)(ngQS zOkMV~R9cTrpW*3R6pvAnNVRfTl=o=C-efaT)5j1Rob7E@Yjd6LjV=eNpSG8n;YFBgx*6}{pt-?Do*8bW4??!~kHEm(rH*8A zuIHYwr!|}TeSZo@N-8<5a0)j|6_u=T0pDLEqLh@-jQa!9kj@`Kgz1#}D@B4dpQusS zfl0D^3I@jW6Jjh9YdG&fV@4tE^BdLmblPU3N>kf{D58`ejuS{NE-@FI3t04nOxJg` zn@Xu(hUkQsIdJ2&vN-|3ldu~>&7Ha0d3hfre$O&Ic!akeV@9^uY9WHv%!|ULM z1-seMPJ~zIw+W}Zp4IoiTlWXmgdRR~C1@x_mRengj|8fUy+08@owGrsyi^-gic++( zV65*TZ9us!7DTERnLfnP&+nLKsIck;^2{fpMkk`|&TUfiI|m_ZK!d4;wQ>$BTc2>6 znVfEk+t;0k#Ms=fq1>~3a&*3f} z3`}1q&NXz?gyvxP$ntFEN`jI(N)`*y!^40Gh4ZMzm~|=;Zz~!pdtz-4L#`cqq_$x3 zVr4PEojM8@Q6m*Zg15iQ#!Dy;itr=unDaBzLx@+9DZ1aL6GG4EzHwBvU@s5qhf6B5 z7x#om5cV$hfBi(A2~4-9;iSH&eR&x$KaMHZoXcu%n3)0T{Q0 zy4$j5KjPf@^>e0~V*;+ZRDw^%63d$*)--YbcQQ;!)#E|qde0H(49ey?QfeN%AhG`F^> zoK63b&JLxvsFbos|1{efZa8WQLnkJcBw+#yK+aCvu7Btin$;DYv$7*7I@AlIRt!&s$9AF!hyCad!=JZ6tLLWF^YZN+ zb+17YO>A5KY?ktOFt7Z%;zq32Yz$&JZRXLXQbGD@7+ts*QY;O(BvUwOE6aP$aG5#- z>Q<#;$#4glhwJ%Up7`Z1hPagoP@fU4v2}so`k;oYD?-z+GzY%H+B5DiL zW(40Nq)*Cy7_qZlU$toInsvA4SE0TaCQGXz$DdemK z1y36=?o3~r!+Ce_4Wlic2_VNMt7AYerK%q~RE8u<1X-5ZK|cXJT1o#Rqyvht225_G z0X4yRg+aEO&-Q}egm{s!bH#v8H7|9BQ(6Y{{*%x8+#2oDCqK|)E{(Gar*)tUX0P_g z>GPbnUCK>g4bo+3Q3et7HS#pT$HHO{+Sm@n-Zl+7g^ydTVW`rjOH}+R`1C`#aq5U{9jth>GHsv|ym3RtaNdYD7ri9f z*r@POU3g_4Js={M9~}VlA_5IlJttT_BcAAzzXgS~70A&wKqnf#EnpY{e;ruJ?dU`| zeI|hhm?IY8)ers>y<^D^R3b27z4jY;Z7_&8*;4NT8TYuBR93=~M*=2-EVIBP`mEbf zVDvM|D-7-xb#7Ko?u@=eDm5Ci_C08ziJCdo;2KqOd?~v*U$b~(cj?!s{m>38&M$o8 z^?5YllnsWAYhaf!P5&4KfMCG~QME>h!)c6uH7j)`>Z`b+FzZPqqCrAjYedxIYx0&W za@37dS(_0FAt@L>Ei@DOQ&-%fLHdKCixIGk`M~!s`}7eW9>W>l9Uj?S@v+kC1Mf zJPq0_YXp@EgCX>_p)#yamroWyq)`C~H``J5QvN~Hh0-~_9vMsvX9(Xf2pEpgG1au{iN|LCC+z$A9^VJgnuC#%j6;Nk*@g z0p)`{alh!dKG2u(z~%JgdvU7uZ3Wm6Gual+9C@mS*h4zaI2P4aTLHZM0(Od$3m{g!IUkQS2 zWP{$pnZ96M%+qP}n zwr$(CZQHg^?%U_~-o3}^?&r1M*7#?{ikR~Yvc2zml==A>4u?~njwLJn7QpTafhw!(kc)!UvPvS{FO)Ydn?R*Vcm`fQP@Q>UK38n* zrSuEzS`~W%qQWG&7EMdkjM)z8X{ImJ$qm%aZ6To-L{7uMe?b^Y(BoQ$Eea%1_|#Ae z^mON&Zsh!`8|$ql{WI)LGICo5cK)&j#J0tyi^Y?IE9`NdhUq@>K0nGng@vcxl+WE) z8_j@u*noK`kcfeUAtm0^n*k7J2lfI4z|AH7oBFGT8JKYg5yD1cL(2=nYD1sTmyy_e zeCHBYRa9?*iW^Pif^!YLVi zJ%n?fhMcU3t_&mh?bvjeOf4X~JUqKNY@g3-4H#P?)^(}=a{U2omgnnV_L%>$Q?r}0w)DCZ;+K^kyqQE=!$qPDN6e@Zmu$>>-ZM?fTn;pbYK2m3{3Mq+XY$RN`#{lu(ZS+QHqP(Q z-@D??-><#Byuo(Zu}enuh$B^zz$QvqBhM|Y3T)*C!??gadXA9Wc@8#(JK86WqG-lV z73Nw9V#-vakR~7qA3wr;>Q1V}ediU&V;^nYCEubWnbJ9p%c@LQD1xkS9oI{NFqGtV zn$F%L=Zer;sx~P-amHkr#f5gP)Qw7ym?X`bh?Y_Chm$d>PZ9dKo$CP6OJ46}agm1|ew`5TU#uH5OYBlRD-USVoKfE6tqdX23FUoA9T z3TDuXOD|Erq74E6y^%ZQY>Nf~SISpzn2vb8)lUR~ zoqI7ZK*PIg3h&_CV~_QkZ~Ntv!GnApE&CR)?5{8ooW2YQF7~O2*)viVVmY24u19&i3E3%1V(nVtq^R zxv|b7j)q5s&xx+OT>r7W9ym>?y&B#VWL+yc?Q|D5M&9U{55Tq}ea%;u4QH|C2mF{!6BNVElf}46!y2;x z<=7?1>`=3qdTu!e(b+M2yC8I}btbjLWOwMD`8nwEP!1*qhIyh{VYj|?7{M^!^{xIX zgancq$5EFlYtcGm*XmlVk_;I)Jgn^DmK@lD#w%foFp&sDQjZA%ZRMt8ePYI;Lcdj; z5$szA42nr!pP^dTK4=CNqWd6bGvBuMGz6yE?Jomh;iXe)(`x~jU@dh1&8 zFXzc7597mzAe6VH4r_{|)GWcSyt-3Xf3V;BJ?!T%0E$@62`x}gr>*)AZYQiToa;~p z-A@B?l|4@yiAPaU?JA45(xFU5SNq~v+Pik!A6%>NZV!|ZJu2;wJ}F`+sN#ZL1tWZvje!gGH9b4gn1kP|3pqA-_dE z(T&NBX(e1^>5g*u35HbvVkgsvkEi4j;|%YyPRbe&J>PIMOm^g^U!EM5AiVrKE|F)P zP9d4Jx@$I6M3pYmJl57vvCx(Ic47BJS>;dhRs9A!J!01ia){gG96#VQ$5&O5ag{ z9pILxsE+h58#dWQ;R=@LP;7Iw0H4^p@&ldCFWPZl$_B{mFEr2HoY!7rx~4K$YRpl} zWnXA{mc)x*=3aWV(jHF6sgM{^y`TqXr1ICFe=d3cQLW>eU*Qfweh6zY|GP!wCkONY zoIhqNUE3opBXLbHxltKH(bF*p`S7Q+*5NYmCadqG04}th1#QhKgw8qV=8~N1G zB<2VU3q{kusI%8-KIQHLr~ax-6s4MWKjWM}XrDNUO>0&zH&vXjpiOQxzi)Z|pt!a? zogbsWzaFiBMeTV3aJ^(fGx!oq4BP%nNZQ(>GoeWAOf54^t{+5uZ{jOEQhda6?zsz7 zYSd^7|MEF?507Gs0Sn4drnE7CKU>N;rZPCPuovQugo}G8UmOIjo7B{tes>DxRt-$j zEk1c60IyYOvJUHKvmR1fNwp7qDm>e3(CjjkYR_0ryr-Pcq#uv!2#%-oo+>599CbbH z>*+Xj&r(NgQEe=DGE6k>Qm8evU^uL6^-x6~ZC#`nFFJe~2hYTaO%^XdA!!2R@Nj%s z0Ig-Rc3PsQxR9!pchhs*YWk4(ypC}!O!A6ZB!)4g*R1@Fx!H%NQG05JLdCJuyICt( zU-uVYO7a0Yu5_h~k71Pz(h9?lsl2JfS`A{~h)jm0qz>n#r5tveBQWn&d3>UnNbnkW zIPSO&9ILcT2<^&7Skpb$cRDE5thCEVtHNL%A}_{1*krPln=IDUeF@I1!7_GEi&P?`0K30eZL z^RZt}WGNZ!eaq>Z-u7D$P0RK2Fom zRoJd@srj)(+vx>v<5$g_?4bFF#Pi=>L6a4VWglw(=h=yBP8-(FYchp53Pw%uup^R?N-ncg&$f4xhKj%6@RicA-V)zN9b)UFEgRzR z0fL*MQM|QcL_9 znT)W>eCa%x4HgwTqm~t%NDYnE^t?x!4t#auXSi1+2AfHwYvPx_(-}*z*QP88Uz5;MqhxpwMm+#NB zW&tgo-UZaa+)^klYM9JDJCBmOuubtiOOs(D?$C-q z{BG!4J9H0LEe?P9^YG)MioXAbU+(r1Whq8l27L_5f?mDqXM(aUhvrzq^M+GD90u9^ zEPTb){mkQ}Z9tQcz1Y567y*_{pVb(|fS`_{!%sgQp-8beTUfmV!qOp!Kky(GF;}sCOxw^b7Ck4HR3jK@M#mpQ$K6^7*P|!+*309UpUu7W)cEs! zrvx2o^TD(5%S!vRa}6F-)r&;$SuTfz%c*lFYan^$+qi)fddv!{F`2uu{RO)VBZB2$ ztL8tP*M$d4z^gxAU9|tT*Zr5Q;eQlQDQkoOi?YmAP?tvFMf?hCS`RPR07}Ti&#AMF zJtX#HCIq65Wn=<)>KzEabgBp+r@jIf{6zbL&|V-V=0?r_lpSJNw-i?wses|IpZeMG zUuReseSRJfOaHo85ykt>6v%2cNS|C_sHFm-ry8*@g((i{N$m_dNohOGWN|*)4+YXR zXydinq#<435oh$q$^3P(zx0&+jDxBdjNd~mvoa6Ow75`F_Eyp{?0C)1Z#P_`mUhgM z0K2+b5R$+`*{@EdJeq)-ckUE8wEZ1=3_xVe4Ea_xB5tOoI#rCZi!dD^va zGhPn3B-8Y2FD09K{2i@cQ|x51J^KpC-;w!HI?9lTfCkt?st=z}PfSa^58!6FfpkSb zgt|b@p)=x0m*c{{ww{kAg@Ty-Cxq=Sl;p%Z=Z`62EI@y2PyA{A)W|i&hu4_DNhZF( zf#uwkNwi_Nzb7$)AD)_g0yrc=n0SMqjvwJ2^*r|+yeQ!B1JXs%5&FwKb|T#RpFW$> zmSzUJF9cpFjyxDJ372C4c_}exH#Vu>VZWlAZbGrHN|*AWGN8lm2{o243C{LP9^;N>g}=o} zGu&8G4n({c-d5E{AJ`#0))}6m0TWQVipc4IV7vQPf zkd5HdQT9;xS)r%+kV7(BK6>9^2Q<&j;20^K+AWZ>NY3qm~4W~1x@rvi&FMR+`!V*OgCMuzh^@zNGy$#A zT15dRw$zo2U)Crw9qlNO={=ObtZEk$TqXZdsc1mMpT8lYQ z<{>n~-Dwo=EB662@6ynh9DhHU7?RE|FBNRHm(ien{X9bng71^=XC16a;ZDjprSrzVRo2z9|2eW@o zeueFiKSsv%)sBw(X%^^^ZL3(fJEfVABm5SsGCN4}b#5`9NF7rdl*=;U&&-Hd@oTL( zpOZ9Jrh0?AN|}T-mUBw=PMzK@%NtF!uIsYztPiIi@U>(BjI66Rldsv)AV~gO90H5p zOj6G$*-RY1H);dUf0{8e0BpR#iXUNACRa;rh*T!wc03Yd0k*1a2N}?qSWA;#w-HzQ ztIP!SZ=$GlYg3{!w<|y&G?*+N`raWY+3#Raj{c5T?~?CZNbDD!MzyJn0FpOx=6j0i z#YA-oK1qg?(hg11nEH5lbl^MM;krVPF|XcOzAc$^&I{NiSr4RXf!R=MD0L;J*g7}P zuxcG~MD<$1{-QyfV9n*Mx((kC1yOLD6S1)=q)dyTHXL0R(d0R-JjY=9lNBn~?fIFe zmUoj;$7thB$a>XPv_bn7w83^4t^TvYLhM)uR; zi;5KImD|ERWt6fgM+A4vvWah%L(u4wFOxQgLo%p#r8l!%_LUmUH6dOcwXF>NzzpkH zc_xkg`M6@$5o))s&wwmpAiO>5w$R$m<)1_e0lblpn9HqW%9N_BJ1zsA5lZaNxHAJI zXCynL=zLnzHLyViOV?bFOp?p^p_X z!oi9kty(QI#_Z5Gv)UduWRw1%oGZ7Qcqyk_?y+)~=5%FwsoHC=gcADYmxM34Gn`)5 zO+>Gdkz?u2YF3R&3e?!C6m-^=i+Gi>k%qjdYbR9e+e*Ryf)WVRgT4)`~ zBL~|c`L{$>?>c&5HK#1DvqrhQh!Rl*D{k0=PTMiIdf~jeq9kr8ETk1W^4u>EYz-n0 zVGi9kiCOz#TJ8MGBAKrwCA(%for)cG3+PHD5Hp$BEwi#z!}aKtY3M-Li7KS0)v+*UDj1>2FuW$uM4vtu7ESF+Zyu95)E4<1;dUldJC6=e(6)(X zz_IN~$7s4b`57&5rHi<)1=dsjEhA8TLFk`?8e8MLGd^x##ZSHC{6c% zN8(OFN)|PESSKyiF?EDe>V~k(51=b>+~c=KKX4IKu=1+|p!KdK(E(v>mQh)@;_229 z<1>XQS{F^hRAPQY7#C>uD<3n$Bj_^l$+}|RseyeUz zbUMaC#>jHy4gelx;e5E7YO5ybds3JgmEmGk{9I_AnMW(IB8n+ODl~EY<^9S?1@`6_ zi@-Y3z4H~7N3ADas@Ak?^1ycf_Ikz0vmzh6g1JdKP`5AAwy-qRhbXBpQEtO)u8X~Y z#(KV+EY??K67IPr*gRbpRW*;N$aGKx&S1l5%reCSe_rMwTcCog`$?QwhzOg=HypuyXAsgu49=&3)QlxIxIiYlq-D3Ya<`i zIK#N$p=~Af#^4Vfu(~hQx+?;6%273&TC)~uWuRqB9A)l}92gWMmYr7Xa-olY_<;6?rDz!h6jSR6u*_M6yf(z@(CU)GU56;1<9-CJ>Fi-eh0?*4vefG`q~V ze?!aD=Th|j{b@C%;Q#kl}Ot1{CpmGNA4+O;tLv!EATq!0VW3?N&qJt69cZA3KsTC1p$WWN{vYkuE9ASI374GSQ{nA=@t8}aU`nT(ljurNUf#wiPE}XukntF zLQ{J=ZGQ`#*i|x@tngMR@E8>TECx};5tmJ0Es}tdK9BO4B{y5=K4X}mu{?p|{*YI% zgU02*$8dCD{6^Qf!!A!mkb$1f^jM0wnZiUACqxv6)o`bDTO(j+RbUU0jb{w538k;J z#i^;5!nk?=RuGv~L^nKh3AWkz;ypu(VoW8S7ahfRB{7{L>wvj_?%HLLBvE}aS?Xu} zf(_MH)%5`B+*i%cPjeEasNsh}0AILVMuRkloaG^M>VF*k4#Tf)TU0EmoR{R2Sb1#h zHYG8YDpcEjkH7w>AztSiI{*r_iO_IifJ{zQN^Io!~=hwua7HFl>o(ERO4dlPY z8a5Uf)Q9Zmf^g}SuWRqjF}%kTkWBYMWG8w~*Zk6V%KU}ZI=pt70R9@Zl0FGs=i~s< zM=Pf*1#FkJe+09s>)gu@p=c0U*=Tq2NQITzU@AAEpq}>LlFAxQs?g=N*s6`{GcXQ2 zajC=>Oir{Clj1P242$)S5&>%PlkbiW!8WOHyKXbcwFxVN^j3oW@y(Q<=2&D?`@HG-P;$JGRo4!N2`zDG;}{XwF44|vw!u=m53nvF#to)K8QWz=$@+F;oO={?ega;IBh zt&N2!KWjlNb@dtS&8I3`mNg z7Y28fBQR~qY?C1ndq+1p#fNW`{} z`LvL2>RfUqlkRbPn7>9p=#F%WP2{%Nux9l%G%v>Pb?M#!XgRuNz)to>Hv4l2p;e;P z0X}~oHEkxj^y_V``li@2mH9H-oa(m9&@9HfH;odX4H{bD5A3M?%`PlHhlIMxuaA^@ z*gSR+p5Rgxf0rl_xY#NigBjuCd!&?L6!SDE--r=HJ=`2^go5rF`K-_wB3MR7eSGLC8aab{q>!s?fhN#zxlN*Vaz_i(iV7f zKtz*pW~@|`z606*%wqE@q{A)#{rfIQbm>;Kx!@AbtbAuQ7nW01gw(5!6rjF*mjt+l zp2QDVVI2d{n~!fo(wIazAEh*(B@h7eGYeOmUl3y$nO8l?0~WH^VU7;5jM4h#9V}D= zIz@D&1`t_}`pPHrPG*oT-HyC(G;xpCJjd{RA5!TmM5jZzf}G(B0K>a*tRen8`H49W zLy5Lrs*pD^%8%Vk;G5V`{;ww}L3D4Bv?l`P(2Q3a9QG{z_<{rK0M7!6dF5S{q1lyu zfzlz?m_f={gkWo*$PbR>cYD`Z$)E7f2X#(^u7ZArfZQ3Zg zpA_oGx0%A#h-^VVTzK-ZzwhcuHJ z2Uze@JvWP`KsDqSI($+TN|a#~%jtkp_6k^w-aWu-aYm`y4wmKtPI%rqnckhCA=iNn zpnVhAanZyz8G`raYwqdOy;A+7kL$BL%^8~dRf10I9=+6Qe%l*eJW5-G)bLgLLHS~Xd6 zMbi6v&;kC(2*g1Yu$|+GcEMqi*92mZq6W-{m^LJKClfVUqv5xnQ36n+iCy*Yag1GWI%abW*!M?9Nav;Lo`weZz>jjkyjI08mAVVuQ6J zZJ~6vNvix5ho3<9aD^#`eeY7)F11-XvKqTFmS%kt({ce93-RZ05lPRZ>hLD1#aY+B ziq`IV4=CjI_g#xLc8ajj@>!bI8wWsxc^i(&l7Yd=T6D9#wN6wPP=&1P&;t}oX6!dL z6AD4i*yYK*=Ls(XpI9>_R5gi0-uZ%G^{Mogsbkg#+JtJg$`EpwfTiaqgGWd1l!Cp8 z<5YzIr}!3C{^Wb=E5 z-YxkZNC$A*l^^uclb_qBt*3saQn3&VR{Na<%UJ8drWPbQ1GXw9|87=1nv%}Ast575}r+P#bLg60sO7Jsvap&O& zyEbMO`{sragdG0_wM^FWL3srwdUZH*=6$^Cu9)#hXK8Gst9 zVOOtVXIsK;-wdKO;0?qSR_7CkH!Qn$y>{loCdEg|Cr6S^^y7rAy+?E}6o8~0>;=_J z;h(?*5*&Hpn*Ig|MNZHS;!9vD#itT5C8821=*ixNi_XM8TMcyfVux*o9p}Lkl)Bd< zsD$Nm49Px1%~fVT8=)xi{zxksWQkBaC}B!Cpu!73z2f(&9u7&s*9q+%3{7})+*Sl? z4RYp>!HN+1#D#v0xbU09Bu)+N43%j{mEd*=*BL0`j$IaBBeC&+79I&kRV2R?pAi$V$2-W`ghmOzXk{QC7B?%fmUw&g&2 z!sk!vcHMrx=|1(74C(%Q`2g-g3T^=;?$|r7KP@Lb@&2&+D_4=+JfFzcfL^NmZg!7JQLQW z|4!s9VC7_xWi_N(1N4+^IoyU+p+I%rRk7qcujnMHnw^|gxpyMB@-bOA=54?NBxbl= zj+7*^?RXvu5D2WKAKnHJrfFiXl_nS4u$1zZ37=}xcW51F(+vD|UG{qYtdo52OUIDO>^Q z#6H=BE^T0VOG2NajIb?Dwtr9RH>$6XR`T=`xtZdqhJEO*+P(em34&_LH#2UGQk$#T z-NckB4DFghMo#v`v(?`N9^51s7ozUa7yk(mc%0aX~aE1ZR8ZS*=_XYyxPug2DX}uvt+zQZ;u#6!Rf) zzas<^7WucwwNdnV&3nHy#Z(B#C9tpMs7$7!~$gWVWg_|7GHe6CVy|jsp`^nU6j^l|{uh*|H0w1^p29seO5aOUYTG|Y}ijQ1~RD7)SB5HzWyeTM7F1xRA_tS$+HQ}X1{v$ zIVSfdLtawW%{B&2Vp7=#O%7BN9+uQ@QT5UC`O~AY%e(U34@i`Giy^#%`aRW&*3kf} zPtXN_cPIINn?Z#7qYNaMta(P90L}x+`h!*_QXpslII4;n-90~8i`_O(qTPf+52Mvy z07LH5Tz^&Gk&HR;6wUJH9UAkjjS`hua4eF+$^qj z6|)?icSg*&!~>{iw6`u20Ilk`I)iol@o1_m@%|oQ4Mv&101$hBMHAL99!K*NElUdo6^Umn>;kV&}#3WzoMWpRqT>Jv;pJHdA(A$E!lkyU}8&n`tk|9^Dy<_Vp z)V@QA8@tR}RWztkD0^~xzPz%Z=JIS0?*4lJ#PiYmz|F^!r!S%-*vF?9jETf0%7tPx zG9uC#Qnxq)$LN}zP&BHdv)3fPkBV@nBiRR0V4h&euhsQ^bZD~50#?;AibeBJB{E7% zr6W)Y%^}6*)Io%DmhnNw5!Wa(S|L9HKqrpK<5tufJ$I6tOg}Kqs$pF0s8|ERc$b>h zWW@X}OhW0N>n>_fHSW-6o%C3D1K**-!%2S8|9tD!N4?hML{ zih!H!#?)9~7St{jLs3>Q@nyT0L^C~o-sF>Hjv#(U)wr$@4zW4^N~CEIM%4K6YW$QS*mWe7nzlyRBN$8030q?wGs&)Fe) zFD0olLv>-1Z)s6HLEMp3?~KB2HuDK($*rNTs*FmyjEIMy%$yQYvDretn|i7wx*mr_ z));;8kj&F@1cin*Fb8wxRXoAXu(%-6(tSdo8VLvQ4bD|Wi-Ab9m|z`28t9NZiE^G0 zjMCcP+~XZz$Q=ekC=^Cru(%^nd2eY5VLzJK*rHr!sVqLP-#lbV1}PFc;=wu?8?r1A zZ*f+YOD2})50ZaI<5*KsHcQHqC5M6(N_e3TPX(wLxK&?NkL*q!WzMZAHn$|jM5GEdFS*I_g}u=;0cd_Xo44k-r^TZziFh3eF$LfJqln`NYWSdZPFLy zZL$~aPH=iuIogQ{wg^(zXcnXhdIk_V>emZpU$H~BhP}G^-Dd8t;tO=6=ZMBNikwlS z7q)PrHZHDg^~MgCAgQviSoZjjq{XTdhqT<66c)`p=~g*wu|c}g?4T(uIkyb;af73= zje&=Uz}ATqk)sU_W)}MXJ=BMTMx`C%rmbGLNt#Zxjj;Z3b>&Ut3RDLs49xRUw}|_9 z8&02{8eMZk`9?O;Yde^7V*6I)K(oAL3HQCT;5~=jQ&1R7d6%Y>lKVFz&#ZhyMoH%r zq@)RTZa*KFokEz7?6ES-7PqOpL~)QyNROKp^Iet2m{4O6^3|>yeaX{i=QYHgz3GXU zivvjZ)Ac3jClcPA^=I%^X!OrW*uDiRBAmY~&7o;SLy`+wRtOO1{FZx>Jh)$>GTx+U z5SF+Z7aM;GSCQ$ib(e_?phh;j(_a$}iYu&hsL3{kKdS>&#OYRz)$uU=Lpn=_xf@`k zKL-wQSAl9z2#^6A==}j5KD7o3CoyxdNJ3F+@uzvG+`xlvQdpPwDWhbngJR}F5QM1k zu!P~s)@&1$giNQ_T_e=2tHO^29OS!$45={jkWi~V?pQ`Vfmw1PPvJ9}uEu(_$u;?-M1}jWFWKmZC$kU}h+{3Q6&m_h<)!=%5j@ zjr7)qEcOR#qgOIXtk@oAyzxm~o|0?AYs-zYk6*z$hV80$JF(@I4>}PJAa1ur&jd+_ z96N>xnDvA3332T$*+r;eo>h>V$A>R_$@ttel|?SQPY?#4HeLLKm`;juI&-sJUV`~X z7mDOzKJQ}U=t2{Pg&Frok>d08C_6%t<$}(1rOC;+_$e@)N6wPoSbG>QNqQL3$p*AC z@SM>0m;_Wa^HbrfMo0|>@yf#rY}b?tBYCxB8K<=lxQZlGp7%81QaO!Hf(IN$-pa|@ zX&b0{r&C~91Uyo)cMZ;dYjwRNcHA&`*dRrBg($i)DZ15WYsYMv;BIXFe6!lY$K^dM z=_1NzV3^|oB4y|D;QOD2;U7Nh?IT;9pdVHz#83Z5`QH_W9|qok)`Nc`bSD1ip@Bte zYRAg6xJex9-GZ3!j=i{-BS|fg*XDeiH)+8X1X0&^*GUwm$3{OX>QQL_jQ#SU zMF+s`XZ9t2Qoh;RvP<`88M3V(CT+3I?jyc`!1($`A}@;eX$0?)v03wHV#n+7$lRo8 zZA9y6nMH#OEcu4JEIXS`+DEK9wlz~lxte?nZ|#S>p5W`_4APkb_%#Qc;v@fIn0%>u zba`8hyY6OGIySgsO>-S@bA)eni;9vA>)CZp~H>mv}u>Jzwv6|_cZJQpO8CpwL z16B)GJyx*+>%wg~CjWZyr_d8<42J2REE~i?Fn0NB?A<8+lmXeJd0=^3OZMp=N}~x` z9HXpB0)m>qX@P=!bN}X>FUzo^W&8OpUw?}If8%QU|Ljcv`Fs8s4(@-1+!7Tmf7nQf zUetuEgjZ=kFLDY4a&)4Bg5{ocWR?L1fRQmyv87WE8RL^gl$#e;&w@KBkK*sV>%FN? zLtf9qY+UWC`h3M|JO^3J@0`aO?sYWUpRWKtR37ocK^SZXH8K784Q(`rSf`e^SmB)URKhmq>F!1bBvT4C#&3|~RKw?I@Ax*D zXB#k zyC@YTm$OpC;)ZA4q%Gd~G1QgS$vCs52e}l2e;Zftbkf!FW{u7J#6Sd|O}Qg7>MK3J z0?v_}+uwrp1N3N$tT4e(L5lPdI*oudJ{+>!Z809`A=&yb);K}A_*6=nuAqG_t}YO4 z9bY#)o2;5%bA~&11F~Wy8PFF}X9osBH3xx+6%l1+WE5rllgXuohw8$rZl{>b&oFfbyB?IZ#+%@7)e9>$(l+Ay?!s#^&jpI(9%+U`d;2^s-p0{HTb3e z`C(a!3Q9J7%q+PV(ZofVph>luqzBI1x00MY4YTf8jn}qAqKJKa#Xo*oA#MRFQDVa( zx6`X6_Tk4!ZLswB-FOn*NVKa{!L)L=n!^=+z46fQ4q(={wzi*(uXTzSes{oDUWWeeVM!kVMp{MMC-uZp`ErjE<#BhrwXId`B4E2Qw&xoH0FLhM96*z-Al z3gqH8Fdv-Wn+S9zY%mIAGEJ-H8nlpDJ}mO>2Jqwr$sx`yhPhB4TMRqQq(Nlq5h5mD zQuhLD52h%BCTkH?Ld+09MprjH@)@;2Gt>16QH-sW#sB$knjv3+@mdgsM(Qv%`wI7Z zMCz28w@k_`El&oemc7T4Mq8mS#iDacLI{-Ld8;9?76gYT!PX9+3) z(AL@x_>P<39;CMuwgNyjj4|KoY;5<_JO*1^+FHMJK+R1<`t9JA;%dflx2O?OYZ%a1 z3k2t|{{KTRl}^41Z2H=7*Hq%#K}aB>kMofj0!%HAXa<<4?I^RJ{ff&S zySj!8U_j4SEpcvEVWBrtPN)?ezGiAva3H?Rwb=M9b{tEt?F3N?dz%zo&KKz2RCFf@ z9g~5O?JfoKAgkF`n@`F2OE^^H%fp4>Z$)Qyr}wmSElXrLIP6qkBUI7vlIL}^c7iHp zA4e)s9&lubZ@P-)aIPAj@7}7#=ez|0?)Z*Xwb^J*kIaCy&A z;3t-(g8%Cm$^W}#{#S|p4^{0{F?Y~dbo`1sn%bB>iNe-5rQ~QLLaQm14WPuJjKQg; z9IsXAynzUi)ZuJ{*N-$B;QaUn;HpFW+YbyH<_}=5PBE{j%K(kYUJ;ZnfK_?{BavlV z6Bv|e8u#*9Hs#0z$MIGxL)--49gL9qru`L{@wNTa*VES&x7%lQwa1?xyN_xZ@!Jd- zJj{99jqvK1oxbeTmogY^PVI?au-BKgsOt9X2{j(FJE&I|UT&>~=;VWaL$>?$J6pc4 ztcP7hsdLtSe%R^pWaWcn<;_(9uI;#J>+xadf*sY>cl*`%s*6uSFu9j;_zmCd#GP=L zN+6j1clN2*8^Dhm)ardGtYgQIL?3ex)o`B^rOv~%Rz9w3nQ4ke`$D&-G2?OiM+;eL zrf$JWYuS7Cg%4z=?!fzUW@o2|XQwC2lMS2wAJWlu?88Ha{iM#FZcfL6>_6g6Cp;pOR(r#3U$TAoH}sP|=jp1W zbW%YCN`7FIf7FJtYF~JOhee=J^R^}RB4o70;6uY!cV$w6oKp|lMvjp?ucn)^STC_b zS-DM-P#8$fsFFV~2@OGoO?^}YREz7}Q$5b^e$9pvdHz=a%~0IPgA+N?E47utj$SNT z_)N}AWU>z?3Y=UdtyF@N3qtuI!1y|BG|ui_c?#Pr@fd&XB}{NQ&lK(5DQs%hwpU^+ z7yeF|{R+XPU1X)rs9ANmJ3;>Mw8!Qsx7;0%?ve&ZR8ez5{6R8iV35kR2%w2H~xlhWwD;6)(>jhUpM$yN9iu@FhMR8tuA;6#)Fah1x=YIuYn z%+Fny;Dyj;7}sD1=YxZqubAdjO{~z>MrasLua)+;dP{cYiq9QzLKGN`d?SmKPfJ;Q zSD@$^4S7!J{8$_ISP42a$&J|$p>@5z+7eYS!*LaV&eq_&Ky}vvzqdlo=fK((7);GZnr?_5ft3a26 ziY?q~Kfu1;oUz!Ugk?stU0H&n^-zTHgodvM@;XD|31rPov_)<4BZs1eb8`ioXDwGN zo+|2fO_$tg0(l+hPxOz<=>Jgm&Ow&GN&0tpSzWem+cvvw+g6ut+qP}nwr$&QeP?HP zXC`Jges9G2tKvjd#JTgyJM;78b!9>5ncs@gd@6NPG&ulwuf=xdFlc2iz+(HfL4VT zTQ5T7FsDaW8l;dW!R&gh>>TD+ddLtsq~QUX2+1L^LdJx|Qgliv@dO?Tm8+DvG3#%x z3C#_+!PebAa91qMC9OW^@xcbH4QYddA>gMI@VzG@Ez<-LRA{ED-c>ur4 zY<_{%%iCmS@a#}3j3PHK3d6BCHpFDbLyyYY zS9Af9;p4%0C2qfH=(H&t)Ur)0f+wo@oECdlf5_HyLocWp$Z^foxe<$RNCcAbecd6c zCM*VJVmRh;M^r2wdpY1xKWJNHf}1ayNd3d;#@-M9ZUc`LWt`;&?n9u0+dPX|(0D7Eq zN~rq~9pp|MpOjP#4jNVZL4_yl(XXbwi8-nowAnYH&C1i%zmG_Y_k`CC`rI>Vm9O9E zLX4w)o8a%%e}%}FAJjV^OtK44 z|A$M4Nf+PzB!xy$NyKKgts{bE2&QU+tbUx}SVg6aZxd0PMO?2m#IXh^cFu(J&=;K9UyFH-zwp8ku{Non?uQK|R zbTuI&$4Gl*Qjy_o8hN7@i1E_8iQ4afvuIwo>88SHSVz2Jb+a<3q3X=6UZ*7+rrt5X zK-(JXcZq(t-G6h=F=>QC=F`-Yp0x%O=YUA>>s+;XJvf;k8PVn1R57(wK##7$Tby5tMm@^2JF0YV}1z25s9oomZ9pzowGyD6o;d4d)Oqny(DAUytYF6d0v3&2;O_yp4ye+^xS=7_& zJ5-X%RF}9r=~A-dqs(1If9{8m4=Zpm<2n@tObmrh6(Ud*L|6Ul}FtEITSHko&_V3Xh4oiU5;aT>^T#B5W#IhIRgMvi*ip&!Tu(PY!^kyqP3 zkH4@mw}{rCUBTgl^Pobbm#Dr6Xx!JybRNWtT_3S*woBm*A=K{950%o8YN|(4u=qJ7 z$YYhlkK58dS(V(6?KW^XPYzDnkAzZTTcO#!jW*w`WR^3S)Lmc>s%$2*%@!FOptve! z9%by+LW}btk#9dR)EwtHEyMv;gKd}#2sr>U9~#jt>5loJ7q?0z0o;q$ua)>GHMduz zJV?dk@+foNp_&pX40S5$`dN)3WJo}oaqOjOWoA70mW!Hd(@^K#$1X9T$^Jf$6OjtC zYwCg10wc^2il$bwj3Z=#VnfktRmsgJw{g@1cU?=p0E_kA;8FQ~JGnWT?#E0R{tRdp zs*awvUyB~YVORyDEyl#!WiraP6ZeFrgflbyA=55H`5O?+T=%MsaC*|ArJU7p+R||{ z$q`QV1Vzlv-hzo%K(%F?dX96HAqG(g<%5p^z08|IW@)I4blOluZFy<9%B7-f*_iJV zd03jAVhyFs$M;84Lq>7M{}g-Tv%kb6rKL*ME}`Op(tONt7sNoMz%s7f-Y+$wDdG;m z*hUQQk{n^E?umMsqgv)t=3FQITri~BCs6R1Gj0+?*blYG&XysCZj^8{0vfK*h%WF< z5I~nMa$^^nPzXK?o(wa8{YXfdO+9g1x3Exh$S%__Oo1Il1*Kn+{LG=jG^t@MMP5nO z4XMjtQB{59m|gS-VIP*AH@m@5&iO{!?sUW(qGmIx2k%V4Ai8~!1<)6>uNOzp7h}&CWlvYwHV*Ey^POlrsCj?KFjcOVCQ1`G(q@zvvMP>OZg&<5$0MQjVq!M^*Bo{3mavYXf05u`MqK!wDkbhTm< z-KE~nkVia);g`=34^zLqzl0?#goKK0!$-x&;t*)ds$k-2$>0)ZIy{@vOC0!i#WeK~ zSxU3_rWJ*GJ+wQv^JpKOEJH4}X*4ccVmK-U9K_)m#1rnNpgmAF@QCC^&J^!4o>DNL zmNA;`dW4S#be~6z#$7AqW$q>wx#Ep|9Oa3M%>yT93VKvtdZPIxO=oYt)0>?)UJ4PV zf60i(&L`+j)Q~7!VNf<{KB7-uxXx_(|AjKYhYai;E&smK>wi$DJ%S>lr&XFkT(!ik92{i4Z>U%_wbD&MKmRaf zVIf(yC1|>B@><%O!_v5Z8RA;SE|Ml6$5wcNmQgSACblb%JkI9jlKu;<^s2s@weO zJO&JQ1xuQ+zMoG8y-9CPM>fqy;zj}}R7t35+9!lS?lieoOfj40NVIuc<2lfW<%!j` zWmxn|BSCY+fF?t|KJCs`Dt2E(|AF+w3tJ+(Z9&a_dPwC8AeJBj(pg`E_)@+az6xW5Ngs^@E27KWF_Pv*RzxiCGd9c|9 z7BHbvcWDkm9Ss2jZq?sQt?hBtlKbDA0L!Og<*nxD8m(&SoO#S4*a|j74dJdD?c9T| zCn&a=pWpGV)wn?!LsWH*oZ7J1RbeuP1%cmjoh%hp2j^ovOxQTSckZ+mZcv~M;VNIM z^p|wK!;G`aMh;iUpPHq z6C&vBU{AY-mbW3&3DGOEzd@aYo6aTYF<|7Fi{|=0iE?m-pFOnV7GW zvFUwm=}{|X4ixu95?(r|O>nN{09Qi&ePbiZE}j5rvsBysl1~J){(!6Ul1Z8PwmZ9U zv0d1C8{sA>;CbD$9q4#1!&N)@rgQow^{k5MN!PmRzsfFo9y{KgXZesb+WfHQ19q9&uQR$rUl?^ zi-e#^)Yycrpg9>Od30XF{LruWV032$pFp^CRX3?{kSvA{bmMPk5JJAuD3Cm+UQt84 zQ&T(RO!I}rCi<2<)q^3#4d92^>B~`2AB>0M_-vjR-j>XWBv9mmU(jr*nM|xOI>I&j zOSfp}%MIx{in5M^e|$l^0`UVI%1(UvrRpQi^Bm(ja0#B=z`6AN%K|Bxy1Trq@AVTe zc>f0i{71C=cLbu*4KN3R3-TiuT7 zZvb#V1rA^cFN*){P?n`)6)}C8+BSS|_!n!WH*3w_psjrD;R>PSDzRpye9^_JnQ~j~ zV2S0!Ulb4>oGtNLx1zxQGQDn!+s$ih^XT0E;DLtoS2o-i>>&Mni4PB?K;IC*N+=IZ z9UD~|Lv4#TGfvV3iNT#WqAP89&^5~Kk?M-O+^4$=b5<-5PRs<`3MYA*OwctdH$m4R z!ST?Z?mH1T1|Rwn0c!{(ocj+M)aP^`l3(sx-7N2qAU|gcI?)x|lp4+y-0IBI6xJx$ z0-MSj`5MGK`9@esUqFbqviC7aahBsniR{vV?@uQeHqKXPJsNp$A_Ye`nds-ujUUxJ z#3JJ_M29L2ni9l@w=T0YIbw$)G@1x9(2BblG9`-QR$kkL&*8q`1lqG=SWg&~?=_8W zQ33QcGg%BEwCvp%%HuL??%eFO&z`a@HJW<4jTx0v$~---rA&TSIMH*peu)lFVvCWy z6}SBv%2<>w<2Ee-Idy*8G##?1$|!@G*>Eu~o#G~j-F3h}`4c0xJ|Rs?Q>7-dvL0{b zF+2(lAsEDP(izj7u@ZwSzF+LTynvA{q?$&+ohsVm!A8N|Jcq8-Nv1?nY@@A*Yj_|` zzdlo3m|Z(jm7XJChgxNk6njgsDl^AXZejIgtumdW-3{Zxnjr~6S+2#E=H{~rCo<)m zxl;ImZANDU`m0>4B7HW8D3ZYU{ZLJjZ{8-hpI&d9nA%H<6wI41M2y5uo!ZVEj6K zncE;+dO?tW4guWKCyaPqGl!)wJ9znoDEa#I`*Nx_F}diHyP3TH>*{ zz;blRLZg4%GaXYM=V5Kp8ha4H-tEUYrcMXfWOpK&Xp6d;ggw%K5$R8|9e(760j+1YUfpejDW zvz^Nnrlx)E0(sah(s1in0Co62{K$6^p<1@JY5qhZt-fc=-k-~s=EMY6UYS{-aFJR7 zp`-;pb{u{T4QE)<4fKto#X=w8u~)6rRMygrHG1wk+;@Wr;!b7qMv{Oe+A^}=~oig(wF z4u%QzK)A}VRb>BrP4EV0giul@dK(Ul-J}V1WPsj)yr1WmOF8RdQ!~AhB(6%|$?xS< z-sAgyK}rQj@SQ_2mXLn@h9sTSy?S0{t&7~JZnkf=My?B#;WHHSj;)Zq?}Bq znnUAj2locIoT{Ftd6*vMR9MPB1{hhS)_azW`=WwE9xewP&Q=Oybpq!v|~} z5{+@J4y<{%vQQGPSN2dx&8++-UBIVxk*OCX#FxQ5FF!^%Rwjn&4;gCat$@qOSf?g^1;3XCGB?y^}gB+X`=EIJ<1d6#GnCs z!hSp8b1nYa?(X>R(dF-{53GA{u490e7sL56ip_1|DEldevaH^L1$1+1~73&m;Fzu!yY4@>wbHp? zO&I_H?Cf`&pS-})^TWKbZ*1!k)vUatn_{BS@UeaV{rH3({{6&+A`scl z1AclPc4WQLcGW!)v)87v5)>Kdf!2^$(Gq7wkXKG7!Q{(211ElnE&)QG3o ztF?M%qXJ#8vJHCYQ2caX5O13|_Hi_Bm()p+{dBEvEKoOAM)O?b#ZNjltUd?39-{;3 z1x|2LW~O%W&>xBHg9ozd&TeEhQKP0PMhR!h4!z!w- zK>|IY|M1m<=KDWq)Az}$A6m6|u8MjR@YKgrxgX5+I=8IV!xNJc+k$akD*lK(e1l+o ztC~H3x^y>JavYR&)VEhyO6y7xb?M|S$>3dihVm%d%4W5lEPR+dWOlK?%^$&8^Gy=v zme;b2iXGfaCzAyGR3Z^RP$U^7*IIyu`F+e+I3yq$B}31JZS=<-LUC8NaJWd82Ae`O zLYPMQw0*w#Q7&&(A|kX*E^kI6f}Mtm970YXub&V}QYON;|M-sc3)T^6nZ}mW`Rcvc zPD6{y;jC$~&yab7b4 z_+VhEKT;P6q*=V>Biffde!K@AdkF`h@=E50EuLoQJ|kIn-C;fnwP0}C{=}XB*)D-c z14*ucJYh{CRoQZ!L*I4IkQZ90$|_TtXz{~%r4TY3dVWnaj_fqBYtm?J8*HoE6cU-& zd;VJ9EeXxNIxp>sZ06}*_>T=$xgN*c zxLX|d4KE>*xnrczF)%)Ym&+yS$(D<9`4mc(+^Tbj=pA-Q(CE@FjWx--?#IiA@RUFh z%q<~OMJ0+%>Jc5~7EwOvk(biy3pFIUs`#{LhQ|(hkw;0wtwh+ZzUsrjAQ*k{Fq~=k z1KhwLcnxNMqO6l@31>XBErxgrg*~$*CVWL&?()nKeWv+;!u%Pau@QWSN4ufu1l8^d zdj62T@e19K^KXkZ%*)RW{+h%XCFGYCa)v9>C=gAo4|gSuYB9y{x+UB?>MZPi&VEZJ z&+$BBQsg5$VruwtHRRTU?@Lh5ZJ?4240@I<@}8Wg5~;vaKzOsZla(JDjsN7qk5pW* z7dv%&J)cay-s>p0*b%T74uWTFWwW>QLC)hf6%k~MPkm>vP*YF_^iT$5DCmDm&ERE& z&ed6Hg7)oz7z?b zU#N|1>?*l86o?s(b-mGS`qU|M)1Syz4vTB2Xw?gov%+-3CTJhAQZV)u^X)zm?pT=% zW!J!tKqmS&?H)V1vKFY%oYCXA^|d{;)M97{?YPXNGIB4!^qBTzXqwlS5fqQwk0G*o z#eov4K$$Y!Q)>biiQffO0}rcq^Ck(v34IGs0q>*wif4KUdnqp>_N{Eb*Bua9#p66? zJfjVhOE`Nn3Os{?(NAfU_AmG#0C@Q;FyHdKw=F}j*pCw4RdGoE$sX+;k{=%$kb|`vIT{wl z{VLiEbBz@eBM9&1%`@s^RTR@h%cqBP+497(rFwb7_H*Hz}`mxF6@X$Ey5yD_bI(uXx6PRb$1Uz%H3O2~pJ zabyhhZB%s3EMPug{|N9_s-G&xVH3YNn~J4!pr?g5%??eEh_T9nqL+Nx8e(0<$OOy$ zi^)My+y5Y6<2gbux*Vt=>szueK;CF0f?D+%iTqbjp~G@)$md?<_N8b_Rd;(ZHeJE$e8{| zNee6WNJ7#zs`}iROoq<`pPSCYh5+fG-j0bnt$1Va?Mp)j*W`Q3L^Hy)C-viAtIsg z`mU+S`P2vWKmQKAsHdb>Uxxvd`NT({3PDGRnEE8$%P7GAi7o1$((-iKVm#Tx@OJjp zg!2O%H%k!68NS)O4Lm&RQ6RVKyu_=7N6RY;=2%+e$pnM{T#94huwEH8>L2k! zs>F{BsU3xvLZJkO5}MH_O|^+c98cSfLQ?q}r!58;?DT9*kh zIMzJ`NcMk~F@zfw9B>4ZCeIo^t}E5b1&4ZSD9l7)1H&kC^1}#Ri3LD?7&j}-gmEL0 zt}9)20WLopcFXO+>p7(ki__-a*Fm`#~3 ze_0s`isff4=eiyvogHxyEy~oiP-%-Og zV(kux-E$0gBIF`yug`9T!$9-c3ap*tN#90?ZUzBJAh!YnF?5K_h23p-H24ikJ0BAo z)P;Y8HUj9sdW_$I0s4FOHwZeyYbPY1}uz zMhlx-TnM#}1rZj0dnVMQc1@I*qbE5ZpEg3nOU$eTFp3C}lSk(r^X>7Q%EDCupq-ce zWRqaM>{Mn8YHKbxn;Y%V1U7VagF(tp{yQ{STd-` z{5+?83S5M;AJvF`SVpWN$$4Y@s6;C)|2(PuCRkk(^nXt(|D_K6he>7n?=L+`y#S~=iX48?<2_K>@yW*!> zdl?*$_jG==KjWa7+swziUyWn>>HDvveu(a6cwuNcVB$-|a+ z+Qxb|-VrT2^FFMW?3|y;!CKHYfa$yU_QQO5o@TUhXEfcPg}u(#J^WDW_UL}3$KL*&4Is&%c%%!aJl_Q zoZv~MoxZ&(#^bs~r#r!Xck6h>+uO+GO2H=k(m9gsC)eGKJ>TDNe|2L!Q4H^-hMW;I zk1qEswOXb%_xxT@@|g#PdQtmG?lJKv@zw2#^I?@F284Tf=9M)Xf%K@1bS0d-Q=Ofr zF&wWx3YZzjeq)DHwCEBO#Mf)XBjAzcw95^ai3;pfIR;TS6%U#b2!vM-rDR$uUsQWQ z*-Y*_Xx!}?=0*<>sFIJI(ze$=B?C04i*K_-w8;?6Us-m>c_(8N&GwU38A+f3gE)pJmEO|)D63&h%(_7*x4+t@1|aaD%Rx_X zs8_07wK;&iZ{rIKrSH;JEVrOG3z!ZkG!oSaV(3XoQig8JHqE1G&NlN2heI>zFq&9n z%HtIK@pXfw!U*Q+cm7p$B`4m)Pjsq`O9cAf-is-nEYjf|em5e5G}>TRkr*ZdI4yii zeQd~nQezxTqu**@q3h23JfDxGMlUV)ukgsSL>i;4WBJ-W2X*||a9g4&MYPOKTUoiZ z2RPI&6$V;ms+Pz*YKzeU6o?1GwoH3fU;ko(-A%m++y7lOetx%PbpJkR>}?G7Y5v!} z;cp$9{P*Afp8OTIWahrxvv0$52ZzX?_+5yC(-rT<*}1-WAowG@I;m*z9u%4byNi>J z3^!lb10?Qu1E56IgsIP<&mw439p+%i;s*!m-%9W!jxDyM#x75A(m0eln*jrHB##&8ZO@i}{Qs<~Tt5T)F@6L!xsyh*OY8AeGC7m>oV z1N7Ta6%BG^LG@r9Yj}NIaGu6&Z7ApBFf4VZLltSZ zF|cySri1x%wT6>4!S>(e!baB3kt?^PTrQ&>innm#^*f8jCY2T(i%w-V=B`uW8|<#H zW0j`!&QMc_N7JsY$BCq`EHQ&Tom4v#Jk?knD*B`-;stW=Dxbzs16ZfHE{1c@M^h^< ztB)K463C%6sI?2-wA$8a6V>vqm-QsO=tjpofqHMc`t>cPYqyf-bBAnwn&edH7}mc6 zSL~zXYRY%1Z|N^fnxlRq?KO`NghC*xBB3-7+s*{Fhk8QM-runQ*|2%MAHD1}1c8dP zB`936Ok}3G7u^$)s##085Yf}%P|2ssP3p`;yiJE!b)6JH)+i21n-+|60D%Cpjkygx zu7^mWZ2kE8s1Yj+6T>Qa3pR^lM{E;fpavAKpPau64xNJ(?f@#D`IB)5$%N5YmLA=t zlww*1D38J0sPzY3=PB$lmr#+IjG2h2slH`?!ws{+g&hXhYVdquZbbg-ushda2Y-PN zKH+N>S#m+d_{{l{sRrbH7xig{BQ2Q|04(wr4g&I)4!=sel$?rtSZwt(wmc$RNEu$= z5aI+wP`!0@LDX=JbX;PAOmuA439)PLRsq5XbihMVKZZCIcm5Vx<-iXd!5=h3KV?LI z;PlgKg#c!T{EXCVM+SBOKy|^A^fv^rJ`w=hdHQbcmNpDmE93Vg zLHpfDk^Uc##BaO*c0~Jop(tl=4KEAJg@TOSsIH}#pr$Q)FteJV76G15hG!xL#&|0l zeA8FExf^S!jB5sm#%y#nyjLQ82UG$m@&SNyGl(-HMJVyLEZp9+~mYPuJDd^mE9#rEfs zmeo`)tOOLIwV_N z_+UCgYH|ZG)I@#xB?D?NX^%Rd{XzeDF77XEMG2fJiKLH&u=0Wq{0amAN7YpcaNr*= z&+8=qQ5B&jKmfWL0}!Z{vhl<;k@SFXU|VSKIHY>b(#*#Pdj2xHj*(kFQ*DayaytkB z#?e5V&Og2U%3mP^nAY!a?->n%1>ihif3#HBIE8NC@QfO+!_C?*_2t9$a>-O@yGr>5 z%fA4WBt?G|fo0&H1BdPdOK4?-wCk`vC*XgDKbDY8RL`FR8>v`jcw>EKD(4VqD}Qh1 z-fA}mIm_(&Lip#J%?#?-q{w&V$$dv2@&6HdGIj=rcHbj5{~dQ%e`R1U>$Am52in0O zJtf018-a2?KE#wjegt^b^S^Ob-*sB$jKxfA)}Gr82=y4zx!*+4uH4d$!hjxA*W>Ok zJB~7*{&;vZ-;D}NE`HslXOzzu|!?%BV*)WV;t1^!$qLVO`Xc=FYk`_7b+8PQ^PD5oGZ} z(7*ft^^AoDKin!SeEtX@O1t_(g3yZdO!&_^!5OdSy z?LOKPT_kfThNctK;oX41pyUPG(E30+Y*&JnM}VxziTzuy!-&-yeU_0` zvnzWGSEalf&kdRrWz8*(!Vt+Bl_BP$=3m}6*H5KwddM&K`U zYdYGnE&TTkbb)P%d-zGYuauXLXJNs?noLwko%l|`Axk2!r&sdx?20blXfZ|3xx+|_ za0M{YkYOtl{ZrN^NpwRM`dagpygdY=SOi55aGj4`0;Poq$3*dvgg~-He1SkSdcK6; z2L(xw|AmWeITx;2z#KSkRvwYB8qq|jr{$gT&IiouD;y zCEvxQ!*?;s{qI9j{;!=F|I;`8ch4YZWnkzcpsR0UC~RtI@%IifLrz@=oez}@aI8TA zE=J2@fyD=uHa#HCQR=qfWp%@CzC22mOoQK40*Qky@@Mi*>#^ zPo|7;a&B$_K6`I4LRl2S4zzvawd%fT4AZ6CfO)B*Sd?GXudzNE^!;U{vRV;6q|6dW z`{{I2rw^9H1pcakDq&5@Z>a$FoG^TA}+GD;+#s3xBt#Z0N?qD^WO)-|1(ScD+K;pDO9MemWYo+k*m?}O+|i2L?;MlTe3D?2w(~s z^7=dInHRp>3}w2gufZ`Gk{eCH6M{E+65&H0Sgn;knXWeuBmQP)>@E6J8R^v1X7xB>%0 z_Zk;8`@?$FyzMrA~aMF_8W& zjiLT5_yFpH2~;-R^eLW@Fdg?3+POR!YOZxEK53Y4GBzxuMK72zS)mP_>S;6(q(k`n zShzD>$W{Am=&^G@cP1@S@5VV`@Eydm zu%FWEt(K-Oh=TjXo7F@*uFQTl7ggBWMlDK!_C7-@Z*EBETV8mb zgpb~CK+r}C!`%hTRyv!p*WX%WPI@|rJuAc~TB03Kpc_;<(IMIhP+6dx-@W1fE2z2A zMB{XX3Uk#Ph1H?LQkb;Wih-HyPBrbZUUiyei1F?Tc5E*lp=%L^enwm@5g7V9TX&q8NsvVVdcS7e^%((0JZ;TZ?HdJX3m{XA11*p?P%PwEH_F)ue>=@^=ui!)b7M#!850;9k41>wl`nDs@;l975eU4jleh$7c zLiwR?VCw0Y6&6NTXkL<=?TQ7h0W$gL-1a_KrZzNo%|5ayxaRqT;fHUT>PAVN0~} zG0jRP9t)GYC4q_C9~y^!hs&Nf^j=lC(n%`WJ4J@^eD=d^)VQvna zj;@8A*+L#JoZlr&mOHfJ1)riZg=Y-wDV3LMhoD#S2D9opVy!+K4pM+%c#%M^`kR$+ zaEDTHn;-S7*^b^8E`RQU&V$==WA7thW*}O@=y*4$$sqiiL7q2ZD-nihUu=qJ2SZX&fj99PV(4eoF3rx0JZq}V|Ix~ z%i-V92Wu|NZpfi$qYT6G`=z7&z43R9;tixDpZ|F@LhsSI1(N^kJdqJ=ErG!YJ7^ro7($n-w5EXZCAbDcu{>s~KH%@|4;EvtsOQnO zLU|H+ciGy4a9iU}^xO+G4Wr1;p$XT6vfIPewX(93V%+j}_jHK-qcKOxo3VYWCkWHs zAUg8Y5#&4=Kgl7@A?P0RM0wUazfE|9oot~^;YzMU;(>ahO(n~+ly`s%Bqidq$X<{G zQxt?q2KZLQZYJq9{K;c{+%;$WlWl3V+>##QOHLmIL#sGwQnX+9I4WZ+L~?)m1zR&1 zu;OKZzMSjH9|j%j)>lNM*u_`*l%AC5oIT`{-I}Y$@UDE$M0cws6CH$8nlbZZ<^0@@ zz;gsE3sm5!nI{(Csvi4n85%Iggvzg(gboRe&4hvqKI`m_&zS+DgLZ}=n_S=KGbd%r zNDEG@4+Es3NY%LdNkb%sJpUT!C+SX_4~kA^x#1+eHJhPob$TPE+N8>)S|X*kT4!x~ zT{_9`6GM{_)!r%4M}BzF#3)lWMfmJ>qEV^^unc7nfbhYWVl~)R`4Pk=tqL*E_+==39c#J;0l+RMJFg z;T^B!pD_o6zo06-2f@N#uA1&LzQ&ibaCiWdd-Gs>Q6cdn{RLwAL{XiG{e#7ViUjik zhy7u@KVaXJ;pwRK0(btzVQ9=DT5c#=3>fGL1r7`CL9jTZz0;_{5lo4<%1T@fCsvvp zz0Rj=nWo;x>Rx-Wkc`1nmNkGhSqSKgYTy$olNq3pO=Zbrlr{`+hoOXp`~tzNQZ5k| z5wj$fIe$&mQ+Q4kP81bOYH95GwJ@VDTqqa#pi#ib@MV^|bSAZ7rLfWIs#!)|7OKQl z;>vp{MP0VYEl`wJy~xkcbOtrsSYi6I1;>T*cEz&BFs@Z&`*yrjJ8-apwCQj*jYLOz z1OZuq^%+~lv{_uB+~kC~GD@vg%12f=Saz|RQ)`7$TU*W!`1p>LX7@)lSWl@x-5=D2 zR2Lf(zA{gMpM)J12rO=e|a$;%0(tQV6tLp+GADvPYg&RAS zN_(=)=(HiLBKd0;^&kG3=N5Ge^}y9)!wo~?r$E>|612jkGodCxTCol2L244P9veRc z7}NS`hiB|umY<2aMP~$2iJSluk6zwKzxqD`fyC}#>jl6@_T9kvA7HtaUxB{x`nhkZ zp;=$m{iEQP!-1k;Uw1PQa(hb_Y(r&tP(P(uq&a#UZq0qL(%s!eK7iKkz8K(VWM>CW zMp^&FbkoL)>SE3%^CHKQ;zl?cWW#P1glml^=cM4s?_09xY)eLQ-vd@M@-iQaJB!}_ z&Tw5P8VN&HEl8|_@#_ydPV>I<&%O;sHS+&egHXCD#|8c7gXn?%n6b-6g(+e)b&&Uu z4z+(J#A+zdDXi~g2=)y+|Ks)l?;4tay8cz=|6-lg^MQ%+Qi4K2>(x{ef&&3ch&hVY z0O-fZ2=-z$M5wW;zy~NV`+n?x4D5MVpQJvQfeuR93dW6fFJ zq=WFm`kW!)Cbza^A&A6C8dd_z@X9udzVZ;2WX+j^99E>wBhxjtoz;xmF14K+l&1lT zAdSP(r7hA#wEk^6%KZ|7(&o+uXr@xe!6<3=V-o#CHu{amTT8oL$~!uK4AVuCk3y}2ajf;vGQLf z3EEWqMxxbo_MRrBiCW|FC2yL6E}QjIUc!2i@a7CI5g3JCBA3AQcY#Q7CJ^+25NOnv zcbb)|>y>5_k=Ur}x}Cie0`u;3HDt!(Pc1j+cvS%nb6m894s~YsJT_vw)Y{2bu&SHO z$bngDFK&JXBS>1-lqDR0t8I(y>r+_q7LX*%hViO$0W%=1N|Jjlk(_DEb{Rp?h(E1! zAudak``W+t&QN|{*nk3GcssT_Z-gRx|z;pdaT$LcGOMGkz}czT><(HO{HdWcY6CvAk<2nh+( zVtyepXcSKhxKHlZmQ#;1&_IRBJFsE}-VEAoI7_vqjxq99F4v37aXj-laOj3kYizFH z`%lqEe_DD|z6j>IIgUAM4O5L9j@IbDn&lr;%=-RpLAXa2Ppy z(phOTSrIuir&kXFijA%m!^|ss%?RYiEV>g!u#274y5CbER4W`-8VME*dW3V6SC*2K z?M4*Rf=qbhSC5E4So?uAif&gd3r(EFHd@0kOHZB2^?)tnsn8WdkZkokYH48)wbv%@ z52u`UA6k0Ed{$iQ{VzFCt{ZGa9LqP+c7ZX9d~A2Yx4Cn-;;b$Ibm~x1;gvVBU!nqh zf!B1m2H-datwU$>=RyESHksv+_wv)Z6sTgqQ&9Te#N~@Q>$d*EA?dHHX$OOMnfUv0 zJN&+y{?n`Xe|n!RYN%@EAWR$pWLPnFixn8=v;=|Nb$ zt#syV_B5E2Y))!1o&rW;uA81mU{;Zh9SZ;ELg%cCx4gOtvtmEBW}j()IpXnlxsdgP zIwwX5`iNe1?+j{k&zBud)!i=)o%)wPW0>_##%$M;jcOl^DrPUzFJ(00MC{ZFrMeHB znwjcTOAzL%=(86slG*_4Q7JU~{Ic1E9Vmg)Wy*@^Aj-W$ zV|(L(H@TA#I;6@m@15L}7ft;>Xs#*naFLi`L7nO|c84!Kd z6K?B?JIps`^q6d5T^nxNySGJp%4-j!P)NfZ>Jp%CSd@t-059Ed-I?T*Ws57wHKnW4 z^su2y!nw4TR@YQktm_x(keNs;XSO-7`s(-mCoKw;#sTY}1p{t$=B?m?;Y4lJFa%k! zhoukZ9=Ks;7`@OG3||{+5>AQFS`w=NA7k&po!Pc+4W}v<+qUhBlZtKIwr$(CZM$OI zwrwY0_QAbp?|b&w-nO2$e!w%V0XkCB~ zj%WCCj^{lXNDdMYScE$)JTPTc-Zae!19!odn04n^A)hCE$Sed^Jg&MvW)a)(gEsgj za(R^d804RkK1OapGd0(iknL$4@c<~$%8uKG*7QDVIpP8mUOwG75c!f9+QwOqKMS9{ zNk8AdCiX|4=pcj0o#HWBAhrJ~H(Q8#!FnDaC7olcpc*D1REKMq_l8AQ>R z^Id(KzcJ_Jh&&*>SjMd9%TS)(v-&`G_K?j;&Pw%q7i^0M8fQjamIupHj(YuNtV=j- zKba{g000=`->Mh9I(7#CG5q55d^=kvpWr%Vazdcle=8D*d3-5ljltCFLYI3?J8J|#cb2lSBL)g+#mwA@{{#6ewU54GHDl>)p7V*l8$(BgpN$8 zBrE# z)j2Vc^xVM44|SdLUG3KRQG-ydpb^JqYDe>$(4QUQt9dY?0oYJsMy)KVRn002e(wVr z9h_urC3d2Y-Q@e=cPMJ}UQ=x54)J1=g?UmHawIHg;RY45Ex$IK)a6QcBxu;8mgyh3 zxb)A5XB=`%c3FeOnXlB{Vssie?1ZD5oEc~Y^3Hiai( zoi24=W@k{tndr5ulge9(>HGe4LPwMX%70cp@B}W`aiT)iuryUvC*lkOcN|Eq7YedX z2@gzMV$uWcQ{Vq>+-+M+oT&$y5bYfAv0SdPlv)!ikk-=ik*W-GraO&Z7gXHm7cIR) zPVLH1ZDIm7?4nkS3E^0iu7jKkpUzpDW7cU&uHx|6o&IqU(byg%q z$Mh;~QVSedos zldshTL?qkt(p1?A@OenmlK`wlN9W*b?A21BJh7}pNELegiISE+2xOl`5YXyse0jRt zmN?H+EHBwktD^k)1KFUqIyno>T*q*MmxXh8`l}!7wNj2R_iDg`y(=$$JQm^sdzzh8 zx3(rNrQH{OC`5El?KuK{Kgb-=OUyd#sjy#`D2dFaEVhYkxpePbbR%bPFy36wwk4J# zUcE$Ld{{v?@L6w~hmYMTjf`pJ*Bg}aA?vjslMo-Zsxkpo{&%+OM0!@eg1B0+)9OsE zhn{th_#KUPwX2BsZ=X^|E|_IE%FKg$88^*7+&HaA&ys5e-kSTNPbgcJPe4cSnBM_E zdx*cdK(9)G4|l*vRO!SG`Bz|XlO>>WZ7!<>IhT%9SVgL3*_MR~md@-D z7Jzn^ynjCdLEtEoPM^IxvCBL)EV-hOY3CKxH1J7}S?$x{Ylui*r+G%MixTSuTzmR< zr*O@Ql~$H@b0?Si@$;n31SVN#zYzV}oJ;(}4DT;j)1$g`)=j8a7yrg}8@+*-# z0Z*?#2(u|+EF(!$(QUp#X}Fs4bL&_{9=U&D^Hxw9yKlG2;nZ_EIU;{R*xSbVw-2vx z>KZ-UARUi#&LruPVDA)iJ)~<;BFF5>oEgTR(#Q6z+uXBrz6X`iT-|1UP+T1=S*I0l zMF~H-sXwVgsL&edbVVNUcqL&^CuIuiHWcswV!Wab5BB`o$%c;mxh}{^n;eXeQZp2^ z%Leg!Gjklgvj*{mw(FW!bPcU~WtZFD_wfRO?;7QCl@xwxfqY-39}i=_iQI`q0-r4? zTo}`Bs^%v(vQ!A6F{TdPXHK*E(!S$_AXN{#s5Wxj7_m_(wsDW=T!Pr8ML!4msW8`a z-x<8a&3{gRPc}Tq)gLeLj=L!ILvFpbi=z4kUzM>vn9C~(j?Q_ZXc}Q2$j)%i9VOyC z=C=U~2b^Ob9F*XxWl!Hv2niT)3IzANq^F&I?;-s6rW8AN;MgLC>%V#y-_Oa7r zr*>1%NwYl||4%NHH-fg6tR?!7vFuOY>?E``kl2-udedDZf?}qUZAD`&2>rNx#VPt7 z8TPNAFQcFVeZ=npPhfM}NjLpPDBa-4K~BOd-ljIbkyRZGC+l}dS5KhzN|6$3 z3YC!?_Ug&BuLg=M6|D#4K{4M+JjAX#15KpEd(y$fDGMt?`h;{7I=@2Bg8J;^1q%L> za+!&va8*kkTm%=qiXgNG5~YoFhF%6u4+^1#2B6M1kX5C|&+z@in?tTQqx3UgPzVc7Veo-7&~f}WN5mT= zOIFGrfbIA7W#qGRDUx0p>eyHwJ8V!7@Zr=uGFefapAFFX;!Ucls7C_r(OPR~;xEYg zSX6{9;#8VSeRHZvC1h^U+j=UmH2mm8atrMA(pG1p)P}3uNU#F!r)y77Ixkccw~IkC zul(~&C4{u=kV8Z0+~B;BZEILXs+n@8V(86)Ua% z0E*gG?y2=9vEk?!#k9Gj760gP3!Dz~Rmwab34CHYroi8y{ahsN<#)?D6y;l|$I?VF zYo*3)Wz(Q3`?Z)vS24>%8LH{9qqrLLBG zFJ4Mz5SGdlln1=eb!5fCZvixjf@r>A8kvnkm-$A(ABsIE{AzD7*|_n#NEo*QEj68Y zR+xm*)7-VX7cjkA>)~?_a2u5{@I$~Llzqr?JUqh}3xop1Bphg&$X*wXZx1RM>OqDV&@e|k@&+P4jRc9DI2wBHDV*a!C+Bx+s781^sO2pLe05~JgYT7*E5WK`Kj}y!chnXp3 zkCTTe-ru2TgYmNZJmEngOSjoHY2jq-g2vNqtRRdv;E zcVMK?{u@+F2c?GwpY68FbCaq+I8udXl2pA(+=3k1QQ4beyJp!8){53fl_q}+*FF5M zVlK2v_O%LPjePb*;VzkSjdzde8TuX9?T|FP4*OZCe7qCIqwAwC0u&t~BolMmwQ_ry zz)^z=@8J^T)vY z)e^Upez0 zo6M!zBI@0VzM-1J>(zIjvCuZA;*4>iT!~7)C|uq!M46aE(k0rspUieow^@CsN6%3#%TmfT4Jw|Gll55k$Xys>{}z7|K(F?q4}=pNo4H zZ-W!2OuFk$;X}rpYm->n+@Gu!}dtNNxoxPS|?}6Qq=Q%nm-n;_%0bByVlPoDzf=*Xc*lEzB2_kNRQ@t zwWxh6QbdjFs{=gx$qwQd!#!YzHLY8JM_IqE(Y*-()XsJu?Xx#*>S%nxc$=D$s*$>$ z%9#o@t~t&%?m7N#oCm)bWikS@d!CEFq`ry^(5*)RTu_E#<)@&6RFdupdEa}TbWTTCY@8PfT^ZJq^M8SEwONJ z$o?Re)e!SiUbH>NaiB`rFXetC67IfCcRM8R&CA#WQK2x(a+~0KJ-cLgyR*6kU*Gc8N3D<$bf7zD% zoN3&7*$eZAN|eYQ<(JD!n2H1bTI(Xi2yzS8BldjMKl;4n)B6m%8=UOiAC$-SChSA< zlcM;S3Z+|}ff~gsYi$O_D%g~pMN zso;W*DsXltrABpVS|;YucKO9r==42h5zR!M89Yoa^P;55E65Q}^X3pldDGUphHfL` zDIuvuHT=b_D+pnQ_!$&^(tf^c(QpjBinb`<8pu)8Oze~>Et=RU{30m6dRvVZ%8U~_ zQAGRq1=m1>#;X&Kw&m?dkr^s+rKcOmmvrX*BZQU4YsXi|q-%3*IXki+Lj};`MF>*h zao|rZTI=~EoMY>4?L`r(IABC}s&4u%5MbokDlKE0RQE57Pi)ZSF3|#mJ>3(bBW5@>3lyvax zzCVl_-T4%o$?K%W*yAr*>eVxSUx`xY*>(ewuD4NRn1u*J-c*@?T4{OXNl4Kc*yX8y78*yrJ3iLOtNo-&Wbu@1?Q0|Fdw#AT#Tjq zb#=t9qB*bb&`l`JrY=hHyd|s9*p9{$dZt_WxR(@bDm|0=o1iV^k}~luQ^DfHJe(tV zItHp^&qWSOmm5Zd7FF6+T`LGV&+20p8FRO;rjCakySF;(G~>55{n`^8XQ0V27aG+q zA42z422~3I%dr%Vmdp=2;t-dwPiXVDuJ-rT6iDI8s{tfTk?u zt%f=FGwzA#J zq1O*{q`fq@DJNZUg3QxddUSz?1Y3}Lg*K?8rV;-jS2A@}Z-?_2!|n1S*?EhrXbq2F zT{p1mt@KN&sHjx0UD>n275(+0@X!2IvyH=Kln%iMTVRVlJ7!&xZAfO>gX^T)B)xUf zd%C@>pwCp+u__2kRq+ z>S6}3Ni){@k#Ty#6Du4SN*i*Ca}m_Ebtzg2ze(74MF8LsDn|q<_)XRo*?JIQ>qz|Jpyqr)b6~$)Ql*+xB|K~gT9heu|E?fYsTXRC^kK$PM`KG3qLqtm zRwF^owPQ(c062{-%!@8YCh~S zZd>ZS@C#-YG>;~9I#`FJks)(8eEG*NgWz{Z*x!;O{0SWmfb)~e9?F6}q!aZb#d#u? z-j`@UTD!klOg30s#)P9xLWL_L;v}vWu}UY+EY#}oVx>UMRaJYWUvTjlIU=0I`M)6M za7h%mf!WZkdmd!+?01~(?TADMBbMATf=i1PY8l@I{~%=#wV4GxIdiSZS3VWe5y}g{ zrywFx*VaRe{q2d);;qXuDr)PUooUC=lx(mA7uZOA8Vf}dgg&|skEh<$pxCPV-QSwi zJ3Va-V?0Q~-m9Sp^OGh4$#6#XFd11kbZ(L}N@naVH_Q_6dgK}TpPk_Z zoWTs7QWfUj(BZqX^mPRog`s$((rI=hDR*}k%_wb*S$ZEItW z7PS4#DO$=26>2Mkb60S&_2}_m)>>x{=MwN23L@ygk1CfTkGwDhIP$N{9*>Hu#4!%n zXtchrmChc@W2t@8orLja_Pp4|!JY1tF$f~L%={*~3bU0TvCTh(CKtr&9h;h7);}_7 zG)guw8h&IoJaP#SOB`ZTuKcj&0&(CnhaLCE;-NKG}nd zW&I~a#s(y%Ve@sQ6W!P_fO}R2fcNg-r@wg_y}}@;deN^}X+wSl$VRS5MHK|u88>;u zVA$-CJEagpe1$Eq-M6mkh}{7%!KbfKsOgI*vmOErk%dW*s z*7h^Lw;L(QZwnW?P$A2pLa;-TC==Wuh(6_MiC~2rl9pCMy+Rbb>4s0~hE8x=rVtVK z8C0GuAUP+zhq7QufID!}6WwD<0LMOx;V2iT^~h+$ccXE7XQkN5q7U;fK-%IifFGwh z&_2L5wd{ODsYYm|qg}C|h7k!*vn^wT!=KEJ)Jggx|EyqbfBf|?rnL%Le;3QIyUOj? zCG&3xzyEtzq5CJiO^99nx=q3c8s&K7hT`w2T*LYit0Kb?@(Ryw1>iY*TX)q{AjjyM zNVrp0iT`6J)~{qcdK7)~llDu|>P8W(EQ zPu6eJ@4pkiQ?4s(6FFqP%@AW!J`v+dQvdnw3@Z$ma!S~<4j{}P3r!m=F@}O5LqQ*4 zL3mNeFZn+mrGH#X04I6Hg}?5Xc7Jc6`tvNae^UZ~GIWT@{m}$~&Kv0O1AtFTx|7MT zW32^0jS3G$P~07#S!_7t->pMDmaCfp0`sE8;bKizK11H|nn$SxqX%ijo!_?kxbeD? z`h0WWO9DV*LlB7_KA5eI4-6G|}a}Dda>MiY zWMV{-Hg0C@#Bd4sCJA?+QBidtrsZo$RpI!uftD5+FADZ)?gd(-z;@dx_9D*O-hDP? zDkNXI`5fiNGc?^^T|k%w;jLI1?<2jOS5<@k4YJi7D$QzVURW+3MuT44w!gm8bQBi( z%T(D`Y$%z!VoY=o5)!MrLd+mzZ{MebG7pac+@icXh=k?cWQR8DhmQ4N6NjDzV>~@~ z>Trw~MVgLPUCt$1q2r?MbM4S^tF;MCp3~t-%a@(1YVRen(XiRo7>_v@2tS{=J=S8L zym7cAJH&4|tB9Urv9RdE?N(BOC6}BNHPF)ltz2RUB$K&Cyew%)9AbTM56eDCr+vBO zj5SbUG(Qzrh%$}8#N)H=ZkZ?eSGty_W58fF$+H?r%1x!=eNU(!cWha*hyuRe9oBIU zjW|Z&KHC%pzLWn2G;IsLJ#@5v80E@QWTDW8Uq_)5yWL}+wt zC9|=kQq}x+3ncg4+!6%-(P^e(dFr3gKWW}<*fa2k>%mC-DFVl zP4Q3g(eWn))PtM(VPV2c#rcT2Q_Wd3dsp1u-0AtV*Ix*Hajn>}U4k{KctMtIU5d0{ zemVvvv*}i)qPscr{&1Kr((}+|*mUB1r^1H=yT`zc^@LqnyWCGed z-D$T$A1hZHJyrFytQ*fb?qqnN4I^D-R9!c-^WChu(6uV(Rz=XQGkonrq8prNR~#a3 zOhFt+OzFgtNn2ypjlan?oAf6FIj#2jt?a7)qX0%UQphMr^q~7$vnT2Oo-o#rJrHpc zb`p6MeiVU;Pi2+8N!w!{wu;>(>cfrYTyN&XY-8%iwH!`t22EAiUcV{z zqW~LiXt5jQEIDwc+u+kjB7v?4C zololvQ(h`ys$VMIINrFL9-z*gGgUhfhIgtKpn*yf-Kh0S(@LOHi&^_ zY;qOgYc?>mETSGQ+Q5bmhm2!71044{FU#NQe}v_uDdSWBl|lDRp7G-SIwV~{{(qO^ zf4HyyeM(AN=^Mxz82$_A$bSi&(qDZ>Wc~~F>gC0t!OX@y5y2?+Nc}UV%Av~If?Da( znTguMxF~a4du2-L(`fh0IQz{c9aG3O_jt>85ALt$7fwH)uWxUa0Fr881rbsBqO<+2 z!Q^xuSi?=!W2_Z=8Z7SuY_J%59Qb-9z=mn{^9k2=I_nHMe5;VRfh?U z$SNrc(puR^r{JW@>3+<5&S3N2wl_6QoFh4`<4So=%*g9WG&s~$vH4vDUeJnvQg3R^ zZ$q)pLP>BQ5RGt5jXjyR4Y1vQ+BaC{PB^xTbX+^L-#lledH#NO{7!z%FF&_rA<2Sl zCFDWwP)nuVL6=uha}jSSZxuQ1$k&-%NW*FN0M?6PtKw}yi+NxU&7pH)Z2`L~5>MzueKV-Sf#tsI z0wte=M~O)z%-TBdPIlDOgNF$p(y?2JRi;^>>1}S+_uc@(R$sGIL)PUX!`oOM`%oB? z8LrWy$0o6Y*j|mmoF^PW_&YQ91?To!9W|Oh(wQJc%z`e%_>ZD*kRij_3ew>asL-z) zsBFVCh%twx6$eA~wHXLmzc*%b0SAxP_u1uGB&}M$|HhS!#xlKujEm6uK@y@m#Yuz# zdyNo>1GjVt@w$Q8Px+D#h$NpNS1$ODACx)~cxqJR3Rq<9fo@d#j9knqBX)E)4d}sNmO}n!6!NK_;iZ)c16n^**+?W6(TJhvFz*yBq%Bx{=2_C^ z29v|-ze(_f9uuU6%bl|t?Cy4|=Nhst&2))tfGi+#*YJohMsh1}8F;f+wg}JnMGwzX z^F;ao;_2ZJ7A;1k54r998ln2x2`X9@Z<(~`j%VD5| zp@8@S4VK~${T*pau{3ce72@2O<^evx5Ko4du;g3meGfWuko^%Y!eQ=}*&862F^)bJ zjyl(Cp85rYxUR5Z`rfd9MTgPD^6TpB+3MT#qE#1QbA%NMtzVR^a{n%3;q~B}{cmEKv%tIk zIC|ey!gn1(NHBo34a(xRh~ke4&IQUW)g+{L9p_=32(W;s^eE?UQbD`{&rpnpR}-z0XzZ0JAr&rhS;e3c!P;wt5-x*QTT z>KP3)>|l2HKmE${YwKMFV^}^2bnQ!Pc=w2zZL#0~^{%M^Id4wZ&;eN4@;qi22;rle zahn8LcRZGj-s?Q8=pfgFoDf32?;P}<-gSii=jUd;69Fduz6QI_!gztf)GuPDh6>YG zw=*jWHqUlhSH|S^dC=8-1}X%o(K;s0g?{wZQ!Un}AC2K*&Kf<9;aD7T*Fc@|SLy>w zPpLZBU5By*__e-!KU5NJ5SsK!owWu)nkj}jdAQCO9e`2h0T95|@;q7+9)qd+Kd>5>z*dh0rHNy3z zPc~-Ht-Djiod}LHlKSnL6NGvYOzgf*GzJ2k@6CKwNOPK?PRte7RP9ZNW0{n%*H@&e zL-`6`vsM-S>2sa+>W$*Q^(;3Jpvt2Gk|1YK!e!fP9Q;t|nf18~FM$xG2OZc$3(ZBOB)ghPhKN2=j_jmBnk&yy)Jp>6z8A6`# zA;51T{!nE(SlQC5>5YQeqX{7pIKPAFH;|Q4HAqv&*4VW!(%lT{ak8u~0U~3haD6LK z8}*iSq#Xtw-(#YoE}+qtnD|U1soyP?gaggnxVEI!>%PVxE(@XgVY(>O(yyB zm@t?5$KJ*%HR5HW6v0tz6kKjH5UEaeA6E3_sS~ImV0e(f3 zZ|^*&M?8rvOT;h>JFIXlmo>aZ8r@_c3bZH zeNzQm4MO~@Lg(wUq-0;}POz9I4^%@y29sKgQ+#UPs`iC=D3Hpe(IipqCaj-u>_(^) zkCMC#dG%80fn@rASMxr@@6=2Ar%w|aA;!fgkOAvIZy}u#Aqr2UL+N}oQJpvW_LWya zcN?M4cWn{|WgbZP_#aaCAAMRIoD?D~Ut9Ir-}f~BIe6mV0P0UPgPPr};un0w=p)DS+!4&+eHv6p+wUVz6o>AfsrO2t7kZO^O7%e z)7LR)>$uXsyl86M)SQU?c)Cs61SsFW<@oaa^8qK>DF>9)Q@FGTF+p7#(IpLnG?$*i zFuCV%?{Y|PAwVsnN-kVlI{rl8Tw6E&Ss|F@(|`)O-NM5d{ZY6Y7xWcPaK5t5##h>` zvO+BL=o*_d)u^!?0?a;hd4m-c9neV9TsQ8awhWdG{&?oSExLO9de8~_)i^Td789j;Kx?GbHCpe<=(#+f#30n1 zgL!s$_1mSHwz4bsmBL=&!>`DU(gxz*f=_0;-sj=pr1e<0kr-DRDhRy5_AV*xSY-?O zg$JuVd#Qlzup<7gJ}9_sy4z5JYe`H^Mp#!mt9GxDMxkC_aSKO3!6&gCg35gDJkTtK zw_!zSGSjwi^gEGW8dt00iE*_q*!oPk*xy}fD&f-9+S+{bSJ{=`27fp-$Wb{yx?lcU zS1PE_P|cb2;lT+-OP8|}UxB*6B9zlUfj;^~RB0m94b(4me;`{Tm@%vo{(MGlh!#Yt zigt8G6u42qU6|Nn+WE?6Bw@YDAZigtnj|@68jHj!%~iPAZIs zTc@WcwCBbZW6|aqFn^|#MWMzfc)tQ#xzt-h>LImWtKt#Hd%$QGUOD3@BOpuYKXoP` zbBMx^w@MME(nqe>(~pXIMJoZJwfqQ0*i6jg{d||fWDr7o&JQ9w`EJb9E78NgXM*4v z_Oq4=+X;@fJ)~)x0Lr?gQz)pg?Pu!NVs*~g2tE0xC-KZFvjb{B1x+?6 z^lD{s&@G)~%F5t_LCZ=P=%p+akOVc?CWdf)hGh(X)_je>wDLbP3m=U=b(jWTf)%`I zrIat(zlgUp1)Kfcc>2K$QaMCmzulk#(gE`Vss_Ey@!bcf{VxqQ?7SaHY+rvt^w&}H zH%T~u$FBUBt*JnET=F{|YWfWYXm0VEczbd&2=lc*0G%-Hv^gViPq*5#MRl`z%{tSK za;7-PSBz%-^(iFBCpXu(^jt-L^i`L$y_!+!2YKV2kg3HS8cnn% zD85QEKC}kZnIIv+KyjgG-<+Kh>9v8loGA}3Na2#!k=<@@Gm}L-Is{J9UTLQUY*sxQ zS#sJ5J4#)hbTLbQD^m_zS9nh~Gp@EK_6(Om3bOBA-O_45GbKR&-nnmO2bJ$(zY(Bg%5 z39M7N9I*bV8cOlVKav8tb7iar^Mev-7A%)APB^<}U&4rC1Q+o^f|1T|xl{9Y(Ea5i zvWzR96FZi+BwGFQmuO!t3Fx9fqJ1^KD)To6&HondE9YQsZEj-luYBu>=*h2WU)X`J zpb+DQ64ynoi)cjd~)QPN~QMCQclzNq&H&$t6XhDq_vd%!pU)51-EpwuB za@%CXS?#89#wUxj&QA8a%)XM|^mO^Q)K6C(j$bdKc(l=^9>GduGKAqb$Kz{9Lw+&$1g zujBb^d}a5qzlq@MRHgr$b^PCwV84h#hJP;Lj94|XURp$Nsm5dbiPiSW)pnoIa?5pM zb3I%>ocxnMx~#gNV`D3G^+<0jTwq*JfNwIo@U=lcFs68$PPW1mtUjKe9bN#x;2vaQ zgU$)h2z5|+#<&H&sr99!Nkx<4i9%}m1$KV$tkGanAR~r_7V3)Blq<_19txe#<>~O5 ze+Acp!b=>${+LC+A5yqmB5IFls~pm&G}LSIV@+_&5chv%{1x4VdWq)2u0IA9UKGqv zNK5_x+j2u_1JFj2b&!Uce!VABg|C`L{j47ck?~W$WP_`WiNEDqZROS))kbE!(o?bF zF#qWZ@Jo8pW&gsr=&f_2a9aY_A-9+6~-2~kf+4DGyIIF_@ z;HywH;ipTg$HFl?zqumuU?$ER)0^~!nwCNTLRJh|45UBpp<9w(^H0B7_)ARlIglNK z-&ZaFn4-Y<-)s4Io6)}=%Kyiq`L7*mC>??(I3&Z|sFSVQr#_dBz3QLiV?3HpHA_ z>!?-wBf4t4Ko=|@d(hNtT<83`|=}yL&qgrTAGOgm|)L0BETxc#@ z7Nk^VD(#2^PM_j+b(T5~p)6U@##@Cs6NkD~Io3blaSJ|E!qA<71D9LD8>lAq5pCbA z7}%#1SjF)styyFVAlp7COwF~!JF%kuc?d+tew9Xmeg#TM4*B#`VD#5T_F> z5{Jj{Jy0w`#QZQcmh9L4R>yeQ$t=wz#KN6V*;lx^5T+bz^ZXxV#UBV5pN8%+@T%YppC$g_hNm~xq9Y;0AmP(c)L?=cny69rM`qxdWy*-(+%)TK4C>ER z*F3j6L2(RzYnPq9K;@YPRGNVd$bDsTqbgJxWV~=$ZI(PYlq)%NPG(Q;U^nS6 z4KQfX4ml1qc4Qr8STaC77P!Oty_VH(U(w#!+$!YQ%Ua$TSuU-apf)uWR`vn?i%Opedn z=GbJG-9)sDo0kiaH#lNlwD&$^dA<%4lo=#^M(vkFcczrLRic%zRjgI06{k#vm`Q}p zN_Hc^UBo5shWJ2^>3Fz0a`j%;kLPB{3JjQ4%yoyO)hu)?kXgfCkZSY5(hr1w!;ArK zL$qIe_6Bg!cs1v;ZKOGhpD^BLLFOUGYw?<($^l7D@j14s6!M0fg440HG@*?~u8p;F zp+@mQl?@vv4}klyVHdD$9H)8Pc9M=d)j}s|{-p>hV1@}gjU2KWOEzN?-m4K&)J90% zwjlq|9fDy<^HoMW3;}I2?UQL$am@^X75;WHf+jo5%?2y?+W>z z7kr1LV!0?~dd&2}4B8ml6x!r~F^EZsQHW`faWDi@i-WD!yNpl&?9P(o50mb9Vi`Go z$zH^U0)Pv{=sNCnR(+VUIO^Z{HphQKAfm2SC7oYdI6eH|Hd_C$QT`v%JO4aPJCwxa zFcnZaIcL{<^WvhDVd;{igY^wTarbi2@M8eb5kYyy#pjYI#Ep!N4T(89!BCmFc3u?> zTBZXO3n=S`d*sRF>qRwMtS?u%wG-SZh%#5#XMfX%078&8KR%pavTwE=dTySNT7AA< zA$U`}%lcsn3(?B>!Ok!(LD!{@=c7u5#1LtU3q_akG|+^B;HHkkwPSWuz|Zj+4%oXA`sdXk!0&aM zk3?7N42_`@1RUE1dZ=?7*%;1B|141>v0p`;{K!8OPj&$t=Tl){r%SP~RiW6l$Ogar!f-j{X2xr)-tfbr|s_{qStWVF7K#C1KcL^BYf>sxE4^v-^V0X1O zRi1o4Eyre_MO!CZ!Yq?P2kWt=HdBHBDWtcrOfj8|p*VpyfY^3hXdqx6Fp5UAXPZct zKLyNy)_YNR=f1IJc@~zK$6~&B>$-wk5R)R(CZ84tyI#WsF0?qI;AV^07(=5B{Vg-z zTDt*2pai-ywf>MS`BLlM7bGC-40V?E=I8UEP(OWB6Y7}N7T`s1!wqIeyYSQee^V0&HJf)}QPnX1Tg zYf8u#6fws7O@N7xoC`Lh$H%3Bf~Mm)zNRyB?yT?J+sMHO*hvZ^oJAxOMhoxO*Z13e zfFTJW#!@ZPLdo0)os>+w%f1+uKL&^LNt(d{v(tW_C7rfGC`a}RMnW&$g= z0k0qUzK9dV9rVmzwCQ%z)H?JDJe5q#;gb=Jgd@c6L3lPr6-)SsN<4}bLNk%kkz=Zb zcMuuHQvKAPiaIy*MA#d3Iz#S>w(L5QBF6}>2g~ZkLSFU3%-q|!2jgs zP*E1C*vx(ql4r0TpY-K9QOE$<2Z-2Hx`F^5MT{aI)I*m{}kWptMh?{q=;XoY0^1sM_=;>J|*xg9w$f zdOqlSo#?vu@m74{Q+_NzRh$h4eIaFIqY(9V4kNJv$v^8lJuLSERdx zZ!_{WAr>;dAHV+id4|`;^IjaGrQ^CKTuQd0BZsjbpj4jd@dHBUq#M!P-N?sU@fGkk z$%w_Q@OFw$F5HJsmV3!!Z-)n$47hMvbjZm1Nr;(BYsmPO4thJkwZA%t5phPjiHWtA z#Ksb1(g1L&cWI3%TrY51+!{B#p!Ag@1uKZbYTd!T9?^ud!CP6R4|+JM+2{jl>w|4A z+k#uFmHE1=0AVba;$DgyY+ZC=w6SN^=y2P~(yo1Qa7%48(RY_$Dwfgpe4xI{JV@1f zWxhk0-go^O*+|O85afL!8`dvmL;L@TZ2pW^#jFtM5Cfv(>P`6Y3Vu1)|8n6AU~_O0 zk%*U=6ZJt1RdwhcNvZKAFya^kEa47Af`{+~;0Cwxut5oyV4q39Twz;$RA%V%{Con_ z#*k7i-qk^fN_D1`o@~tbb{?Joc?z+RR9>a5Ro8h<-lR-K@k;dX=lRf$n8`p(`{LO8 z=}bAB&ienO>@9=hY_oM?LU4DN;10nZg1fuByE_DTcXxMp2sG~Q?(PmDU%zwa-Fwf} znf=wNr|AAot$yx%t#w&AhIy=X#kN{w{Z#D2%G}i0$lw@3<;IK4igi-14AG7E!JPz4 zwA8URvQN2lailpv&7OazNsVM;tgqEJDJ1nN4DA55QazR2iLQ{pi7khGdxNT$(gYaS z9|Z1ES;#Rb=`;LG*LN1%+Zky^6;;m1-t)_=sFJhFh$=~n+0a|%VUt$B5+%qfaxc;K zA`aemmy-&fC9ff|^N;1aQ%Jg9>KbV4EZ){%N3VMp|s(w z!F8duVY;x~sp|0C@Z34=$!=g0dy0A~EJ9UtDWj+7H`I0B5pJW$xN7caa840|&-Wly z&i!oK!nKGlsFzNV^fpT>Lb&l{z?O9kqdU5`06s^7|3=;%XWO^gGEf)OMw!I{u1~lRotCWGEj(ma4zGf(VOYEk8mfJX{$YkoJS%t z$IjIAXu@J`y7n0nWTamf?4u4lpc=`V`NoZz>#M6wA?@jxQ=(JkQ^wP9WnQ6*!~{Kx zURmGl53X022gv&Zk`2(nU%H$sV%lpmi2_Z;Tu0H*3*WPG?9|&(Dpw#{&o+=9YcRfE z6`nAPt5}y7H^VQtROdWhr01;8S8V1u>~eJZF&wMRcI{8%1=jU-ZXnqbZkJDdW5im& z{uI4N%uKIEcLIka@Ik6ZaN~t!*E@_XO=k1k%^2jSyKI?dgJ)NJ;cplURzWFwOvUl2 z;~oAkCFmM~nDKlFz5o4dBb;lf_4qel@9-Ze-Y+yD!nX*&phjrQuoU5FVyH#YhS7%6 zDbOg;3nP>w<|7yfIgoo3yag7(rmpH@jE23s~7Z1^K6ND=)FVaOzm5*NmbPtGv<%UCyy?5EUX%TlpRxuRCJ z7G^%RIaG$cg@D%c+EVqqVXM=doV%poa}T>6Ibh~1FNM>#&$I9IyYKe3*4CugACE`0 zpNB8qkaTV9fi(89SXVCleBPUrtL!{&got6Qfrd|UA$FcSEy9FPJIHuwfL4|Z`}@~2 zte)y!Zi0m!TZc17MjrZ*YtI|79JI#Lf!)%5tO`L#;l;)irhE6y4Ii zh3%$5nDq$(ApsXPT7w|w!p>-8J!7{GB=QM!(jJn8sBkwg1)10ZMVO4vi39ub9P>4n z{qmGdCeb1BX6vY;sI7YCq>Lw=!0J)sgI~>tp(9<)F8EmEXIj2Q5lc_OOI3wztmvJq zaWC8_Q?^DU(swSdEQ!)M;m&e%coV|W#$tvuE49DSu_bXOz>~XA#hvZkj3-*OvzcH? zRs2f731g>b+L#h2W4o|b?(c)|v20TKKHrRj-j6R@4=>nkR|%>s&zAsrw3vDw}CYh%g7w+>b<)NiAi%)kRvWDeV#$+nC<4 zgU6^0BE>${tB6%({OF=5$IT|>B*k>vqC)Kl*hwA@D%RHY+rAuePho*Nth6a&sqDKZ z;T0<1JDu%ROn>=Imy zjDWoLWN4$@=1r3ANQm0TGRJ7KPBJ%i3^)Anx_SoOX4!G+9A9WD?$mP%4a4Mah3#EoxPv&ka zViI4%W2Oy)LvqhJg6FwZ4TEErZ#n|T9cyeT#_AcF)7cWYd4-%cvcz1B2tp#=YlY#DQX#DuWxN@I*l#K3yTT zZBCy26fHaiuSa?riHqEm4h=9Hh*ogr=vxbgF-0HJ=&OJYb83+o=eU@SznV-cje0k53?HtE7#Y?cSN9<08lJ7<5`A))EG8cQJ=exP%YznU@Jv+Ak) znx&b?6BV-J6q8!Hc(t`Q^A_cm&?HMVMGYw`XbC#s1(0n~4KbZoj|z}jz`3S}8-P1S zI-696t03Z95H$j#nA1fr-6KaMFnSO0zO`Zt&E3+fyN$tVP&olZqvB2J^4j+Ir`juQ z@}K;dY}Pg3A=z4b1Y){QGmrD_LE3)}lMJvrPt3KU5;=eZyCd^9n zBuu=&HH+*SN^HScx}!&|i8pMEruHg1?i5+6S+kJan>qpa<~nr~(=T{WC5iO|bq~#4 zt!o&uP)nwaNSRTT2H1hFplq1jAN(0E(Nv>JwHiB*pH2iwpA+NTSGW=x?*o^SNGgPO z{d7Q|sp2wB{E+A8*8-ujW69U7NkT2EVpXT5vC{rhn(y$3SQN$TfO=_qHUm1bO_X29 z^)b_r%&KfR2`*-{ZB?PhhngT?KBKY=w`tU{R(S7^u?OU#b1w$n;;BA z>V>H{2TE6H_@UkYu<(?^vLGB;(DF+ewO7}N_M&KyH_ISwsA@Hex6BFV)!$1W*oZ79 zqg6;{rFN`NWb>_Gqm80z$Ropnl`7OffIak+ir=QEMEInXC4!-^OtCajcE>cLSkO{5 z_rB5&yUd7t9xY6r zR-Pg3a+Ad6zD#P0ABG08|J2oA?uewa8n!gOvc7WxeV#JO;E2&@J6=+N9G`hkMCJ~K zPN4vxt>(pd$HsJVO6!gXWF^;#)}DiP(KL(pO!2;eRe(wbM(d`G@H~p^moJ-iFW?Kb zjDlln@91(l6Rt1+CVl$S(g<0PglGUwFvP^u|KuHK^Qv$<#lY-Fqmy_iTXNkF0GMXv%>YGU|m6=zegOTeAA$9pP z(wzC?7sMemtunPW%rI3agA~!HY-RyvN-jk(25U5`@!0N>p&{6xVaHwgw+HfWk4WDx z8N@Av&mO6d>tmBo$IwvIP{>{YU=RWE9-qL{hQM))!L7?~>fRS2iJ3)pr+eQu{6%cS z_1$IN=IMe;yGs*(p}baKAlq|{)g6<8Rd<<2{iKmwAp2}lT%g8fD%IEfSDEif)k~Nw z{}8Bmz~Bc1)v?(ORbZYHguuESeCi6FGXigiMoOQdr@NFuFl2TB)S9m*g_F&r>Zk2^ zM={t%090SU0J!Ps~kC-;=Cmr+8*;gzejU* zhvx3GZt(W|dij z)F!H(T!O@duxy~L>x44-yGxaUTW4L->ov;8X6T7A!rxC{7hz36PfzP^%~8~|sLJOok}8Smm)PDg zTzKhG){=)@ti<)o4HYYEH=aYJoyHe?j~%t1S%xwpm|e`6h_+gIYwAmO^_cF@`?C01 zzEswU?s%uDAyNLmcFd;*8;AvsL!$vRxakcuR(6zSBF9e6N|hF;7pIpbCrc$wB~wbE z5hqS@uh-X9W)SG4&seixLXYg@k^e6mYu2N(FFA9`ujKyh_w(Stc1EGz!66ki&RT1 z>kD_`EZZrSa`#IxCJN3=f{(DDh zt;ptZvH^Qie=o9Tdb^Iah+D;^{vx~!z+#LN?sv;Ql2`q*t7Wy9M^W>Mc6$3b!=O#L zfM`TY9QD}_P@*yNNBXj;7HlP!umf^4C_!8CE?sxuMA=;zwtu{>d>U zhW?9V_$El0C!Ju2p7}pykvVl{e39(T06)DO%pu!XQet5wo{S^fEQ3(5GHu z^a-)D89qPlB(}#JUnUG?O6u{QeiQ-RzW^7to4X~8OvQ`UjK<(@?cW21EG>d}A1Lqh z-$Q&pznY(Co7%*;!7e36&q5E6qi1wdw)suO)7RFh`WB#%d{vA1`POjwN!}_*om~w} zqXBm~uZ!i+e5vypbdJ&L^_F22WJp23$%8ZpFFjmxmw{}5JI;GGL^2ck{Q$1f?Ec$B zm_hkg7pHJTPqS38RpcLjGcVsjMAHde*%Rnhrb4o89Qdrr6%}cHMG<6ha8`7e4Mhhg zCJYtt&~>=QXT7R`kO*#d#ri06z(c3nFL_T~d;X@cPI3rY3R-j_tIwG!yC3%YSZOJr zzn)%Otf7c}xH=~SSqo*M8ZIDYpkDe&9+C;5u?CjnX zuCBCS`B4kz@h$2b_c`BZfm+GTa_lz@^M_=frtv2Me&%d6|7d!Dr@%ps#CAQEIs)F1vfkb8@k>TVWPvP=&t)dU1$? zS88{OE%t?qn~Ib%QtdgZ9@B`4w^@=iCZwswMhJ+WxfOc_JKrf}Kh%AoAFs_Eq~n?H zBoKiN!-0Ywm|c^*2^8OCHbv|x?qEC*eln{RUzi~{a5W&o(D6ebUD@b$)#_hQR@QU$ zslPJEVS(@Ae^7$|TSm?QRsII#IncN5;IPI6L&pVBj93&Z4Ow~+O#*<+#C+CWvsdH2 zK>lBZCS6YWGTw@r58ll4%x1FlJ>9)spg+E-q96qia?lPMqzg19w_?{R;h>ANOSFo& z{w0BHUh`1QO!{DZwYghAXdSgH?Cm7YWTIpatIny~Bu4n1p&#dHtfU?Etdei*z6eM* z7I4 zHAxRvCqJh6rY8|~*|$vFf!3uuJ{Q;VEr~R$A1g4q7u_`OT`yK0gZPCXr06C_0Q01B z{k!do(TY{t_X}oeoF`latP(g#_)xe|c(JgT>Id$E5(m{jKIBaU-g~$UXfXw>QszfX zXtxUhSj=25eOeIJJm=T>G)5txG!M4Os9$&%7)L;^`@a?xj=-`o4p`9tC3NWDu1eAI;#8 zPrIXoT;@Ojq&dK=15@OW629jKfkWKBbxZiQo)Bb0|xT(aC0%|S6aIdRDB3UVU~4|VxF>bdiPU?e^)qF`jPcrpw!Y>Hx~3$#>69qFm*{M_0aX7ge>pZPGh&kVNM zWhA1jXgR;>gN)gm+zTSd+euX6o|_xH)K6zPo{{fv*EwESoZD_w?PhjYANQMxAOu5) zIM&1A-^i`xgHegB0i!qw3}6lw5MQ$5_An1`63}u}8dDFYlL1NIGNY?agim^317 zlZ&@gEpItYEg1uxk<0>Sz(FA&qHG_bU$_c~!IS(in#kq(tIV2=4SXe;hZm^=6X*b| zNtSqOGCDn-f`eRz$KY$Bjiy|TnGFpZB)J?~GccNbj0xkV<|eY4zOpc$;KVdC+9T+b z!Za&>`rdV|v7$H_ie__#rmh`va}!1IPv&OrzsbCcHSIJgDwDwkjXb#uHNg`@hG`ft zKq?13;{*7)nG4fFWwHT{XPm4CW$Dg)tK}MWCXb?WFi_kZRw-Z=%xeLM_Vl=S&DJ=M zC9bw6P2R!|sd3xj*CmWnGLz9|oa{ROvh?D4lJr(spwZd0j^?5PCXMJ` z&e$J3HWLh0%rcUgGq@%>%%V>fO3^&o!s4d_@&<2$DzuDJ$~86L1d;A(ll#Co4W~+7 zq^3>5_;mRbCE1hSYTV~x+`C-?+_hs^mz!}~w#yg?PZ8tBWFY=>>}dgiI3?xqur4&b zhTg}pd8&?@nS44;X|=v9ts(2U#to@V^3uzM&PL&bc7Ap;2FDAiVRhDN?QEGSj|Lb- znLb7prw77P(NWAw6|R1F{MXH)ry`?}zXPvK#sRQwa zj1-_)-56@7N8y%aRY3zKNmX~PWnHbslJbu7hd#x&@(ah;45L;e!|g)LJ2r1Uq42?; zfCi{P19bFnGyM**?4Z?n>+;k4_4u6*h-3!CAXkGvPoCvP@bYLLNt70)RWQ#+%v?7h zW^=#Uo9L>qFm!SYpS-|M_+AQwW?gU>N$Pon;x3M`6npYA48N}SMN-!h`abBRB76%7 zOd>33^u?8*U_1p)Xp|L({w8>d)p61hyu8Gl^=jQV{$`Wo+geKa{II_ux3Y_rsm+F% zFe%if%58mRKT1)%S}(Ey=SElHYCJ7h7VZ-N<7J#)lHDgHEJ-frBkI>=G5-eHZLwI* zSE3kHQa4c6G+}(%0_all3$mJQ@^~uG?3VBrt}X2X-vHmBq7`_FUqovif|Zl=5nh@J zIixC(hjuLBw`Cu-@NFMY3EO9*;4=N zix%!xfpv!zm3Xr>Sh5Wu7qM)(+6TV1ZJ6%N!Aft^#wtE@&jIyfvYWouEnFc*K&p&? zsk!}2WLd7cjd0-Fw8MFHY>KZX8SXWr9--cXY!+OF_=fBnlBygY~X{+ zVE-immHrMYH%2wH)Fn(4#SUbTK^fiQX`KU+n`4kj6Hzh8`W4A{8}muw6+;G;hhg-J zKxFmU?yl84paq?V*Z=AWL{7o!+iQm2hV~oT(q!V|zTFf1eNw1qO8Vd^qE3(dnIlcVnmAySHdln5P6e z)&i{1eQL@nh!MwWU1lH|tzB4>sdmM|0VzglJDoRn|H&!9+T{J75w>xK5Na2jFZ2~} zml+%C_y6F`{-yV$r>Q5`1a=;O1$_Q*dcXhanEAIl;~(6-f5aC5y>sc51*!r>uE9co zpy+-9ML|pY;ZFt=Brfic9!K_7K!8OV*9)Dkp?e4sELS907$^-;2&*Romqb;#n8-d( zH{|c#;s?PqY>dFJQk$vL51g@gNi+73vSUh@;C?t@_P6f0)(WP+L>lXC>2?$QPJ1cQ zpHL|1kqrJ7dpp8^Iz6ONXLmbX{qWXqPMlC#ki*rPhegb`qB%D$mXm~$p>Uwt8R?Yv z0j7%8K`8S3afEmqwGR<3W*{#z>~?C8B&gs$!vh&kpxhq`>upyp{GqAm#qVeq822@i zuFnJ5;S>gM#CRFYf$`4ss%F&v#dn_lxOUY~E*PD6+J_Wu6d8z);`ohaNHL#o{04q+ zjZyeXFPI<$(?8nflkQii9Gs^YyL4B#*L=TrH)QqktWzU`nr&FtL%8~`$qV6AE`Zh+ z3eq)D8-%<3W>*3*y`!kYtpBpyUGDieVa?mzgj^nwG_nNz`wt`dKS~26uA4amg+{;< zSr{2O+d2MIm+2D22m{K9B;x11xmZn0%llIk>jowuFFJxEY}H`ZM8-ZL%&hSSgg{an zr#kBh;Pxc03Wj%3|J` z^gAyE4BBJHXnUJ^(_PADg9mkN!Eu6sY!My=r1%>M2nFyzwsSONurzYEv$J+$aBwkkF>zw} zS4Z)`8z>KF(=g4U5^I`kjIx;#M}X1zKzItZ}pgWR=m5uU^x#5WP&(A>7l(kf|xfRA4%x+ z+YP6;ZjI<+U*O%bkmDnG`*R&$%$R{Rg5JZPl)G>a!Tm1B(BWB-HZQdtVIm;)&|VQC zc6^t{@uh+g+FJ(Z$%X%E7uRl%d9&~x9%^MS=H5X3!{Q6p~=bg zd_Ly(!IMY_Gg~oY!vW7|W982lcAZn5*`#0K_~8fomsjFk@nOZ5b|Y!n%qr{5SrJX7 zi9?^ugH9GtK6Pr;WC__qNB@j?kNw zSa9DRSL0j4JQPKbf(PA1wB{Xp*zjy`{5 zYp14W6h4rXqEj-voyZm{Q7hQcFr^I$SC`iJ6*+WHfFurgr)D;etlBu?Zc-8{CcCY( zP-IY}_9P*rsVcKhEcp(JqB!#!FU@2zG{32o?27xT% zAaU04Dv>;=9w2)-b$}*eyf=}+Qqr*X_4tIfwtmzUp<+L`wmyiN{XluxH+m3HW4YsL z!m{gg5jUOoBeTG?>d!?%WhZTEphN`V4J#V!RG3L*`u>rWX*8^zikiFBLn*vA3B}?i zKZbDE0-k^A78Pg?TihnCe_Pa)$y~4QMoj&?)kSXrFGX`yi+|pMtY&UDL=ykf@ueNF zMj$rE?4~LNh|eSRF3g68#n72A-O_w$4&xqFo892zm+-6v3*djnTDa+o+1_*G@{N^* z_nWx|{Vdy;yL*a!X^e?(6BJ`;Ic<|gNZt3uci5;h0)uq^0QXzGWvBYHeR#@$BZvQ| ze;)L(*3#HNx$&!Dp5@UusH6ju+TCr?=EeySWC-9xNN0_59xe9gCTq*$2>f#CE?3Hoh!u8ml9ld8n|t(XyOq^u3njU8j8!GM zZU7A+-C3<+TRP)6e)vn?k5iUy-pdLqMB9l{HuI9h!Y$zm@k(ptoU3|efRV5<4#&8e zO06)Xq#q?-(#Xqm+R+Vbx;qawIB~%pD%lzXHf?TtZhls7|2UC7W5jE>uD|3+FSS=q z%Q5^aLFhY;ie5lzVqlBEkU}B%#nAVr#IfGsEvqv zB|)ezy6bz?K2)2i#7uEe6@}F|Xm&;8UQDNSF{ygQ<9*S!JMx^~bT$tf^tNEZZg{o* zOUd*{v`9hDG3NsGOv)E+MHAk^#Oas1kJftihG}18vyZ=NlJt4G zjRcS)Zj?si^Q&=5hrEwo_Z-;?nxtKGA%fVq$#P8rJ;qB3 z=vEJS$1)AY(#|#f#gmt$GpRKvLyNwtoRyr3@`@d%M&o{b6Ro^1gJUuV&%=4Mv`($r z%9X8!-$j<3jmX$X(i&o=LmyCO!0kpi=@vKb5_5!~q?^$qT%kp_SbDYaVnDISbM`?* z){3q`*0MpT$rnn|j>ry_&!HSvBn4{0w26M~xTRKX!(pb2ZA1fzR)|4>lbX-C326AO5KW9(iy#VwYe?C|Z zH1L-pgf(`)yK{`etI+rBsxVZHgHqHrfwW?Tlc2^jWK_jjp;!KiKa_$?P?|6;bs*3k zV(RLrw+5{&+cpu@&!!v@-R)oOpH%e0S%#6J;{BnLQvatTxVTKqpW!0ZfWsu}o z!pxX#A-0Gj*)RvU_4k9bOCGvBupG_-u=HYEyl^4Rg8_F`G)%Fd3PIN>z ziVCfH7~C!*28DKts^Bi+LCmL7h(SS7OjZLOR(c(x>4LacKfFWuzW$Isc?U{&C>oDe z4zJcCa&1K)&~Y#{$1`CU_f`zl=2D?8`I*H(B@j=%tOmJ{q~do1i!bhix&Z_eK6c%Y zcQ$$md&`ZkqoNCRpf{1;8+Ayfu9zwL*zN+2FTB!#<-`W`2N`WG z0lldhUC@(6Np6rB4gvoDqxY}W6frx2FG$hXeef`nn7iABc}WUp6*tQ-FQE$Wa`5F)QJ}3YJE_P!@39$;JCYAMtVcs&+|Mt?e@eZkSX@f^JYSP=A5q_Qh6^ z-Y{}QyGv9Ay&HKzLEy3^gGYqvU<^NpMhfBtu7S$0KoJZT2gCcze=EWlbZHymf)9y= zGC;095Cs~BuY>fxpCYrE)k2Ww|BP_)^q;;1-Hs3ErbRhmn7%K=OUKFp>y1T*b^!0Q zBYVk&`b<{;R?{yy8v-lBeKxQ-agV=3$~8;IW>k41n0qorf3^9oWk<9g9a5NKi=t&W za8J`)+Io1?5R#9i1|YQOiHFXeth6C1-|?cMKlpt3@e(&ns4t*X{RBEOVW$v(m5SlA zVKfnb%{a_E1l4dzP#EKd=en`ahklCl*dZn2iHw2L8aQ@KZsa@R5X;DYL}!_J)t9CR zRg1orc$E+O$3%MvrL-=A?p<+NNNw~nDrXq&zUTAC){DQv zAT1jQ&Wd<7BfD0PFCBP2yMyfAFDXAXO5>R>=)aKR*zrxt)g~{1Q$t22`LeJ($xcUE z9!on~LR1Br5-c-?WF4nR@LCvyYeG~_2%jtWx6m@Lzd6cIpRO*&JG0$d4`n2CcQRw! zTzQQ0Q_APe%xU)59fJvLfAcQgX@97=;eBRkc51rT#}x4@a2Sq|v`ETN?*^AyJZ+KT z@$0aGk6O4nC0u3()sjV&wm5E9Rg#ivLdedXLuE^*EG?JGGaOh#nGe)l z^Oy2VFd22~PD#gGx{!Dy(-i*Uhpg&%PbDaQz{Ha^c4Yb>&6B*q00pM_c7dZ$XvXVd z{pD=z%$3JQIky;9W<296XQA0Rw|x49%46MNOb(S#Y9%}6F9$n*|Ck@O`QEOg0dC6Rl*~7tBi=i;a z__+)1kU8ptxy;{p(+>R@bezfKYhT@RROdRTcC4e>FyY15m}Cs=wBzzmj${-cmV&G% zATU!!GvAjnLYY;WQq_A$(0J!o-SitLY7Adj3r$?o3#)Q1c-uLJev_!uC(h(D8iy=n zP9ISeQ8V-u?sc}E4L&P-6Y|U7a&R5o2>FXG)eXof<<3l8O$f_EG+zq}%B_baqm=2a zvlyC1EeOj^0o{=W79Cnqh$~kKRc=YN69Ji^a;#!Zsxe0tB<;=vEyS9nHj`)RVXOoe z`|-+S4J2HT_Fogn(?%!Jz(fjIDqgRKRX(uz<3+m;2jx_>hu5inL+H`@#Y!VE>$L_M z4jxh+-w>WE+K9^ac9*GrNlJSMWf-J}uWQ)_L97&i9-P||Z1w=MG!$Va6 z)s-Jq&+7E8bg9kt4|fIZ3u_gE#A`|eD&3;w@yyLp`=qV>kCr9Is~C+9+RQRrU~D>= z}gY}$Cg5hYS@9Y&j{pq%{uCOCT-^`53Q&<%_-{j)CRxO7_kb+s+#j5DQA5x z6wB&vr?!+Q*JN|nbkTQel(pFD>U7f zH(~>p#HziA1|za*J=aHQ=6HJZk>46rzX;#^Y-!6rnN%0P1$W|8_d9N3{~m@21<$xr z^gV~Q7}tLeulLW-KOX9ceQ@FDWn)@nrBPayQ0|<=QrsHaZ{6kez%`*r-L0n53#j#l{>%Ughma^E zO_8sU+R4RDKJX4LRp9Il82h@Vp6!OLQeS6=yjVYEvcM5VhhStyTSQANyjIIVj8_<> zX_2>wwGv1j1E0m3uomOVBV8*p6N`^EBoN0Aeq}Ec)yj$k7=Ig zJb~<9><>PyJ>)ygn2=Z4I&-eSif5ZNVUYyL^c$GL9k5cI>;=OJ6ampOT>651R3fmTq{1IHr*U$I?KYZ&(KE!X2^5@|4w_TPgbrv#TR5(aN0Sqc} zltev#h&!560eHy@ycC|W<^wSa#CMt)BiXD$5yna5t$qF4{2Wc5$)GCI2(@r42I?@f zP_r;@abS+!l1BAlZ&+L6@7?d8k{e$6ipSV%&6vKHzO(Ttd7ng@!c@Y@ZcpIh`Oz4X zRMPW{0Yqf;dK^i#;V7XvMTlsjIF4h&ih!anN>o($oI7vXzJa#!L60yFo%T!c_Ru>= z$WxI)s)FB8-Vt8~U%@(q%sN-Xgy0BExh|o05a`ij+3a?*t z$-4E3bB$hIJp)AOVA-%LpEFu-MqpgOtFgJxNMhdcnt9M| zevGjqcAgYoqX(rPu3zQ!8_4P2^l)?3v?+-UUV6(rcrzUhKF*L^sAyD~V0YSX3F)qq zdzgTGc>7@NB%|%{K3#q7$EBgBf6Lo@A*SE!I`YEyQgayCFG&b-nqt5Gw=^@ z2;eLO0i6Fws_fs{5b;uSe2Px$;qwEXgOVexqT^;`eDJr8~CHaWNk?2rV86O)B?jzkxtA^ zHMPBjLrUouvPA6Lw&O+**MiS*J|D$>E56=#3 zo(!}UQ_Bk1jAWeJ$mYqO$&X)$@Q0T76|Ba!(#>g`YS+#A`R~bVwdgM;#Y?QyZs}J> zlSRZ^uApk{H8Kv77rChS1;k&h>STAUQ>;)%KA3ez1;YMV{v_3d5&q%rl%O!`F`8zq z?I^%R)K1w>nx3Scq8%?SQ9}%^_aTvG(NpOJLjk-G52Ee*RAm*kolJHyjltUt3Vx zI2j@;871pEw&+t(i?Z9|jBk=k@=7o*aVt-ewkfy`rwx%8wwiwNdNzv>c)Vi0&te`~ zowN)@WiN+Y?aWTc=^mz6KJUwRejqCYjg$o|F@^}7L_X9h-a#qT#4mW@M4OR@kk7%& zN%Am8j1des#qz#n25S9*l!la2?jzrosQAkLwCIv82f4nkn?U81g^Xh#jSruNXRs%z;UN&KaoFX=R%}sXNh**EMc;ZXCL{RAo+k?8kE` zS3yu*FM!!{Mr2ZK%geI6P{{p|w9NhKIx>VrVyEk%1=*#fh6u-?Af*)T(_9$YSPRD? z)-hRUzw{*3Y1Uhm#UPg_t@H?1EswB=W#w3^&qiG%Ai$%EB58@o_$9P;O*kjvPCY)V z<(grYm&*DMvj?l8Iznun&IDykn0&%mJ$Il}Dz^=VNw$O13bEvk`%R3HM-v=EHJK$Ao*oy6w z>9BlfOjrnM`?il?6?Rw=)fA_wo*4g&3>qC<#hcEE0F#U-;TDS(JF>06?rs}hs%(4( z&=X~M6J+qZ4QnEffJe|-QrPOgP1)czhD2B$Yhb~FxI7zukz-LA+LdRqQKErxsbU$d z7;jKx-D%oVyq<9_urVO295myaZCTpK-8CyDKLq#t>ctHS*W3+G<=5~j)y8o~zT%Hs z-H>&_t);9Od*(Wzwh({_50mz|K{d)b{`Dqi5g?SQB&(L}*A-WAtB%LE9Lu!?%gC8T zeD&kQ{})R_K{=^RxNIOnH-+91@9g#`XZZI^7Mvmt;&NdwQ7_8Lt)Ja@#B}pGWW4t9 z0@|hMahr}sYz~!sS;uFcsG6l@RDZWRT7 zw_D+UqaiJR8v*BnW(zpgL&EzJ!gF|pIH8+=K?JL;I%aqUSa&X&;OSv}Ui)tlLQi-QnL%Zl{!OSpx5O8$T=(~-?f>Qp z{)N_`3zAf_0S|kV{~xs8+{M<)#Q47l-oGu({=vik+nrb89|{THeX|Txjn^5a2W4cs zN2K`mfUtmipZpiTD+I2V%ase+gXuG>lYGLN*a%ZLA0$so?6%{HUB|2Tjen@hOUuyX zhfE2yJgiZZC=_4Dj2ckiE`on*BOh23+^rCaUlTq+Gs)~S2SkhuxfT*YNBte*bt>wIJYp{@n)f2vo&;^;g#9R! zmDg&@mOWZZmGWHcR^6N(3Z^FvVNq4|a=BOv<}R6DfHCK2vV}A{8gx0nMlJJWdZu#? zP9U&yLUlY|ellXN2*SGqGaI)Nuz$$4PuD4W$p7Ml@*C)-D3RwN(ikFIXpFf1Lzh*e zFpdSw0Z~LpiXUEm5ss39(h5hZjMN1m7h1LV-;nraOP!_vx732aEAels1scF~AEZxm zu#^EZJW*vZ;jV-{+AzqBFh^4Ap`Q@tbC_1E(*6AnxX(8)B0Uk1susy_7qJZ&t9Yb+ zBzzZ7($mX&ZhrHf{Jy@QFgqwIfG~X~Flb3Ar8JC6DT9ffh9DZ08rMSpD62H|nsXFe zaq$o$60%)#q3N^6jWNZyRY~$hxmw0L6Nq+eEpVWl`4qK%2(!jGJ(X65l}1P*&oj^p zovbo*z3WEa8jN}rY3!U#$%QvlaL*>FXlZVgGvZwGwB%}v(RpC+9Ij7<--03EV`_bM z=P9GZAkOTYl|@B;&05EbTDE|jk{gz(Z>pVui@VU&Vn_+WEwKrKwW+sGP5sm-zfN^# zn3ArI;ck zy_#tiix^W{In}pO^z;mCpjrP&Esz)GhajC8Z3|v!@|>kG&>aHBz#jTj4F-7diwzDU z!?rLs-ssZ_neDY2_}+x*evn?%12_*Y2=;RBzLWrbpM!b;8kG}d#%Lbg|DV(Xg5$gU zbQU4MrCVteF}Eh_0JKXR2jq%@&9TQSt&p=f#d&MOw$8&B0m9qMcMN!yqHB^KvKs7) zN!T73I85BryPE3EVq=ob$w-JoI?HaU51UKvuBR{yX`PxhM{oAz?n{Kp1 zaY9A=sLu@Kw_J$twB&)R0*v(f92Z`NC!|tJ!a0Ra85H9quH=0Fmw%@ggxY6qIfDP_ zL^Tf0;f_cqwh%u9pLb*c-faEw??(>t)AUlspNt7*4` zRz2*{kztp9=C@Q5r2 z_Ei6YAoH)3k^i!G{AXXKsAY!&OxuDmZ+C_T21ph+KWhFOtB4g8hnj11uwrG2wlnUK z*0=AB1XR`YOGU5!LxO}o4bsf^rmr_;bCh)rdT2KUu|2w)YFb?kb_kZ`}&Mq>~bMCq4oEz`C=Q=0#jrZh! z=oR`&dBOfB&H?koK|<82WfKwNcFDN1z^U>F&%9~R3+N)VHm@?+b)35%BP>84_ zaGSh4FOBboBm2pRkZq+Z{mX=i!sGP271QEm*9~gf@ZIIIe{|5zRSLhZr1PIHV9DYW zm`xd?Mk~NAND1wwy;~#Nu_shJ!tA)5ayN-ehqXD2x0J+&h=%sl$b`ZqsaKn2>O~Lt zBa_>F+UEOLa&DmLWJRMo$j~x@b5M!&r937Q&H; zHk8|hKcgb+S-N&W4OKOz%9?8rz zvPOKmSkA3p{)KqS(W%Sdq37Ic2^7<@fdnNr;=d#)SsI*-#>WXt=0JiH-Un)X}Mm4iEeXD8dabqI5NHaeJ=SI=j zjWM-}y?pIe%>!fI3iEI3M-D~fs0Mv@yO_$+dAky4}2rFf{X^w3@tqQGVrq)2gWaa{RST6yV-aR$fR|b1chdwFnGZ+!YWOmD z{@f)Zh08zGD3pTsr4bkgK2-UKhdI2aRP2wEyuW-!gtusg%HBBAI^S);7RWg2rkb~2 z%8s3mX}{F{S<3igl5^YEX9w2NhaPWeHL4dIx)tjfk~nsBDZQ9*H`;na@f7BFt^`^k z7?iy^NXY1KA1f+)KUDsZQqFDg!UZ+8G^&AD6O-ng)ICFbTLHDQ+Zt;PFL;T+j+(1(^X?D z_3*C^b$HXSdb^+7U=JyL)}Y7uB!iaI{g6~f;UQ+PDzVW(N0y*tmTg^6!9uYb?R13v z!ex=Q)>h+ZpPA=sO-2Yz%RJgOMLyT7sSn`Kn)$T0bH6Oo=3Psf@~fA+O*S_}!CMsy z!9-EWdig-u2t5mOQ|zb}G{-Bs55zF|Ul1Rqz*{jspDgs%H4OS87f<-MH?-9iO%QP=Z? zvh%NmP{nrLa)eBL$#s}TjFTTnX@*FoK}zXDW_37CU$4dtxLV$dkp1G4ShanFHNjAL zmeuxFuz55?M3U%_wN5D{9WA!qMv0Q>Ud9Bzu@83I-gqK4Je}THDr&4O(Rz!~Ne8u8 zs_TZQ$-U^Z%TvgiK16NxiA64BM`2Anv(uX$u*rrL^oTYvrE+YIeDoyhW34LoxOD%) z9@eht9m5YC#CFX|O$ws+nJO&$l`F8`0pAasO3uFBpIUZjz&|?Q zGCU>oa)VwYm3&@7CDVHmek&2_C$#tE3E%g#9{GNdFZpD4u(DbGb;UU@MXcWFXsNbv zaf6SjdjOr-KBy`|!PtIC_XU%7du!3~C0D6CUS<6jZPl^S^b2f=lIam_AbhYwZ1>ohNe z9l(w^@5XI;`V1nnEl_<6((<59F5z0xR8 zB16N%BYJ&#ZuE!TP3xIeW=?6Zsfbt8Zv&X8nyr!XdxM`hieZUp-@0Slf=t^sP%!7= zvnZ8w(4rnK|3z#m4U)3HL}uhjmzhEr>E1Ruo3r6Y>|Fem;8{{JSJM^JbsKvLE7f-D z$etu+9{a{|6ZG5N6SKBf-3A=hqxF8`t@VCk?iaJ!42tV~v=-?r$7~xwh8oq|@h{5S zHF*^IsdSc5w37*T%89w`&FS-Q`8RUn-S0>jWL*6&;Sv0lAfuQNPoZ_WwY|#7K)o%% zQG1!U_Kx$m)u+#6{Zkcr22UDYDMOaK_ZgKqqa~%(Ylf4Xj9VdAUcJT`JKxcy*M!~a z>0Z5jTFS(e$k(&d*RZyUWkUEl%||!tn;!dqSq5qJ11(d2je=&h*Q%g$ihHF1WunIH8gtsWY|B2|Pt zndsN__+P)~ngc%!BAH_C=6J_rWiy3{lr?A7$ZJD`!&*gK7$p{uAirRtrjMu=;#Brx zX@R&X@MSKY-WMLDiTI^)jW6RG;siwxc`u}V1s{$$(vQ9rZiC<-AsC~sNQcGsKOhZx ziB5Ycx_l?qvgI~CDOzE^IrsK0qj8fFJe|Xh;5n2{jyh^Z;-Z2m?&O`u4U)|3Kkoz` zNU0DygF_;%KiZ;fAh|1KTvx#KOJ#jwpwi1W)b+}5#`Vr_hA&O0BTDm&M>%v?Wra|t zd!4a))Y3$Jl$9itF8HCj;q#?-W{)%Hh#1Vvw|i30%O7sG*a>_3{*=07G(?aX9bD3} zkt_@;NL8@Cteo>RytcDwR_h}>Lwz^54y9ZPO{%(5`t=pEbT;fK!rzM1av}NDmxik< zr29W*`lpdi-SvHk#|Zt)riAjLZ=UGWjUEt?^PpLJNh6D1+9285S+(NG%?aO%0>NCY*ZdXH29f>JE&@V|g92~q z(Z`R5G?S!=vo)57k|aLWV_QEedXS6woCMwR@m2E1z@mw8BBH}wf=?FZA1~nVv$=bL zb&g`T_n%zfduniGgPg~Nl*A`#sx{#Cr=74ewD08HPZd*4~=#@VkHwxP65 z6tUA$t4UqVF4$Ha4HMpqmJU=KSgd2U11qPMen-AC=O<5gX~MNblrHq-iEU=h);bM-_AwXh-F; z;nY%%+QvPRzJROz-2m87OKM7%m#eVEHfQzMVd)kb;3}n}yB7Sds!wj66BT#u%UtFR z&J8Tz+Gv_2tyj)=DgnI8vy!^fl|+AJBdT24Ep3b;obN*mg?VE%T6ZrcAfgg|B9bWB?LrbOZieYSNi>K4<)$WYWm9Q`t;v@{xc%Cf`~9L! z!w16c8t&er(Pg3uH~m@_xXZ`jDJH-q8W5f{z3yha85|X|jiM3Uc)@Q6M6zS!sr&JlRUsq5pFg3>b zaT8rdI=NX(lZJARxzf){a@1XdbqCRicF}CVJM^aOJ?A*@TPxpHV+9Es((tx6`3R->D9 zSLkRJfTksx;}!A|QEqA%_}U@p>W$Jc7bGpXMg=0Y6M!5z?%82#Q! zmQ}tHeXD-Tf7_sY9zA`;hqWN`BMSBB!{+RP`gp%mRb>q~eulmM!NRL`rCiv{U0{{o zf_?=9Cp=AuSln=nyDnC;PgrKKc<<}(-PfgG+yh|}Q0UhNzPBQabyz=`91x>eyv!8f z@xH{4JAl9zD>*8TIOvJE_{yO8NG@tNHj!g%iJh5keiJx}>D2$7bpF zjqLmfgsLA}6H18<#f8%iv)wfj#RNHBHGMhW(8O^24hLAdWVZ|J*BJRlccLmaqR`l2 zI|j(tcMRJ-^jmbutG>nI{N>X|6eZe(tAL8Xl0*1BP&)y*c_ z-TJUH#P#(f*Bc?N;*Q0m8x}ukSD(m!4-N5by_aK|8R&`K0A}2t=p5SQAi6016>U>1 zkomK5ice+t%;IvH`A&n+gW;`POo1QUA_59_UphQt@=ku(y4$^(u=J{NNk{kN(b?Tj zt%b#4=&9&?z%u?0{MRDDQ6vLLJ3EM#gNUh}fujZ3?qr#DsuKLaXQUm5Ylb{@zXZF1 z@zq~sD+cRG4h7F1uwS*6mUUpo1TH^EVtNQ7k>=wGOG_g|`Rc_+efNk*YH{J)XFL$6 zUNy)xzOPxB7-#E6%PkM(3cMF0nwC$kBCR5Si}5T&e24 zmo>oGOV-wTEIr#8QZJ@B4wzk0S2tVF){vRbuOus~Dxg{7pRHY!APcUdPUH(V{OG~T zcKezCOS`u|vf`;fExW&ES#qH@qdqD+sv~>UPeNt>p)?=WSj?s3JAn|n3-~8mJ!C;OBq>Ia zrj04UXBC^IU^Lq_fKkD}xV*4EW@@(etGSriu0JBjz}NgK!OoZoANky=Sj2m-yNW~j z8#${a3|g7OGw)U7EDqlY&miPw=$d0sL|QdI)lo`UkJzz9@6V5r;CxF5&LH4N<3F;9 z0V^(H6V0fFTnQsDXiC`1PZzJN)Q%``R1+O@k1xY^sd!LNFdjz9A3)c|lto>=YOii9 z;iCF^GjT1s09|Lcf?xxEVN-G|I_Q$@mjx;zyJ;sn6+9a$(Sfh7pT~Z@4-BG_T~0BM ztb)vP+EH=$vY_q9bXE%A{kR+%LX}Sw(A&(2JIV#|@1-C$tE@EJY%gq!7{dGDWq7+iC}?w ze2_Jn;73hPtRwGqYi70A=@3VfAX&R>c`L-4Y^LKYGc5w$B;wS4_#+78g{uh-3M#na zKUn<`(QXNia)b)Gg^OUW=iTA#RSNK&WZ9O`N6C=plr4I9z^R!qN?N*lKf|RqBx@5> zeE%s0rbqoG#q)XuK8&D{gjei8TLgwcXn6(PWBro|?Z7bLYr)V+FNI3Y2- z+~HjpILfvN|3dxA+9c8@H41X#(w3o#+^0a! z-DvdN9zYRkqR86|cM&)G5*pU^>get3w3IBO=Yf3lDefb{&gRr4$!h@*Wagj+3^JG7 zw}=JF{FnnoZoYY58;XqiwhGh{6OJV7htXsRQbx={?nc_YsW2Iui@VeM!TqjN$*dk{)k3;{g@k-Tn;zf)|3dPbhBa>dfM@%X2!5d78 z$i45L4Wl*a2nc*sljnKYalI=sKV$uN?BW-W%xM}^PGEz*EO;Q|OMH%%3`Ank{F`!q zr)82NqJ6$jvnk(Dm5$!0USb;S8&c|_8HSr8$pvB2w%XXe&z;Rw;qYcDq+wMC8bD$$SmOXSh8tO_f$Pl6TNuHw43fBGg2}T zb;Y{LX!PV;?7Kr;e2_6MO^i+IbC@t$%2>CxYAQMdGKsco0upjJZcc@NSiudbQ>Q)((AlJ#(2;1XY9~ z1;OLHzRAPay{Y=R1Bo@?nt`k`;|xI_xE*(CihbtQ3P~3W8eiX8DN&?U6B=}8es~)J&7Bj-%4ZV=SJ`r)Y?uP!RZ5W3s+;7OHkHmM$_rXnv-V}bVM|H zL$n<=BEx|aNUzw7vF0j@J}`RuD8>8d%(S6F3&yID$lVQ8eAw&GhMvwlqyh7XP~cv1 zy7Qd`xIp~Z#No8F6sderm{7w$(*8Qb&GA%G?@=-3yK;&`%aL$P%cz&gi)B(59Ha#E zY0>fQqZ09XOOuONJi>pHe6PrrdO&wCFREhaKyI+i-m;AuKgaBgNN@epq`l|st%d#F zo#knSNkQHxXaW(NGy*bnoNK7I>Vc6M5WKk`T7i&D_Dx^?{g{-WXW63A3G2pe#303h z3-i<}>3WP7`oBwOi=n6)w_na2R~#Ms_T6dOvuK>k{R!!PPWt8tMu7`g9-wWiy2r7F za-oHk?CdyK4URa;mddhQXtPzaw_&)Mgt{xB#erXfc|Xjitc_KC#`&27#^GX6U*4cv zS0R+(eg5f=^M~t?R=%r5t=o4Q+@2@l`d((N**{chWouv@zO0jXm?M=HGx^#1&Lo(d zmm(l#RlaS(uVY{Kv5V{x@4+J-m8Q80>2(&}PKHc#Q=RGb$GfF&J91eIbsHsv3R#r3 zPAXv*#4)iXnwnq@{pzC(8l?LPePr|Ws_Bo%#WfuGbeZ1yVFi?TrLtvp*5LKB&qVSL zvyOjdKlq$DawSB*a-|`2E1xtv0sN|lN=b>?-}I6y@{WzJu$&OlHEec2jpiC(bkfpz zMH`%`%fz$h0o9rMW;q?TiBj{#>Z!|hOL!G;nX66t%U7(6wE8&d!+Ci|e}r8r&hd@) zuG&^xZh6Wx#uc!Y>vvT+GHJO;I;J7WqIvbfTaV^pk!xn#KQ3XFH;nF}yxZM>P+~6b z$5T5wQ~SuIx{EyPEm%^dlAJ)yax7yL^ob~#VwH=dd((<52+EWv{kI-N+^AgHovHBT1!}Zbo=X-%=vc4LR{vH zKPm~cTlXjUa6Ww_`;dEZd*&PX6Gynrw$oUjRm8imxv$z3O4U1#rg$MtdNhX9x2$|< zey%*0+`k?{?eQ^3fl#m$^CjVR-B5hG#|{V`5?Q)DF%+(m^GQc?b~i?wII6@v2rup3 z)^_n3^7(FGb_GFZ={<4Qy=`?`F#=YNERx&Qn@*1ho;|%5!6^0WE?Kfm(2zoWPnKWg zc1-`Y&0@db*UOn+ehmiGul*d@tB?_KOgo3xSn$7Nm+upPGw`Xg^H*w++kRwdSfjhM zvlo!6xWXjMB9;@Et-dTV0}Z<3ZR^Jp-S(<}_+m61rNVwvxE8c+vOS``H+TDyy^td^f{{rY zB}mue-ywdR)k)2)ULsE23{RbIxZvWubVF*2JIbj7=IUi%gmaEb` z<&dpStBPi>iquapucKcjbm!)_S>3sSV#KB*QLpZ0lXn-N8Z8Fb^LCb`E-Q9VZ^LZn zwP#3#q`S;XaxVM5Nq#M#tfJovOy9VCvn1T8f-kg2dA;4hMDBn+xG8Jd{ZOJ$w5^eUlFNpe1N|BrpQ!pH$j1n><=}@F?T(Ir^#q& z@;pZEQ%9p4TY}wPh)If4g{*6%K`Ft;Hd1_!y<3Z%0qG;l+PP+Up;y0%& z6a>5WC*~rlhB;0B|62hsUhM!*6(zcMIVUCSzE?fO%b@}E|}=Y?BS;qVW*`O zsgAto5eNIs5@wfZ+)v|s>=fFGILx!nv^$TS&oO`^oz>v$7#(DZ$*6aLd%ThWfp&pc zaX`QS`v3)(KyqR#LJU&!;!KucCy3R*MMnSpW$3a4=9hsw1B}OY2F_=JrtAIXi~j&H z7+Bju{-s9+-K=>pNgX~HHUW%GifF%2ul%nlHrAGmE|wNWYSY#eoEV-JN0-Up&xwx9 zzbH$gO>1W@6f3BPM4wZ8e-Vjzs*+G=A2~k#_Q+u`mTaFo zn?oLX3lR_|hI%uj+X) zx2%#hHBsLj6PC7pP;;CTGxZZ9GD9#*^LYe}JcuW*8!AM(O4%;>Fy9lG>xqK1;5WYMnaYiAkmEmPqcI zZjug@_%$8dSDzNG<|@^J0?!Hw_MiOMrVbxts>-%uJ%2uA)L|x{@NTBzYLukkXz{yr zqAGt9iS+s>ITbTRm#^bH4Dq{rKHOUTvbR&ajK&u+7H_%uxsR?8&GfrYinhtbOQ%aF zU-I|xRKE|~wn;S_v{-?#*=!P$XNjxtyzu__!>X=DT%Udc4T} zYVv!}GVQlncb1xWR}qp6vRMwcNb7JTc$eCDg3N^=rX+;x>s~!N8j&S>^g;E{->qHx zrW}UVg`=N=i}Xeb83<#k517N!IqqCa`&T|w@?jTovdTl7NS{9+)|Daw2C z+Z+yrUVf_rmp;2zcsqYxdEF`~Q{{5CP=4o!DN@O&4s1~ciw0RIF_y_go10#z2DV@6 z1*)=UCP0+21paXTAEFI3PT1I4+d%9bOd4m$SZ9M0G;D3jBbXeU4U!fws51?-j=o+W_0VG*c%N!-nk39wzD0 zE@{S&zCM~oX?ofYSs7VIgv%oS4pCbnO4UH^76e4>3zq>o{|vI1e*F~sFAUJC5cKok zn0N9uR1mb$LEd;TO@UwV2L50Je~zClA7}}ff4?RtBrhc{rmVs!Cw_te0zq?tj=ur@ z1Qy8uJQ$z@p(fzJp+#i=_6;B=pZdo9Ki@bR4o(^gmG<~|p`Td){tuMvDlj8~Q?TYV zH7o%B8GyU+0f1^gmZk=OZ31vL%|8!R5NG%}_BN2eqX0)MPoWgSp#0vQ`?t$ce`$VQ z;OD#$z)rvg0x=$c8~7~qnS}ZJKjnLV-QfwFvif_TcA}`?*zOaTn*0Ei3&6k$t$cd? zl=+wfdV{0svw2>pDon#7uiVAeS_e=AfUc)Gh6O&$e7FeVBEytzunxDIZ?nA`0g>$uO>XD zgv)i_9Yx1Us;AS1Vf8g!)FbEykTC-~gNCK!r_6^6a8WotaUPlMNo`tKj^#Zr)<-~N zY=BzR(6cSaPnpm5ZMYo&xP{O!@Ay>%OB@W8in4QsudpkDifI{;yiBzc^0bHo#}_r>YXa1W50H_4jS-$0TJw{%n6^fGX?vk&jbtflraU zlV;=%7;PW0>ft}e|J8^Y?!Qq$Jww$>-`dg25J(mO&(=VVxe8;sG#lveH-Iri?~Xry z%6#g?{zi4;D*xG<$MePL1^`7j&@E>v%B22A0d39ypfG4FA}e<>=Yf)}a=;4VY_FTi z{TIc5>j~B2?7lA*I^mB^#0CZZ}7jVsgLmzI1-4Bp@8-B9NQA`S?1GW@;8Rx z=)jg81bFdh@W;7+SXF7;CypTjCM6H_x$qg9QeaN?7cW1KzyC+R@z3}86ahYefbXmc z9g3X)%!(O$<>f9Q`&p~acnBByY#H-2vZGB7y| zkp1}5i4)lwyw70(%=KS|oHIdy%0B-#yOtaX85(52(|k{mk$ulWKCP8M#f5?4gi;-se`Gt)t@baDqi8$R^~b|%I%WEss}X|J`WJFPZSRyl>NDz2mxR(3V!u@l*6g9`rhZKW%=r5H1DG zr1<;^SmDp|hG+m83+MyEGx~pC3>O*hR33V|;*9J5^EMLJRQnSAt!4l==;Z$Flrr%J zT(|KAV1$3u9Z5tT>szR=BO2@ z04-(w0$@5jz*N76%XHQij{WQ}ZFx5g3Qq#`c6?RCadXOia+}~XoE^f>8DO9h)DT6k zWgL*5^^d_ZwB;ORdjpZPg_$i3&s(!W`{4zeXCD#J->87`pVdxP8(aqXeSVI1plz5T z5PVMsFf0OXI2-M#yWlcF1IEcHAPE5*%9>i5I{bwq6yq(8889!sgbnLrX}xeMPS#TA z%z2=KYtO_w$^y#Q0FddA`xN*r^HCgyiwY-9|LJH@WOhog^-KV{3oyyEDp?$Zi+t83 zj~(s2euYv*%(L%m05;hK(1@H7esU5n#d&M-WBdR2Jlr{vntNF2$R8lY2fF&KwqmBv zB|Npd0sNa8zP299Z4Si%+lOy~H!`$mnqv47F7mGt=d=^R$1PIwbF~5_-U3u~R$`%L zxVSJ@14#T&xeVA*>q4KftR$cZ`#+p&cLgr;S(86j`Clv^^s0u(5Rg3&kR8}l`}ZmH ziQ0h6a9;NU;|$Oa_zD8$D*FclB!5mKWWT~CIophLCjC(B^o$?yI(9&=r}*ilxPb<&{1N^24&XAKjm>8$;I~C%M9dPHM}aN^?K!*L{sKaP zH;aH1;waR?rVh#uc3=mH@j07nyuyb!?f?`|0hOJdSZpG|r8&=9|A!{&%3aNcPOh~9 zzO%9Q@ddbC@S3FTXk%kx3OQ%~KDMMZCDSNb}Pwi0AegFupf1HYJCX}E;w`?J>}u> z)i19cL7oYS3|)ku-AO^mf{Xfx$bVBv7w4QQv~yAZ(Dfnqx#%a;p}#?=kSG>lUIBqF z--KPrbmPEjm}(*5lqR^Pwk$3;#VhP|Nj1i6h8E;3Z|6P?3cR|zXO?A3cG=rT0tpr76aI%k-KY6SKUxD#YTI=IL% z8acTU?zGI%0T}iUvJ>R*^l*`(8u?GpfxS8A1T~iJTvV9lij}>iCB*L6wEWaPGO+T) z-gk1sB+UVr=@%dn58>W^0*elNzsLzXH&AK^&f@y){2adfz?P;x0aX$`7xcVBwXhPy zmV7$_SC@keex7fhGoytqM|Ofwpa2*Dl&zfq#sao>(Fv-kGF(*nQrlS@IvPOi&YMa> z?H#r%&k56z3S1_b3Fq-h`qv33Y>kl<5_4cF_Mc|+?jcLw#J6vz|B{Z1OHg~^mU0q1uAzk%EQn>VE>gAB|$K_DaGpLY}>&^CYs0{uV9 C==8Jz literal 0 HcmV?d00001 diff --git a/lib/licenses/jctools-core-1.2.1.txt b/lib/licenses/jctools-core-1.2.1.txt new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/lib/licenses/jctools-core-1.2.1.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tools/stress/src/org/apache/cassandra/stress/Operation.java b/tools/stress/src/org/apache/cassandra/stress/Operation.java index 92a54f98db..dc5bd2ff8b 100644 --- a/tools/stress/src/org/apache/cassandra/stress/Operation.java +++ b/tools/stress/src/org/apache/cassandra/stress/Operation.java @@ -20,11 +20,11 @@ package org.apache.cassandra.stress; import java.io.IOException; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.SettingsLog; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JavaDriverClient; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.InvalidRequestException; import org.apache.cassandra.transport.SimpleClient; @@ -106,7 +106,7 @@ public abstract class Operation exceptionMessage = getExceptionMessage(e); } } - + timer.stop(run.partitionCount(), run.rowCount(), !success); if (!success) @@ -138,11 +138,6 @@ public abstract class Operation System.err.println(message); } - public void close() - { - timer.close(); - } - public void intendedStartNs(long intendedTime) { timer.intendedTimeNs(intendedTime); diff --git a/tools/stress/src/org/apache/cassandra/stress/StressAction.java b/tools/stress/src/org/apache/cassandra/stress/StressAction.java index 7c37ef8be9..f3d9cbd1c8 100644 --- a/tools/stress/src/org/apache/cassandra/stress/StressAction.java +++ b/tools/stress/src/org/apache/cassandra/stress/StressAction.java @@ -22,6 +22,7 @@ import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; +import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -29,12 +30,15 @@ import java.util.concurrent.locks.LockSupport; import org.apache.cassandra.stress.operations.OpDistribution; import org.apache.cassandra.stress.operations.OpDistributionFactory; +import org.apache.cassandra.stress.report.StressMetrics; import org.apache.cassandra.stress.settings.ConnectionAPI; import org.apache.cassandra.stress.settings.SettingsCommand; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JavaDriverClient; import org.apache.cassandra.stress.util.ThriftClient; import org.apache.cassandra.transport.SimpleClient; +import org.jctools.queues.SpscArrayQueue; +import org.jctools.queues.SpscUnboundedArrayQueue; import com.google.common.util.concurrent.Uninterruptibles; @@ -183,8 +187,8 @@ public class StressAction implements Runnable double improvement = 0; for (int i = results.size() - count ; i < results.size() ; i++) { - double prev = results.get(i - 1).getTiming().getHistory().opRate(); - double cur = results.get(i).getTiming().getHistory().opRate(); + double prev = results.get(i - 1).opRate(); + double cur = results.get(i).opRate(); improvement += (cur - prev) / prev; } return improvement / count; @@ -219,7 +223,7 @@ public class StressAction implements Runnable final Consumer[] consumers = new Consumer[threadCount]; for (int i = 0; i < threadCount; i++) { - consumers[i] = new Consumer(operations.get(metrics.getTiming(), isWarmup), + consumers[i] = new Consumer(operations, isWarmup, done, start, releaseConsumers, workManager, metrics, rateLimiter); } @@ -238,7 +242,9 @@ public class StressAction implements Runnable } // start counting from NOW! if(rateLimiter != null) + { rateLimiter.start(); + } // release the hounds!!! releaseConsumers.countDown(); @@ -353,18 +359,28 @@ public class StressAction implements Runnable return op; } - void close() - { - operations.closeTimers(); - } - void abort() { workManager.stop(); } } - - private class Consumer extends Thread + public static class OpMeasurement + { + public String opType; + public long intended,started,ended,rowCnt,partitionCnt; + public boolean err; + @Override + public String toString() + { + return "OpMeasurement [opType=" + opType + ", intended=" + intended + ", started=" + started + ", ended=" + + ended + ", rowCnt=" + rowCnt + ", partitionCnt=" + partitionCnt + ", err=" + err + "]"; + } + } + public interface MeasurementSink + { + void record(String opType,long intended, long started, long ended, long rowCnt, long partitionCnt, boolean err); + } + public class Consumer extends Thread implements MeasurementSink { private final StreamOfOperations opStream; private final StressMetrics metrics; @@ -372,8 +388,10 @@ public class StressAction implements Runnable private final CountDownLatch done; private final CountDownLatch start; private final CountDownLatch releaseConsumers; - - public Consumer(OpDistribution operations, + public final Queue measurementsRecycling; + public final Queue measurementsReporting; + public Consumer(OpDistributionFactory operations, + boolean isWarmup, CountDownLatch done, CountDownLatch start, CountDownLatch releaseConsumers, @@ -381,13 +399,18 @@ public class StressAction implements Runnable StressMetrics metrics, UniformRateLimiter rateLimiter) { + OpDistribution opDistribution = operations.get(isWarmup, this); this.done = done; this.start = start; this.releaseConsumers = releaseConsumers; this.metrics = metrics; - this.opStream = new StreamOfOperations(operations, rateLimiter, workManager); + this.opStream = new StreamOfOperations(opDistribution, rateLimiter, workManager); + this.measurementsRecycling = new SpscArrayQueue(8*1024); + this.measurementsReporting = new SpscUnboundedArrayQueue(2048); + metrics.add(this); } + public void run() { try @@ -464,8 +487,24 @@ public class StressAction implements Runnable finally { done.countDown(); - opStream.close(); } } + + @Override + public void record(String opType, long intended, long started, long ended, long rowCnt, long partitionCnt, boolean err) + { + OpMeasurement opMeasurement = measurementsRecycling.poll(); + if(opMeasurement == null) { + opMeasurement = new OpMeasurement(); + } + opMeasurement.opType = opType; + opMeasurement.intended = intended; + opMeasurement.started = started; + opMeasurement.ended = ended; + opMeasurement.rowCnt = rowCnt; + opMeasurement.partitionCnt = partitionCnt; + opMeasurement.err = err; + measurementsReporting.offer(opMeasurement); + } } } diff --git a/tools/stress/src/org/apache/cassandra/stress/StressGraph.java b/tools/stress/src/org/apache/cassandra/stress/StressGraph.java index 3b383fad16..196964c30d 100644 --- a/tools/stress/src/org/apache/cassandra/stress/StressGraph.java +++ b/tools/stress/src/org/apache/cassandra/stress/StressGraph.java @@ -35,7 +35,7 @@ import java.util.regex.Pattern; import com.google.common.io.ByteStreams; import org.apache.commons.lang3.StringUtils; - +import org.apache.cassandra.stress.report.StressMetrics; import org.apache.cassandra.stress.settings.StressSettings; import org.json.simple.JSONArray; import org.json.simple.JSONObject; diff --git a/tools/stress/src/org/apache/cassandra/stress/StressProfile.java b/tools/stress/src/org/apache/cassandra/stress/StressProfile.java index 1343cf12dd..b45462f93a 100644 --- a/tools/stress/src/org/apache/cassandra/stress/StressProfile.java +++ b/tools/stress/src/org/apache/cassandra/stress/StressProfile.java @@ -50,11 +50,11 @@ import org.apache.cassandra.stress.operations.userdefined.TokenRangeQuery; import org.apache.cassandra.stress.operations.userdefined.SchemaInsert; import org.apache.cassandra.stress.operations.userdefined.SchemaQuery; import org.apache.cassandra.stress.operations.userdefined.ValidatingSchemaQuery; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.*; import org.apache.cassandra.stress.util.JavaDriverClient; import org.apache.cassandra.stress.util.MultiPrintStream; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.Compression; import org.apache.cassandra.thrift.ThriftConversion; import org.apache.thrift.TException; diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/FixedOpDistribution.java b/tools/stress/src/org/apache/cassandra/stress/operations/FixedOpDistribution.java index 9a3522c8b8..aa5ddfdf12 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/FixedOpDistribution.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/FixedOpDistribution.java @@ -36,9 +36,4 @@ public class FixedOpDistribution implements OpDistribution { return operation; } - - public void closeTimers() - { - operation.close(); - } } diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/OpDistribution.java b/tools/stress/src/org/apache/cassandra/stress/operations/OpDistribution.java index 33a0c93b66..bef8954b8c 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/OpDistribution.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/OpDistribution.java @@ -25,8 +25,5 @@ import org.apache.cassandra.stress.Operation; public interface OpDistribution { - Operation next(); - - public void closeTimers(); } diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/OpDistributionFactory.java b/tools/stress/src/org/apache/cassandra/stress/operations/OpDistributionFactory.java index 14e6dfbcfd..2477c36b96 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/OpDistributionFactory.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/OpDistributionFactory.java @@ -21,11 +21,11 @@ package org.apache.cassandra.stress.operations; */ -import org.apache.cassandra.stress.util.Timing; +import org.apache.cassandra.stress.StressAction.MeasurementSink; public interface OpDistributionFactory { - public OpDistribution get(Timing timing, boolean isWarmup); + public OpDistribution get(boolean isWarmup, MeasurementSink sink); public String desc(); Iterable each(); } diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/PartitionOperation.java b/tools/stress/src/org/apache/cassandra/stress/operations/PartitionOperation.java index 93290fca7e..bad0a94e0c 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/PartitionOperation.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/PartitionOperation.java @@ -29,9 +29,9 @@ import org.apache.cassandra.stress.generate.PartitionIterator; import org.apache.cassandra.stress.generate.RatioDistribution; import org.apache.cassandra.stress.generate.Seed; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.OptionRatioDistribution; import org.apache.cassandra.stress.settings.StressSettings; -import org.apache.cassandra.stress.util.Timer; public abstract class PartitionOperation extends Operation { diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/SampledOpDistribution.java b/tools/stress/src/org/apache/cassandra/stress/operations/SampledOpDistribution.java index fc0229e97a..6d7f9e4205 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/SampledOpDistribution.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/SampledOpDistribution.java @@ -51,12 +51,4 @@ public class SampledOpDistribution implements OpDistribution remaining--; return cur; } - - public void closeTimers() - { - for (Pair op : operations.getPmf()) - { - op.getFirst().close(); - } - } } diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/SampledOpDistributionFactory.java b/tools/stress/src/org/apache/cassandra/stress/operations/SampledOpDistributionFactory.java index 0b206f9953..180003919f 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/SampledOpDistributionFactory.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/SampledOpDistributionFactory.java @@ -21,17 +21,19 @@ package org.apache.cassandra.stress.operations; */ -import java.util.*; - -import org.apache.cassandra.stress.generate.*; -import org.apache.cassandra.stress.util.Timing; -import org.apache.commons.math3.distribution.EnumeratedDistribution; -import org.apache.commons.math3.util.Pair; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import org.apache.cassandra.stress.Operation; +import org.apache.cassandra.stress.StressAction.MeasurementSink; import org.apache.cassandra.stress.generate.DistributionFactory; +import org.apache.cassandra.stress.generate.DistributionFixed; import org.apache.cassandra.stress.generate.PartitionGenerator; -import org.apache.cassandra.stress.util.Timer; +import org.apache.cassandra.stress.report.Timer; +import org.apache.commons.math3.distribution.EnumeratedDistribution; +import org.apache.commons.math3.util.Pair; public abstract class SampledOpDistributionFactory implements OpDistributionFactory { @@ -47,13 +49,13 @@ public abstract class SampledOpDistributionFactory implements OpDistributionF protected abstract List get(Timer timer, PartitionGenerator generator, T key, boolean isWarmup); protected abstract PartitionGenerator newGenerator(); - public OpDistribution get(Timing timing, boolean isWarmup) + public OpDistribution get(boolean isWarmup, MeasurementSink sink) { PartitionGenerator generator = newGenerator(); List> operations = new ArrayList<>(); for (Map.Entry ratio : ratios.entrySet()) { - List ops = get(timing.newTimer(ratio.getKey().toString()), + List ops = get(new Timer(ratio.getKey().toString(), sink), generator, ratio.getKey(), isWarmup); for (Operation op : ops) operations.add(new Pair<>(op, ratio.getValue() / ops.size())); @@ -76,9 +78,9 @@ public abstract class SampledOpDistributionFactory implements OpDistributionF { out.add(new OpDistributionFactory() { - public OpDistribution get(Timing timing, boolean isWarmup) + public OpDistribution get(boolean isWarmup, MeasurementSink sink) { - List ops = SampledOpDistributionFactory.this.get(timing.newTimer(ratio.getKey().toString()), + List ops = SampledOpDistributionFactory.this.get(new Timer(ratio.getKey().toString(), sink), newGenerator(), ratio.getKey(), isWarmup); diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlCounterAdder.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlCounterAdder.java index bb8135cb69..2874b4e411 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlCounterAdder.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlCounterAdder.java @@ -29,9 +29,9 @@ import org.apache.cassandra.stress.generate.Distribution; import org.apache.cassandra.stress.generate.DistributionFactory; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.StressSettings; -import org.apache.cassandra.stress.util.Timer; public class CqlCounterAdder extends CqlOperation { diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlCounterGetter.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlCounterGetter.java index d91ab37458..eb908b0138 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlCounterGetter.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlCounterGetter.java @@ -27,9 +27,9 @@ import java.util.List; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.StressSettings; -import org.apache.cassandra.stress.util.Timer; public class CqlCounterGetter extends CqlOperation { diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlInserter.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlInserter.java index fdf50070c2..3fda6c2c9d 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlInserter.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlInserter.java @@ -27,9 +27,9 @@ import java.util.List; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.StressSettings; -import org.apache.cassandra.stress.util.Timer; public class CqlInserter extends CqlOperation { diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlOperation.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlOperation.java index 097c1a054f..647ba87e59 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlOperation.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlOperation.java @@ -31,12 +31,12 @@ import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.ConnectionStyle; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JavaDriverClient; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.Compression; import org.apache.cassandra.thrift.CqlResult; import org.apache.cassandra.thrift.CqlRow; diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlReader.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlReader.java index 0ee17e93e6..61c6553612 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlReader.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/CqlReader.java @@ -28,9 +28,9 @@ import java.util.List; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.StressSettings; -import org.apache.cassandra.stress.util.Timer; public class CqlReader extends CqlOperation { diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/PredefinedOperation.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/PredefinedOperation.java index 1f9a2c81b7..db355047ba 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/PredefinedOperation.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/PredefinedOperation.java @@ -26,10 +26,10 @@ import java.util.concurrent.ThreadLocalRandom; import org.apache.cassandra.stress.Operation; import org.apache.cassandra.stress.generate.*; import org.apache.cassandra.stress.operations.PartitionOperation; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.CqlVersion; import org.apache.cassandra.stress.settings.StressSettings; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.SlicePredicate; import org.apache.cassandra.thrift.SliceRange; diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftCounterAdder.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftCounterAdder.java index be34a0723a..42f8bc9059 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftCounterAdder.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftCounterAdder.java @@ -28,10 +28,10 @@ import org.apache.cassandra.stress.generate.Distribution; import org.apache.cassandra.stress.generate.DistributionFactory; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.ColumnOrSuperColumn; import org.apache.cassandra.thrift.CounterColumn; import org.apache.cassandra.thrift.Mutation; diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftCounterGetter.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftCounterGetter.java index ca81fe9b11..4bec3b2bfa 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftCounterGetter.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftCounterGetter.java @@ -23,10 +23,10 @@ import java.util.List; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.ColumnParent; import org.apache.cassandra.thrift.SlicePredicate; diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftInserter.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftInserter.java index 1827c06b98..ecaa140e25 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftInserter.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftInserter.java @@ -26,10 +26,10 @@ import java.util.Map; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.Column; import org.apache.cassandra.thrift.ColumnOrSuperColumn; import org.apache.cassandra.thrift.Mutation; diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftReader.java b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftReader.java index d77dc6a81d..4d530b9266 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftReader.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/predefined/ThriftReader.java @@ -23,10 +23,10 @@ import java.util.List; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.Command; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.ColumnOrSuperColumn; import org.apache.cassandra.thrift.ColumnParent; diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaInsert.java b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaInsert.java index 9eebce21b3..96b3392983 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaInsert.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaInsert.java @@ -43,10 +43,10 @@ import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.io.sstable.CQLSSTableWriter; import org.apache.cassandra.stress.WorkManager; import org.apache.cassandra.stress.generate.*; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JavaDriverClient; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; public class SchemaInsert extends SchemaStatement { diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaQuery.java b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaQuery.java index 0d8e756efc..2764704052 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaQuery.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaQuery.java @@ -33,10 +33,10 @@ import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.stress.generate.*; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JavaDriverClient; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.CqlResult; import org.apache.cassandra.thrift.ThriftConversion; diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaStatement.java b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaStatement.java index c83787b378..ca1f5fa679 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaStatement.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/SchemaStatement.java @@ -32,9 +32,9 @@ import com.datastax.driver.core.PreparedStatement; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.stress.generate.Row; import org.apache.cassandra.stress.operations.PartitionOperation; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JavaDriverClient; -import org.apache.cassandra.stress.util.Timer; public abstract class SchemaStatement extends PartitionOperation { diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/TokenRangeQuery.java b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/TokenRangeQuery.java index 7a0f02d8f4..f561f6101e 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/TokenRangeQuery.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/TokenRangeQuery.java @@ -39,10 +39,10 @@ import org.apache.cassandra.stress.Operation; import org.apache.cassandra.stress.StressYaml; import org.apache.cassandra.stress.WorkManager; import org.apache.cassandra.stress.generate.TokenRangeIterator; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JavaDriverClient; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; public class TokenRangeQuery extends Operation { diff --git a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/ValidatingSchemaQuery.java b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/ValidatingSchemaQuery.java index 4547a37bae..a731b99b51 100644 --- a/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/ValidatingSchemaQuery.java +++ b/tools/stress/src/org/apache/cassandra/stress/operations/userdefined/ValidatingSchemaQuery.java @@ -33,10 +33,10 @@ import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.stress.generate.*; import org.apache.cassandra.stress.generate.Row; import org.apache.cassandra.stress.operations.PartitionOperation; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JavaDriverClient; import org.apache.cassandra.stress.util.ThriftClient; -import org.apache.cassandra.stress.util.Timer; import org.apache.cassandra.thrift.Compression; import org.apache.cassandra.thrift.CqlResult; import org.apache.cassandra.thrift.CqlRow; diff --git a/tools/stress/src/org/apache/cassandra/stress/StressMetrics.java b/tools/stress/src/org/apache/cassandra/stress/report/StressMetrics.java similarity index 57% rename from tools/stress/src/org/apache/cassandra/stress/StressMetrics.java rename to tools/stress/src/org/apache/cassandra/stress/report/StressMetrics.java index 86e9a7af4b..a5965471a2 100644 --- a/tools/stress/src/org/apache/cassandra/stress/StressMetrics.java +++ b/tools/stress/src/org/apache/cassandra/stress/report/StressMetrics.java @@ -1,4 +1,4 @@ -package org.apache.cassandra.stress; +package org.apache.cassandra.stress.report; /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -25,41 +25,57 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import java.io.FileNotFoundException; import java.io.PrintStream; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; import org.HdrHistogram.Histogram; import org.HdrHistogram.HistogramLogWriter; +import org.apache.cassandra.stress.StressAction.Consumer; +import org.apache.cassandra.stress.StressAction.MeasurementSink; +import org.apache.cassandra.stress.StressAction.OpMeasurement; +import org.apache.cassandra.stress.settings.SettingsLog.Level; import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.stress.util.JmxCollector; -import org.apache.cassandra.stress.util.Timing; -import org.apache.cassandra.stress.util.TimingInterval; -import org.apache.cassandra.stress.util.TimingIntervals; +import org.apache.cassandra.stress.util.JmxCollector.GcStats; import org.apache.cassandra.stress.util.Uncertainty; import org.apache.cassandra.utils.FBUtilities; import org.apache.commons.lang3.time.DurationFormatUtils; -public class StressMetrics +public class StressMetrics implements MeasurementSink { - + private final List consumers = new ArrayList<>(); private final PrintStream output; private final Thread thread; private final Uncertainty rowRateUncertainty = new Uncertainty(); private final CountDownLatch stopped = new CountDownLatch(1); - private final Timing timing; private final Callable gcStatsCollector; private final HistogramLogWriter histogramWriter; private final long epochNs = System.nanoTime(); private final long epochMs = System.currentTimeMillis(); - private volatile JmxCollector.GcStats totalGcStats; + private volatile JmxCollector.GcStats totalGcStats = new GcStats(0); private volatile boolean stop = false; private volatile boolean cancelled = false; + + // collected data for intervals and summary + private final Map opTypeToCurrentTimingInterval = new TreeMap<>(); + private final Map opTypeToSummaryTimingInterval = new TreeMap<>(); + private final Queue leftovers = new ArrayDeque<>(); + private final TimingInterval totalCurrentInterval; + private final TimingInterval totalSummaryInterval; + public StressMetrics(PrintStream output, final long logIntervalMillis, StressSettings settings) { this.output = output; @@ -70,9 +86,10 @@ public class StressMetrics histogramWriter = new HistogramLogWriter(settings.log.hdrFile); histogramWriter.outputComment("Logging op latencies for Cassandra Stress"); histogramWriter.outputLogFormatVersion(); - histogramWriter.outputBaseTime(epochMs); - histogramWriter.setBaseTime(epochMs); - histogramWriter.outputStartTime(epochMs); + final long roundedEpoch = epochMs - (epochMs%1000); + histogramWriter.outputBaseTime(roundedEpoch); + histogramWriter.setBaseTime(roundedEpoch); + histogramWriter.outputStartTime(roundedEpoch); histogramWriter.outputLegend(); } catch (FileNotFoundException e) @@ -92,10 +109,9 @@ public class StressMetrics } catch (Throwable t) { - switch (settings.log.level) + if (settings.log.level == Level.VERBOSE) { - case VERBOSE: - t.printStackTrace(); + t.printStackTrace(); } System.err.println("Failed to connect over JMX; not collecting these stats"); gcStatsCollector = new Callable() @@ -107,56 +123,14 @@ public class StressMetrics }; } this.gcStatsCollector = gcStatsCollector; - this.timing = new Timing(settings.rate.isFixed); - + this.totalCurrentInterval = new TimingInterval(settings.rate.isFixed); + this.totalSummaryInterval = new TimingInterval(settings.rate.isFixed); printHeader("", output); - thread = new Thread(new Runnable() - { - @Override - public void run() - { - timing.start(); - try { - - while (!stop) - { - try - { - long sleepNanos = timing.getHistory().endNanos() - System.nanoTime(); - long sleep = (sleepNanos / 1000000) + logIntervalMillis; - - if (sleep < logIntervalMillis >>> 3) - // if had a major hiccup, sleep full interval - Thread.sleep(logIntervalMillis); - else - Thread.sleep(sleep); - - update(); - } catch (InterruptedException e) - { - break; - } - } - - update(); - } - catch (InterruptedException e) - {} - catch (Exception e) - { - cancel(); - e.printStackTrace(StressMetrics.this.output); - } - finally - { - rowRateUncertainty.wakeAll(); - stopped.countDown(); - } - } + thread = new Thread(() -> { + reportingLoop(logIntervalMillis); }); thread.setName("StressMetrics"); } - public void start() { thread.start(); @@ -182,33 +156,163 @@ public class StressMetrics stopped.await(); } - private void update() throws InterruptedException + + private void reportingLoop(final long logIntervalMillis) { - Timing.TimingResult result = timing.snap(gcStatsCollector); - totalGcStats = JmxCollector.GcStats.aggregate(Arrays.asList(totalGcStats, result.extra)); - TimingInterval current = result.intervals.combine(); - TimingInterval history = timing.getHistory().combine(); - rowRateUncertainty.update(current.adjustedRowRate()); - if (current.operationCount() != 0) + // align report timing to the nearest second + final long currentTimeMs = System.currentTimeMillis(); + final long startTimeMs = currentTimeMs - (currentTimeMs % 1000); + // reporting interval starts rounded to the second + long reportingStartNs = (System.nanoTime() - TimeUnit.MILLISECONDS.toNanos(currentTimeMs - startTimeMs)); + final long parkIntervalNs = TimeUnit.MILLISECONDS.toNanos(logIntervalMillis); + try + { + while (!stop) + { + final long wakupTarget = reportingStartNs + parkIntervalNs; + sleepUntil(wakupTarget); + if (stop) + { + break; + } + recordInterval(wakupTarget, parkIntervalNs); + reportingStartNs += parkIntervalNs; + } + + final long end = System.nanoTime(); + recordInterval(end, end - reportingStartNs); + } + catch (Exception e) + { + e.printStackTrace(); + cancel(); + } + finally + { + rowRateUncertainty.wakeAll(); + stopped.countDown(); + } + } + + + private void sleepUntil(final long until) + { + long parkFor; + while (!stop && + (parkFor = until - System.nanoTime()) > 0) + { + LockSupport.parkNanos(parkFor); + } + } + + @Override + public void record(String opType, long intended, long started, long ended, long rowCnt, long partitionCnt, boolean err) + { + TimingInterval current = opTypeToCurrentTimingInterval.computeIfAbsent(opType, k -> new TimingInterval(totalCurrentInterval.isFixed)); + record(current, intended, started, ended, rowCnt, partitionCnt, err); + } + + private void record(TimingInterval t, long intended, long started, long ended, long rowCnt, long partitionCnt, boolean err) + { + t.rowCount += rowCnt; + t.partitionCount += partitionCnt; + if (err) + t.errorCount++; + if (intended != 0) { + t.responseTime().recordValue(ended-intended); + t.waitTime().recordValue(started-intended); + } + final long sTime = ended-started; + t.serviceTime().recordValue(sTime); + } + + private void recordInterval(long intervalEnd, long parkIntervalNs) throws InterruptedException + { + + drainConsumerMeasurements(intervalEnd, parkIntervalNs); + + GcStats gcStats = null; + try + { + gcStats = gcStatsCollector.call(); + } + catch (Exception e) + { + gcStats = new GcStats(0); + } + totalGcStats = JmxCollector.GcStats.aggregate(Arrays.asList(totalGcStats, gcStats)); + + rowRateUncertainty.update(totalCurrentInterval.adjustedRowRate()); + if (totalCurrentInterval.operationCount() != 0) { // if there's a single operation we only print the total - final boolean logPerOpSummaryLine = result.intervals.intervals().size() > 1; + final boolean logPerOpSummaryLine = opTypeToCurrentTimingInterval.size() > 1; - for (Map.Entry type : result.intervals.intervals().entrySet()) + for (Map.Entry type : opTypeToCurrentTimingInterval.entrySet()) { final String opName = type.getKey(); final TimingInterval opInterval = type.getValue(); if (logPerOpSummaryLine) { - printRow("", opName, opInterval, timing.getHistory().get(type.getKey()), result.extra, rowRateUncertainty, output); + printRow("", opName, opInterval, opTypeToSummaryTimingInterval.get(opName), gcStats, rowRateUncertainty, output); } logHistograms(opName, opInterval); + opInterval.reset(); } - printRow("", "total", current, history, result.extra, rowRateUncertainty, output); + printRow("", "total", totalCurrentInterval, totalSummaryInterval, gcStats, rowRateUncertainty, output); + totalCurrentInterval.reset(); } - if (timing.done()) - stop = true; + } + + private void drainConsumerMeasurements(long intervalEnd, long parkIntervalNs) + { + // record leftover measurements if any + int leftoversSize = leftovers.size(); + for (int i=0;i in = c.measurementsReporting; + Queue out = c.measurementsRecycling; + OpMeasurement last; + while ((last = in.poll()) != null) + { + if (last.ended > intervalEnd) + { + // measurements for any given consumer are ordered, we stop when we stop. + leftovers.add(last); + break; + } + record(last.opType, last.intended, last.started, last.ended, last.rowCnt, last.partitionCnt, last.err); + out.offer(last); + } + } + // set timestamps and summarize + for (Entry currPerOp : opTypeToCurrentTimingInterval.entrySet()) { + currPerOp.getValue().endNanos(intervalEnd); + currPerOp.getValue().startNanos(intervalEnd-parkIntervalNs); + TimingInterval summaryPerOp = opTypeToSummaryTimingInterval.computeIfAbsent(currPerOp.getKey(), k -> new TimingInterval(totalCurrentInterval.isFixed)); + summaryPerOp.add(currPerOp.getValue()); + totalCurrentInterval.add(currPerOp.getValue()); + } + totalCurrentInterval.endNanos(intervalEnd); + totalCurrentInterval.startNanos(intervalEnd-parkIntervalNs); + + totalSummaryInterval.add(totalCurrentInterval); } @@ -229,8 +333,12 @@ public class StressMetrics if (histogram.getTotalCount() != 0) { histogram.setTag(opName); - histogram.setStartTimeStamp(epochMs + NANOSECONDS.toMillis(startNs - epochNs)); - histogram.setEndTimeStamp(epochMs + NANOSECONDS.toMillis(endNs - epochNs)); + final long relativeStartNs = startNs - epochNs; + final long startMs = (long) (1000 *((epochMs + NANOSECONDS.toMillis(relativeStartNs))/1000.0)); + histogram.setStartTimeStamp(startMs); + final long relativeEndNs = endNs - epochNs; + final long endMs = (long) (1000 *((epochMs + NANOSECONDS.toMillis(relativeEndNs))/1000.0)); + histogram.setEndTimeStamp(endMs); histogramWriter.outputIntervalHistogram(histogram); } } @@ -278,8 +386,8 @@ public class StressMetrics output.println("\n"); output.println("Results:"); - TimingIntervals opHistory = timing.getHistory(); - TimingInterval history = opHistory.combine(); + TimingIntervals opHistory = new TimingIntervals(opTypeToSummaryTimingInterval); + TimingInterval history = this.totalSummaryInterval; output.println(String.format("Op rate : %,8.0f op/s %s", history.opRate(), opHistory.opRates())); output.println(String.format("Partition rate : %,8.0f pk/s %s", history.partitionRate(), opHistory.partitionRates())); output.println(String.format("Row rate : %,8.0f row/s %s", history.rowRate(), opHistory.rowRates())); @@ -310,7 +418,7 @@ public class StressMetrics printHeader(String.format(formatstr, "id"), out); for (int i = 0 ; i < ids.size() ; i++) { - for (Map.Entry type : summarise.get(i).timing.getHistory().intervals().entrySet()) + for (Map.Entry type : summarise.get(i).opTypeToSummaryTimingInterval.entrySet()) { printRow(String.format(formatstr, ids.get(i)), type.getKey(), @@ -320,7 +428,7 @@ public class StressMetrics summarise.get(i).rowRateUncertainty, out); } - TimingInterval hist = summarise.get(i).timing.getHistory().combine(); + TimingInterval hist = summarise.get(i).totalSummaryInterval; printRow(String.format(formatstr, ids.get(i)), "total", hist, @@ -332,14 +440,18 @@ public class StressMetrics } } - public Timing getTiming() - { - return timing; - } - public boolean wasCancelled() { return cancelled; } + public void add(Consumer consumer) + { + consumers.add(consumer); + } + + public double opRate() + { + return totalSummaryInterval.opRate(); + } } diff --git a/tools/stress/src/org/apache/cassandra/stress/report/Timer.java b/tools/stress/src/org/apache/cassandra/stress/report/Timer.java new file mode 100644 index 0000000000..b3df52f985 --- /dev/null +++ b/tools/stress/src/org/apache/cassandra/stress/report/Timer.java @@ -0,0 +1,63 @@ +package org.apache.cassandra.stress.report; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +import org.apache.cassandra.stress.StressAction.MeasurementSink; + +// a timer - this timer must be used by a single thread, and co-ordinates with other timers by +public final class Timer +{ + private final String opType; + private final MeasurementSink sink; + + // event timing info + private long intendedTimeNs; + private long startTimeNs; + + public Timer(String opType, MeasurementSink sink) + { + this.opType = opType; + this.sink = sink; + } + + + public void stop(long partitionCount, long rowCount, boolean error) + { + sink.record(opType, intendedTimeNs, startTimeNs, System.nanoTime(), rowCount, partitionCount, error); + resetTimes(); + } + + private void resetTimes() + { + intendedTimeNs = startTimeNs = 0; + } + + public void intendedTimeNs(long v) + { + intendedTimeNs = v; + } + + public void start() + { + startTimeNs = System.nanoTime(); + } +} \ No newline at end of file diff --git a/tools/stress/src/org/apache/cassandra/stress/util/TimingInterval.java b/tools/stress/src/org/apache/cassandra/stress/report/TimingInterval.java similarity index 64% rename from tools/stress/src/org/apache/cassandra/stress/util/TimingInterval.java rename to tools/stress/src/org/apache/cassandra/stress/report/TimingInterval.java index bb9587f49a..4d124a29f5 100644 --- a/tools/stress/src/org/apache/cassandra/stress/util/TimingInterval.java +++ b/tools/stress/src/org/apache/cassandra/stress/report/TimingInterval.java @@ -1,4 +1,4 @@ -package org.apache.cassandra.stress.util; +package org.apache.cassandra.stress.report; /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -26,93 +26,32 @@ import org.HdrHistogram.Histogram; // used for both single timer results and merged timer results public final class TimingInterval { - private final Histogram responseTime; - private final Histogram serviceTime; - private final Histogram waitTime; + private final Histogram responseTime = new Histogram(3); + private final Histogram serviceTime = new Histogram(3); + private final Histogram waitTime = new Histogram(3); public static final long[] EMPTY_SAMPLE = new long[0]; // nanos - private final long startNs; - private final long endNs; - public final long pauseStart; + private long startNs = Long.MAX_VALUE; + private long endNs = Long.MIN_VALUE; // discrete - public final long partitionCount; - public final long rowCount; - public final long errorCount; + public long partitionCount; + public long rowCount; + public long errorCount; public final boolean isFixed; + public TimingInterval(boolean isFixed){ + this.isFixed = isFixed; + } public String toString() { - return String.format("Start: %d end: %d maxLatency: %d pauseStart: %d" + - " pCount: %d rcount: %d opCount: %d errors: %d", - startNs, endNs, getLatencyHistogram().getMaxValue(), pauseStart, + return String.format("Start: %d end: %d maxLatency: %d pCount: %d rcount: %d opCount: %d errors: %d", + startNs, endNs, getLatencyHistogram().getMaxValue(), partitionCount, rowCount, getLatencyHistogram().getTotalCount(), errorCount); } - TimingInterval(long time) - { - startNs = endNs = time; - partitionCount = rowCount = errorCount = 0; - pauseStart = 0; - responseTime = new Histogram(3); - serviceTime = new Histogram(3); - waitTime = new Histogram(3); - isFixed = false; - } - - TimingInterval(long start, long end, long maxPauseStart, long partitionCount, - long rowCount, long errorCount, Histogram r, Histogram s, Histogram w, boolean isFixed) - { - this.startNs = start; - this.endNs = Math.max(end, start); - this.partitionCount = partitionCount; - this.rowCount = rowCount; - this.errorCount = errorCount; - this.pauseStart = maxPauseStart; - this.responseTime = r; - this.serviceTime = s; - this.waitTime = w; - this.isFixed = isFixed; - - } - - // merge multiple timer intervals together - static TimingInterval merge(Iterable intervals, long start) - { - long partitionCount = 0, rowCount = 0, errorCount = 0; - long end = 0; - long pauseStart = 0; - Histogram responseTime = new Histogram(3); - Histogram serviceTime = new Histogram(3); - Histogram waitTime = new Histogram(3); - boolean isFixed = false; - for (TimingInterval interval : intervals) - { - if (interval != null) - { - end = Math.max(end, interval.endNs); - partitionCount += interval.partitionCount; - rowCount += interval.rowCount; - errorCount += interval.errorCount; - - if (interval.getLatencyHistogram().getMaxValue() > serviceTime.getMaxValue()) - { - pauseStart = interval.pauseStart; - } - responseTime.add(interval.responseTime); - serviceTime.add(interval.serviceTime); - waitTime.add(interval.waitTime); - isFixed |= interval.isFixed; - } - } - - - return new TimingInterval(start, end, pauseStart, partitionCount, rowCount, - errorCount, responseTime, serviceTime, waitTime, isFixed); - - } public double opRate() { @@ -230,5 +169,66 @@ public final class TimingInterval { return getLatencyHistogram().getTotalCount(); } + + + public void startNanos(long started) + { + this.startNs = started; + } + public void endNanos(long ended) + { + this.endNs = ended; + } + + + public void reset() + { + this.endNs = Long.MIN_VALUE; + this.startNs = Long.MAX_VALUE; + this.errorCount = 0; + this.rowCount = 0; + this.partitionCount = 0; + if(this.responseTime.getTotalCount() != 0) + { + this.responseTime.reset(); + } + if(this.serviceTime.getTotalCount() != 0) + { + this.serviceTime.reset(); + } + if(this.waitTime.getTotalCount() != 0) + { + this.waitTime.reset(); + } + } + + public void add(TimingInterval value) + { + if(this.startNs > value.startNs) + { + this.startNs = value.startNs; + } + if(this.endNs < value.endNs) + { + this.endNs = value.endNs; + } + + this.errorCount += value.errorCount; + this.rowCount += value.rowCount; + this.partitionCount += value.partitionCount; + + if (value.responseTime.getTotalCount() != 0) + { + this.responseTime.add(value.responseTime); + } + if (value.serviceTime.getTotalCount() != 0) + { + this.serviceTime.add(value.serviceTime); + } + if (value.waitTime.getTotalCount() != 0) + { + this.waitTime.add(value.waitTime); + } + } } diff --git a/tools/stress/src/org/apache/cassandra/stress/util/TimingIntervals.java b/tools/stress/src/org/apache/cassandra/stress/report/TimingIntervals.java similarity index 72% rename from tools/stress/src/org/apache/cassandra/stress/util/TimingIntervals.java rename to tools/stress/src/org/apache/cassandra/stress/report/TimingIntervals.java index 05860065d1..747a42accc 100644 --- a/tools/stress/src/org/apache/cassandra/stress/util/TimingIntervals.java +++ b/tools/stress/src/org/apache/cassandra/stress/report/TimingIntervals.java @@ -1,4 +1,4 @@ -package org.apache.cassandra.stress.util; +package org.apache.cassandra.stress.report; import java.util.Arrays; import java.util.Map; @@ -7,46 +7,17 @@ import java.util.TreeMap; public class TimingIntervals { final Map intervals; - TimingIntervals(Iterable opTypes) - { - long now = System.nanoTime(); - intervals = new TreeMap<>(); - for (String opType : opTypes) - intervals.put(opType, new TimingInterval(now)); - } - TimingIntervals(Map intervals) + public TimingIntervals(Map intervals) { this.intervals = intervals; } - public TimingIntervals merge(TimingIntervals with, long start) - { - assert intervals.size() == with.intervals.size(); - TreeMap ret = new TreeMap<>(); - - for (String opType : intervals.keySet()) - { - assert with.intervals.containsKey(opType); - ret.put(opType, TimingInterval.merge(Arrays.asList(intervals.get(opType), with.intervals.get(opType)), start)); - } - - return new TimingIntervals(ret); - } - public TimingInterval get(String opType) { return intervals.get(opType); } - public TimingInterval combine() - { - long start = Long.MAX_VALUE; - for (TimingInterval ti : intervals.values()) - start = Math.min(start, ti.startNanos()); - - return TimingInterval.merge(intervals.values(), start); - } public String str(TimingInterval.TimingParameter value, String unit) { @@ -55,6 +26,11 @@ public class TimingIntervals public String str(TimingInterval.TimingParameter value, double rank, String unit) { + if (intervals.size() == 0) + { + return "[]"; + } + StringBuilder sb = new StringBuilder("["); for (Map.Entry entry : intervals.entrySet()) diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandPreDefined.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandPreDefined.java index c4257191ec..b7f87e95a6 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandPreDefined.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandPreDefined.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.cassandra.stress.StressAction.MeasurementSink; import org.apache.cassandra.stress.generate.DistributionFactory; import org.apache.cassandra.stress.generate.PartitionGenerator; import org.apache.cassandra.stress.generate.SeedManager; @@ -37,8 +38,8 @@ import org.apache.cassandra.stress.operations.FixedOpDistribution; import org.apache.cassandra.stress.operations.OpDistribution; import org.apache.cassandra.stress.operations.OpDistributionFactory; import org.apache.cassandra.stress.operations.predefined.PredefinedOperation; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.util.MultiPrintStream; -import org.apache.cassandra.stress.util.Timing; // Settings unique to the mixed command type public class SettingsCommandPreDefined extends SettingsCommand @@ -53,9 +54,11 @@ public class SettingsCommandPreDefined extends SettingsCommand final SeedManager seeds = new SeedManager(settings); return new OpDistributionFactory() { - public OpDistribution get(Timing timing, boolean isWarmup) + public OpDistribution get(boolean isWarmup, MeasurementSink sink) { - return new FixedOpDistribution(PredefinedOperation.operation(type, timing.newTimer(type.toString()), + final Timer timer1 = new Timer(type.toString(), sink); + final Timer timer = timer1; + return new FixedOpDistribution(PredefinedOperation.operation(type, timer, newGenerator(settings), seeds, settings, add)); } diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandPreDefinedMixed.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandPreDefinedMixed.java index 0e361baf60..9c58c5b7a8 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandPreDefinedMixed.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandPreDefinedMixed.java @@ -30,8 +30,8 @@ import org.apache.cassandra.stress.generate.SeedManager; import org.apache.cassandra.stress.operations.OpDistributionFactory; import org.apache.cassandra.stress.operations.SampledOpDistributionFactory; import org.apache.cassandra.stress.operations.predefined.PredefinedOperation; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.util.MultiPrintStream; -import org.apache.cassandra.stress.util.Timer; // Settings unique to the mixed command type public class SettingsCommandPreDefinedMixed extends SettingsCommandPreDefined diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandUser.java b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandUser.java index 7f30688e30..66e6df3046 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandUser.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/SettingsCommandUser.java @@ -36,8 +36,8 @@ import org.apache.cassandra.stress.generate.SeedManager; import org.apache.cassandra.stress.generate.TokenRangeIterator; import org.apache.cassandra.stress.operations.OpDistributionFactory; import org.apache.cassandra.stress.operations.SampledOpDistributionFactory; +import org.apache.cassandra.stress.report.Timer; import org.apache.cassandra.stress.util.MultiPrintStream; -import org.apache.cassandra.stress.util.Timer; // Settings unique to the mixed command type public class SettingsCommandUser extends SettingsCommand diff --git a/tools/stress/src/org/apache/cassandra/stress/util/Timer.java b/tools/stress/src/org/apache/cassandra/stress/util/Timer.java deleted file mode 100644 index bb19bb6149..0000000000 --- a/tools/stress/src/org/apache/cassandra/stress/util/Timer.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.apache.cassandra.stress.util; -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - - -import java.util.concurrent.CountDownLatch; - -import org.HdrHistogram.Histogram; - -// a timer - this timer must be used by a single thread, and co-ordinates with other timers by -public final class Timer -{ - private Histogram responseTime = new Histogram(3); - private Histogram serviceTime = new Histogram(3); - private Histogram waitTime = new Histogram(3); - - // event timing info - private long intendedTimeNs; - private long startTimeNs; - private long endTimeNs; - - - // aggregate info - private long errorCount; - private long partitionCount; - private long rowCount; - private long max; - private long maxStart; - private long upToDateAsOf; - private long lastSnap = System.nanoTime(); - - // communication with summary/logging thread - private volatile CountDownLatch reportRequest; - volatile TimingInterval report; - private volatile TimingInterval finalReport; - private final boolean isFixed; - - public Timer(boolean isFixed) - { - this.isFixed = isFixed; - } - - public boolean running() - { - return finalReport == null; - } - - public void stop(long partitionCount, long rowCount, boolean error) - { - endTimeNs = System.nanoTime(); - maybeReport(); - long now = System.nanoTime(); - if (intendedTimeNs != 0) - { - long rTime = endTimeNs - intendedTimeNs; - responseTime.recordValue(rTime); - long wTime = startTimeNs - intendedTimeNs; - waitTime.recordValue(wTime); - } - - long sTime = endTimeNs - startTimeNs; - serviceTime.recordValue(sTime); - - if (sTime > max) - { - maxStart = startTimeNs; - max = sTime; - } - this.partitionCount += partitionCount; - this.rowCount += rowCount; - if (error) - this.errorCount++; - upToDateAsOf = now; - resetTimes(); - } - - private void resetTimes() - { - intendedTimeNs = startTimeNs = endTimeNs = 0; - } - - private TimingInterval buildReport() - { - final TimingInterval report = new TimingInterval(lastSnap, upToDateAsOf, maxStart, partitionCount, - rowCount, errorCount, responseTime, serviceTime, waitTime, isFixed); - // reset counters - partitionCount = 0; - rowCount = 0; - max = 0; - errorCount = 0; - lastSnap = upToDateAsOf; - responseTime = new Histogram(3); - serviceTime = new Histogram(3); - waitTime = new Histogram(3); - - return report; - } - - // checks to see if a report has been requested, and if so produces the report, signals and clears the request - private void maybeReport() - { - if (reportRequest != null) - { - synchronized (this) - { - report = buildReport(); - reportRequest.countDown(); - reportRequest = null; - } - } - } - - // checks to see if the timer is dead; if not requests a report, and otherwise fulfills the request itself - synchronized void requestReport(CountDownLatch signal) - { - if (finalReport != null) - { - report = finalReport; - finalReport = new TimingInterval(0); - signal.countDown(); - } - else - reportRequest = signal; - } - - // closes the timer; if a request is outstanding, it furnishes the request, otherwise it populates finalReport - public synchronized void close() - { - if (reportRequest == null) - finalReport = buildReport(); - else - { - finalReport = new TimingInterval(0); - report = buildReport(); - reportRequest.countDown(); - reportRequest = null; - } - } - - public void intendedTimeNs(long v) - { - intendedTimeNs = v; - } - - public void start() - { - startTimeNs = System.nanoTime(); - } -} \ No newline at end of file diff --git a/tools/stress/src/org/apache/cassandra/stress/util/Timing.java b/tools/stress/src/org/apache/cassandra/stress/util/Timing.java deleted file mode 100644 index a304db78e2..0000000000 --- a/tools/stress/src/org/apache/cassandra/stress/util/Timing.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.apache.cassandra.stress.util; -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - - -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -// relatively simple timing class for getting a uniform sample of latencies, and saving other metrics -// ensures accuracy of timing by having single threaded timers that are check-pointed by the snapping thread, -// which waits for them to report back. They report back the data up to the last event prior to the check-point. -// if the threads are blocked/paused this may mean a period of time longer than the checkpoint elapses, but that all -// metrics calculated over the interval are accurate -public class Timing -{ - // concurrency: this should be ok as the consumers are created serially by StressAction.run / warmup - // Probably the CopyOnWriteArrayList could be changed to an ordinary list as well. - private final Map> timers = new TreeMap<>(); - private volatile TimingIntervals history; - private boolean done; - private boolean isFixed; - - public Timing(boolean isFixed) - { - this.isFixed = isFixed; - } - - // TIMING - - public static class TimingResult - { - public final E extra; - public final TimingIntervals intervals; - public TimingResult(E extra, TimingIntervals intervals) - { - this.extra = extra; - this.intervals = intervals; - } - } - - public TimingResult snap(Callable call) throws InterruptedException - { - // Count up total # of timers - int timerCount = 0; - for (List timersForOperation : timers.values()) - { - timerCount += timersForOperation.size(); - } - final CountDownLatch ready = new CountDownLatch(timerCount); - - // request reports - for (List timersForOperation : timers.values()) - { - for(Timer timer : timersForOperation) - { - timer.requestReport(ready); - } - } - - E extra; - try - { - extra = call.call(); - } - catch (Exception e) - { - if (e instanceof InterruptedException) - throw (InterruptedException) e; - throw new RuntimeException(e); - } - - // TODO fail gracefully after timeout if a thread is stuck - if (!ready.await(5L, TimeUnit.MINUTES)) - { - throw new RuntimeException("Timed out waiting for a timer thread - seems one got stuck. Check GC/Heap size"); - } - - boolean done = true; - - // reports have been filled in by timer threadCount, so merge - Map intervals = new TreeMap<>(); - for (Map.Entry> entry : timers.entrySet()) - { - List operationIntervals = new ArrayList<>(); - for (Timer timer : entry.getValue()) - { - operationIntervals.add(timer.report); - done &= !timer.running(); - } - - intervals.put(entry.getKey(), TimingInterval.merge(operationIntervals, - history.get(entry.getKey()).endNanos())); - } - - TimingIntervals result = new TimingIntervals(intervals); - this.done = done; - history = history.merge(result, history.startNanos()); - return new TimingResult<>(extra, result); - } - - // build a new timer and add it to the set of running timers. - public Timer newTimer(String opType) - { - final Timer timer = new Timer(isFixed); - - if (!timers.containsKey(opType)) - timers.put(opType, new ArrayList()); - - timers.get(opType).add(timer); - return timer; - } - - public void start() - { - history = new TimingIntervals(timers.keySet()); - } - - public boolean done() - { - return done; - } - - public TimingIntervals getHistory() - { - return history; - } -}