<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[measure.sh]]></title><description><![CDATA[Tech blog of https://measure.sh - open source mobile monitoring that gets to the root cause.]]></description><link>https://blog.measure.sh</link><image><url>https://substackcdn.com/image/fetch/$s_!6JkY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb3fbd53-812b-4446-8391-ee9adcac7031_600x600.png</url><title>measure.sh</title><link>https://blog.measure.sh</link></image><generator>Substack</generator><lastBuildDate>Fri, 03 Jul 2026 20:27:05 GMT</lastBuildDate><atom:link href="https://blog.measure.sh/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Measure Inc]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[measuredev@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[measuredev@substack.com]]></itunes:email><itunes:name><![CDATA[Gandharva Kumar]]></itunes:name></itunes:owner><itunes:author><![CDATA[Gandharva Kumar]]></itunes:author><googleplay:owner><![CDATA[measuredev@substack.com]]></googleplay:owner><googleplay:email><![CDATA[measuredev@substack.com]]></googleplay:email><googleplay:author><![CDATA[Gandharva Kumar]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[What we got wrong about ANR detection before we got it right]]></title><description><![CDATA[A deep dive into ANR detection on Android]]></description><link>https://blog.measure.sh/p/what-we-got-wrong-about-anr-detection</link><guid isPermaLink="false">https://blog.measure.sh/p/what-we-got-wrong-about-anr-detection</guid><dc:creator><![CDATA[Abhay Sood]]></dc:creator><pubDate>Wed, 27 May 2026 10:42:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!qGvS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qGvS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qGvS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png 424w, https://substackcdn.com/image/fetch/$s_!qGvS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png 848w, https://substackcdn.com/image/fetch/$s_!qGvS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png 1272w, https://substackcdn.com/image/fetch/$s_!qGvS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qGvS!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png" width="1668" height="1111.2362637362637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:970,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1668,&quot;bytes&quot;:4717102,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.measure.sh/i/197767629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qGvS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png 424w, https://substackcdn.com/image/fetch/$s_!qGvS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png 848w, https://substackcdn.com/image/fetch/$s_!qGvS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png 1272w, https://substackcdn.com/image/fetch/$s_!qGvS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ecda205-1add-43b0-96bb-a07b9ecfe1cc_2528x1684.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>At Measure, we build an open source mobile observability platform. One of the trickiest things to track on Android is the dreaded Application Not Responding (ANR) error. When the UI thread of an Android app is blocked for too long, Android decides to throw this error and lets the user kill the app.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.measure.sh/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading measure.sh! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aPYj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aPYj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png 424w, https://substackcdn.com/image/fetch/$s_!aPYj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png 848w, https://substackcdn.com/image/fetch/$s_!aPYj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png 1272w, https://substackcdn.com/image/fetch/$s_!aPYj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aPYj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png" width="251" height="429.97391304347826" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:591,&quot;width&quot;:345,&quot;resizeWidth&quot;:251,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure 1. ANR dialog displayed to the user&quot;,&quot;title&quot;:&quot;Figure 1. ANR dialog displayed to the user&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure 1. ANR dialog displayed to the user" title="Figure 1. ANR dialog displayed to the user" srcset="https://substackcdn.com/image/fetch/$s_!aPYj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png 424w, https://substackcdn.com/image/fetch/$s_!aPYj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png 848w, https://substackcdn.com/image/fetch/$s_!aPYj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png 1272w, https://substackcdn.com/image/fetch/$s_!aPYj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35445969-c7c9-48eb-bc3f-1f4aec78bd5a_345x591.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This post is about how we detects ANRs and the attempts we made before getting it right.</p><h2>Main thread watchdog</h2><p>We started with the simplest and most well known way to detect ANRs. Run a watchdog thread that periodically posts a token to the main thread&#8217;s <code>Handler</code>. If the token doesn&#8217;t come back within 5 seconds, track an ANR event. The implementation was entirely in Kotlin and easy to ship.</p><p>The problem was that it was flaky in practice. The watchdog would fire on hangs that Android itself wouldn&#8217;t classify as ANRs, while missing real ANRs reported by the system.</p><p>The root issue is that Android doesn&#8217;t have a single universal ANR timeout. There are different thresholds and trigger conditions. For example, input dispatch ANRs are triggered when the main thread fails to respond to input events within 5 seconds, while broadcast receivers, services, foreground service startup, content providers and <code>JobScheduler</code> interactions all have their own timeout rules.</p><p>We did not end up shipping this.</p><h2>Using ApplicationExitInfo</h2><p>From API 30 (Android 11) onwards Android itself writes a full ANR dump automatically. <em>ActivityManager</em> returns a list of <a href="https://developer.android.com/reference/android/app/ApplicationExitInfo">ApplicationExitInfo</a> records describing the app&#8217;s recent process exits including ones with REASON_ANR. It also contains the state of every thread at the time of crash.</p><p>However, this API has a few limitations.</p><p>First, it&#8217;s API 30 and above only. We support older releases (API level 21 and above) where <code>ApplicationExitInfo</code> doesn&#8217;t exist. We needed ANR detection that works on every device our SDK runs on.</p><p>Second, it only tells you about process exits after the fact. We only see the record on the next app launch, by which point everything we&#8217;d have wanted from the moment of the ANR (for example a screenshot of the moment the ANR occurred) is gone with the process.</p><p>Third, the system keeps these records in a bounded ring buffer, and older entries get evicted as new ones come in. There&#8217;s no guarantee the specific ANR we want is still around when the app is launched again.</p><p>We use <code>ApplicationExitInfo</code> where it&#8217;s available, but cannot fully rely on it to achieve our goals.</p><h2>The real signal</h2><p>Signals are how Unix-style operating systems poke a process when something asynchronous happens. Pressing Ctrl+C in a terminal sends SIGINT to the foreground process. A segmentation fault generates SIGSEGV. Each signal has a number (SIGINT is 2, SIGKILL is 9, SIGQUIT is 3) and a default behavior the kernel applies if the process doesn&#8217;t override it.</p><p>For SIGQUIT, the default on Linux is to terminate the process and write a core dump. Android overrides this behavior. It shows the &#8220;App Not Responding&#8221; dialog and writes an ANR report.</p><p>All we needed was a way to intercept this signal, record an ANR and pass it back to the system to continue doing its thing. It was harder to do than we initially thought.</p><h2>Catching SIGQUIT</h2><p>The obvious first move to detect a signal is to register a signal handler. So we registered one to handle SIGQUIT.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">struct sigaction sa = { .sa_handler = on_sigquit };
sigaction(SIGQUIT, &amp;sa, NULL);</code></pre></div><p>In a regular Linux process this is enough to get notified of the signal. </p><p><strong>On Android the handler never runs.</strong></p><p>To see why, it helps to know there are two ways a thread can deal with an incoming signal.</p><p>The first is what we just did. Install a handler with <em>sigaction</em>, and when the signal arrives the kernel pauses the thread mid-instruction, runs the handler, and resumes. The catch is that the handler runs in interrupted context. You can&#8217;t allocate, take a mutex or call into the JVM. The list of things you can safely do (the <a href="https://man7.org/linux/man-pages/man7/signal-safety.7.html">async-signal-safe list</a>) is short.</p><p>The second approach is to block the signal on every thread, then dedicate one thread to pulling it off the pending queue with <code>sigwait</code> or <code>sigwaitinfo</code>. The signal arrives as a return value rather than an interrupt, so the dedicated thread runs in ordinary context and can allocate, take locks, and call into the runtime.</p><p>Android picks the second pattern for SIGQUIT. At runtime startup, it blocks SIGQUIT in every thread and spawns a dedicated thread named Signal Catcher that sits in a loop calling <code>sigwaitinfo</code>. When SIGQUIT arrives, no thread has it unblocked, so the kernel has nothing to interrupt. The signal sits in the pending queue until Signal Catcher pulls it out to produce the ANR trace.</p><p>That&#8217;s why our handler never fired. Installing a handler doesn&#8217;t unblock the signal, and every thread inherited SIGQUIT blocked at runtime startup. The kernel had no thread to interrupt, so the signal queued, and Signal Catcher continued it&#8217;s work.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GvHB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GvHB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png 424w, https://substackcdn.com/image/fetch/$s_!GvHB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png 848w, https://substackcdn.com/image/fetch/$s_!GvHB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png 1272w, https://substackcdn.com/image/fetch/$s_!GvHB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GvHB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png" width="1360" height="1150" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1150,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:72561,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.measure.sh/i/197767629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GvHB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png 424w, https://substackcdn.com/image/fetch/$s_!GvHB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png 848w, https://substackcdn.com/image/fetch/$s_!GvHB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png 1272w, https://substackcdn.com/image/fetch/$s_!GvHB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1885733d-5c68-477c-9f7a-c28973cdf234_1360x1150.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Watchdog 2.0</h2><p>To get our handler running, we need our own thread in the process with SIGQUIT unblocked. Then SIGQUIT becomes deliverable, the kernel picks our thread, and the handler runs.</p><p>We spawn one thread, call it Watchdog, and unblock SIGQUIT for it with <code>pthread_sigmask</code>. Signal masks are per-thread, so Signal Catcher is unaffected. Watchdog is now the only thread in the process where SIGQUIT is unblocked, which makes it the only thread the kernel can deliver to.</p><p>The handler waits for the signal to arrives.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;c&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-c">static void on_sigquit(int sig) {
    sem_post(&amp;anr_sem);
}</code></pre></div><p>The trick is to keep the handler as small as possible and have Watchdog do the real work after the handler returns. A semaphore makes the handoff.</p><p>Watchdog spends most of its life waiting on the semaphore. When SIGQUIT arrives, the kernel runs our handler on Watchdog. The handler wakes the semaphore and returns. That&#8217;s all it does, because waking a semaphore is one of the few things you can safely do from inside a signal handler.</p><p>Watchdog is now back in its own code. The handler ran on this thread, did one async-signal-safe thing, and returned. Everything past the semaphore wait is ordinary thread code, so Watchdog can take locks, allocate, call into the JVM, walk threads and capture the state we want for the ANR.</p><p>This works, but now it breaks the platform&#8217;s ANR flow.</p><p>Signal Catcher is still parked in <em>sigwaitinfo</em>, waiting on a signal we just consumed. No SIGQUIT means no &#8220;App Not Responding&#8221; dialog.</p><h2>Handing the signal back</h2><p>Watchdog needs to send a fresh SIGQUIT to Signal Catcher so the platform machinery continues to run.</p><p>We can&#8217;t just send another SIGQUIT to the process. The kernel looks for a thread with SIGQUIT unblocked, finds Watchdog (still the only one), and the signal comes right back to us.</p><p>We need to target Signal Catcher directly. The primitive for that is <code>tgkill</code>, which delivers a signal to a specific thread by its TID. Which we don&#8217;t have.</p><p>Getting Signal Catcher&#8217;s TID is the awkward part. There&#8217;s no API for it, but <code>/proc/self/task/</code> has a directory for every thread in the process, and each directory has a <em>comm</em> file with the thread&#8217;s name. At SDK init we walk the directory once, find the entry that reads &#8220;Signal Catcher&#8221;, and grab its TID. When an ANR fires we record it and send SIGQUIT back to Signal Catcher via <code>tgkill</code>.</p><p>The platform&#8217;s ANR flow now runs as it would have without us in the picture, except we now have our own data captured at the moment it happened.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9U-x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9U-x!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png 424w, https://substackcdn.com/image/fetch/$s_!9U-x!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png 848w, https://substackcdn.com/image/fetch/$s_!9U-x!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png 1272w, https://substackcdn.com/image/fetch/$s_!9U-x!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9U-x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png" width="1360" height="1150" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1150,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:87071,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.measure.sh/i/197767629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9U-x!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png 424w, https://substackcdn.com/image/fetch/$s_!9U-x!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png 848w, https://substackcdn.com/image/fetch/$s_!9U-x!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png 1272w, https://substackcdn.com/image/fetch/$s_!9U-x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee2cc2b5-7f82-4482-a309-291d933b3327_1360x1150.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Putting ANRs on the timeline</h2><p>Capturing an ANR is complex, but fixing them is even harder. We did all of this so that every ANR comes with the full picture of what led up to it.</p><p>First, a timeline of events that occurred before the ANR was triggered: HTTP requests, navigation transitions, lifecycle callbacks, gesture events and custom events. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ED_Q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ED_Q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png 424w, https://substackcdn.com/image/fetch/$s_!ED_Q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png 848w, https://substackcdn.com/image/fetch/$s_!ED_Q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png 1272w, https://substackcdn.com/image/fetch/$s_!ED_Q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ED_Q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png" width="728" height="599" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:1198,&quot;width&quot;:1456,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:224844,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.measure.sh/i/197767629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ED_Q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png 424w, https://substackcdn.com/image/fetch/$s_!ED_Q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png 848w, https://substackcdn.com/image/fetch/$s_!ED_Q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png 1272w, https://substackcdn.com/image/fetch/$s_!ED_Q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e00d27d-5645-4b8d-b1e8-e328a3b21834_2066x1700.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Second, the ApplicationExitInfo record from Android along with the stack trace it provides. Third, an optional screenshot of the screen at the moment the ANR fired.</p><p>This allows scrolling back through the session to see what the user was doing right up to the moment the app froze. </p><p>You can interact with a real session timeline at <a href="https://measure.sh/product/session-timelines">https://measure.sh/product/session-timelines</a>.</p><h2>Sources</h2><p>The native ANR detection code lives in our Android SDK on <a href="https://github.com/measure-sh/measure/blob/main/android/measure-android/measure/src/main/jni/anr_handler.c">GitHub</a>. On the platform side, AOSP&#8217;s <a href="https://android.googlesource.com/platform/art/+/master/runtime/signal_catcher.cc">signal_catcher.cc</a> is the file that implements the Signal Handler thread. </p><p>The man pages for the functions and signals mentioned above are linked below for reference.</p><ul><li><p><a href="https://man7.org/linux/man-pages/man7/signal.7.html">signal(7)</a></p></li><li><p><a href="https://man7.org/linux/man-pages/man2/sigaction.2.html">sigaction(2)</a></p></li><li><p><a href="https://man7.org/linux/man-pages/man3/sigwait.3.html">sigwait(3)</a></p></li><li><p><a href="https://man7.org/linux/man-pages/man2/sigwaitinfo.2.html">sigwaitinfo(2)</a></p></li><li><p><a href="https://man7.org/linux/man-pages/man3/pthread_sigmask.3.html">pthread_sigmask(3)</a></p></li><li><p><a href="https://man7.org/linux/man-pages/man3/sem_post.3.html">sem_post(3)</a></p></li><li><p><a href="https://man7.org/linux/man-pages/man2/tgkill.2.html">tgkill(2)</a></p></li><li><p><a href="https://man7.org/linux/man-pages/man5/proc.5.html">proc(5)</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.measure.sh/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading measure.sh! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Mobile breaks differently]]></title><description><![CDATA[Your observability should reflect that]]></description><link>https://blog.measure.sh/p/mobile-breaks-differently</link><guid isPermaLink="false">https://blog.measure.sh/p/mobile-breaks-differently</guid><dc:creator><![CDATA[Anup Cowkur]]></dc:creator><pubDate>Fri, 17 Apr 2026 09:55:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!bX-k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bX-k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bX-k!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!bX-k!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!bX-k!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!bX-k!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bX-k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2154408,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.measure.sh/i/193563890?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bX-k!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!bX-k!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!bX-k!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!bX-k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feac6e5e8-3b16-4ecf-815a-6345113ef8d6_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><br></h2><p>Most observability platforms started life watching servers. They got really good at it. Tracing requests across microservices, tracking error rates per endpoint and alerting on P99 latency spikes. Mobile came later and the same architecture and data models were extended to support it.</p><p>Except mobile observability is a fundamentally different problem. The failure modes are different, the constraints are different, and if you care about monitoring your app well, it&#8217;s worth understanding how and why that matters.</p><h2>You don&#8217;t own the machine</h2><p>When a backend service misbehaves, you SSH in, read the logs, bump the memory, restart the process. You have full control.</p><p>On mobile, your code runs on a device in someone&#8217;s pocket, on a network you&#8217;ve never heard of, in a country you&#8217;ve never been to, on an OS version you didn&#8217;t know any one was still using. </p><p>That device might have 2 GB of RAM shared across 40 apps. It might be running Android 9 with a manufacturer skin that patches the lifecycle callbacks differently. It might be an iPhone SE on a train going through a tunnel.</p><p>This changes how you collect data, what data you collect, and what you do with it. Every byte of telemetry has to survive unreliable networks, respect battery life, and fit through bandwidth constraints, all while making sure your observability tool doesn&#8217;t itself become a performance problem.</p><h2>You can&#8217;t revert an app release</h2><p>Backend deploys are reversible. Something breaks, you roll back or quickly patch a fix, the whole cycle ideally takes minutes. You can ship ten times a day.</p><p>Mobile releases go through app store review. That&#8217;s hours at best, days at worst. And even after your fix is approved, users have to actually update. Some won&#8217;t for weeks. Some never will.</p><p>The life of a bug that ships is just dramatically higher. A backend bug is a bad hour. A mobile bug can be a bad week or even a month. You need to spot the problem forming early in a release cycle while your rollout is still small or pay a higher price later.</p><h2>Crashes aren&#8217;t errors</h2><p>A 500 error on a server is bad, but the server restarts or just keeps running. The next request probably works fine.</p><p>A crash kills the session. The user was in the middle of something (placing an order, writing a message) and now they&#8217;re staring at their home screen. There&#8217;s no automatic retry. There&#8217;s just a person deciding whether your app is worth opening again.</p><p>Then there are ANRs (Application Not Responding). The app hasn&#8217;t crashed, it&#8217;s technically still alive, but it&#8217;s frozen and the OS is asking the user if they want to force close. There&#8217;s really no backend equivalent. It&#8217;s one of the most frustrating experiences a mobile user can have, and a lot of observability tools don&#8217;t even track it properly.</p><h2>Stack traces are useless without symbolication</h2><p>When you ship a mobile app, you don&#8217;t ship the code you wrote. Release builds go through optimisation and obfuscation - ProGuard or R8 on Android, symbol stripping on iOS. Your carefully named <code>PaymentProcessor.processTransaction()</code> becomes something like <code>a.b.c()</code> on Android or a hex memory address on iOS.</p><p>This is good for app size and security. It&#8217;s terrible for debugging. When a crash comes in from production, the stack trace is gibberish. A wall of obfuscated class names and stripped addresses that tells you nothing about what actually went wrong.</p><p>To make it readable again, you need symbolication: mapping those mangled names and addresses back to your original source code. On iOS, that means dSYM files generated at build time. On Android, it&#8217;s ProGuard or R8 mapping files. Every build produces its own mapping, and if you lose it or upload the wrong one, your crash reports are permanently unreadable for that version.</p><p>This is an operational burden that just doesn&#8217;t exist in backend. Your server logs say <code>NullPointerException in PaymentService.java:142</code> and you go fix it. For mobile, you need a pipeline that automatically captures mapping files for every build, matches them to the right app version, and symbolicates crash reports as they come in. Get any step wrong and you&#8217;re staring at <code>0x0000000100a3b2c4</code> wondering what went sideways.</p><p>It&#8217;s one of those things that&#8217;s invisible when it works and completely debilitating when it doesn&#8217;t.</p><h2>A request is not a session</h2><p>Backend observability is built around the request. A request comes in, gets traced across services, produces a response. Clean and well-bounded.</p><p>Mobile users don&#8217;t make requests. They have sessions. They open the app, tap around, switch to another app, come back twenty minutes later, scroll, hit a button, get interrupted by a phone call, return, and eventually close the app. Or don&#8217;t, it just gets killed by the OS when memory runs low.</p><p>Understanding what went wrong means reconstructing that journey. What screens did they visit? What did they tap? What network calls fired? What was the memory pressure at the time? What did the screen actually look like right before the crash?</p><p>A timestamped error log tells you almost nothing. You need the full session timeline: navigation events, gestures, network calls, resource usage, UI state and much more context to see what actually happened.</p><h2>Performance is relative</h2><p>When a backend engineer talks about performance, they mean latency and throughput on known hardware. You know exactly what you&#8217;re working with.</p><p>Mobile performance might mean cold start time, warm start time, time to first frame, frame rendering jank, memory consumption, battery drain, or app size. And every one of these varies across devices. Your app starts in 400ms on a Pixel 9 and 4 seconds on a budget Samsung from 2020. Both are real users.</p><p>If your observability tool only shows you averages, you&#8217;re seeing a number that represents nobody. You need to slice by device, OS version, app version, network type, geography. You need the distribution, not the mean.</p><h2>The latency you don&#8217;t see</h2><p>Backend tracing follows a request across services. A request comes in, hops through some microservices, and produces a response. The trace has a clear start and end.</p><p>Mobile traces are messier. A trace might span multiple screens as a user works through a flow. Adding items to a cart, entering an address, hitting checkout. They might pause halfway through to reply to a text, or lose connectivity on the subway, or get a phone call. The app backgrounds, the OS might reclaim memory, and the user may or may not come back. </p><p>Then there&#8217;s the network side. Your server dashboard says the API responded in 200ms but the user waited three seconds. The gap is everything that happened before the request reached your server: DNS resolution on a flaky network, TLS handshake on a slow connection, request queuing while the cellular radio wakes up. </p><p>Backend traces pick up at the API gateway. Everything before that is invisible unless your mobile tooling captures it.</p><p>Mobile-aware tracing connects both sides, the on-device spans and the backend spans, so when a user says &#8220;the app felt slow,&#8221; you can actually tell whether the problem was the network, the client, or your API.</p><h2>The telemetry paradox</h2><p>A hard part of mobile observability it that the thing you&#8217;re measuring is the thing being affected by the measurement.</p><p>Every event you log takes CPU, memory, and battery. Every network request to ship telemetry uses bandwidth the user might be paying for. A heavy SDK that captures everything will make the app slower and drain more battery, creating the exact problems you&#8217;re trying to detect. It doesn&#8217;t matter much if a tracing sidecar uses an extra 200MB of RAM on a server. On a phone, your SDK&#8217;s overhead is a direct tax on user experience.</p><p>Mobile SDKs have to be absurdly efficient. Batch intelligently, compress aggressively, back off when resources are tight. Capture enough to be useful but little enough to be invisible.</p><h2>The fragmentation nightmare</h2><p>&#8220;Works on my device&#8221; is the mobile version of &#8220;works on my machine,&#8221; except it&#8217;s orders of magnitude worse.</p><p>There are thousands of distinct Android devices in active use &#8212; different screen sizes, chipsets, GPU capabilities, RAM configurations, manufacturer customizations, OS forks. iOS is more constrained but still spans multiple hardware generations and OS versions.</p><p>A bug might only reproduce on Samsung devices running Android 12 with a specific GPU driver. Or on iPhone SE in low power mode. Or only when the app is restored from background after 30 minutes on a slow network.</p><p>You see a 0.5% crash rate and shrug, but that might be 100% of users on a specific device having an awful time.</p><h2>A different beast</h2><p>Mobile observability isn&#8217;t backend observability with a different client library. </p><p>It&#8217;s a related but different discipline with it&#8217;s own primitives and fundamentally different failure modes.</p><p>The tooling that your mobile team depends on should recognise and respect that.</p><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.measure.sh/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Tired of stitching crash reports, logs and analytics to figure out what went wrong in your mobile app?  Try <a href="https://measure.sh/">Measure</a> and get to the root cause in minutes, not hours! Subscribe to our blog for free to receive new posts straight to your inbox</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item></channel></rss>