From 3192cbf3f4c6b834faac4d4f68fee9d93b95e5f4 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Fri, 16 Aug 2024 14:05:31 -0700 Subject: [PATCH] =?UTF-8?q?=E2=8F=B1=EF=B8=8F=20At=20a=20hostcall=20to=20g?= =?UTF-8?q?et=20the=20amount=20of=20vcpu=20time=20that=20has=20passed=20in?= =?UTF-8?q?=20the=20guest,=20in=20milliseconds=20(#412)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a proposed extension, to help customers diagnose where their code is spending time. One explicit thing to mention is that while this hostcall limits itself to vcpu time (and thus does not count any time spent blocking on IO, for example), it is based on a time passing rather than cycles, WASM instruction count, or any other timer that is independent of the underlying processor(s) running the code. Thus, the values found should only really be compared to other values captured in the exact same run. --------- Co-authored-by: Jake Champion Co-authored-by: Cameron Walters (cee-dub) --- Cargo.lock | 1 + Cargo.toml | 1 + cli/tests/integration/main.rs | 1 + cli/tests/integration/vcpu_time.rs | 28 ++++++++ cli/tests/trap-test/Cargo.lock | 1 + crates/adapter/src/fastly/core.rs | 18 +++++ lib/Cargo.toml | 1 + lib/compute-at-edge-abi/compute-at-edge.witx | 6 ++ lib/compute-at-edge-abi/typenames.witx | 1 + lib/data/viceroy-component-adapter.wasm | Bin 180080 -> 180590 bytes lib/src/component/compute_runtime.rs | 10 +++ lib/src/component/mod.rs | 2 + lib/src/execute.rs | 55 ++++++++++++++- lib/src/linking.rs | 1 + lib/src/session.rs | 5 ++ lib/src/wiggle_abi.rs | 1 + lib/src/wiggle_abi/compute_runtime.rs | 14 ++++ lib/wit/deps/fastly/compute.wit | 9 +++ test-fixtures/Cargo.lock | 55 ++++++++++++++- test-fixtures/Cargo.toml | 3 + test-fixtures/src/bin/vcpu_time_test.rs | 70 +++++++++++++++++++ 21 files changed, 278 insertions(+), 5 deletions(-) create mode 100644 cli/tests/integration/vcpu_time.rs create mode 100644 lib/src/component/compute_runtime.rs create mode 100644 lib/src/wiggle_abi/compute_runtime.rs create mode 100644 test-fixtures/src/bin/vcpu_time_test.rs diff --git a/Cargo.lock b/Cargo.lock index cc23722b..be81ac6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2422,6 +2422,7 @@ dependencies = [ "hyper", "itertools 0.10.5", "lazy_static", + "pin-project", "regex", "rustls", "rustls-native-certs", diff --git a/Cargo.toml b/Cargo.toml index 10705ddf..c2fde00e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ base64 = "0.21.2" clap = { version = "^4.0.18", features = ["derive"] } hyper = { version = "=0.14.26", features = ["full"] } itertools = "0.10.5" +pin-project = "1.0.8" rustls = { version = "0.21.5", features = ["dangerous_configuration"] } rustls-pemfile = "1.0.3" serde_json = "1.0.59" diff --git a/cli/tests/integration/main.rs b/cli/tests/integration/main.rs index 1896211b..9e303406 100644 --- a/cli/tests/integration/main.rs +++ b/cli/tests/integration/main.rs @@ -25,3 +25,4 @@ mod upstream; mod upstream_async; mod upstream_dynamic; mod upstream_streaming; +mod vcpu_time; diff --git a/cli/tests/integration/vcpu_time.rs b/cli/tests/integration/vcpu_time.rs new file mode 100644 index 00000000..1bf2aadf --- /dev/null +++ b/cli/tests/integration/vcpu_time.rs @@ -0,0 +1,28 @@ +use crate::{ + common::{Test, TestResult}, + viceroy_test, +}; +use hyper::{Request, Response, StatusCode}; + +viceroy_test!(vcpu_time_getter_works, |is_component| { + let req = Request::get("/") + .header("Accept", "text/html") + .body("Hello, world!") + .unwrap(); + + let resp = Test::using_fixture("vcpu_time_test.wasm") + .adapt_component(is_component) + .backend("slow-server", "/", None, |_| { + std::thread::sleep(std::time::Duration::from_millis(4000)); + Response::builder() + .status(StatusCode::OK) + .body(vec![]) + .unwrap() + }) + .await + .against(req) + .await?; + + assert_eq!(resp.status(), StatusCode::OK); + Ok(()) +}); diff --git a/cli/tests/trap-test/Cargo.lock b/cli/tests/trap-test/Cargo.lock index 5fe5a907..da8838c7 100644 --- a/cli/tests/trap-test/Cargo.lock +++ b/cli/tests/trap-test/Cargo.lock @@ -2346,6 +2346,7 @@ dependencies = [ "hyper", "itertools 0.10.5", "lazy_static", + "pin-project", "regex", "rustls", "rustls-native-certs", diff --git a/crates/adapter/src/fastly/core.rs b/crates/adapter/src/fastly/core.rs index 6f9fb735..0d8820ff 100644 --- a/crates/adapter/src/fastly/core.rs +++ b/crates/adapter/src/fastly/core.rs @@ -303,6 +303,24 @@ pub mod fastly_abi { } } +pub mod fastly_compute_runtime { + use super::*; + + #[export_name = "fastly_compute_runtime#get_vcpu_ms"] + pub fn get_vcpu_ms(vcpu_time_ms_out: *mut u64) -> FastlyStatus { + match crate::bindings::fastly::api::compute_runtime::get_vcpu_ms() { + Ok(time) => { + unsafe { + *vcpu_time_ms_out = time; + }; + FastlyStatus::OK + } + + Err(e) => e.into(), + } + } +} + pub mod fastly_uap { use super::*; use crate::bindings::fastly::api::uap; diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 83a8a2fd..3ad5be1b 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -40,6 +40,7 @@ http-body = "^0.4.5" hyper = { workspace = true } itertools = { workspace = true } lazy_static = "^1.4.0" +pin-project = { workspace = true } regex = "^1.3.9" rustls = "^0.21.1" rustls-native-certs = "^0.6.3" diff --git a/lib/compute-at-edge-abi/compute-at-edge.witx b/lib/compute-at-edge-abi/compute-at-edge.witx index 164b8e7d..29d061a8 100644 --- a/lib/compute-at-edge-abi/compute-at-edge.witx +++ b/lib/compute-at-edge-abi/compute-at-edge.witx @@ -977,3 +977,9 @@ (result $err (expected (error $fastly_status))) ) ) + +(module $fastly_compute_runtime + (@interface func (export "get_vcpu_ms") + (result $err (expected $vcpu_ms (error $fastly_status))) + ) +) diff --git a/lib/compute-at-edge-abi/typenames.witx b/lib/compute-at-edge-abi/typenames.witx index 745b3473..7dda6e3e 100644 --- a/lib/compute-at-edge-abi/typenames.witx +++ b/lib/compute-at-edge-abi/typenames.witx @@ -358,6 +358,7 @@ (typename $has u32) (typename $body_length u64) +(typename $vcpu_ms u64) (typename $inspect_info_mask (flags (@witx repr u32) diff --git a/lib/data/viceroy-component-adapter.wasm b/lib/data/viceroy-component-adapter.wasm index 7cd704fdc039c341b67ae75c99f4d4f509c66c7c..84e57249610fb41bd8af6d3b4fe70587655df898 100755 GIT binary patch delta 33202 zcmdsg2bfgV`S+YN!!GPD%dk6JX|pW7-hSr_2upJ91vEA+%dJutc3Bn>O{ChTd4;1! z6s%YQ8+Je=#;&nNlc=ak>|zpQ58vfnin3F=-FqJMN>;VF7j`<(zzdgc3E^b{>A8Tq)m5jCm$w* z0aB&`A*Bck+bH;VnNT87+h7Y1`8`nf&mq?gG9}aqWH@A#c^7h zq*cK?wL^zd{Hv|yfAMeM&**COw>HpvU9u=%JDL{M(Yk>HN7dF8)bY={8d_5u{=;X@ zz`B}(f?9MbparGLA&DW$SgfG7x;y?ACQ3__g@wt|WURKXuC4$#(pq%p{{((p4gS^D z@xS9cxo!L;ZXxB4 zWlwLMKC7j$H`0$NbJrj2Yc^2Pus%@KXzKypweET`Xw+^toQ_E>g^Y@ag%&?j~LF zGu%P!p0LTa+N`+?XFIWbNq02)F5f{o&V8i3{Q{dZl@`(1{lVrwRW%Qg6mOW`m5XA#NVl-PY5MG$ zvB$GNnp?ctvEP#JVGExuY<+?hXD@_Hd6JZbKj$u-6*fPG=H{lR#^%{xv+u{A##pV* zPNR3m^tRYDq$vE+YH?UUON!@$9Nor+i>7-`ZtOWy)N0Lkn`edKd>*a*;4H`STH0bS zkZ$46R?nFq`(5_OTyM4;dy#YxTjoxi*XA}am>qkG^ymz4zSH|csI+-aP58=>yCnk4 z!=#AAaSHYZ*aH3);a(hUw#{qACC_PVZe0j?M-8pstmgUo4axA1d9(TEL-1w~CyONP zq1AK%iLu?Q=|CF$Lm;Gwuu*I1@L)^2Um(;Kva{FF!bI#1IyBgw?iDiVj z79D{r{n^-l9FukjZ>9&Z2U=)pBK9_wcwaVT4y{VW-l2oTJD;rU9)B0(@=^ZOzxa~= z1@S-ALE+D%`WF<+jk8=e_Lo5HKakZ0bTpPUDWDk|dq3FGe@y%X+J|ktlvWj4EjV;H z_F?vS*Xgt8daZ4-kAeg1N3hYC(jkf1Co~=Q4Q{J1BA>FWFQw@O2Qs_rQaT_J`z&hi z9i#_ziT^D-PB4BzA^Cf7@PIyj|3R~Sv~gNnTMLkQPW>#eZCbP22+D}D@oQ;iP3)gE z6*1e6PT^s*rnTTsu`h#X2K0%4MF#}W4X9;1*3u&E=gGApq(50pM^fSR2p2;b?Mq|Jf;ofw zlI3hGeg(G=>KZ>M-k(iiw0F^5uL&3uJ2yTs{2hEVsEBQ0bP$c5AB-G4h<(dwHH}>u z%o;qJTokMyTw8H*yhnCpV0iV@XM-x%#MzE}X?3sICGosX+s*F6`q|!s*rjpy*}b$f z9$Om^juNW-tczE622R*7;!!N#kDYcOt?8CM#>?XD)ca_CJa&2brIuZJAMF;8T@epH z69&>>$JsOY(Z2E6m2vj*eYCnVwmzQBj@;CIdVNGASFvZWptV7tA%*1X=<9fV-N2^e zYB6U45o6uRFVxf6HNl!81Ie}cTi1od@H&1^n#QgV_715cHw0Bf>&cD5n4x{iCj6}p z``i>P9NLfE99)jSw*>bM9YJno&;6PX>m9o-f2-h(E%nh>Zx2Qc8$dP(lZOpDbW3|L zce>Lyy?M6Py0AXvUR&c8*$s7S;mCSNyl1xIbg#L-skwRPyq4IV!HQA6$X#spowPTN z-OX;hllG^vd)V`L(xEiAjbE>1$-8JlB6e>)&Az=8aG5l^kle?6^z9zeO5*|xhk8GA z@8UN<5H_*P52eNN*n{!l0jWQEC@39KLw2pZiq`gxJsz))780r`U_6}T)Pv~ed$HfM z@mpy{P3*OJd3Ldm1&#@{35oJ`wtFk>6|5LhNd6Fg-GZ-wWSh5Ak;eYSFV)f58(|ac zdj~Xw*qebkvZB{p?eL1m13(%hNxVC-M%LB7-43;mc8+DSci3ll(DGpO$U^dNux(^t z^5@`{kt4}p_~jZJ`)kl|RCUFB?MO7wYN>CrTEXkz4`z<4qaVbB2C0^Q7!OXBYUoFC zR`&o$zmMar;Q`u@#y(-EUP-HH?9*V)u-;@3TXQAtMPr|_+pgrJ|BXFgQDTJ zhG@eZ@a(*&o-_ zqJFWjb8v5TnrHjdPX_>+TfGQtSb{x!IjtTNyDZT^2lzT zTX1o(SLy?L*txzwm>?IDn}S2+L1bmnEMJp;blKv?v4&M8$=~oVY0_~2j@X!35?_)t z8ffy-Kvd+4uoEs79gu4wXj!Ncsrji7AvkJWD%W^ymdK#_K$UsCHF1~ zza?)AEcM8qE5cR9)3hNmqv7II@{VA;dVW!~=)YjrV6s+T82*G>{E2!}w&{axQ@3nW z@eKiydqp7V%D0xL$(sUEuO_R4!}K-TyEqhr$;VcZM}rN=VKV-1Jfs|dCmo)Lr9ahSd3T_3)Zw|qk7^&Gt2ro~?92b@i8_|LIgXWJ z{Utf7{ic8v1Pv|KG@0A>*kHn;yZ=X5)Xp+q4U%S`;OY|!X!7=;@d(a9=d=t6jx`4b zj~{W*|L7X>4sdT4D!5oYX%g8IoHA)G{{C%JD^>)Sp5tfRg4L4`s=A4@&omiAOX?D7 z!oPG)$C9gpk0%G6m;C?A<+7L=ih>R<7i>QEh@WKDum3o!ek0q2?O7*O2Ig^`OT9JA83UJ(fM%tPc)6wXk=D z4s-}j)o~%(6|8m$O{Re%$<={%>Y9DwOCx633wbdXXNK7p&J3rVmScwP z@cmD2uWKAhZV!%bJQ2&?)X1UyeB)HIJP@Zo{r?GN>x7!%XuB{t!k#khn^1x1lJ0^X z(YF4D$?!t-w}01k*BSl0rMn)T?w0%}c;2qUQr@#yVkrxpgYkE}a|hXSXRBL6vMXBL z6^C-P&(HlTF9(-U!et<3GLeRGo1cdT|9U2Z~Au@GiKVUT*u&j#g@@MZ|6#H!`U-R<1rE= z#Sn6HT3+pp`#&YNL+$gYI{b)S?+~|ZiU7#(kC1MgmtFid=n#_6zIqJbXyYrSTkyNK z)R#j+oeeIL{rG|$m4+$?zs#w0^o8G}(*N|vb}HTH#&PR&RGK$q2b z_e&hh4Kz#pI5l;4gefky+aMm+$HA``EhwYpm;i4H9?}pZ)Zx|1OgRk;?r5-+svVvFdL* zZGI`&=6eS_9##HZ*mTWD!$3gdqe^hbM-%9dWx@R)t@_{Slj{F{FIbijq`Ric(0DR* z)ue*DGt<})ceKsixL;Z*hrP;?ZO*H!$lCqY$<{7nf&-r1FVA4sSNXb2A^9Ctmp(zS zuMdKB+3&m&?ffff{;uzT6Z6W~pVut?Z_=OhbWXA)nDdeY+}rPbx%m#goA+0TUI$A{ zys%%gw2lo-et*B*Kt3XE+FudriuI9bIgPGe9&9{w(*Jh*>xcB=?}^9Ynn%lm9$$03 z+V6y8K3?6lzv5NCxOgNGck~Tby|iEYf{$F@p?!_%*5x)H|BEFr3Eo)FL(cmhf_=}n zU-92!+q3uMBt0Zb0-*U*>wt=lSqKjq_gB>_6H^egXSLV$m3b~x^Z4D=Sp#}FmHItk| z-enthVC`?Tk~H~sUh~Q(EOd3=EFU$I{&;Go$1I{_{)1!vq%g7 z95&ZIVtd-qXHFZL!}t1bTe$Xq z^U&!AcKkdt0tm2d9vO@~Z44mps%-SA8D7^IhGXT++=J2#bC=Oppo+R(He=a14 zkiBgDnWPRMR`jv#OmaH8gMD}=X(6w%Q_dnsR(;EzB>fX$pxxu?#88^3D@Z4judy9x zkuS2exQ>7pcm6V@#aDkx&dTP7g(*`d={U06c`^e#{a0iYd2L(rY;pm?=vOQvb5b_9n?IkiGUWhL7mx%~|kp)jkU5{m{Yn!-?R1xxfw*3`y zJ$Z$Fx*Xd%g^6=XFZ!DZ56XweyXtXprn(qkEwV~5Bu5Hz8f#ub`p}Kb*+z~pZ(U51 z@dO%$3(+{0H9SW8(QB5o-8;zOIPyW*o|V9oC9jbI^rGc#!Zc7)mY6wp(+k_LVNI8k zKeMr~kfr3UXl1+k$_#ozd!LXAOgV?7>BY;}%?*d*^qU0`J!d)l_C-wF4xyrR$>7ne zIFkkAi*v@;C1QL?5KRVQ8dHL&`zL1%0OwtdrxS{^U|7AJlf&S6Zrg18xuh>$vz%>t zF`SlTR}N$=!oDHtBs*^4#6wpuXAjLK)!1^^^TA|;z)|E+Q#1FFLemm+?a@ zXDfe0hPU@8)51d|&oS#UQp&&tkt9E|Ff_O8Hb zBw)S-BpmQ3GPLUBP%eOO8`8Psj2E9z*&3Pjrynie_R-@cLCDAKo|C{N%AO$iv&-Ki z!{{fA*-bZtR~^)VZl5k@b>wWJ%{6p%lY@4`{o z(T70-{)@F7MjmHJu7iNx%Wrs<9m+s>iA@~V&rVx!e%ZZN8%yN`<6f{`I3FYKfYo0ONnB9&VgEs4I!^@ zZ2x$}BL4VPJh_x#E(T+cbENB$E*%;_B+JV{Y^4p)$5`=2P|^MrQa)RD5$VeYJsQrt zv!R{M3N78I`ld%K~7r*j3Uq`RY43RP3XYH$K!c5> z3Y)cp^kcnNLO%@Ht`+1s@-SPm0xCuLOC#3@yXRG~;Bs$A!j)u2 zHgEYIG;zKkg-AJdMhV(%=#}KsK_SUD{8*oC1m%Wsx&y8tv$E6i{!uPvI7=dtFu$rxr-K=nJH zow|v9S#$wMhje0OtSg%{j-=NePfE63bQ4gWT(}K`4It#AZO`07jwR5v>uv-8SP^}! zWMnh>kgQ@m?f{vuJqaYgntk?Lu*O$UBHP$A0(jq=C2a9QWEWc{W1LHP&%XlZr`-o$ zu;?x_m|mJ~pM5t;vpv5hU0B;;P&pnDNq_dq6F9{44gzPJunmlrWxEc$7d+&$C2V&I zc(;8o`39PJIN*{CfPgzg`4lP@S!}2XrCH^-A%lsh3mSwD-u^c>eiP|abp>yu6LSC4 z1h6o>$JpXcAkXy`q;OI97@_e*mx98s-HM8P6i*~|w4{6SL_E%1Qc^rIj*sHvi3wyh zLyA^|uJEf1E_N%Ph!>!gbS<7(h>ybJiCytgP&~04KDrc7EW$^kcw#X=;>8n7@IfI2 z!suE~bHa7-q~68F;p2#rL`14I>5xz{R{9#4#&6kyuZ1Ml^cobNCzjB(g* zISW!_;p-u58umK+J=N)2QM)=*z)GhPfj*r-$ak*;jh^9Rm$Ti_40-!XGNAC+5&kgi z=VSyk*24-8_N}Fc&&*xcO}|gjKvtnz#M#ZJ42D zr4Tb|piFWr7re!>RSsMCE;*&^YZfy8NyDUVU%yLMrlu6qI3>wMtc%y$+T0qC#mIt0 ztT6iqop_9r86-{!nGlO560y#2*CAcv+ck>$TQNrRr}5`ufN=J3 zfSPSHqtBC(`M{`T z&y>>x$SBq|ML!^;+22#tCjy&NL0jmL- zW4WS{aRfW#ndFG5)vKg^%I%CL3$7-6n&)LQf??8`yj5XORMJ67$?+`P78FU(2pQE8 z*xpKNCsoOp9bLC|*K$4C%dk0B^n{**<|&>k>AvjBf^T-2#>e?N>r+k3lNrNNT-nX2 zmaY4um0=UA>H2bA)@)n16<6>yTTm6Vu$kX8f-SD4C2U9y7LXBTK@)sSkzK_#M3yo0Tc(SYNy60pvhA6VhwRCFIa8*l@G|{p% zn(tW_+gVGmPC9-@kqpbQU3Aqfo3-?!r<6;Yt!aWQib7eb0IcB~W{+m}*+4p$^_q=ieZMy?E!PD_6GT-u zeASf{*Ef@`Z0l%rIj9dEh~ZVk)^)?tTtPBa&FncpYQ4V?9a^qpSj!hQ#o=qSGiF&w zYY98BFOJmlEg9?2WL($E$f9nR|BA0*47;f>70X3e5)}(ekg;#wam>`hX!xQ1=!kML zqlg&+i;^u(QGMC0=(sn8dYLDl7qs2HvxiJBv;X62%2j$U)9VfKK6*$h z7Fn~DMB~qFr9;?*ybsVw&G@1#*sg6$l5EzVlkHQ}pBgwrN0B{I);!hHMNKq&&5fqt z*q;tA^YKrRRYkBhTU9e=@9ZHxH5Z38tRBl3eAyB#!?In^a9o^YpAI;6X9u>?o^`^4 z>22AUUWxiN+Y>z1wq?~*Wk=E#vu|Ux=U4g82x*MBh`{?L+C5GGESUk|OvZZN+y)&m76N45bILsl%{DoSfzB zo@iMzI3MV06tj-P+{ykpy)9?rkarEk+=6OoU=Wgk-3S1!Il9x_{fE=4GC^`g1E*|& z`#G9pnL?+%o9#2` zQk8@2%3sp%Y|aQ;Stc5WU<4M~EeN8fi@G6$=sEjVvIKF+ zNZKFN?y9orOJ2rTZ9x}IHH&g5|B60Y+2w=U|{B$vmzq&1vtV*83YE=zzXiQvBAbdDkEU@4BKDz;`GI4?SvB?SQj(2|b)v#lz06Pc)C% zr(Xs8la3iNnBzgO0roU;?AF$ZuMI1nDLZYbcRij#58W1FHbd(Njl z*-g9)v^Wtg&Vgk1RarC4Uo=Nu3dVpR7@p#~9@ZucpiEUTkIS~+cLBubMPmRLkR=8e zu^3nOJ*ZH}NBp4WJh1(K7vj%%V`z^uQAaOH*9=urp|O}JgfdfNlg85C<V;q&aJ|?T8<0`fSeO0v0Q#uAnvBd{s4z6uF zU<|e+DN;tY%~Lyf`Gj{7H4A)4hPVN;TasX&c3P*2jv9|H;9a5umC&UDTmiBiGy%}hK=~1EO;&WkRtA<^5sHPoS~P)H zmw6BzHbgD(F9XQwrp+e*5=aQZ6|#3GKn7)8=(m;!_`436uV*^bI?Z)x1MOQ5jT*Y9 zD?{7JK(TjCcR{BWAH9mE_u&Dyy^O7Lq1d&j1@PV48dwhLP~ z5k>?w2pJHDTBbug^fTwPBCpWx5NwZ8?f1bAv`!%$e3lxzY9M$^ zhka%^g64?k?1yJv&ZJ#Lnp9@PNXR?6+?i~@jS@} z!}mP1B|6W|C(=?j@FZ|_!M8O-hW_RWzLHUVa}FDEJiVpd^HrEY9`s=cBm0io3RF0P z>TJegw6@GpL=j3701qorg*2KQt*iD%+It@bSo1|G<4CRyauyU{GuxW^KpGo+I33ah z199un08#?oL^kPAwA^$!9a#nzW9g6~(DR{tS++T!O*#UWLSX6}a3%2LWENtvpWw#Gp)jif)+;vxhY4DxA+| z{^w@i4{D7CGk~i}whwwS&z#PYSYCHEberQ3!7*ykj2ugJBoPXbqIl+65id&~3O2U- zP$1P=2ZLa=3<$;pMnM0tVP%=W>^$LfS7X8t59SlPFk4`fIGzs43at25G$BiG$jvB0 z3)Z=M=g9_eB4eJt?~D%~f*IwEV962=24<`UK4mUyUwRQ6bSSJ*XqV9G06oRUCRE2< zO!7ynPbmU4dKLDdE5hW2O1%W(z0V$mHZbT^TG|U%y9nuv{Ts3_+Jd>yxnyp;zo433 zd@Ajmf@%pB&oXql3^Ykn&GX2j_CBFcp*xG8Mu$Q#mcbf5Re*5vz)Z~Z+0>)x5MsfJvG^^T9L}YNN}(f{Pn?%G;>v_K0W&9 zvmxMu0L>ZNiY>{W@I|J?!*$fDRp&YtXj-|CQ`+Z;|j8yR6 z3@k$P(p>XnXf6Z5!TO*J!bX!d#W2@$?EpNfnZS%6+fu4IDO_u{tWg`QB4wMHS?B2XQ zOFt6;efGt1P=;Jpg!c#z8(ov&O_0qivR&Rff|g8Bvu_&Dy?Ig;JH2{R%m=;?B?soS zDnTt5fWPLiaZ+IMGMjn>tzjpf0)BSUGdd0PLPc4U0!tJRH^yCb&tC&2W!1X(t(B`|iT zKz2;s24(fTlYmtjnDH8idB&>O0+nm6Jl>8bc8GM2NJzIE!c{59xu zOIs`4K!z{qlEytiFm`28HE$w^hTYg5r_tJ9IJV%5F2n_x0;r^4`9_we!o@ zv+rKNW1m;A-?{Ib*YE1|;`O^paSh(YoqO^6Jss~|-$u-6_uZz_NRr?fbrkea8>JgneiG zJJ=t|dj(8~neVxC2{dxZG^@iNb*I&qc_j>Ma>-^R0FLrwK z`b+z~c>QHEE;^|SoAxM&TS0=4*M)=1@?rG6lGmzcV7I}(u;EUDu5G@W*P6+|rh>Yp z>0G;Z&EMx*kF#mAOoIDNa0RFvP;DIZwOnI3ts`(3l%#;`;#Iq%`FdWf2g3=H5R=RB zWr^k=a;?WXSh5Z`k_hJJ!1JtNH3uN77YGzL$A4@!lm zZoZM%>gX1PfTY6TEg9fJZ|1c^-?0^FNRSZlb7su9a;6-21 zq#%|4oNHa{;Sj)zA>9=%qrz3Cnt$ok*rOcd!ZV=4x1W&>+c5u{*9wgj)|UcSXo;fc zoA2dXD}25o7n(m@pQ0_h@VuJu=e2Sf3Ly=<3?>MC!5`$c>V~JPE)*3g0EmH@ALd$L z<|8W^*HUyXBZyE}5fb<)uN4~=JP)1~_!u$1`Ejnb;&j|A%NqP(vH-aWl}a!_$!mo< z<%n>AA}j>1Z=0XyT3_b(!bgYDm;vL*72wS=_vE#5cOpcgB_I;zI*R#OUMt+QaPD!r z$2A_${99ftj7xw@hj4U#59-X{v#tFjfubP1FbxvLLOjM0xwl`0$%>f3KYpa2WeF|^ z5}aECv}E)1AL(Z!wxr6a%peHR@?7(uxm8t6!+sHQfyxNPgJKLL*Zd-{6^QN$ilD$V z1shT}zx?4jfYk_J!QTQA@4@^vzxt7Wo&r*D3jYps|80&pbQ<*=MQ9G$J2|H?JK zJRQ?Ia1_=>CaX>i?vGhzZ)u@oq?=3;t1jhN@lP4u5-JhG+u zwh9ps`_%EwB}_OMYSxw^(1sBQtW0^(WZ`btTnVvH9j4M!>PMbr!~m-*p+>-u2e*r1 z`5}k!&1G~()Zv5Kv`@;H6dl0p95^Xc4Zre1UC;1t{?U_-Fh;j#pkXyyfUTC^Jk18Y*iaqdrX zT`n--F9oZ+kZqqsPfc;J9?%*-TLi^K9Mna0el#uufx}V)93Jq(__72IR$hMxia`jXs%BJ$TKWg!-a_%uvPU*HZ|Gsxi!qJC8XAPB_s z1PKmjOafESyflBBG909i!-f|KYcU{3UD!`F^iUj)Hc$7#do4)^hZIGKgmq}M2GOI- zaG#U0xwD14x&cR#EnYwmsfnr>fIq>9iugX_#kOn#7ag`6&Ap-$7HHN5sC-p6}um%-?0j8FZlQ1`8?gRVs>L;jFg88}S3g-{ll7P?`xcN1?Eg|w(f;luD z+B=xDhX5s1CCj{)Ejf#Oyd>x~61)?@G*OW(^E$Tsf*d08fM4&ha8EdirDb$jdT>R< z(SSq-^ZNY0Rb@yJKrTjxM+@L(g2jGAC#*-%FU1KTaa%)y>mAZb;-lP{LpsF$YCt%! z(-B92t%O_xLp3*bngk)gija38YzKCV=rG*lx?|o%kBf-os!_BM1PXmj6b%LTtS-O{ zteH1wX=~}(v@QjQ52AS<&q?sqjA5C#04p0tQ;xAJq`7Mw@WMLCTF^Z6R@%gQ%1LNL zG)+TP8UaPFNJ3+~jfUZyF>Kc&T3_z-%m65m4qOS4mzH^ZXReT0OnaqxMhL=h;0u5| z;@;-w96g>S(q5Rt$I&2a3AGOKXe`0pLZ?S84Y9!9DTEI}?2sXFg(6t&n_F|uhy(U6 z6EYn6ka^$&t04=_J2;xK?Go*Zu@RXu8FfgyH=A9AL!YEZ&9tkW;WpE#$1HUg^ zUcfc;E(*6@HVRmkGJv5D!r0(Ef`*8!d3UGg?(Ay}QRYeT#u#AD&;?=lXUu!B2!$cU z*ENg;)=X=j8yyciORe?SRuB#(7Cu#7Z-J5rRNlMYcQZ4vQ z5qp7pC7JgDfpTl?%2ukh2D?@G=n8jTz?Y@Kb$vfT)zJ%4z`iNT&j1a0a)7KmKDP4! z4!ffdB7k)%OHwuDrT9?B5pn~vJeUVH!~lzWK-xl&fOAfOHQ*_1xGdYSHY^s3_dPMlj%Uq<(0DF8dXh2ZGp(1d?lk`RY9 zAMex$VZKVlm}EpUbxqU}fk!OI{4FQ{<5Cv#Dqv>8F7zFhhlHPq%{`N3Q;8pdktX@Q8J&W$3$2d2G$V8E9Cf$>@duY$c3l^ zfP(n~J-$;f1oDbG#=ubVcp3=S2?N72f5$eipp#N6a`iON2laxnNeFenn8V^+@UDn+ z2>4(%Xg}bKu7X4%^Cfy>G)EY~TemXwLqm>(XjRWf3qneuL&l&|>LQsa= zKMGvTFuzslb@m|2v5`LnMd<p&Gw>frQ|IXV3Xpz zI~8RkZ@1TEi@(#UBpa%*4bE0%D?tgiP8Hd@#Q#DEw#BGgHgG5&l0|*C z8nU##OxrS)XRGC#U5+n($T=)qr>!u4ZnjQax2R6rdGYo#ZQYQKk8T6W1*p?DkX*>V zW3&oyjGHxh6uB6M+G^;EINNzQ(n8L@jMi~!3wC2kJjQSHw4Ie~>t%FeN{0TR3s62` z%c`PinXBT*L}nart7YRar`=L8Euc|zQspT{hG4FaAJ@@G%;ZWpf+fQWK-37D3Q!gK zvYNT(d)4ADiHgM`BYfU9^zLDz<%8|>XlVulLY2)+f4GL++IUz(j;`Ysavd)n$II_sA6AY-$+*2kD)Fl1s-gU?*<*(G zL3u0`j^jOUM%}o+s2F!S{@#kZal`S%Ew39_dwc%=I?BdvM%B1_vISM+1|GDvy;G;! zaJ*{V9qon0IyC^l?)-t8ad)9)TrD23S&fo$#Iv-K`|a03JY?5l z#z3kZnuY@dTtU9C!`)owj(C&-f#`qVGDs3++aL&tU2!Sj(<|A>|at}Quh0HhyS2Pc<3`0kx^3nMC2yZXnNRug$1TYVYjL46{u(J7^ zc-B$>hmG)0XrK%v=t9$iPY(Vz^RalCo86b8ImPsAX!&qBRiH~DGY_fKu7CvJh+${o zZ9op73ZoZRh#|?Ex(gNas>$QLVjiBnXxI!T(>sjzg!(VQW;9?tibz`!WJNKb$nG5Z z1%1nHXcn-M5S&K>v4yzNlkq5^eD;krT?R<;3?^hL!OlXi6!e2FH$njcDI$c#16|aTGB!g7SER zDe?+>mchPt01qBw=FSQodI64C#p8m|n2}KctsD{X3_QIyY;bH$HlL55%B3mKCa5V_ zMWp0<7#hhCHkN3<5RVc<%I#5$?%SZ^b5rc(V;J_JRzLXt)@dgA34IQ?rjD+$q0}}ZoFLx}hsK1L+n5dZ= z7RLpQU^F5~2FI}j%@s_|e5GThW$dJlNMnHAhe)uBR-6%AccB;$YNBJZktb@&zg- zj;#5zeFM3^QKiJNK;i&AVc1?L5sx;Gy_PO1Q;~Ve4GIs)1OJb2e#@S_mY!3_Q%9g# z!I`TgWl*rq@A&m|dm@RKlO>d2`29TJ{8!W+b|Uf-c zliTw?xULKX*M*r1XDPHy8~P_6wR(u2m;xDdMHuc0*|%XPnadL$86h3n9=$-=@Jhl} zilk0t+`7mpHqS|L!Zz5BV`-w7j5@7cfP<)X7+b0Vr!;H}^V|g74Lm*J?VFLHhm3p4 zMH;OPcOrZz=6ShRy$o=5P zh@F8(f^=66330G}UGw5jy?U0zmxZShBybI+F$j1JV?}~Zy^S7O4&O2+z=I}8mWGc- zHdiLt4&ETZ$(X@|9Z=HoOn@Sps}hl{%B6awQb@LfVuL3b@Fa^Y!q2!maY}oZM+p`M zjDS@I4(cJsXu-d+CflLE!0B`z>VAg4I(YqTMC0*ffe*%rbH4G5(K$`0kQ65CN&C0E`Y7`;_cpLx|1NC`sg2@uuTCm@*NqSjS zl627J|4v2HE5d@Ls6m>mN7^lZWmJ!}3$I7I9_2^}lCTPCbpj&o>aZSZd;<YKUEQBYJ;GTf_ zshQU&PU{$oAxoq?lo#&wgRZ7zYefAJ3IYk|Si0kzwALfb%z- z`ccG6wm~9-pkN+mkXh>sh)WsfrUblT**@hdAP53;P~Q=VQ{kgBZ|c-s0#`#bPuajz z1jwe51*i;&HQ{NXn>+O?u0@pJ;c++(q8SWIKtKhqN5m*@;XqRlgd|YIGP&r(6(*M} zaUv8fC_m<{iSC?{hvKIZ86vkOx&ya6e&5a}JWMO=Hz!j3ayEeio~WqDmPFUa##!^4 z+NR4}+3bhuAo%Q%I$|J_;n_TRC7E}ytq;@rwYo71waG>P(3K5sDJ>yrI8x9?ig>nU IJ01Oh0Gn0BE&u=k delta 32375 zcmdsg2bdI9)^^|9%@BsrOb^2Z24;pFgo<5N1Yy(?v#_pfcBm*rm>GrvWmkm(45-8s zu909sQ9%(E*954myGG2e0q}Em*M#z|X?2zVy;a>a(ki$4<*0@0LHe^rg$m11uf($2x( zOHK}6D(bQ2=Au=!q+3nRbgQl9xczcNmXlIXN^ z+2A3i6#q)`AOAeazo;OnDO)kOr2mM@%KrU_SC-I{((oVtmXwrKR^o&I%PUQE?V4=g zKG`*yjKzdfe($iJUGT4cqU(@EtYFY!eD9h};QwSIkxW)rR_38KTAG&^wpY@>vIJlA zDrp|&)e?h-;9sINUP|*SD=F{1v?Q-m#20j$msgUP7&L59Y)IFlSW&D*3Rd*$U-HSY z?WDkRt%fGAv8KMktE;gs*LP##;CqGC7h2!8BUH1|%@!D>8j zX3cqCQ*0yY8h)Qy`&%!%vWet-b+c<5>+8Y_ca!efpRIN7Azhql_0H59bQ0FuOu9xF znrf$e;gx$yN8d&FvukI1u`MK@|Cok*tfr&A*nOk}e&Y_5-5-{@R+BYzPMs6mO1hxP zclm(Bh8`rT)(d<}{xs{nnX%uq!);nbV-E%U%6sY$lODWM?KCgEk5}U(nHhT|?5UyA zv)tIDq$?`ndrRAo1><@bjo(Jv<9b+SdpMXzuWr_K+iQ&N2uo&q-qhIRVMFIP)?yi- zAl)0Rrpc}S@s)fsxUqNX+&_?xVbQeOx~Z|JGC%nC#GWQy!V=z3So%kjpSe(HP4{Ad zBAvsZGv`bXi=RPp{j_N{^>tpo@5i1+TaEQj4JY*2bEH%FqtW84`#i~?i6iIM%&DF0 zO><)}kWP(Oom)RWT)!7l%D2jL9Iv4%wv%)We>Qr~?AS}0A2Yo=H}*2=5|+%IJgdpA zKff;a3hCB%QTZev3>T!yn>Gz!`35&cEAlAm6fR+V%XMoTV~+-p^eHyBksg@|POqQs z)u6z0n(7{T;rP%kwDO;VgUIwdw2!yM`LfXJuB!y8he{{2nq8gm0g)cUSTeeMd-v`V4i}8Qb0k|@N#(rQEuAxKYu^%bB=o&h>Jhq?qJh01I zjkVzj=LOb)Qqmk;IG}g%y8&hG_y`vS!oc3F%f+-kjV%n`8_+*~NxVNBdo8UB2X!fm z2awCyJ=fCyG&v@eaV#Gle&*WgZMRnTon zWjju^4Bz_4uZivpP9D;}--mxlBq ztAl;`ToZI3I*hCh4j(#{tYeR@p~Z>VP4PbnmJ~X;qzlj!?#Wu!!WM*Pp&QvT&-`bfqGiOeVZ3=dX{mI?bl(M~jW zPtbf=3E3Q6e^}Xwd*fN6axAcpzSHXKr_O4K{nr7PYwKou7|Qzr8D2&Hg3q#G?C|#S z58@+M-3huKJ3NoojipJ7vw||V^iBx0*kAeg4vtmlc++Bk3$_pMK|W&7-${#U?Bn2z z;gzMIw2V3G9D7t_*Sm9KpRzu8(GrR?W8B3zU@trEE;_6{_IW%@kF7UY=Xo`CGh<%_ zn?@9qeXMdVUyc86Y1yrHnriFotj0NgISKqdczZ-;{L6T8uwz(ZUPxqLvF5e37ma-# zY#UY-Odi&Se8YCErGshg+hE!lx z1;)BOOnW9`*CZ-}7p0!;@P~O{*Cwj)t9M=q?yd`-mj;mQgYI&1J23v(4fxh8eq*AT zRc@q(t2WX^BDOlwGyIjuw_#0!Rcxfa~?dhMrop%?DWSU23P%;Mpw)>f)mIfJ?+Lo4FYgo$`a|>@tV^fbhd*Pn;PCVrnWB#~MIAFm z`Rjv;a#^$l9r)BB&2A5xwPLa)xK3N1xr?ttul z&S%|Py7e4PFGX$S|##ouYo;Y}4*5RWD{!(A)BGH)X;)%n*&NTQ&w!wI`^7$3< z>ilTs|2+1@49Um&%4bUW%8weCUHSJ8e)~h?vKx2s+dnw??akGr57PU~)yIQ)=9)u- zV~*J0mS!g&$!T`^aWX#l9h1W6+T(Mm^p`3u>;f{5D)c$wq?Q&=HwT2Befz)NQR`6e zj$Bba2C2cMsoM?so*pTHUDj)s20 z&E|lh=ZO#euZ|&S1NUcE1sBhnFahY}t_dsgdBnsB|Tlm~h4{Pmd|MO{U;dB4a?VC@#7$Xr*=Oa1&^l6Zqe>na9wre=< zEPm>ip2<($I*XsW+Tk$#QVaWjzQXu(`eN8~&A!2s zbK3Wc$bk-`g_T^0b^xm#M3WVukmS-}^Et~8TwfY7!!p!`yyNnJa7}i7DQAY663z_o zI5*1-Th{mAxqU{>Vc_shW=H5&0-txHH znPf&ZuLCw^jnDUeua{kyP{QeuGJ1p%e(-as;9svE;*Wp5`tAS9V6L2e4%xotqsb%4 z(2%QEzuK_{abP=Jhn*436^V-cp{I34owoEWn!p}oPQ&Es;sL4$HtJWq$3_&=$;MJNNMs&Z6!`;%F9la*L`s^>fYZ{cU!h@yQprvHg$usv$N;sDL%fw zw4v<^&A61G!tIxE?i*Fze{=T!aD{%hLL}X*N22{-Dj)r`<&VtfW2wISZn7iTcXbKb z7QA=;)bfbVTO?G5Vz?^B+2?!ng@Uj~q(1S>oCvI4XJ>9~;hq2PjeM8GJv!h<>$>BA zuE!s4j|M*=5bmi?Y)oD|KM+5gfSK&saEQ%h!`s=JOnCChz<8%CeQ7~(>^m1=E;}wh z#OBicLv}7}7muRtHU`!26bF5OoO}rG3nPT^$#Ceg_M-$lnUFzbKD&UBlZtn6LInZ= z9F0}P(-b;YqM}`L8{0+5PfkzhmWaic`oTS8{3a-eNwDLyh78EMvLq&}A&8 z(RpmkSTg7F*ti77c=tKAL1pS{qv`>F<vP{lkLb#wU9P=Pp0`KmD-$oVxnc zbN`1!;oSe+R9ZMOeR)B!XN!F(vf*RjY(rY))}VTMd9eRY|M=mfxTZm#Gvlz z)vfdRO|W})7y8PALv=0(TAQywWFhYESF^hUyLo(L@};2RH3#JNA8kcBxXJGyc|rHz zM!{eMf{`zr72NeuKH?W2e280yaAZ1c3r)4Ld{+4o=@E>3_~b*y!Ol3UMTiC zs7>~fJlcLP3A#U2oJYqYGLAc(1_TqH>>51&^f0Eko(EZrr1H1)+dD+ST-?$2L5Hr1hK9n(T$@`DCTKuB+LIT*4IOjX$ zY)CmQ9Inq776db1<7(J{G!Or|&B)R4gZJL-eW=YZ{j`PWI@@>@3VxOFZ=YbkLVw2CJ zKkr0;{ww%_sfX zlF6hOE{IccVecilP&l>q!uH8zM*f>TqOp=xw5t}_UFVU0WCgQoTU)uMmdqu8Vg06% z2k^+k+f#@^R%o#&Uq0%~5Qy&g< zU_H^;w{=+DZESHpD%`<#)Q6*3I}f9{sh*5PN%;LQ4H)EIIYYU+f%GODbM6RhZOYWz zimB8Rau2(*F>Ld;M$$l2P7^sku^+p@#?3;_9Zh5gpQ|z}9C!UJk|wvZ^|Qz@vV*-c ziwwl=-_OGGtY^yX@S-)F4Ci+&pH0r^e>W0h`Uv(Jy#aL4)48p;=hrRQBh zMv&Vw1hZ-*5$Wyo*_dV6mkjv~nuF18$XS@*%^}Z_uh`MQCC8A*+0x&l!sBdn^yih| zlJl@iqkcyk$eZl0-;oo0KFOhORRS>yUE&pq!8B2shjZ~JOI}F6$sEyD1V^;{Md1-0 zcoF$s+sN$B6>-F_^H>D7^&)aRd2{Qei^(E_)<2$0#-!fl=dPMmw(Cp~?SPtSdE`9O zmp$<^c62`5es4$}l@DS~-@KI6;lZ_#P_Mj)TtF^oiOu9h@>lk&%^=PX*=79CF7^oj z^A_8;nG{ui%9%=jHNq-*2i+>V4vrs_jL>^LzoKjPPWH`ZWCVSE0b4dXT(y=nMqkUh zw)N5Zgc9;`)cskPk>%vS*=Ls#gT9(mkDY!wsi1ExU=wDB?S*HEpA@=t0sC$mwmRII z(e$kaZ1^Hl)FT>9g2&G%r}V>c5t*D|b7zuraI=b~q?j#TM3OmVxOEZfP2X9-j-L~D z5grOoxAe^gta&3TJqY3cc|i{0Zo7i?FWJR+7i{q$d@roC`dYlXKQi?PpNC;i)hojH1&k0Qg^_&2a2Wp86c9=iq%@*B42 z8nPd9f6uk#bI{(->+tJ4w)1-Y`JU~(0sHylO{61ZgUE|)=$oX9{xFX~5nifz3Xkz<#({c&05$KYqt z|K-i>nyzFgJxl&du413Q1fuHl zJjB)FsQ91H@FKo1Emp3!93n^k>>?Gd89osjB z^zKqkW>=FL6>*4@ghhi{$L`4Oo$Zx07edpV;4TkC^`kgo8eFkjlHr zv#jBcu-@uB$Zql+yZFu!d^~U`+12s+P`tz{DH9jrTtSTAMI`z{Rz`A($=<&!#8M&m zayH`oPA>fgUU?&#fs@sD6B$cip3jypBLnjinPF}sB6)?4zbE7*AvhRv59!4o-voi2 zK?~u9zIPvRVc~%nK4^1+Gex%T-d6S$%DlZ?=u9vATZr-Ez<3qeQxwm?fOX$W%ITH` zY{$Jg>R&G=L-XR%7&-SOQzIUAA6o)cz?znjqBc|LL7b@geqKB%r{p}=%u(P2oP7&9 z;%D+wZ1q-llER=jXPyAaT~ro?i>F%1rGiw0YryXQ2nphOebwAlwJQSPQX*_*;m6u z`j1xunV!ytOwYXrp7Tex@3pW-pVz?w!b^W@bIG93)^7_M&y*n%u+%cz$e)&DMTE&hGHTBnWX* zebUy%hh#}1+xs245Wr>X_vF_&=i9%BQdzdO-#^KzB+-i;F@QC6p@l2E(Sofv|3L01 zq@10xpL{@iuN+H@3i~8t9q?|FhI%}7ACGmQaY`yy*3t@6x$+8HmcTnC3WIT-3s*i$ z+mpU4|A6OE`z2y*&wkSL{{8gW&ZIv(whJ9a2C$u`wO(GK8IzqBoPFj1sANsC!i5j~!aVB3pmL;m`gX(D{FNuST54&FtKs zbaYB{99=LR(RU<6N!yA^PGD9~+J`;YllDqknl9O@leR@Ct*U}!(y6Svj4EtIF&$9g z=#HUDswGOkZRnbA###3qo6kAY2Q>CuqNUp6L=me#PXy)BPYR)`KloclS!WjNiLTd_K zPZxB}Pur?$=$;{%?b(ds(HPpXu3KnlwxpB}NI8z{ik2>`j%?r=dEe}C0q@CVoy%xd zQo%J#@-P@#aui2khnLaXlp=^}K{q7J@deGYb+c0)zh@Y4wwV1`MoW^4tt*CN34*2i zy5$*cU@y8RWw@H}d#W!Qimas_%j`UbuW4`gO)omCK=WJ~Q}Xn*AWDX(nO)e9QPGI= zSYtUIj#d>}bq&K+6gllkKD(!!jxNA9IJn@tf?+$FD4E^(Ob=(Hd(+C4A{s*4@Y1#` zD`{0T%w!|m+>efCclDiEC0e%TJBFh1J_K~%XF6*hiUxMhrUmTn{y*>Mb2w=EHU7-qlLD&5%kzsC4&QLqeM^koS%@%*&ee@fKK zZahfL#?7Ja+4NKKel1P&WZ&}BI4zp4st#K_h@PD?Fn7nt;dA7)Yxur7u5m&DTeKt!`Qr`^k{bW zP~2kKw&aPfZ`dNvjgw{<52a@oNCJpmaMPZU)-+KN&Ebrk0OmCBLTvxAVYF+3=-IZ0 zz4vkKu@k;If{$Yi+jAl9Z~#?{U_dw>j;naGWe7gIdl)?<<+_@k_GQ6Or&U9dd{dke z5$H7+(IPheF!TX7qJxfo!4t4hzHCZt;>j4r_Wrcvs*7n4wwu?ojC9&^6w$T>Pfc5@ zDd*Jc!j2t|*R}a+O?6ZdqaqoC3nY#uyFxg$2Kfev-k`~v4nsPcGIdWq_L2?YpGy*CSLNy zPtaNOAcl$t;ePO79Te({X%9=R>!y(*!{_FKQoj~Z1w7bs6c0y0v_Pr`UbMlXq=zydwU@Ra7L4nwi(gvF+ z(a8lGrl8oes94x?U)IgT*$f%f`0;$a2^@%FgQ4phCYqKU(8y7I44HdHS`5r{HY95{2BzTON zhKw*2)%7IHv(x5y$R$3vFVkRM7Ep&Hs%bz3Fmv-5zLYYXq(KI>+^x~WQjRJMn&Rmyq`D4vZyuLfvNLp0u33hQPLsqURP! zo@BTpP>w3fXoyslWVzPjTo42NwRrrZndhUu4`!-Yo>^< zuHlHVZeys^&2SX#rYwU8*rFn`<}q|)%EKbaSlqOlRzZV~c{&8#GQ6v;{BYW{yClhi z2Z-mYvTwVZc?O3&G8;FJ7P6}jr#+J3ieST%Ase!f6;s*f!|Az6-S>4Fgrlc%ct8QH z&sbVlVCWW@i7tUV`4T{ad3KxDPFezC^VwJ|y<`hux(-m3?TMl$o9DDt8=UfdRhNL* zG}-q=$Mel|&y9wA-#Bn1uxZt>!E8l)!;X_Cjn|%s$`44 z1cqQvn#vn7*qmy>v9#ej=-#owjX^-VX<1PhA5>%UT@kz`t=Wd`30(G?Hhbx8bg^tX z;Oa?7&>ksS&=pI=is+v28fnjTCiDA`W!sM6>{O6+!?zVr(H&3nT+=;2YQ6YKI=lc! zNd^uyY#)bDSJI}}(t3)mK9aN3blL+$7B$0FbpieRnbCc7BrQ(rt}XexD5V`>sC3$A zeU75*ODxxsZNUM40$PHaFnLRe#T7Zi;^!PiJF+s919IO+(~^+}8-W;;%vyeQpcLGJ z8jqT^Pl2Zas!Ndh5F0=YnmL7YjZBR`M+1+8(PQrb7*tsXD1?|g8iz9N=@P&O^bue; zTl36mta1WfTVQbjr)z=;*6cf)1crqkClvU;@9LuA0#Ul4O3AEa;sp?jV{QZITyp|= zD`dCmdY0h|J~qS?&3ZQDQf$Phx6$GQSRDu(u*LRuunFJ7em8JQcQmWHopwH;Du6MV zJ)jCuyzc_w&uG){o)gipr^$k40k!~Fduc&78>4)n-*UZDW@YOE2%a8*8fjbiJqxG`h)o9*G=IlM-fs#--vjm89Hj%Tx`KHjzkU?^ zVm(b9uuHnG0=24W!q!|BVCW*YZ#-4m>a%G@fs8^hJyEqBT?D)~FXm_H*Fe>rCqkJ7 zN)&BRG3SDI`5JY56mW0OIkY5|_91sHU_na;ZxID^9*KBQSmncWXj#gIxC1`7Ek)pP z-ZGm>Bwi+*4*sm1LQ{nhi3Sk7?Mt3+;8msOd>D-@PlY77<`h~{C;(n4TrhbKRuV^H zLALne`=B+HaqP~ei>(3=3pQ{Mj>SR}Ei`(`XBGEnF}@4&h0RE7z6yYW(Ohysy$I+R zZ~&tMzJoVF;rA``Qr72GdJgm_Ne7cnLm*?uqInsa%DO*5Phfii-3q`134O;_^3JlB#4|2>Ugwwdegi~mO{o!=lqY!%{fD=G3;)I-*%%x;D-<#3w zgx}M$2(fx;QP4dX%Z2GVifk^!usOJ?I0K9S(C+~jA3cM1ErgJEEFa^yLuM(N%d;EV z>rBw04~Qe$K#h(FF(O;$3bx}+u3bDt`&8y)G4L{5wZXLk7=ZyaGa&4-W9XeJ;3u3T z8CDoYHlU1{SCfbzO&AY8_~p^yM@HI^z^|dtdydalrIl>w+34ov@lbHn*l|Sy#R|3v zROXwjqQckDqLqcL zokfGjjI#&Rg?TM+G|lR7rrp`q}{_s(T8gCCK+W!u39xUGW&So^#IS6b^M7 z>OPQ^%#lOdydE<<9^)>nq21Vya{<#t1)v+8Jq-hvYk?u$&|0M`WkUfJVdmjDho3X^ z#@5oVY()(%EVKYKfo>t}Rn@gD$6OsvFwMTG0R?*+w0F>OT9qA;m};)UC^@R`axczd z$wZ9OaMBu-b63WEz?&6wZAeivn?8~DDfF?i2DBnrm=sBH1aloZCtAQ)CejixOASB> zjMPwIF;N8bCQjQ5+jtZ0Rh3HsP@!ST19$|w6`@d@H3UWX2?xr#37 z=6dooufx8LjE)Rob#=6TX+u*ZtV&pW)e*o1LE#Vul6gBhHmbDO0fJsWi55cMxPl{r zN>qq5u3v1xYqfifxZ`JEt9>U)Ie5Eg#%{%5B+XxReHS?*YPH0osZp*3Km*kuYM%~_ z=a?H?U&6iVz;|%po%0Irpt`*9*v$vLar@o_U%0)c&HJ|RYx}xw@BY@8Z9hQFHZ%R3 zMUx|3(61!fkiD?U_|WpU=Dd&l!2@2$9qcGCA`gY{-VVMf@2Na;kQZ-1+WOw@#}0h$ zb}+VgQT6uleb4+or#o`qx&3(b9%#IB`-#>!Za?`mFWe4p!0WcNZ`kJVk$$?(+qVCB zz{|G(l=H6bXL4V){cO&gwx4VBqV4C~zGwS|*4J#~E!!{hmuy#O-?F_k=Ox?O*J|^3 zY`@&*72B^I@P_SI$(WYy=#~P66hy#c73KytZJ4iROEIucy{lM~`DRY34;vNEKgd9aUMQM>&X(4BxL1LO z6|zbQGcWC%Z{?K2FbuORcr36yFuM76PN{2&;QXA;`LNVF<~up1kie4Y!*T=5Ca{qC zZnkt2@7IIXPlqOKNE$?)V(w}w^-(Io+#XtqBvA$HF?Z*bI^cM)_Nowo0Is&VCtJFg z-z)h7z^a91N{eamq4#o14M`C+4+age&NL*}f3=jJhdqF$Nw6U9(p)tZ%=g?ld05mF_ds|AU<6gir$<@Gm6`&J1RzA-ywFIu)z%rZGVEi?|$SGA6 zSOg3U&VaNfLI>QJQwm9m!AkHZ!1M>t(0}KYdM=1Vas-%VAbBM7@7dD5d{mO|x?C2^ zo}+4lWq#SF6r>^RKCmQCC_EB^`Bj@jPR*_kY6494f%!f2>y}anNX&=*-cupuVQql^ zW}DyS7Ar6iIW}mLn_GadMDVb4*_8`@OLFNB}LhFpf@0=9Qp_01dU*?c5s=60?%!lS@d zLQmHf*IXTyAB*h*&AAw(3llDUaVT2@3f@BHRh6`3FNNnJ;RqdJ0V)_8;0nB9poz8F z{&!zU^9yAdputNuxU>va1wF0HPIB)s+8Lcd#SnDh6lBVAR1#?aqU!4W7_4tgjTIwk5r$`=7@$V8EGU_bs&B}2c2#soHVJF8ek6~0R4YM3{NZ?*`(r2s>f;5i0tI_8Gx%#C5^z=2f& z>k$l9kT^Q*sW_kJ9h{)YvghZ~!W4Ihxq|MdT@Cu5rI~lO;Uw@}mGixl5lZ0$-oeTa z?ys14QEtZ>&DJ#2zA0Q3)3PjaD+9DcErq=l&Lzk#MKd>LPwqB} zmT|TK{}vdI1pgYGPms5FvxZACN4`lP&8NMv2)c;<2SaqgT~yD!r?tW+nUzJTTzz&>k3%Z5c%fDlFS#l7J&au-$-cLQa^cgVdpKok&?<`#M; z_w|fN8SaFgUy{%$bZ0oA?~BUD_R8e&zy^R;;W)?iH5XbPbmaTl=L_j^1q%2K?CYgT;4}jgE)zFdEqY&vms8i@SkG!qN|yF$9I6 zSZwWO^f)%*a@wUp2hxX?4%`kt0ul6&NaoAwF>p(8zZs$hEF`Idxxm~O9($vlXSdO0HNBO6=Ejb zg$eFu!;J$k!0T;VZO3j{jy3TRZIXs3FRj4HEdyY_(K4+3lF7AAGqEh-kqXG$2qSzD zfnmPMx~-sR4b!0A0=t6$3)mVs8;4JuHhcPTl}kP-yzr00aShc@O#hh%T`T(GIUAlV z3y-SsTv_CuFjZEt33;-1g?X~#yW6D6B70i0WX0cWlO!uxiws%sXEJ1U$Y#h2NY(H_ zKyIuLGr6(i$c+`us~Qp9gS=QDVMSWvrt381NcEvc{)JcUhga=!|sz$zzSA{kZ*hg)C$Y#ywy!!%g&Z!%f1 z5)jYdhDosE|7gvDl?+Dr?-yA8hX>1$_v(iz?-d>x;c2hv{wV7e;hC+9;=Jog14@zG zsyA7HELWv`YztcwTm^du^p9Vf$!--#XTb>Mwz@pbZ53RE#8y{?iLDU2&+}TvuZmBI z6q+BeqorjTJ_m&cP)~xn+Xr~`J#%qz;+5xgSjOBtCnhg zQ!pAsjvz7%$_CVA!CV?=m5bn};$5-n*VE30VPK%a4b2){ZxFD{;!&2ZFagr?D9KjQ zil64#3Zhh5?V}W1SBDw42+yq5m#oU<*Xl@kR;|he*6SL4=@-AYCB0T&@pV6+Uh8^f z*Qz36R;_Y8+{cq^@zq_8+*-YnRcjwU*CMwTvT7l>77x6yojRtuwrsA(rJ~_E%6YfauZt zSj}y8d;tutK#Oqg!EXU|Mgm$`vjJlNo7-SrfY(|zG|f;wgc(@AWIhm&0O<+0^H4uH z)8N-f_zYkncXn)Lb8knJF#gMk15gYZa8$Kq^TDXeo*U34G$I5_0%z(5Tu!jE{65p< zv<+wy02%fbT>y*XKus_o;t2Jv7_&R!9sx3639FaH@XVe zCYg`K$3~QS_$`Qoc=%@8u@L$(EIf$uRdnbys%kzO&zPQ3hf6^VZgw9dhhrZxteR>* zhDh<@pkI^^g!T$N2Gt&pYN(u$yAcC4Amh14u1G54Oc36ns@vjEgz*6BFgCY>?YW*_ zRRB;TAYRo4E-;|BawzffCfW?G9sy($#HI+VzT$8wanarM5;!|l*!p24gWD1=dle8O zysjZ|6OMg2THpeKOU!&Cx(>?~luDp)ZnuLHVLlmNFNDqmoQVYmx`vriHvbT>;o4Q` z$}UgAQviJ_O&u!J`8hG~dbtcmVnoUqhG|v=0Ptn19Ob8+_N5g{sYEGkjmL^1z6wna{+} zZ0R~#ioj%W9L<1^3F!;94!bW2=Cf@o6~X&xX^x&o)TXDvM<$rh#m|T)2M2ReA@@|l zKM%2}VPgc_d_I19OL5mSIZ{yJc?N)hhnR=lNRY=b#LsQ5#U0uCT=PJPNg5$wKCE>J zjMU5*;}JFsUD@hmpl;~%sFAib&^9zV!`vC3!J|?d06C0a(7*)H7__UG;*lS9*@K7` zfD^lbcL3H4?;gOC0Pq0nd%0~TxUvfiJb0>iuWvm1+Wx9y10#U-J47Z~B8mIzp+Y>Mlc-cY4h>HZIvw>dC*W)bt z06n=7LBy~J!gmK2kF8eCH=>)&R*Qh_8Nl;Ufe{A`E+Au+Er&&Q$0t&h{E-B0pXY_%C)Ix)YCfLv%7Cxw*3Q7N{<4Y2InHJ8uc}0fQ+_ z0&p=PjL&>Kes)yhi_<8#D?mfE5xQx^<0!z9t(ot%sn8*_5HOCxOair4f`b*P&3qRJ z0NspktJB)52nY(_G{_tAQ@i3*!YbUwU7muc0m0yiP)B49Y(|o~8(f3??X%wPGVVIy z@jW~;R@0$mn|s=BEu7oo{yXqX!}JN&9o8_gu=g^j1^(^I!zIN*;4T6Y;hq35cFhCx zB1VAv{h!U2_!p!~k|pNFKRh5=A~K3*b0w0$@vrS8l>8AgB^KhzW}Yb#`00z` z6(ygx$fDM~hg@O$EX>TUTMP~evn-tcz0)(jX-EPKl0%3-Jm36j1w)2~RX6uX>>{jK0E*&vc!VM%Py#$v zfvt32;`E4Ry>S-h6$}NZ2@wL^KMUKuVKyfwL=_ML&!kd_D#gvKktB+R;RqeqokQ|h_98KJB)hJauU0DXjSz$A>~;|oN1 zr+6eEkF-IAfM#CCuOHc~!D@0Q<35B{%PtJ$;66NtMKv#vrWgAE+aCez1K1n57(DMV zDFiQn7%o%{f)RlOPgSdcA4WM$&<>MCHg*Rc G@qYlP0PV&A diff --git a/lib/src/component/compute_runtime.rs b/lib/src/component/compute_runtime.rs new file mode 100644 index 00000000..0f2d6d8b --- /dev/null +++ b/lib/src/component/compute_runtime.rs @@ -0,0 +1,10 @@ +use super::fastly::api::{compute_runtime, types}; +use crate::session::Session; +use std::sync::atomic::Ordering; + +#[async_trait::async_trait] +impl compute_runtime::Host for Session { + async fn get_vcpu_ms(&mut self) -> Result { + Ok(self.active_cpu_time_us.load(Ordering::SeqCst) / 1000) + } +} diff --git a/lib/src/component/mod.rs b/lib/src/component/mod.rs index 56c5a6e1..c667056c 100644 --- a/lib/src/component/mod.rs +++ b/lib/src/component/mod.rs @@ -59,6 +59,7 @@ pub fn link_host_functions(linker: &mut component::Linker) -> anyh fastly::api::types::add_to_linker(linker, |x| x.session())?; fastly::api::uap::add_to_linker(linker, |x| x.session())?; fastly::api::config_store::add_to_linker(linker, |x| x.session())?; + fastly::api::compute_runtime::add_to_linker(linker, |x| x.session())?; Ok(()) } @@ -66,6 +67,7 @@ pub fn link_host_functions(linker: &mut component::Linker) -> anyh pub mod async_io; pub mod backend; pub mod cache; +pub mod compute_runtime; pub mod config_store; pub mod device_detection; pub mod dictionary; diff --git a/lib/src/execute.rs b/lib/src/execute.rs index 5c632dce..77cabe2a 100644 --- a/lib/src/execute.rs +++ b/lib/src/execute.rs @@ -18,13 +18,19 @@ use { upstream::TlsConfig, Error, }, + futures::{ + task::{Context, Poll}, + Future, + }, hyper::{Request, Response}, + pin_project::pin_project, std::{ collections::HashSet, fs, io::Write, net::{Ipv4Addr, SocketAddr}, path::{Path, PathBuf}, + pin::Pin, sync::atomic::{AtomicBool, AtomicU64, Ordering}, sync::{Arc, Mutex}, thread::{self, JoinHandle}, @@ -365,13 +371,24 @@ impl ExecuteCtx { let req_id = self .next_req_id .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let active_cpu_time_us = Arc::new(AtomicU64::new(0)); let span = info_span!("request", id = req_id); let _span = span.enter(); // Spawn a separate task to run the guest code. That allows _this_ method to return a response early // if the guest sends one, while the guest continues to run afterward within its task. - let guest_handle = tokio::task::spawn(self.run_guest(req, req_id, sender, local, remote)); + let guest_handle = tokio::task::spawn(CpuTimeTracking::new( + active_cpu_time_us.clone(), + self.run_guest( + req, + req_id, + sender, + local, + remote, + active_cpu_time_us.clone(), + ), + )); let resp = match receiver.await { Ok(resp) => (resp, None), @@ -430,6 +447,7 @@ impl ExecuteCtx { sender: Sender>, local: SocketAddr, remote: SocketAddr, + active_cpu_time_us: Arc, ) -> Result<(), ExecutionError> { info!("handling request {} {}", req.method(), req.uri()); let start_timestamp = Instant::now(); @@ -439,6 +457,7 @@ impl ExecuteCtx { sender, local, remote, + active_cpu_time_us, &self, self.backends.clone(), self.device_detection.clone(), @@ -588,6 +607,7 @@ impl ExecuteCtx { let (sender, receiver) = oneshot::channel(); let local = (Ipv4Addr::LOCALHOST, 80).into(); let remote = (Ipv4Addr::LOCALHOST, 0).into(); + let active_cpu_time_us = Arc::new(AtomicU64::new(0)); let session = Session::new( req_id, @@ -595,6 +615,7 @@ impl ExecuteCtx { sender, local, remote, + active_cpu_time_us.clone(), &self, self.backends.clone(), self.device_detection.clone(), @@ -639,7 +660,8 @@ impl ExecuteCtx { .map_err(ExecutionError::Typechecking)?; // Invoke the entrypoint function and collect its exit code - let result = main_func.call_async(&mut store, ()).await; + let result = + CpuTimeTracking::new(active_cpu_time_us, main_func.call_async(&mut store, ())).await; // If we collected a profile, write it to the file write_profile(&mut store, self.guest_profile_path.as_ref().as_ref()); @@ -712,3 +734,32 @@ fn configure_wasmtime( config } + +#[pin_project] +struct CpuTimeTracking { + #[pin] + future: F, + time_spent: Arc, +} + +impl CpuTimeTracking { + fn new(time_spent: Arc, future: F) -> Self { + CpuTimeTracking { future, time_spent } + } +} + +impl>> Future for CpuTimeTracking { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let me = self.project(); + + let start = Instant::now(); + let result = me.future.poll(cx); + // 2^64 microseconds is over half a million years, so I'm not terribly + // worried about this cast. + let runtime = start.elapsed().as_micros() as u64; + let _ = me.time_spent.fetch_add(runtime, Ordering::SeqCst); + result + } +} diff --git a/lib/src/linking.rs b/lib/src/linking.rs index f23bb6f5..e81bed4d 100644 --- a/lib/src/linking.rs +++ b/lib/src/linking.rs @@ -303,6 +303,7 @@ pub fn link_host_functions( wiggle_abi::fastly_uap::add_to_linker(linker, WasmCtx::session)?; wiggle_abi::fastly_async_io::add_to_linker(linker, WasmCtx::session)?; wiggle_abi::fastly_backend::add_to_linker(linker, WasmCtx::session)?; + wiggle_abi::fastly_compute_runtime::add_to_linker(linker, WasmCtx::session)?; link_legacy_aliases(linker)?; Ok(()) } diff --git a/lib/src/session.rs b/lib/src/session.rs index ab3b435d..b6378c04 100644 --- a/lib/src/session.rs +++ b/lib/src/session.rs @@ -12,6 +12,7 @@ use std::future::Future; use std::io::Write; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; +use std::sync::atomic::AtomicU64; use std::sync::{Arc, Mutex}; use { @@ -48,6 +49,8 @@ pub struct Session { downstream_client_addr: SocketAddr, /// The IP address and port that received this session. downstream_server_addr: SocketAddr, + /// The amount of time we've spent on this session in microseconds. + pub active_cpu_time_us: Arc, /// The compliance region that this request was received in. /// /// For now this is just always `"none"`, but we place the field in the session @@ -153,6 +156,7 @@ impl Session { resp_sender: Sender>, server_addr: SocketAddr, client_addr: SocketAddr, + active_cpu_time_us: Arc, ctx: &ExecuteCtx, backends: Arc, device_detection: Arc, @@ -179,6 +183,7 @@ impl Session { downstream_req_handle, downstream_req_body_handle, downstream_req_original_headers, + active_cpu_time_us, async_items, req_parts, resp_parts: PrimaryMap::new(), diff --git a/lib/src/wiggle_abi.rs b/lib/src/wiggle_abi.rs index dacc0ec5..bd76b3b0 100644 --- a/lib/src/wiggle_abi.rs +++ b/lib/src/wiggle_abi.rs @@ -51,6 +51,7 @@ macro_rules! multi_value_result { mod backend_impl; mod body_impl; mod cache; +mod compute_runtime; mod config_store; mod device_detection_impl; mod dictionary_impl; diff --git a/lib/src/wiggle_abi/compute_runtime.rs b/lib/src/wiggle_abi/compute_runtime.rs new file mode 100644 index 00000000..b672aaca --- /dev/null +++ b/lib/src/wiggle_abi/compute_runtime.rs @@ -0,0 +1,14 @@ +use crate::error::Error; +use crate::session::Session; +use crate::wiggle_abi::fastly_compute_runtime::FastlyComputeRuntime; +use std::sync::atomic::Ordering; +use wiggle::GuestMemory; + +impl FastlyComputeRuntime for Session { + fn get_vcpu_ms(&mut self, _memory: &mut GuestMemory<'_>) -> Result { + // we internally track microseconds, because our wasmtime tick length + // is too short for ms to work. but we want to shrink this to ms to + // try to minimize timing attacks. + Ok(self.active_cpu_time_us.load(Ordering::SeqCst) / 1000) + } +} diff --git a/lib/wit/deps/fastly/compute.wit b/lib/wit/deps/fastly/compute.wit index aed7bb04..efcad638 100644 --- a/lib/wit/deps/fastly/compute.wit +++ b/lib/wit/deps/fastly/compute.wit @@ -1111,6 +1111,14 @@ interface reactor { serve: func(req: request-handle, body: body-handle) -> result; } +interface compute-runtime { + use types.{error}; + + type vcpu-ms = u64; + + get-vcpu-ms: func() -> result; +} + world compute { import wasi:clocks/wall-clock@0.2.0; import wasi:clocks/monotonic-clock@0.2.0; @@ -1126,6 +1134,7 @@ world compute { import async-io; import backend; import cache; + import compute-runtime; import dictionary; import geo; import device-detection; diff --git a/test-fixtures/Cargo.lock b/test-fixtures/Cargo.lock index f861e605..0e17f73e 100644 --- a/test-fixtures/Cargo.lock +++ b/test-fixtures/Cargo.lock @@ -29,6 +29,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bytes" version = "1.6.1" @@ -50,6 +59,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "digest" version = "0.9.0" @@ -59,6 +78,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", +] + [[package]] name = "fastly" version = "0.10.2" @@ -76,7 +105,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sha2", + "sha2 0.9.9", "thiserror", "time", "url", @@ -138,6 +167,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "http" version = "1.1.0" @@ -277,13 +312,24 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "syn" version = "1.0.109" @@ -310,14 +356,17 @@ dependencies = [ name = "test-fixtures" version = "0.1.0" dependencies = [ + "anyhow", "base64", "bytes", "fastly", "fastly-shared", "fastly-sys", + "hex-literal", "http", "rustls-pemfile", "serde", + "sha2 0.10.8", ] [[package]] diff --git a/test-fixtures/Cargo.toml b/test-fixtures/Cargo.toml index 226b9546..fa143029 100644 --- a/test-fixtures/Cargo.toml +++ b/test-fixtures/Cargo.toml @@ -8,11 +8,14 @@ license = "Apache-2.0 WITH LLVM-exception" publish = false [dependencies] +anyhow = "1.0.86" base64 = "0.21.2" fastly = "0.10.1" fastly-shared = "0.10.1" fastly-sys = "0.10.1" +hex-literal = "0.4.1" bytes = "1.0.0" http = "1.1.0" rustls-pemfile = "1.0.3" serde = "1.0.114" +sha2 = "0.10.8" diff --git a/test-fixtures/src/bin/vcpu_time_test.rs b/test-fixtures/src/bin/vcpu_time_test.rs new file mode 100644 index 00000000..b3ba6e72 --- /dev/null +++ b/test-fixtures/src/bin/vcpu_time_test.rs @@ -0,0 +1,70 @@ +use anyhow::anyhow; +use fastly::{Error, Request, Response}; +use fastly_shared::FastlyStatus; +use hex_literal::hex; +use sha2::{Sha512, Digest}; +use std::time::Instant; + +#[link(wasm_import_module = "fastly_compute_runtime")] +extern "C" { + #[link_name = "get_vcpu_ms"] + pub fn get_vcpu_ms(ms_out: *mut u64) -> FastlyStatus; +} + +fn current_vcpu_ms() -> Result { + let mut vcpu_time = 0u64; + let vcpu_time_result = unsafe { get_vcpu_ms(&mut vcpu_time) }; + if vcpu_time_result != FastlyStatus::OK { + return Err(anyhow!("Got bad response from get_vcpu_ms: {:?}", vcpu_time_result)); + } + Ok(vcpu_time) +} + +fn test_that_waiting_for_servers_increases_only_wall_time(client_req: Request) -> Result<(), Error> { + let wall_initial_time = Instant::now(); + let vcpu_initial_time = current_vcpu_ms()?; + let Ok(_) = client_req.send("slow-server") else { + Response::from_status(500).send_to_client(); + return Ok(()); + }; + let wall_elapsed_time = wall_initial_time.elapsed().as_millis(); + let vcpu_final_time = current_vcpu_ms()?; + + assert!( (vcpu_final_time - vcpu_initial_time) < 1000 ); + assert!(wall_elapsed_time > 3000 ); + + Ok(()) +} + +fn test_that_computing_factorial_increases_vcpu_time() -> Result<(), Error> { + let vcpu_initial_time = current_vcpu_ms()?; + + let block = vec![0; 4096]; + let mut written = 0; + let mut hasher = Sha512::new(); + while written < (1024 * 1024 * 1024) { + hasher.update(&block); + written += block.len(); + } + let result = hasher.finalize(); + assert_eq!(result[..], hex!(" +c5041ae163cf0f65600acfe7f6a63f212101687 +d41a57a4e18ffd2a07a452cd8175b8f5a4868dd +2330bfe5ae123f18216bdbc9e0f80d131e64b94 +913a7b40bb5 +")[..]); + + let vcpu_final_time = current_vcpu_ms()?; + assert!(vcpu_final_time - vcpu_initial_time > 10000); + Ok(()) +} + +fn main() -> Result<(), Error> { + let client_req = Request::from_client(); + + test_that_waiting_for_servers_increases_only_wall_time(client_req)?; + test_that_computing_factorial_increases_vcpu_time()?; + + Response::from_status(200).send_to_client(); + Ok(()) +}