<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Mikolaj Grzaslewicz - Deployment Every Day</title>
	<atom:link href="http://deploymenteveryday.com/author/admin6321/feed/" rel="self" type="application/rss+xml" />
	<link>http://deploymenteveryday.com</link>
	<description>JVM (java/kotlin) performance, programming, frequent deployments, devops</description>
	<lastBuildDate>Mon, 23 Dec 2024 11:24:18 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>How To Find Performance Bottleneck For Any Website</title>
		<link>http://deploymenteveryday.com/how-to-find-performance-bottleneck-for-any-website/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-find-performance-bottleneck-for-any-website</link>
					<comments>http://deploymenteveryday.com/how-to-find-performance-bottleneck-for-any-website/#comments</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Mon, 23 Dec 2024 11:24:03 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=241</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<p class="responsive-video-wrap clr"><iframe title="How To Find Performance Bottleneck For Any Website" width="1200" height="675" src="https://www.youtube.com/embed/il-qCyvP7CA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
</div></figure>



<p></p><p>The post <a href="http://deploymenteveryday.com/how-to-find-performance-bottleneck-for-any-website/">How To Find Performance Bottleneck For Any Website</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/how-to-find-performance-bottleneck-for-any-website/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Is (website) performance improved after improving percentile 90?</title>
		<link>http://deploymenteveryday.com/is-website-performance-improved-after-improving-percentile-90/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=is-website-performance-improved-after-improving-percentile-90</link>
					<comments>http://deploymenteveryday.com/is-website-performance-improved-after-improving-percentile-90/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Mon, 09 Sep 2024 15:14:25 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[distribution]]></category>
		<category><![CDATA[fcp]]></category>
		<category><![CDATA[first contentful paint]]></category>
		<category><![CDATA[p90]]></category>
		<category><![CDATA[percentile]]></category>
		<category><![CDATA[statistics]]></category>
		<category><![CDATA[web vitals]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=236</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<p class="responsive-video-wrap clr"><iframe title="Is (website) performance improved when percentile 90 is improved?" width="1200" height="675" src="https://www.youtube.com/embed/1Fbi2EgqUpM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
</div></figure><p>The post <a href="http://deploymenteveryday.com/is-website-performance-improved-after-improving-percentile-90/">Is (website) performance improved after improving percentile 90?</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/is-website-performance-improved-after-improving-percentile-90/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>No More Guesswork: Validating what improves First Contentful Paint upfront with No-Code Experiments</title>
		<link>http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments</link>
					<comments>http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Mon, 12 Aug 2024 15:11:41 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[fcp]]></category>
		<category><![CDATA[first contentful paint]]></category>
		<category><![CDATA[web vitals]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=205</guid>

					<description><![CDATA[<p>Let&#8217;s take a look at performance of 2 websites with a goal to improve it. Let&#8217;s use FCP (First Contentful Paint) as a performance metric, which is a core google web vital metric. Following tools are used to verify website performance and get fix suggestions I&#8217;m going to use those tools to see Let&#8217;s start [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/">No More Guesswork: Validating what improves First Contentful Paint upfront with No-Code Experiments</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h1 class="wp-block-heading">Let&#8217;s take a look at performance of 2 websites with a goal to improve it. Let&#8217;s use <code>FCP</code> (<code>First Contentful Paint</code>) as a performance metric, which is a core google web vital metric.</h1>



<p>Following tools are used to verify website performance and get fix suggestions</p>



<ul class="wp-block-list">
<li>https://pagespeed.web.dev/
<ul class="wp-block-list">
<li>tool that says what potential performance problems a website has</li>
</ul>
</li>



<li>and my own <code>performance-explainer</code> which aims to explain
<ul class="wp-block-list">
<li>what exactly needs to fixed</li>



<li>what the <code>ROI</code> (return of investment) of each fix is &#8211; to focus on fixes giving the biggest improvement</li>
</ul>
</li>
</ul>



<p>I&#8217;m going to use those tools to see</p>



<ul class="wp-block-list">
<li>What the user perceived performance is</li>



<li>How to fix performance</li>
</ul>



<p>Let&#8217;s start with website 1</p>



<h2 class="wp-block-heading"><code>PageSpeed insights</code> website 1</h2>



<p>Let&#8217;s see what a popular tool says about performance of website https://donorperfect.com. That&#8217;s just a sample website that was sent to me during early phase of developing <code>performance explainer</code> to check what is says about performance of this website.</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="744" height="1024" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1-744x1024.png" alt="donorperfect.com - PageSpeed insights scoring" class="wp-image-209" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1-744x1024.png 744w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1-218x300.png 218w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1-768x1057.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1.png 896w" sizes="(max-width: 744px) 100vw, 744px" /></figure>



<p>It says overall performance is poor. <code>FCP</code>, which is our focus in this blog post, is scored medium.</p>



<p>Let&#8217;s take a look at <code>FCP</code> improvement suggestions from <code>PageSpeed insights</code>.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="975" height="966" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all.png" alt="" class="wp-image-210" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all.png 975w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all-300x297.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all-150x150.png 150w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all-768x761.png 768w" sizes="(max-width: 975px) 100vw, 975px" /></figure>



<h3 class="wp-block-heading">Problem 1: There are render blocking resources slowing down FCP</h3>



<figure class="wp-block-image size-full"><img decoding="async" width="971" height="524" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-1.png" alt="" class="wp-image-212" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-1.png 971w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-1-300x162.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-1-768x414.png 768w" sizes="(max-width: 971px) 100vw, 971px" /></figure>



<p>It says potential saving is 1,050 ms. That would be a huge <code>FCP</code> boost and much better use experience when fixes applied!</p>



<p><strong>So as a developer you might think: if I or my team puts an effort to fix blocking resources, performance will be fixed. Right?</strong> More on that later <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h3 class="wp-block-heading">Problem 2: big images</h3>



<figure class="wp-block-image size-full"><img decoding="async" width="877" height="742" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-suggested-images-fix.png" alt="" class="wp-image-213" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-suggested-images-fix.png 877w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-suggested-images-fix-300x254.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-suggested-images-fix-768x650.png 768w" sizes="(max-width: 877px) 100vw, 877px" /></figure>



<p><strong>Ok. Looks easy. If images are compressed more, those should be sent faster and should speed up FCP. Right?</strong></p>



<p>At this point a developer trying to improve a website might think:</p>



<ul class="wp-block-list">
<li>It&#8217;s clear how to fix performance</li>



<li>And a there might be a shy thought <em>When I apply the fix, how do I know it really improved <code>FCP</code>?</em>
<ul class="wp-block-list">
<li><code>PageSpeed insights</code> does not aswer this question before the fix.</li>
</ul>
</li>
</ul>



<p>Is the performance tool job done? Well, if the goal is to actually improve performance, that&#8217;s merely a beginning of the job. After the fix developer needs to know if performance was really fixed. That requires measuring the system again. The tool needs to be a part of development cycle.</p>



<h3 class="wp-block-heading">Hold on. Let&#8217;s use much more time effective approach. <code>performance explainer</code> in action</h3>



<p>First: take many samples and see what the performance really is.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="445" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark-1024x445.png" alt="" class="wp-image-214" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark-1024x445.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark-300x130.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark-768x334.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark.png 1311w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Let&#8217;s apply acceptance criteria: For 99% of visits, FCP should be below 1s.</p>



<p>Is it achieved?</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="536" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria-1024x536.png" alt="" class="wp-image-215" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria-1024x536.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria-300x157.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria-768x402.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria.png 1306w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Unfortunately not. Within 150 samples, FCP below 1s was never experienced.</p>



<p>Let&#8217;s take a look at FCP across all samples on a percentile chart. What&#8217;s the latency user experiences?</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="662" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles-1024x662.png" alt="" class="wp-image-216" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles-1024x662.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles-300x194.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles-768x496.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles.png 1306w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Now you can see the big picture what&#8217;s going on at this website. 2.3s as suggested by <code>PageSpeed insights</code> never happened. Maybe <code>PageSpeed insights</code> analyzed samples when that indeed happened?</p>



<h3 class="wp-block-heading">Let&#8217;s apply fixes suggested by <code>PageSpeed insights</code> with a no-code experiments and see if <code>FCP</code> is fixed.</h3>



<p><code>performance-explainer</code> can make a no code experiments in the real browser to answer question: &#8220;<em>how much boosting particular requests impacts the measurement?</em>&#8220;. In this case &#8211; How much will it improve <code>FCP</code>?</p>



<p>So let&#8217;s pick requests too boost.</p>



<h4 class="wp-block-heading">Boost render blocking resources suggested by <code>PageSpeed</code> to simulate the fix without any coding</h4>



<p>What if I speed up those requests to a possible maximum? For example, if original request in the browser takes 400ms, it&#8217;s possible to cut down the latency in experiment to ~50ms. This way you can see what would happen if you changed the production system.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1302" height="781" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-multi-boost-render-blocking-resources.png" alt="" class="wp-image-217" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-multi-boost-render-blocking-resources.png 1302w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-multi-boost-render-blocking-resources-300x180.png 300w" sizes="(max-width: 1302px) 100vw, 1302px" /></figure>



<p>Ok, go!</p>



<p>A few minutes later the result is ready. 400 baseline measurements (original website) compared to 400 experiment measurements (selected resourced served to browser almost instantly).<strong><br> <br> What&#8217;s the result? Does implementing <code>PageSpeed insights</code> suggestions would fix FCP? No. Developers would work on something that doesn&#8217;t improve it<br></strong></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="311" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix-1024x311.png" alt="" class="wp-image-218" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix-1024x311.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix-300x91.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix-768x233.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix.png 1322w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>If you speed up those requests by 75-88% those will have no impact on <code>FCP</code>. In production it would mean either actually sending them faster or compressing. Neither would help fixing <code>FCP</code>.</p>



<p>Depending on the product, company and the developers team &#8211; fixing performance might take anything from a few hours to a few weeks. So in case of a false signal, a lot of time and money could be wasted.</p>



<h4 class="wp-block-heading">Another <code>PageSpeed insights</code> hint is to fix images size. Let&#8217;s make a no-code experiment and boost sending images</h4>



<p>Wha&#8217;t the result of boosting images that are supposed to improve FCP?</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1309" height="535" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pagespeed-insight-suggested-images-fix.png" alt="" class="wp-image-219" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pagespeed-insight-suggested-images-fix.png 1309w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pagespeed-insight-suggested-images-fix-300x123.png 300w" sizes="(max-width: 1309px) 100vw, 1309px" /></figure>



<p>Note images are boosted almost 100%. That means those are almost instantly available for the browser yet it would have no impact on <code>FCP</code>.</p>



<p><strong>Devs would again work on something not improving FCP!</strong></p>



<h4 class="wp-block-heading">So what will actually improve FCP?</h4>



<p>This particular website makes ~120 requests. Which one impacts <code>FCP</code>?</p>



<p>It&#8217;s possible to tell with a static analysis. But which one has impact strong enough to make it <strong>meaningful and worth fixing</strong>?</p>



<p>It&#8217;s impossible to tell without statistical analysis based on taking samples from real system. Which means using browser, just like user does.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="697" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90-1024x697.png" alt="" class="wp-image-220" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90-1024x697.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90-300x204.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90-768x523.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90.png 1300w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Some of requests are not even ever started before <code>FCP</code> across all samples &#8211; that means there are requests very likely to be irrelevant for measurement. But you can&#8217;t tell that doing 1 or a few checks. If you take hundreds of samples, you can start making conclusions how tested system really behaves.</p>



<p>&#8220;<em><code>PageSpeed</code> shows suggestions that do not help fixing <code>FCP</code>. What to do now?</em> &#8220;</p>



<p><strong>Request boos exploration for the rescue!</strong></p>



<p>I&#8217;ve worked really hard to implement request boost exploration in <code>performance explainer</code>.</p>



<p>The methodology is the following:</p>



<ul class="wp-block-list">
<li>For every single request browser makes at given website</li>



<li>In the real browser simulate request boost up from 10 to 100% (incrementally)</li>



<li>Take hundreds of samples and apply statistics to see what&#8217;s the noise and what&#8217;s the true impact
<ul class="wp-block-list">
<li>It&#8217;s not using production system, everything happens in the browsers with requests fulfilled with controlled latencies</li>
</ul>
</li>



<li>Tell if it&#8217;s worth speeding up or not</li>
</ul>



<p>Instead of guessing what to do, let the tooling figure out what&#8217;s worth fixing. It might take some time, but the results are accurate.</p>



<p>The lower the measurement latency impact, the better.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="752" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix-1024x752.png" alt="" class="wp-image-222" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix-1024x752.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix-300x220.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix-768x564.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix.png 1317w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Are those easy fixes? Myabe it&#8217;s just a matter of deferring the js resource or making it async. I don&#8217;t know. Latency fixes are often difficult. That&#8217;s something that now developers can figure out.</p>



<p>Huge part of the job is already done &#8211; knowing what to improve. The more first request from above table is sped up, the better <code>FCP</code> is. And that&#8217;s where the biggest <code>ROI</code> for improving <code>FCP</code> is. You could also speed up main site, but the <code>ROI</code> there is smaller.</p>



<h3 class="wp-block-heading">Main focus of this blog post is FCP, but let&#8217;s take at LCP (Largest Contentful Paint) also. How to improve it?</h3>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="675" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix-1024x675.png" alt="" class="wp-image-221" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix-1024x675.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix-300x198.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix-768x506.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix.png 1333w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><code>LCP</code> has a different bottleneck. It&#8217;s an image, with maximum possible improvement opportunity 65%. What improves LCP might not improve FCP (as in this case). Because it &#8216;s a different goal.</p>



<p>Note that there is better <code>LCP</code> when image is sped up 80% than 99%. Why? It sounds counterintuitive.</p>



<p>Because there are request races in the browser.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">ANSI</span></span><span role="button" tabindex="0" data-code="// R1, R2, R3 - browser requests
// R1 is most likely to be a LCP bottleneck

R1 --------------------------------------------
R2 ------------------------------
R3 -------------------------------------

// after enough R1 boost LCP has no further improvements
// now R3 is most likely to be a LCP bottleneck
R1 ----------------------
R2 ------------------------------
R3 -------------------------------------" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #d8dee9ff">// R1, R2, R3 - browser requests</span></span>
<span class="line"><span style="color: #d8dee9ff">// R1 is most likely to be a LCP bottleneck</span></span>
<span class="line"></span>
<span class="line"><span style="color: #d8dee9ff">R1 --------------------------------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">R2 ------------------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">R3 -------------------------------------</span></span>
<span class="line"></span>
<span class="line"><span style="color: #d8dee9ff">// after enough R1 boost LCP has no further improvements</span></span>
<span class="line"><span style="color: #d8dee9ff">// now R3 is most likely to be a LCP bottleneck</span></span>
<span class="line"><span style="color: #d8dee9ff">R1 ----------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">R2 ------------------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">R3 -------------------------------------</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">ANSI</span></div>



<p>Systems don&#8217;t behave deterministic, that&#8217;s why hundreds of samples need to be taken. There is a lot of going on in the browser. If you cut off latency of a single request enough, you&#8217;re going to see the improvement and after that new bottleneck might be uncovered.</p>



<p>That&#8217;s a lo of knowledge, I&#8217;m glad you&#8217;re still here <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Now let&#8217;s move on to</p>



<h2 class="wp-block-heading">PageSpeed insights: website 2</h2>



<p>I feel at the moment of writing my wordpress blog you&#8217;re just at is not fast. So let&#8217;s just measure it.</p>



<h3 class="wp-block-heading">What does PageSpeed insight say?</h3>



<figure class="wp-block-image size-large"><img decoding="async" width="745" height="1024" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights-745x1024.png" alt="" class="wp-image-223" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights-745x1024.png 745w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights-218x300.png 218w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights-768x1055.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights.png 882w" sizes="(max-width: 745px) 100vw, 745px" /></figure>



<p>It says 1.8s for <code>FCP</code>, 6.2s for <code>LCP</code>. It could be better.</p>



<p>However does it mean every user experiences FCP 1.8s? Or maybe that&#8217;s the worst case? Or the best?</p>



<p>How about fixes?</p>



<p>It points initial server response time could be better as it&#8217;s 1 140 ms</p>



<ul class="wp-block-list">
<li>Is it where most improvement can be made? I don&#8217;t know but it&#8217;s the biggest latency shown on the list.</li>



<li>Other fixes show potential [kb] savings.
<ul class="wp-block-list">
<li>What does it mean in terms of latency? How much will it improve FCP? Is it worth investing my time to apply fixes there?</li>
</ul>
</li>
</ul>



<p>I value my time. I need data telling me what should be improved and in what order, not only what&#8217;s wrong without even knowing if it will help.</p>



<h3 class="wp-block-heading">What does <code>performance explainer</code> <code>FCP</code> benchmark say?</h3>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="605" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark-1024x605.png" alt="" class="wp-image-224" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark-1024x605.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark-300x177.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark-768x454.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark.png 1311w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>It says user never experiences the desired FCP below or equal 1s.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="638" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck-1024x638.png" alt="" class="wp-image-225" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck-1024x638.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck-300x187.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck-768x479.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck.png 1311w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>It also says <code>TTFB</code> (<code>Time To First Byte</code>) is where 72% of time user is waiting for <code>FCP</code> and that it needs to be sped up 54.55% to meet acceptance criteria and have 99% latencies below 1s.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="662" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles-1024x662.png" alt="" class="wp-image-226" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles-1024x662.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles-300x194.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles-768x497.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles.png 1310w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><code>FCP</code> takes from 1326 ms to 4807 ms across 150 measurements. Ouch. I want to give my readers a better experience.</p>



<p>FCP at 1.8s level shown in <code>PageSpeed</code> didn&#8217;t look that bad.</p>



<p>So let&#8217;s figure out ROIs using <code>request boost exploration</code> in <code>performance explainer</code></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="518" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix-1024x518.png" alt="" class="wp-image-227" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix-1024x518.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix-300x152.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix-768x389.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix.png 1316w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>The only fix worth investing is fixing <code>TTFB</code>. Not resources mentioned by <code>PageSpeed insights</code>.</p>



<p>I&#8217;m no wordpress expert, but I thought &#8220;<em>How about caching the whole response to spend less time on producing HTML? There must be some plugins for caching</em>&#8220;</p>



<p>So I installed <code>WP Fastest Cache</code> plugin, turned it on with default settings hoping to improve <code>TTFB</code> which is a bottleneck for <code>FCP</code>. Immediately I had questions in my mind.</p>



<ul class="wp-block-list">
<li>Did it improve performance?</li>



<li>How would I know it?</li>



<li>Would refreshing page in the browser give the <em>feeling</em> it&#8217;s better?</li>



<li>What if it gave the <em>feeling</em> it&#8217;s better, but if I refreshed the page 10 more times it would actually give <em>feeling</em> it&#8217;s worse?</li>
</ul>



<p>Let the data speak. And what does it say after taking samples and comparing it to samples before installing cache plugin?</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="231" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment-1024x231.png" alt="" class="wp-image-228" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment-1024x231.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment-300x68.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment-768x173.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment.png 1300w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><code>FCP</code> did not improve at all. Without measuring it after installing plugin and comparing to baseline (no plugin installed) I&#8217;d be pretty sure performance is better. While my readers would feel no difference at all.</p>



<p>Additional plugin would only add complexity to the system. And I would think it&#8217;s improving the situation. I removed the plugin immediately.</p>



<p>Now I have more ideas for speeding up <code>TTFB</code>.</p>



<ul class="wp-block-list">
<li>better hosting</li>



<li>different caching plugin</li>



<li>different skin</li>



<li>&#8230; and there might be 10 more ideas</li>
</ul>



<p>However that&#8217;s a typical <code>trial and error</code> fixing methodology when no data is available for showing bottleneck in more details.</p>



<p><strong>It&#8217;s easy to produce ideas. It&#8217;s expensive to implement them. And it&#8217;s risky because it might fix nothing.</strong></p>



<p>When you think a fix helped (while it didn&#8217;t), unnecessary complexity grows over time. It contributes to a growing production systems maintenance cost.</p>



<p>Since I know the bottleneck is PHP backend, what would really help is to have profiler samples. Or maybe some plugin showing what&#8217;s slow on wordpress backend if there is such one.</p>



<p>It would show where time is spent when request is made from the browser. In another words &#8211; data allowing understand better the root cause would appear.</p>



<p>If I knew PHP backend bottleneck, there would be no guessing what to fix. As a result</p>



<ul class="wp-block-list">
<li>much quicker fix delivery</li>



<li>less time spent</li>



<li>no unjustifiable complexity added for solutions that were supposed to fix performance, while fixing nothing</li>
</ul>



<h2 class="wp-block-heading"><code>PageSpeed isights</code> VS <code>performance explainer</code> summary</h2>



<p><code>PageSpeed insights</code></p>



<ul class="wp-block-list">
<li>It shows improvement ideas. Those look good and worth spending time on
<ul class="wp-block-list">
<li>But in reality those fixes might not fix performance at all</li>
</ul>
</li>
</ul>



<p><code>performance explainer</code></p>



<ul class="wp-block-list">
<li>It&#8217;s showing the whole picture and how often user experiences good and bad performance.
<ul class="wp-block-list">
<li>Thanks to to hundreds of samples taken</li>
</ul>
</li>



<li>it does not present good sounding fixes ideas. It presents the ones that actually impact performance</li>
</ul>



<h3 class="wp-block-heading">Do you want to know if your website (on premise product too) is fast enough and having it fixed if needed?</h3>



<p>Reach me at mikolaj.grzaslewicz@gmail.com if you want to have performance of your website fixed via consultations. My goal is to have <code>performance explainer</code> available as a service to support more customers.</p><p>The post <a href="http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/">No More Guesswork: Validating what improves First Contentful Paint upfront with No-Code Experiments</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Want to have better accuracy of performance benchmark? Stop using playwright Page.waitForCondition</title>
		<link>http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition</link>
					<comments>http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Mon, 13 May 2024 09:21:53 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[plawright]]></category>
		<category><![CDATA[polling]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=168</guid>

					<description><![CDATA[<p>Let&#8217;s compare accuracy of Page.waitForCondition with custom condition polling For measurements I&#8217;m going to use 2 different approaches to see which one is better for performance benchmarks and has better accuracy. Custom condition polling implementation Website 1: https://www.theathletesfoot.nl/ Measurement ends when button starts working. Starts working means, it&#8217;s reacting on clicks. At this particular website [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/">Want to have better accuracy of performance benchmark? Stop using playwright Page.waitForCondition</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h1 class="wp-block-heading">Let&#8217;s compare accuracy of <code>Page.waitForCondition</code> with custom condition polling</h1>



<p>For measurements I&#8217;m going to use 2 different approaches to see which one is better for performance benchmarks and has better accuracy.</p>



<ul class="wp-block-list">
<li>first using playwright <code>Page.waitForCondition</code></li>



<li>and the second using custom <code>condition polling</code></li>
</ul>



<p>Custom <code>condition polling</code> implementation</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="// part of performance-explainer project I'm developing to measure things

package com.performance.explainer.api.waiter

import com.performance.explainer.spi.waiter.ConditionPolling
import java.time.Duration
import java.time.Instant
import java.util.function.BooleanSupplier


class ConditionPolling : SpiConditionPolling {

    override fun await(
        retryWait: Duration,
        timeout: Duration,
        condition: BooleanSupplier
    ): Boolean {
        val start = Instant.now()
        while (true) {
            val satisfied = condition.asBoolean
            val elapsed = Duration.between(start, Instant.now())
            if (satisfied) {
                return true
            }
            if (elapsed.plus(retryWait) &gt; timeout) {
                return false
            }
            Thread.sleep(retryWait)
        }
    }

}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// part of performance-explainer project I&#39;m developing to measure things</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> com.performance.explainer.api.waiter</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.performance.explainer.spi.waiter.ConditionPolling</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Instant</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.BooleanSupplier</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ConditionPolling</span><span style="color: #D8DEE9FF"> : SpiConditionPolling {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">override</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fun</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">await</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        retryWait: Duration,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        timeout: Duration,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        condition: BooleanSupplier</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ): Boolean {</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> start </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> Instant.</span><span style="color: #88C0D0">now</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">while</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">true</span><span style="color: #D8DEE9FF">) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> satisfied </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> condition.asBoolean</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> elapsed </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> Duration.</span><span style="color: #88C0D0">between</span><span style="color: #D8DEE9FF">(start, Instant.</span><span style="color: #88C0D0">now</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (satisfied) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">            }</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (elapsed.</span><span style="color: #88C0D0">plus</span><span style="color: #D8DEE9FF">(retryWait) </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> timeout) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">            }</span></span>
<span class="line"><span style="color: #D8DEE9FF">            Thread.</span><span style="color: #88C0D0">sleep</span><span style="color: #D8DEE9FF">(retryWait)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        }</span></span>
<span class="line"><span style="color: #D8DEE9FF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">NonBlockingConditionPolling</span></div>



<p></p>



<h2 class="wp-block-heading">Website 1: https://www.theathletesfoot.nl/</h2>



<p>Measurement ends when button starts working. Starts working means, it&#8217;s reacting on clicks. At this particular website it means validation error shows up.</p>



<p>That unfortunately cannot be checked with locator and <code>.click()</code> as button is not disabled and it&#8217;s clickable before it has event listeners attached. That could be fixed with waiting for <code>loaded</code> event, but that&#8217;s a bad idea for performance benchmark as explained with details in this blog post. So below code is <strong>NOT working</strong> for checking if button is working. Take a look at the explanation in source code</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.TimeoutError
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
    page.navigate(
        &quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;,
        NavigateOptions().setWaitUntil(COMMIT)
    )
    val button = page.locator(&quot;#add-to-cart&quot;)
    button.waitFor(WaitForOptions().setTimeout(3000.0))
    // plawright waits for a clickable button.
    // Clickable means it's visible and not disabled
    // This button is visible and not disabled before all resources are loaded.
    // Because of that, it does not actually measure user experience (button is working) 
    button.click()
}
// measured latency is duration of above" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.TimeoutError</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #A3BE8C">&quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    )</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#add-to-cart&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">// plawright waits for a clickable button.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">// Clickable means it&#39;s visible and not disabled</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">// This button is visible and not disabled before all resources are loaded.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">// Because of that, it does not actually measure user experience (button is working) </span></span>
<span class="line"><span style="color: #D8DEE9FF">    button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">Broken measurement</span></div>



<figure class="wp-block-image size-large is-style-zoooom wp-duotone-unset-1"><img decoding="async" width="1024" height="529" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-1024x529.png" alt="" class="wp-image-175" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-1024x529.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-300x155.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-768x397.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-1536x794.png 1536w, http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example.png 1567w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example.webm"></video><figcaption class="wp-element-caption">Measurement target &#8211; wait for working button adding item to the shop cart</figcaption></figure>



<p></p>



<h3 class="wp-block-heading">baseline 1 &#8211; wait for working button, use custom polling</h3>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.TimeoutError
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import com.performance.explainer.api.waiter.NonBlockingConditionPolling
import java.time.Duration.ofMillis
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   val conditionPolling = NonBlockingConditionPolling.default()
   page.navigate(
       &quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;,
       NavigateOptions().setWaitUntil(COMMIT)
   )
   val button = page.locator(&quot;#add-to-cart&quot;)
   button.waitFor(WaitForOptions().setTimeout(3000.0))
   val success = conditionPolling.await(
       retryWait = ofMillis(10),
       timeout = ofMillis(5000)
   ) {
       button.click()
       val formError = page.locator(&quot;.js-select-size-and-color-error&quot;)
       formError.count() == 1
   }
   if (!success) {
       throw TimeoutError(&quot;Waiting for working button timed out&quot;)
   }
}
// measured latency is duration of above" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.TimeoutError</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.performance.explainer.api.waiter.NonBlockingConditionPolling</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration.ofMillis</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> conditionPolling </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> NonBlockingConditionPolling.</span><span style="color: #88C0D0">default</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #A3BE8C">&quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   )</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#add-to-cart&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> success </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> conditionPolling.</span><span style="color: #88C0D0">await</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       retryWait </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">       timeout </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">5000</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   ) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> formError </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;.js-select-size-and-color-error&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">       formError.</span><span style="color: #88C0D0">count</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">success) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TimeoutError</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Waiting for working button timed out&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">baseline 1 &#8211; wait for working button, use custom polling</span></div>



<p></p>



<h3 class="wp-block-heading">experiment 1 &#8211; wait for working button, use <code>Page.waitForCondition</code></h3>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   page.navigate(
       &quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;,
       NavigateOptions().setWaitUntil(COMMIT)
   )
   val button = page.locator(&quot;#add-to-cart&quot;)
   button.waitFor(WaitForOptions().setTimeout(3000.0))
   page.waitForCondition({
       button.click()
       val formError = page.locator(&quot;.js-select-size-and-color-error&quot;)
       formError.count() == 1
   }, Page.WaitForConditionOptions().setTimeout(5000.0))
}
// measured latency is duration of above
" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #A3BE8C">&quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   )</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#add-to-cart&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">waitForCondition</span><span style="color: #D8DEE9FF">({</span></span>
<span class="line"><span style="color: #D8DEE9FF">       button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> formError </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;.js-select-size-and-color-error&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">       formError.</span><span style="color: #88C0D0">count</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }, Page.</span><span style="color: #88C0D0">WaitForConditionOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">5000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span>
<span class="line"></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">experiment 1 &#8211; wait for working button, use Page.waitForCondition</span></div>



<p></p>



<h2 class="wp-block-heading">Website 2: https://www.blogmarketingacademy.com/</h2>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="562" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example-1024x562.png" alt="" class="wp-image-177" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example-1024x562.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example-300x165.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example-768x422.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example.png 1457w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Measurement target &#8211; wait for working subscribe button</figcaption></figure>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example.webm"></video><figcaption class="wp-element-caption">Measurement target &#8211; wait for working subscribe button</figcaption></figure>



<p></p>



<h3 class="wp-block-heading">baseline 2 &#8211; wait for working button, use custom polling</h3>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.*
import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import com.performance.explainer.api.waiter.NonBlockingConditionPolling
import java.time.Duration.ofMillis
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   page.navigate(
       &quot;https://www.blogmarketingacademy.com/membership-site-service-credit-system/&quot;,
       NavigateOptions().setWaitUntil(COMMIT)
   )
   val button = page.getByText(&quot;Subscribe Weekly&quot;)
   button.waitFor(WaitForOptions().setTimeout(3000.0))
   val condition = NonBlockingConditionPolling.default()
   val isButtonWorking = condition.await(retryWait = ofMillis(10), timeout = ofMillis(3000)) {
       button.click()
       page.getByText(&quot;This field is required&quot;).count() == 1
   }
   if (!isButtonWorking) {
       throw TimeoutError(&quot;Waiting for working button timed out&quot;)
   }
}
// measured latency is duration of above" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.</span><span style="color: #81A1C1">*</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.performance.explainer.api.waiter.NonBlockingConditionPolling</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration.ofMillis</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #A3BE8C">&quot;https://www.blogmarketingacademy.com/membership-site-service-credit-system/&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   )</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Subscribe Weekly&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> condition </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> NonBlockingConditionPolling.</span><span style="color: #88C0D0">default</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> isButtonWorking </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> condition.</span><span style="color: #88C0D0">await</span><span style="color: #D8DEE9FF">(retryWait </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF">), timeout </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000</span><span style="color: #D8DEE9FF">)) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">       page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;This field is required&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">count</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">isButtonWorking) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TimeoutError</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Waiting for working button timed out&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">baseline 2 &#8211; wait for working button, use custom polling</span></div>



<p></p>



<h3 class="wp-block-heading">experiment 2 &#8211; wait for working button, use <code>Page.waitForCondition</code></h3>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   page.navigate(
       &quot;https://www.blogmarketingacademy.com/membership-site-service-credit-system/&quot;,
       NavigateOptions().setWaitUntil(COMMIT)
   )
   val button = page.getByText(&quot;Subscribe Weekly&quot;)
   button.waitFor(WaitForOptions().setTimeout(3000.0))
   page.waitForCondition({
       button.click()
       page.getByText(&quot;This field is required&quot;).count() == 1
   }, Page.WaitForConditionOptions().setTimeout(3000.0))
}
// measured latency is duration of above" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #A3BE8C">&quot;https://www.blogmarketingacademy.com/membership-site-service-credit-system/&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   )</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Subscribe Weekly&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">waitForCondition</span><span style="color: #D8DEE9FF">({</span></span>
<span class="line"><span style="color: #D8DEE9FF">       button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">       page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;This field is required&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">count</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }, Page.</span><span style="color: #88C0D0">WaitForConditionOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">experiment 2 &#8211; wait for working button, use Page.waitForCondition</span></div>



