From fc2fa6c0521e22dd1b28c1066f8173bdb7c0ac46 Mon Sep 17 00:00:00 2001 From: itdevwu Date: Sat, 14 Nov 2026 15:46:09 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=9D=20docs:=20show=20what=20xprobe?= =?UTF-8?q?=20does=20at=20a=20glance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README opened with a dense one-paragraph definition, so a visitor could not tell what xprobe does without reading the docs. Lead with a plain-language promise and an animated walkthrough built from real v0.6.0 release-gate numbers: triage ranks a CPU hotspot, a dominant GPU kernel, and power-capped clocks, then one spec measures request → launch → kernel on CPU and GPU timelines. Light and dark variants follow the GitHub theme; reduced-motion viewers get the final frame. A short "What it does" section names the searchable concepts (eBPF, perf, CUPTI, CUDA, PyTorch, AI agents). scripts/render-readme-art.py generates the art and a 1280x640 social preview; the contract test fails when committed SVGs drift from it. --- README.md | 42 ++++- docs/assets/social-preview.png | Bin 0 -> 65557 bytes docs/assets/social-preview.svg | 1 + docs/assets/xprobe-flow-dark.svg | 34 ++++ docs/assets/xprobe-flow-light.svg | 34 ++++ scripts/render-readme-art.py | 256 ++++++++++++++++++++++++++ tests/agent-contract/test_contract.py | 20 ++ 7 files changed, 380 insertions(+), 7 deletions(-) create mode 100644 docs/assets/social-preview.png create mode 100644 docs/assets/social-preview.svg create mode 100644 docs/assets/xprobe-flow-dark.svg create mode 100644 docs/assets/xprobe-flow-light.svg create mode 100755 scripts/render-readme-art.py diff --git a/README.md b/README.md index 16f7a79..13e041b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@

xprobe

-

AI-native heterogeneous profiling tool for CPU and GPU.

+

Find out why your CPU, Python, or CUDA process is slow — in two commands.

+

A bounded Linux profiler for AI coding agents and the humans working with them:
+ eBPF + perf + NVIDIA CUPTI, one CLI, strict JSON, no daemon.

CI status Latest release @@ -9,12 +11,38 @@

-`xprobe` is an AI harness for measuring latency between two observable events -in a process, on the CPU, NVIDIA GPU, or across both. Its bounded native -profiler combines sampled native/Python stacks, eBPF function, syscall, -tracepoint and CPython runtime evidence with NVIDIA CUPTI, an agent-friendly -CLI, strict JSON contracts, explicit correlation quality, and no daemon or -server lifecycle. +

+ + + xprobe triages a slow PyTorch CUDA process into ranked findings, then measures request to launch 1.14 ms, launch to kernel 99 µs, and kernel 1.2 µs on CPU and GPU timelines + +

+ +

Real numbers from the v0.6.0 release gate on an RTX 3060: a PyTorch CUDA workload and a request → launch → kernel path.

