From 8598c87ef4e932a7c9f4a8c09689a8f48f74a0a3 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 27 Mar 2015 15:49:52 -0700 Subject: [PATCH] docs(bench press): add initial docs --- modules/benchpress/docs/index.md | 230 ++++++++++++++++++++ modules/benchpress/docs/marked_timeline.png | Bin 0 -> 22370 bytes modules/benchpress/docs/overview.svg | 4 + 3 files changed, 234 insertions(+) create mode 100644 modules/benchpress/docs/index.md create mode 100644 modules/benchpress/docs/marked_timeline.png create mode 100644 modules/benchpress/docs/overview.svg diff --git a/modules/benchpress/docs/index.md b/modules/benchpress/docs/index.md new file mode 100644 index 0000000000..ca682d1d96 --- /dev/null +++ b/modules/benchpress/docs/index.md @@ -0,0 +1,230 @@ +# Benchpress + +Benchpress is a framework for e2e performance tests. + +# Why? + +There are so called "micro benchmarks" that esentially use a stop watch in the browser to measure time +(e.g. via `performance.now()`). This approach is limited to time, and in some cases memory +(Chrome with special flags), as metric. It does not allow to measure: + +- rendering time: e.g. the time the browser spends to layout or paint elements. This can e.g. used to + test the performance impact of stylesheet changes. +- garbage collection: e.g. how long the browser paused script execution, and how much memory was collected. + This can be used to stabilize script execution time, as garbage collection times are usually very + unpredictable. This data can also be used to measure and improve memory usage of applications, + as the garbage collection amount directly affects garbage collection time. +- distinguish script execution time from waiting: e.g. to measure the client side only time that is spent + in a complex user interaction, ignoring backend calls. + +This kind of data is already available in the DevTools of modern browsers. However, there is no standard way to +use those tools in an automated way to measure web app performance, especially not across platforms. + +Benchpress tries to fill this gap, i.e. allow to access all kinds of performance metrics in an automated way. + + +# How it works + +Benchpress uses webdriver to read out the so called "performance log" of browsers. This contains all kinds of interesting +data, e.g. when a script started/ended executing, gc started/ended, the browser painted something to the screen, ... + +As browsers are different, benchpress has plugins to normalizes these events. + + +# Features + +* Provides a loop (so called "Sampler") that executes the benchmark multiple times +* Automatically waits/detects until the browser is "warm" +* Reporters provide a normalized way to store results: + - console reporter + - file reporter + - Google Big Query reporter (coming soon) +* Supports micro benchmarks as well via `console.time()` / `console.timeEnd()` + - `console.time()` / `console.timeEnd()` mark the timeline in the DevTools, so it makes sense + to use them in micro benchmark to visualize and understand them, with or without benchpress. + - running micro benchmarks in benchpress leverages the already existing reporters, + the sampler and the auto warmup feature of benchpress. + + +# Supported browsers + +* Chrome on all platforms +* Mobile Safari (iOS) +* Firefox (work in progress) + + +# How to write a benchmark + +A benchmark in benchpress is made by an application under test +and a benchmark driver. The application under test is the +actual application consisting of html/css/js that should be tests. +A benchmark driver is a webdriver test that interacts with the +application under test. + + +## A simple benchmark + +Let's assume we want to measure the script execution time, as well as the render time +that it takes to fill a container element with a complex html string. + +The application under test could look like this: + +``` +index.html: + + + +
+ +``` + +A benchmark driver could look like this: + +``` +// A runner contains the shared configuration +// and can be shared across multiple tests. +var runner = new Runner(...); + +driver.get('http://myserver/index.html'); + +var resetBtn = driver.findElement(By.id('reset')); +var fillBtn = driver.findElement(By.id('fill')); + +runner.sample({ + id: 'fillElement', + // Prepare is optional... + prepare: () { + resetBtn.click(); + }, + execute: () { + fillBtn.click(); + // Note: if fillBtn would use some asynchronous code, + // we would need to wait here for its end. + } +}); +``` + +## Measuring in the browser + +If the application under test would like to, it can measure on its own. +E.g. + +``` +index.html: + + + +``` + +When the `measure` button is clicked, it marks the timeline and creates 10000 elements. +It uses the special names `createElement*10000` to tell benchpress that the +time that was measured is for 10000 calls to createElement and that benchpress should +take the average for it. + +A test driver for this would look like this: + +```` +driver.get('.../index.html'); + +var measureBtn = driver.findElement(By.id('measure')); +runner.sample({ + id: 'createElement test', + microMetrics: { + 'createElement': 'time to create an element (ms)' + }, + execute: () { + measureBtn.click(); + } +}); +```` + +When looking into the DevTools Timeline, we see a marker as well: +![Marked Timeline](marked_timeline.png) + +# Best practices + +* Use normalized environments + - metrics that are dependent on the performance of the execution environment must be executed on a normalized machine + - e.g. a real mobile device whose cpu frequency is set to a fixed value + - e.g. a calibrated machine that does not run background jobs, has a fixed cpu frequency, ... + +* Use relative comparisons + - relative comparisons are less likely to change over time and help to interpret the results of benchmarks + - e.g. compare an example written using a ui framework against a hand coded example and track the ratio + +* Assert post-commit for commit ranges + - running benchmarks can take some time. Running them before every commit is usually too slow. + - when a regression is detected for a commit range, use bisection to find the problematic commit + +* Repeat benchmarks multiple times in a fresh window + - run the same benchmark multiple times in a fresh window and then take the minimal average value of each benchmark run + +* Use force gc with care + - forcing gc can skew the script execution time and gcTime numbers, + but might be needed to get stable gc time / gc amount numbers + +* Open a new window for every test + - browsers (e.g. chrome) might keep JIT statistics over page reloads and optimize pages differently depending on what has been loaded before + +# Detailed overview + +![Overview](overview.svg) + +Definitions: + +* valid sample: a sample that represents the world that should be measured in a good way. +* complete sample: sample of all measure values collected so far + +Components: + +* Runner + - contains a default configuration + - creates a new injector for every sample call, via which all other components are created + +* Sampler + - gets data from the metrics + - reports measure values immediately to the reporters + - loops until the validator is able to extract a valid sample out of the complete sample (see below). + - reports the valid sample and the complete sample to the reporters + +* Metric + - gets measure values from the browser + - e.g. reads out performance logs, DOM values, JavaScript values + +* Validator + - extracts a valid sample out of the complete sample of all measure values. + - e.g. wait until there are 10 samples and take them as valid sample (would include warmup time) + - e.g. wait until the regression slope for the metric `scriptTime` through the last 10 measure values is >=0, i.e. the values for the `scriptTime` metric are no more decreasing + +* Reporter + - reports measure values, the valid sample and the complete sample to backends + - e.g. a reporter that prints to the console, a reporter that reports values into Google BigQuery, ... + +* WebDriverAdapter + - abstraction over the used web driver client + - one implementation for every webdriver client + E.g. one for selenium-webdriver Node.js module, dart async webdriver, dart sync webdriver, ... + +* WebDriverExtension + - implements additional methods that are standardized in the webdriver protocol using the WebDriverAdapter + - provides functionality like force gc, read out performance logs in a normalized format + - one implementation per browser, e.g. one for Chrome, one for mobile Safari, one for Firefox + + diff --git a/modules/benchpress/docs/marked_timeline.png b/modules/benchpress/docs/marked_timeline.png new file mode 100644 index 0000000000000000000000000000000000000000..6bec9d083c98453b32489645d9a6fba429f00357 GIT binary patch literal 22370 zcmc$^gLh>?vj=)&+qP}n*2H!)v2A-|+nU%uv2ABE@dOk5Wxjjgy?3qm2fV$`I;;2Y zuBxs^byfX3N?B0~0Tve)001D!NQ|a`Ek2yLF z9WeqtS(9nxAp`IDiWZx+$0LbYagF5!vqz=YFpaq|)1zv{BuDyl?(X1WuEf^3Z;1Sp zJ_qD2ik7I7GwFY4_7JlrhWUAF2eTjjJk< znNos>$zApui?_(6LyXKa*GS`XPGfUfm(MTI#w?fgnqgIooZIE(M51KTV&P@J|$5Kso^M>7&GZ`0~`P?6jLV1xQusy67m_w85?d)S<$JLOEVlVfFR7GkZrztK6~E$ z#M_mw6VDG#AeK-nU{skI7n?1XEj%b{nB3Hv86pF93S zV2jTQ(FNLtqZ?&6;BNBTK(+>hk60efHrR0>>Li~7tr@r(!WYRGgV!H-Ah<8%Y1hrM zgG7{Y{Ok6b;z{xi=MC_IO;?-urz_LU+{bd^yofgmTeW~fYY&2Wukmw%DhAkI$e8IKt=`HSgH(2?w$@D?vx zh^@*`eS?l-4p$oFocA~5uS__tDaqgubI zeY`Z)VXDaa$Ue*Iz)I9wpgq+#)@0S>DWDl(|F-qV#plSst+`3Qsiwu(sMH9D!Yyu`8S2YK?CfcSR8}fe|HX0`Hw)f?J!hX&G{9|HN{6(D2g`>~N+q4hA<2*eSi zp=sf~S$AD~9sQ-4@$s>{$)1cOMWBUB7|=smMLj|VVcB3O;a}ZA+(p)c|3WAY)&e!d zAH|i)(j?g>eIxIpw{V|{C%9$tmoXUddZ;@|UQA<^=pZgY(0>)Zy+NvsW1?8W@(0NoVNo7PphxNLrLLPiIXhlXOq%qb%D6>GAIps8`gU&?{o1Fg#ggL{^cPtvx z@oA2Rh9{~U$5vf;uZQxSlN{5@!}ObUeO>!b-^IU{9sV7CHNtg9wI@22P0rgB`|5es zRhnYeXqq0i^Sa4>7Ou6Q8!v7{Zk6XhkF}35j(ZG=ntI6$*#hi7#*{vUI55W0q2VrV z9oLmxPuzZ$528 zwskqwF{LrhFc7YzbQil(`C0DIJa(mBcIuhvsvsE8xcXba621#;KKL^HO50|9 zq;;sAt62MX(~zXkQ@?8J;F4;guCko&*X{SkxLVf+-bVJp^wIJvKnG8M^ET!e69Uu{ zB09m7#%LX06;j=my`zcUT`W7FB)GlRKpACePbX^cPC?4##HPS2tD>5}>J zBvl=^fpypSfR_8L?H07vKR@$NTCVJR_8p6RW-IH80}MVhK^}u#xcux5x`*4{?%l`! zd<;|!ba`NI<8)5;zP=f3^l&{kT`37_y&~V%Ung!ACynAJ*!Z(O*P4&{pWM~YUbj%8 zC=tfiOB#IS-M_BRzm=zyQ_ME!bMUwM`+uKusJR*jVihw%Fi{y|ypeoL4cq)$9sRs} zy4~=+csjw`&#r9C-mUDd_HlUawIQ`VxQVz!N+yC6s`Rh=ym;z3mYYhqOy)6kGL-Af z^gVwfynEmO_Og@`NMojW3`$)RmYf9e)P(|2AdX|a0Ej214u=*o=i~_!nD0DN$3BPI zyqe4a<0Qg%Q~(ue1j+9dq&PJY!XT#9SS+dM)F4kr*d3V zHTei-v1c_@Xc!Wb5>ZrCQCg0|P+V^=Blxwo5d=IeN3KhW*5y|j531acT@SjtJf(7f z393J@Ph?j;ZG3#Uo9pYD+kUU2Rn^r9XR6TD;OL+tF~U}J&Ofm=|MP|mIuT7Hp!`)1 z#se`Q4(@tKe}n}I3v4uzg+d2~4ce0w)^6Cd7aHyn6(%XD$yoTR6D4j(QP!Yu-&As{ zL;fPAD|7v-Lx#2oPr-n@d{En>`x5iTmf-(iwnnbp_^=RP-4!N5?uN!rXVk~--lChq z0R7TWIP!(CR2B4}E)^)sw}K<`#uxKrdb@LViYx1<(W7?{ecsxt&+XdDzwi7SD>jfC6Phb=m_ zZfo}N5mK$X0{DJuu+!N9<%sHlh% zS=oeQ?6GS?MyWr_%w;wa(6q!chw&R zb};|({t6;876ToF#Uu&l8M=q1@0!>?yZgA~gM_dr_`d(J>5$KA4$BqrvY1aO;Qh@| zLUW-$;=`-j@3i#a;;_cm(r>ar5ZXtWx9`4@)X^Io-arx1yYBXRyqsj})7z2)X|>;; z@Du;INeG}1?K%J1g-FQ1h5>SKZmqqcDO}sPd&;i!GK^&yN$K(<=XHco3iq|^fh(M&%)6_dWJ-q*|h!3ef-T0d%W{d%K;PPcPR z2Axn^+x13>LGnL0cn;kS){B%p-{&JfzjO@zb+%l6)paFUL*ij}WS^12W{EhxRjx#I z>Y3z!lxb<@AfCZ#hg${zl5{KdwvR;QdksPf@6vy*NM?*H-F?5Jo8fRVbDSTbxHGgXewc_e)Imqeo2niA8;S65(efrldm*@LWkd@%ETOk8` z?-t~!A!QTl@4!Qco6a>@&oT!J+rK7!U+;`+%FtumjVR!aMbvoXL~Ar|XHb^4RqBazq4o8fhmu0l)f&E^fO+-}}SYR1mS4fYTQqyp;-(Q&Ps`^Ca2E zTA-WNBx@uL^?lqkT>xhTK3rnS$TkbbBIamm!w_!+l#QnPPRp{(6mz3#%i<%YZ7s!|AdDDIncT%ihckW4t)&t~hsiPKCDl01^ zBzkj9!-wEDPokhaXsO!k}-CAlvHopP79vbi2cm3gX#g1Y|vtrUf5|iOXH(IVz z7Y>TKYwLSQFeO>P_6D6UaWYmHf&$$ng)N(RQkb(;VL}M{b0n(uW|)#wR?c^e4vSvs zQ*`X*<>d@L8iiK8YWVr0iT*J!MKrAYxm@QTb|(0DjI4si{PY4I(g=}wv&{jEv#_H? z$9($A`%&?>(@9xnP==j~<`XH(_!pJAfrGQW8<^u3Yz zAr7|Vd#m6YR)}N{Ebu&>i{WCSuwTOFk1R_}p$wGiWDLAUd$X=Yz~h|r^<3@mby;o!04T*?9Mjghaq&W%|N}os18^Y7i`XZ88ZR#D|QaUx_i(`k9Q_W6c+ z_Yy(p!!gTzzwiq!`R~TpMpYnlcMlTE*Dt$A6S3}2C<6Tq0&-)bQ_@!VUq+E|Q{l4| z>EX{N)+{c<5EzEu05fpm;D9Qn@Aa~aD$f%X;$!Ercl0R~sOLOR;fgHW|6u%k-tmQA zAA4BXo$LMw5Yng(eTywFSSlJ8NUfoFG8BR&7%5``Z6ZvWaEMJDnwA_#*ywVsXrUh2 zQ38*|e+~E}n=ahZuyto<3j!USKF3opHVk7+p`g7Y1mlYJ+Y}!Y0Y;OW-FbjvkcNaD zGPfUxO&qe5lz}Q1%al6=^*x`oF~-3Av{X~y4PzrDI~)%<7E2^Gm<+W=4RnmymTCaE z|439>0o~A|(}P5%=B2-h1V=!a%`kdn8o`UjD(*qnh=e?DPi`(YSOWorWJdh8#5Sgq zbD5Z&Or`;5Whg_mxt~uW8%=}eC|5ItJS+@>_m+|%O|RFEOcsVVI!93;j1l(m7tL>E zyP)SzpFlTI@YfhKTNqEV6!SN?J)22NPT?qWSyv&WgZt_6=;3I`(9@w6 z9wrODCHJSy!N6oR^vXN;e15!Eyrvua%TP&SWRMLBh`@_VpyNW1ks%kFvON`0FC>H+ z&w+4U{)kA7TPZ!Cctf{bD{n(HFuoU=6JqUL2L6z^GPk(PahWkT!xTYlM8-lUV%cMM zEX3Rc99z+TXW#{U`_;L)Z;n?hJqxPsC}V|7N`_78Jj7|7dpanpHE#eN!-EMDV}$w}U%9x#dZ3$*zE~8Wi%HL9!Q-HpJinwsR60KMXiD?ff0P0 zJR%Ae4v!=HD%*JH2ta>{eGJ0*@gh$s>hQvEFxzpQH#v6v$s_J*ag;6t508_Uo{@ej z`{A}1RxH3Hee{z8$PlKPSJ|WS1D^TPDUsGVK88bws3%* z(?T^wg9 zLpU(AT;J;uQk)Lo1k3RT8=-)VYE@@^cUTH~n0x9;tc#S}8aJx1nI)&V_l~O>Kd~~B zuU`~})W!sg5`7Oj5vF?HpVAM<=vpgt+f>+|%CtX*FAxsEF@+<$G83FT30dEaNW9oC z9YL=bn;9AF3f>V&99sGX#bkmz14$kmR^ca~XomE})vsfJmuB*^r%Q#3YTHs26TDZs zs5bYZlQORngT9f~MVe+bqc&v@iL#}`i-D7zEs|Qq>9CT;O)rb!(g7D3 z^8MwNh-N$xQIJh~V)mL1qY4ctKE*#4xk)A{IaNLyG;DeqzeBD#1XHw36KUAbi;D?c zsbv6yYhN^E+mAw)O0HPfZxM4e(Aj|cc>Z;`CIq~ZaXFcv=ChUyia1#B6zwUZg=>Uh6NpP|x|rk!ZvuELf=TM_hfzSHZms9Z`DM04r+#00^NiDuq>m+4p10`LzC4itsMae4UJC<85SK z=rL4S{Zy26M2TdlWUHuajrM$MhTJR}#tmowWLogNg<7Jr;p7L6itvd7US-Ry-3yJ08Jk z(PX&1`x;n4)>xN5G|F)32(Sn=4VSj?IeOA1w|}p^bFs9p)GsP9apn zOa_1CWR*V{O5n&*PPZRJQz|Tsdl(0W=6t0WT8;tqx?hWh#>)uJzsV#BLv7`fh>o%v z&KBsPL0@k&vGHy(Y!(-!a8jG$@OTNOp{U;Vl@pl5Lw0O4Y4`OE?-6mPF|A9&GX9FT z8&iFJgYzbre+@6eMN+~gZr5h0Itj=Nux@X<_DFFSp~?~yU!-ATSy!eVZAQSX)OrFE!d?GK1{$~v`sOH>UgbgS}xk^zH`{w0Yn|w5IjockvelZ-FwJY&g;5;qOg#Rd_mfz7QS8seW%4W20m2ScPI9cN6#{a!Tx+ zWr+$S-KRiITID?mYEIo^D7}k!OF4E^T-^<3pKhvQJdBd{=#dd`#%6KVy=iXuvtNrh z3&L{fo-kmr#E1~KY(6&>5VALr^x4}K5S;8L_WMCt9D6~kcm13+#3VybdWB5Xdq%J{ z;1DFlb!q^7_!F-Jbp8_>VOT-x0OE&jGU%At92o>4 z2 z%Au1Z3WGZdKum#L+-MTR=pje2rXPZ&S)l}#6a>2J5zmrMh$1T@WB$BD?~jvOVx(3^ zVTXYDZXE@>PL4!KQ_zNtGEMFY5E}r)7zoJ&<*hjmV&{*vK;;)kFrJru5~(W?euafK z8iwfRFVg%A-Q!#gK&c5T0GM-wz`7=|Gxgs(qxy=*=~$5&kuz+-#gR@!XnSWWQ^BLJ zhqkI!p7=!ung|d08Nj=GFF>SNA@$1?_T$2Zg$G4;BNg-yNcc0(r6ib%pk`&+cYR35DtfE5Ro_E#@;+r_+2;A!Q|4QYW0d1mGK_TM4%-zMM2Vq;>Xdhx`2Ff^WfhDTPUDrX5GIplRcBKO+epO8;sKoxBK*Hb=(vsA}R{1x^^8dtQg%-#tE++TJ! z1MAlsyxD$@V2)Y5|1gWBv_3Z@?Q$a`p!bAxa6tR(?p$VJ)9lD_e&NBS``#hoemI(0LItr@LNuO=~lhp=(mJEtm~hDrae7B07x} z>X}%@PgC~d z49bXm&I2FD4`>U&QogPbL4@|hSz?1r*eqm-Sb_|{LYfs0&4l0@$jkMjuGI|FN&!(G+2U3CIO-$On!!#Ti()_6!1B(Hw13 zpn@zt64=y!Eb*Gdo4}xj(G2gJ^=F_;+Lmz>1ln5$NgIx=jI5z-**eW&RN0bf{df++ zE{?KODYz7cH$Uhd_lSR4_Nm~(wIx&Zmgz3!Xq!nD;ouMuA4{l#H8e;*8sz}x+-Zr` zLOO#7pSqje9lp_k27%}j1M0qUAXpXMG8@#RV7ysC7dG>3;nk#;3kD*}*f<2CXu`84 zKGnMl`Ggu&U*>1d5#x8(uLMr4{Fri>YX##$ekqB)^esq2~ zUN%2nRbnth&S2A{yn@q2&;|^@jM-A*WNRAETv=F?BfNtr!C+8UQ^P%w-3VL>_%%w1 zNVe1&RFXz$*zSWsbmcvi3le&E82ARJ8j`=?{st-+)Rgy!!}uCa;Vco;#S&kjoS_|> z?nNR~Kscf?Sw>zF!Vne+(*TSw;UfbfDzeoIFfobvRgF@!$_wJqzs`m?zQ|;6_$ za#AEa5L4+O0Sta4cCR2X*!m!Jd8I(%2xs*G)o2%1QtMo zDDDmWqlobv0pZgm87^*1Aq+Xvo-mzqVo9LCD#v3cb{MGY94s|Y;|YzUA^eYEP-$S* zUQh@T;OqQ~1Z&(YMlL>lrghQ&JW+*BTR3KplQ5^v51c8WLoC}6>#i03t z8w^e@9n?qy!9MSJR>Z9uh?h}3D_vL)K$K^JgAG*DzJp>e9GPPM4o0(Z7{rPT>L6NL z7>f|Z8rYB5(vLTFt5(6D(jadN411oB0OXoo@Sp!q!u=97eIU} z8J$+}9jBpFKwany90{5snKRuL295*Y;F5#j!XXl^r;v;3gc=^nvEzrxBvEa16ge7%45KK3KurLN8YNZ%Zzg zF2A_K-uzY>AileJE+$M}aDBovNRD5W46-kB0NA)D7AYf|M+S!#M{2kARBA^t3pf4? z9ROrN*hu;Q8|4q(30WmdzQTr3;2sDfPZo*8!yY*B04YS2DN)n*>V)EhIG!y!eyrI6 zX~mcZH#Z$SZRAhhU(~;7qt}VDh{FS-GOc{M{gev~IE_Np_$PL@p8YpRLcFiVV z`QGGEaAf(hdq@VU@05r*)jl%SWK3)=Zx|vGws(11#NUR#v&p(C?GBySFM4IN7)72@ zXy6&&42QDf-D|*?r&zN4GBub$$1q$`=qR=g2<+G8Y~6TEooRq)E(5&+fPQC<;W+-GQNvp8_27nOh6b9S-7X?X)KgGN*g6-EKvHw#%x2$l$~ve!Wu0{nvjE5Flc1pT~Zq>FF^K7Ah5e@ zQIshnn)=?VqWK@<=KF;UV*1*Zacg{Gi+9i!k*W#dvj2cVL6N|dJgjCv(G4Tz-i(Am zb+dmx6Z!`(<_Bsl|8uJaMTI3&1VJ?t)xT)7kd5M!$Rs_fa&+<8kG4b1QZQ^lbxOaS zZxAAqet#DE4DLnI3*1{13EHAN(v0Yof6@W}IuDprPlXNI2?u1~^9-nfCm9IcOb%82r|rI;K}B!QcZ3MMJFj2@Y){^Fi16)9UqsAvHc@!VkT z+6&mI!K*jlwgLO{l*l_HAs2Zc(7U;Ym5XEG^N*(Hm9NDx7ghpM5+e``9C|mp)0WaWO!9C5hW> z*SI2XPkUCCh;p&5?O|wMZuKn{aetsk7!D3m5yD?$CsXhWm6aCh_v{)mcQ+ZA^K5x; z{P<8Qi}c{UghfK&`KEd^JOg&GIM`9_V!}{1^^_q;1+U+3UUwLwN@TZ=hl2Ui|CZ^i zcRf#GXd2Vd*AoBI#&ww^WHC2XBZ!Ac7~rt@^Uw;;*-k*G{buimC`XWlis$2ZT`S{` z(_Rvc*v%fM{dPo~`zHij<;xAzN`T+qT1kyyVvhw)adlkzgbxOYAbw_UF2m8Vmh?TX zotROLmWujxQSIx>1z#|c)2sEb2y=3#O)q8v&X-NHXNc5gE=8h{n46EX?dnHD!puxr zDy8UFWPFC5-p?kxSD4BfTtUanu?roa?bqyHJ`IEWgxM7XL8rCr*u$cUtn^j;ZB1qG zhsdC&2!vQp8=^1^LcST3XjWrketGNTbj`jF6{D ztFgL@-rLxy{gHYs4ZE^yjO1{H^JIBnkM3^Vd}zbd8e?tCgt2(1RGpS%Y4`8IE(YD8Dc7kdd*ey$neXtaYpK zJQ+%)Y9zDR-tPN4MkcT)3%Y2nBoVy2s2(ufur!jUDnmaZfN(n86zUSGV*%(S>-;_E zalKaw%9ScyIQNu|4F68h?tUJ1jf1f%5+!{h#HdQc89&mv> zZDsAd&-UkvJwaApMskvL!8bb(BR^I&6KK587SHY2uib&2{CZHukP%qPlZk&Xv*cc1 z(ml|B@q>hn5f_cPkiKn2u)}w8YT$jADED#CtT7pB@`RWN{fWb&UQ;0Z z>>W3|xb^4Ht?zg#S^Y{A(ZvcVt=%Kxd-EHxxfWe5>M}}_dWLmhHa{3W2%y!l4N;1K z5N=Z*J9G#ADeZc&7y;j(+P_U*QGb-KnakoO{@s=x5;P))0kq)=IxpB+y;fiZ-PmPm zQRJ(S7fi9ZiHX2w6l1KVj3%Xo7@SQZy8x&4Cy+QiEd#xtu;8`2{Ar%j0FxZsDzM?- zcbA2JNvsE^qX^P`%6j3JLuwb3+f7R2GI66+yvpC)f8EVbv7O-pTx|Xu0Cn&@-)*ONg0?O&ICJOH7PqlU}bbkG(*{68IV5 z?JQqnHpnoL#wd?8PzQ)Ot!1sQ-8kyUQ!pyqZD9@E9$X<{ti)gD!oX+tIuKO9uB&gP z+%PL@CpW3Ci-^P^rP7set6En27o*f~{-L0tI1rFU#5HXS!=i%?#25rG8I_oClVHLC z?)k&x{J<{UEOL^wcMDIZahvZRF8Z@dM-T(XV-ezB>y&WlYOSuLm&yc@?=`EKN_ zC!pV27*z3R^;q#L9lN?t@vsnjiO}FKmHxny!D>V*^A{<{LqVKhY=aeMQ1j7@RbJeH zhUVw0QnsuycY7wbos37Ao2M$bKkz;X>`(cmqN!CR)VPdUxr+6^D88%_xN$xYf7vpr z!v347kG)f>I(^51k#Rd@*^opi4#EA5YIWQeN;q>>p!$~XT8W0t5IQDhB=qOcu&iaf zu)sRxJ@xCiH{XWEV6TRPHj}$fplo$LmiLsRt9yurAAI=hks^kT9qDYNenf`p0VN`0 zSyhJ&P)jRz#D^&>33rj*qrI+P-Y`rz3)ku0pTz^0{WlrENVZrL@c|-a^*nU$7|5qZ z@y+9{<-U4uyJZ17bhg3kjD@`l-`(T-`rhw>wuv2dPJdp z5Gw=$6GZEhJS&{;a*(T#VkeW60J98*Ni`A_|wQCuOD|^Gpb>1&is+1_tg;@L`f#%;4HBJ+v-LX z3(1!m5bka^dE9Tr#Z!|@K_$SG`(dc9fKtKoM?p~=3(j|q`snYvZ7CBeo2R@oio5&o z%fXbYMjkT>LN+oTh^yP>Wv*p!XF(0ZNgEbcMeIGIG&rgl^)V(7qM5qpM)*d@^PK@} zm>LU{MLN5oDv5%Ii>{D2g@n;!4#mmSuIGBp=U|x00{Pn};izK?*H|lhaVzoG3-e2Q z#)U&?3Z*0AcU9;oLJ*5br-1{#bPuk}K_39=L<;ugG~1})`)^8mMEog&Ocz8ngU(9{ zXs^x6UwJQovtO}l3N&xgux!7(A9cP<5j{CV^dvGsIMnPWBAd@B#*N-T-BQ`uXR%n= z_BDTI^E|9$>>3e~#ME|}udH3yGgI^5{^lymL@l0roXR9E?|Akayqjn8Yx%Qi7bo%I zu%hLBuov0kc)+wpdiof}ezOtBHp1Wc{nm2xF(%qRk0(4-u)G2>Qw!YF_&%-WHU6wE zB&z&vc&1Ugo~jR=GGD6|#NXd{#Z0ezoeYvS{d1108m2juF()NeL~p#$MiB?3Wwmby zgt9H4*MK`DWD#u^JMVr$Q}<>}OSRo|1RI~FDts)laUw!UA1qM~>e2EyI8mIk-pWr@ zSdc!Fe#MxrEj=Rj;{-Ma{$2#dKJz20trnkZiuH_@frEN88u=XRgN;VQqErO9f!=oe zmBs3?S`?vY3X3k?BKpg84Q;YaJ6O2l{0Njtj!o5FStk`0AKCr*#7rI6@J~>PLOpRBk%WUn1?uT_{yXYm*!qOT$ zwjBZ_{xp=^4?RbXn`$>b`?)R^XL)a>@1iCinJp89315@W?eE}q7t#K^R~IBqqO7mq zV>wGm)Pq16xc)A@Uvc)&hxaB24Z0f!^_MN0Ug%Pku{}{JC&wi~G-cwWXb==GCx_f( zeKBpCvNJnFl&wcNxDB{ zF@jLykmW3<(`A$KgtHTqUOlw_RQyvoya8u{+A1g;M~?Zu2tSX8K^G3HRKG3+_S-;% zI~z>dh*FFG3#T3>1GN@BjxS%Av0Roo>aSGae&yolUy_#pB!K#EhxjO4aerG!*T*;X z8o5FC(Rs{Wh_H#Y^m9$myw=W}ud1T^!wMx^PM6lHsN}`)E6U==0dczgX!|L5(Z>Dk zmG1vM8_lr!&F$F%X{yJv?&=!x z-xx)}&f)MU9-h zSvX~N(GItPpMDw(LJTGojx%9M`|J@^y9~Sz-dp{GC`3PL1FThKL}PC*>D0mzj3gKi zw||>wRBcDt7#gbb`a2#n+$N)$0tx-Pi2Ir^cRulZ?wX%&zg(6qJOoU_MZ6ffx*URh z^l}-G?vl$mic;}JH#da79umKUxw2pDRW`Xc9!q6IQbwv#_*gYHfr6t3z5(_N66Kr( zWzY_@etJo%g#H`t8EjPIeQn*gpYzmfd^i5(33IAnQK zFxqj%ll^hwhJaV=6`~Vy9LWOu%h_?M`SLygc59#eVjk{gcSrNZA9M4*PC>hek_@4q z6s2}4PtU!K_qJKW?e4atm1mhZ{l3A8On$aeanlaQ92_BgWF{2O4taGBMS7LKCC3_h zC$D!UqRCCKv4<|a#ee{p{MZ!7o>dJ~m!EHCu)5*;<)@inrxg>MZ6k8DivjrDomJgHgvsYW_tQ4-~Qp$#6)yloL!UjE8Ej- zG{1~WsT)@R2pxASKj=Y)GQ>x-vg<58mFz^V1vob#b_bl_(BH?DSz`SN6NRoA&xP>KkgB?Ta^vs19koy3smD{p|~2@ zgW9UvT(sp;0$i$R<^QG;ALJoMU0pp&0Ml2d#4G&g*3E7=ww?)DJUaJZIec3aZjBm= zFfz!jsj?p!yBaJ`glIS;Qe;#}alZb>H?8j+94#}05Qy}X)EcCvLPJH`1sZp4fxYKg45{b_WiArPVd3151$+LUf|t>jq(F0 zF;Q&;WIl_Vdx<@#cGnBCd_(5HwTEn}PIsH%)?FI0lHb0MEt^L%R>tfiMsxSYg%|Jx@15iryhX*0DncJ;Hsz*0i z+zA>b<@3EtitYbtR!g>b-!`DXn_z#@E3gvX5?}cRG<=zCG>{w4_Jo=3$0&@L6Slg- zoin9aUd{B5nu)2M<>bERB_34~l?fGnxO~diqPyV8_5q9ICJ*RImMD>VUVXEq4kI;* zmM8}xlyv@i4hzq(*D~E=7qqgq>5jWg*Tz8+Id6Syeo`ih?`Yr;B2h&ZuKl_-b(t(OKh%slD zv1bm<6h5Kh4k4D7mXj`z0!OEJ3WnMZifkpY|HefC3B>Kvf7V8gBJpdB?QwvE?#0|7 z{_x~bMGIyq+mQ6F8wVrV$u$$wTs^L{L$p-U7H}Y7!6U>Xz%{<>W(o)1{H&{Ax$X2fu?5VyifJ0xRFw%%;A3K8;FiTG@hn!>H0G^t z7tt$hHt~$a4<~ba*lK@ z#di@+($UtTH4aOJ33w?9?MQx?_&^jWA|ZOZx?!V~R3gHe-jpDqrDGE_yH7il+Myld zkIK&2=-bWYaSe=Mc$IGc%&bR^HOpM{fdvwxMmoiUGF$eBD9v&aZ zB_*g!w=jmH5 zPi^aqfYgPnPr{6r7Pf;dM-<8APGAbt9B4qn`WG2q-$NcVmgG}D` zzRZKq_lk^x>o=T+8!CAvA7QJ!i1eSm+q#pR7k|<9UO=W|$6JO**{vOKg9SUA>?7O; zCK~Z(6%HyW64bAwvF#&gK5RND;p1{HcoDFDZjRFvt|psU2-|Z(juW#q?VTGJ-HyrM zE~>sYyFPQNHYlu$WfGmdo^>ZA_~Ki}FU$cZI} zE7Am@5)wM^a#%m`QhmRDIcS8fzJ0T9z$W%T$S3`^+=O|0g09 z-Rhg~eK3H~A!DM#$iH+`B}Yi-7dr^wZo4q$@plU4(beZpjoY}(O2~%Vp=yj&e}BCo zufV~0a`Smf8OafhFwcEd%+F8ORxVv%Hh*Tqm1iYNR|S3;bs4xuWSi%oup@*L^n4z1 zgzQU;N*ArIpcmbEUef6>$X?lbwz4SWXR&LtMJ`0ZIw0JQQBvW*G{;_nSJ@w z(l0htBIf-!_8HXIwCzAGj^2ec;?qtzwgsh*T6;ZE^Y*CV5Km$E5&NvMvGj^Z-!49T zQtY6<5oOiRr#~oS$i>qk!wVl5&z%}OkTc-gdIyY12Mk~O1a~jw2Nx%EXeB~{{DdKz zTMEbn`b5ka9UW;3d3A9aul>KbqKvTm;Bg0rY|^mEr5md_NxZPsF>hMj_hu)61BWkh z9tn2ppRX6-1z&x3B1dD!L^krl*(O+FD5EipzN&n2Q3(UpZ5Jj_7!pZy>^@@SSh{a^ z*Nhuvx&5MK&g0#_zlM`98w{beCgR_AVbbs{%bQEeFpHe!OMJ>1C+{sUr;41ujR)Q} zZ%3-*GZErGXQ?wf!h~>&@T@sQc>4hx$Fy*Yy2cp8va{jq?N+o!Q$N18wE=ue>Hjn| zwy$%cnZ+C$X$VE_sRA&GUWK|6&r)M-bXatx<_h3&l~y*FK)b_)271VWqx?nxfeZUc zuJA-LRhzXrEH0r%C%a0W~_;L!zqA_NQ5!h{p5%GB+ zo(~krA(tYU06YbzagOi;Rfh{)xYUplx%6ezmpwsNRT1ZIO`tcPhglzx$ zplC#vdB&J%x)v5;)#k$M<|fwK8aT}yqlO)%*Ogb*Kk-h{$ZX57Ec2qZl^?7uyBR<# zozE;Nj=U^n*Z|853ri5;duJukd`O3-sj@lwih{*#oa zp;0qNMZdPB6nBzyu^2@-wS^|Y)71NJ6lcy$sZL|pzsUnUDR*D&7re-+zX5L(SQSX=Z`Xn^f#kkI}g~l z?z7=j*z64q#YoDqFM1k=n18Zb;wK?e45E?Hk$V|QYO|NVXu7U6KA9rliw z)bdek6Llu00f`t^9r)40ddg%5H+&YBi`wTk| z+7K{Y`%+Bdp#EkKR>H2W+M>-Z0)nH?i4oz@B;L}er!g~)fu=4I_&e(jL;IVtp4l1Z z$-^T#cIj}w1BPGutOPF%CwJX;@QEQh!z^-e6Q)JlEDo*XgecrzamS@gm7Je`?VQA+ z11t=&55HExNQ~o&YK_XaSRz6>L*!t(r7663fe!+VV|;PXo&r z3%{zs50VZtbzMI<>CB1I%`k4)jW?E*@sF&yds9c?Pw_w~(-bakAI7>i~m5S);jr|{2DPF3TMmYcOI z-7;>hB@BljDMA`Tw25sOxl2xs`@@ShKY1(ybYe!8}j1IO`wux>0b;$mZbh3Us^y|b+B-R0#BArxmaK@h=w zBP>2bSVIVLG)=(rTv0_mv-{_q5(^P03#`Uaj=g;C^Aq(lV;vI0^bp?G4Szbs%=(pQ z#;3%G6Z!Q?HnJoKVo5Q^q_}XDjR=}{KU$<6mWkoxcoKXlrXc4{jdjyQjFB{9eD&)6 z73IvkGbl20AYfjB{mR)1*slj)JicgMg=QLT3@V}8mmFLd|HCIPGf}pe8COBiBvmB{jwk4Gl$Q_Q&5XV*U~71dX|;#9Tcm@!)YgjwBPWgZi0?yPscJ z!Wr&UM>a;mZPXSF=d%aKiV-c+ebFj$q0?bu+T;zb%e07ei+HN7G3%OD3^EL=69%Kr zt`2>o5wz}o(hbe=sn_NUv>Y6SU0zj3MS_}$UQZM~>3_ymh8+gpF}acHi6>ZeO?l8y z7@A)G%>ZcL`|qPC6>H%Kox~+8Oz=%KA11*F=0$LIxv5qUQnEqVPZ~X z>Bg{E7L`&LlfI(&N>UyZ*cT?(ZkeAnXL78|Db{i^+T)&lu(CYA)N$491lHOi9G}FUq$8=|==42V_3|hTRah@|p zI~-W@hRYgSCffi23Z_X!K~(niz5O=qu;L6dfpz8tZK+PGL}iVW;1N~AH^Jv!z14~b z#H0`3#3I{^3rjv;Q^7}PBsCJuqhU9x;>&NU7Okti0-F!Nb9$kmN5%Hc2FdP zYTvX7F=vTh_DjX}XeAdd_+j27_n8+i|!q(uvCU_uD+`O-5NIz3Tv~k>E3*QN4P6t=3qN<*KEfAkP+_Gu6 zb#zW79|I-QXQhJipD!w4jPQLhql_hWjtDm5u`>Wmz+w(_@Re3GAW(093CkKanA$_f z9Y=~BM0|)tK^BOkUHNfvFpeLrDo={X9z|h#@XE0!dym>^8g^;O2pU7Ma&vW`BvaIw zFy@15Z1q`b5jc8GX0xAz@qkul{}+0~6dV5}Q2GR>gv9|G5v$EYGfk42su95z{cK$o z)0aRoAU&Ko@#;AVt`A2vmS%{>uwp*&AK7(LWvwTQ2S6s#_@}QzrkP1Fn?hL=%yz*} z5XvXPm}>B>G_qJb5-^4@Nej{_%wupCJW)KjeY>X?(dbH~iO1PZ2A+8FC_LdvxG|I& zJytWB0f6~-)Dy5Q{`SROb6)Oeu1*;d16&WFL4&GJtvWV zg_Yr4R-%JP?#je?$)l^WJ0fhP_!;A(ODpOhdL^HE9|jo?zh>f@9+u5JnrQrB z%YE0T{r%N~TOU3w@}sPTT9=#_&jby@7ehTO(+@mI7X5YVy!W%VY#@|Dut{(6|FEchTPV*q0$nj;7x$i=hb>*@^8epvMID;l4GIcHL=QQHdU z`NEs#;PuDfDO#}dAUUu&hz7U%+}Z@i2?Clw$lh0NDgMMQeby6c9?qT|%f}B<-3MRN zIB~hjSYiX3%rt*;d@LHpO8SD8T7b{83~R^tUY!c6-|ntqrxDY&0?!j9yWRgjIk6fk zJ9=m}4i4d-c9?Q&Ez$&^%T>|TRJNeUqpK<4${B(mBqqq3NEi$y6}BidgOcaB!HGIu z{Zq4*vLcwo@X+dPvu<#CnX?iID;C%?XH zAW~z*9alqZI<#z4#ooNyc~j$=1A;gPs1<6>>9=`@+N*1|S34LsTCs+Wd>U!lc4;=4 zAU>Gld%|{`Y{(3Fi>;qnjUUaL37V_ZbtQ0>)$kx#F@*{Ryi9CTKskLr2Pr_$9}b$-;Jv_^J6b?5!=kxi44pk@UQ&^8BuPXpOl&q-X0Uy zCRv}p4c?^aZStWqbQ%GIEP|s>6iw_UENI}k9k zpS{+`d&j-x#A@QOwg?F`gaiC{Kbz{ZI%f1bl%2Ym|9;dAt)2@vN+@*w|Pv)}pMK7r|7d z6aoqX&j_$ztg5O?oN2+AL_BBe>|GU6b{08RP9dNW=pG0tU#xqygW^IV(4!IH_3yN_ zwD@@KsATVc011N1iuGvSQNk$%x(5Og5fSO>=|18`MU*}QR*_W*CIQd5KstsK)|Qx?yHE>1LEqILO>x9LIcpm|F zTIA;DdY`U~qKYVe1gs*f5KsvC2?1rr`~XC+_dr zvq!8^4j(=|IXSuMS(j7F7wa;Fihx2uArMdmlobmo{Hk(=fI^_l2&jm%%MdC83IT;c zTL`GP!P`Qpv(KGQXJ?D5atZ;3KnD=;`rF`i-iC$-!NLCn3OsqBFdskm00000NkvXX Hu0mjfv!ymc literal 0 HcmV?d00001 diff --git a/modules/benchpress/docs/overview.svg b/modules/benchpress/docs/overview.svg new file mode 100644 index 0000000000..998837ae94 --- /dev/null +++ b/modules/benchpress/docs/overview.svg @@ -0,0 +1,4 @@ + + + +