<p></p>



<h1 class="wp-block-heading">Custom polling VS <code>Page.waitForCondition</code> results</h1>



<p>Each baseline and experiment has 200 samples taken. <a href="https://en.wikipedia.org/wiki/Hodges%E2%80%93Lehmann_estimator" target="_blank" rel="noopener" title="">Hodges-Lehmann</a> estimator is used for comparing results</p>



<h2 class="wp-block-heading">baseline 1 VS experiment 1</h2>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="874" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-1024x874.png" alt="" class="wp-image-180" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-1024x874.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-300x256.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-768x655.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1.png 1301w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><code>Page.waitForCondition</code> 7.958% worse than custom polling</figcaption></figure>



<p></p>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="898" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded-1024x898.png" alt="" class="wp-image-181" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded-1024x898.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded-300x263.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded-768x674.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded.png 1302w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><code>Page.waitForCondition</code> 14.182% worse than custom condition polling when TTF (time to first byte, irrelevant for experiment) deducted from measured latencies</figcaption></figure>



<p></p>



<p></p>



<h2 class="wp-block-heading">baseline 2 VS experiment 2</h2>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="873" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-1024x873.png" alt="" class="wp-image-182" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-1024x873.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-300x256.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-768x655.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2.png 1301w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><code>Page.waitForCondition</code> 2.665% worse than custom condition polling. Additionally timeouts went up from 3.5% to 6%</figcaption></figure>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="898" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded-1024x898.png" alt="" class="wp-image-183" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded-1024x898.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded-300x263.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded-768x673.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded.png 1300w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><code>Page.waitForCondition</code>17.427% worse than custom condition polling when TTF (time to first byte, irrelevant for experiment) deducted from measured latencies</figcaption></figure>