+ +## What it does + +Point xprobe at a running process. It watches one bounded window and tells you +where the time went — on-CPU hotspots, threads waiting for a CPU or blocked, +container CPU throttling, hot syscalls, GPU kernels, and GPU clock limits — +ranked by impact, each with a ready next measurement. The second command then +measures that exact latency: a function, a syscall, a CUDA API call, a kernel, +a copy, an NVTX range, or a whole request → launch → kernel path from a single +capture. + +- **Built for AI agents.** Ships as an [Agent Skill](skills/xprobe-measure-latency/SKILL.md) + for Claude Code, Codex, and Cursor. Compact JSON with a `good`/`caution` + quality verdict, so an agent can act on results without reading 100 KB of + events. +- **CPU, Python, and GPU in one tool.** Sampled native and CPython stacks, + eBPF uprobes, syscalls, tracepoints, CPython GC, and CUDA 12/13 activity via + CUPTI, correlated with explicit confidence. +- **Bounded by design.** Every capture is limited by samples or duration, + timeout, and capacity; there is no daemon; CUDA injection is disclosed + before it happens; the raw events are always kept for offline re-analysis. +- **Honest about what it knows.** Sampled shares are estimates, temporal + matches are labeled heuristic, and anything a capture cannot answer is listed + as not covered. ## Install diff --git a/docs/assets/social-preview.png b/docs/assets/social-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..b0092f525807695868b02f6250e1bab22e424d35 GIT binary patch literal 65557 zcmeFY_g7O})HaHGtTY7?l%^=X2}o}$RXWm3Kzi?;&{U)&(gH|{^xh#zD53Y>d!$21 zp(X?fe4OL^-oN0xW85F^9vLHZWbCXp_gZs3^OvgQDz5BBI;Z z;?0nIH?O}`;@@M5h#nGskbS4^n}J-u|CZ9`Qh5Kaz^nFG4;6oRyhsU&Ql$F!pgc!c zf&;r>G!4;RE|>In@&O{9n^3S>=SrVNG`6bA-l^1y3UmIx?)KiJfqyR&Z<%|4r4ahJM0fs*O(8@i ze}zqbF2Uf*x4pNT-+BaA#)3-u-)RW90AO%cMx*Ew@((44MCD^GP1s-X#5d z&;R|$`piVsFnqE%d;h-)+S;zIupxfj^7-HSZr!@+Dt&Zt0H`}1KLw)WmGaOGXwro+ zF&)5eGN-r?dal+%c-Mto>U1p0PD>_?oScLt&rXa}K)`Qu3$@tbC9U&GV{FP!t2Yk( zXM3CVNt03@MW$}y%v7m1hcy{2gLoFktbhpcZg;D9k!u&`7DP3hj z#-yiyNb7@J_6a7S8pz1VG8vUb12)nM^}Gd8=$7;Mo$tlT)m0^-hdwYD@h&44aqOfz zy}UROi_9ga7P?}1_^=A`joNxROBMP{gH7rRS<9U+oS3B_SV6hVnia5iKpVUwZZ`oo z32^I*c$5idUR?n;{S=@Kyuu<%hwAc-IPwv-R~P5!v9ys+Tip8lXz9ywztcvK?I+y& zZ@I>k3=FNQyaexB50PKuM~}8g6Q%sHjoVM8YWgNin=%`pOJCK)2<4;de~&lJ>uWY; znQ0K-suzpWW&G(6ue;C32T<(`)hXSbwd5xpW%@*t%7->=jKVHZE=^c8iiN;$$^SHw z0(Nzvpg!7~;kj=wFntt!Hi@{pZ16a<+SuOo5)4ndI!w70PIi(S^i)OlG*%eCRO4i% zuTRr>J65lfKY5P#>U$6OvU=A|(Bd5-r_7G9lR94!}`-qB*U z$@(Jt;oV;o;vr2i`2`QgNY=|con*sW&+PTIBpV&mrdMfO&(QCgaSD#FdBpq)KJb$lx0EH9(}VFB(Y)ZGt*2z+7G8B3gDK+VmRaBLuMFyy zJ#XO?<3H>j?yub*dJ)JRuhf89615u8rtJj_2e;dp2E22`Zd02E9F;eu#W?SdL)^*A$JYF^aDCwihyxxHXBS;IVKPWlJjt>sl%^q{`ExH7-hY`bk}#ihm@Mv zxaw?-@vsMAH z0h6n9_^`Fd_9{j3^^pKOiEmUip`#*WQ4-ej@;x&fmhKMrB@XT$9<-I%A35M4?0f|S zqGaI$+k_Dl6^*aZ(bX+UDuKwPwk-vsW)5iYW?dYbUQSHs4m~?HTgSo6Ou_COikh17({8_w(Qv3UE%|>cex3LQ zC7t+&L}G-UeUS&OV$#QbYYMYvc(CEJd*;4{y8hGf`=eLB=kybLCx|nzE2Ac#(X6Pb z!>t!8X+A4oZYwhYhY+XND0M{5qqs8VQ8*?XA)7-s`a5tBa<+j=RLXzP`bYS4rw2vCJ6WC->Ep4^o7ulQU0J61tfnb} z6!*21P)m_h-{}{Ho#)1;Gt%}OkNa7h{NG4Q2JWuM_&GI|Mpef?B$iqF>ZZZ13f(9Jsf?aIm)G4s%qy~GB` z4?$-*b*hbRI8DyD?Zo87^=Er1ZGC5F&M#rVY9F--**!TMA&;dKv`0+w0HKBr-Y5@s z=|KDhbMCmW#V0wr+X4;!iL5@;jC0eadg_;l%G;{~)+5G_sEqf4faToiCw!dQovvG6 zeEENN1*{{w*T~5Ec#ry4JOBA_9gv;B>~9YLE`gnm@|>LpZp6zC8|+p0GH$y!(lc0b zBwn5Eyu4ojRzqwTCH(>ee1Fsjj#?`8tetDZVE6Z*w={U|ftcf@42`rFhecF26Gf>% zon&WZWawQT>XnjPSy{E>R;0_Nx-xY2SGvDVE}!n?lEbgIrJMYZD*+9bSwV2m(#{m` zaUWcC@TJG11pJBC)5||CI5G0LI9T|9I)*IMINnIDb{WkA2cH!mxn?V#_EKlrD7EW~ ze=|9nzJiV>E}xFVu9gY+_GZiX5ZOETDHVD7rng`%hba|e15h(WogF~(7@O|mElH0* zIAnKt`11wddh>i?wO?H>0@Rx1 zfIgV&1#xN%Pu_E;0=qNR)w#4$PX-OOjhC;pbH&9_nlmf2xQ}Cln1skHS(c+0gpzdm zzVS>b23i?iQ#B3RptsalKJkgASN`8gM~IA#wdS$$*bH(D=*r!2TXNraj1`Zw`CLuOX&iZE|`5)PLT?P#Er-_ir3AbO< z*3r~)bG-9#2!L1*aMwI|69Ji zae354+jf#_{6B+weUD^;Wo%Rem%$Bq3-t4AxftMdX+5~pI9%p!sxl|*V-%H9FDM9* zeEg8c^=yiLBC!HZt__Fl7zjspO%6^XWV}x3*DDNj-MnfiV6$CwG|3<3{u`MHDkQ?$ zc8%$SAl?SIjc;)h?y~@&_{9%wkHf;g83wZP@}?}^v9E`gnio$u-SBD)od^jbDkV<_ zB_#w223?$1FhD&I%)?)B_DX_U9$5eCWHwV_%T)w( zvCjDY`}Yk_k0Fs0*6}^-;fR z(C+K~3xmBdEM zmK;X(X%(u{irPQ^YYICHpNQMuXYca{;~lnFw?{I?y$Xn-K1{JmWvs&)j z>DKUQQKAt{mIX0?@#=a%=5FpE1^c&mPUar%2M1rc9r5nLE}_?K;pmTJWJlw8oHDIFZ~E!o*#I}7Z{1YbW0Y4?VQ%cTPc^p2RUdYO_ zLPPQJ;r_y2Fud6-y0kQX`h$?ug0_yq2mjsnaE688oiPm9klp(coJPb7k%S4pIKXl0 zc?(Xj%7j0TdQeJUZg3=BkZ|od(rT^wMg^@Fv`gds{r#mbyCx*hv4p+#xHL-2|8c7^ zU$0XRBu`XwXvMu9w^yU7xf8&mliqy_S7);sd3hri_Vo+lTO-`u^%LXO4JzKqLa?Ix zHSZ%0ljl{(R5+tNI-{%xgV5$-%M!D~_}^KIf4RB@2=n`cK{v*dUGxf3OuR_*K47)35mg@#a(<7gPvXy>`zkpEDBUHHBsUmOkeQ3)@1Kqv@3%H z4k1GC?*3~OA)l>(y!q_yCqg8f&&xy)2zg5H$QYG3n(Kn&y6>-6C+>u@rLfX)0AM>;?JM7}OC z3KE|#JE3DU`=7nPW+M$FlJH)dyUP$ThLAB0I`9*wyoA$M8mT&~Na8V-uy>A=md^f` zD;Hlb0*vIXZ_*|P?P+tk+vw=&@%s8QB;tXBI=a?dU+_m*^vd#j>c6q}U27Yg;1*|k zO53!@s*XRJ?T#3;0%s6Us+9^Sxs9NnljPC~E8?6fqb*mLFT=_I$CJ>)@I*thl~=$$ zr_vk96iS$<&ZU#LT9Ehk;^{I%rR~&=10J%E2m_c z;2%@Yp##oZR=?s_EI^Z>5<69|VcN>8z{^f@>L-tXv<`>_XW@Nt6#x(GQ7PX)Z-x6I zIXM?NPTgiK0s3vZkE=!oaPfQP7|(K<)NaQ8rff{1o9X8e{L;@ z?xu-yz1rx(T!$E`f_s-CZA57*1v5o^NhO0RyjQ24N;*P$yJ9+(yJ=cj3%r=JU ziE*)a=xhN%N1)OAmj0gysaiqqB2+D3g|w@?|3a^=MI<4u{S_G&OHt0i9&P z&cjA4^J)=v7SNofD>RP8$0IQ!L(ZJKy5L=Ih2E^To`Bs&ifK?|GRx$D`?+rvPUKkX zd~-h!h%ScKn-u)_E72csT8V~tXH@sUz5g3{65XP`PV2aOdcOL5!|N*2gvIB7pOMIu z{Qpk&@y~Pjlm8i?=qo)(+uy1FU(H#%`l`LZ$<;bH3ZI?y+Ntlkb4!)TnHT9o6vz^z ze{m=fHYD_Qq2i=$7E@K5oz>Ra>JL_G(I{|e_!w%z;CDIh%KFi3 zKT%Lx$I!^MWB=QzGzB|ivi%?C%qqvtr9WzzP!@obm5ydlKUj^b$j#TiSF@t^rM$eH z{5KLDwZQn7H^s`lCL)1KS%4{P2Mv=+FhKN!d>wW01#*!tvwq%J)#M;N%>=pKsUUvn zf(%n3?05UU%v^|DEUGA6YKGF#vYATQ1XYZR(EN)dwY=loh-7vR(~q{0kC0KGzVTGW$eFP%c~~s<~NCJE*Bs zR|(0;RDZEXzs0Pg2N6&G)fL1KIcR{^DEZi|$7Vx*eWc}~7*CdCKKaUGa6gb$>WvGQ z&8_J(Q)T=CE-nU~(csWoa9omAl95yz&U+-lA7rSB_^x_42&J*g=7}RW3o=KSiq#W( z1JeROv|s!>*OQ_`nfSO57v7E1+^m1Z%pisO98P&t*6FTT6d`vl4P5+Ge1_F#2_&Ga zX7014uqE!UGF6N+f-bHl%|>W)+qfEcGBtm@jaO{*4s$03w}gCvd7`>siu(*k??sOB znk!Aj&;+*zcMvFfD)4p>GZ(JCIgN49`3-$Nt^8LnR%DUe?X1a8aWZ%nw3C!L<`uCU zwTVW9-w%opEr7Hu1c&mpk4WlR{h`as;G<|;FT(Rk*`ew`OIC3)R)Y(&Mr>ERj%o&) zeol#F;v$w^zjyANO^l@p^gMAA8PE1z^fl{LFyd)&d|cp9_gaF>+)+jQNBV2F)z6Lf zd%~G5xul&7$;NE0Q_`LGgW;_jpXf*<)z;9hNCYl%kVlIz??omz} zKS6GdMa?x?5rn|6@^b1iq^_|)L8*E*yN*}do1C~xlfbePKiwpUwz<>ONWP&bxL7~W z{03YcEqRhYDI@^1uVL*n3rZ2fpEZ4xJtIEieQ{%v8hY6=;)1uT*eo1{WGqZ3TOk)! z8l`~2MTxU>Y?YH%x>0S=Ax3z=qj(78>7N0r>b39OkFPE0kI)&)^p?tO%B+24Lj(rR zyJdrLW_Z8faonKxEe||-cwQ&!0L!K#M zrGim7A1;Cvcx=NFEwITdFCUlZp@#dxU)H^B{?|W3ig&EsNP#gNSP(>V=hxD$i{*Qo z#(m~M8jADV(Th^o25%l$Q?HZ!wUFwtQiev%k0V`wXJQJni@ReOxVsT4kxm(1v9UB? z!m>r}!&@A2fgrJ>R->*SwnK7_Us7O*KdGq*ZlMjp8RL!P5&+<8 zS8>83t)SQK^Va3W*}e-FI#JP?N{`!MUV3^CE8-w(vCOeU4b z@o$>512X_dE5%RwRfWe@-jdM?<5%YP-=_U2r<~oOTWH;u)g1iX;Jfs4l>43!)4*}% z403NO*u`Pg2%+VmhZ(5#e*Eg%V_Y=HGkr{h`k#C*5$gYO4OJ3A>tjW7?60Qkg66U} z+e;t(ybWfLZL{E?E)$*n$xs=3+-qg}RaBlDJ z^y=YtPsC!uHlci3>t3M3t1k6t)ad1I`$x^N#X5-i9I9!!EYUEsw{lUd;1$y&u(*JtaoRy|2oszej+D1f`aY zWWC0Z=r-voX$N@BrguA^!pvbso=!dOiR0P#O}buMhQ^pK1<4#OznsI4#%`u?ctHy? z_??nU*`D+3`-;{Uep;y!8o&iXFGF?37HZ_0t;`GMz4CJ;Fvxz&v`v&apY2LSm@>R_ zCG`U^HZ4O$lu4WA)lm9DE@Q1xCH;>udG()k;zGYS4}ng*-zbZF)l5HnA6P&3kB!WH zBg!!`aqE2|q@=8B!`RT!yNN!6;_REcN(B8bW{<}{%ce&?JT4O*@GKf5ZZ;CbZozf# zmk6z?Yu+*Mi6qPH;XC&pikZjB>0|X}T5_7pb~<*ah`X14TE-fRoz<3)7G*`;S3J~Z zBg9}S_!s1rhWsdM9n`5K zU{Re!=~=^Z<15qN5mUa45Q9;k*rT1(Ve`Fk`PpbDlBnOpQb9;IcJW)*KHS`9-m_r- zMR0ab*XQ*$OeP)G!qP1!K5dgEM(GR?z;5TX!@BHL=8IlS0Kul8*)ttePyqenah-LO zT)g1#>|fAkPVx&+v$f2AHkttkP{|>7WZ#$OB~8JyLf~*)StZB z+@N}fqJ2g$ZFY4Q+Y3d#8_;C)I>{8VSwU9Oda-5uOw}=)-4KT@eB9u(=)YjYr2~?q zzOHR^+ZncakC&UsGoV{5Z!?G4*Sf#W*Zvhics+_yoS zPzXw9M)TRT^S3*{ch>iW=xW?~nU&Zc-&G6xv^w-dXxcM77}?A$+h7fZD-|bsE;Z*9 zcK{Z7z8SU6<>lvEY|_r6`Z3L5k;87L%KHM0cFv1KW78bvbU*>jz$fKGuP9=JWEi@j zFgcA{iL&`3@(r7|3RS(NayRb#12-Eom1v%%>dHEaQVr^-%p`Q{oI^v^c7b9K2=b~y zOj1(EJuGe%z`Fv{kK*4+S9}=RojTwJk2-hRMcdq|bHtw=f=s5_4Yju;_&{d)w% zGswfc-#*~|gw@4ARYH zYCC2HsT{fhCE9TeR9AgPMb&`gkUr|jmfj_4;N`G$9sTlqbA|`^6P)xtQW?&7n!Em_ z4|8NlFl|Y_6AWr)ZqkY_H_9SsrWbI+#_Y^2t%y;^MRaYh#@o@(AkhJv$}UoRX~Mf1 z3Qu|q3sOzhx{dts}&A;wxckCFvy6<*lZHU+@4#uw8W{XXg*BhEDDB}^*@@h!3 zwU16B94!yw=xGOo2*TBYR*9zcaB=hZKQ@g&6z6As+AGkbooPu?J^g&7SMjN2Z~5e= zQ|IN31Q0XVJ!ta}pM#N+tLqVivfY>BA=%-q;NZM;OrDi8iK)|R@w%DtW-vT~O6}ro zsgB!@`Y<}F4tSarj*dIpZt5MO;l#)&)T=ylT#34k)C{t)TbLQP1F_suIk4<$zMI=D znUlivau2pJSxo==wd)~a#jImXt$a>^-Oxe7J)juOdG4oOS#&my5tp0QW8aH_(LMqy z;`4^>_*-Z1K?t?kr{;~DnK_(;T;uX`5nq^YeiNM+V*cc|G2~nDz{l?DbBmp0b<(I~ z(rxnBwB3SdmtD;z<@7*sfw6*W7A@VMDSfZqGKtb31|B+~A`ZHPKNHMDD{?zZ_$=;o zfw}w*WrZbNTjxP*h31j&!x}OC`}9{E;(_>&%CdY(SxdDTS281_4Pb1R-xX5R)6LG| z$5X$nmyTyv;=(IU(qCIXKRrrdRZmZUxJDlwY`*%loO?MiQeiZ$JrCZ~aSI**tg(Vw zJm?@Fp>gW73*7!V^qnuWzj`5q+tf)E9v~FXj_QF+OKPXVt+t5+(@VU}7vU@mhl3Bq z-lQIqolq6(u)DmWe?a<4&Gn7oBL!A?{7MJ%Y$kRfg$!p?c~UgQ+2DQ_MvB74J?6ti z-;zdSQiwZ9>l>L*4nFE_r4N1$#>-=#yobkM4y#(EPA1+kFKHHR((2f1Vz5=_X{gAT z3bd_brn~$zkzWmJd{Tf~$^@DAXCyza0c#2bIvg3QTks`sZz``_4!I1JIf-Kpl`4}Q z`nv8iDw0r!v2X(vE9ZSn*5_#oe?3(XJ+PDOS+O`&M*3(?A#dXp{2$`x&Z+Mn5F~eU z5`jVHts@Bbt;zM((1-DC5^jcXgWNx7!?tC#K1vHm5j@4*k%dE`Z(#o5mAp@_K0|f) z4^J}DEjH)d9Kf?rkyf{7Of3U?HeG?SgGX)TxjG z_daG%+!G;I|5k?xa0GX9lAWD7TZ8c{E-blyV^kvQMzmyNC0A)ZHuIy8+2HnG14EOB zvBR&l$^Bg**1D=!Am@)O2BZZElzmn8_%)<&;d!R^Y9Lfo)2(NhvstsCJ*)=BuL|ck;5!0OF&T3ZXG* zO-#$;M*YyO?CQgM;H(dl?ac^mD5LnQ z3*3wUMsX%5IfM=AzNl!*03A6LK5v~(eF|czO0Cm#U9&v8gTkr03|{sW>{Sb}*50j& zWd_3V2Zf*qiK7<8eFNp zh;N6O++EWC3Awx~U-2!=FRscKvVku!PCBHTev6;XX%R1%n*H+-2aREr2GqJWNSt4{ zHz+%T{)DTkSx)j6C%er5$QVx;Ps3fJNognNN_XTmC()$PYQ-cyWK87nuCQ09@S;&V z>{&@oLnVGEcZOl)+iERil(|$y9kox{)urVis)<{fb4)rLfL6b z>`ggb$t&rj43D|2Ml!O*jaIs54D*HIUn*K13n^p)#&h_@$bQiyinM8?l(Y7Vn&ZS>kR;TI{}LfNH_Z@YYQdu#XC~Q~0}Bx*@CC;?iKnZJ%E5 z(QsWGG=8gebOjjP*;*@1eQA4m1M-qc0p7=FqC4}$30$gzm#p@?ZMdS*J#1z3m0vn= z5aGqjDL!(nOMV?Zw^@H>BBJ~XopUc^0U1Cc=5g5pX%;f&IUy}wDKp7i>}lB_ueO3> z41sMWl_m8Qi=NvMj3~TfmtAW`yVlexpt`h3P+(>&gExPELf7+cx)Ho4_S1Z2)6Wb? zISkpC9sgVBk1JX~qXQdV)KJBe3P%fcv=h()rNJBiTJ0;SL&#mrurrFm{+q2kTa@Dn zV?Qb2L&n$MmgyQ@+5maHSar*?NfGkUw!V?{^XB`R756CpL);Y*N1&CiB%Czf<>LPA z<#*nI9B!V(&+?sqPFZnjk_g6k=2l;Vlpzzs7?D}1@-eQU!7ezrIhUo1`~B{O#a*=q zX^I>NUgWZATNdoz_pCo~w{1HP$(j?zCKf*xr^%XI8cgJ7ytiHh8+Y#saj>al)EpS) z=5p)4f|k(q^iKH-Y)(CD*3w9`=#SqWKZH6@kcG-f{%pTYv0sdR3J0o0>p<9sdEAm9@CK28ZiT zhOKqpRjYL~qNIf4snWqCwl^LoF@YS979&#TN?f!AVIeCmsRJ}_8T8AP^J-u)V48W*>&?U; zTFL!`<<0;(8q@22F z{}#FiX&Bq6fVGD39z82bC4V6FBJw8U7nvrjQB8`2C~E>=YKONR5*FW>M= z9^OuuHRl8yG)OTT0%nSDQ+nvO_{l^|c4g}QYt{i6+d(=On3To_WxgnM=2P zXoXQ+F1yXAGpTiZ2$#tgQT!7^W_j~VzPF}dwGRqBKM#;d1N3IOs0G+>YDKRp_~Ov# zaRxhNrj@E*pvsR6hcNFrnrHKO4bM{TvpGcrHHWhX;~q4+WxS8P$uyr3FIE|i9nEu6 zaz%#s9IGyrG$#UQnsNQYz0Mx$Frn;vLFR35r_<9G!E>Ja;t$wsb2SUj?cz5b{FXJa z)-e)T_L|lA0mr-(2NsL=44A`r$?j!4V1Px=E!9GaH_Ac-0Gs*l*a)?a#a8+orMf}< zu1? zp?7Xp+yBrrob2}L$3e*zMGHGMgYAZ>Y!2Aep&uKut<;YKV{X8jz%E1e0f}F077R7DU8v>vE@2QGVjSvlpcf_%vwDP^}C$ZS<87 zO;P;}anXvGYE~twzn$-e{6U*+(ki{Er5$MZfmtiiiou<9-(fz@$UDf%?p>h^X~76J z3e)!lt95vW^HA1Hb12qqYM3cvKiGd>@V1oV5gaTN&#}KNoAt-tMa+xX*(T+iqMC04@be2>GAhv4shMbdLjlXC zaYKm56V?tND|LzE znoZO7Z={S)jgEtZ4ov0Up(K6(n+phk0*EzZEQDkNKGJF4j zD-V_N_mAXRBs5I@S3{@C@)vg&wFCXqX_8W+J^7s9L%JUV`MBZYN#&I$1EmwYMLC9@ z`og>gW&Y5QstqeTt^Ac*u*jq_qZwm=?j4LSXC0KSu!g-2c4BYI;&g{!aQ-Zz#{X+Y zQnBVa^jts?aWnI?)3K%9=Z|9xzyE-w-#8;J>JiRT^Xc$%M^3KNrY9ceg)46*PSq+L z$Lqr#XrX>}dMcWBipo)N2V2H3shM|^;_qxckhE*ep{tk+Fqc#xr5W4|go8b|B zlLQ{suF;v?`LP9>p7`a8jWX!|)+}{mro5-5c2RZK5*H+qQ)Uwy336E~x-l zx(NXUyPRb$ak+Uma=!68w+*&KpxbFHGis{O_sB>FwuH+{gF{NolgSJ6I@Kniw>;%F zbGDu@Ht}?`>s4<3Oh4PYjw`|W1dmy+@?Yo8q|O;D)ppbnH))p^+0VyYc?yXY5P3UP zVM$Hha5zV8J3o0@ASQQuk)MORK^4nD1>5MR2LGg-z~;7TfU5V7;-rj$&!Y+d6d2Ew z<%5IW44O?F(toRKovb?Llnz^@bBp*3FzZGY@7I<`q?d4!(s^1@ry)2uskb;^mHQ;S zlt3Nxg+oC-H$5$`V7T40Lv!qcKSY%hJ~5o!^s<&-o;0>hmrZIF3ug-^s& zC&I*24VT$wi{E|Mg-d$*?yxD*xMJ2v37d`Q78f6!R;n4L&v#<=E-{(7SqwK~lYLoD zz`1d*S$OgS`Y*hATT}%U)et^SCu)#mEt`MGOHxksB*B=7c=RFIKK>QiPf&*)Ua!o0o# zRjqT{>#(TC@_|PP)w9)MUfzPA;*FXn14xK6HMd8VVjRhJXfAh`fxQpiy9Zz3F>Em$ z++f;zJ_=CWH2A>pXfx%wGci32Ps=S|tl4eKF1LB$|51UJulA;5;jCh0m$^{+_`=V{ z`M}3cMYD#^kzB{_^|}HSP(koNd{eV0yP0yOgAbCs;L*OaOPgR_kWHtGVFfjn81xK! zxT(ZBFT3Mpr@5qGIfXUPp5uhhkBmmt6dm`pbOtI^p}@O2fKu28%s#@%IR&-B_=s&l z1fJq^gZbFjd9b9kyx~Quupv*o7t?#a0*z(-eEt91zwq;Dp81Cp&dwn2mxfR%x1N zk|(YG76g1wm7ND`O5I9i`Je&LyX4xPdFJ1?QeDmdZt`yBV(K*5i@^qr5IRr@DI+58zp2#CTTO*iO3r!BA z5w`mUOD&-?d@!YA3!5+7)%_1e(7L$lncI%b^O!VlNc#00@Ij7aZjs}T_y-?g5uV3z!AM$dP&WB?jx=v0&cRQRC=+;djz?| z_-Mobu9|igzzkqL6RFT=`_y!YaRd^Ku2yC>^ke4^2gIwbCYlE{ag2ISde0XyPlEJn zQV^OePCS~DDwJhI&eS4ObxWFKU%&$ehT3w&Jl*?VuhG`QJzmy4TgX}N&4IlRJnqex1`#568*xv!L!PE@obFVvZ$rc#0=dR;1QYkng2y? z_(uKl2V1tLzNAWa+-u(Iru=Y3I(dB1>DcQcBV09_$Ju7)quo(OchZ2kbZk@puu!%G z@aRr+;^}2IF09%|MIw-^5bRRxppjz0bJsP2Z(Bo=@#5KkIAcEc&!3rGk9c68B345m zuyn})3wx%F>Amig_?m}wdgL&0o~vIpB5m-?s`b2A_Mfnt$m(MLmzC;A`Nn}QVnHo@ zo8=(!8!OouNkIRT553$>(FM4VH{{*EfwdgQh3W$Ng`k*iu$U? z+<%Pwc-yRKptXx(Q1q-<{n_*ZQ5NH4arS|UQXgmeaI(*AdX;U@c~_kb0wIq6(0rY| zM2#I;=i7jEx1WIiq~L&nUfQU8x6qk%9S}RVBrs3Qx0fQVbG&_e3(EuM=eB0*@el2` zLk4isP@l(5R2-YE(dYg}%Cn{-zGWH1pV|dL5_6)rv?ln-%Ps~z)Kyxm)5oFBG5pHs zi{m&sFGoh@NCi0cAj77xPUc`ezAovt<;*%Ryk=3^XIFoZ-gzE7dZPk~D)xx7F9#s> zP2C$O_!oYMFyMr<9m1zoKJ*SNT8bl?7mNo36MzMS>a|NgN{@qFJi~?QYe8$hH8=__ zoBVo{WP3L0OuJn*_PGJ^qLY89Y6CZ|kcu~OBP1C1UgDOEJbL)V9l&0uS-Nx$TQ%6F zkjFEv#Pf)>04m07xu=~eEMvODGSnFT9$PENb#!90SBj`vnqew=mo}QEO?5Yn-zH!V zlax;GrT^zw5s+1Z940A=L;Q3J+jvDv^1$qit(MbKOT40of^NO4W9x%Mn_OUeI2Sn_ z5grCTwIpj2;Aa(Hza_@}d#3j1P=!}i;HcQQZgu4T3-w3^mG37FONtm->*-7>uTQik zo6ok?lzXXx(avsP5nMKGsls+$E%f5;Tgoyoo5cg!=X`0}aAaT+QSUl93U7UjsEYXd z-JM#(r)uh9-%xVgC-D;QJ>T3%gH~|fl#2ix*cbjC7Pg0lrUKXEj8L3_*%~W7)2gI?FI-GhE^@rVs1^DGK87*2N zNr0)L6?z_Mioxj zH=iefWXSf9&7x&V{}qqYE!DXDG=JCv$YWu>o2Jc)k z392+a+uvZb4#)q>Q5(>nFUgQJ{>V*KvXi`p~_Ps}Okab-PPp)g^0 z`!sZGv;}){*pD1m+g%_I|6XM#|HghLs<&{NYC#M%=HNRN(>*H1KVFH?*EDfE(v`q1 z9l^kEdKwNS1)-l0Cec>su{M=iRxTu)!hZc)oF<0#mS?H7pF#h&IXGnG3%Y53o=$Xt zs7?E8`Z%zv4BtB67c%1*0`!4xK7msG9=#Zvr?Xle9({^^u<8%NcLFq3JY8@PUl8`Q+q0G& zd76890eD~J{tQ`les(qS)y3LToPyCudVtqqpWW@F=r`yCDq;&mMB7l)?nmtM&QJD``nf@cwXT3x;0m@+wQBUjeIMm7E$6ULAKsT1Cp%&(o^AK) zec@JC=rdw6R06y@aof{rWOt(9<7>WStzh>zu0^P4&Yl1ysG>C3^WWltgFbSpY7QGm ztE7&et84nB`?IZD{h;N?)oI;x)>d56Tx0Hnw44S00OOP4+)$^j%;^kqrW52tZWIh% zI)Ab0ESOEUHZF;bIQaF&y5jU)W$@R}L86G7vM5Rt$x;dFG%wQ%LnssFk;u#yDJbtf~5Iav^d$Sx( zI$xZ{(9=R!aLqOq0Up+c*{V8)2@OaG4=t^1E~YW}xU^;-Ue{mK-)`4;4KDM2^89gX z?Buh}^z)e=Yuu02v?2BMY%fu9*mC@Gv=#t5xH#+bTp1**Ot9D{yzn+z>_rGb%04&; z^O^$MTfL;kcz~8=;jiDU#*c-16PxbwJo%_&|Fgf}NT{)f6E4UVWTGDsZj*P>T{t+G zQcya?UO`Orlun#i1cbUQj;~wM3#&-Rh}t&NJ}l?#=ZrlhBTG9@jwy@&-&}xg-z%&R zZ3HC@uT@_B#tCD#cb;0V^)$&ZA6lIoS~0F{;<(HETs*njpXRJ4xHv zTDcYbCxp*|4LozW5L3!n?qJ)ZT9~Q2Bs57henZT@XF^k8L2AbKVbkG#T1Hal^O*|P z!IZB4ym<2Rh=8Kyj&0OJ)xlI-i}$n!yLx|w`4=dg??Mzzy@}7*$k!dy9kN?(vZ1^4 zfQ*^Z%?1WZ6<$sdauF+#0PAi%qMa17SoEfOdq01;#e-6zTG&G=$Gfe<@VXt|@pb0g zH*ACU{nsQYq+%Fl47KFFr7NIN)HQKSH!v80mf;*7h+e~xyT6K+qduFtoc|~bNDSs* zTRAOy8(iEJ{u_^_%s;d^#|{pnH|n)iUhHXq7D_EvIH^!({fRHQ!YC9HI$m1jGIjpM z7=CbYD2Y1aAIWU~VY*1}kk0N`PAJ-_2;J!$i_nMz(Kmc3E#n#zsXMoH>!o~FceYlY zo6B%r%&RS*54~W(w2uRY9Q|3m0_xkV{#-_yAM}v#W1x z|55u8$-JO3i1mv7yknS2=tK+*%|(u{ zzT{TLag&@x9X8j!3?IR64M;iZtjOWj!vz*FYATV76IUL9cR{n^>0t|Bnt%HGkPo=1 zgq)Vu?BU1&evSkwr>>-a&FpgCaxToqc<0lWph5vo4!_~vv_}@bp`l^lYF(iJwohHR zJ1sOS>l*%kJ$+aX4h9Lb*vsm_wJoYF%}*~|Sc+K>pR92~zUAhW4nS_40&~;#SejS{ zqukXBt?EpcRrjLQoG*e!Kl2?>%H#ckRl;BkQ$^MGUBcb(CiZ^2o{~pt`~8jaL*Dlf zaM%Q>fxDxH&P?^oyOY-x_Q$YRF)wDtql!JqJc$<`IVy1#8}3gK;?oA0*VNG($>?n* zHvnE2bp13{{xX;Je%6GmK}@igVwRa)kntbU;3Xrz2=K>Pj4vH5^T(`ri+msgZ#i>M|K)YpL zZ8_|v$0^Cfg`KW%A(cB4f5XX*lFc&f${pvc5vpxKy>8P;b7K-VcTTOU-naDMZ8K>g zBD$6G@7p$Q<-d$`pH%)I_Wmj=t|p8YMS&o}LU0WPYuw!l?j77ExVt;SJ-9Ry+#xuP z2X}XOCrCqZxSfCRJ?QHRIRG@eY57A<^6eX_(!wX?LfUpds6Z% zl8fV45P$9UmBrO5IyieQOl*x7nn{--%aFN*tn26 z!(@DnIe%eW=?6&#G&ADHgIuQjb7_A6t-U9j5k|ky>v60iE!-WBdkxFeJAnRSZ1512 zEe`|3s+ET`O@osQL_-`AFdh-V8}$0c)%`*L<6F~L-ze^nPDguS)SZ`4vPojEv;CW% zuSJE}P-3u0@~_&Pp@|pj7tf-(&#O0hzFoudf@~b!vbw)qAj9l>8vhxh1mnRu8omz~ zX>pdFi-l@w>fA4Q?^ZkXQps3fmWymtZ5Ff~Eg`j=z9{+Vw@?~<8OU>CJSrq2V=^9Eu!@nzJo6I9@gwQ$+_g0{t4;E0 zCL66Gga34MblvPQMDt?z!|oBU;qffaVFK&lB?5nc=n#cs1OGnii_&QQ_X+0zQaQu_ z-_zw^BmZx{I8d(=_xRpzUVeToB&2^*7NdabjB!Op#l;#!8mPzwj&+G8p*}h(2|I1H zL#djW2wedU@b4Ob2Y>$#;-knPZy2_mu_jiKqJ$+VB$24jf$yGhq?HB&Co42-4BGs& z2OgCE9WM{!9hpv;+$BVdJ$dwoVdg!~-vuCrUL_I$!$uuZmidv(TF+%dI!^GrqZ`);Ek7_`4V(+ToCT>nW| ze-SFq3X5sr7*ppzsvkP23r1yGtC*wpx0g{(giB^k|6A4z(SM- zS?mwTA)~3&G^oD=Yx+-QPEm}kFy0W%eNkd2GK_(aG~alSXTK4{?!4x@`4@gdYqkrf*qd0Jch z$J*oHt}E^C5O~U&a|D00sC)1<8SQOd5MemrnqeLv7br)NKW3$K!b6vG`zT`g%~$@-ydCk z39L5sDbM5$S5nNxKJBGx;HR;>+fdawT#MQqP20qi{z&q+wYKz4%=oK;aJ!p>2A6#m zv_ra>p!#opBC(<9FLj6S+CepB)m+>{KD9@l`r?|q(uimecNd=9olIu=4sn?q&(p5+ zmVSGuBmVjSzIsb!g@IG6dyu_Lo9a||Awyd}` z3Zyw8Q*Rt2i&wx&$yncIs|%er?*|U)v$s#sx1}@bi%3-Zc6}gJxZ((cPGYXdlR+%_Cf}^{EO)7(6s8ajW{MWGJKSYnj|$b;r@9IJtUVe1o4O)AgZT$nWn``HWh8=3;8)csF1D71u6H%em|j=Ag(y02oQ(2F3bDX*z4 z7WMQlo2duVD8L+Af}E{mG0ag8lxLcO7PZ49+Z{B`TKp?EzkiH)j}|Y^Zbuuie;e znb{(R=A<=E3Z>->Q#sH!-D*Z$gn6fR*t=Vea`T7v2OUBe_JNJtbJv86Eyy1))&_u;DZ}n6u^O2S&7) zXj_MNFMAz84-I*fKjgE6y8cNvBdht&e?G=X0 zXFR5#)5_Mu5QOb}nv%dVEfypxZqIciBk+Rcxv~@9qYrF30eV>q zUf0mo^paUKk|;XQQ%=Ns#nCZh?&F}cx|5E#r=ype02QQhUtXPoSxv*~%MQWx_v6@4 z^2p=F<`4QOgPjyb;9K0d7J)HG=8pIKhA=SqRbOBbe=%>U?^&Kt$WAj;ynYOy=%oqM zbpSd|(lUHHztQL z_B`Wq2&NnX)P}R*#`i#OcbpoEdIghNcGR#~)FKoW6_y3USgu3W8TycdK~Loq=THNZ zBf7mvKOr=PG%nu&pfIG3riii1-heZ1aH-GInHvEg<{dQ7`IssC?j|$1G00bX2E)bO z^)rlP^wDOtF;s5RN$)RNRDMM5j&UuUQ%K3rII^>1 z7KQrMfY_qJyHB`EgaRJZT5i*mNb!edD{-#7L|dTNKRlT`S9D^;M+=0PVf_j}QeTsM z6#h&*=>Z&-V)NNO_Svseb_K0f;QibeC1W!#=D!K%Fxsp9AUBZ@>TEO9*i2`V?uLJ3 zAQFeWg`1n>=YjnRKtmh4ee|33VPSM zp)FA{hB{aC`aCH~Nep5sOta33H8XlHP5lWj1q{^7g2DlY63&I$Y-K0^)i6lNp@`XQ z*0M(#_lUZme2}2*FBV`xZ8cXsherSe{J*e({w2&_-_ZCMFW*s^%|k(y|@^-yr!(Iics|p3L4sjzH0g_gt=JS#!*1aw^+q;NUx8 z_qd`RSzG|kVXcxJ#<60|+YI+A24zOCWem@Gyqv*0?TMoFXdPTeKJ@kx`Pf2b6L>Z8p&MW6oSjwGRx3Am|H4R2;U6(BMCZ8S}8+Wi@d+I70a zmdz>gH*yH>gS6PV&%mGTrB*TVh_Y8GRr#H&K{&tvDwN(Szw>n|Um2V8PdbR1F0mKf zxI&cCNX;;R9mMHCwrX+aj?W<`K308{N7yIBKP?;GFzDaYX?=_e!{-poPp)v4>P_L_ zn8OrywbD4{$Mjrx1IBFMOsEzObwl%;)nFL3siLm^)Qw#LmI}`%K!IvQS>M8KZ!FQq zCZ6*}`09YV-5hd@zd%r}-@H-&aFXdg_`x@~CbCM;phvlE z>Vo462DIn*o#mtU6*|Q#_<9RKMVukk$^%3Bx0%N+Cs~Gr)~_c!*huw0Ug(wk6x&)e zzGPeH#QQ};uH^lY+{dZP_xNJwHNNvIms~h@Z}bl;C9|gAmFo|H=vY|n0rQrlF!~1r zTt1nr8LkHA*ow^MfIdZ((PjVRk*iOG*koGIJISdAKlf7+*Ua_3=&}ib&elt_Na{u2 z+m@4wuIln@`(KSQ$P9JHXrPe>%q8*OXXk!9G^5L10X{x~Hp@nx8F}&d;KgyTWJ}9; zcET0Y1~Y`MWC7{nz3X`-9{Ayc{f#F0mL{qmi#G;jVk8&NJNGJkCym7GK0V5tWvNF-*uWrBR$G? zq#xqt|4>y^ZSS)P8{jfgOv!OR4x@{ZJs2B^4l4dem+?M@+#QCx)z?W(2dw$>Q=acq z5vJ*br=vGlz4$2^Acx}))Q#C=1Jm{_(|u_k&+Tt763f}^`<3xriQJy zBO@u(Q5g6vo|2?3&gv?f1yIUJcc2(1%n`0qi~JnC^rQ1WREL~W^^uri%-q=mJ>os5 zk);cUZ??^O5UEBIExVO{`n3WxPqi2KGI6fg*flYamxcRI;*scdHCD=a!7X7L4VDvl zfOOC#cze;AUG!#>d1yb1zd3Izwb1@K^Es;0&N#)^_`8DBR`5;Y&YC_LfBh_Pe0jprwqGVtMrysfU{OWRmY*wPt?OOl1`b zO6%;`5!`ml@B%e-5LCsdqa3+Eyfzo#!!v7*Ru@6Fp-fp-XUJ!XAxuwyh9*EM#?wb$ z4t7npZ4HVw6PDHD94s%piUN1%CU=Af7PDj-c2n`7pODr@FW^x_!`k#{r$EZ1naa<} z7>qdfUSO5}nu@#y_swha^w{- za2!ZmV^qsCeXFoo>z+gD z1iMv#s|SzGJ2MMm-zQoYcK)weth@V*i9ffpwjO95gP4?Kd96KcagShz&NiC_n^s zkSK$8#)%du?dOmqB!lI2gBd@cYY-@%NxeDejFl(_P|X410rpW#Ve7W>U{)n8Q|L+C z{5L^$e}_gex=~@tcWMf>PEbzxQGi)c9jTP&FpvFE3beuIA562%4wz;mh)u#x!{DOCCWvA}L8HWli z(>e}1V{;8@eF@mas<=`N7)&6tpE8QVdF;!)M8_WIckmTJBbCB{7xMaJxv2Vmuv`wK zC}2m{uTUaIx01754hLWrm`vCH_Ym;QZoR4fySvZ##v*F&ZFhfsG;G#m6;PwsySB6( zy6NjeiiCOA37l{HWqc?p<-81Rt;eK(M|f$1zrORXIl={q^JxPDy;V{Sdo+WcNke- zT@OJ!ro_=oabjd;oUDIF3m~93?t?-e9vy%G`DYkAjn=4902_c~5770}dbXbuy=hRd zMZS4I9Ior!mRY1+G1N+k2c}4nAOidLPL6`!>}Bw^b&}mP6>vJN$J0i87)Ps?9-d7C zfa zD%e#K64{O%pvN_x{`lC+$OI*Q5miMWzSn*3&(s{yb*;B)>l~f2OjMp zn__EL>tIPm0r##w6+#p3B0#FPS!t|1juZKWIkmNtB|~ zk;6(|olVB5H zUH?h~Ce~c5Pq?|vw?8o(HLbQC7-f;h?}D6_IJ0}J?K2ABUjGONgMyao!d z^;fzj#Dcnui*Vg)?R^YfZRBmHO#~aRnWwn4iEC$PbR6&$8I@1ZC% zfzcE^(a$vaD+AP~4++KUKsXk02iE{*11uhX`;9uf#GQ9c9$^U~z(~}j5XB5kDY-p9x&Dmo zq#<6;kP z`U1n%jEn>~^Vv~k_G)gA!`64jg)HvpzBW;8Jk8*N^7+JH{kdl%Ou#2!Vc8dF{Z={X z46aY_@S6D!WQanWdg$u*!(l|x|1dkdxDUAeCm_I}tuA^Z%61VVM_FF!Mpl_5z@l3Z z*(D4qBgN8{-O178mIQq2vNp*qLfy|@b(L6qnzpr2$<}Iq3^_VhJho7}@D)%tUma8E zFSBW;-4R}L+Dh`uU(=JXfJ>rakjGtaH7eoh7#OQG~LNn;$?CkFTtu6J0<^w4U zywIbl>Im70cMEzVS7JyrcRN74`8jkCi}ObVu7(bf%8AAAne*Z_O+-fhM_^%dm#22> z^9_fg1!XWSOg9rDd*5%#vvOJ%N|<0cs7;%nx%CXKceX#QJ!-WK9&J*DM5_5FplZ5Q z<)#?!1GE}O_*2MuqK0}k4=tBT@yOkx-{|=+_ZOWmx-zq_2T?*b)Z0(bJydXMQ~xcO zHU$5d%6A}hLcatGiDFw4m_mm=^b557-+zA#+J9?&m%4yGaOk7L5AX+%&#abK=;iI@ z9F6GIrlPI>FRjbI=a08G@Gywn@J7&s@2jvJ`(<}?a|!qzBHj@-E{7(rj4@ekR88Y0 z!5*(P+5nPc}Yu(iZkm{(ezuL?V z9~LZPTO>S-1b9wcEWZR?n++_#*(FU91J@xxS>j8ppKaDBK_msuLS%njT!fDj-VP{0$3ko z%htyWarxv{cJ{A?u~M1EKBV!)fA_*JG(sUduZ&6twR_n+zxr!BI_Q6gmy9nzOpIQT zFh1ONxrC!RIbycak0AX)K{~?CVn#S(s#i8t&(bYw-ma(;(l#Ay!$5;rl|N|v7quJ@ z7@b=^q)^B?PrhS|_qsd(G@Q%NbMVE``9Z}j)O4vHA|}SA$^EILg(ZIcY2cb+H9luk zQ_yrJf4GbIxUgN#Rfjq&bBj#L&D)(U2pz&k{sPx6%EshOH&gRzN8p2E>!~Drpc6Pw zAz?TSGi6yIDT#;XVkJOo_*vOk3c4(P{Gg6Jv!idrGn4{IGU?j^xN-a1nfQ)ajCWCRdf^Vsp_Wl=nv zw~i7J)F|Sn$!cX-r-cAO)qkhkBS%p&}nNHC?b?cA%XKUli9tae_R&{*>%SpPI6kQ znROx}+S15_Lvsx0MH%$GPVe?AZm%Wl?8xfCn~2Ri8P4h1xzzW~N~f(*pDB()#D++0 zPh`dI&cx`L|zPMCQ-d^R8q#=T$nvgl?RtuyxEyD zT*YUCwPpA8ERJ&)H9$Y|z|r%YGlUVFIH5@-Xg2Qn`S!81Av5TAUV{DScT2aYO>x61 zlI-e+>QJbONs|ieDgttJ$YZQ?fx7H18u9R z;xMTm_q_S5o_Bze55#P0pzHJ6Nd~)4A2IRQ+_%nM!+zWJVk>NHZLg5n-ysj=<|YMT zh(}>r_04#ElhC!e^7+@zO{VA!`m_5pLt3<^F=+LE(pI!dbTeBKt2d%+4#Ajrn%e-$ zXGEJ$Kp=Hv)6Q}xaU20d|AQCTDo$r9NA+t6Dqd%Hwe{y|7KfFq{-kLNIGASW%F5OU zs~Iy5;*6+MPs0*-v-z-mkvFsTA&aCpN4WyKhd;X?r9Vt2h-Z$WD+*06*-ojmPOoTF z6}1RY=p95x5m(|Qf+?D>><=oFuDQH86Iy_dE@zi#46BbAvT9oK ztj`_@^|00NGB-iiqC!UffmNkq#)+AvQ8YzFtOlm0Mkz{t*hzH+!y-qQq(>>T+b>k> z<$@y#nhDmZYxnh%{8o@933K~Ndk zxhB!2DD7mXzBs8oy5z}$>^!im>ho2daHb?Z<>r7MT1x)hP-g=_OIBMm+l-s=#}qVv z6NVDH!c|I^(qA8=LV)j=l5$-FS_DcHS(a#7S?IxyO zB~@mg`+?TGrc(Vuqu*N)cB6S5zB0)~P|7iJ=)$y^{XA>SLr1*aw0S1!^`;9a!kk5Ifz=!Ne6LvZ#f&`z;S;dMV!!fqwBY_CFOiQxWk z9hH^RNgA0wW8S;F8u@XJ2v32O07LdxHFz?VTmHs%i^IB}j3VbkRdg}@xUD1<<9tfqDEPjPWYzsvB zXL2M;21PrA(Y%BH(MzDE4LVr<$EW{qdmaS}MA8uR*|&tung>RMP-|{IBEw4p#Szis zOz-sz1U>iuTDKwEg1QxV*aH_-ECeSbp70p8I~k9eCs%8;NYZ|l4t@JhI%j>ra~&bgb@AeV8Ky14BxOdlfh10Rl)sIkYO?mM7cnjQ3%Ra+zOi z&%;&6F5bZz@*AI}&&*^I?VjuYbhqg)MiwjptRH%PyQ}s^2dfL_rt{TH%g{!Bg<~5A z8srY-3O?Bz!a~49_^6z8AeEzp?Hq}W4FAsWsqjy#`~C9+Ip!lyA3#J5bZbop6Sljn zwL7%P*n#R%(V92wW7&te0xsoOQ#^yXLd@SkU;g1u%TJE6*2PPuyd=b@)-YtALHm*Y z&+hW4CN#45mpllQZ}V@FIMb?G3iok& z{!1YI6(=b11v%>+SO3y;4B?M= z0mY>fF-<;A+2)RQ;1{VL_B}w*i)h^c2&jJBdr|ZcB(_CLf)QDB5G9>XYiVY~QojZbXiZ zVCXyz_c?#>0?BGM?C+s)(ikdlIeN^gs9X=yvJdG3GzvAlqg1|r$|KdiVDBb<_p$Ft zzI!6}ql+F;@((rcrilHC7BXfS+FhYM(02V+9^opt(=v{8cTkbAyc#Y?6z`y-s{t}_ zvJ)X51{npaA9?jI0cD~tg~WPqB#*hWv6%+Vpq5l6(sGd}J$dIy5U%I#J0U^38BMVR zVL-*mQkUw+x7d$4gZlL)@wfRA)dzZVBMd$5YC3 zvFR;G9h<`e<;MWg)M-3y%`}K6UDoC@oPwB>Uq`O=+c;Lvlu)JB&eq)HNZDN6IqT&K zQqto82&tFT{2RCL2;f+|MSBY4_Hg`;dNCB=cU5vr#P8tSZR+UAjsF!)F_3s#=NSez zdbrhj-v?s=iPT*p#R*wR$1Wq2_CK|g5blzXk>f}juC;kJd!A6L7Wg`6*4v}je<{cd z4%z9>a4AUfnWgK@i$eji58OjmCaXQE*`XRq!6I{KMVYhoq*aYB^#ORoF9ZVQlRSSn zVCEwgAH1O~t}@D)<6#>)coTf3s2k-h8xenL5?M=yALK9LWGp9qf^o}Z66`%RMNd3` zD4ZY`0|@%Y;-)IQ*(2UXwWXnW1&* z>EJp>J!&SJaDhk!^*c?!*8w1)DM5lc%MBP|477jUj`eANG?@;q?7IC_#%uRpfE$}L@DTD$#1LrMf}bt=8z1ZOwZ4?mpkR~CLADzb52j@ z+jhfI?-o#2nVJsg9?$9+(xH6J0xka@((kp9_=55ppr(K&o^ zSz1qCIYEO3G3Gnpw9m&PGcElOI$h?I4{t1iW+RUdqn4!c&)n7<(2h5ngrDl^PE%p_5cw1~OVwSFI*`q+W~y(#ns+WId2TRRuqGL{`1qQq`){43odSO>0n z%P}8)P?l?#_zk>4 zoTGQSKX||ECF7qgqoxP@nNeax)o1ip`7EkQd%A>OO!dY2U&#dTYN_if1j|xktf#DQ zqza1jgCf>LG+I^ixioS3IqX2sRH@RJ-Q zb8Z87Yi$u8e9@dc(s?_GcnQB!*GwM6dCV6VsobQmnIaS#>|f_G^BnBV^yR7jlG(1(9pH3l$dk)$wte4P0ofW3`ON1WNq^e@(^N?zY zM$VeDJ(aObiE?X3j%$1ItgYH<>1Q$v6ECi-n~zg&njpOu;R&nZwDSY{n@>t?yWt43 z`)>0g#q*M7peBn(Jb5qS!nUH%Qj(7cT-3eN1AWZi*7w_ut2M4Vebv7>_T930=4bEC z-K?saxwj)}kgH_YOY7+3o#1nf^TD3=;&h#J;znOBGjlU{@$kgTZs34L&m+H%K08f6 zR3DDiZtu=j4sUk-ZIdU*#Kz7g;bZg?Z#5d(;oVqHHk^s{w8og#0SS#~T@MN6w9_>2 z?C3SP3{`{g_T|OYjVY+Az$lu#q$>1 z^qt_9*3<+v#`(A7M0a2a3TyW#(_%NoOmVmDWKS&ET$-z^d^KFRWX&JHp0^$YYFmA+ z6v*htZkj7xWRdSMe(R+d+@ujtt6binizssGBQgd3jhFe8$*v{0vPO_`5YW>!T{lf$ z9=XrAIMA08(fM8smW=@IY>q_M^P_sA;kP3SUt%uJa@u8DjG%6lWENGf>QivEC4L5m zp=z?1?^_v>+!^DKN3WUw5*Avt9%cuGd9PSPSI++WGYN&*j;z0ENwq~*qj=MFHZeP* zWNbiMyh~dx7DsE*pF5TFQlI#oGMo9Tz@E^^ z04J0aAU0q!L3SUB9wjfW>d#Cr#)fex4aHCbgV3QA5&>CN_iCuRr(d4ZMWu<1=`=ab zvYyv}m_Flc#$WNEjn9?j^+~D11w2!T^Zif_%cxS}-ektho=bA^<$Y%*KYs26o(n=JF`qtBwsl_KUu}gaX$adK!!wi|?$8)Af>?cCDcIOb<$-5R z)fMHVbJRlj$+_htG9Jou7Yze3^Y|>K7Lz)43Qp#SzbkJFv|TK!?jf!^CHGdR)ezk4 z0*ewQ?fWIIPf07M-g+?Zk@J%H^V2RlI(ZrgCH9I8{JQ+~SJOqid|lAq!ht|P~vdNj~>H1>kA6Pn?DYb7sqTpbB=y(yu|9^$3 zu2+9^ODTRVbPy^3ES!Wl?k3RT^hB+Qfz6nC>bwIImweaALXqYKIYh8{YvDWO=1ST} z2i+LO99%A-;fgpO?8s8>hrfh&o%?6l-w{Fa!=vFJ^48JIk0JXSh+og;)SgL6EAc|A*J`3iD%fwJktE$CSmVFo%zoC9Fq%n_3PMM zOJP~r@V|^SD+ZR}=RYVvlJb349jn!MmfTXv2fKBT;vUPV1|{FC5txhVu|*S;^%Jap zb*pZMKYD+IY>HLv)-lY8q%XQfWMy=ehn3>a$a=%@Qt@9Zs>@y;?#MBP&v?s1sRuoU zYaaMh=%czgW#34*c4pYW(^XxLSY7=Rk<~fSE7kDVo!&Ykyv#&u{;K>;mVE>yx6iE0 zZ(J1GwB=F1!svS$H{y|YTP7}QMLezf`LMjqXK1H(QCTYNdrL(A!N1yo!LM&ayjp!h z{k25wl$P*sW(>%s=u*V{H(8Hw54w8p++_}$Yf96Eq_*Z`nN9=!kXvc-PArPIM#QG2 z{)52&V{zV}noyYz66K@%Gf{RbN{!tJkC&-gr2Kz-%H4f}N)^ z){*&yZadbHL8oj6Hk)xxpcV4c*!1+()1yAHOD)bzBQM4h;~=qt1`4 z@w@B)E~=97JeKI8RQev~hDGlk+LQpvw{Ay+;p)lP&BZo(Dr^gBFK65AL}yoR(xE~_ z)jW-w?aTI0x=J4y8c$qeNdoZWC9!hGIVj^uHsCdB28+XbtyNuMY^~=C=NCSeD`)d? zHFvgZl+BxhN{x8p!4u;gq&rN`&Q@>L7n~`IoUw>IR0ew&m~0_l`V$aSV~2WSU61kA zaT|kC7pHbfUx&`_)$6q1r9yfY%$yuuGofyB<{89Hl<1%6FC8G6;n(-fdqgf{TRVMZ z-X>e)PSxraDdFi4oZ1Hk)dFg(U#$QzMm2(DiR3RE_SG0NUzv}YnA1*;bP>`Au_Ggt z6Ph@@T%50!5q#wI*}VrmwMTouPe+R^UqPlX45Cbwgra-Q$3V2ZSTHSI_cHH}KGh%W zPO?`kS2qzgBF5j&GVrxy;3n_GAEz}4&?t=mx*8k6&3}#X zD!!7PY|nw~RCjBWGEr8|dwzC@#G{<`t7+y7&e*rqLhmY#d%05Smh=EUVCGmuL6YT- zTaHOmhL>=wY(Pu-uv4kaucRy2s_S1@t@TsvG;yl%z*X;i{yraCk5Vw(YB$;Vu6(#9 zz3iZqHPK1Hob!c?^M?b@?3(Iji@s0|n7_71?_q;hV<`6={k)134I13x5W{=(j!B83 z91aRnHu_l}|3`ao`7%u`K1!YZA^KEFsCykbMTT)E@P?{1Ezk(!;#i8eDmEnFGtOQqPCNqwcf$p^v93O_=s=1qgNQ}X&N|=obsYEl!!C4=blk#{ z#!&11120Whd1z4I2sefk?IjH967;ry8-(;ogfK+sU^3As99qW~j_Vvo=HpM^$?CT; z;8#t<+7rUDif%dzO2t9u4BPeWc4eKA!t+QDMlW@ef-lQ3DHkN;JQt%ajvGQzQau^( zlJ1ji<=J?w(p$pS&dEaWEI>|-k}iu8PPf`QS|h4 z6*Pa*`SMqQcp}}mxKfYXY3bJy;jScDdX_0RPuApjkbZxVtdk%$uzVfN+z3w$_md2& zhq*zJ#^Rz&91)<6mRR`P2~v6a5GosTM@a-T(+I%X@#;aQ`*fI!%V@65pXZx2iR^6Q+jAo zldsqx+C;Xx7k8`4P{fAg%LM@HUOEOE$7m&1jE z0VSt=w>tad+c#y2?Pl6(hRpM<;n(1)Y)pMNlhR(ZShBx!k>X!X%|0=nXMaLHA=GOtTo-5K3H!4m@zBLgjo%W9yx>Lj0L-}y2jeo~T zon{RuZQJ;~BEYX~d3rTL|1(WaQgYdy^{MKq>)2z$hkbrGA|#lfv6-Jz6a*<91?Dfl zV%{`v$^(*)TVvDsoh0*#6|)r=`@X?a7#Phku+Sh}M{xcbeweoZ|Ac`j63ME@A-s()H!U5q8rx4C+U!H(~6x~o?)N6Z;QbbbsT65sM1PhMS8 z#~?B#1GAUWDXFNkXo57{o7;3(AaSSPe;c+mYP$uTzI5%9?#q5g@Litn*~*L zX?|?6t2UV0C6(!L8@J`oux&-c+D06EGMX)BG1LXy$~Gh?o0@&{a?yZtsg|m24#!tSXRHZ zwlpSR#|_3u7Zk>qV_SQxCbdf!Z2FiB*0Ux!4S7wu4O--AE*%iJxK+7Kc}nc6X*aST zCL6^}dg*$(Y><5;)M(pRN+0s_58#p%YPf8b(NZ2GUcig=r{ztY>7N9$a}OXAci?EI zJJ{my>xImFCq)OzzUzABTza^u8MvpzE>s5t?8PU3>v~d#CiFcyB&{#dcKc+hv~bGI zBZDmjCKZgE`l~zq#iJTj%{33*%(TXC>Q$hdb&52JUuH=o8$#HF~6k3L2EW`_;edo8$%WRgu#^5Jjpx(|#0eI!4>g;>dJniHwV)-pWnWW~=D@9a<3g|L z&%i54Lk_Jpt+X_0yaRj2`y0wQi@uofqyr`N1;ddlZUY*@_$!RbTn;+^N9IkNeFXJK za^`Qy(5-E_n9WT;Yies1b9rRGo1}UCJ(!3!APpbQ+23#d_k%o1a~s~vYh>F)KcQyB z;;`MI6B&qrrm9`7&`O*CFwmzsj_tr~k)SH(jNkb{s52!Eh0}b-ke^8uX^cJ-Yq*To z#?mi+moPh>xBOVnK0D#&U5>?E@Mx|lD;syRW{#%ijr`;K#i5jROk^DC`GS@Xx9M5F zG}NeizZ_hUNZK`7Te@qF2|tUbFmyGH3D_`r86pT!9Pt<)>E^?bMj!pUcf1OmNQzaA z`8nHVm~DmA`cWLhx>?F*TreT}laM;~!P7cD{$yd#s#>_w@7*ztU>BW>Ntx=VpL5Xt z{S2o-M77f9t$yo39cBlH^-%UeNen`GV)Opynl}8GUFj<~MD(`kP&9ml1Vci+PgLqw zaDvBKb*}=r|FWgOFwiwbrS11{7tmc~3zI2YXXdJ;)wW$M;&!e=lBHzj3(?Cq3#U{M zH+vUB{iMvTsH@GSEL^-uigF6?E%Nhr#wLQb>30YrU%$)yH*(q=zaL&}@CSld@B3sE z43fe0yHguzl7rtr4^rFCjLCk1GoDJzTFFhU$ur%>E)b&I_~jU&;_nYtRAvU2-St1T zeN>#j3YH!fDq)Ga+CxPMvqs-c8c`fh?2|xwfa~mB+1ioKeReVlEB9MrJw26^$7xMZ zg|V%k;4>V5%`aO>VkMR2M9g%&KPy2SH%hfom}zH=5(2S%CLL_b$JvkGpcdoMI4`Xf z7xP&c@yye_foeM#cm za?{CDLC^Z$vEXn;$4eDMR4_?e1mFLF)TL-_x{$Ld$uiGX6DFfG_@fLPo@~IASxu-} z%8DLK#p^6EK)f_g8i35mo$j?lR`J&TAMt+go`gO|&u-~gN^DGoUW=x6Q@%ETUL7Wq zd2`SK_su1|0i`L`g=3hQ1C4hFqHSj&+~i`ov^c5S||jN|mU=%Ereq ztk=@@eq=pO;GNHjbrzRd&HlR0=kvOBqCZ@6LYpfajWwJXJGgq$OcFJjhmP1f{&6zm z{WmZ7(6oS4?7BC6B(#!b{E77k-+wbrmt6}Y^vPhG&0I?+(|CJody$JrW+RQm=?}@D zBcaq!e{}&Hr6-T#bVWRxUaye(=Z6E|JGp;|%%vUQyqaGWDNd5%7Gn*65zH;6eOwD6(G8y+)p(4JLtF+?+4vHt(m#toV%WU3kw5URc58h6I9|vOpncW* zIV6zdA4=o|L`!KrKIPJ7K{d|z^HjKJH4seK_;Nb=sonN6@$wVh?Q-(0wN6Toio~;! zU58%JGR;`OXOWae^{9aEF5m6%2bY4-P<;9ubH#}=3hTagWn~jT;)!gP^iXEOOfo^A z*O@_!SPXL(s{Mxzu0+#Z2A&!HanQv;m$T!`N-&C3pUm+`F}9T5(YD3c%lZ{c_HJjo za&1*t`WIJxyW~{|I3VdUHgqpK2uFNwMRBDqT&J#SOr_08`tyj2VhPPp+D>_yof(m8z0S`gNxcRXLfl%p zZcX6hMs59k$4HP{Jd5<_(qV?_f@f4{uXg+gmzq?nPWKKfk&&LAQj}tS^z&c>9RAPA6=o^X2%*|V+BtZFv^ZI3=NJ>x?CmQvGOI> z(C?&R>eTQ|N$F?DnM*m6h!NlNQ`${cxHgoG=i;@0o5n8WD#2LD;G6huA~;6fPjnUW z^Bd_-Qj~SU&RSV3wTsCuO%f_s$n_4SXu@^m@AP?x|aKyK0J_p{slE-nRC$p7pE|sllkzJ9ojPo?j(x zNpjNWOK|KSHz>4pU9)5uO19U- zXrDnhk-;dA*nC8cKVF>7+1hmeF9;SlAPiw$a2g=3#T4;owN;WV=}e1!Ji z`RW*W|M~mpg3Q`dHK{D2dXvqtj%r`l{=s7Lb+tf2)}3G*5xK*mKxfzA9o3&@uPiOU zdD^Wf#gebIGE7!Y)j@3)3etb%P{>CT^u>Ak2T&``(p|BoF2w84YRJWk8AmshYj331 z*yE8Vyz@bW6s=OtIV0b&{H2h6>aD(9 zf98FdaUa4!SPtg~=z7kHC_@fJj9*Fda;l_2H;KWVJe5*XX44M6f$f^@*P*Otbw(PL zjn))-C9-+E8M}Y~M$f9VtFOSlN$-^Rut3c&@p4*5Ox`V_V3Fv8CACi$w8+!89@Axd70Btf zflZRH#5(l8*;B_cxO|pigo>Wm=CsA<{QcGBR{Y-n&N(aavcEyrZeOjTFjCu96%)#6 zG-)W5GLLf=Z=UwZiBsqJ=N`aBrWYq4%0KcRnRwSX!*fZyf*J#)7iJ_0`Xy?{uZX%q( zlrgEFvsj*cW^btA7y(yqg!J3Q9|wh!8CkzH_W^`i7Aq{{q9?@7L`~b)mOU0TNB?nV zLH%6_wR;yN^!?D1(&a_rM^qY5+mbReYEqF2dTCWoIsrx-UDA-{gVA&y)g?G1X{~%> znzPEhp|D5p?HP02l`xI&OPAq5EPSxTyo5tK+d){0t;Af^4M zZQ&moeFmph#AL+j?Y!w+4uMg++L<}x%Tx|0d6R~)t)s`v7KMJxxXMexC~0)gBJfFDW!f6|Xg>dzy#?u5bYloff!MnY%7 zzUT5D{f!D3ksFP7euHrK4&{(q>bFv2Vb@#i&sk&AoX$_zWKq!VO@wUEPmW)ce0)lRj46Xq{hVNy^53eFSz)9qEC9Y>q!z3f@_;w~o+K1wN_b zw@iQQe`5iOpHXoc8R-PS&!llW^AzK(>~`VzMTfbWXyKK!7|vydB6Fm^0*J%_$@Yw4MUgQvJi7_F-P?C z8Far?febDgEw_X)3UXNs62tTz2*sDkF*4LZbULC$nHZ<3flX>LVv*Im5WK1SuuZJ* zTIrI4x4Be-EzPkSY>sIMo9^e=`hEk0nX=J|nZ#d<1_;}T{C6VWLnjrEcW6$Do>gU_ z>V$LKQM*YZnlddC0}81FN&Z{gFjYHlXlcS|J*8g@2NC)Qm{C9IreYV6$1-mYUc4&x zq8=plG|=vK=6jeU<-B9@$G|~^`F0b4WV2@Lf`<|a16`!p@zR)_o;F~@87$3+Sa%kd ziJ_cg#<-<}+cO57KjV^kZOjg&hM?yM1L-DCd-)pju^q(0N4tefbrl)9{h;aUaU9&+Rd6j$EnQt(km?_Y1i4 zw9TYUtH{F*4(7q{Z3j+5d(NppN{Cm)g(b2@3d_qzPupIr?DSglikue@M3Soq>t8BfY1q@M79jWPO&7g=NLYmIiQlEjXBOsLmRElk`Xw5lD22%fuNfIv=!i1n%MY zyZ{#7WsGY4g(TO*`|_ei2APg}2d>{(E);1DoRDy<0mGg5lTr=c^XpQ=P(6AVF{w|% z!I27M7dG}pc7pTs&`~mCtFjx%p|vtCgRm&Y!9ntZfnwR{D29zxunpDk?S)47n9m(_ zGj*_fp=7)Zj%i6rcY#sN`vw;9HkSqpX80l-ff<=M%8CB(`z<94<5Ph{^QU(^G#iB2Ys*ZEU*5->+72gM z0m>u?T~D={Ugt_7TqJap2yQ$br7uH~H-bcOr@NZmbp~q7qpg1^tZ_Z`MK4=?S!iqE zaBF~>WF#8@WJfY1S#rm@vUe=xyZUbTv`q8Vy#|?{crJ#`x>{z)V4u{;CC>L{%VSBU zaszbF+L+6_85J?OWOPZi)*$`V-&ZWnBV{I8urMnPG68?)s6YQTw@3w+K9_>?Vx^*Y zq@Y~|5V#i>yzUbMH62d=29^WxCh8ZV?)A3$qE~YzDy=lKf*gvx) zzgu!et{Chvk|S$V-i?73N=4`)Axe8YvlUxPa+4x`p-bO<%!bD%3f}pqFNG0QxtdFNb@9t8pvh&;KkchV@VB?O98cDmfg+K^tYAfxF6k@sZ zXAdN$NVmf7cTSt$8$78SEVd73%z9?c-_!Og3#ZzdBapFDGFLcke$;eA%TuVbQP~qr zvg<8#cZ><)I#k$yH-NR@(?gE<>e#fR)lfc$-%FsbaIn-4Bjy8RwLz{TKy7W(W!vPz zMiK^O>6o<-4Pn}VoV|p?@d?VR`MY#}K<^JfuCcA2h3dkH|LOcP;Bl< zOqPI^5G44=Y7%`sI!;NNdX=1)&`!zJmxy5yg*t`U z)CA{!!pXt$8R-o{WPgtiECUPx->w)iP1t9e)#3#|Gsvf8|KhA02ZXSCK-#xS#sYG+ z@_`DxFpr)8J6pP#6d+~oAyry`%lvtI*f<)XUAJG2?8JY7)6eWG6GrEt5&_aQUwt*= zHvE4^XN@MoBALJvo@Z{;Vyfsh8r&2@noa&fQ(fs`ygltfJ}}U}GaeymSYzND`Fr@! z?*A8X_x}TS^~!mXiJ_1;j{r4H>TMg?mt1`oO*O&GuOXx39F(B3gFtzJb zzno?)#0vI$Am-v_nVXNiMQm3={|Ja>Gx>Jvy<50)qNNh1*@ zr_MqKXPkZ#cUJ5@pydQ22A6#?9A0(zRk=)Cn8R69WdHWZd3+UQwWrGmsl&ft0T^T)pt-;cX&xmrcwOXlGga^ z{ttQ|M**0#>BMh?2&n0n+OHdYc5L2amgM;K#zfmxfB$sCy(?&sc^}~*A95A95R-{w>ReM~vlry1_qRk^dsqHz z($75w+hwsc_v~yI?shlG!y9Ump8{*G!Rzbky703g!mT-@jvjtjLT|V{!5`Wr5(IqMV` z@LwuI1k*Bouk+dziq~Vv@#(&X zbSr(|ampcGOi4C;N081y#UMsC{+=`K`u&%!4<53VGbP1^KX86!O?*2Gh)^V5LluRRjv4Qe{?5EDoebjkpAi03Zujn zi*#cUJWp0&WD=OuRjav3{$u19Z6mlWL(lIs@i-Ay4Fw&2@CM1h(tq!;AgEWDJ>vY` zwg0J;Ue!l|a{2MXZ$$o|sHr!~5BgUP3b%R-ehU{^!NCHWu6kBBzI0Ka-kH=&Y*Jbi z;k4FDP>_aeR#9chO5BtxeGcD2r@XT>;=jYoqvUC$mvop>5N#C0ot8+UCn%H0O@4PV zw*O@JiO-21oz)J73_^XZ`}Z*H8 z&NWV{vLkgOrP0Dkj;(q$QEg`ZG4;i~y<_Ly-9)uQ0gbzPNd#y555TYhjzyafjjm|k zoe_|&t)rvt8Ph?lpy7tQ3!iGQ<+LgNIR#&bm!@5tV>1Zy4V_n#)A|m0krcgOy?GmP zF;J-RH4g+4S93VKRUNi^ydl_jj!1QtO_IDoLRLDT-t5J}<;v^|{MSj5K5^8+fBrIp zoQPu4K5gVZ%rRL$GGmNt!72H#BuAJj-+VQ1rP=<4?A+G=Q|a1+|9>mAm*E4l4fdGR z$19dkPC-YE5P0=K{%b<^sUba3xXYU&mwP|mktPV^`V*@Fl z`wgcEoHni)?6jtFJ)K0=jOZM29(`oZc|`7mt&7aBGlc~$n}5@t5io0gaJ$qVIpH6{ zhHLNf=Pj+|i$w|doLkYBczKqN&R+ed7R&>g?V3q>s`zu$>SK3Y{tU_Oe`5h7;yNrv zRAs5P35?SAXQ{otx$JQ&vV2Oymdv1t5Qx(UHxk=8fA0O{t#Xoog@*wOzsyC)3Z?Jv zGb?s{k}PLe#TDqmJqviAcBZ2&TY9J;+IZK$?sb*U<4?afkqD;dtjY-%AN8Tss(z-r zUPA*-oLgjS>Z_ykf4W@uf`<~%jPfgPIgJ3C;E{JAt@WdQPmdE+pM$uNRzln`1FuNR**+|fGiSQP=sqZ=&viNpj)#!L11UdrI?dZ?=2H78KWf4mGfg6JXD8L*)2QGS z34Z#O=3pbTG${;l`!55}Ms@YN-5Te|0yr9{aHf{AL8;7YM72a)EiN>Nx zeLVU>P=C>lVu|W3a$(xm3^}ixf;MBJY=5ja)!x3j(R>1f?W-{52`OVuHS(Xt|J)m| zCf;D-En6}qz3t8M%WndqVj7}P?46~LDQcGAmDyjUUd<%4a${jyR&R|&IBTf>x!Xc- zbd%+N1Je5x#(uBLZYsL|?5VApb-dwkpCn8c=HVTC<8t?#b@6~XzTeSas@-oG_o*Rh zRT0|$a8X}2QG`Qt5rtBCYy8yYg{`^Ki)IGO;3SSXSfU2E{oMiCqF&iV@qCF_QuOrl zdaCzQNT4dgY^}|oWbJ)!g`DqTm_i}Ku+(Nr;wxSA{Vs`~nUAZvlkOss)b2q?HsxOo zXX;V4Ig`8javFbX>)KzU5(+j%5L6MRNX1SJDIi@DIe@|Q#PR}1)!7H^^v}j}th~ct z2LkCqRPK^pOsH9l~U|lBYIw%wJnp<2|wQce8=l z=O{W}Q7R9A8 zs%Wezw(&f~oK`k4OvF}EbsdJOqJ%kjnXH2R{*KNv?$6`4MsyMw#m8Re1&EVZ5XmrdS=&_hW}8a+@C-v@;Ulr7+);ora0j zHjP$JRk5Ma#YsPQO1HP`LE10VjVDPrSzp`yX%Mc?F)xX5iD#iBKt_vIB_ zA_3%VLZoQk!~R@wEB)+@y``o(%VE599(PE+=iH=U67wpxkP$@L z$Hj24DD{Wa_tKcAu+PkXlrl}>PBo51XQfQVVN_q)O^|voY|i5r?RV89 zq&PHYNuJfuRwEs7is{2u7jLtq2$)qq7gHxWTTCIm3zTdW3v7_GzTU#d#GC_0Bx>^O ziIk#HlN2+8QByJPEBmxU#33 z2^}Fv)*Y5OG`VREQ53fY=H~epM9ZAKMSUwOR{dpBziOco@{+MnufadG5v z@)-*L?%Ok5hW%K2bhDbu=ijBanL}xUwwjT8xFuigFeHN)>=hv=JM~Tb+#ws~WY_DM z>jt#av-5Tdg~dfCSe0KHz{VGYwmwC--DM;O$DvQN>N4L9o`7Yf(_Hg+*>@Dr&)45T>Q!#gWYx2mqg864G;`c1+P~Y$R zO)^|9QI7!^RyI4vj^qyZ`I>1sQ+1>DV+bS&dr2r^e0zeW-Tr!c+R_Qc>(8% zXGFYcRp?`XpL)w2qZ@ASGZOE!@V+5Dwu!TXJHazLXG6`#TvPpzr3Vq94Umw80yr9i zG%>dg;dFig0yHptu)R5OqZp((8%zL^-cEW-nozN|_v`Di4})-g={cRP{x9A64L!^6 zH{Nugr;6C9;lF0!vr#T*St{IBdeqgVK3T-P%lF)7_bK3h$|mrjd4V^Ov;c2`_`fDkn- zFu~E|Q4n10te-Y)$!tiVX+3RhDgnw8?Q+?{^pIPZ=Lf_p?&jT>8hgV76mTmWd{HmV z>)TBkcf$I>dU)A%ahrkkE_8T5|3Q+svacg3?(;z0tEoi$zj*XdCXf$jk4KbJ{^iVA zw!wJit+npgEG%uEORwVje4w=OuClVroXqJ{)l^6Kc8IwtijMqXy*n-W%kxf)zX5A| zB89OiU*r!S6uE z#^`{=ox7@}x{3JZM&u2%5L0A{ChLT$OA;V@vnUO=oOhx!1MgaSRYs^o}JiE;DWk6P6>Tn@!tqNIa?_ z16UWZ4Z>(Qi`S24p900d(*w^rNEH7KDX%)XGrV=DKGihf6ZiEM;OJif+v z{k#3Rm;8^FH!T8l83mrwz(VYz1V>m)EV(JO^i($&>dvSHgFVD2K4_c6~DTBNdD@5dSKm*(tt_Z}A`T2h%Tz$Tfa^&B#6aPCB) z4!xv{xt*MvP~MCdBZ*ZmadWC>`{B6LT_1T)ob#IW6|&qq$T6Xg%Gy2|NbERq(8^T93XidJ)BjHX^`{Bu;+J& zF1>$LA6beubV`3YW$79DU6^crgr{I{M!bU zLqp)DUB=np0!k%$CSoqKdBi>21PeLO@$f^gpEXIr4MZwz&$TMMlG|pn)#_b|@@>qV zyy~9Ti5k@S_`fKZ&KrbNpZ2*2ZR5p@XV4aIv32{*r6<49) zMFGF8CX2vs!sbn$F>f@e9=7pBrPG3|m=paX6IGAD4(AR@$74||4lQf!Xw$AEUxmsb zG3S-3J27x`v)*|NvT#Xt8J@5FPwi17gY!fd+o2w@LdKh(U=52o`gmh`yxY@s+D0`% zS%x*Z3(fcq6^eT!DXU6GN?K7&1WHT1Om!)JK;3OmpXSKHd_%Dx?kB7-F~H#Hs-V2c zLA+?HGoY5(LO>b9)a+G=rSjP9n?@6fXqhL832soHD`lg0(dSAGUKsAOWw(qf9w^u8u5#}3{GXe=LK#DxVE4R-%J%Oko9 zyetHJxBx1s)m*k9S9y{}y&&Ru+aRMg0Len9i6!2nrrO>asGI2!>8`%;t#*?%^-j{`V_ga-N)LfUk zDPcraza4*zE!+`k3ne=dVLMeUBX9iB29#rrrA z8{bH?3k2aNF|bemEQyjz!y+H%8mD6po;S^i4z5qn*XzoC(eIr02lP9UaR2Fd`VDPM zmFZzua~31&dMm=A56BZJlM3f}j7Em}q$^c>oe zx2)=V;(yX#;AYomv}^h{WH_s@nI4bD>e`af1J6XsQSVgzHyANnd6rg%jgc}111t9I z0v&d$zNV#tuWxenP`QwWsWWxqh$@3*?YY{C-&$_j>FJgqn)*r?COtfbXhIX6d27sa z(m-6E&8ulXVWuXk=FjnO3z2g29hsU(pGNI*t9LtXugb}{9u!)rO=Z{BdX#|=Nmb&| zBvq<#B2+hauMCImX+cT%+k$R1cU(#kNo^f@|`=dG`SoaD!Bs5gvCuPD<*@nleuc?XsLs1XV~XklOw`qC(mdy4c3BJmJdFpm`TxD ze9xa{4pXCRinu%YakHQQ_=$^=Iq&&IWtH;Rce+1Jc*B&n0g4vS@%dp~wDj!Vb_<0h zoII%GyxCKf6L@2kaZMvq7RwBw7^Tt=M`=XeYptZy&M7;?<3`Se`Kk_85h|ugTrxE& z$1=Jm1ti6VeS(K7W#`N7p>n3RLzNLP@@U|aW2pY(pu z&pOZ+25oa4_WtGhy=J3CYNJC#Wp7Na>*GBRW#hD+?2fN`qFMX&N@yTkju(;N7#&zW zVWWtCW%A2FCN9!`+;YUC%6lj7+B>EE9p9Nd$}{~Z;_a-%1i1o({ou;%Rr501Vl4D3 zCYDt#PTF`4mfEeF(>>VEn;*kdB9#n2W2=&a4m-jm6_Ct=+9{w}U?&XWJ~wUH1we6G-fwS6qC2 zD*>key-{6fDN)-N*SN}~Yjsrmy%6}$@>7|9W!!eD2FQE{t7dKeL+VIzy;=cIM;@>f z*Yd@AkGmSC=;0_J{;f#m((;oisX^STFKy|LZ;ejlojT#1zYYO0l4T^P3J8OoF5_~EHd!z!&?S895QUA{}Q(H_V)g7VXIfK{!zSuYhYmk zNAkY|4h~M|KWA^>0?Y8fp6kyCSeH9H=>S2wA6lt>ENH3BJ{FceI3%7>h-o51if?G9XB8rUS+oGoEjRS z-+BCbKPImjO%#2TXYjNayszy#_g**ibTn!?Z9o-uG@X%=7aqoY5E*#2WWG@p+d`4& zj9G9nZA@9~%!ICrHrJ;Q#%_aiAsK!Aj?ZD%0P@FxlST{Mq4JgK$*s_?2`}32B;3B1 z;IHKeJwlnMQFOdxzK4^iM@Vg>K7o{sWQGV{I?3fU?n9G3IMef%|4@b^$#J;pEYgr@v)|NgQggWX*7(q8)9DYK0| zz~1xRNU7Sr@595XPbM!ex^1N)m~e#7B_J!>E+grXPt7TNnE3{K4l+^jwf(Hyg&F_& zsI_`;^sCo>?8QV4uF)#K%O(NuIRW>^ME7K2%5%31>MG{y*^TVd^6oPc-d_)PHkT)J6{n-i&$idrpiFS{fTk0}FK7ck0_uM?QXAF!JEfz_ z1F+>aMyTMtXohDPUfD8TW^VrB4na9YV!9Wve9@Cb@g~PbNK|36I&%hOS^RKJxQq&A zxI1f>Ve|Lee02l%1kwhl`JwDPT(LoI%FWiH1NB}v0c*rCv4FSOcd#i5_38GxeTqOL zuqkJnoev*$NPy^7a9=M<9P3#W`+RcjsP@!Sy{?yQBsY|E)8@2MjjO$SXzEsQQeS&) zJ{h^o-=`|=`{@%78i6>)bQ8t*im97)pmJ)@$j#jWN(fY-JBTUWuDocrZNsTId|!PnhY4!cf|O@hZA z#G3&#z1*CB!qsO5Ohiz_v-1prWV7+wO{{MLN_eBwY;qCUf_MEK&H*1E+u=O?d6|%l z{xR8YK|kUUl}xN11|}Bpox`{*EwA|G%`ABNVDl+pvd8LsHTn5XuCKICFh9f zJKLZf$|);09X>Y6X!Fi?Cu&N>q<*Pczj0Jsk>_#*mC$9aEU8RCONdYWpvl=L#i+AI zbkkTkJ>LWR0I}q^X&T->aYW}ziwI7W&U6ty#c{l$!SbDk1&OAJBA`2PutZ&xN;s0sc zo}7I;V3*b4<~_bW^g8BwI&Fc_s`0ozkG2xpqkD@}8H~42vqN2kEW@_2pnIz4Cr|9F zM;fPl<99tgmx6s?HS+`bYa848AJUd|x_K7bf86xW_OXK<`5(IOWMq7!#jGZo6=#NhVqoS;UMKqrbLz0?_%>b6&bS{X?dsYwqh0JL zTLMa?A#XgT6FXvf7-E6GvCPf#d{KStF-!1fftStu#m^D?E5;E7&KHlPe}6(inJXkD zX?@1742AZYyc5_LYu&3$xPg9tNQ?9u$HsI4{gkR)8lDV2>K`*^&G^sPjZX&V$Fm*; z8}yVFjStsv(uvIN_r_bzqgEZ){oMNW?Tr1Lv5t!!%1+8oLJRmDdw8rNH5s@!dvZW! z3@u%-Q1GV+mXGySTP@LK0^JC)8?}XQIg$V}kU>DT2+S?lC6{_wV8gCg6Lde+n4hnv zecd+}1mU@K@7qAYKe4v0s+X+La%ZF5M3_}Bs8*l@xQ8jLb@_btM_4N|4-Z~<)eG2e zPI~9CY&Fe?*{3Jmqgs=P6<*Ux5G3NET| z!mZLm1j#j~+oKSAizXQY=$bF82@ z(UGIVk2zIE*PlUmb`oDsl)TxFm=;yUYGu;OD2<+R-- zSmCnme*>8y*KAGCOt+L@cL1o26^<_ z-{c?=N&yaH+xL3;qhDZRHJycp?d`)TLg1hp17YNzPz`6H?WKlfAXL_y@GS~TNN_rp zyJHB9)QgO!H@NeQ@QXdrfN72Ve_9KG0p5{$nZc=88UD{!ScU~I{de~NU?Bhyy*Gaf z^CRQc%GgLyx<0pC&35jY{R1N`jHi{+2=Bpyco8>?F&%*w=T+jSF_;Lr*%KW2HPS0x zy>(*tJcfQOB37uJ=}Z{K5BagTY3UvI|+fu?s&Ol=3GnC{g7sX_MHmzV-Do; zVkEYmhNA((^t&=7ndz}VxcslN$Km2vmHRT5I_1YRYdP6+VVDkjfBzr+&d|Jq4Xif^ z9*hko+gs1jvi$W!))n2g4v>RCRFp|0R&T%+=oAMc_Awd}&=p&=kd$YQOX2 zfnK5y?*x3jWvXhsIy{^zm*UIx+<79Tcis|#g?W-#70PUU7M&~5PHv&6cX50T7;~q% zfPuA%j>8JsPoCh-d|t|Df6|?5^ZLtaH+o@Gz8M>54%3+ov%)RH(e)qxQZBA7Mftu8wXpe_G{1$fx~IKBxmHs*I5xl;l3RXc<= zSK&W!0LEXR|GS)|9~%daTTSchA9|Nny~FDy9m zTxM-{T>j{eg2d{cC;41Nmyk**I@EuiMt`E0;$8AI}G#bkRWfysZFT z;%nTnUfp*KoecJ5Tz-FGm8jo5< zZQ}Z*TfSzuJ|FKpL=TF^SPmj5gE_UHOA$PA(D0Zp3uWedBNv~@=<6Xvm_XYJtP*aw zp8Lika5vKn`^)0r`>ay8o_l(PMtlbP=D&*Yos7OlkfnbZJkmDceQb!8*LZU7Uuy~= zNo$iLXwa7{N>KK1;||`*a~{S57=f0Uf8#1 zt%d!j)BbK;&-0OXB#+-MqQn2ywlA-3+RUwBpLM2Q%hh!hb|xn>}?Ut1!4N`wZ##898&3XDu+!|7DLj|>Wmi0BL_w){H8&9Xe*7`;BV*epZH?=(GO z69eN7P9@+~=X{Kwr<~$$9;;8U&%S=ofCz_x(Ay(}$;T}ML-xY@=^$JfQ{5s2dQ*Hg zKDiGI*lsy}d}+l~Z;TK?(!Dc*BvZg2MD$M}5s)V|#Qr6k?KV^A#Uaih;HsH^?&VuZ z=|K|o@-t8>VD@pr7&e=c2w2YeY-l^$hu>T@bP*e$!OcF{Do3pvvMle5#kFu5WPIT-QX{CUpmTmFnBz?->eY^KnHJb;IfBYkEH1lt3<|J&d(w@H?Ctmz~ zrvEs#2>=rRsm-ZEjnD3;(q?D^Mt4|Rm`Q+2p`x~-79gk!1-ap%o($rWQOK9D<@|96 zclu&V-qPcYk?lZur(2kPGaGV6E1Q%SaMTSgB%hJjDFZfx#;G_+CMbvQ5`$)kltQ(O z>Lm3uNOLw^Fqt#RP_*`(!G?oP?UUJR8TzM7Z!{W#hA+%NMZ?YDUWEV_3%TcDT)E8S`9>_qt|#$SI{m z=^)hFh5Ch^@6#XF#+{Po$#x;`#b;~k%^~tU4!X*j?RE+>eZ!atkv~z)FMG5q^YXh& z7)UNW)f^#>&Ig<4kW5dvoaxo?Kx06WPJkV`HTu@yBfUDh8Sb$5sTHy1zKJJrw**v;2MY~NSswJ=rNT?*!#6w`y~>d-Vj#bpv^6}jx+ zrEI(O9~OV&k`J{-QY-sya&B8wha6f3LMo4g3+_Re$olXukuJYFvTJaj>qqa%Atlv z!&SAu+`QwAauv$u>NU!1lkAowOh;B_cva&95T-n-!O;yI{?yi#6>(ECY_4_K)~GHxj7X`xZl?a{v3`mM+Q_Hxg%d?eHPEsO z+re4RjYCR*vS2LGQc1R$x&?`N;U8Xb0Igb`#oq?L!=V)?rZ*XRYCZJLRlpPv`|rho zqSAaGP4l9&RsAJ0n=K#`8`x({)6!cS2WXnzveI(z5-!4gL6<&l_35U(mRMm(ROf{v=X357SVUJNVwk(mj(u!GDTh}bLUB-Ij zQM48VW(dH3M1D{cDa=T@8ySjaZI7b!{5SSqzjTH~QU?xvpGGD((9=^^4Mygfej^=^ zAWt+&pn^%6W^2)T6E59g_0RhGq$Gv3OtXrTvbxwa{n`cih2^I%)_8qv++mZ+YL?jz z%tM^NmuMavd+1RFmD1TYC20_Ub!8U=Mbn6NbO}cTW(~X?&~V*Od`fzbG%2!{o6yqk zInQMtQ;x<}nP&UEVteji2KrKm-s=Q`G49lmL|TfrYx2Bl!#(ld(1j)Z6!&yTDM@RN zH09w%EXnd#FPhy8|G2cOVE$CwddJc1=P0jWW{i2yOp%v)<+Y^`Jj|(Kuw$(1))X?& zSC!MB2{CwVskstA>}U$)DYr&Q8#VmoD&EPYrWBnmk4jV}OADqE@&3-^QD9iroepj> zmT#URLbGC2MiU8^qwKe<*Pc*orE{AR+n?J;+m`Beu~HV1i)$g_l(5069g3X`tg)-e z7z16g%`#}y)~9aQLZ!V7>563r#U}34mVbGA^k!qpXKL5f%+H%HXlbyy-jhb#1(vjS zZZQ=J$y;l0NP1G{DU&;;aacGC96h^tlhy^-NOU7RaZ{<~>rg0@mKW(}-ylP(woDjR zu{E1^*^J^^G?C_hhR9K9$aD%o=kgScCZ5giD|t=@3+&10tT zNSH2Uy(YO_>1aivl~JT*MawFl=W9-JeeyzDh=&{1dH-!|Ip3RMl#QApX>pBqJ1Y@O z;$I6^wx)BIv}02tY=IhI@E1pk-dD_hN>r;Aamep0>cY@sbQOr7*-n5g-d73q7GNAj zsRZ488b(Ttt7D<&Tt&yDG_)1yEdv$X7x|ThV{SLzGp?N8zsZUgnp+*uSdwOwC~n;^ z_l^-N;NtZ%2CecMs!Dk*ScdN1ed%rhiOCP$%qU?ZFZ0}U#f)p{>rSyN#ThVkSz{4` zZ|Tsp+AbYJshsA1*PF8K;wW}6kEKi<{LET7*u{eJQhsiK@M^qJFHUyL_-8JB2}?d-~4P&z#+GtUCP282n;9pTEV>_3Rs2( z{{a69`v#JgS@t#{Zm6}^3#tr`U!`>7{e*!hCENNSQ9k{P`6JA!m4JZ2`uaybQG{1X z7$3ZezPy0sx4+xU6m~)hkKczY`t`0l*viUeHa9;9M+Ftmc_P5H1Nk*$kzU3v42(AZ zzR#lp=pgEJe)S}Djv(!9I#hv`-rjw3{LDtEzp~zD4xsTgi2Ot0*NPoI?&EuT82CwO z^bTH+4f3C7_IoAHxNKO#0lS}UT>$!rdjb1T)dh4F#I&{F;$!wnLA(NY*8Q~7(Cw6#~W z!Kvi#m`BakzCvG#PuwZ=b{^N63HJCd)@uv?{;C%Gn!TMF#C;Opc%#0N9$t3{TftOX zQZT%J!ZkPLGP<(=!GdgA9gv3sPzpFWKp)lGta6dGoTmLI?oMd8=g5#b0|ai+%fAB& z8IzlrBg^5OYfWdkAJX^tXfgf%?4b5UpZNh5|08rAM#$z~`xDLXk=NZg=f~DKbM?b)s;76{9Xogm~$Nkgih`kyVdd`dM6Jum#yFs zc~9p5qq?_@ilh0vMM*-85F2@a=up8q-Peb4!H?p^Dyb^8O%>ZYrU>YA$G{_S16*w&(Y5R!Cwa}SDc{O9c) z4_(8FbixyFRYK`nnO)XL+2vu{+=Tpp%Hny1IRjA2Z92NOFP;zv)#L zfwd=ivT#-)>rQYHUomT$@`#9$hFCEVWHX(PPlNoSENXQWouCzPo8%E-p#N_!pz{pO z__#;%qVcY(>dOPN%+KvIDLVTd70_J*Guc2QW{@UqasWtc*uMVyzE<+d2W7cf@ z_j+c(EsqVmRcgB)7(j(mJ>HQiKfwbSmOx;DZjy;L)$#Z0{q#BVE|Ne!p3wL|K;v8n zlbgCnh%5hno*@bn(Bh{NAF4a5Ag0H$^Dj9R`XJ}ufMxwlhp4J`+?3nqUnjO|o_Hrx z_CR&*`^j_DLq(POj(|kgN|3bAPpE* zVWX~aqMw)}Pbpxz6XCHC@t*(gV}a3xWxA*=P2!~ZdINN+!~8yys+xX6#64BSG@-e| ze&Ruj`NH`I@u7|+?@mExucnX9UoA~y_QBHD1Esjw?&PY6gnJDo9?;!Nqv&Pu&bgXq z4ANYzVm&adri$`$gJw+eG84}VI9XH>?zG-sfIqYEpp~%9=wtY5;>fX(Q33D(oVOie zbgO9_9=qH?Iqk*S_XixDswa-o96JryMOPspS0)}wW&6M9i`2JWMeW6tI01}X9FQ1Q zA08DM9(E?DbnZbMeW_E#3-MWt`W;YxmHDU-Tq<9&Be|uwV{_nlok(-izlPf%?=xW5 z6w8+T^{e~3EzM6NKzu#>KittR{D(5815j0vv|-d}rE$#s35hH}`^;`-{M(my;F{`z zKP!Z(+&CFa_0>~72IPxk$=(wg-qdn>J$YQEckS=B(o+7>!~T9q-L!ehOe!NBvkejs7Su4nO=wn0t7_ z&gorC=yQ5K`$vO=Chkt5l(g*T4`qGay>8|+IxK5%G9UQ5ejl==1EG<_nUM6d!mg!S zyFWYL?ACcze`h7VK5PpG=Z!L14-tygfASa^(UVz#>e)UZ<9w%gUL##gr{4NOu;Brt zL>{=-1ZYU{7<_jS5>ZFyCLJKpwpFM#fL zG5Q8#A>lRz&-7*-{z{@HJAh38wlx@Enx5ro6(x<$h7Jcf&e4H{$ zjoTRYW?mKPI6m#Nd2^%6$gb zwv{C;pU9JcidJ;7$lIrMF)Q>XlAd@e!@|)z_I4$#Y1C?gmS1)sYLoF|(zB=NAi_x| znhLU1aFU*|UI~*|y$qHVE2oENg+#_)^+7t}`svv{HYCFfB;|uRT|3!b(qZ&hg-0%K zZgG)Z0;)(O9Ych<-DKKrg&%(&$x(HDQw`#(l}Y=*>BCKMA-qymb#g)0T`- z82K`h%^gyvdoACr!50eA{|goMn?^A>Cy)9`l~Pq{GGKAq8^rd?szhM$wzdO*LwdcJ zWp3}%-V4)L_uC&L;pORd89vm9RHKQMepAljxmX8q{Y{MaHeD8~Ik8O3eA;N2G!$oZ zWJ^CmpjfB?3t<@9*=~8UHwj{1QO!B~;ktWKzZcz)c$@qCuY35*`CE%Oz2SZ9nHjL^ z>@hS4jgNlKF~tqMkHwF#340|bxi}cSgtS;^>MCS+2xm3q7cTBK!rp#ItsHDr^vAF)U}KY5 z-@O=IrysR+8rZ8Six_7nSA_5-5`{t3v}7jJa~S77O^$`T5C284wqq;kSzXvhE2Pur zUS%VqFEpLI_e4vDT&-86cOAcBe%E6ZK<}3>D6Jp3Alx||S&84$gdjoq&EJ=y4h61vx&LAIzM*CEnFU?K;8rS{Mh zB`(%gea*%a$_*0Bx&UZI^tX1A3lD)-Ghk24-@rk=c`{|-X!>v;%_dt?mqw7sJ~eY*;bKR<7UasUE_-Z#^$$2o z-v7{~+&T@14J3;ajkhXKt999^HuY2$Z!oXpgas+HTwJPl!*li@D13R`q0qQ8R25VeWRl^BDsDRt~h=fo8l8ys`0ner}pGXBN-;d%H$X zg$C_*!;8fmz}4v;632&vcIb(yBX<;Ml?TkO;Zigu+d`6pm{J)JZ;tipZ>0<=Jcinx zkdT#?WNz>j5Ghxf9LlEg>W`T_(jEzJsulQeF43E;t}QSyqGZYK``QhPybm!K@0tYJ z`D|)xUFO1*0IYXow`ZD83Pq<5CARx} z!wG_dA-;UXmYuz5;i?l3sa74ZVk6JrLE5h$VvJ+h1h?VB0adT*#1esMsSt2C#K#A6 z=yUE;C3QhAy80V}9!=Rg$}ffScSq?oytX2kl=`+MPrbj$y6`quoMMs~!4gNSjQZ;L zK1xd6CTB=_BJv7g@7%MDjrpiK6ojmA;RTHOOfaX{dk)JZXL_=k4rBFYU}rdef&GuJ zZS?Lsv&5SL;dSdJ10mz;0hgAmEebd1R00D7TUuZLjskoEn1N05Zrr#A3Og>UF0d^z z|8GEEUgM*2Y%_lo@IA$YG)k>{5FFhD#;DMse}qPJ@_}z#6T&O|PE>_Sp2-6kV6q@-fy5EeIs|%M6)-4$YPIug>Vav+cDClE=vjZCTFP8$C8J^)oKI7lG0sH_&K1ttyF-!URX-_5H zZyF;2&c!J)0muapPjYz7B`+FiYy8&hnfUs+ckf>U^EmI96^F^vtu5tc`gw7nLVGPuCgmM2IX_ z#7@a1Dm~Fk)BL5@VVHIn8yg8ZzmM12lp+j}rb8u)mMUqoHTt4pIyySG&V3^lglX`A zLUu0veRTDOMBd$Nj9^7ltj66+l1L#zAFvN=|Ln6D)YTc!19!5VZm6kA&EQ-|-7iup z!{CsFChy@B3JiDJoG1<33^$9$)%4k|O3MyIn($t~#k)hEIp>r6KK!vWt%zIcarH$) zzlgHWwwqU%9}0KgrWN}8%dpZDpaXmyR84RAZlnF_xd1bVGj6ozGgO4V>q8mLd#Sq+ zhoTTpql}`l=IEb8&@XLIsg{=Ht6sWn+cLujha&_`)q9&iN4nvJLpOAW@_UT=N68Db z`Rr^pBH*shYx}H~BCZpjs-xMUWjjwU7$f+5k^GxddFT9Er zEKuPO5~Cj5qH1R{)0h8NoC+YF1h%b(UK}}?24Sein6;|R)O$ZwQ9jZjfs6C$XXoV}qV))t>SH!~9^hN#=QFxb z`{x2z?4Ro2sdXY{IYJEZSZrcp#=e#Y3ct$zp?w#wo^{s&6<_!C*157 zm~ST^jx5e+ulDC=APuG4bSVj^oW*`;12qt>%z%Xs$!f&m*u>S5IM4cwHAkxPN|vqJ z=;Cbs*@ixC=!|8a&v3wQOZ@D`aEr9v600tS%5%SqR>30(+i~-(pQ!KBv-(~tJ@^ag z-}>3*?bEsQDcn`++5Sl|MEDKt7{_T?ZGeIf-dLSM#qXbYv3oTV1zfORMzfBMVBp9L zUcqsmAvc4_X$~OkbN>dpkpVC=1_r*HB_Wcy(DcV-Xmecc%G93k#QzGBCwUsJ_5Zke-K@Q;GDQ{!rDu_5I8=Do_P=c)zbi8%|Ha927+AB=rXu@Mw%yXk<#B@1uTg@qT0z+;*hDchWv9y&h z(BqF2b{iF){B@5Z)%kIinOxLN^GN*9m6dtP$|w!V9MyE6;fZIxgljT7#MDI3DWB4- zC}+1w2TEHBuMRJy^bGd@@YnXav|pbH_oApQ`=7Z0slh>s(!&uS8dT0va#_b@(l6mq zk)p-ozeRJf?~73(+f#z!;_gfJ7k_EbeGf~EOcQFA@AmZ6J8fV`eBAo;EoFFzx6|8Z z#HYWzHy%04a}3R6M*y!%cxU8&YOzH1a^2s65wz3>pjOTVhP`%p@wOU-_C5i_R*{Ej zPG;>X|6}8rFk1OoSPz+dW6Uim zZ-+6R)izTvi=2A5nrQ56*?az0uX6RfoF&V6v|3twhDFe32#p1FZQ%ZH%@_`Ci7mg# z9+@0QyXijl8;k3?G~ZVdQy02oNq0|o=`P!J3l7-}5329`8^+YXyeGSia>;0|3#{2* zJ&C@{e|g$3|K+7iR<1kB2fX0r1?7R4A*%LUyn+|M{Ng`?$4OqvZmsv%U(puV|Lr&m z?n@!qeE4vy&IvOw#CXJI!_cb~u+dRmcj>3%(Uy1>WBKY{!1UGNtmIj*srl7@mXmV! zxMhIu`H-OK)$+r$wk_*sGK|j_!2^`*MuAh`OYm4SY^GT;RwjmvjXuCCkcjJ}Bao3> z9A2;9U{D%Qf1wSQ@AoLSnEo4%xIF6Wd$hgHwz|H1NhDX7$#HZofY{+=1#+QTRaHq- z4p@Qc^M0A=GZ3xx1WVX$@M&N$^3nq3)Mt$31n=gr+ANukXrG=IRl||{tIkK=v~P|M z-l?fL(xMg`*i>HfXfr#V5bGww-2eU>MArxeNE+Sf9tyga3MA(BUj1pPN# z-qxx6oDM8!#L)R&Zowetm$J1#emc3W-DRz;3TY6TA|slbE$u7fg-+UP73ZIueCKHs zKR+^wBWRI$k({q@r;B1W z^p=Nju8^Ohu}GdF#D+NNVGLo13t=l@h{GQtXrl%yRA#SgvMzf7JxTbB)>VBXBbR4% zr^Z2`>#3l)RMAMc{gGIw)zGlUDEoR(f%EF#A|J_nRpR%<1QMT)iXH{SXWrVX6UFb* zfhsVQ#)Hgi1eZaaaoM^gukQywdrEGkbN$v`QD-fWvZkfDqvh@d%s4k=c!g#S8+_+` z!-wKK$=zfI6cGF09$f3bjqY{H%GgR5igWPGzE0@|MJj1|wSKCx|7v|szEw7EJ!7cB zoa<0zJEg5D=G0*^IAcV7JQe1r`L#>Bu`T4k(iuOQc4mrG7RvJ!OV!XbfBw*!6)I_# zES1zb~ zzgYbtossR~y1qT8rT*?3Qow={s%l_T14~9m_MWl6r{e~9RHC=5^0XYLb&X!YG&WI< z3A=|xaXgM=QtH#~aLTB^VrKb2-B&<=w~#g72rIdi@40%t0x@_^PJzjJy!V>S$Y`EE z1-85kJH87<01muiBNQG#W-Kgimu>olZ2G=+1urDUll)}SD&UPe>+z*pPYBSt)ELqY^qu=iE|H{t$uuovK_hw0i}a<|*p8RU~4Haq{Fh+*eV)Spg*|f>}Hzkkvl7l0;=HVKt_77IeOXTJPgyUOR z==_M-Yn=i?uNd}LXuKz~XaG(g_3{xyXwXF-2=M_;%)df?C;bQ6fj>oCKet|p+=8H{ zSTo!Y6{?Tge*8l;G~5B^AH1g7uDl_)1s)(c=e!FD@qkl+JHng)J0<_euE402KDi(u zt-EuratC@+?Ds|2*$d@!e1^G|yQG&JC&4-Q@tWv>WhehU0q@4k`0Ec%bVQ(6nzOnG zKYXreBN3}-OZ1X{j)czTbtlWxj0)#RYRj&|ua>8%R&B&T*k;7>aOTD-r9H(-oVagN z(Yk(Vw_awHSF+yt)>>5i+_K0;bu@QcrKBE0F z)CyGI?I?~Cs_m$Y_r9a#Gm^J8_wbsqz?%T7%4Pi}s6XVf_@dJ+BI0uH zXV3JsSNlQxwvy#tB1f~jqpKha%!a!MPBz`yW2j4n#Ddoi-32m+rsIHIyi!Bvtig`v>yd zhFF^D=&j50-MLj1CzcIz>hYj~+q9f~(&z8=E%~?0?3W!RDb@-s+w@J-eWu`zx;DwP zTE^-UH~^2XNdGH#CRZiC?o)UXy8q|b066{s;gXUn$*}i-CJic(V%q&gSF+3xPjI#Q z0_N5!-1X;`D$iQfj4@7Zts% zX=;ak?`A{D#9^+!IQUtA*ufaW%z;|@Sf@41qOb}f$mz=~eJ-w1@`ZN$E%R59;e)!c zoX{)EI(BODAp;n9_bI&V8%?CkgOms+iJXO8kOgig)s&uL<^La1MA{+-Zj7WwyHL3(R0f@9Qc zoIVb{v?A5<3q(LmQy+|$&g6Hh%u}@NFrSFd?nyv@TI_@BtKLZOD!NlklsYcPrboK< z!Vqg9o^nilAownB_OTZBh)hFQdQd;HRdkQXut;cyJxUX|hCVImb%V{W3t0TDza_y8 ze`&c`bnD`Fd6ih9Nl}^ry9e?puRTBM;jLct5$j?vGS?ruOtP|&F;Bm(QQLZ|!k_Cz zO8SCzHSWEshv>lP<3c2gKFeA9r6uj6E%7 znd_kp)Tis{UI!pN;M7e03LC{ns(^5?%TUD>5Uq(_)v)BA%gCu}ng&caoa?a$uaCHG zNAd_e?$c~zBuZ6a9}LBkk~PasXN?_g9~;`U=kykfn&eF;Lp0C>S4AXUDxK?PMcxTP z3`#AS`lTG7J(#akxiTD=(6j9W&Vo_r_P!2N=^Hv5Z}2Ucp%S(3oSnD-t8cC=w%_L3 z;Ej=NO@Gfn(~-6=fTJrN(Gl#Mr{rx35-hm1SD1N-%N&~467e$q>|ySW$cdkEtL2n* z&aE;|tVwbsq*x~9X-Ql%ZQViXbuzKK97H`n;^3;A`go}+zEf8Grr#UmW*;!69`8Ke zjyl3c&{Ng#@7kq{Ev`;C6jHEwN%9e+1s_v#avgl9E6yr!+51E@66bMp8b&)@D~2qB}@EgRN$=(3yCy$ zx)%HeonFG+h4gE{I;6lt%A`WLO_oBXO`+1l1sSgRj+3QVvF;K-W{`uEqe~&o5|bJk zS@_CX7{ly0(;oPh@@o!y%I-A_Sj%~E&o+;qgW1xWh*bppqK_Vz;K-_^s<1~J!Qf}U zRDqUn7po|1px7c0+eaVCzV*Ijxj?Ia z+bg%_<&*hlNlBdvi(3`WFn^|91hEZ?b}vs=Qbr-PRitt0F_3RDPnPW*xODMB+?C!$ zZPu=JV%p6%PqB%};Rj(_tD5CT)|EoUsUsD#CpnM1%wT#6wI5b(;`H+dES5SM4`6IC z?Qd~EvM1ScEmYy5wUuJRbM=+&O^S)9sdrctzNscJhbpwXt%bXnp4P=?D`kX@|9RM} zr2rL@2~}NFKNPd)-L(BBl+B;#CCoe)D4p(~1$(NLC_q-ogDq01JyL4DY!~R~eWZ9% z6gHjP4_6gQ$jHolDG)w!Fu|8;PZQcZGM@Xc6ErrJEGeg7A0d+SoeP|^@{2zeU#?Xi z;?v@xB3{hdJ(Tx+$&a5wDoZ~#GtJe_KgT}(wLtq=kf23uPMg*0i$UM)?y_f03vN(( z{IV|Z%Nnvsj|UW<>Ol_H5(0$YU(h_~-OuY68yL-=xrBxBRG9ef)?`!m2Scd~4-Xlr z=xOZ;beNM~N{^qx7lmmj4vBbE0KDx!yy0;-C8;st6)vs=!>xZtj)q4(M zbM2H;*m{9b9<*iBFDuE?4bfU1!&+JwQO6Uakc0&CX-tjVD(0Ljl%?>Y{DTUwJEO+- z4!_)Oefgl(uZMnK_s4+i{-_{VuDkInRLe%_YoK-s^&T@@Pfvi8%?VpwtAoQc;rHH9 zvG;apWLYA+evOGfGW=)kup1U$L3v|jXHc7*ut9ovd!6m&gBPacqg@Rotx?4$s zCj5EcD76u-G7g`%a9zbQL3aZ`=3bd$*h+={MR-bofT){Wq1*2e`3_4>0UGY@$>h8& zQS2{ccMzeZB$(AQ!Ou3S3&~VFKK0TP;aYD~j=xcA*W>LkwML-AjeYqH5AQMQwO_z` zeWsL*Gln>$Zbq-F5WKq(X3h#?yVdGNk>H=DJa04et$u+Xb!=cml!Fe!cUbzBLHR=a zV2F@q8>3%ihc1=Py9Ri_C(2*iURFH}H#8_)g2XK{fWHn1=FP-fvg8fLCIMoGQ* zOyS6o#{u~lOE9Qa2KSVPukOPJz0Fw+>R!0*im30XH(J@|3?dK${qXj<9jei!xhS$*AV@;h@%T|#T zrSp*^)0g&L`mzUv72lqd#-0CV^2}I%#st?Y{3hwfrJFHU{N35%<*o3e68OVuuFeqT z*ND1_h!N+^XL8KgRzl~AH4pUYs1_9tpY49TP0qouWvkbYUwX6(y_g-rh0DWbbZGRg z;5rN$1q@PNDd+D<%<^z@SWREmHX&)nDvzCg<^%h7#I54z-XCnBL?!UOwh^xfq#XM7 zHoZZVC#KZOg{Vp?@0p|9dEr{xox$XKm&g4berYt}H!b>)zvqWk-t;Vw$cG)ICo4ekwSBMpt<`I`GS|#>3Olb7KlQ>bDS`vO%mBt=b za4cjlCjZ#rl!Z)Mob$;axgNBw@oKeag2C4^UA+xhxNzYTX-A8Y7JVj%pjRf@?b_ub zQ^X5Nq!XqfkV!Rg`Eg<{c5XP}#?KY23Sl}j^Ef1^&cLQ3G`7b$>FUFv#+3px+$rch zP>H?2D>8xiV_??ZK8rI%0(r1y?p_ZwC~M)=$N2CMIJW4kLDy(`e>!{+p2WYxewd)& z!xH)T=`*J*w=@}!nDxvdGm@oBw%IQ4ux4t3IP}Xd#I8m7oAO;2SU6A2)6hV~@)y$Q z&@?8H10rx)Y`qZPwd5rOefT4dOYZVxAcN~$2RG9#bRLBRCgTrRMon1Mu;=W(vfUtZ z`bLc`NJ1t&ZY=#=_olNfueQn&I2$3SL^pWm&!hu4Nf#t{Pquw8fO;9fE#R}Z-db!Z z+$Nwoida+QRaa{N)TdrBbhKvvE&#!lsP1iO#P7v{NHB<{Q|Tg{Rrd;l$&eI6ATFL4 zXa`p8Z;PW#k%e|)U(>ZSMw+_$n5W>{JLBepU4Ak6sG9LCbz4`^S4CvZesNb>UJU2V zO>bZQPV^w5bX9p4Rr@a_?tz3L)fl%2l+H`#y zK|T}JNqn?lIYdt&AQfvBJj^^+dSBd^9o-?7*ZA_wpgGnYZY9K_EOi=9p&czab2N?H zdG4qoF)M5<{XTrSHrH1$Hg=g08$!px%BPT(5P#YHk94CG1st;_GT$d#w2F&9RS z*@!PkE1$4IB;f0kl6Bs1g{Dp>?;%bhYeh1x?9ubQVcjm7VeUF>wpA%^6RTnrw`e_v z5(*M*6NQI|2o?6Opdc>}pH(Eo@-LSi!>W@SgP%Fa5v3WZ`+66C0p$j>(qy}Xi!q-E zz9&5vKHGBkii<*4)OhNvUx~+s^cai3qzGCcdB`{T`n?E<}jlt>o&pyKVFgj52YfVdx3lO1|>2oZnAS*{jU{%$^w@$p;T_1e$~t z`kx_@S_R{46f=c`P{LLO7F(pG8qG|E>Yu!gSjJdyhkO9R4@VXZpsT-z)0nXN8C4A; zK|aiikJ5%k12rZk@%HsX70bP5UZ<*87ciJM=8`rGzeSv)?#$e?H@RAII;qcM;%NRJ z`TA{d4G9u-q@ONWBzz(^4T!iF(dp+B4`9)6LVDB(;dk|TN0m<;%2rU?>fXu@qZOj~ z4^#SV3JR*YcSZb-y!wo_w$W2e!e>2E-T2!f2B`)QTUrk^i0mwCdxmcfLcu-l<@QmS zK zcFWa;cm$1tUdi-E;V1nk>I=@lG4wP?=y` z%Dv{9w--RNAyB!h#xD%ViBJ_P?-)EbXjGJF#dXR!3yUL?fsnW%aFD!w5UNMDii7Ir zw#EF2R%9Q~<(eShRKNA?gPn=8Zj7=G&mhEZ2kmkVotu|oL3)lNG+n|$}v{!#1tnox*d#Idp1QP;ld!A zLVU*L^+IaQm*aw1=nPh4h40p_7W&!}O!A>dzW+sEzJ^QqXDy!5v9Wl)pw$sKUb}q$ ziq)ka-l?v%+sZk_-CD#VVwPAaTMs*EInFhVimJ`hVJeI70fCAwbuNY>jZRg-OVcH_ zASt|JG>F~2Wn6^cAWgWnyN>PQbo=<9^#}>pO=Xv_hAd3VLbttIT#mp|{(dM;&}YCnIcR;=4*wDyB{d|zb^ad5JM%10Xw zOB(|cPK20n*a{))FTauME(C<2X?16;#fhC+N!!s=>+09hmV}DRhH_#GbfcNX0ePI4 zH}R_Bz*Y+%yE@bdc3?&#*tKFNjDEodqXYXbH+BSy&+)yv3l$vn?OAqu@EDd@5F8-4 zW{3F}r#(F-w?K5D($*T)roGVY1R+HqG^#|Oo0pp4nZx_g!KDSh`;=c>)zquK>z8E=BvfbA2HA-8 z623FEFWC3yOkc^Bj*%7>R|>sztz&@cRC;Ykt>gF-i&*okM6xySD=;g7P;1O&aliy{ zI6c$oe|Sn6E|$Teu#Do0F|JY47tql8A(Zh_fq6tD&CxF2s06etl5s94=%BA{u)X@# z?JAK{n3{!8L7@hpS10{3(+&Ryl{F($LMaE6oAjK!zoXY*3F@WXzQ zzmB%927Xf>D)c@VK^1$1qQtu*()qAWrzpSS=M~(a&v?`V?pD(Gt5|rOq$9$ZF})0q zW8l#{`vWIYE)OUgbR`5LzCYCtclq6Gf#0gM!Z)^2Rbz!d*Sy_EPo=0RpuJVdqNG(* zfQGI)*zHFMr)1`vKhGhgddi-lzkd6q?8_Ir&s8)5Zh6v=K!dU7>p??u!LOQ)LdYU( zDe<9YGPIG;w?gCtZ4J^afQCXlYX-jLkB230D-`6?5$sPD1bHiWc%^u zht3X%lY7e7e{S@r?uHJqo&oTY#1GqY(7yMNHl(Q<|MuYvyjD6O)B7oVaiQ&J=Q}_- zf`?}+J?uK0!1@!f)$IDxLU+PUI2osmtn5GA2atbWSy^$h?0@o60sjX1&36cji91m{{On)i!RC)IxQHzp$Zrzd<0&i*-`H{8P1z3F~bGc7J2 zTPF&n^zlA6J$wHAIVs>YLP<$Pi9t^=BOo`q6kYDqmgwR0@CsAMRWu3(^SOf TS*GZ%QQ-dojd)~2 literal 0 HcmV?d00001 diff --git a/docs/assets/social-preview.svg b/docs/assets/social-preview.svg new file mode 100644 index 0000000..e76538c --- /dev/null +++ b/docs/assets/social-preview.svg @@ -0,0 +1 @@ +xprobeWhy is my CPU, Python, or CUDA process slow? Two commands.xprobe: triage a slow process, then measure the exact latencyA terminal runs xprobe triage on a PyTorch CUDA process. Ranked findings show a CPU hotspot, a dominant GPU kernel, and power-capped GPU clocks. A second command measures request to launch 1.14 ms, launch to kernel 99 microseconds, and kernel duration 1.2 microseconds on CPU and GPU timelines.Why is PID 4242 slow?$xprobe measure --pid 4242 --triage --duration-ms 20001CPU_HOTSPOTlibgomp worker spinning: 77% of sampled stacks76%2GPU_KERNEL_DOMINANTampere_sgemm_128x64_nn: all kernel time, GPU busy 54%54%3GPU_CLOCK_LIMITEDsw_power_cap under loadn/a→ each finding ships a ready next spec$xprobe measure --spec next.jsonCPUGPUrequestcudaLaunchKernel returnskernelrequest → launch 1.14 ms (first-after)launch → kernel 99 µs (exact)kernel 1.2 µs (exact)Two commands. Bounded capture. No daemon. Evidence an agent can read. diff --git a/docs/assets/xprobe-flow-dark.svg b/docs/assets/xprobe-flow-dark.svg new file mode 100644 index 0000000..f7b5d22 --- /dev/null +++ b/docs/assets/xprobe-flow-dark.svg @@ -0,0 +1,34 @@ +xprobe: triage a slow process, then measure the exact latencyA terminal runs xprobe triage on a PyTorch CUDA process. Ranked findings show a CPU hotspot, a dominant GPU kernel, and power-capped GPU clocks. A second command measures request to launch 1.14 ms, launch to kernel 99 microseconds, and kernel duration 1.2 microseconds on CPU and GPU timelines.Why is PID 4242 slow?$xprobe measure --pid 4242 --triage --duration-ms 20001CPU_HOTSPOTlibgomp worker spinning: 77% of sampled stacks76%2GPU_KERNEL_DOMINANTampere_sgemm_128x64_nn: all kernel time, GPU busy 54%54%3GPU_CLOCK_LIMITEDsw_power_cap under loadn/a→ each finding ships a ready next spec$xprobe measure --spec next.jsonCPUGPUrequestcudaLaunchKernel returnskernelrequest → launch 1.14 ms (first-after)launch → kernel 99 µs (exact)kernel 1.2 µs (exact)Two commands. Bounded capture. No daemon. Evidence an agent can read. diff --git a/docs/assets/xprobe-flow-light.svg b/docs/assets/xprobe-flow-light.svg new file mode 100644 index 0000000..ea0aabc --- /dev/null +++ b/docs/assets/xprobe-flow-light.svg @@ -0,0 +1,34 @@ +xprobe: triage a slow process, then measure the exact latencyA terminal runs xprobe triage on a PyTorch CUDA process. Ranked findings show a CPU hotspot, a dominant GPU kernel, and power-capped GPU clocks. A second command measures request to launch 1.14 ms, launch to kernel 99 microseconds, and kernel duration 1.2 microseconds on CPU and GPU timelines.Why is PID 4242 slow?$xprobe measure --pid 4242 --triage --duration-ms 20001CPU_HOTSPOTlibgomp worker spinning: 77% of sampled stacks76%2GPU_KERNEL_DOMINANTampere_sgemm_128x64_nn: all kernel time, GPU busy 54%54%3GPU_CLOCK_LIMITEDsw_power_cap under loadn/a→ each finding ships a ready next spec$xprobe measure --spec next.jsonCPUGPUrequestcudaLaunchKernel returnskernelrequest → launch 1.14 ms (first-after)launch → kernel 99 µs (exact)kernel 1.2 µs (exact)Two commands. Bounded capture. No daemon. Evidence an agent can read. diff --git a/scripts/render-readme-art.py b/scripts/render-readme-art.py new file mode 100755 index 0000000..a93b7a7 --- /dev/null +++ b/scripts/render-readme-art.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +"""Render the README hero animation as light and dark SVGs. + +The numbers come from the v0.6.0 live release gate on an RTX 3060: the PyTorch +CUDA triage fixture and the multisource request/launch/kernel graph. Rerun +after changing the story: + + scripts/render-readme-art.py docs/assets +""" + +import pathlib +import sys +from html import escape + +WIDTH, HEIGHT = 960, 540 +CYCLE_S = 14.0 +FONT = "ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace" +SANS = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif" + +THEMES = { + "light": { + "bg": "#ffffff", + "panel": "#f6f8fa", + "border": "#d0d7de", + "text": "#1f2328", + "muted": "#59636e", + "prompt": "#0969da", + "cpu": "#8250df", + "gpu": "#1a7f37", + "env": "#9a6700", + "track": "#eaeef2", + }, + "dark": { + "bg": "#0d1117", + "panel": "#161b22", + "border": "#30363d", + "text": "#e6edf3", + "muted": "#8d96a0", + "prompt": "#4493f8", + "cpu": "#ab7df8", + "gpu": "#3fb950", + "env": "#d29922", + "track": "#21262d", + }, +} + +# (start, end) as fractions of the cycle for each stage. +STAGES = { + "cmd1": (0.02, 0.10), + "find1": (0.12, 0.16), + "find2": (0.16, 0.20), + "find3": (0.20, 0.24), + "next": (0.27, 0.31), + "cmd2": (0.34, 0.42), + "lanes": (0.44, 0.48), + "edge1": (0.50, 0.55), + "edge2": (0.56, 0.61), + "edge3": (0.62, 0.67), + "tagline": (0.72, 0.77), +} +HOLD_UNTIL = 0.93 + + +def keyframes(name: str, start: float, end: float, kind: str) -> str: + """Hidden until start, shown from end to the loop's end. + + The whole scene fades out and back in around the loop boundary, so + individual elements never need their own exit frames. + """ + s, e = (round(v * 100, 2) for v in (start, end)) + if kind == "type": + hidden, shown = "transform: scaleX(1)", "transform: scaleX(0)" + elif kind == "grow": + hidden, shown = "transform: scaleX(0)", "transform: scaleX(1)" + else: + hidden = "opacity: 0; transform: translateY(6px)" + shown = "opacity: 1; transform: translateY(0)" + return f"@keyframes {name} {{ 0%, {s}% {{ {hidden} }} {e}%, 100% {{ {shown} }} }}" + + +def scene_keyframes() -> str: + h = round(HOLD_UNTIL * 100, 2) + return ( + f"@keyframes scene {{ 0% {{ opacity: 0 }} 1.5%, {h}% {{ opacity: 1 }} " + f"99%, 100% {{ opacity: 0 }} }}" + ) + + +def style(theme: dict) -> str: + rules = [ + f".mono {{ font-family: {FONT}; }}", + f".sans {{ font-family: {SANS}; }}", + ".anim { transform-box: fill-box; }", + scene_keyframes(), + f".scene {{ animation: scene {CYCLE_S}s linear infinite; }}", + ] + for name, (start, end) in STAGES.items(): + kind = "type" if name.startswith("cmd") else "fade" + rules.append(keyframes(f"k-{name}", start, end, kind)) + rules.append( + f".{name} {{ animation: k-{name} {CYCLE_S}s ease-out infinite; " + f"{'transform-origin: right center;' if kind == 'type' else ''} }}" + ) + for name in ("find1", "find2", "find3"): + start, end = STAGES[name] + rules.append(keyframes(f"g-{name}", end, end + 0.06, "grow")) + rules.append( + f".bar-{name} {{ animation: g-{name} {CYCLE_S}s ease-out infinite; " + "transform-origin: left center; }" + ) + rules.append( + "@media (prefers-reduced-motion: reduce) { * { animation: none !important; } " + ".cover { display: none; } }" + ) + return "\n".join(rules) + + +def text(x, y, content, size, fill, cls="mono", weight="400", anchor="start") -> str: + return ( + f'{escape(content)}' + ) + + +def command(y: int, line: str, stage: str, t: dict) -> str: + """A typed command: the text is revealed by a cover that shrinks away.""" + width = 22 + len(line) * 8.4 + return ( + f'{text(48, y, "$", 15, t["prompt"], weight="700")}' + f'{text(66, y, line, 15, t["text"])}' + f'' + ) + + +def finding(y: int, rank: int, code: str, detail: str, share: float | None, + color: str, stage: str, t: dict) -> str: + bar_x, bar_w = 600, 260 + parts = [ + f'', + text(48, y, f"{rank}", 14, t["muted"]), + text(70, y, code, 14, color, weight="700"), + text(70, y + 19, detail, 12.5, t["muted"]), + f'', + ] + if share is None: + parts.append(text(bar_x + bar_w + 14, y, "n/a", 13, t["muted"])) + else: + parts.append( + f'' + ) + parts.append(text(bar_x + bar_w + 14, y, f"{share:.0%}", 13, t["text"], weight="700")) + parts.append("") + return "".join(parts) + + +def timeline(t: dict) -> str: + lane_x0, lane_x1 = 150, 900 + cpu_y, gpu_y = 362, 440 + req, launch, kstart, kend = 190, 560, 690, 760 + parts = [ + '', + text(48, cpu_y + 5, "CPU", 13, t["cpu"], weight="700"), + text(48, gpu_y + 5, "GPU", 13, t["gpu"], weight="700"), + f'', + f'', + f'', + text(req, cpu_y - 14, "request", 12, t["muted"], anchor="middle"), + f'', + text(launch, cpu_y - 14, "cudaLaunchKernel returns", 12, t["muted"], anchor="middle"), + f'', + text((kstart + kend) / 2, gpu_y + 28, "kernel", 12, t["muted"], anchor="middle"), + "", + '', + f'', + text((req + launch) / 2, cpu_y + 22, "request → launch 1.14 ms (first-after)", 13, t["text"], anchor="middle"), + "", + '', + f'', + text(launch - 10, (cpu_y + gpu_y) / 2 + 8, "launch → kernel 99 µs (exact)", 13, t["text"], anchor="end"), + "", + '', + text(kend + 12, gpu_y + 5, "kernel 1.2 µs (exact)", 13, t["text"]), + "", + ] + return "".join(parts) + + +def render(theme_name: str, animated: bool = True) -> str: + t = THEMES[theme_name] + body = [ + f'', + f'', + '' + '', + text(WIDTH / 2, 47, "Why is PID 4242 slow?", 14, t["muted"], cls="sans", anchor="middle"), + '', + command(90, "xprobe measure --pid 4242 --triage --duration-ms 2000", "cmd1", t), + finding(130, 1, "CPU_HOTSPOT", "libgomp worker spinning: 77% of sampled stacks", 0.76, t["cpu"], "find1", t), + finding(174, 2, "GPU_KERNEL_DOMINANT", "ampere_sgemm_128x64_nn: all kernel time, GPU busy 54%", 0.54, t["gpu"], "find2", t), + finding(218, 3, "GPU_CLOCK_LIMITED", "sw_power_cap under load", None, t["env"], "find3", t), + f'{text(48, 262, "→ each finding ships a ready next spec", 13, t["prompt"])}', + command(304, "xprobe measure --spec next.json", "cmd2", t), + timeline(t), + f'' + f'{text(WIDTH / 2, 494, "Two commands. Bounded capture. No daemon. Evidence an agent can read.", 15, t["text"], cls="sans", weight="600", anchor="middle")}' + "", + "", + ] + return ( + f'' + "xprobe: triage a slow process, then measure the exact latency" + "A terminal runs xprobe triage on a PyTorch CUDA process. Ranked " + "findings show a CPU hotspot, a dominant GPU kernel, and power-capped GPU clocks. " + "A second command measures request to launch 1.14 ms, launch to kernel 99 " + "microseconds, and kernel duration 1.2 microseconds on CPU and GPU timelines." + + (f"" if animated else + f"") + + "".join(body) + + "\n" + ) + + +def social_card() -> str: + """A 1280x640 repository social preview built from the final frame.""" + t = THEMES["dark"] + flow = render("dark", animated=False).split(">", 1)[1].rsplit("", 1)[0] + flow = flow.replace('' + f'' + f'' + + text(64, 96, "xprobe", 56, t["text"], cls="sans", weight="700") + + text(64, 140, "Why is my CPU, Python, or CUDA process slow? Two commands.", 26, t["muted"], cls="sans") + + f'{flow}' + + "\n" + ) + + +def main() -> None: + if len(sys.argv) != 2: + raise SystemExit("usage: render-readme-art.py ") + output = pathlib.Path(sys.argv[1]) + output.mkdir(parents=True, exist_ok=True) + for theme in THEMES: + (output / f"xprobe-flow-{theme}.svg").write_text(render(theme)) + (output / "social-preview.svg").write_text(social_card()) + + +if __name__ == "__main__": + main() diff --git a/tests/agent-contract/test_contract.py b/tests/agent-contract/test_contract.py index 7c321dc..93ffcf2 100755 --- a/tests/agent-contract/test_contract.py +++ b/tests/agent-contract/test_contract.py @@ -193,6 +193,25 @@ def check_skill(workspace: pathlib.Path, binary: pathlib.Path) -> None: assert required in setup, required +def check_readme_art(workspace: pathlib.Path) -> None: + """README art is generated; committed SVGs must match the renderer.""" + with tempfile.TemporaryDirectory(prefix="xprobe-art-") as directory: + subprocess.run( + [sys.executable, "-I", workspace / "scripts/render-readme-art.py", directory], + check=True, + ) + rendered = sorted(pathlib.Path(directory).glob("*.svg")) + assert rendered + for path in rendered: + committed = workspace / "docs/assets" / path.name + assert committed.read_text() == path.read_text(), ( + f"{committed} is stale; run scripts/render-readme-art.py docs/assets" + ) + readme = (workspace / "README.md").read_text() + for asset in ("docs/assets/xprobe-flow-light.svg", "docs/assets/xprobe-flow-dark.svg"): + assert asset in readme, asset + + def check_schemas(workspace: pathlib.Path) -> None: schema_paths = sorted((workspace / "schemas").glob("*.schema.json")) assert schema_paths @@ -287,6 +306,7 @@ def main() -> None: check_skill(workspace, binary) check_schemas(workspace) check_installation_docs(workspace) + check_readme_art(workspace) documented = check_documented_flags( binary, [workspace / "README.md", *sorted((workspace / "docs").glob("*.md"))] ) From 3fa1420b4bd6363c339ed1f20f71b36213840d9c Mon Sep 17 00:00:00 2001 From: itdevwu Date: Sat, 14 Nov 2026 15:51:11 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=96=20chore:=20prepare=20version?= =?UTF-8?q?=200.6.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- README.md | 2 +- docs/agent-integration.md | 2 +- docs/installation.md | 12 ++++++------ install.sh | 4 ++-- skills/xprobe-measure-latency/references/setup.md | 6 +++--- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e36d596..d68f238 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) -project(xprobe VERSION 0.6.0 LANGUAGES C) +project(xprobe VERSION 0.6.1 LANGUAGES C) include(CTest) diff --git a/Cargo.lock b/Cargo.lock index e90bab7..09041fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -732,7 +732,7 @@ dependencies = [ [[package]] name = "xprobe-cli" -version = "0.6.0" +version = "0.6.1" dependencies = [ "clap", "serde", @@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "xprobe-collector" -version = "0.6.0" +version = "0.6.1" dependencies = [ "libbpf-rs", "perf-event-open", @@ -756,7 +756,7 @@ dependencies = [ [[package]] name = "xprobe-core" -version = "0.6.0" +version = "0.6.1" dependencies = [ "cpp_demangle", "nix", @@ -768,7 +768,7 @@ dependencies = [ [[package]] name = "xprobe-correlator" -version = "0.6.0" +version = "0.6.1" dependencies = [ "regex", "serde_json", @@ -777,7 +777,7 @@ dependencies = [ [[package]] name = "xprobe-exporter" -version = "0.6.0" +version = "0.6.1" dependencies = [ "serde_json", "xprobe-protocol", @@ -785,7 +785,7 @@ dependencies = [ [[package]] name = "xprobe-protocol" -version = "0.6.0" +version = "0.6.1" dependencies = [ "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 1357027..f9bdb3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.6.0" +version = "0.6.1" edition = "2024" license = "Apache-2.0" repository = "https://github.com/itdevwu/xprobe" diff --git a/README.md b/README.md index 13e041b..d9ebd8d 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ only installation action required from the user: ```bash npx skills@1 add \ - https://github.com/itdevwu/xprobe/tree/v0.6.0/skills/xprobe-measure-latency \ + https://github.com/itdevwu/xprobe/tree/v0.6.1/skills/xprobe-measure-latency \ --global ``` diff --git a/docs/agent-integration.md b/docs/agent-integration.md index 36cfa2e..feea27a 100644 --- a/docs/agent-integration.md +++ b/docs/agent-integration.md @@ -17,7 +17,7 @@ repairs the matching xprobe CLI itself: ```bash npx skills@1 add \ - https://github.com/itdevwu/xprobe/tree/v0.6.0/skills/xprobe-measure-latency \ + https://github.com/itdevwu/xprobe/tree/v0.6.1/skills/xprobe-measure-latency \ --global ``` diff --git a/docs/installation.md b/docs/installation.md index a98ea9c..f1ea2a1 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -13,7 +13,7 @@ user needs to run: ```bash npx skills@1 add \ - https://github.com/itdevwu/xprobe/tree/v0.6.0/skills/xprobe-measure-latency \ + https://github.com/itdevwu/xprobe/tree/v0.6.1/skills/xprobe-measure-latency \ --global ``` @@ -29,7 +29,7 @@ The versioned bootstrap installs to `~/.local` without root access: ```bash curl --proto '=https' --tlsv1.2 -fsSL \ - https://raw.githubusercontent.com/itdevwu/xprobe/v0.6.0/install.sh | sh + https://raw.githubusercontent.com/itdevwu/xprobe/v0.6.1/install.sh | sh ``` The bootstrap downloads the release archive and its SHA256 file, verifies the @@ -48,7 +48,7 @@ prefix, download the script and pass `--prefix`: ```bash curl --proto '=https' --tlsv1.2 -fsSLO \ - https://raw.githubusercontent.com/itdevwu/xprobe/v0.6.0/install.sh + https://raw.githubusercontent.com/itdevwu/xprobe/v0.6.1/install.sh sh install.sh --prefix /opt/xprobe ``` @@ -60,7 +60,7 @@ script with `sudo`. The installer never elevates privileges itself. For a fully explicit archive workflow: ```bash -version=0.6.0 +version=0.6.1 base=https://github.com/itdevwu/xprobe/releases/download/v$version archive=xprobe-$version-linux-x86_64.tar.gz @@ -106,7 +106,7 @@ missing or damaged, manually install the complete version-matched directory: ```bash npx skills@1 add \ - https://github.com/itdevwu/xprobe/tree/v0.6.0/skills/xprobe-measure-latency \ + https://github.com/itdevwu/xprobe/tree/v0.6.1/skills/xprobe-measure-latency \ --global ``` @@ -115,7 +115,7 @@ installation names the target explicitly: ```bash npx --yes skills@1 add \ - https://github.com/itdevwu/xprobe/tree/v0.6.0/skills/xprobe-measure-latency \ + https://github.com/itdevwu/xprobe/tree/v0.6.1/skills/xprobe-measure-latency \ --agent codex --global --copy --yes ``` diff --git a/install.sh b/install.sh index d9e8ecb..46eb4ec 100755 --- a/install.sh +++ b/install.sh @@ -2,7 +2,7 @@ set -eu repository=${XPROBE_REPOSITORY:-itdevwu/xprobe} -version=${XPROBE_VERSION:-0.6.0} +version=${XPROBE_VERSION:-0.6.1} if [ -n "${XPROBE_PREFIX:-}" ]; then prefix=$XPROBE_PREFIX elif [ -n "${HOME:-}" ]; then @@ -19,7 +19,7 @@ Install a released xprobe binary and its CUDA Agents. Usage: install.sh [--version VERSION] [--prefix DIR] [--uninstall] Options: - --version VERSION Release to install (default: 0.6.0) + --version VERSION Release to install (default: 0.6.1) --prefix DIR Installation prefix (default: $HOME/.local) --uninstall Remove xprobe from the selected prefix -h, --help Show this help diff --git a/skills/xprobe-measure-latency/references/setup.md b/skills/xprobe-measure-latency/references/setup.md index a2bf8cc..3ec6e54 100644 --- a/skills/xprobe-measure-latency/references/setup.md +++ b/skills/xprobe-measure-latency/references/setup.md @@ -11,7 +11,7 @@ absent, reports another version, or fails a required capability: ```bash command -v xprobe >/dev/null 2>&1 && xprobe --version curl --proto '=https' --tlsv1.2 -fsSL \ - https://raw.githubusercontent.com/itdevwu/xprobe/v0.6.0/install.sh \ + https://raw.githubusercontent.com/itdevwu/xprobe/v0.6.1/install.sh \ -o /tmp/xprobe-install.sh sh /tmp/xprobe-install.sh export PATH="$HOME/.local/bin:$PATH" @@ -45,7 +45,7 @@ The release archive needs host glibc 2.34 or newer. Otherwise build the matching source with the host glibc: ```bash -git clone --depth 1 --branch v0.6.0 https://github.com/itdevwu/xprobe.git +git clone --depth 1 --branch v0.6.1 https://github.com/itdevwu/xprobe.git cd xprobe mamba env create --file environment.yml mamba run -n xprobe-dev just build @@ -83,6 +83,6 @@ references, examples, and analyzer are part of the contract: ```bash npx skills@1 add \ - https://github.com/itdevwu/xprobe/tree/v0.6.0/skills/xprobe-measure-latency \ + https://github.com/itdevwu/xprobe/tree/v0.6.1/skills/xprobe-measure-latency \ --global ```