<p></p>



<p></p>



<h1 class="wp-block-heading">Let&#8217;s increase custom condition polling <code>retryWait</code> and compare results again</h1>



<h2 class="wp-block-heading">Results should be closer to <code>Page.waitForCondition</code>, right? Right?</h2>



<p>Intuitively with increased wait before another condition poll, results should get less accurate. And this way get closer to less accurate playwright&#8217;s <code>Page.waitForCondition</code>. Right?</p>



<p>Nope, in reality increasing retryWait up to 40ms makes no difference.</p>



<h2 class="wp-block-heading">Custom polling with <code>retryWait</code> <code>10 ms</code> VS longer <code>retryWait</code></h2>



<p>10 ms VS 20 ms &#8211; no difference</p>



<p>10 ms VS 40 ms &#8211; no difference</p>



<p>10 ms VS 80 ms &#8211; 2.644% worse</p>



<p>So there is difference noticeable in measurements when increasing polling retry wait from 10 ms to 80 ms. Sweet spot is 40 ms</p>



<h1 class="wp-block-heading">Why is there a difference between custom condition polling and <code>Page.waitForCondition</code>?</h1>



<p>Let&#8217;s take a look at playwright&#8217;s implementation for <code>Page.waitForCondition</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="// com.microsoft.playwright.Page#waitForCondition(java.util.function.BooleanSupplier)
 @Override
 public void waitForCondition(BooleanSupplier predicate, WaitForConditionOptions options) {
   List&lt;Waitable&lt;Void&gt;&gt; waitables = new ArrayList&lt;&gt;();
   waitables.add(createWaitForCloseHelper());
   waitables.add(createWaitableTimeout(options == null ? null : options.timeout));
   waitables.add(new WaitablePredicate&lt;&gt;(predicate));
   runUntil(() -&gt; {}, new WaitableRace&lt;&gt;(waitables));
 }

 // com.microsoft.playwright.impl.ChannelOwner#runUntil
 &lt;T&gt; T runUntil(Runnable code, Waitable&lt;T&gt; waitable) {
   try {
     code.run();
     while (!waitable.isDone()) {
       connection.processOneMessage();
     }
     return waitable.get();
   } finally {
     waitable.dispose();
   }
 }

 // com.microsoft.playwright.impl.Connection#processOneMessage
 void processOneMessage() {
   JsonObject message = transport.poll(Duration.ofMillis(10));
   if (message == null) {
     return;
   }
   Gson gson = gson();
   Message messageObj = gson.fromJson(message, Message.class);
   dispatch(messageObj);
 }" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// com.microsoft.playwright.Page#waitForCondition(java.util.function.BooleanSupplier)</span></span>
<span class="line"><span style="color: #D8DEE9FF"> @Override</span></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> void </span><span style="color: #88C0D0">waitForCondition</span><span style="color: #D8DEE9FF">(BooleanSupplier predicate, WaitForConditionOptions options) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">   List</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">Waitable</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">Void</span><span style="color: #81A1C1">&gt;&gt;</span><span style="color: #D8DEE9FF"> waitables </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new ArrayList</span><span style="color: #81A1C1">&lt;&gt;</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">   waitables.</span><span style="color: #88C0D0">add</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">createWaitForCloseHelper</span><span style="color: #D8DEE9FF">());</span></span>
<span class="line"><span style="color: #D8DEE9FF">   waitables.</span><span style="color: #88C0D0">add</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">createWaitableTimeout</span><span style="color: #D8DEE9FF">(options </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> ? </span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> : options.timeout));</span></span>
<span class="line"><span style="color: #D8DEE9FF">   waitables.</span><span style="color: #88C0D0">add</span><span style="color: #D8DEE9FF">(new WaitablePredicate</span><span style="color: #81A1C1">&lt;&gt;</span><span style="color: #D8DEE9FF">(predicate));</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #88C0D0">runUntil</span><span style="color: #D8DEE9FF">(() </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> {}, new WaitableRace</span><span style="color: #81A1C1">&lt;&gt;</span><span style="color: #D8DEE9FF">(waitables));</span></span>
<span class="line"><span style="color: #D8DEE9FF"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// com.microsoft.playwright.impl.ChannelOwner#runUntil</span></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> T </span><span style="color: #88C0D0">runUntil</span><span style="color: #D8DEE9FF">(Runnable code, Waitable</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> waitable) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> {</span></span>
<span class="line"><span style="color: #D8DEE9FF">     code.</span><span style="color: #88C0D0">run</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">while</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">waitable.</span><span style="color: #88C0D0">isDone</span><span style="color: #D8DEE9FF">()) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       connection.</span><span style="color: #88C0D0">processOneMessage</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">     }</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> waitable.</span><span style="color: #88C0D0">get</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">   } </span><span style="color: #81A1C1">finally</span><span style="color: #D8DEE9FF"> {</span></span>
<span class="line"><span style="color: #D8DEE9FF">     waitable.</span><span style="color: #88C0D0">dispose</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// com.microsoft.playwright.impl.Connection#processOneMessage</span></span>
<span class="line"><span style="color: #D8DEE9FF"> void </span><span style="color: #88C0D0">processOneMessage</span><span style="color: #D8DEE9FF">() {</span></span>
<span class="line"><span style="color: #D8DEE9FF">   JsonObject message </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> transport.</span><span style="color: #88C0D0">poll</span><span style="color: #D8DEE9FF">(Duration.</span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF">));</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (message </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF">) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">   Gson gson </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">gson</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">   Message messageObj </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> gson.</span><span style="color: #88C0D0">fromJson</span><span style="color: #D8DEE9FF">(message, Message.class);</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #88C0D0">dispatch</span><span style="color: #D8DEE9FF">(messageObj);</span></span>
<span class="line"><span style="color: #D8DEE9FF"> }</span></span></code></pre></div>



<p>Under the hood in java implementation, playwright uses condition polling every 10 ms. The same interval which was used in custom polling. So why it has worse accuracy than custom condition polling? Source code at this level does not answer the question and that might be a question to playwright dev team.</p>



<h1 class="wp-block-heading">How about <code>Page.locator</code> VS <code>Page.isVisible</code> polling?</h1>



<p>After I saw it makes a difference when polling is used for more complex scenarios I thought &#8220;<em>let&#8217;s also checked if it there is difference for plain locators like <code>Page.getByText</code></em>.</p>



<p>baseline</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   page.navigate(&quot;https://github.com/&quot;, NavigateOptions().setWaitUntil(COMMIT))
   page.getByText(&quot;Sign up for GitHub&quot;).first().waitFor(WaitForOptions().setTimeout(2000.0))
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com/&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Sign up for GitHub&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">first</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">2000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre></div>



<p>experiment</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.TimeoutError
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import com.performance.explainer.api.waiter.NonBlockingConditionPolling
import java.time.Duration.ofMillis
import java.time.Duration.ofSeconds
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   val conditionPolling = NonBlockingConditionPolling.default()
   page.navigate(&quot;https://github.com/&quot;, NavigateOptions().setWaitUntil(COMMIT))
   val isButtonVisible = conditionPolling.await(retryWait = ofMillis(40), timeout = ofSeconds(2)) {
       page.isVisible(&quot;text=Sign up for GitHub&quot;)
   }
   if (!isButtonVisible) {
       throw TimeoutError(&quot;Button not visible&quot;)
   }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.TimeoutError</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.performance.explainer.api.waiter.NonBlockingConditionPolling</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration.ofMillis</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration.ofSeconds</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> conditionPolling </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> NonBlockingConditionPolling.</span><span style="color: #88C0D0">default</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com/&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> isButtonVisible </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> conditionPolling.</span><span style="color: #88C0D0">await</span><span style="color: #D8DEE9FF">(retryWait </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">40</span><span style="color: #D8DEE9FF">), timeout </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofSeconds</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF">)) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       page.</span><span style="color: #88C0D0">isVisible</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;text=Sign up for GitHub&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">isButtonVisible) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TimeoutError</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Button not visible&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre></div>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="630" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms-1024x630.png" alt="" class="wp-image-185" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms-1024x630.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms-300x185.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms-768x473.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms.png 1300w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><strong>Uff, no difference</strong> between using <code>Page.getByText</code> vs custom condition polling</figcaption></figure>



<p></p>



<p></p>



<h1 class="wp-block-heading">Summary</h1>



<p>I started with checking if playwright <code>Page.waitForCondition</code> replaced by custom condition polling every 10ms makes a difference. And it&#8217;s faster which means you get more accurate results with this approach.</p>



<p>Then I also measured 20ms, 40ms, 80ms custom condition polling interval. Up to 40ms there is no difference in measured latencies and it&#8217;s a sweet spot for retries between condition check.</p>



<h1 class="wp-block-heading">Conclusion</h1>



<p>If you want higher accuracy of web app performance measurement, use custom condition polling 40ms instead of playwright <code>Page.waitForCondition</code></p><p>The post <a href="http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/">Want to have better accuracy of performance benchmark? Stop using playwright Page.waitForCondition</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example.webm" length="84722" type="video/webm" />
<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example.webm" length="134053" type="video/webm" />

			</item>
		<item>
		<title>How to make performance benchmark 530% worse by relying on web app automation defaults?</title>
		<link>http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults</link>
					<comments>http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/#comments</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Sun, 05 May 2024 12:26:21 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[DOMContentLoaded]]></category>
		<category><![CDATA[end-to-end]]></category>
		<category><![CDATA[playwright]]></category>
		<category><![CDATA[selenium]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=112</guid>

					<description><![CDATA[<p>Let&#8217;s start with a necessary definitions: what are DOMContentLoaded and load events in the web browser? DOMContentLoaded According to docs when browser fires this event it means load event When browser fires load event it means DOM content is fully loaded. Browser has finished Both events are NOT about REST calls. And both are not [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/">How to make performance benchmark 530% worse by relying on web app automation defaults?</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">Let&#8217;s start with a necessary definitions: what are <code>DOMContentLoaded</code> and <code>load</code> events in the web browser?</h2>



<h3 class="wp-block-heading"><code>DOMContentLoaded</code></h3>



<p>According to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event">docs</a> when browser fires this event it means</p>



<ul class="wp-block-list">
<li>DOM has been completely parsed</li>



<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer"><code>&lt;script defer src="…"&gt;</code></a>&nbsp;and&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#module"><code>&lt;script type="module"&gt;</code></a> has been loaded</li>



<li>async script, images, frames might not be loaded yet</li>
</ul>



<h3 class="wp-block-heading"><code>load event</code></h3>



<p>When browser fires <code>load event</code> it means DOM content is fully loaded. Browser has finished</p>



<ul class="wp-block-list">
<li>executing all asynchronous resources from the DOM tree</li>



<li>loading images, frames etc.</li>



<li>it comes always after <code>DOMContentLoaded</code></li>
</ul>



<p>Both events are NOT about REST calls. And both are not relevant to the user browsing the website.</p>



<h2 class="wp-block-heading">What do web apps automation tools like playwright and selenium do by default?</h2>



<p>Take a look at following lines of code measuring navigating to a page and waiting for a button to be visible.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1359" height="607" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button.png" alt="" class="wp-image-116" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button.png 1359w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button-300x134.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button-1024x457.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button-768x343.png 768w" sizes="(max-width: 1359px) 100vw, 1359px" /></figure>



<p>using playwright</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers cbp-blur-enabled cbp-unblur-on-hover" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="val millisBefore = System.currentTimeMillis()
page.navigate(&quot;https://github.com&quot;)
page.getByText(&quot;Try Now&quot;).waitFor()
val latency = System.currentTimeMillis() - millisBefore" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> millisBefore </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> System.</span><span style="color: #88C0D0">currentTimeMillis</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Try Now&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> latency </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> System.</span><span style="color: #88C0D0">currentTimeMillis</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> millisBefore</span></span></code></pre></div>



<p>or doing the same with selenium</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers cbp-blur-enabled cbp-unblur-on-hover" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="val millisBefore = System.currentTimeMillis()
driver.navigate().to(&quot;https://github.com&quot;)
WebDriverWait(driver, 30, 100).until(ExpectedConditions.visibilityOfElementLocated(By.linkText(&quot;Sign up for GitHub&quot;)))
val latency = System.currentTimeMillis() - millisBefore" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> millisBefore </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> System.</span><span style="color: #88C0D0">currentTimeMillis</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">driver.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">to</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line cbp-no-blur"><span style="color: #88C0D0">WebDriverWait</span><span style="color: #D8DEE9FF">(driver, </span><span style="color: #B48EAD">30</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">100</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">until</span><span style="color: #D8DEE9FF">(ExpectedConditions.</span><span style="color: #88C0D0">visibilityOfElementLocated</span><span style="color: #D8DEE9FF">(By.</span><span style="color: #88C0D0">linkText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Sign up for GitHub&quot;</span><span style="color: #D8DEE9FF">)))</span></span>
<span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> latency </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> System.</span><span style="color: #88C0D0">currentTimeMillis</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> millisBefore</span></span></code></pre></div>



<p>When you navigate to a page, by default those automation tools wait for <code>load</code> event. So the line with locator is not executed until then.</p>



<p>You might think <em>Pff, so what. It&#8217;s just a technical detail</em></p>



<p>It is indeed a technical detail but it impacts performance benchmark <mark>dramatically</mark>.</p>



<h2 class="wp-block-heading">Waiting for <code>DOMContentLoaded</code> VS <code>load</code> VS <code>NOT waiting for any event</code>. Fight!</h2>



<p>Let&#8217;s measure how quickly user can see github call to action button.</p>



<h3 class="wp-block-heading">Baseline benchmark &#8211; wait for <code>load</code> event</h3>



<p>Take 1000 samples. Wait for <code>load</code> event, then wait for github call to action button</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// only most important code shown here. Processing measurements is out of the scope
page.navigate(&quot;https://github.com&quot;)
page.getByText(&quot;Sign up for GitHub&quot;).first().waitFor(WaitForOptions().setTimeout(2000.0))" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// only most important code shown here. Processing measurements is out of the scope</span></span>
<span class="line"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Sign up for GitHub&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">first</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">2000.0</span><span style="color: #D8DEE9FF">))</span></span></code></pre></div>



<p>It looks like this</p>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1303" height="885" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95.png" alt="" class="wp-image-117" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95.png 1303w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95-300x204.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95-1024x696.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95-768x522.png 768w" sizes="(max-width: 1303px) 100vw, 1303px" /><figcaption class="wp-element-caption">wait for <code>load</code> event` at percentile 95, button measured as visible after 914 ms</figcaption></figure>



<h4 class="wp-block-heading">How to interpret the video of web app measurement?</h4>



<p>Playwright video recording is imperfect. Video frame is often not aligned with what was seen in web browser. I confirmed that with modifying the measured page after measurement end.</p>



<p>During recording web app, right after measurement end timer is added to the page. This way it doesn&#8217;t interfere with measurement and helps to see when measurement has actually finished on the video.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="const measuerementEndMillis = ..; // depends on the measuerement
const timerDiv = document.createElement('div');
timerDiv.style.position = 'fixed';
timerDiv.style.top = '0';
timerDiv.style.left = '0';
timerDiv.style.opacity = '0.8';
timerDiv.style.fontSize = '14px';
timerDiv.style.backgroundColor = 'yellow';
timerDiv.style.zIndex = '10000';
document.body.appendChild(timerDiv);

setInterval(function() {
  const now = performance.now();
  timerDiv.textContent = now.toFixed(0) + ' ms, ' + (now - measurementEndMillis).toFixed(0) + ' ms';
}, 20);" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">measuerementEndMillis</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">..</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// depends on the measuerement</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">timerDiv</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">document</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">createElement</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">div</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">position</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">fixed</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">top</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">0</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">left</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">0</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">opacity</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">0.8</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">fontSize</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">14px</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">backgroundColor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">yellow</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">zIndex</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">10000</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">document</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">body</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">appendChild</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">timerDiv</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">setInterval</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">function</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">now</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">performance</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">now</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">textContent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">now</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toFixed</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C"> ms, </span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">now</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">measurementEndMillis</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toFixed</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C"> ms</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">20</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span></code></pre></div>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="556" height="140" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-timer.png" alt="" class="wp-image-136" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-timer.png 556w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-timer-300x76.png 300w" sizes="(max-width: 556px) 100vw, 556px" /><figcaption class="wp-element-caption">X, Y<br>X &#8211; milliseconds since time origin (page started loading)<br>Y &#8211; milliseconds since measurement end. Timer is added after locator is satisfied so it doesn&#8217;t disturb the measurement</figcaption></figure>
</div>


<p><strong>When measuring very fast page like github.com landing page, you&#8217;re often going to see a blank page and right after that frames recorded some time after measurement end (signup button visible). Frames are lost. Based on that you should treat every moment of playwright video as possibly inaccurate</strong>.</p>



<p>Having said that, let&#8217;s move on.</p>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95.webm"></video><figcaption class="wp-element-caption">wait for <code>load</code> event at percentile 95, button measured as visible after 914 ms. Mind the significant part of the video with button visible, yet measurement is not finished</figcaption></figure>



<h3 class="wp-block-heading">Experiment benchmark 1 &#8211; no waiting</h3>



<p>Take 1000 samples. Don&#8217;t wait for any event, wait for github call to action button</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers cbp-blur-enabled cbp-unblur-on-hover" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT

// from playwright docs WaitUntilState.COMMIT:
// consider operation to be finished when network response is received and the document started loading
page.navigate(&quot;https://github.com&quot;, NavigateOptions().setWaitUntil(COMMIT))
page.locator(&quot;#summary-val&quot;).first().waitFor()" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// from playwright docs WaitUntilState.COMMIT:</span></span>
<span class="line"><span style="color: #616E88">// consider operation to be finished when network response is received and the document started loading</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT))</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#summary-val&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">first</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">()</span></span></code></pre></div>



<p>In selenium you have to opt out from waiting for <code>DOMCompleteEvent</code> this way</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="val options = new ChromeOptions()
options.setPageLoadStrategy(PageLoadStrategy.NONE)    
val driver = new ChromeDriver(options)
driver.navigate(..).to()" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> options </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new </span><span style="color: #88C0D0">ChromeOptions</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">options.</span><span style="color: #88C0D0">setPageLoadStrategy</span><span style="color: #D8DEE9FF">(PageLoadStrategy.NONE)    </span></span>
<span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> driver </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new </span><span style="color: #88C0D0">ChromeDriver</span><span style="color: #D8DEE9FF">(options)</span></span>
<span class="line"><span style="color: #D8DEE9FF">driver.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">to</span><span style="color: #D8DEE9FF">()</span></span></code></pre></div>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1301" height="887" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95.png" alt="" class="wp-image-119" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95.png 1301w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95-300x205.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95-1024x698.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95-768x524.png 768w" sizes="(max-width: 1301px) 100vw, 1301px" /><figcaption class="wp-element-caption">no wait for event at percentile 95, button measured as visible after 156 ms</figcaption></figure>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95.webm"></video><figcaption class="wp-element-caption">no wait for event at percentile 95, button measured as visible after 156 ms</figcaption></figure>



<p></p>



<h3 class="wp-block-heading">Experiment benchmark 2 &#8211; wait for <code>DOMContentLoaded</code> event</h3>



<p>Take 1000 samples. Wait for <code>DOMContentLoaded</code>, wait wait for github call to action button</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers cbp-blur-enabled cbp-unblur-on-hover" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.DOMCONTENTLOADED

// from playwright docs WaitUntilState.DOMCONTENTLOADED:
// consider operation to be finished when the DOMContentLoaded event is fired.
page.navigate(&quot;https://github.com&quot;, NavigateOptions().setWaitUntil(DOMCONTENTLOADED))
page.locator(&quot;#summary-val&quot;).first().waitFor()" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.DOMCONTENTLOADED</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// from playwright docs WaitUntilState.DOMCONTENTLOADED:</span></span>
<span class="line"><span style="color: #616E88">// consider operation to be finished when the DOMContentLoaded event is fired.</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(DOMCONTENTLOADED))</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#summary-val&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">first</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">()</span></span></code></pre></div>



<p>In selenium you have to opt out from waiting for <code>DOMContentLoaded</code> event this way</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="val options = new ChromeOptions()
options.setPageLoadStrategy(PageLoadStrategy.EAGER)    
val driver = new ChromeDriver(options)
driver.navigate(..).to()" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> options </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new </span><span style="color: #88C0D0">ChromeOptions</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">options.</span><span style="color: #88C0D0">setPageLoadStrategy</span><span style="color: #D8DEE9FF">(PageLoadStrategy.EAGER)    </span></span>
<span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> driver </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new </span><span style="color: #88C0D0">ChromeDriver</span><span style="color: #D8DEE9FF">(options)</span></span>
<span class="line"><span style="color: #D8DEE9FF">driver.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">to</span><span style="color: #D8DEE9FF">()</span></span></code></pre></div>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1303" height="887" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95.png" alt="" class="wp-image-121" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95.png 1303w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95-300x204.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95-1024x697.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95-768x523.png 768w" sizes="(max-width: 1303px) 100vw, 1303px" /><figcaption class="wp-element-caption">wait for <code>DOMContentLoaded</code> event at percentile 95, button measured as visible after 802 ms</figcaption></figure>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95.webm"></video><figcaption class="wp-element-caption">wait for <code>DOMContentLoaded</code> event at percentile 95, button measured as visible after 802 ms. Mind the significant part of the video with button visible, yet measurement is not finished</figcaption></figure>



<p></p>



<h3 class="wp-block-heading">And now let&#8217;s compare the results</h3>



<h4 class="wp-block-heading">P95 comparison</h4>



<figure class="wp-block-table"><table><tbody><tr><td>no waiting</td><td>wait for <code>DOMContentLoaded</code></td><td>wait for <code>load</code> event</td></tr><tr><td>156 ms</td><td>802 ms</td><td>914 ms</td></tr></tbody></table><figcaption class="wp-element-caption">95th percentile  across different type of loading website in playwright/selenium</figcaption></figure>



<p>Percentiles do not answer the question <em>how distribution compares</em>? Down the line you&#8217;re going to see <a href="https://en.wikipedia.org/wiki/Hodges%E2%80%93Lehmann_estimator" target="_blank" rel="noopener" title="">Hodges-Lehman estimator</a></p>



<h4 class="wp-block-heading">What hypotheses are we verifying here?</h4>



<ul class="wp-block-list">
<li><code>load</code> event
<ul class="wp-block-list">
<li>(baseline) null hypothesis: waiting for <code>load</code> event has completion has no performance impact</li>



<li>(experiment 1) alternative hypothesis: waiting for <code>load</code>event has impact on measured latencies</li>
</ul>
</li>



<li><code>DOMContentLoaded</code> event
<ul class="wp-block-list">
<li>(baseline) null hypothesis: waiting for <code>DOMContentLoad</code> event has has no impact on measured latencies</li>



<li>(experiment 2) alternative hypothesis: waiting for <code>DOMContentLoad</code>event has impact on measured latencies</li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading">Waiting for <code>load</code> event VS not waiting for any event</h3>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1303" height="429" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait.png" alt="" class="wp-image-124" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait.png 1303w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait-300x99.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait-1024x337.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait-768x253.png 768w" sizes="(max-width: 1303px) 100vw, 1303px" /><figcaption class="wp-element-caption">83.829% improvement when not waiting for any browser event</figcaption></figure>



<p>Title of this blog post says there is 530% performance degradation when you rely on browser automation defaults. So where is it? When you reverse baseline VS experiment, there it is</p>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1301" height="279" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load.png" alt="" class="wp-image-125" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load.png 1301w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-300x64.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-1024x220.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-768x165.png 768w" sizes="(max-width: 1301px) 100vw, 1301px" /><figcaption class="wp-element-caption">Null hypothesis is rejected, waiting for <code>load</code> event comes with bigger latencies</figcaption></figure>



<p>Null hypothesis is rejected, waiting for <code>load</code> event comes with bigger latencies</p>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1302" height="312" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb.png" alt="" class="wp-image-126" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb.png 1302w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb-300x72.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb-1024x245.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb-768x184.png 768w" sizes="(max-width: 1302px) 100vw, 1302px" /><figcaption class="wp-element-caption">Time to first byte is irrelevant for hypothesis verification. When you remove it from measured latencies it unfolds more accurate impact on frontend part.</figcaption></figure>



<p></p>



<h3 class="wp-block-heading">Waiting for <code>DOMContentLoaded</code> VS not waiting for any event</h3>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1300" height="279" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait.png" alt="" class="wp-image-128" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait.png 1300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait-300x64.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait-1024x220.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait-768x165.png 768w" sizes="(max-width: 1300px) 100vw, 1300px" /><figcaption class="wp-element-caption">79.642% smaller latencies when not waiting for any browser event</figcaption></figure>



<p>Null hypothesis is rejected, waiting for <code>DOMContentLoaded </code>increases measured latencies</p>



<h3 class="wp-block-heading">Waiting for <code>DOMContentLoaded</code> VS waiting for <code>load</code> event</h3>



<p>Having 2 experiments we can compare them too. Since <code>load</code> event comes always after <code>DOMContentLoaded</code> it&#8217;s expected to see bigger latencies which is reflected in measurements</p>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1300" height="278" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load.png" alt="" class="wp-image-129" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load.png 1300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load-300x64.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load-1024x219.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load-768x164.png 768w" sizes="(max-width: 1300px) 100vw, 1300px" /></figure>



<p></p>



<h2 class="wp-block-heading">Based on measurements, what are undesired effects of waiting for <code>DOMContentLoad</code> or <code>load</code> event?</h2>



<ul class="wp-block-list">
<li>To understand better following part take a look at difference between accuracy and precision</li>
</ul>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="764" height="794" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-Accuracy-vs-Precision.png" alt="" class="wp-image-130" style="width:507px;height:auto" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-Accuracy-vs-Precision.png 764w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-Accuracy-vs-Precision-289x300.png 289w" sizes="(max-width: 764px) 100vw, 764px" /></figure>



<ul class="wp-block-list">
<li>The undesired effects</li>



<li>Measuring something neither reflecting user experience nor relevant to user experience. User sees a button and clicks a button regardless of events in the browser
<ul class="wp-block-list">
<li>No user ever thought &#8220;<em>I politely wish this site had dom completion faster</em>&#8220;. User thinks &#8220;<em>Why the f^%@ is this button not working yet?!!??</em>&#8220;</li>
</ul>
</li>



<li>(much) longer benchmarks
<ul class="wp-block-list">
<li>for ones that aim for measurement count, e.g. we want to have 200 samples</li>
</ul>
</li>



<li>Less precise benchmarks
<ul class="wp-block-list">
<li>for ones that aim for duration, e.g. running for 10 minutes</li>



<li>because you will get less samples</li>
</ul>
</li>



<li>Less accurate benchmarks
<ul class="wp-block-list">
<li>because you&#8217;re not measuring what user experiencies</li>



<li>As a result system under benchmark might actually meet some acceptance criteria, while measurements show it&#8217;s not</li>
</ul>
</li>



<li>Disturbs calculating correlations and figuring out frontend latency causation</li>
</ul>



<h2 class="wp-block-heading">Side effects of not waiting for <code>DOMContentLoaded</code> or <code>loaded</code> event</h2>



<p>User can click a visible button, that does not react on clicks yet. Which actually is a reality <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>However that complicates measurements. Frameworks do not check if a button has event listeners. It often requires polling for a desired button state. e.g. await condition click and check if expected message is visible</p>



<h2 class="wp-block-heading">Summary</h2>



<p>I&#8217;ve taken thousands of measurements of a few different sites, not only github. Systems behave the same. Waiting for browser events makes measured latencies inaccurate. It might be 10% away from real user experience, it might be 530%.</p>



<p>It&#8217;s non-zero, reproducible and deterministic. So if you already made an effort to make synthetic performance benchmarks, I&#8217;d recommend putting an effort also in increasing their accuracy.</p>



<h3 class="wp-block-heading">Consider changing default options for browser automation</h3>



<p>For playwright and selenium (and whatever browser automation tools there are) you should opt out from waiting for any browser events if you&#8217;re using those tools for performance measurements.</p>



<figure class="wp-block-table"><table><tbody><tr><td>Option</td><td>Selenium</td><td>Playwright</td></tr><tr><td>wait for response received (<strong>best for performance benchmarks</strong>)</td><td>NONE</td><td>COMMIT</td></tr><tr><td>wait for load event (<strong>default</strong>)</td><td>NORMAL</td><td>LOAD</td></tr><tr><td>wait for DOMContentLoaded event (another option)</td><td>EAGER</td><td>DOMCONTENTLOADED</td></tr></tbody></table></figure>



<p></p><p>The post <a href="http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/">How to make performance benchmark 530% worse by relying on web app automation defaults?</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95.webm" length="43031" type="video/webm" />
<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95.webm" length="41413" type="video/webm" />
<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95.webm" length="41614" type="video/webm" />

			</item>
		<item>
		<title>How to use percentile rank to measure performance</title>
		<link>http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-use-percentile-rank-to-measure-performance</link>
					<comments>http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Sat, 20 Apr 2024 13:52:04 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[estimator]]></category>
		<category><![CDATA[ui-benchmark]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=104</guid>

					<description><![CDATA[<p>If you want to have a simple yet meaningful criteria that answers the question Is my product fast enough? this blog post is for you. First things first. What&#8217;s the goal of a UI measurement? Who consumes UI? Humans from planet Earth, of course. They get annoyed, frustrated and want to throw away your website [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/">How to use percentile rank to measure performance</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h1 class="wp-block-heading">If you want to have a simple yet meaningful criteria that answers the question <code>Is my product fast enough?</code> this blog post is for you.</h1>



<h1 class="wp-block-heading">First things first. What&#8217;s the goal of a UI measurement?</h1>



<p>Who consumes UI? Humans from planet Earth, of course. They get annoyed, frustrated and want to throw away your website out of the window when it&#8217;s too slow. They also think very neat things about your product when they are waiting too long.</p>



<p>So the ultimate goal of measuring UI is to know if/what parts of the system should be improved in order to keep users happy.</p>



<h1 class="wp-block-heading">What question should performance pass / fail criteria answer?</h1>



<p>It should answer the question: &#8220;<em>Are users happy using my product?</em>&#8220;.</p>



<h2 class="wp-block-heading">Why happy user is that important? And what are consequences of having frustrated users because of poor performance?</h2>



<p>You&#8217;d better keep users happy by keeping your site fast. Not only you users suffer. Your business suffers. The longer you&#8217;re making users to wait, the more</p>



<ul class="wp-block-list">
<li>bounce rate goes up</li>



<li>you&#8217;re losing traffic from organic search as search engine prefers faster sites
<ul class="wp-block-list">
<li>It&#8217;s a real deal. I already spoke to people having this particular problem. Google changed the rules (web vitals) and whole team needed to work on performance to bring back lost organic traffic</li>
</ul>
</li>



<li>and for systems that companies use internally daily
<ul class="wp-block-list">
<li>users will start to look for alternative solutions that frustrate them less</li>



<li>or have a grumpy-cat face all day</li>
</ul>
</li>
</ul>



<p>When system passes acceptance criteria it means particular part of your system is fast enough. It means there aren&#8217;t too many frustrated users. Product does not need to be fixed in this area.</p>



<p>When system does not pass acceptance criteria it means it&#8217;s too slow. Which means there are too many frustrated users. Product needs to be fixed in this area.</p>



<h1 class="wp-block-heading">What&#8217;s the perfect threshold for UI pass / fail acceptance criteria?</h1>



<p>0 ms so no one waits for anything <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Jokes aside, let&#8217;s see what research says about latencies.</p>



<h2 class="wp-block-heading">What research says about latency and user frustration</h2>



<ul class="wp-block-list">
<li>https://hpbn.co/primer-on-web-performance/#speed-performance-and-human-perception</li>



<li>Latency longer than 1s causes likely mental context switch. And bounce rate goes up</li>



<li>And that might be our frustration point</li>
</ul>



<p>Even if a particular part of your UI is far away from latency 1s, it might be a good, ambitious goal that you can track. Don&#8217;t lower the expectations just because system will not pass the criteria. You can always measure the gap to passing the criteria and progress on closing the gap.</p>



<h1 class="wp-block-heading">Measuring user happiness and frustration is nothing new and it works well</h1>



<p>There already are well thought concepts. For example</p>



<h2 class="wp-block-heading"><code>APDEX</code></h2>



<ul class="wp-block-list">
<li>https://docs.newrelic.com/docs/apm/new-relic-apm/apdex/apdex-measure-user-satisfaction/</li>



<li>User satisfaction based on T, below which user is satisfied
<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f603.png" alt="😃" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Satisfied, latency <code>&lt;0, T]</code></li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f610.png" alt="😐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Tolerated, latency <code>&lt;T, 4T]</code></li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f621.png" alt="😡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Frustrated, latency <code>&lt;4T, infinity)</code> or <code>error</code></li>
</ul>
</li>



<li>Score [0, 1]
<ul class="wp-block-list">
<li>0 &#8211; all frustrated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f621.png" alt="😡" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>1 &#8211; all satisfied <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f603.png" alt="😃" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>(number of requests within satisfied range + (number of requests within tolerated range) / 2) / total number of requests</li>
</ul>
</li>
</ul>



<p><code>APDEX</code> makes sense. However I will explain percentile rank as even simpler and as powerful concept.</p>



<p>You might have also heard about percentiles, so let&#8217;s compare percentiles and percentile rank in practical terms.</p>



<h2 class="wp-block-heading">Percentiles. Usually P90, P95 or P99</h2>



<p>What&#8217;s the idea?</p>



<p>Sort the measured latencies array ascending. P90 is index in this array at 90th %.</p>



<p>P90 = latencies[latencies.size * 0.9]</p>



<p>P95 = latencies[latencies.size * 0.95]</p>



<p>etc</p>



<p>Using high percentiles (P90-P99.9) you can find out the slowest latencies user expierence.</p>



<p>Calculating percentiles requires whole population (latencies sorted ascending). It&#8217;s not applicable for live monitoring of systems as you have to store every single measured latency.</p>



<h2 class="wp-block-heading">Percentile rank</h2>



<p>Statistical meaning for PR(n)</p>



<ul class="wp-block-list">
<li>How many % of other values are lower than <em>n</em></li>



<li>Llike on exam: how many % of other students you&#8217;ve beaten with your score</li>
</ul>



<p>It doesn&#8217;t require whole population to calculate, you can count it even on stream. Just increment counter of the passed threshold(s).</p>



<p>Implementation is simple</p>



<pre class="wp-block-code"><code>val badCount = 0
val goodCount = 0
val threshold = 1000
latencies.forEach {
 if (it &amp;lt; threshold) goodCount++ else badCount++
}
val percentileRankPercent = goodCount * 100.0 / (badCount + goodCount)
</code></pre>



<h1 class="wp-block-heading">Why not just use percentiles and P90?</h1>



<p>P90 means <code>what's the value at 90%th index of sorted array?</code></p>



<p>PR(1s) means <code>how many latencies are below 1s?</code>. Not <code>below or equal</code> and that makes the difference.</p>



<p>P90 = 1s doesn&#8217;t mean PR(1s) = 90%</p>



<p>Take a look at the example. Given following, sorted array of latencies [s], 10 elements for simplicity</p>



<ul class="wp-block-list">
<li><code>0.25, 0.3, 0.6, 0.6, 0.7, 0.8, 0.85, 0.9, 1.0, 1.4</code>
<ul class="wp-block-list">
<li>P90 = 1.0</li>



<li>PR(1) = 80%</li>



<li>PR(1.1) = 90%</li>



<li>PR(1.5) = 100%</li>
</ul>
</li>
</ul>



<p>Let&#8217;s change the example. Latencies got worse, but P90 stays the same</p>



<ul class="wp-block-list">
<li><code>0.25, 0.3, 0.6, 0.6, 0.7, 1.0, 1.0, 1.0, 1.0, 1.4</code>
<ul class="wp-block-list">
<li>P90 = 1.0</li>



<li>PR(1) = 50%</li>
</ul>
</li>



<li>As you can see, a single percentile does not say how many samples</li>
</ul>



<p>Let&#8217;s change the example. Latencies got even worse, P90 stays the same</p>



<ul class="wp-block-list">
<li><code>1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.4</code>
<ul class="wp-block-list">
<li>P90 = 1.0</li>



<li>PR(1) = 0%</li>
</ul>
</li>
</ul>



<p>As you can see, relying on a single percentile brings a problem. For P90 anything between [0, 90%] of latencies might be as bad as P90.</p>



<p>Percentiles are not designed to answer question <strong>how good/bad this latency is?</strong></p>



<h1 class="wp-block-heading">Let&#8217;s construct the pass / fail acceptance criteria based on percentile rank</h1>



<p>Based on previously mentioned research and 1s as a user frustration point, let&#8217;s construct the acceptance criteria.</p>



<p><code>PR(1s) is expected to be at least 99%</code></p>



<ul class="wp-block-list">
<li>Which means &#8220;<em>At least 99% of website interactions user should experience latency below 1s</em>&#8220;</li>



<li>Which also means &#8220;<em>User can be frustrated by max 1% of website interactions</em>&#8220;</li>
</ul>



<p>What if after a benchmark, criteria is NOT met?</p>



<p>e.g. <code>PR(1s) = 80% and is expected to be at least 99%</code></p>



<p>It means 19% of the responses are too slow. 19% of interactions needs to be fixed.</p>



<h1 class="wp-block-heading">How to measure improvement if you start with PR(1s) = 0%</h1>



<p>You might be in a situation that all latencies are &gt;= 1s. 100% frustrated user. You want to improve the product by chaning something.</p>



<p><strong>Before change</strong></p>



<ul class="wp-block-list">
<li>PR(1s) = 0%</li>



<li>All measured latencies are be above 2s</li>
</ul>



<p><strong>After change, new commits added to the product</strong></p>



<ul class="wp-block-list">
<li>PR(1s) = 0%</li>



<li>All of measured latencies are above 1.5s. It is an improvement, but percentile rank hasn&#8217;t changed.</li>
</ul>



<p>And you can&#8217;t just say &#8220;<em>this improvement doesn&#8217;t matter, user is still frustrated because criteria measuring user&#8217;s frustration is till unmet</em>&#8220;. Some improvements require a lot of iterative work and you need a devloop in which you notice the improvement. Even if it&#8217;s still far away from having happy user.</p>



<p>To compare if there is an improvement you could use comparison benchmark with Hodges-Lehmann estimator.</p>



<ul class="wp-block-list">
<li>That requires access to every single latency from baseline and experiment</li>
</ul>



<p>You could also use a simple measure which I call &#8216;wasted user time&#8217; which you want to keep at 0.</p>



<pre class="wp-block-code"><code>val thresholdMillis = 1000
val allowedDurationMillis = latencies.size * thresholdMillis
val totalDurationMillis = latencies.sumOf { it.toMillis() }
val excessiveDurationMillis = totalDurationMillis - allowedDurationMillis
val wastedUserTimePercent = excessiveDurationMillis * 100.0 / totalDurationMillis
</code></pre>



<p>This way even if percentile rank hasn&#8217;t changed you can still measure the improvement.</p>



<p>And that&#8217;s the exact methodology I&#8217;m using to measure webpages performance and answer questions &#8220;how to improve the system?&#8221;</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="531" src="https://deploymenteveryday.com/wp-content/uploads/2024/04/image-1024x531.png" alt="" class="wp-image-108" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/04/image-1024x531.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/04/image-300x155.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/04/image-768x398.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/04/image.png 1318w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Real life example of Percentile Rank used to check if a website is fast enough</figcaption></figure><p>The post <a href="http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/">How to use percentile rank to measure performance</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Example of 40x speedup using async-profiler recording analysis (JFR file)</title>
		<link>http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file</link>
					<comments>http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Wed, 27 Dec 2023 15:06:56 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[async profiler]]></category>
		<category><![CDATA[bottleneck analysis]]></category>
		<category><![CDATA[flamegraph]]></category>
		<category><![CDATA[JFR]]></category>
		<category><![CDATA[performance improvement]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=76</guid>

					<description><![CDATA[<p>40x performance improvement in this example is series of a few improvements combined. I was trying to check what can be improved during working on a small project. As usual, I run intellij profiler from time to time to see the hottest methods. And that was part of my development workflow aside from TDD. 2x [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/">Example of 40x speedup using async-profiler recording analysis (JFR file)</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><strong>40x performance improvement in this example is series of a few improvements combined.</strong></p>



<p>I was trying to check what can be improved during working on a small project. As usual, I run intellij profiler from time to time to see the hottest methods. And that was part of my development workflow aside from <code>TDD</code>.</p>



<h1 class="wp-block-heading">2x speedup &#8211; remove String.format() usage (technical optimization)</h1>



<p><a href="https://github.com/mgrzaslewicz/set-equalizer/commit/c68b0081335d25782417b95a7e3a78bb1c4239bc" target="_blank" rel="noopener" title="">git commit</a></p>



<p>Remove usage of <code>String.format("Move index %s from %s to %s", indexFrom, listFrom, listTo);</code></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="579" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0-1024x579.png" alt="" class="wp-image-81" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0-1024x579.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0-300x170.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0-768x434.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0.png 1207w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>It&#8217;s not surprising it was slow, but discovering that is not obvious. Imagine you&#8217;re introduced to a new, big project &#8211; there is no way you would eyeball the code and say this is a <code>bottleneck</code> (<code>hot method</code>)</p>



<h2 class="wp-block-heading">10x speedup &#8211; replace streams with for each (technical optimization).</h2>



<p><a href="https://github.com/mgrzaslewicz/set-equalizer/commit/59a124aaf2e33f58695b76b3e5568dc6e27d10b3" target="_blank" rel="noopener" title="">git commit</a></p>



<p>Replace</p>



<p><code>return calculators.stream().mapToInt(c -&gt; c.calculate(listA, listB)).sum();</code></p>



<p>With</p>



<pre class="wp-block-code"><code>  int sum = 0;
  for (var calculator : calculators) {
      sum += calculator.calculate(listA, listB);
  }
  return sum;</code></pre>



<p>Bottleneck (hot method) found</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="807" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1-1024x807.png" alt="" class="wp-image-84" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1-1024x807.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1-300x236.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1-768x605.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1.png 1233w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">2x speedup &#8211; add sum caching (technical optimization).</h2>



<p><a href="https://github.com/mgrzaslewicz/set-equalizer/commit/8b3f2f431b23a9d7fa29501f6f010fcac5dc13e6">git commit</a></p>



<p>Replace</p>



<pre class="wp-block-code"><code>  private int sum(List&lt;Integer&gt; list) {
      int result = 0;
      for (var i : list) {
          result += i;
      }
      return result;
      return Math.abs(listA.sum() - listB.sum());
  }</code></pre>



<p>With a sum caching <code>java.util.List</code> decorator</p>



<pre class="wp-block-code"><code>  public class SumCachingList implements SummingList {
      private final List&lt;Integer> decorated;

      public SumCachingList(List&lt;Integer> decorated) {
          this.decorated = decorated;
      }

      private int sum;
      private boolean sumCalculated = false;

      @Override
      public int sum() {
          if (!sumCalculated) {
              calculateSum();
              sumCalculated = true;
          }
          return sum;
      }

      private void calculateSum() {
          for (var i : decorated) {
              sum += i;
          }
      }

      @Override
      public boolean add(Integer integer) {
          sum = sum() + integer;
          return decorated.add(integer);
      }

      @Override
      public boolean remove(Object o) {
          var removed = decorated.remove(o);
          if (removed) {
              sum = sum() - (Integer) o;
          }
          return removed;
      }

     @Override
     public boolean removeIf(Predicate&lt;? super Integer> filter) {
         var anyRemoved = decorated.removeIf(filter);
         if (anyRemoved) {
             sumCalculated = false;
         }
         return anyRemoved;
     }

      // ...
  }</code></pre>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="659" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0-1024x659.png" alt="" class="wp-image-85" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0-1024x659.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0-300x193.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0-768x494.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0.png 1235w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="185" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-1024x185.png" alt="" class="wp-image-86" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-1024x185.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-300x54.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-768x138.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-1536x277.png 1536w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0.png 1575w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>After above optimizations flamegraph looks like that</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="249" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-1024x249.png" alt="" class="wp-image-87" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-1024x249.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-300x73.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-768x187.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-1536x374.png 1536w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0.png 1577w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h1 class="wp-block-heading">Big picture optimisation VS technical optimisation</h1>



<p>Ok, now you see that you can discover bottlenecks using async profiler recording. Above example is rather an easy one. For huge systems with tons of legacy code which is hard to maintain and change, it might be really hard.</p>



<p>Even discovering slow parts of big systems is hard, but that&#8217;s a topic for another blog post.</p>



<p>Technical optimizations are often easier to find.</p>



<p><strong>Technical optimization</strong></p>



<p>You don&#8217;t necessarily have to understand what the application is doing. You can read straight from the flamegraph that adding a cache or optimizing loops is going to help.</p>



<p><strong>Big picture optimisation</strong></p>



<p>You need to understand what your application is doing. You can optimize by organizing processing in a different way, e.g. discovering that you don&#8217;t have to fetch and process some data to in order to display particular piece of frontend.</p>



<h1 class="wp-block-heading">Run integration tests with profiler regularly</h1>



<p>That should be part of your development process. Like running integration tests.</p>



<p>In a perfect case, scenario of integration test should be as similar as possible to production scenario. This way you&#8217;d discover potential performance improvements or issues before production deployment.</p>



<h1 class="wp-block-heading">Know the difference between async profiler sampling modes: <code>Wall Clock</code> and <code>CPU</code> usage only</h1>



<h2 class="wp-block-heading">Wall Clock (Total Time)</h2>



<p>If you&#8217;re interested in real latency of your system, including</p>



<p>IO, e.g.</p>



<ul class="wp-block-list">
<li>connection polling</li>



<li>DB transaction locks</li>



<li>reads/writes</li>
</ul>



<p>synchronization, e.g.</p>



<ul class="wp-block-list">
<li>waiting for critical section access</li>



<li>waiting for tasks in a thread pool</li>
</ul>



<p>use <code>Wall Clock</code> sampling in async-profiler. This mode will collect events from threads in <code>SLEEPING</code> state also.</p>



<p>Beware it affects performance of measured process more than measuring only <code>ACTIVE</code> threads. Why? Because measured JVM has more threads to iterate over every interval.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="576" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-1024x576.png" alt="" class="wp-image-89" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-1024x576.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-300x169.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-768x432.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-800x450.png 800w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0.png 1090w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">CPU only</h2>



<p>In other words, without <code>Wall Clock</code> mode, measuring sampling only JVM threads in <code>ACTIVE</code> state</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="573" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0-1024x573.png" alt="" class="wp-image-90" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0-1024x573.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0-300x168.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0-768x430.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0.png 1093w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">In the examples above you can see hot method <code>httpClient.newCall(request).execute()</code></h3>



<ul class="wp-block-list">
<li>Time spent measured with <code>CPU Time</code> is 120 ms</li>



<li>Time spent measured with <code>Wall Clock</code> (<code>Total Time</code>) is 9170 ms</li>
</ul>



<p>Would you optimize CPU time in this case? Probably not. You&#8217;d first focus on IO as it&#8217;s the bottleneck.</p>



<p class="has-luminous-vivid-amber-background-color has-background"><strong>Subject to optimize needs to be chosen case by case. In order to have this choice, you need have both <code>CPU Time</code> and <code>Total Time</code> in flamegraph &#8211; and it&#8217;s available only when using wall clock mode sampling</strong>.</p>



<h1 class="wp-block-heading">Resources</h1>



<ul class="wp-block-list">
<li><em><a href="https://plv.colorado.edu/papers/mytkowicz-pldi10.pdf" title="Evaluating the Accuracy of Java Profilers">Evaluating the Accuracy of Java Profilers</a></em></li>



<li>Safepoint bias problem
<ul class="wp-block-list">
<li><a href="https://jpbempel.github.io/2022/06/22/debug-non-safepoints.html">https://jpbempel.github.io/2022/06/22/debug-non-safepoints.html</a></li>



<li><a href="https://psy-lob-saw.blogspot.com/2015/12/safepoints.html">https://psy-lob-saw.blogspot.com/2015/12/safepoints.html</a></li>
</ul>
</li>



<li><a href="https://github.com/mgrzaslewicz/set-equalizer" target="_blank" rel="noopener" title="">https://github.com/mgrzaslewicz/set-equalizer</a></li>
</ul><p>The post <a href="http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/">Example of 40x speedup using async-profiler recording analysis (JFR file)</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How To Provision Your Ubuntu Workstation &#8211; Workstation As a Code (WaC)</title>
		<link>http://deploymenteveryday.com/how-to-provision-your-workstation-workstation-as-a-code-wac/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-provision-your-workstation-workstation-as-a-code-wac</link>
					<comments>http://deploymenteveryday.com/how-to-provision-your-workstation-workstation-as-a-code-wac/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Tue, 05 Dec 2023 21:20:17 +0000</pubDate>
				<category><![CDATA[infrastructure as a code]]></category>
		<category><![CDATA[ansible]]></category>
		<category><![CDATA[provision]]></category>
		<category><![CDATA[wac]]></category>
		<category><![CDATA[workstation as a code]]></category>
		<guid isPermaLink="false">http://deploymenteveryday.com/?p=51</guid>

					<description><![CDATA[<p>Why would you want to have fully automated provisioning of your ubuntu workstation(s)? Here goes the full list of software I&#8217;m using regularly or occasionally To sum up: what&#8217;s the problem to solve? I want to run single command to have my machine set up or updated in a few minutes.I don&#8217;t want to be [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/how-to-provision-your-workstation-workstation-as-a-code-wac/">How To Provision Your Ubuntu Workstation – Workstation As a Code (WaC)</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h1 class="wp-block-heading">Why would you want to have fully automated provisioning of your ubuntu workstation(s)?</h1>



<ul class="wp-block-list">
<li>If you also love automating everything and hate doing repeatable job more than a few times, consider automating provisioning your workstation machine.</li>



<li>You regularly or occasionally use many apps from <code>apt</code> and <code>snap</code> repositories.</li>



<li>You&#8217;re using some tools that are not accessible via <code>apt</code> and <code>snap</code> repositories. Those tools get updates and I really wanted to consume them regularly. Manual updates were a no-go anymore as there are too many of them.</li>



<li>In my case over time, the list got big enough to make it impossible to move easily to another workstation.</li>



<li>Moreover, I wanted to keep portable <code>dotfiles</code> between machines, have a backup, easy restore and history of changes.<br>What does my workstations setup look like?
<ul class="wp-block-list">
<li>stationary workstation <code>2x ubuntu</code> on 2 different drives.
<ul class="wp-block-list">
<li><code>primary</code> That&#8217;s where I spend most of my working time</li>



<li><code>fallback</code> in case primary fails for whatever reason</li>
</ul>
</li>



<li>laptop <code>1x ubuntu</code>
<ul class="wp-block-list">
<li>I grab it when going to the office</li>
</ul>
</li>
</ul>
</li>



<li>As you can see, there are 3 ubuntu installations. I want to switch between workstations at any time without losing updates to software, its configuration and my dotfiles.</li>
</ul>



<h2 class="wp-block-heading">Here goes the full list of software I&#8217;m using regularly or occasionally</h2>



<ul class="wp-block-list">
<li>It might have evolve from the time of writing this blog post, but you get the idea. Just imagine updating those manually or providing configuration &#8211; from the scratch on a different workstation.<br>I&#8217;m an experienced java/kotlin developer hence many tools are related to backend programming and debugging.
<ul class="wp-block-list">
<li><code>apt</code>
<ul class="wp-block-list">
<li><a href="https://github.com/sharkdp/bat" target="_blank" rel="noopener" title="">bat</a> # alternative to <code>cat</code></li>



<li><a href="https://github.com/sharkdp/hexyl" target="_blank" rel="noopener" title="">hexyl</a> # hex viewer</li>



<li>crudini # to manipulate ini files from command line easily</li>



<li><a href="https://github.com/sharkdp/fd" target="_blank" rel="noopener" title="">fd-find</a> # alternative to <code>find</code> with some improvements</li>



<li>imagemagic
<ul class="wp-block-list">
<li>I generate PDFs from images from time to time, it requires tool and conf. I don&#8217;t want to remember about providing special config entries every time I change machine in <code>/etc/ImageMagick-6/policy.xml</code></li>
</ul>
</li>



<li>mc</li>



<li>nmap</li>



<li>oathtool # 2FA code, e.g. oathtool -b &#8211;totp &#8216;secret code&#8217; | xclip -sel clip</li>



<li>paprefs # to have virtual device capable of streaming via multiple physical devices</li>



<li>python3</li>



<li>python3-pip</li>



<li>pipx</li>



<li>snap</li>



<li>speech-dispatcher # for speaking exit code in terminal of last executed command</li>



<li><a href="https://github.com/tmux/tmux" target="_blank" rel="noopener" title="">tmux</a></li>



<li><a href="https://github.com/tmuxinator/tmuxinator" target="_blank" rel="noopener" title="">tmuxinator</a></li>



<li>unrar</li>



<li>unzip</li>



<li>vagrant</li>



<li>virtualbox</li>



<li>yarn</li>



<li>zbar-tools # to scan QR codes from image or camera</li>
</ul>
</li>



<li><code>snap</code> packages
<ul class="wp-block-list">
<li>foobar2000</li>



<li>intellij-idea-ultimate</li>



<li>postman</li>



<li>signal-desktop</li>



<li>spotify</li>



<li>sublime-text</li>



<li>zoom-client
<ul class="wp-block-list">
<li>and its configuration to open meetings from browser links</li>
</ul>
</li>



<li>yq</li>
</ul>
</li>



<li>a few <code>python</code> packages</li>



<li>git
<ul class="wp-block-list">
<li>git-global-config</li>



<li><a href="https://github.com/jesseduffield/lazygit" target="_blank" rel="noopener" title="">lazygit</a></li>



<li>custom aliases <a href="https://github.com/mgrzaslewicz/dotfiles/blob/master/gitalias.txt">1</a>, <a href="https://github.com/mgrzaslewicz/dotfiles/blob/master/my-gitalias.txt">2</a></li>
</ul>
</li>



<li>knowledge
<ul class="wp-block-list">
<li><a href="https://github.com/cheat/cheat">cheat</a> and cheatsheets</li>



<li><a href="https://github.com/logseq/logseq" target="_blank" rel="noopener" title="">loqseq</a></li>
</ul>
</li>



<li>zsh
<ul class="wp-block-list">
<li>oh-my-zsh</li>



<li>powerlevel10k</li>



<li>aliases</li>
</ul>
</li>



<li>java
<ul class="wp-block-list">
<li><a href="https://eclipse.dev/mat/" target="_blank" rel="noopener" title="">eclipse-mat</a></li>



<li>jdk v8, v9, v11, v14, v17, v19, v21</li>



<li>jdk-tools</li>



<li>jenv # to use JDK version set per project
<ul class="wp-block-list">
<li>and its zsh conf</li>
</ul>
</li>



<li><a href="https://bitbucket.org/mjensen/mvnvm/src/master/" target="_blank" rel="noopener" title="">mvnvm</a> (maven wrapper)</li>
</ul>
</li>



<li>docker
<ul class="wp-block-list">
<li>docker-registry</li>



<li><a href="https://github.com/jesseduffield/lazydocker">lazydocker</a></li>
</ul>
</li>



<li>javascript
<ul class="wp-block-list">
<li><a href="https://github.com/nvm-sh/nvm" target="_blank" rel="noopener" title="">nvm</a> (node wrapper) and its zsh conf</li>
</ul>
</li>



<li>browser
<ul class="wp-block-list">
<li>brave</li>



<li>chromium</li>



<li>edge</li>



<li>firefox</li>



<li>tor</li>
</ul>
</li>



<li>various
<ul class="wp-block-list">
<li><a href="https://dystroy.org/broot/" target="_blank" rel="noopener" title="">broot</a></li>



<li><a href="https://github.com/dandavison/delta" target="_blank" rel="noopener" title="">delta</a></li>



<li>imagemagick</li>



<li><a href="https://github.com/Prayag2/konsave" target="_blank" rel="noopener" title="">konsave</a></li>



<li><a href="https://github.com/lsd-rs/lsd" target="_blank" rel="noopener" title="">lsd</a></li>



<li><a href="https://github.com/cantino/mcfly" target="_blank" rel="noopener" title="">mcfly</a> and its zsh conf</li>



<li><a href="https://github.com/sachaos/viddy" target="_blank" rel="noopener" title="">viddy</a><br>zoom-client<br><a href="https://github.com/ajeetdsouza/zoxide">zoxide</a> # smarter <code>cd</code> alternative</li>
</ul>
</li>



<li>dotfiles
<ul class="wp-block-list">
<li><a href="https://github.com/mgrzaslewicz/dotfiles" target="_blank" rel="noopener" title="">home-dir-as-git-repository</a>
<ul class="wp-block-list">
<li><a href="https://github.com/mgrzaslewicz/dotfiles/blob/master/.tmux.conf" target="_blank" rel="noopener" title="">tmux</a></li>



<li>tmuxinator sessions for multiple projects I&#8217;m working on
<ul class="wp-block-list">
<li>my own</li>



<li>company I&#8217;m working for (Atlassian at the time of writing)</li>
</ul>
</li>



<li>project-specific bash/zsh aliases</li>



<li>various apps settings</li>
</ul>
</li>
</ul>
</li>



<li>vim
<ul class="wp-block-list">
<li><a href="https://github.com/mgrzaslewicz/dotfiles/blob/master/.vimrc" target="_blank" rel="noopener" title="">config</a></li>



<li>vim-plug</li>



<li>neovim</li>
</ul>
</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading">To sum up: what&#8217;s the problem to solve?</h2>



<p><strong>I want to run single command</strong> to have my machine set up or updated in a few minutes.<br>I don&#8217;t want to be afraid of upgrading or reinstalling system. I don&#8217;t want to miss anything when changing machine I&#8217;m working on. And I want to have fun and joy of just running <code>provision</code> command to setup my workstation</p>



<ul class="wp-block-list">
<li>install all the software I need. And I need dozens of tools</li>



<li>update software versions regardless of download source</li>



<li>restore <code>dotfiles</code></li>
</ul>



<h1 class="wp-block-heading">Benefits of automated workstation provisioning</h1>



<ul class="wp-block-list">
<li>You don&#8217;t have to care about configuring manually your laptop or any other machine running ubuntu.</li>



<li>You just run <code>provision</code>, go for a coffee and after coming back &#8211; everything is configured exactly your way</li>



<li>You can throw away your machine and have the same configuration within minutes
<ul class="wp-block-list">
<li>It took many hours to get to this point in my case, but it&#8217;s totally worth it.</li>
</ul>
</li>



<li>It trains your mindset that you can apply to other projects: your machine setup is a project and list of git commits</li>



<li>You know exactly what toolset you have. When you forget something, just take a look into the repository</li>
</ul>



<h1 class="wp-block-heading">Side effects of automated workstation provisioning</h1>



<p>Whenever you make a change to configuration, you have to make a change in the code. That&#8217;s a approach shift</p>



<p>Thanks to that, manual changes do not get lost as you&#8217;re relying on automation instead <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<ul class="wp-block-list">
<li>Manual changes are extremely easy to forget about</li>



<li>Manual changes can be overwritten by accident. <em>Can</em> means it <strong>will</strong> happen</li>



<li>You&#8217;re going to forget about your own decisions in a few months from now. In git history, you can keep the <code>why</code> for changes</li>
</ul>



<h1 class="wp-block-heading">Let&#8217;s switch to solution mode: how to implement WaC?</h1>



<p>You don&#8217;t need anything fancy for provisioning, however bunch of bash scripts is IMHO a no-go for a maintainable project. If you want a robust solution, it needs to have timeouts, retries, self verification of changes and good reporting. There is already an out-of-the-box solution for that.</p>



<h2 class="wp-block-heading">ansible for provisioning workstation and keeping it up to date</h2>



<p>Way better than tons of bash scripts. Maintainable and easy to read.</p>



<h2 class="wp-block-heading">HOME dir as a git repository</h2>



<h3 class="wp-block-heading">Did you know you can use multiple git repositories in the same folder?</h3>



<p>So in fact you can use your HOME dir as many git repositories</p>



<p>This way you can use dotfiles for as many projects as you want or need</p>



<p>e.g. <code>GIT_DIR=.git_my_project git pull</code></p>



<ul class="wp-block-list">
<li>and this way you can keep your project-specific aliases in a dedicated repository</li>
</ul>



<h2 class="wp-block-heading">tmux + tmuxinator</h2>



<ul class="wp-block-list">
<li>I just type <code>tmuxinator autocoin</code>, <code>tmuxinator jira</code>, <code>tmuxinator jpt</code> etc. and have all the right set of console tabs open. Even automatic ssh connection in a separate console tab is waiting for me</li>
</ul>



<pre class="wp-block-code has-black-background-color has-background"><code>&lt;% REPO_BASE = "~/repos/autocoin" %&gt;
&lt;% DATA_BASE = "~/repos/autocoin/data" %&gt;
project_name: autocoin
project_root: &lt;%= REPO_BASE %&gt;
windows:
  - mediator:
      root: &lt;%= REPO_BASE %&gt;/autocoin-exchange-mediator
      layout: tiled
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-exchange-mediator/
        - &lt;%= REPO_BASE %&gt;/autocoin-exchange-mediator/scripts
        - &lt;%= DATA_BASE %&gt;/autocoin-exchange-mediator
  - binance-bot:
      root: &lt;%= REPO_BASE %&gt;/autocoin-binance-bot
      layout: tiled
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-binance-bot
        - &lt;%= REPO_BASE %&gt;/autocoin-binance-bot
        - ~/.trading-bot/file-repository
  - gateway:
      root: &lt;%= REPO_BASE %&gt;/exchange-gateway
      layout: tiled
      panes:
        - &lt;%= REPO_BASE %&gt;/exchange-gateway
        - &lt;%= REPO_BASE %&gt;/exchange-gateway/scripts
  - arbitrage-monitor:
      layout: tiled
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-arbitrage-monitor
        - &lt;%= REPO_BASE %&gt;/autocoin-arbitrage-monitor/scripts
        - &lt;%= DATA_BASE %&gt;/autocoin-arbitrage-monitor
  - auth:
      layout: tiled
      root: &lt;%= REPO_BASE %&gt;/autocoin-auth-service
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-auth-service
        - &lt;%= REPO_BASE %&gt;/autocoin-auth-service/scripts
        - &lt;%= DATA_BASE %&gt;/autocoin-auth-service
  - frontend:
      root: &lt;%= REPO_BASE %&gt;/autocoin-frontend
      layout: even-horizontal
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-frontend
        - &lt;%= REPO_BASE %&gt;/autocoin-frontend
  - xchange: &lt;%= REPO_BASE %&gt;/XChange
  - prod:
      layout: tiled
      panes:
        - ssh-autocoin-prod
        - ssh-autocoin-prod
        - ssh-autocoin-prod
        - ssh-autocoin-prod
  - balance-monitor:
      root: &lt;%= REPO_BASE %&gt;/autocoin-balance-monitor
      layout: tiled
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-balance-monitor
        - &lt;%= REPO_BASE %&gt;/autocoin-balance-monitor/scripts
        - &lt;%= DATA_BASE %&gt;/autocoin-balance-monitor
  - strategy-executor:
      root: &lt;%= REPO_BASE %&gt;/autocoin-strategy-executor
      layout: even-horizontal
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-strategy-executor
        - &lt;%= REPO_BASE %&gt;/autocoin-strategy-executor/scripts
  - puppet-deprecated:
      root: &lt;%= REPO_BASE %&gt;/autocoin-puppet
      layout: tiled
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-puppet
        - &lt;%= REPO_BASE %&gt;/autocoin-infrastructure/machine-developer
  - infrastructure-provision:
      root: &lt;%= REPO_BASE %&gt;/autocoin-infrastructure-provision
      layout: even-horizontal
      panes:
        - &lt;%= REPO_BASE %&gt;/autocoin-infrastructure-provision
        - &lt;%= REPO_BASE %&gt;/autocoin-infrastructure-provision
  - db: &lt;%= REPO_BASE %&gt;/autocoin-db
  - metrics-client: &lt;%= REPO_BASE %&gt;/autocoin-metrics-client
  - telegraf-influx-grafana: &lt;%= REPO_BASE %&gt;/autocoin-tig-monitoring
  - autocoin-data: &lt;%= REPO_BASE %&gt;/data</code></pre>



<h1 class="wp-block-heading">Lessons learned</h1>



<h2 class="wp-block-heading">ansible</h2>



<ul class="wp-block-list">
<li>It does its job quite good for linear tasks. However I would not recommend using it for more advanced problems like running things in parallel to speed up provisioning within a single machine. YML is not meant for advanced programming</li>



<li>I haven&#8217;t used <code>ansible</code> before starting <code>ubuntu-workstation-provision</code> project. Ansible documentation is quite impressive and has good examples. It&#8217;s easy to learn.</li>
</ul>



<h2 class="wp-block-heading">Bash installation scripts</h2>



<ul class="wp-block-list">
<li>Some tools on github have a installation script e.g. <code>wget &lt;link&gt; | bash</code>. It&#8217;s a bit risky as the script might do anything or get hacked, but I accept the risk in favor of convenience.<br>On the other hand I applied similar approach to provide easy dotfiles installation for any project and that&#8217;s the code I&#8217;m in control of &#8211; so no risk. <a href="https://github.com/mgrzaslewicz/dotfiles/blob/master/install-mg-dotfiles.sh">Example here</a> so you can create something similar on your own</li>
</ul>



<h2 class="wp-block-heading">Fail fast with fresh virtualbox machine</h2>



<p>Running provisioning on a fresh virtualbox machine uncovers some problems and I run provisioning of such machines from time to time. Here&#8217;s an example how to use ansible with vagrant</p>



<pre class="wp-block-code has-black-background-color has-background"><code>Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/noble64"
config.vm.provision "ansible" do |ansible|
  ansible.playbook = "/repos/ubuntu-workstation-provision/playbooks/provision.yml"
  #ansible.skip_tags = "graphical"
end
end</code></pre>



<h2 class="wp-block-heading">Things to avoid</h2>



<p>Try not to parse 3rd party HTML to get software versions if there is no API providing latest version</p>



<ul class="wp-block-list">
<li>HTML is not an API and your automation will break at any time
<ul class="wp-block-list">
<li>You&#8217;re going to have more maintenance work</li>



<li>Example regarding zoom installation
<ul class="wp-block-list">
<li>Initially I used <code>zoom-client</code> installation via snap as there is no zoom in apt repositories</li>



<li>However it was failing to open zoom meeting via clicking on a link in browser <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f979.png" alt="🥹" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>So I moved to installing .deb package downloaded from <a href="https://zoom.us/download?os=linux">official zoom site</a></li>



<li>Version was buried in the HTML code so I parsed it with regex <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26d4.png" alt="⛔" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>I finally had working zoom meeting links <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>And one day HTML on zoom site was changed, parsing latest version failed <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f979.png" alt="🥹" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>So I switched back to installing zoom via snap and zoom links were broken again <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f979.png" alt="🥹" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>And I provided proper KDE configuration to make zoom links work as expected <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>
</ul>
</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading">Yet another experience strengthening belief that <span style="text-decoration: underline;"><strong>automation allowing to throw away and recreate things wins over keeping state and list of steps</strong></span></h2>



<ul class="wp-block-list">
<li>Approach with manual steps that you run or delegate to other developers is
<ul class="wp-block-list">
<li>not scalable</li>



<li>not maintainable</li>



<li>hard to reproduce by others (and ==you== in the near future)</li>



<li>error prone
<ul class="wp-block-list">
<li>sooner or later you or other developer will make a mistake. Period. I&#8217;ve seen it dozens of times in many projects. It also relates to installing software and configuring it.</li>
</ul>
</li>
</ul>
</li>



<li>Where else can you see the power of throwing away and recreating easily? A few examples
<ul class="wp-block-list">
<li>Docker images
<ul class="wp-block-list">
<li>They have a recipe that creates an image. If you rely on manually created accessible in some repository, butone with no recipe (no <code>Dockerfile</code>) you will get into trouble</li>
</ul>
</li>



<li>Test datasets
<ul class="wp-block-list">
<li>In the performance team we missed AWS bucket deadline and lost our test datasets. No one new how those were created and people who created them were no longer working in the company. We were fucked for a while and had to create automation to recreate them anyway.</li>
</ul>
</li>



<li>Browser tabs
<ul class="wp-block-list">
<li>Why bother keeping all open for your project?<br>It&#8217;s easy to lose it and have fear of closing the tabs. Just use session manager to save the session, reopen it at any time and have the state you want again. Then you won&#8217;t have to keep dozens of browser tabs open</li>
</ul>
</li>



<li>Performance testing
<ul class="wp-block-list">
<li>Create a process every developer in the company can run easily, in a perfect world with a single command. Now compare that to list of 20 steps to run manually listed on confluence page<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f926-1f3fb-200d-2642-fe0f.png" alt="🤦🏻‍♂️" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>
</ul>
</li>



<li>Example from <code>workspace-provision</code> project
<ul class="wp-block-list">
<li>Manual <code>eclipse-mat</code> download takes 2 minutes if just unzipped and run. However during manual installation, I&#8217;ve discovered with trial and error that actual steps to have it working goes as follow:
<ul class="wp-block-list">
<li>1) download eclipse-mat</li>



<li>2) unzip it</li>



<li>3) move it to <code>/opt</code> directory</li>



<li>4) set <code>-Xmx4g</code> in <code>MemoryAnalyzer.ini</code>. Default heap size is too low and I will have to do it anyway after download
<ul class="wp-block-list">
<li>Why? I already had it consuming 100% CPU and taking ages to analyze heap dump. GC was running all the time</li>
</ul>
</li>



<li>5) set it up to use java version 17
<ul class="wp-block-list">
<li>Why? It&#8217;s the minimum required by eclipse-mat. I have multiple JVMs so there is no best default for the whole system. It just didn&#8217;t want to start because of too low default java version</li>
</ul>
</li>



<li>6) create symlink <code>/user/local/bin/mat</code> to run it easily from <code>command line</code> or <code>krunner</code></li>
</ul>
</li>



<li>It&#8217;s very likely I will forget step 4 and 5 when installing newer version or trying to run it on another ubuntu (including other developer&#8217;s machine)</li>



<li>Having it automated means I can send someone a command to run
<ul class="wp-block-list">
<li><code>git clone https://github.com/mgrzaslewicz/ubuntu-workstation-provision.git</code></li>



<li><code>TAGS=eclipse-mat ./provision</code></li>



<li>and it&#8217;s
<ul class="wp-block-list">
<li>scalable
<ul class="wp-block-list">
<li>n other developers can run it without asking me for anything</li>
</ul>
</li>



<li>maintainable
<ul class="wp-block-list">
<li>fix needed? No problem, just change the code</li>
</ul>
</li>



<li>easy to reproduce by others
<ul class="wp-block-list">
<li>no brainer, just copy paste</li>
</ul>
</li>



<li>error proof
<ul class="wp-block-list">
<li>well, you can always make an error while copying and pasting <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f979.png" alt="🥹" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>



<h1 class="wp-block-heading">Automated windows provisioning?</h1>



<ul class="wp-block-list">
<li>Nowadays I use windows to play games only, but I used to use it as a workstation too so I had some attempts for provisioning automation.</li>



<li>Take a look at <code>chocolatey</code>. It&#8217;s a windows package manager so it&#8217;s easy to install most of needed software automatically. It can be used with ansible too. And that&#8217;s the <a href="https://gist.github.com/mgrzaslewicz/ac916081a6923e597da63d0e408a3b19">simple provisioning script using chocolatey</a> I used to use.</li>
</ul>



<h1 class="wp-block-heading">Resources summary</h1>



<ul class="wp-block-list">
<li><a href="https://github.com/ibraheemdev/modern-unix">set of alternative/better/fresh unix tools: github.com/ibraheemdev/modern-unix</a></li>



<li>WaC implementation: <a href="https://github.com/mgrzaslewicz/ubuntu-workstation-provision" target="_blank" rel="noopener" title="">github.com/mgrzaslewicz/ubuntu-workstation-provision</a>
<ul class="wp-block-list">
<li>it&#8217;s using also <a href="https://github.com/mgrzaslewicz/dotfiles" target="_blank" rel="noopener" title="">HOME dir as git repo</a></li>
</ul>
</li>
</ul>



<h1 class="wp-block-heading">What does your WaC look like?</h1>



<p>Have you already tried implementing your own workstation as a code? What have you learned, what worked well, what were you struggling with?</p><p>The post <a href="http://deploymenteveryday.com/how-to-provision-your-workstation-workstation-as-a-code-wac/">How To Provision Your Ubuntu Workstation – Workstation As a Code (WaC)</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/how-to-provision-your-workstation-workstation-as-a-code-wac/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Not Giving Up After 4 years, 30 One Time Customers And SaaS Failure</title>
		<link>http://deploymenteveryday.com/not-giving-up-after-4-years-30-one-time-customers-and-saas-failure/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=not-giving-up-after-4-years-30-one-time-customers-and-saas-failure</link>
					<comments>http://deploymenteveryday.com/not-giving-up-after-4-years-30-one-time-customers-and-saas-failure/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Mon, 02 Jan 2023 10:05:47 +0000</pubDate>
				<category><![CDATA[saas]]></category>
		<category><![CDATA[arbitrage]]></category>
		<category><![CDATA[cryptocurrency]]></category>
		<guid isPermaLink="false">http://deploymenteveryday.com/?p=16</guid>

					<description><![CDATA[<p>TLDR: I learned from creating an arbitrage monitor (arbitrage scanner) that&#160; Have you ever wondered what effort it requires to build a cryptocurrency arbitrage monitor (scanner)? Can you make money from executing cryptocurrency arbitrage opportunities? At the time of writing (Jan 2023) autocoin-trader.com serves 2 main functionalities: It got 30+ paid subscriptions, but almost no [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/not-giving-up-after-4-years-30-one-time-customers-and-saas-failure/">Not Giving Up After 4 years, 30 One Time Customers And SaaS Failure</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>TLDR: I learned from creating an arbitrage monitor (arbitrage scanner) that&nbsp;</p>



<ul class="wp-block-list">
<li>People want easy, 1-click money</li>



<li>There is high demand for software that might help achieve that. But such software is impossible to create. There is no easy money. In order to make money, you need to put effort into whatever software you use.</li>



<li>It&#8217;s hard to build you own product</li>
</ul>



<h1 class="wp-block-heading">Have you ever wondered what effort it requires to build a cryptocurrency arbitrage monitor (scanner)?</h1>



<p>Can you make money from executing cryptocurrency arbitrage opportunities?</p>



<p>At the time of writing (Jan 2023) autocoin-trader.com serves 2 main functionalities:</p>



<ul class="wp-block-list">
<li>Find and display arbitrage opportunities with list of steps to execute them</li>



<li>Calculates your cryptocurrency portfolio balance based on different sources: wallets, coins, exchanges</li>
</ul>



<p>It got 30+ paid subscriptions, but almost no recurring ones.&nbsp;</p>



<p>Before I got to this point it was a long journey full of motivation ups and downs.</p>



<p>I&#8217;m a highly experienced backend java+kotlin developer and started working commercially in 2007. Angular based frontend is a no-brainer to me…</p>



<p>And at the end of the day it seems good technical skills are not what makes a successful product/saas <strong>at all</strong>.<br>Ready for a journey? Let’s go</p>



<h1 class="wp-block-heading">2017 Initial idea: Why not make a cryptocurrency trading bot and earn easy money?</h1>



<p>I had a brilliant idea. Why not make a bot that will buy and sell cryptocurrencies and make money for me on autopilot? So I would just press enter and wait for money to multiply? Sounds sexy! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>I would just put effort into creating and maintaining the bot, money would multiply itself.</p>



<p>It just needs some buy and sell strategy. I started with a really simple one. Sell when price grows 5%, buy when price falls 5%.</p>



<p>I did some backtesting to see how the strategy behaves with historical data. As far as I remember it had some advantages compared to just holding ETH. At least in the simulation.</p>



<p>When I started executing the strategy at bittrex exchange with ETH, I realized in the short term it’s losing money doing a few trades daily. I run it also with <a href="https://coinmarketcap.com/currencies/golem-network-tokens/">GLM (golem network)</a> cryptocurrency and results were similar.</p>



<p>After a while it occurred to me that more advanced price analysis is required. I researched what technical analysis libraries there are. I started using ta4j as the most interesting one. <a href="https://github.com/ta4j/ta4j">ta4j</a> is a technical analysis library written in java. I’ve even ported the ta4j library to kotlin, <a href="https://github.com/mgrzaslewicz/ta4k-autocoin">it’s on github</a>.&nbsp;</p>



<p>I spent a while implementing basic technical analysis signals.&nbsp;</p>



<p>I soon realized creating a bot that really earns money on autopilot would require:</p>



<ul class="wp-block-list">
<li>Providing a working trading strategy with set of rules</li>



<li>Or using machine learning</li>
</ul>



<p>I’m not a trader and I don’t know trading strategies. I’m not experienced in using machine learning either.</p>



<p>Creating software that does the calculations and makes trades is far more attractive to me than learning technical analysis itself. I imagined people providing their own set of technical analysis signals and buying or selling based on them. Software would ‘just’ monitor the market. And when market conditions satisfy the signal rules, buy or sell.</p>



<p>This vision that people could run their own trading strategies made me excited.</p>



<p>So I stopped thinking of working on my own trading strategy and shifted towards creating an engine to run the strategies.</p>



<p>To sum up, I earned zero money at this point. On the other side I gained crucial experience around cryptocurrency exchanges which is:</p>



<ul class="wp-block-list">
<li>Connecting to exchanges and using their APIs</li>



<li>Implementing technical analysis at a basic level which produces signals for buying/selling</li>



<li>And that was a good code base and experience for moving on to the next step</li>
</ul>



<h1 class="wp-block-heading">Next step: turn the bot into half automated cryptocurrency trading bot as a service</h1>



<p>A friend of mine was trading daily and he wanted a tool that would help him manage many exchange accounts at the same time. He had a few people that trusted him and he was also doing trades on their accounts. As you can imagine, managing 10 accounts at the same time is a lot of work. And the speed of execution matters a lot. Doing that manually is time consuming.&nbsp;</p>



<p>After speaking to him I thought automating what he does would be exciting. And it would later scale up as probably more traders need it.</p>



<p>So what did he need?</p>



<ul class="wp-block-list">
<li>Managing many accounts from one place via API keys</li>



<li>Viewing open orders from many exchanges that he would cancel at any time</li>



<li>Simple trading strategies that he could run in parallel. E.g. sell n% of ETH every time it grows up X% and at the same time BTC is above Y Z price</li>
</ul>



<h2 class="wp-block-heading"><em>I will make software as a service for one man, more people will need it for sure!</em></h2>



<p>With that mindset and a few months of hard work I had a UI and backend services able to run many simple trading strategies on multiple exchange accounts at the same time. Plus all the monitoring to know the state of the system from devops perspective.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="575" height="1024" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-575x1024.png" alt="" class="wp-image-18" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-575x1024.png 575w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-168x300.png 168w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image.png 584w" sizes="(max-width: 575px) 100vw, 575px" /></figure>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="758" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-1-1024x758.png" alt="" class="wp-image-19" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-1-1024x758.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-1-300x222.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-1-768x568.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-1.png 1170w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>I tried to broadcast my system on reddit</p>



<ul class="wp-block-list">
<li><a href="https://www.reddit.com/r/BitcoinMarkets/comments/dak7z7/automated_trader_does_it_do_what_you_need/">https://www.reddit.com/r/BitcoinMarkets/comments/dak7z7/automated_trader_does_it_do_what_you_need/</a></li>



<li><a href="https://www.reddit.com/r/BitcoinMarkets/comments/dbh1bd/what_do_you_hatelove_automated_trading_software/">https://www.reddit.com/r/BitcoinMarkets/comments/dbh1bd/what_do_you_hatelove_automated_trading_software/</a></li>
</ul>



<p>People were skeptical to say the least and posts gained very little attention.</p>



<p>I gave up focusing on marketing. Motivation dropped down. I stopped working on the software for a few months.</p>



<h1 class="wp-block-heading">2020: “<em>I’ve put so much effort into my product and I’m not making any money from it. Should I give it up?</em>”</h1>



<h2 class="wp-block-heading">No money earned for such a long time, motivation drops down</h2>



<p>After 3 years of work at <a href="https://autocoin-trader.com/">https://autocoin-trader.com/</a> as a side project there was</p>



<ul class="wp-block-list">
<li>trading automation operating at many exchanges</li>



<li>managing open orders at exchanges</li>



<li>displaying summary of exchanges balances. Kind of ugly, but working</li>
</ul>



<p>I also added an arbitrage monitoring microservice working within my infrastructure. <a href="https://github.com/mgrzaslewicz/autocoin-arbitrage-monitor">Source code is on github</a>. The UI was not intuitive at the beginning.</p>



<p>Zero coins earned working on the project so far. I just felt tired maintaining the project and I did not want to put any effort into the project for some time.</p>



<p>I could either:</p>



<ol class="wp-block-list">
<li>Abandon the project and move on to some other side project</li>



<li>Figure out how to use what I already have, make it more attractive and valuable</li>
</ol>



<p>So I decided to give 2) a chance.</p>



<h1 class="wp-block-heading">2021: Less is more &#8211; why I removed most of functionalities</h1>



<h2 class="wp-block-heading">Initially many features in the product</h2>



<p>I had many functionalities in the system:</p>



<ul class="wp-block-list">
<li>Trading bot</li>



<li>Showing exchange balances</li>



<li>Tracking and canceling open orders</li>



<li>Arbitrage monitor (arbitrage scanner)</li>
</ul>



<p>And none of the above were great.</p>



<p>I bought a ‘from developer to founder’, <a href="https://startupmyway.com/">Bogusz Pękalski’s</a> course. It changed my mindset and I knew what steps to take to increase the probability of creating a SaaS.</p>



<p>Back then I had ~3000 users that signed up to autocoin-trader <a href="https://youtu.be/lVqPx88v3Ms"><strong>from this single video</strong></a>.&nbsp;</p>



<p>That’s ~2h of marketing as a total effort. That&#8217;s almost zero marketing and for such a little effort, the result was huge.</p>



<p>The demand for an arbitrage monitor became quite obvious.</p>



<p>I tried to manually use some arbitrage opportunities and I found it hard to use. Spreads at 2021 were quite small. Benefit/effort ratio was too low for me.</p>



<p>In autocoin architecture, there is exchange-mediator which is a unified API to all exchanges. Whenever I made breaking changes in the mediator, I had to maintain functionality no one was really using &#8211; like managing open orders and trading bot.</p>



<h2 class="wp-block-heading">Remove features to speed up making rest of them much better</h2>



<p>I decided to turn off 2 functionalities and leave 2 of them only. This way it would be much easier to polish them, starting with arbitrage monitoring.</p>



<p>After the decision that’s what was left to be improved and maintained:</p>



<ul class="wp-block-list">
<li><s>Trading bot</s></li>



<li>Showing exchange balances</li>



<li><s>Tracking and canceling open orders</s></li>



<li>Arbitrage monitor (arbitrage scanner)</li>
</ul>



<h1 class="wp-block-heading">Let’s focus on making ONE feature great and see what happens</h1>



<h2 class="wp-block-heading">Strong market demand</h2>



<p>Using arbitrage opportunities manually has low chances of being successfully used by people. You have to compete with bots and other people.</p>



<p>However I could not deny there was and there still is a strong market demand.</p>



<p>I’ve created a video which is ~2h of marketing work. 14k views</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<p class="responsive-video-wrap clr"><iframe loading="lazy" title="How to find arbitrage opportunities on crypto exchanges" width="1200" height="675" src="https://www.youtube.com/embed/lVqPx88v3Ms?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></p>
</div></figure>



<p>And that resulted in 4k+ registered users</p>



<figure class="wp-block-image size-full"><img decoding="async" width="797" height="822" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-2.png" alt="" class="wp-image-21" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-2.png 797w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-2-291x300.png 291w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-2-768x792.png 768w" sizes="(max-width: 797px) 100vw, 797px" /><figcaption class="wp-element-caption">Some users fill in the form multiple times. 5268 filled registrations gave 4483 unique users. There is market demand</figcaption></figure>



<p></p>



<p>I decided to give it a go anyway because of a market demand.</p>



<p>Apart from the email list, there was already an audience I could chat with on telegram group</p>



<figure class="wp-block-image size-full"><img decoding="async" width="568" height="67" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-3.png" alt="" class="wp-image-22" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-3.png 568w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-3-300x35.png 300w" sizes="(max-width: 568px) 100vw, 568px" /><figcaption class="wp-element-caption">Email list -&gt; telegram group</figcaption></figure>



<p>I decided to deliver what people say they want.</p>



<p>Anytime there was:</p>



<ul class="wp-block-list">
<li>Question on a telegram asking how to use or understand the product</li>



<li>Future request</li>
</ul>



<p>I’ve been putting everything into the backlog of improvements and bug fixes. Before going straight to the implementation I sometimes waited to see if more people wanted the same feature or improvement.</p>



<p>Some of the user questions and demands are in the <a href="https://github.com/mgrzaslewicz/autocoin-arbitrage-monitor/blob/master/README.md">README.md</a> of open source part of arbitrage-monitor service.</p>



<p>When arbitrage-monitor was good enough shape (objectively not very good yet) I’ve splitted product into 2 plans:</p>



<ul class="wp-block-list">
<li>Free plan</li>



<li>Pro plan for $10 USDT or other coin</li>
</ul>



<p>At the beginning I’ve asked people to try the Pro plan for free to get more feedback and improve the product even more.</p>



<p>Within the following months arbitrage-monitor SaaS went from kind of good to much better.</p>



<h2 class="wp-block-heading">Being honest about expectations</h2>



<p>I’ve communicated clearly from the very beginning and many times on telegram that</p>



<ul class="wp-block-list">
<li>there is no easy money</li>



<li>you have to put effort and time</li>



<li>Executing arbitrage opportunity has its risks</li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" width="534" height="166" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-4.png" alt="" class="wp-image-23" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-4.png 534w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-4-300x93.png 300w" sizes="(max-width: 534px) 100vw, 534px" /><figcaption class="wp-element-caption">Being honest about </figcaption></figure>



<p><a href="https://t.me/c/1189042043/2139">https://t.me/c/1189042043/2139</a></p>



<figure class="wp-block-image size-full"><img decoding="async" width="762" height="413" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-5.png" alt="" class="wp-image-24" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-5.png 762w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-5-300x163.png 300w" sizes="(max-width: 762px) 100vw, 762px" /></figure>



<p><a href="https://t.me/c/1189042043/2284">https://t.me/c/1189042043/2284</a></p>



<figure class="wp-block-image size-large"><img decoding="async" width="542" height="1024" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-6-542x1024.png" alt="" class="wp-image-25" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-6-542x1024.png 542w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-6-159x300.png 159w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-6.png 576w" sizes="(max-width: 542px) 100vw, 542px" /></figure>



<p>the execution steps for arbitrage opportunities shows the risks listed</p>



<figure class="wp-block-image size-full"><img decoding="async" width="763" height="341" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-7.png" alt="" class="wp-image-26" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/image-7.png 763w, http://deploymenteveryday.com/wp-content/uploads/2023/01/image-7-300x134.png 300w" sizes="(max-width: 763px) 100vw, 763px" /></figure>



<p>Honest expectation shown for Pro plan payment explanation</p>



<h2 class="wp-block-heading">Great experience learned the hard way</h2>



<p>I’ve gained great experience in building a product. I’ve also learned for the second time how not to do it. It wasn’t my first attempt to build a SaaS, but that’s a different story.</p>



<p>Just don’t be like me. Don’t start with a product and then try to sell it.</p>



<p>Treat building a product like an end to end test. Figure out what’s the lazy and low effort to test it. This way you will discover the probability of success.</p>



<p>I’ve asked questions about new features not related to arbitrage on the telegram group. No one was answering them so I knew if I deliver, those features will not make those people excited.</p>



<p>I delivered some of them anyway as I’m using them, e.g. tracking wallets.</p>



<h1 class="wp-block-heading">2022: It’s time to finally add the landing page <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></h1>



<h2 class="wp-block-heading">5 years without landing page</h2>



<p>I had no landing page for 5 years. And without it 4k+ people registered on the mailing list.</p>



<p>It worked, because people knew what to expect after watching a video on youtube and went straight to the registration form.</p>



<p>I’ve added a landing page because people were asking for a landing page with a summary of what the product offers.</p>



<h1 class="wp-block-heading">2022 Nov: Unexpected (but not surprising) side effect of being experienced in implementing automated trading strategies &#8211; being asked to make flash crash-ready trading bot</h1>



<p>In November 2022 I had a message from a crypto investor and later a call.</p>



<p>In short:</p>



<p><strong>Crypto investor</strong>: “<em>I need a trading bot that will buy as much BTC as possible in case a flash crash on binance happens. The problem is Binance does not allow to place buy limit orders with price lower than 1/ 5 of current price. It needs to reposition buy orders to really low prices as the price goes down. I need the bot as soon as possible</em>”</p>



<p><strong>Me</strong>: “<em>Sounds exciting. I can do it within a few days thx to already many pieces ready in my system</em>”</p>



<p>Crypto investor reached me because he knew I&#8217;ve been working on such software for a long time and I was his natural choice.</p>



<p>So we agreed upfront on the price plus percent of bitcoins that bot will buy (in case a flash crash&nbsp; happens and bot does its job). Flash crash is a deep, short price drop. It might last a few minutes or even less. If it happens, it’s a chance to multiply bitcoins on autopilot.</p>



<p>I exceptionally sacrificed my healthy habits like exercising and sleeping well for some time.</p>



<p>After 5 days of crazy work after hours it was delivered.</p>



<p>As of 13/12/2022 flash crash on Binance didn’t happen yet <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h1 class="wp-block-heading">Key lessons learned after working hard on SaaS which which lead to no recurring customers</h1>



<ul class="wp-block-list">
<li>During building the product it’s crucial to have users you can easily communicate with
<ul class="wp-block-list">
<li>The hard work is to get the users that you will build the product for. That will allow you to find out the user needs before putting a huge amount of work up front. I saw similar articles on India hackers mentioning the same argument. And even though I already read about it I made the same mistakes. Don&#8217;t be like me</li>



<li>When you have users to communicate with, they will tell you what they want. You will just have to deliver it, without guessing what might sell</li>
</ul>
</li>



<li>You’re going to have motivational ups and downs. When you love the process of creating the product, it keeps you running as it’s disconnected from the end result which might not be what you want</li>



<li>If I were to start another project from the scratch
<ul class="wp-block-list">
<li>I’d consider starting without MPV, with landing page only &#8211; to get users</li>



<li>Building a really low effort MVP and then landing page is a good option as well. However, due to my engineering skills and mind I’m afraid I’d focus too much on technical stuff. So personally I’d start without MVP just not to get lost in solving technical problems&nbsp;</li>



<li>This way you can avoid a huge amount of work that will lead to no happy customers at the end. Plus no happy solopreneur</li>
</ul>
</li>



<li>People are eager to use tools that will give them easy money
<ul class="wp-block-list">
<li>Cryptocurrency arbitrage, in theory, is simple money. It attracts new users easily</li>



<li>To my knowledge, there is no publicly accessible software that will bring anyone easy money</li>



<li>And if there is one advertising as such, it is likely to be a scam like a pyramid scheme</li>
</ul>
</li>



<li>Don’t assume that users that signed up for X want X’
<ul class="wp-block-list">
<li>In my case X is tracking arbitrage opportunities, X’ is tracking cryptocurrency portfolio. Kind of similar, and users should want X’ right? (slap in the face)</li>



<li>I had ~5k users on email list and ~200 users on telegram that signed up for tracking arbitrage opportunities. It sounds like a great market channel to offer different functionalities.</li>
</ul>
</li>



<li>
<ul class="wp-block-list">
<li><img decoding="async" width="600" height="332" class="wp-image-29" style="width: 600px;" src="http://deploymenteveryday.com/wp-content/uploads/2023/01/autocoin-tracking-balance-no-responses-meme-2023-01-02_10-20.png" alt="" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/01/autocoin-tracking-balance-no-responses-meme-2023-01-02_10-20.png 1004w, http://deploymenteveryday.com/wp-content/uploads/2023/01/autocoin-tracking-balance-no-responses-meme-2023-01-02_10-20-300x166.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/01/autocoin-tracking-balance-no-responses-meme-2023-01-02_10-20-768x425.png 768w" sizes="(max-width: 600px) 100vw, 600px" />
<ul class="wp-block-list">
<li></li>
</ul>
</li>



<li>I assumed they might be willing to track their portfolio. That’s the functionality I need so I’ve implemented it and broadcasted many times on telegram group. No responses</li>
</ul>
</li>



<li>Finding the right problem to solve is crucial Articles like the one below&nbsp;</li>



<li>Next time, validate your ideas better. This blog post has good steps for that
<ul class="wp-block-list">
<li><a href="https://www.indiehackers.com/post/instant-landing-page-killing-a-startup-idea-quickly-2099c29a0a">https://www.indiehackers.com/post/instant-landing-page-killing-a-startup-idea-quickly-2099c29a0a</a> -&gt;</li>



<li><a href="https://kyleplatt.com/instant-landing-page-coming-up-with-and-quickly-killing-the-idea/">https://kyleplatt.com/instant-landing-page-coming-up-with-and-quickly-killing-the-idea/</a></li>
</ul>
</li>
</ul>



<h1 class="wp-block-heading">Next steps after learning from mistakes</h1>



<p><br>I’ve put a huge effort into building a side project. 4 years have passed and it has no recurring customers. It was and it still really is fun to develop the project. However, it feels much better to develop something that people are willing to pay for.</p>



<p>At this point I have to consider what to do next. From a financial perspective I don’t need to work on any side project. Working at Atlassian as senior Java software engineer at the time of writing this blog post satisfies me financially and professionally.</p>



<p>However, regardless of my primary income stream, I feel a strong urge to</p>



<ul class="wp-block-list">
<li>build a second income stream</li>



<li>satisfy the need of impacting people&#8217;s lives positively
<ul class="wp-block-list">
<li>solving their problems with my products, content or service is an answer to that</li>
</ul>
</li>
</ul>



<p>That’s buried deep inside me and I can’t fight it.</p>



<h2 class="wp-block-heading">Possible next steps ideas</h2>



<ol class="wp-block-list">
<li>I’ve already built a lot of software using cryptocurrency exchanges. It might be given new direction
<ul class="wp-block-list">
<li>Expose it as unified exchange gateway &#8211; API for other developers</li>



<li>Come back to the initial idea and improve it &#8211; half automated trading bot like greenhopper so people can run their own trading strategies</li>
</ul>
</li>



<li>Drop the niche completely and focus on something else &#8211; find better problem to solve</li>



<li>Try freelancing. It might uncover some problems that might be addressed as a SaaS</li>



<li>Redesign and cleanup the core functionality (exchange gateway). Make it an open source library.
<ul class="wp-block-list">
<li>It’s in progress: <a href="https://github.com/mgrzaslewicz/exchange-gateway">https://github.com/mgrzaslewicz/exchange-gateway</a></li>
</ul>
</li>
</ol>



<p>What are your thoughts? Leave a comment below</p><p>The post <a href="http://deploymenteveryday.com/not-giving-up-after-4-years-30-one-time-customers-and-saas-failure/">Not Giving Up After 4 years, 30 One Time Customers And SaaS Failure</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/not-giving-up-after-4-years-30-one-time-customers-and-saas-failure/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How much money (if any) can you make with cryptocurrency arbitrage?</title>
		<link>http://deploymenteveryday.com/how-much-money-if-any-can-you-make-with-cryptocurrency-arbitrage/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-much-money-if-any-can-you-make-with-cryptocurrency-arbitrage</link>
					<comments>http://deploymenteveryday.com/how-much-money-if-any-can-you-make-with-cryptocurrency-arbitrage/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Sun, 25 Sep 2022 17:28:01 +0000</pubDate>
				<category><![CDATA[saas]]></category>
		<category><![CDATA[arbitrage]]></category>
		<category><![CDATA[cryptocurrency]]></category>
		<guid isPermaLink="false">http://deploymenteveryday.com/?p=7</guid>

					<description><![CDATA[<p>Who is this blog post for? You&#8217;re looking for a platform that will find cryptocurrency arbitrage opportunities between centralized or decentralized exchanges (a cryptocurrency arbitrage opportunities scanner). And you’re looking for an answer &#8211; can you make money from arbitrage opportunities? You’re a programmer and you’re thinking of creating your own arbitrage bot Does chasing [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/how-much-money-if-any-can-you-make-with-cryptocurrency-arbitrage/">How much money (if any) can you make with cryptocurrency arbitrage?</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">Who is this blog post for?</h2>



<ul class="wp-block-list"><li>You&#8217;re looking for a platform that will find cryptocurrency arbitrage opportunities between centralized or decentralized exchanges (a cryptocurrency arbitrage opportunities scanner). And you’re looking for an answer &#8211; can you make money from arbitrage opportunities?</li><li>You’re a programmer and you’re thinking of creating your own arbitrage bot</li></ul>



<h2 class="wp-block-heading">Does chasing arbitrage opportunities manually still make sense?</h2>



<h3 class="wp-block-heading">TLDR: Not for making profit. Easy money attracts many people making it totally not easy</h3>



<p>Earning money with arbitrage as an idea is quite simple. Let&#8217;s take a look at the example.</p>



<p>Imagine potato sellers offer different prices in two cities. Obviously they don’t have an unlimited amount of potatoes to sell. Each seller has 200 kg.</p>



<ul class="wp-block-list"><li>In city A potatoes cost $100</li><li>In city B potatoes cost $110</li><li>That gives a $10 price difference which is 10%</li><li>Let&#8217;s call it a spread</li></ul>



<p>If you</p>



<ul class="wp-block-list"><li>Buy 200 kg of potatoes in city A for $100<br>So you spend 200 * $100 = $20 000</li><li>Drive to city B</li><li>Sell 200 kg of potatoes in city B for $110<br>So now you have 200 * $110 = $22 000</li><li>You&#8217;ve got a profit</li><li>The profit is 10% of $20 000 is $2000 (minus transport cost)</li></ul>



<h3 class="wp-block-heading">Let’s assume there is a hypothetical, unrealistic arbitrage opportunity for ETH/BTC currency pair</h3>



<p>Using ethereum and bitcoin just to have some familiar names.</p>



<ul class="wp-block-list"><li>At exchanges A the price of ETH/BTC is 100 BTC for 200 ETH</li><li>at exchange B the price of ETH/BTC is 110 BTC for 200 ETH</li></ul>



<p>That means there is a 10% spread between two exchanges and that’s an arbitrage opportunity</p>



<p>Is it as easy as potatoes to make money with? In theory, yes <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;</p>



<ul class="wp-block-list"><li>Buy 200 ETH at exchange A for 100 BTC<br>So you spend 200 * 100 = 20 000 BTC</li><li>Transfer ETH to exchange B</li><li>Sell 200 ETH at exchange B for 110 BTC<br>So now you have 200 * 110 = 22 000 BTC</li><li>You’re going to have 2 000 BTC more which is pure profit.</li></ul>



<p>In theory, that’s easy money assuming you found the opportunity.</p>



<p>Easy money attracts many people so it becomes a race of many people to use the same opportunity. However, each exchange has a limited amount of cryptocurrency.</p>



<p>Who gets the profit? The fastest ones.</p>



<p>If you’re trying to manually buy, transfer and sell you have to know that there are hundreds (thousands?) of programmers who have automated that.</p>



<p>What you’re doing within seconds, they are doing with milliseconds.</p>



<p>If you want to compete and try to earn something, you’d better create a bot and not do it by hand. And even that is very difficult.</p>



<h2 class="wp-block-heading">Market is flooded with bots doing the arbitrage. You have no chances competing with machines and earn coins</h2>



<p>How do I know?&nbsp;</p>



<p>You can observe it yourself. Because spreads usually keep around 1%. It&#8217;s just not possible without bots intervention to keep such low spreads between exchanges with huge volumes.&nbsp;</p>



<p>I’ve been working on an arbitrage scanner for a long time.&nbsp;</p>



<p>I hoped people could use it and earn something. I had only one man reported earning 800 euro.</p>



<h3 class="wp-block-heading">Does it make sense to make your own automated cryptocurrency arbitrage bot?</h3>



<p>Probably not. Competition is really high. I’ve tried to create an automated one.<br>I’ve also contacted programmers who have been working on arbitrage bots for a long time.</p>



<p>If you follow this path and create your own one, you’re probably wasting your time if your main goal is to earn money.</p>



<p>Sign up for free at <a href="https://autocoin-trader.com">https://autocoin-trader.com</a> so you can</p>



<ul class="wp-block-list"><li>watch live arbitrage opportunities</li><li>track your cryptocurrency portfolio</li></ul>



<p>If you want to use an arbitrage scanner strictly for making money you have to know it’s really difficult. If you dedicate your time and effort, you might get something in return.</p>



<h2 class="wp-block-heading">What are your thoughts? Leave a comment below</h2><p>The post <a href="http://deploymenteveryday.com/how-much-money-if-any-can-you-make-with-cryptocurrency-arbitrage/">How much money (if any) can you make with cryptocurrency arbitrage?</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/how-much-money-if-any-can-you-make-with-cryptocurrency-arbitrage/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
