<?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"><channel><title><![CDATA[DebugAgent]]></title><description><![CDATA[Author, Open Source Hacker, Entrepreneur, Blogger, DevRel, Java Rockstar, Conference Speaker and Instructor.]]></description><link>https://debugagent.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1667657766117/5UtfvXIdI.png</url><title>DebugAgent</title><link>https://debugagent.com</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 22 Apr 2026 01:54:05 GMT</lastBuildDate><atom:link href="https://debugagent.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Front End Debugging Part 3: Networking]]></title><description><![CDATA[Debugging network communication issues is a critical skill for any front-end developer. While tools like Wireshark provide low-level insight into network traffic, modern browsers like Chrome and Firefox offer developer tools with powerful features ta...]]></description><link>https://debugagent.com/front-end-debugging-part-3-networking</link><guid isPermaLink="true">https://debugagent.com/front-end-debugging-part-3-networking</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Developer]]></category><category><![CDATA[debugging]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 21 Jan 2025 14:00:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737283874565/993bc377-f0ee-47f5-bf21-c5c568ba1358.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Debugging network communication issues is a critical skill for any front-end developer. While tools like <a target="_blank" href="https://debugagent.com/wireshark-tcpdump-a-debugging-power-couple">Wireshark</a> provide low-level insight into network traffic, modern browsers like Chrome and Firefox offer developer tools with powerful features tailored for web development. In this post we will discuss using browser-based tools to debug network communication issues effectively. This is a far better approach than using Wireshark for the vast majority of simple cases.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/_DfNti1q6ec">https://youtu.be/_DfNti1q6ec</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-network-debugging-powerhouse"><strong>Network Debugging Powerhouse</strong></h2>
<p>Modern browsers come equipped with developer tools that rival standalone IDE debuggers in capability and convenience. Both Chrome and Firefox have robust network monitoring features that allow developers to observe/analyze requests and responses without leaving the browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737036410565/d5417ca2-f26d-42fa-a826-a82ae3fb9b2d.png" alt class="image--center mx-auto" /></p>
<p>On the basic level, which you’re probably familiar with, these tools include:</p>
<ul>
<li><p><strong>Network monitors:</strong> View all HTTP and HTTPS requests, including their headers, payloads, and responses.</p>
</li>
<li><p><strong>Throttling controls:</strong> Simulate slower connections to test performance and debug race conditions.</p>
</li>
<li><p><strong>Request replay functionality:</strong> Modify and resend requests directly from the browser.</p>
</li>
</ul>
<p>While this post focuses on debugging techniques, it's worth noting that these tools are invaluable for performance optimization as well, though that topic warrants its own discussion.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737036423154/605c6ee4-e426-440a-bcf9-8d987859e261.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-re-issuing-and-modifying-requests"><strong>Re-Issuing and Modifying Requests</strong></h2>
<p>One of the most powerful debugging features is the ability to re-issue requests. Instead of switching to external tools like cURL or Postman, browsers allow us to modify and resend requests directly.</p>
<p>This lets us quickly test variations of a failing API call to pinpoint issues without leaving the debugging environment. It’s especially useful when we have hard to reproduce issues or deep UI hierarchies.</p>
<p><strong>In Firefox we can</strong> right-click any network entry in the Firefox Developer Tools and select "Resend." This opens an editable window where we can change request parameters, such as headers, payloads, or query strings, and resend the request.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737036478891/8b76e224-cb92-4fe0-87c3-10b71ae1955a.png" alt class="image--center mx-auto" /></p>
<p>Chrome provides similar functionality, though its interface for modifying and resending requests is slightly less direct than Firefox's.</p>
<h3 id="heading-curl-and-postman">cURL and Postman</h3>
<p>Both browsers let you copy a request as a cURL command via the context menu. This is useful for reproducing issues in the terminal or sharing with back-end developers. I use this frequently as part of creating a reproducible issue.</p>
<p>If you prefer Postman, you can copy request headers and payloads from the browser and paste them into Postman to replicate requests.</p>
<h2 id="heading-throttling-and-debugging-race-conditions"><strong>Throttling and Debugging Race Conditions</strong></h2>
<p>Network throttling is a highly underrated feature that can be a game-changer for debugging specific classes of bugs. Both Chrome and Firefox allow developers to simulate various network speeds, from 2G connections to fast 4G.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737036498235/8ebdbf5a-a91c-49be-b493-4257174943d6.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-why-throttling-matters">Why Throttling Matters:</h3>
<p>Some bugs only surface when requests arrive out of their expected order. Slowing down the network can help replicate and analyze these situations. Typical examples would be race conditions and related issues.</p>
<p>This is also very useful for simulating real-world conditions. Many users may not have fast or reliable internet connections. Throttling helps you understand how your application behaves in these scenarios.</p>
<p>I use this frequently when testing loading indicators which disappear too quickly when running locally. Instead of adding sleep code into the JavaScript or server code I can simulate slow-loading assets to verify that loading spinners or placeholders appear correctly.</p>
<h3 id="heading-how-to-use">How to Use</h3>
<p>In Chrome we Open Developer Tools → Network tab.</p>
<p>We then use the "No throttling" dropdown to select pre-configured speeds or create a custom profile.</p>
<p>In Firefox we have similar functionality is available under the Network Monitor.</p>
<h2 id="heading-managing-state-with-storage-tools"><strong>Managing State with Storage Tools</strong></h2>
<p>Local storage, session storage, and indexedDB often hold data critical to reproducing bugs, especially those tied to specific user states or devices.</p>
<h3 id="heading-challenges-of-state-management">Challenges of State Management</h3>
<p>Even in incognito mode, state can persist if multiple private windows are open simultaneously. Persistence across sessions is a big challenge in these situations.</p>
<p>Understanding the exact state of a user's local storage can provide insight into seemingly random bugs. Debugging user-specific issues is problematic without control over storage.</p>
<p>In Firefox the dedicated <strong>Storage</strong> tab in Developer Tools makes it easy to inspect, edit, and delete data from local storage, session storage, cookies, and indexedDB.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737036523463/8bade7e3-7f6e-4143-8795-797ce71f42de.png" alt class="image--center mx-auto" /></p>
<p>In Chrome the <strong>Application</strong> tab consolidates all storage options, including the ability to clear specific caches or edit entries manually.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737036537377/861a8e33-4618-4114-b877-2e59d46c60cb.png" alt class="image--center mx-auto" /></p>
<p>This functionality has many powerful uses for debugging:</p>
<ol>
<li><p><strong>Inject Debug Information:</strong> Tools like these let us manually add or modify storage data to simulate edge cases or specific user conditions.</p>
</li>
<li><p><strong>Share Local State:</strong> Users can export their local storage, cookies, or indexedDB entries, allowing developers to reproduce issues locally.</p>
</li>
<li><p><strong>Clear Cache Strategically:</strong> Clear only the relevant entries instead of a blanket cache clear, preserving useful state for debugging.</p>
</li>
</ol>
<h2 id="heading-analyzing-request-and-response-headers"><strong>Analyzing Request and Response Headers</strong></h2>
<p>Request and response headers often hold the key to understanding network issues. We can use the network monitor to inspect:</p>
<ul>
<li><p><strong>Authorization headers:</strong> Check for missing or malformed tokens.</p>
</li>
<li><p><strong>CORS headers:</strong> Verify that the server allows requests from your domain. These are some of the most painful type of http bugs. Reviewing these headers can be a lifesaver. If requests fail with CORS errors, inspect the <code>Access-Control-Allow-Origin</code> header in the response.</p>
</li>
<li><p><strong>Cache-Control headers:</strong> Ensure proper caching behavior for your resources.</p>
</li>
</ul>
<p>These tools are especially useful when debugging missing headers: Look for required headers like <code>Content-Type</code> or <code>Authorization</code>. Debugging Authentication: Use the "Copy as cURL" feature to test API calls with modified headers directly in the terminal.</p>
<h2 id="heading-debugging-in-incognito-mode-limitations-and-best-practices"><strong>Debugging in Incognito Mode: Limitations and Best Practices</strong></h2>
<p>Incognito mode can help isolate issues by providing a clean slate, however it has some limitations. Multiple incognito windows share the same state, which can lead to unintentional persistence of local data.</p>
<p>I suggest using <strong>storage management tools</strong> to manually clear or modify local data instead of relying solely on incognito mode. Keep only one incognito window open during testing to avoid unintended state sharing.</p>
<h2 id="heading-connecting-the-front-end-to-the-database"><strong>Connecting the Front-End to the Database</strong></h2>
<p>The front-end is often a transition point between user interaction and back-end data processing. While this post focuses on debugging the network layer, it's important to remember that:</p>
<ul>
<li><p>Network issues often manifest due to back-end problems (e.g., a database error resulting in a 500 Internal Server Error).</p>
</li>
<li><p>Front-end developers should collaborate closely with back-end engineers to trace issues across the stack.</p>
</li>
</ul>
<p>We can use <strong>custom response headers</strong> to include diagnostic information from the back end, such as query execution time or error codes. We can leverage <strong>server logs</strong> in conjunction with front-end debugging to get a complete picture of the issue.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>Browser developer tools are indispensable for debugging network communication issues, offering features like request replay, throttling, and storage management that simplify the debugging process. By mastering these tools, front-end developers can efficiently identify and resolve issues, ensuring a smoother user experience.</p>
<p>With the techniques and tips outlined in this post, you'll be better equipped to tackle network debugging challenges head-on. As you grow more familiar with these tools, you'll find them invaluable not only for debugging but also for improving your development workflow.</p>
]]></content:encoded></item><item><title><![CDATA[Front End Debugging Part 2: Console.log() to the Max]]></title><description><![CDATA[In my previous post I talked about why Console.log() isn’t the most effective debugging tool. In this installment, we will do a bit of an about-face and discuss the ways in which Console.log() is fantastic. Let’s break down some essential concepts an...]]></description><link>https://debugagent.com/front-end-debugging-part-2-consolelog-to-the-max</link><guid isPermaLink="true">https://debugagent.com/front-end-debugging-part-2-consolelog-to-the-max</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 19 Nov 2024 14:00:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731864833671/e6fc06e1-9330-4c5a-a519-f325b7819c70.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my previous post I talked about why <code>Console.log()</code> isn’t the most effective debugging tool. In this installment, we will do a bit of an about-face and discuss the ways in which <code>Console.log()</code> is fantastic. Let’s break down some essential concepts and practices that can make your debugging life much easier and more productive.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Qi7S98HNhYY">https://youtu.be/Qi7S98HNhYY</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-understanding-front-end-logging-vs-back-end-logging"><strong>Understanding Front-End Logging vs. Back-End Logging</strong></h2>
<p>Front-end logging differs significantly from back-end logging, and understanding this distinction is crucial. Unlike back-end systems, where persistent logs are vital for monitoring and debugging, the fluid nature of front-end development introduces different challenges. When debugging backends I’d often go for tracepoints which are far superior in that setting. However the front-end with its constant need to refresh, reload, contexts switch etc. is a very different beast. In the front-end relying heavily on elaborate logging mechanisms can become cumbersome.</p>
<p>While tracepoints remain superior to basic print statements, the continuous testing and browser reloading in front-end workflows lessen their advantage. Moreover, features like logging to a file or structured ingestion are rarely useful in the browser, diminishing the need for a comprehensive logging framework. However, using a logger is still considered best practice over the typical <code>Console.log</code> for long term logging… For short term logging <code>Console.log</code> has some tricks up its sleeve.</p>
<h2 id="heading-leveraging-console-log-levels"><strong>Leveraging Console Log Levels</strong></h2>
<p>One of the hidden gems of the browser console is its support for log levels, which is a significant step up from rudimentary print statements. The console provides five levels:</p>
<p>• <strong>log</strong>: Standard logging</p>
<p>• <strong>debug</strong>: Same as log but used for debugging purposes</p>
<p>• <strong>info</strong>: Informative messages, often rendered like log/debug</p>
<p>• <strong>warn</strong>: Warnings that might need attention</p>
<p>• <strong>error</strong>: Errors that have occurred</p>
<p>While log and debug can be indistinguishable, these levels allow for a more organized and filtered debugging experience. Browsers enable filtering the output based on these levels, mirroring the capabilities of server-side logging systems and allowing you to focus on relevant messages.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731860357077/0ea52154-5994-427f-9ab2-1061db892b55.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-customizing-console-output-with-css"><strong>Customizing Console Output with CSS</strong></h2>
<p>Front-end development allows for creative solutions, and logging is no exception. Using CSS styles in the console can make logs more visually distinct. By utilizing <code>%c</code> in a console message, you can apply custom CSS:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.customLog = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">msg</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"%c"</span> + msg,<span class="hljs-string">"color:black;background:pink;font-family:system-ui;font-size:4rem;-webkit-text-stroke: 1px black;font-weight:bold"</span>)
}
<span class="hljs-built_in">console</span>.customLog(<span class="hljs-string">"Dazzle"</span>)
</code></pre>
<p>This approach is helpful when you need to make specific logs stand out or organize output visually. You can use multiple <code>%c</code> substitutions to apply various styles to different parts of a log message.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731860431049/09754f9f-4d2b-497f-b289-173c479d0050.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-stack-tracing-with-consoletrace"><strong>Stack Tracing with console.trace()</strong></h2>
<p>The <code>console.trace()</code> method can print a stack trace at a particular location, which can sometimes be helpful for understanding the flow of your code. However, due to JavaScript’s asynchronous behavior, stack traces aren’t always as straightforward as in back-end debugging. Still, in specific scenarios, such as synchronous code segments or event handling, it can be quite valuable.</p>
<h2 id="heading-assertions-for-design-by-contract"><strong>Assertions for Design-by-Contract</strong></h2>
<p>Assertions in front-end code allow developers to enforce expectations and promote a “fail-fast” mentality. Using <code>Console.assert()</code>, you can test conditions:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.assert(x &gt; <span class="hljs-number">0</span>, <span class="hljs-string">'x must be greater than zero'</span>);
</code></pre>
<p>In the browser, a failed assertion appears as an error, similar to console.error. An added benefit is that assertions can be stripped from production builds, removing any performance impact. This makes assertions a great tool for enforcing design contracts during development without compromising production efficiency.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731861354627/3ad7f19e-65c3-4c52-a180-913e1fbae7a8.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-printing-tables-for-clearer-data-visualization"><strong>Printing Tables for Clearer Data Visualization</strong></h2>
<p>When working with arrays or objects, displaying data as tables can significantly enhance readability. The console.table() method allows you to output structured data easily:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.table([<span class="hljs-string">"Simple Array"</span>, <span class="hljs-string">"With a few elements"</span>, <span class="hljs-string">"in line"</span>])
</code></pre>
<p>This method is especially handy when debugging arrays of objects, presenting a clear, tabular view of the data and making complex data structures much easier to understand.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731861439130/39edf253-d0b2-46c0-b8d6-5c6021fdf776.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-copying-objects-to-the-clipboard"><strong>Copying Objects to the Clipboard</strong></h2>
<p>Debugging often involves inspecting objects, and the <code>copy(object)</code> method allows you to copy an object’s content to the clipboard for external use. This feature is useful when you need to transfer data or analyze it outside the browser.</p>
<h2 id="heading-inspecting-with-consoledir-and-dirxml"><strong>Inspecting with console.dir() and dirxml()</strong></h2>
<p>The <code>console.dir()</code> method provides a more detailed view of objects, showing their properties as you’d see in a debugger. This is particularly helpful for inspecting DOM elements or exploring API responses. Meanwhile, <code>console.dirxml()</code> allows you to view objects as XML, which can be useful when debugging HTML structures.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731861913406/411c72d5-56cb-4947-9644-f4652a8bd0ed.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-counting-function-calls"><strong>Counting Function Calls</strong></h2>
<p>Keeping track of how often a function is called or a code block is executed can be crucial. The <code>console.count()</code> method tracks the number of times it’s invoked, helping you verify that functions are called as expected:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">myFunction</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.count(<span class="hljs-string">'myFunction called'</span>);
}
</code></pre>
<p>You can reset the counter using <code>console.countReset()</code>. This simple tool can help you catch performance issues or confirm the correct execution flow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731862072976/89985f0e-b8d2-4f9f-9cd3-21f26b2a5c7c.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-organizing-logs-with-groups"><strong>Organizing Logs with Groups</strong></h2>
<p>To prevent log clutter, use console groups to organize related messages. <code>console.group()</code> starts a collapsible log section, and <code>console.groupEnd()</code> closes it:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.group(<span class="hljs-string">'My Group'</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message 1'</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message 2'</span>);
<span class="hljs-built_in">console</span>.groupEnd();
</code></pre>
<p>Grouping makes it easier to navigate complex logs and keeps your console clean.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731862148254/840f5cc0-65c3-49e6-bfc8-6a0d363a9805.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-chrome-specific-debugging-features"><strong>Chrome-Specific Debugging Features</strong></h2>
<p><strong>Monitoring Functions</strong>: Chrome’s <code>monitor()</code> method logs every call to a function, showing the arguments and enabling a method-tracing experience.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731862646106/693d90a9-0afa-4dce-84f5-e2431d3f432b.png" alt class="image--center mx-auto" /></p>
<p><strong>Monitoring Events</strong>: Using <code>monitorEvents()</code>, you can log events on an element. This is useful for debugging UI interactions. For example, <code>monitorEvents(window, 'mouseout')</code> logs only <code>mouseout</code> events.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731862601046/9ff2c41a-9f4c-4583-ac7c-efd879948993.png" alt class="image--center mx-auto" /></p>
<p><strong>Querying Object Instances</strong>: <code>queryObjects(Constructor)</code> lists all objects created with a specific constructor, giving you insights into memory usage and object instantiation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731862626900/ab85c82a-390e-4465-b0d3-19df2b8f2f42.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-final-word"><strong>Final Word</strong></h2>
<p>Front-end debugging tools have come a long way. These tools provide a rich set of features that go far beyond simple <code>console.log()</code> statements. From log levels and CSS styling to assertions and event monitoring, mastering these techniques can transform your debugging workflow.</p>
<p>If you read this post as part of my series you will notice a big change in my attitude toward debugging when we reached the front-end. Front-end debugging is very different when compared to backend debugging. When debugging the backend I’m vehemently against code changes for debugging (e.g. println debugging), but on the front-end this can be a reasonable hack. The change in environment justifies it. The short lifecycle, the single user use case and the risk is smaller.</p>
<p>While there are many transferrable skills we pick up while debugging, it’s important to remain flexible in our attitude. Next time we will discuss networking and storage debugging on the front-end.</p>
]]></content:encoded></item><item><title><![CDATA[Front End Debugging Part 1: Not just Console Log]]></title><description><![CDATA[As a Java developer most of my focus is on the backend side of debugging. Front-end debugging poses different challenges and has sophisticated tools of its own. Unfortunately, print based debugging has become the norm in front-end. To be fair, it mak...]]></description><link>https://debugagent.com/front-end-debugging-part-1-not-just-console-log</link><guid isPermaLink="true">https://debugagent.com/front-end-debugging-part-1-not-just-console-log</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Java]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 22 Oct 2024 13:00:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729330534385/2cbbc910-22f4-46d9-8807-d382d46338d9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a Java developer most of my focus is on the backend side of debugging. Front-end debugging poses different challenges and has sophisticated tools of its own. Unfortunately, print based debugging has become the norm in front-end. To be fair, it makes more sense there as the cycles are different and the problem is always a single user problem. But even if you choose to use <code>Console.log</code>, there’s a lot of nuance to pick up there.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/1KFlbecOmc0">https://youtu.be/1KFlbecOmc0</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-instant-debugging-with-the-debugger-keyword"><strong>Instant Debugging with the</strong> <code>debugger</code> Keyword</h2>
<p>A cool yet powerful tool in JavaScript is the <code>debugger</code> keyword. Instead of simply printing a stack trace, we can use this keyword to launch the debugger directly at the line of interest. That is a fantastic tool that instantly brings your attention to a bug, I often use it in my debug builds of the front-end instead of just printing an error log.</p>
<p><strong>How to Use It:</strong> Place the <code>debugger</code> keyword within your code, particularly within error-handling methods. When the code execution hits this line, it automatically pauses, allowing you to inspect the current state, step through code, and understand what's going wrong.</p>
<p>Notice that while this is incredibly useful during development, we must remember to remove or conditionally exclude <code>debugger</code> statements in production environments. A release build should not include these calls in a production site live environment.</p>
<h2 id="heading-triggering-debugging-from-the-console"><strong>Triggering Debugging from the Console</strong></h2>
<p>Modern browsers allow you to invoke debugging directly from the console, adding an additional layer of flexibility to your debugging process.</p>
<p><strong>Example:</strong> By using the <code>debug(functionName)</code> command in the console, you can set a breakpoint at the start of the specified function. When this function is subsequently invoked, the execution halts, sending you directly into the debugger.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hello</span>(<span class="hljs-params">name</span>) </span>{
    Console.log(<span class="hljs-string">"Hello "</span> + name)
}
debug(hello)
hello(<span class="hljs-string">"Shai"</span>)
</code></pre>
<p>This is particularly useful when you want to start debugging without modifying the source code, or when you need to inspect a function that’s only defined in the global scope.</p>
<h2 id="heading-dom-breakpoints-monitoring-dom-changes"><strong>DOM Breakpoints: Monitoring DOM Changes</strong></h2>
<p>DOM breakpoints are an advanced feature in Chrome and Firebug (Firefox plugin) that allow you to pause execution when a specific part of the DOM is altered.</p>
<p>To use it we can right-click on the desired DOM element, select “Break On,” and choose the specific mutation type you are interested in (e.g., subtree modifications, attribute changes, etc.).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729328739791/8874f8da-472a-4d19-b566-695a02ad328a.png" alt class="image--center mx-auto" /></p>
<p>DOM breakpoints are extremely powerful for tracking down issues where DOM manipulation causes unexpected results, such as dynamic content loading or changes in the user interface that disrupt the intended layout or functionality. Think of them like field breakpoints we discussed in the past.</p>
<p>These breakpoints complement traditional line and conditional breakpoints, providing a more granular approach to debugging complex front-end issues. This is a great tool to use when the DOM is manipulated by an external dependency.</p>
<h2 id="heading-xhr-breakpoints-uncovering-hidden-network-calls"><strong>XHR Breakpoints: Uncovering Hidden Network Calls</strong></h2>
<p>Understanding who initiates specific network requests can be challenging, especially in large applications with multiple sources contributing to a request. XHR (<code>XMLHttpRequest</code>) breakpoints provide a solution to this problem.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729329078846/2a921993-0de4-4a5a-8ae3-016904ad76fc.png" alt class="image--center mx-auto" /></p>
<p>In Chrome or Firebug, set an XHR breakpoint by specifying a substring of the URI you wish to monitor. When a request matching this pattern is made, the execution stops, allowing you to investigate the source of the request.</p>
<p>This tool is invaluable when dealing with dynamically generated URIs or complex flows where tracking the origin of a request is not straightforward.</p>
<p>Notice that you should be selective with the filters you set; leaving the filter blank will cause the breakpoint to trigger on all XHR requests, which can become overwhelming.</p>
<h2 id="heading-simulating-environments-for-debugging"><strong>Simulating Environments for Debugging</strong></h2>
<p>Sometimes, the issues you need to debug are specific to certain environments, such as mobile devices or different geographical locations. Chrome and Firefox offer several simulation tools to help you replicate these conditions on your desktop.</p>
<ul>
<li><p><strong>Simulating User Agents:</strong> Change the browser’s user agent to mimic different devices or operating systems. This can help you identify platform-specific issues or debug server-side content delivery that varies by user agent.</p>
</li>
<li><p><strong>Geolocation Spoofing:</strong> Modify the browser’s reported location to test locale-specific features or issues. This is particularly useful for applications that deliver region-specific content or services.</p>
</li>
<li><p><strong>Touch and Device Orientation Emulation:</strong> Simulate touch events or change the device orientation to see how your application responds to mobile-specific interactions. This is crucial for ensuring a seamless user experience across all devices.</p>
</li>
</ul>
<p>These are things that are normally very difficult to reproduce. E.g. touch related issues are often challenging to debug on the device. By simulating them on the desktop browser we can shorten the debug cycle and use the tooling available on the desktop.</p>
<h2 id="heading-debugging-layout-and-style-issues"><strong>Debugging Layout and Style Issues</strong></h2>
<p>CSS and HTML bugs can be particularly tricky, often requiring a detailed examination of how elements are rendered and styled.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729330005484/e3d773db-5a69-457c-b209-c9a5c89d98d9.png" alt class="image--center mx-auto" /></p>
<p><strong>Inspect Element:</strong> The "inspect element" tool is the cornerstone of front-end debugging, allowing you to view and manipulate the DOM and CSS in real-time. As you make changes, the page updates instantly, providing immediate feedback on your tweaks.</p>
<p><strong>Addressing Specificity Issues:</strong> One common problem is CSS specificity, where a more specific selector overrides the styles you intend to apply. The inspect element view highlights overridden styles, helping you identify and resolve conflicts.</p>
<p><strong>Firefox vs. Chrome:</strong> While both browsers offer robust tools, they have different approaches to organizing these features. Firefox’s interface may seem more straightforward, with fewer tabs, while Chrome organizes similar tools under various tabs, which can either streamline your workflow or add complexity, depending on your preference.</p>
<h3 id="heading-final-word">Final Word</h3>
<p>There are many front-end tools that I want to discuss in the coming posts. I hope you picked up a couple of new debugging tricks in this first part.</p>
<p>Front-end debugging requires deep understanding of browser tools and JavaScript capabilities. By mastering the techniques outlined in this post—instant debugging with the <code>debugger</code> keyword, DOM and XHR breakpoints, environment simulation, and layout inspection—you can significantly enhance your debugging efficiency and deliver more robust, error-free web applications.</p>
]]></content:encoded></item><item><title><![CDATA[The Art of Full Stack Debugging]]></title><description><![CDATA[Full stack development is often likened to an intricate balancing act, where developers are expected to juggle multiple responsibilities across the frontend, backend, database, and beyond. As the definition of full stack development continues to evol...]]></description><link>https://debugagent.com/the-art-of-full-stack-debugging</link><guid isPermaLink="true">https://debugagent.com/the-art-of-full-stack-debugging</guid><category><![CDATA[debugging]]></category><category><![CDATA[Java]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[full stack]]></category><category><![CDATA[Full Stack Development]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 01 Oct 2024 12:00:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727506079220/32b0ba6e-041c-4c75-ae06-ff828faf201e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Full stack development is often likened to an intricate balancing act, where developers are expected to juggle multiple responsibilities across the frontend, backend, database, and beyond. As the definition of full stack development continues to evolve, so too does the approach to debugging. Full stack debugging is an essential skill for developers, as it involves tracking issues through multiple layers of an application, often navigating domains where one’s knowledge may only be cursory. In this blog post I aim to explore the nuances of full stack debugging, offering practical tips and insights for developers navigating the complex web of modern software development.</p>
<p>Notice that this is an introductory post focusing mostly on the front end debugging aspects, in the following posts I will dig deeper into the less familiar capabilities in front end debugging.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=mM8p2VrrEaE">https://www.youtube.com/watch?v=mM8p2VrrEaE</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-full-stack-development-a-shifting-definition">Full Stack Development, A Shifting Definition</h2>
<p>The definition of full stack development is as fluid as the technology stacks themselves. Traditionally, full stack developers were defined as those who could work on both the frontend and backend of an application. However, as the industry evolves, this definition has expanded to include aspects of operations (OPS) and configuration. The modern full stack developer is expected to submit pull requests that cover all parts required to implement a feature—backend, database, frontend, and configuration. While this does not make them an expert in all these areas, it does require them to navigate across domains, often relying on domain experts for guidance.</p>
<p>I've heard it said that full stack developers are:</p>
<blockquote>
<p>Jack of all trades, master of none.</p>
</blockquote>
<p>However, the full quote probably better represents the reality:</p>
<blockquote>
<p>Jack of all trades, master of none, <strong>but better than a master of one</strong>.</p>
</blockquote>
<h3 id="heading-the-full-stack-debugging-approach">The Full Stack Debugging Approach</h3>
<p>Just as full stack development involves working across various domains, full stack debugging requires a similar approach. A symptom of a bug may manifest in the frontend, but its root cause could lie deep within the backend or database layers. Full stack debugging is about tracing these issues through the layers and isolating them as quickly as possible. This is no easy task, especially when dealing with complex systems where multiple layers interact in unexpected ways. The key to successful full stack debugging lies in understanding how to track an issue through each layer of the stack and identifying common pitfalls that developers may encounter.</p>
<h2 id="heading-frontend-debugging-tools-and-techniques">Frontend Debugging: Tools and Techniques</h2>
<h3 id="heading-it-isnt-just-consolelog">It isn't "Just Console.log"</h3>
<p>Frontend developers are often stereotyped as relying solely on <code>Console.log</code> for debugging. While this method is simple and effective for basic debugging tasks, it falls short when dealing with the complex challenges of modern web development. The complexity of frontend code has increased significantly, making advanced debugging tools not just useful, but necessary. Yet, despite the availability of powerful debugging tools, many developers continue to shy away from them, clinging to old habits.</p>
<h3 id="heading-the-power-of-developer-tools">The Power of Developer Tools</h3>
<p>Modern web browsers come equipped with robust developer tools that offer a wide range of capabilities for debugging frontend issues. These tools, available in browsers like Chrome and Firefox, allow developers to inspect elements, view and edit HTML and CSS, monitor network activity, and much more. One of the most powerful, yet underutilized, features of these tools is the JavaScript debugger.</p>
<p>The debugger allows developers to set breakpoints, step through code, and inspect the state of variables at different points in the execution. However, the complexity of frontend code, particularly when it has been obfuscated for performance reasons, can make debugging a challenging task.</p>
<p>We can launch the browser tools on Firefox using this menu:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727509581631/e9eb7667-8c5c-4a1b-b094-0f67ac7f210e.png" alt class="image--center mx-auto" /></p>
<p>On Chrome we can use this option:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727509751695/5f64ad9b-13cd-47b6-b852-7a2430cf8c45.png" alt class="image--center mx-auto" /></p>
<p>I prefer working with Firefox, I find their developer tools more convenient but both browsers have similar capabilities. Both have fantastic debuggers (as you can see with the Firefox debugger below), unfortunately many developers limit themselves to console printing instead of exploring this powerful tool.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727509825196/c061cb48-863f-4c1b-b05a-900e019705ae.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-tackling-code-obfuscation">Tackling Code Obfuscation</h3>
<p>Code obfuscation is a common practice in frontend development, employed to protect proprietary code and reduce file sizes for better performance. However, obfuscation also makes the code difficult to read and debug. Fortunately, both Chrome and Firefox developer tools provide a feature to de-obfuscate code, making it more readable and easier to debug. By clicking the curly brackets button in the toolbar, developers can transform a single line of obfuscated code into a well-formed, debuggable file.</p>
<p>Another important tool in the fight against obfuscation is the source map. Source maps are files that map obfuscated code back to its original source code, including comments. When generated and properly configured, source maps allow developers to debug the original code instead of the obfuscated version. In Chrome, this feature can be enabled by ensuring that "Enable JavaScript source maps" is checked in the developer tools settings.</p>
<p>You can use code like this in the JavaScript file to point at the sourcemap file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//@sourceMappingURL=myfile.js.map</span>
</code></pre>
<p>For this to work in Chrome we need to ensure that "Enable JavaScript source maps" is checked in the settings. Last I checked it was on by default but it doesn't hurt to verify:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727510147198/f85f8e8f-c219-4e92-adfe-2a5d161f4d9e.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-debugging-across-layers">Debugging Across Layers</h2>
<h3 id="heading-isolating-issues-across-the-stack">Isolating Issues Across the Stack</h3>
<p>In full stack development, issues often manifest in one layer but originate in another. For example, a frontend error might be caused by a misconfigured backend service or a database query that returns unexpected results. Isolating the root cause of these issues requires a methodical approach, starting from the symptom and working backward through the layers.</p>
<p>A common strategy is to reproduce the issue in a controlled environment, such as a local development setup, where each layer of the stack can be tested individually. This helps to narrow down the potential sources of the problem. Once the issue has been isolated to a specific layer, developers can use the appropriate tools and techniques to diagnose and resolve it.</p>
<h3 id="heading-the-importance-of-system-level-debugging">The Importance of System-Level Debugging</h3>
<p>Full stack debugging is not limited to the application code. Often, issues arise from the surrounding environment, such as network configurations, third-party services, or hardware limitations. A classic example of this that we ran into a couple of years ago was a production problem where a WebSocket connection would frequently disconnect. After extensive debugging, <a target="_blank" href="https://github.com/shannah/">Steve</a> discovered that the issue was caused by the CDN provider (CloudFlare) timing out the WebSocket after two minutes—something that could only be identified by debugging the entire system, not just the application code.</p>
<p>System-level debugging requires a broad understanding of how different components of the infrastructure interact with each other. It also involves using tools that can monitor and analyze the behavior of the system as a whole, such as network analyzers, logging frameworks, and performance monitoring tools.</p>
<h3 id="heading-embracing-complexity">Embracing Complexity</h3>
<p>Full stack debugging is inherently complex, as it requires developers to navigate multiple layers of an application, often dealing with unfamiliar technologies and tools. However, this complexity also presents an opportunity for growth. By embracing the challenges of full stack debugging, developers can expand their knowledge and become more versatile in their roles.</p>
<p>One of the key strengths of full stack development is the ability to collaborate with domain experts. When debugging an issue that spans multiple layers, it is important to leverage the expertise of colleagues who specialize in specific areas. This collaborative approach not only helps to resolve issues more efficiently but also fosters a culture of knowledge sharing and continuous learning within the team.</p>
<p>As tools continue to evolve, so too do the tools and techniques available for debugging. Developers should strive to stay up-to-date with the latest advancements in debugging tools and best practices. Whether it’s learning to use new features in browser developer tools or mastering system-level debugging techniques, continuous learning is essential for success in full stack development.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Full stack debugging is a critical skill for modern developers, we mistakenly think it requires deep understanding of both the application and its surrounding environment. I disagree... By mastering the tools and techniques discussed in this post/upcoming posts, developers can more effectively diagnose and resolve issues that span multiple layers of the stack. Whether you’re dealing with obfuscated frontend code, misconfigured backend services, or system-level issues, the key to successful debugging lies in a methodical, collaborative approach.</p>
<p>You don't need to understand every part of the system, just the ability to eliminate the impossible.</p>
]]></content:encoded></item><item><title><![CDATA[Mastering Serverless Debugging]]></title><description><![CDATA[Serverless computing has emerged as a transformative approach to deploying and managing applications. The theory is that by abstracting away the underlying infrastructure, developers can focus solely on writing code. While the benefits are clear—scal...]]></description><link>https://debugagent.com/mastering-serverless-debugging</link><guid isPermaLink="true">https://debugagent.com/mastering-serverless-debugging</guid><category><![CDATA[Java]]></category><category><![CDATA[lambda]]></category><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 25 Jun 2024 13:00:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719308141171/775ed06b-44f3-42a2-b76a-c21b394bf9ff.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Serverless computing has emerged as a transformative approach to deploying and managing applications. The theory is that by abstracting away the underlying infrastructure, developers can focus solely on writing code. While the benefits are clear—scalability, cost efficiency, and performance—debugging serverless applications presents unique challenges. This post explores effective strategies for debugging serverless applications, particularly focusing on AWS Lambda.</p>
<p>Before I proceed I think it's important to disclose a bias: I am personally not a huge fan of Serverless or PaaS after <a target="_blank" href="https://dev.to/codenameone/production-horrors-handling-disasters-public-debrief-1kf6">I was burned badly by PaaS in the past</a>. However, <a target="_blank" href="https://www.adam-bien.com/">some smart people like Adam swear by it</a> so I should keep an open mind.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/B6uyutAbEDw">https://youtu.be/B6uyutAbEDw</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-introduction-to-serverless-computing">Introduction to Serverless Computing</h2>
<p>Serverless computing, often referred to as Function as a Service (FaaS), allows developers to build and run applications without managing servers. In this model, cloud providers automatically handle the infrastructure, scaling, and management tasks, enabling developers to focus purely on writing and deploying code. Popular serverless platforms include AWS Lambda, Azure Functions, and Google Cloud Functions.</p>
<p>In contrast, Platform as a Service (PaaS) offers a more managed environment where developers can deploy applications but still need to configure and manage some aspects of the infrastructure. PaaS solutions, such as Heroku and Google App Engine, provide a higher level of abstraction than Infrastructure as a Service (IaaS) but still require some server management.</p>
<p>Kubernetes, <a target="_blank" href="https://debugagent.com/why-is-kubernetes-debugging-so-problematic?source=more_series_bottom_blogs">which we recently discussed</a>, is an open-source container orchestration platform that automates the deployment, scaling, and management of containerized applications. While Kubernetes offers powerful capabilities for managing complex, multi-container applications, it requires significant expertise to set up and maintain. Serverless computing simplifies this by removing the need for container orchestration and management altogether.</p>
<p>The "catch" is two fold:</p>
<ul>
<li><p>Serverless programming removes the need to understand the servers but also removes the ability to rely on them resulting in more complex architectures.</p>
</li>
<li><p>Pricing starts off cheap. Practically free. It can quickly escalate especially in case of an attack or misconfiguration.</p>
</li>
</ul>
<h2 id="heading-challenges-of-serverless-debugging">Challenges of Serverless Debugging</h2>
<p>While serverless architectures offer some benefits, they also introduce unique debugging challenges. The primary issues stem from the inherent complexity and distributed nature of serverless environments. Here are some of the most pressing challenges.</p>
<h3 id="heading-disconnected-environments">Disconnected Environments</h3>
<p>One of the major hurdles in serverless debugging is the lack of consistency between development, staging, and production environments. While traditional development practices rely on these separate environments to test and validate code changes, serverless architectures often complicate this process. The differences in configuration and scale between these environments can lead to bugs that only appear in production, making them difficult to reproduce and fix.</p>
<h3 id="heading-lack-of-standardization">Lack of Standardization</h3>
<p>The serverless ecosystem is highly fragmented, with various vendors offering different tools and frameworks. This lack of standardization can make it challenging to adopt a unified debugging approach. Each platform has its own set of practices and tools, requiring developers to learn and adapt to multiple environments.</p>
<p>This is slowly evolving with some platforms gaining traction, but since this is a vendor driven industry there are many edge cases.</p>
<h3 id="heading-limited-debugging-tools">Limited Debugging Tools</h3>
<p>Traditional debugging tools, such as step-through debugging and breakpoints, are often unavailable in serverless environments. The managed and controlled nature of serverless functions restricts access to these tools, forcing developers to rely on alternative methods, such as logging and remote debugging.</p>
<h3 id="heading-concurrency-and-scale">Concurrency and Scale</h3>
<p>Serverless functions are designed to handle high concurrency and scale seamlessly. However, this can introduce issues that are hard to reproduce in a local development environment. Bugs that manifest only under specific concurrency conditions or high load are particularly challenging to debug.</p>
<p>Notice that when I discuss concurrency here I'm often referring to race conditions between separate services.</p>
<h2 id="heading-effective-strategies-for-serverless-debugging">Effective Strategies for Serverless Debugging</h2>
<p>Despite these challenges, several strategies can help make serverless debugging more manageable. By leveraging a combination of local debugging, feature flags, staged rollouts, logging, idempotency, and Infrastructure as Code (IaC), developers can effectively diagnose and fix issues in serverless applications.</p>
<h3 id="heading-local-debugging-with-ide-remote-capabilities">Local Debugging with IDE Remote Capabilities</h3>
<p>While serverless functions run in the cloud, you can simulate their execution locally using tools like AWS SAM (Serverless Application Model). This involves setting up a local server that mimics the cloud environment, allowing you to run tests and perform basic trial-and-error debugging.</p>
<p>To get started, you need to install Docker or Docker Desktop, create an AWS account, and set up the AWS SAM CLI. Deploy your serverless application locally using the SAM CLI, which enables you to run the application and simulate Lambda functions on your local machine. Configure your IDE for remote debugging, launching the application in debug mode, and connecting your debugger to the local host. Set breakpoints to step through the code and identify issues.</p>
<h3 id="heading-using-feature-flags-for-debugging">Using Feature Flags for Debugging</h3>
<p>Feature flags allow you to enable or disable parts of your application without deploying new code. This can be invaluable for isolating issues in a live environment. By toggling specific features on or off, you can narrow down the problematic areas and observe the application’s behavior under different configurations.</p>
<p>Implementing feature flags involves adding conditional checks in your code that control the execution of specific features based on the flag’s status. Monitoring the application with different flag settings helps identify the source of bugs and allows you to test fixes without affecting the entire user base.</p>
<p>This is essentially "debugging in production". Working on a new feature?</p>
<p>Wrap it in a feature flag which is effectively akin to wrapping the entire feature (client and server) in if statements. You can then enable it conditionally globally or on a per user basis. This means you can test the feature, enable or disable it based on configuration without redeploying the application.</p>
<h3 id="heading-staged-rollouts-and-canary-deployments">Staged Rollouts and Canary Deployments</h3>
<p>Deploying changes incrementally can help catch bugs before they affect all users. Staged rollouts involve gradually rolling out updates to a small percentage of users before a full deployment. This allows you to monitor the performance and error logs of the new version in a controlled manner, catching issues early.</p>
<p>Canary deployments take this a step further by deploying new changes to a small subset of instances (canaries) while the rest of the system runs the stable version. If issues are detected in the canaries, you can roll back the changes without impacting the majority of users. This method limits the impact of potential bugs and provides a safer way to introduce updates. This isn't great as in some cases some demographics might be more reluctant to report errors. However, for server side issues this might make sense as you can see the impact based on server logs and metrics.</p>
<h3 id="heading-comprehensive-logging">Comprehensive Logging</h3>
<p>Logging is one of the most common and essential tools for debugging serverless applications. I wrote and <a target="_blank" href="https://www.youtube.com/watch?v=53qCLRFcBSs">spoke a lot about logging in the past</a>. By logging all relevant data points, including inputs and outputs of your functions, you can trace the flow of execution and identify where things go wrong.</p>
<p>However, excessive logging can increase costs, as serverless billing is often based on execution time and resources used. It’s important to strike a balance between sufficient logging and cost efficiency. Implementing log levels and selectively enabling detailed logs only when necessary can help manage costs while providing the information needed for debugging.</p>
<p>I talk about striking the delicate balance between debuggable code, performance and cost with logs in the following video. Notice that this is a general best practice and not specific to serverless.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=53qCLRFcBSs">https://www.youtube.com/watch?v=53qCLRFcBSs</a></div>
<p> </p>
<h3 id="heading-embracing-idempotency">Embracing Idempotency</h3>
<p>Idempotency, a key concept from functional programming, ensures that functions produce the same result given the same inputs, regardless of the number of times they are executed. This simplifies debugging and testing by ensuring consistent and predictable behavior.</p>
<p>Designing your serverless functions to be idempotent involves ensuring that they do not have side effects that could alter the outcome when executed multiple times. For example, including timestamps or unique identifiers in your requests can help maintain consistency. Regularly testing your functions to verify idempotency can make it easier to pinpoint discrepancies and debug issues.</p>
<p>Testing is always important but in serverless and complex deployments it becomes critical. Awareness and embrace of idempotency allows for more testable code and easier to reproduce bugs.</p>
<h2 id="heading-debugging-a-lambda-application-locally-with-aws-sam">Debugging a Lambda Application Locally with AWS SAM</h2>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/SlFA-JlTYGM">https://youtu.be/SlFA-JlTYGM</a></div>
<p> </p>
<p>Debugging serverless applications, particularly AWS Lambda functions, can be challenging due to their distributed nature and the limitations of traditional debugging tools. However, AWS SAM (Serverless Application Model) provides a way to simulate Lambda functions locally, enabling developers to test and debug their applications more effectively. I will use it as a sample to explore the process of setting up a local debugging environment, running a sample application, and configuring remote debugging.</p>
<h3 id="heading-setting-up-the-local-environment">Setting Up the Local Environment</h3>
<p>Before diving into the debugging process, it's crucial to set up a local environment that can simulate the AWS Lambda environment. This involves a few key steps:</p>
<ol>
<li><p><strong>Install Docker</strong>: Docker is required to run the local simulation of the Lambda environment. You can download Docker or Docker Desktop from the official <a target="_blank" href="https://docs.docker.com/get-docker/">Docker website</a>.</p>
</li>
<li><p><strong>Create an AWS Account</strong>: If you don't already have an AWS account, you need to create one. Follow the instructions on the <a target="_blank" href="https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/">AWS account creation page</a>.</p>
</li>
<li><p><strong>Set Up AWS SAM CLI</strong>: The AWS SAM CLI is essential for building and running serverless applications locally. You can install it by following the <a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html">AWS SAM installation guide</a>.</p>
</li>
</ol>
<h3 id="heading-running-the-hello-world-application-locally">Running the Hello World Application Locally</h3>
<p>To illustrate the debugging process, let's use a simple "Hello World" application. The code for this application can be found in the <a target="_blank" href="https://github.com/shai-almog/HelloLambda">AWS Hello World tutorial</a>.</p>
<ol>
<li><p><strong>Deploy Locally</strong>: Use the SAM CLI to deploy the Hello World application locally. This can be done with the following command:</p>
<pre><code class="lang-bash"> sam <span class="hljs-built_in">local</span> start-api
</code></pre>
<p> This command starts a local server that simulates the AWS Lambda cloud environment.</p>
</li>
<li><p><strong>Trigger the Endpoint</strong>: Once the local server is running, you can trigger the endpoint using a <code>curl</code> command:</p>
<pre><code class="lang-bash"> curl http://localhost:3000/hello
</code></pre>
<p> This command sends a request to the local server, allowing you to test the function's response.</p>
</li>
</ol>
<h3 id="heading-configuring-remote-debugging">Configuring Remote Debugging</h3>
<p>While running tests locally is a valuable step, it doesn't provide full debugging capabilities. To debug the application, you need to configure remote debugging. This involves several steps.</p>
<p>First we need to start the application in debug mode using the following SAM command:</p>
<pre><code class="lang-bash">sam <span class="hljs-built_in">local</span> invoke -d 5858
</code></pre>
<p>This command pauses the application and waits for a debugger to connect.</p>
<p>Next we need to configure the IDE for remote debugging. We start by setting up the IDE to connect to the local host for remote debugging. This typically involves creating a new run configuration that matches the remote debugging settings.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719306726468/1e301069-2c30-45c2-ace8-a9aa4f51900b.png" alt class="image--center mx-auto" /></p>
<p>We can now set breakpoints in the code where we want the execution to pause. This allows us to step through the code and inspect variables and application state just like in any other local application.</p>
<p>We can test this by invoking the endpoint e.g. using curl. With the debugger connected we would stop on the breakpoint like any other tool:</p>
<pre><code class="lang-bash">curl http://localhost:3000/hello
</code></pre>
<p>The application will pause at the breakpoints you set, allowing you to step through the code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719307563527/796b3249-b969-43d9-87d7-b05603089b2d.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-handling-debugger-timeouts">Handling Debugger Timeouts</h3>
<p>One significant challenge when debugging Lambda functions is the quick timeout setting. Lambda functions are designed to execute quickly, and if they take too long, the costs can become prohibitive. By default, the timeout is set to a short duration, but you can configure this in the <code>template.yaml</code> file e.g.:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">Resources:</span>
  <span class="hljs-attr">HelloWorldFunction:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::Serverless::Function</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Handler:</span> <span class="hljs-string">app.lambdaHandler</span>
      <span class="hljs-attr">Timeout:</span> <span class="hljs-number">60</span>  <span class="hljs-comment"># timeout in seconds</span>
</code></pre>
<p>After updating the timeout value, re-issue the <code>sam build</code> command to apply the changes.</p>
<p>In some cases, running the application locally might not be enough. You may need to simulate running on the actual AWS stack to get more accurate debugging information. Solutions like SST (Serverless Stack) or MerLoc can help achieve this, though they are specific to AWS and relatively niche.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>Serverless debugging requires a combination of strategies to effectively identify and resolve issues. While traditional debugging methods may not always apply, leveraging local debugging, feature flags, staged rollouts, comprehensive logging, idempotency, and IaC can significantly improve your ability to debug serverless applications. As the serverless ecosystem continues to evolve, staying adaptable and continuously updating your debugging techniques will be key to success.</p>
<p>Debugging serverless applications, particularly AWS Lambda functions, can be complex due to their distributed nature and the constraints of traditional debugging tools. However, by leveraging tools like AWS SAM, you can simulate the Lambda environment locally and use remote debugging to step through your code. Adjusting timeout settings and considering advanced simulation tools can further enhance your debugging capabilities.</p>
]]></content:encoded></item><item><title><![CDATA[Debugging Kubernetes - Troubleshooting Guide]]></title><description><![CDATA[As Kubernetes continues to revolutionize the way we manage and deploy applications, understanding its intricacies becomes essential for developers and operations teams alike. If you don't have a dedicated DevOps team you probably shouldn't be working...]]></description><link>https://debugagent.com/debugging-kubernetes-troubleshooting-guide</link><guid isPermaLink="true">https://debugagent.com/debugging-kubernetes-troubleshooting-guide</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[k8s]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 11 Jun 2024 12:45:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718109726941/68872dfe-0376-46e1-9c52-e8f5aec310d9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As Kubernetes continues to revolutionize the way we manage and deploy applications, understanding its intricacies becomes essential for developers and operations teams alike. If you don't have a dedicated DevOps team you probably shouldn't be working with Kubernetes. Despite that, in some cases a DevOps engineer might not be available while we're debugging an issue. For these situations and for general familiarity we should still familiarize ourselves with common Kubernetes issues to bridge the gap between development and operations. I think this also provides an important skill that helps us understand the work of DevOps better, with that understanding we can improve as a cohesive team. This guide explores prevalent Kubernetes errors and provides troubleshooting tips to help developers navigate the complex landscape of container orchestration.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Q3cy8i4tsyQ">https://youtu.be/Q3cy8i4tsyQ</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-identifying-configuration-issues">Identifying Configuration Issues</h2>
<p>When you encounter configuration issues in Kubernetes, the first place to check is the status column using the <code>kubectl get pods</code> command. Common errors manifest here, requiring further inspection with <code>kubectl describe pod</code>.</p>
<pre><code class="lang-bash">$ kubectl get pods
NAME                     READY    STATUS     RESTARTS   AGE 
my-first-pod-id-xxxx      1/1     Running    0          13s
my-second-pod-id-xxxx     1/1     Running    0          13s
</code></pre>
<h3 id="heading-common-causes-and-solutions">Common Causes and Solutions</h3>
<p><strong>Insufficient Resources</strong>: Notice that this means resources for the POD itself and not resources within the container. It means the hardware or surrounding VM is hitting a limit.</p>
<p><strong>Symptom</strong>: Pods fail to schedule due to resource constraints.</p>
<p><strong>Solution</strong>: Scale up the cluster by adding more nodes to accommodate the resource requirements.</p>
<p><strong>Volume Mounting Failures</strong>:</p>
<p><strong>Symptom</strong>: Pods cannot mount volumes correctly.</p>
<p><strong>Solution</strong>: Ensure storage is defined accurately in the pod specification and check the storage class and Persistent Volume (PV) configurations.</p>
<h3 id="heading-detailed-investigation-steps">Detailed Investigation Steps</h3>
<p>We can use <code>kubectl describe pod</code>: This command provides a detailed description of the pod, including events that have occurred. By examining these events, we can pinpoint the exact cause of the issue.</p>
<p>Another important step is resource quota analysis. Sometimes, resource constraints are due to namespace-level resource quotas. Use <code>kubectl get resourcequotas</code> to check if quotas are limiting pod creation.</p>
<h2 id="heading-dealing-with-image-pull-errors">Dealing with Image Pull Errors</h2>
<p>Errors like <code>ErrImagePull</code> or <code>ImagePullBackOff</code> indicate issues with fetching container images. These errors are typically related to image availability or access permissions.</p>
<h3 id="heading-troubleshooting-steps">Troubleshooting Steps</h3>
<p>The first step is checking the image name which we can do with the following command:</p>
<pre><code class="lang-bash">docker pull &lt;image-name&gt;
</code></pre>
<p>We then need to verify the image name for typos or invalid characters. I pipe the command through grep to verify the name is 100% identical, some typos are just notoriously hard to spot.</p>
<p>Credentials can also be a major pitfall. E.g. an authorization failure when pulling images from private repositories.</p>
<p>We must ensure that Docker registry credentials are correctly configured in Kubernetes secrets.</p>
<p>Network configuration should also be reviewed. Ensure that the Kubernetes nodes have network access to the Docker registry. Network policies or firewall rules might block access.</p>
<p>There are quite a few additional pitfalls such as problems with image tags. Ensure you are using the correct image tags. Latest tags might not always point to the expected image version.</p>
<p>If you're using a private registry you might be experiencing access issues. Make sure your credentials are up-to-date and the registry is accessible from all nodes in all regions.</p>
<h2 id="heading-handling-node-issues">Handling Node Issues</h2>
<p>Node-related errors often point to physical or virtual machine issues. These issues can disrupt the normal operation of the Kubernetes cluster and need prompt attention.</p>
<p>To check node status use the command:</p>
<pre><code class="lang-bash">kubectl get nodes
</code></pre>
<p>We can then identify problematic nodes in the resulting output.</p>
<p>It's a cliché but sometimes rebooting nodes is the best solution to some problems. We can reboot the affected machine or VM. Kubernetes should attempt to "self-heal" and recover within a few minutes.</p>
<p>To investigate node conditions we can use the command:</p>
<pre><code class="lang-bash">kubectl describe node &lt;node-name&gt;
</code></pre>
<p>We should look for conditions such as <code>MemoryPressure</code>, <code>DiskPressure</code>, or <code>NetworkUnavailable</code>. These conditions provide clues about the underlying issue we should address in the node.</p>
<h3 id="heading-preventive-measures">Preventive Measures</h3>
<p>Node monitoring should be used to with tools such as Prometheus, Grafana to keep an eye on node health and performance. These work great for the low level Kubernetes related issues, we can also use them for high level application issues.</p>
<p>There are some automated healing tools such as the Kubernetes Cluster Autoscaler that we can leverage to automatically manage the number of nodes in your cluster based on workload demands. Personally, I'm not a huge fan as I'm afraid of a cascading failure that would trigger additional resource consumption.</p>
<h2 id="heading-managing-missing-configuration-keys-or-secrets">Managing Missing Configuration Keys or Secrets</h2>
<p>Missing configuration keys or secrets are common issues that disrupt Kubernetes deployments. Proper management of these elements is crucial for smooth operation.</p>
<p>We need to use ConfigMaps and secrets. These let us store configuration values and sensitive information securely. To avoid that we need to ensure that ConfigMaps and Secrets are correctly referenced in your pod specifications.</p>
<p>Inspect pod descriptions using the command:</p>
<pre><code class="lang-bash">kubectl describe pod &lt;pod-name&gt;
</code></pre>
<p>Review the output and look for missing configuration details. Rectify any misconfigurations.</p>
<p>ConfigMap and secret creation can be verified using the command:</p>
<pre><code class="lang-bash">kubectl get configmaps
</code></pre>
<p>and:</p>
<pre><code class="lang-bash">kubectl get secrets
</code></pre>
<p>Ensure that the required ConfigMaps and Secrets exist in the namespace and contain the expected data.</p>
<p>It's best to keep non-sensitive parts of ConfigMaps in version control while excluding Secrets for security. Furthermore, you should use different ConfigMaps and Secrets for different environments (development, staging, production) to avoid configuration leaks.</p>
<h2 id="heading-utilizing-buildg-for-interactive-debugging">Utilizing Buildg for Interactive Debugging</h2>
<p>Buildg is a relatively new tool that enhances the debugging process for Docker configurations by allowing interactive debugging.</p>
<p>It provides Interactive Debugging for configuration issues in a way that's similar to a standard debugging. It lets us step through the <code>Dockerfile</code> stages and set breakpoints. Buildg is compatible with VSCode and other IDEs via the Debug Adapter Protocol (DAP).</p>
<p>Buildg lets us inspect container state at each stage of the build process to identify issues early.</p>
<p>To install buildg follow the instructions on the <a target="_blank" href="https://github.com/ktock/buildg">Buildg GitHub page</a>.</p>
<p><img src="https://github.com/ktock/buildg/raw/main/docs/images/vscode-dap.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Debugging Kubernetes can be challenging, but with the right knowledge and tools, developers can effectively identify and resolve common issues. By understanding configuration problems, image pull errors, node issues, and the importance of ConfigMaps and Secrets, developers can contribute to more robust and reliable Kubernetes deployments. Tools like Buildg offer promising advancements in interactive debugging, further bridging the gap between development and operations.</p>
<p>As Kubernetes continues to evolve, staying informed about new tools and best practices will be essential for successful application management and deployment. By proactively addressing these common issues, developers can ensure smoother, more efficient Kubernetes operations, ultimately leading to more resilient and scalable applications.</p>
]]></content:encoded></item><item><title><![CDATA[Why is Kubernetes Debugging so Problematic?]]></title><description><![CDATA[Debugging application issues in a Kubernetes cluster can often feel like navigating a labyrinth. Containers are ephemeral by design, intended to be immutable once deployed. This presents a unique challenge when something goes wrong and we need to dig...]]></description><link>https://debugagent.com/why-is-kubernetes-debugging-so-problematic</link><guid isPermaLink="true">https://debugagent.com/why-is-kubernetes-debugging-so-problematic</guid><category><![CDATA[Java]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 28 May 2024 14:32:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716744512587/332801bf-7653-4a27-80ca-1be1e9618950.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Debugging application issues in a Kubernetes cluster can often feel like navigating a labyrinth. Containers are ephemeral by design, intended to be immutable once deployed. This presents a unique challenge when something goes wrong and we need to dig into the issue. Before diving into the debugging tools and techniques, it's essential to grasp the core problem: why modifying container instances directly is a bad idea. This blog post will walk you through the intricacies of Kubernetes debugging, offering insights and practical tips to effectively troubleshoot your Kubernetes environment.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/xkOekt02mNY">https://youtu.be/xkOekt02mNY</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h3 id="heading-the-immutable-nature-of-containers">The Immutable Nature of Containers</h3>
<p>One of the fundamental principles of Kubernetes is the immutability of container instances. This means that once a container is running, it shouldn't be altered. Modifying containers on the fly can lead to inconsistencies and unpredictable behavior, especially as Kubernetes orchestrates the lifecycle of these containers, replacing them as needed. Imagine trying to diagnose an issue only to realize that the container you’re investigating has been modified, making it difficult to reproduce the problem consistently.</p>
<p>The idea behind this immutability is to ensure that every instance of a container is identical to any other instance. This consistency is crucial for achieving reliable, scalable applications. If you start modifying containers, you undermine this consistency, leading to a situation where one container behaves differently from another, even though they are supposed to be identical.</p>
<h3 id="heading-the-limitations-of-kubectl-exec">The Limitations of <code>kubectl exec</code></h3>
<p>We often start our journey in Kubernetes with commands such as:</p>
<pre><code class="lang-bash">$ kubectl -- <span class="hljs-built_in">exec</span> -ti &lt;pod-name&gt;
</code></pre>
<p>This logs into a container and feels like accessing a traditional server with SSH. However, this approach has significant limitations. Containers often lack basic diagnostic tools—no <code>vim</code>, no <code>traceroute</code>, sometimes not even a shell. This can be a rude awakening for those accustomed to a full-featured Linux environment. Additionally, if a container crashes, <code>kubectl exec</code> becomes useless as there's no running instance to connect to. This tool is insufficient for thorough debugging, especially in production environments.</p>
<p>Consider the frustration of logging into a container only to find out that you can't even open a simple text editor to check configuration files. This lack of basic tools means that you are often left with very few options for diagnosing problems. Moreover, the minimalistic nature of many container images, designed to reduce their attack surface and footprint, exacerbates this issue.</p>
<h3 id="heading-avoiding-direct-modifications">Avoiding Direct Modifications</h3>
<p>While it might be tempting to install missing tools on-the-fly using commands like <code>apt-get install vim</code>, this practice violates the principle of container immutability. In production, installing packages dynamically can introduce new dependencies, potentially causing application failures. The risks are high, and it's crucial to maintain the integrity of your deployment manifests, ensuring that all configurations are predefined and reproducible.</p>
<p>Imagine a scenario where a quick fix in production involves installing a missing package. This might solve the immediate problem but could lead to unforeseen consequences. Dependencies introduced by the new package might conflict with existing ones, leading to application instability. Moreover, this approach makes it challenging to reproduce the exact environment, which is vital for debugging and scaling your application.</p>
<h3 id="heading-enter-ephemeral-containers">Enter Ephemeral Containers</h3>
<p>The solution to the aforementioned problems lies in ephemeral containers. Kubernetes allows the creation of these temporary containers within the same pod as the application container you need to debug. These ephemeral containers are isolated from the main application, ensuring that any modifications or tools installed do not impact the running application.</p>
<p>Ephemeral containers provide a way to bypass the limitations of <code>kubectl exec</code> without violating the principles of immutability and consistency. By launching a separate container within the same pod, you can inspect and diagnose the application container without altering its state. This approach preserves the integrity of the production environment while giving you the tools you need to debug effectively.</p>
<h4 id="heading-using-kubectl-debug">Using <code>kubectl debug</code></h4>
<p>The <code>kubectl debug</code> command is a powerful tool that simplifies the creation of ephemeral containers. Unlike <code>kubectl exec</code>, which logs into the existing container, <code>kubectl debug</code> creates a new container within the same namespace. This container can run a different OS, mount the application container’s filesystem, and provide all necessary debugging tools without altering the application’s state. This method ensures you can inspect and diagnose issues even if the original container is not operational.</p>
<p>For example, let’s consider a scenario where we’re debugging a container using an ephemeral Ubuntu container:</p>
<pre><code class="lang-bash">kubectl debug &lt;myapp&gt; -it &lt;pod-name&gt; --image=ubuntu --share-process --copy-to=&lt;myapp-debug&gt;
</code></pre>
<p>This command launches a new Ubuntu-based container within the same pod, providing a full-fledged environment to diagnose the application container. Even if the original container lacks a shell or crashes, the ephemeral container remains operational, allowing you to perform necessary checks and install tools as needed. It relies on the fact that we can have multiple containers in the same pod, that way we can inspect the filesystem of the debugged container without physically entering that container.</p>
<h3 id="heading-practical-application-of-ephemeral-containers">Practical Application of Ephemeral Containers</h3>
<p>To illustrate, let’s delve deeper into how ephemeral containers can be used in real-world scenarios. Suppose you have a container that consistently crashes due to a mysterious issue. By deploying an ephemeral container with a comprehensive set of debugging tools, you can monitor the logs, inspect the filesystem, and trace processes without worrying about the constraints of the original container environment.</p>
<p>For instance, you might encounter a situation where an application container crashes due to an unhandled exception. By using <code>kubectl debug</code>, you can create an ephemeral container that shares the same network namespace as the original container. This allows you to capture network traffic and analyze it to understand if there are any issues related to connectivity or data corruption.</p>
<h3 id="heading-security-considerations">Security Considerations</h3>
<p>While ephemeral containers reduce the risk of impacting the production environment, they still pose security risks. It’s critical to restrict access to debugging tools and ensure that only authorized personnel can deploy ephemeral containers. Treat access to these systems with the same caution as handing over the keys to your infrastructure.</p>
<p>Ephemeral containers, by their nature, can access sensitive information within the pod. Therefore, it is essential to enforce strict access controls and audit logs to track who is deploying these containers and what actions are being taken. This ensures that the debugging process does not introduce new vulnerabilities or expose sensitive data.</p>
<h3 id="heading-interlude-the-role-of-observability">Interlude: The Role of Observability</h3>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/bRnOGb7rUV4">https://youtu.be/bRnOGb7rUV4</a></div>
<p> </p>
<p>While tools like <code>kubectl exec</code> and <code>kubectl debug</code> are invaluable for troubleshooting, they are not replacements for comprehensive observability solutions. Observability allows you to monitor, trace, and log the behavior of your applications in real-time, providing deeper insights into issues without the need for intrusive debugging sessions.</p>
<p>These tools aren't meant for everyday debugging, that role should be occupied by various observability tools. I will discuss observability in more detail in an upcoming post.</p>
<h3 id="heading-command-line-debugging">Command Line Debugging</h3>
<p>While tools like <code>kubectl exec</code> and <code>kubectl debug</code> are invaluable, there are times when you need to dive deep into the application code itself. This is where we can use command line debuggers. Command line debuggers allow you to inspect the state of your application at a very granular level, stepping through code, setting breakpoints, and examining variable states. Personally, I don't use them much</p>
<p>For instance, Java developers can use <code>jdb</code>, the Java Debugger, which is analogous to <code>gdb</code> for C/C++ programs. Here’s a basic rundown of how you might use <code>jdb</code> in a Kubernetes environment:</p>
<p><strong>Set Up Debugging</strong>: First, you need to start your Java application with debugging enabled. This typically involves adding a debug flag to your Java command. However, as discussed in <a target="_blank" href="https://debugagent.com/mastering-jhsdb-the-hidden-gem-for-debugging-jvm-issues">my post here</a>, there's an even more powerful way that doesn't require a restart:</p>
<pre><code class="lang-bash">java -agentlib:jdwp=transport=dt_socket,server=y,<span class="hljs-built_in">suspend</span>=n,address=*:5005 -jar myapp.jar
</code></pre>
<p><strong>Port Forwarding</strong>: Since the debugger needs to connect to the application, you’ll set up port forwarding to expose the debug port of your pod to your local machine. This is important as <a target="_blank" href="https://debugagent.com/remote-debugging-dangers-and-pitfalls">JDWP is dangerous</a>:</p>
<pre><code class="lang-bash">kubectl port-forward &lt;pod-name&gt; 5005:5005
</code></pre>
<p><strong>Connecting the Debugger</strong>: With port forwarding in place, you can now connect <code>jdb</code> to the remote application:</p>
<pre><code class="lang-bash">jdb -attach localhost:5005
</code></pre>
<p>From here, you can use <code>jdb</code> commands to set breakpoints, step through code, and inspect variables. This process allows you to debug issues within the code itself, which can be invaluable for diagnosing complex problems that aren’t immediately apparent through logs or superficial inspection.</p>
<h3 id="heading-connecting-a-standard-ide-for-remote-debugging">Connecting a Standard IDE for Remote Debugging</h3>
<p>I prefer IDE debugging by far. I never used JDB for anything other than a demo. Modern IDEs support remote debugging, and by leveraging Kubernetes port forwarding, you can connect your IDE directly to a running application inside a pod.</p>
<p>To set up remote debugging we start with the same steps as the command line debugging. Configuring the application and setting up the port forwarding.</p>
<ol>
<li><p><strong>Configure the IDE</strong>: In your IDE (e.g., IntelliJ IDEA, Eclipse), set up a remote debugging configuration. Specify the host as <a target="_blank" href="http://localhost"><code>localhost</code></a> and the port as <code>5005</code>.</p>
</li>
<li><p><strong>Start Debugging</strong>: Launch the remote debugging session in your IDE. You can now set breakpoints, step through code, and inspect variables directly within the IDE, just as if you were debugging a local application.</p>
</li>
</ol>
<p>I show how to do it in IntelliJ/IDEA <a target="_blank" href="https://debugagent.com/remote-debugging-dangers-and-pitfalls">here</a>.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Debugging Kubernetes environments requires a blend of traditional techniques and modern tools designed for container orchestration. Understanding the limitations of <code>kubectl exec</code> and the benefits of ephemeral containers can significantly enhance your troubleshooting process. However, the ultimate goal should be to build robust observability into your applications, reducing the need for ad-hoc debugging and enabling proactive issue detection and resolution.</p>
<p>By following these guidelines and leveraging the right tools, you can navigate the complexities of Kubernetes debugging with confidence and precision. In the next installment of this series, we’ll delve into common configuration issues in Kubernetes and how to address them effectively.</p>
]]></content:encoded></item><item><title><![CDATA[Debugging Kubernetes Part 1: An Introduction]]></title><description><![CDATA[While debugging in an IDE or using simple command line tools is relatively straightforward, the real challenge lies in production debugging. Modern production environments have enabled sophisticated self-healing deployments, yet they have also made t...]]></description><link>https://debugagent.com/debugging-kubernetes-part-1-an-introduction</link><guid isPermaLink="true">https://debugagent.com/debugging-kubernetes-part-1-an-introduction</guid><category><![CDATA[debugging]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[k8s]]></category><category><![CDATA[containers]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 14 May 2024 11:35:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715577102941/f4775a28-c22c-40c4-8c1e-0c50c8c0c7c7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While debugging in an IDE or using simple command line tools is relatively straightforward, the real challenge lies in production debugging. Modern production environments have enabled sophisticated self-healing deployments, yet they have also made troubleshooting more complex. Kubernetes (aka k8s) is probably the most well known orchestration production environment. To effectively teach debugging in Kubernetes, it's essential to first introduce its fundamental principles.</p>
<p>This part of the debugging series is designed for developers looking to effectively tackle application issues within Kubernetes environments, without delving deeply into the complex DevOps aspects typically associated with its operations. Kubernetes is a big subject, it took me two videos just to explain the basic concepts and background.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/sWclLQgbIUQ">https://youtu.be/sWclLQgbIUQ</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-introduction-to-kubernetes-and-distributed-systems">Introduction to Kubernetes and Distributed Systems</h2>
<p>Kubernetes, while often discussed in the context of cloud computing and large-scale operations, is not just a tool for managing containers. Its principles apply broadly to all large-scale distributed systems. In this post I want to explore Kubernetes from the ground up, emphasizing its role in solving real-world problems faced by developers in production environments.</p>
<h3 id="heading-the-evolution-of-deployment-technologies">The Evolution of Deployment Technologies</h3>
<p>Before Kubernetes, the deployment landscape was markedly different. Understanding this evolution helps appreciate the challenges Kubernetes aims to solve. The image below represents the road to Kubernetes and the technologies we passed along the way.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715511705281/1e3e8c6c-559f-4e95-94dd-c0500863613d.png" alt class="image--center mx-auto" /></p>
<p>In the image we can see that initially, applications were deployed directly onto physical servers. This process was manual, error-prone, and difficult to replicate across multiple environments. For instance, if a company needed to scale its application, it involved procuring new hardware, installing operating systems, and configuring the application from scratch. This could take weeks or even months, leading to significant downtime and operational inefficiencies.</p>
<p>Imagine a retail company preparing for the holiday season surge. Each time they needed to handle increased traffic, they would manually set up additional servers. This was not only time-consuming but also prone to human error. Scaling down after the peak period was equally cumbersome, leading to wasted resources.</p>
<h3 id="heading-enter-virtualization">Enter Virtualization</h3>
<p>Virtualization technology introduced a layer that emulated the hardware, allowing for easier replication and migration of environments but at the cost of performance. However, fast virtualization enabled the cloud revolution. It let companies like Amazon lease its servers at scale without compromising their own workloads.</p>
<p>Virtualization involves running multiple operating systems on a single physical hardware host. Each virtual machine (VM) includes a full copy of an operating system, the application, necessary binaries, and libraries—taking up tens of GBs. VMs are managed via a hypervisor, such as VMware's ESXi or Microsoft's Hyper-V, which sits between the hardware and the operating system and is responsible for distributing hardware resources among the VMs. This layer adds additional overhead and can lead to decreased performance due to the need to emulate hardware.</p>
<p>Note that virtualization is often referred to as "virtual machines", I chose to avoid that terminology due to the focus of this blog on Java and the JVM where a virtual machine is typically a reference to the Java Virtual Machine (JVM).</p>
<h3 id="heading-rise-of-containers">Rise of Containers</h3>
<p>Containers emerged as a lightweight alternative to full virtualization. Tools like Docker standardized container formats, making it easier to create and manage containers without the overhead associated with traditional virtual machines. Containers encapsulate an application’s runtime environment, making them portable and efficient.</p>
<p>Unlike virtualization, containerization encapsulates an application in a container with its own operating environment, but it shares the host system’s kernel with other containers. Containers are thus much more lightweight, as they do not require a full OS instance; instead, they include only the application and its dependencies, such as libraries and binaries. This setup reduces the size of each container and improves boot times and performance by removing the hypervisor layer.</p>
<p>Containers operate using several key Linux kernel features:</p>
<ul>
<li><p><strong>Namespaces</strong>: Containers use namespaces to provide isolation for global system resources between independent containers. This includes aspects of the system like process IDs, networking interfaces, and file system mounts. Each container has its own isolated namespace, which gives it a private view of the operating system with access only to its resources.</p>
</li>
<li><p><strong>Control Groups (cgroups)</strong>: Cgroups further enhance the functionality of containers by limiting and prioritizing the hardware resources a container can use. This includes parameters such as CPU time, system memory, network bandwidth, or combinations of these resources. By controlling resource allocation, cgroups ensure that containers do not interfere with each other’s performance and maintain the efficiency of the underlying server.</p>
</li>
<li><p><strong>Union File Systems</strong>: Containers use union file systems, such as OverlayFS, to layer files and directories in a lightweight and efficient manner. This system allows containers to appear as though they are running on their own operating system and file system, while they are actually sharing the host system’s kernel and base OS image.</p>
</li>
</ul>
<h3 id="heading-rise-of-orchestration">Rise of Orchestration</h3>
<p>As containers began to replace virtualization due to their efficiency and speed, developers and organizations rapidly adopted them for a wide range of applications. However, this surge in container usage brought with it a new set of challenges, primarily related to managing large numbers of containers at scale.</p>
<p>While containers are incredibly efficient and portable, they introduce complexities when used extensively, particularly in large-scale, dynamic environments:</p>
<ul>
<li><p><strong>Management Overhead</strong>: Manually managing hundreds or even thousands of containers quickly becomes unfeasible. This includes deployment, networking, scaling, and ensuring availability and security.</p>
</li>
<li><p><strong>Resource Allocation</strong>: Containers must be efficiently scheduled and managed to optimally use physical resources, avoiding underutilization or overloading of host machines.</p>
</li>
<li><p><strong>Service Discovery and Load Balancing</strong>: As the number of containers grows, keeping track of which container offers which service and how to balance the load between them becomes critical.</p>
</li>
<li><p><strong>Updates and Rollbacks</strong>: Implementing rolling updates, managing version control, and handling rollbacks in a containerized environment require robust automation tools.</p>
</li>
</ul>
<p>To address these challenges, the concept of container orchestration was developed. Orchestration automates the scheduling, deployment, scaling, networking, and lifecycle management of containers, which are often organized into microservices. Efficient orchestration tools help ensure that the entire container ecosystem is healthy and that applications are running as expected.</p>
<h2 id="heading-enter-kubernetes">Enter Kubernetes</h2>
<p>Among the orchestration tools, Kubernetes emerged as a frontrunner due to its robust capabilities, flexibility, and strong community support. Kubernetes offers several features that address the core challenges of managing containers:</p>
<ul>
<li><p><strong>Automated Scheduling</strong>: Kubernetes intelligently schedules containers on the cluster’s nodes, taking into account the resource requirements and other constraints, optimizing for efficiency and fault tolerance.</p>
</li>
<li><p><strong>Self-Healing Capabilities</strong>: It automatically replaces or restarts containers that fail, ensuring high availability of services.</p>
</li>
<li><p><strong>Horizontal Scaling</strong>: Kubernetes can automatically scale applications up and down based on demand, which is essential for handling varying loads efficiently.</p>
</li>
<li><p><strong>Service Discovery and Load Balancing</strong>: Kubernetes can expose a container using the DNS name or using its own IP address. If traffic to a container is high, Kubernetes is able to load balance and distribute the network traffic so that the deployment is stable.</p>
</li>
<li><p><strong>Automated Rollouts and Rollbacks</strong>: Kubernetes allows you to describe the desired state for your deployed containers using declarative configuration, and can change the actual state to the desired state at a controlled rate, such as to roll out a new version of an application.</p>
</li>
</ul>
<h3 id="heading-why-kubernetes-stands-out">Why Kubernetes Stands Out</h3>
<p>Kubernetes not only solves practical, operational problems associated with running containers but also integrates with the broader technology ecosystem, supporting continuous integration and continuous deployment (CI/CD) practices. It is backed by the Cloud Native Computing Foundation (CNCF), ensuring it remains cutting-edge and community-focused.</p>
<p>There used to be a site called "doyouneedkubernetes.com" when you visited that site it said "No". Most of us don't need Kubernetes and it is often a symptom of Resume Driven Design (RDD). However, even when we don't need its scaling capabilities the advantages of its standardization are tremendous. Kubernetes became the de-facto standard and created a cottage industry of tools around it. Features such as, observability and security can be plugged in easily. Cloud migration becomes arguably easier. Kubernetes is now the "lingua franca" of production environments.</p>
<h2 id="heading-kubernetes-for-developers">Kubernetes For Developers</h2>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/4_uSwwGEK58">https://youtu.be/4_uSwwGEK58</a></div>
<p> </p>
<p>Understanding Kubernetes architecture is crucial for debugging and troubleshooting. The following image shows the high level view of a Kubernetes deployment. There are far more details in most tutorials geared towards DevOps engineers, but for a developer the point that matters is just "Your Code": that tiny corner at the edge.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715533405470/1736c8f1-2473-4bb2-9368-6c712056a15b.png" alt class="image--center mx-auto" /></p>
<p>In the image above we can see:</p>
<ul>
<li><p><strong>Master Node (represented by the blue Kubernetes logo on the left)</strong>: The control plane of Kubernetes, responsible for managing the state of the cluster, scheduling applications, and handling replication.</p>
</li>
<li><p><strong>Worker Nodes</strong>: These nodes contain the pods that run the containerized applications. Each worker node is managed by the master.</p>
</li>
<li><p><strong>Pods</strong>: The smallest deployable units created and managed by Kubernetes, usually containing one or more containers that need to work together.</p>
</li>
</ul>
<p>These components work together to ensure that an application runs smoothly and efficiently across the cluster.</p>
<h3 id="heading-kubernetes-basics-in-practice">Kubernetes Basics In Practice</h3>
<p>Up until now this post has been theory heavy, let's review some commands we can use to work with a Kubernetes cluster. First we would want to list the pods we have within the cluster which we can do using the <code>get pods</code> command as such:</p>
<pre><code class="lang-bash">$ kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
  my-first-pod-id-xxxx     1/1    Running   0          13s
  my-second-pod-id-xxxx    1/1    Running   0          13s
</code></pre>
<p>A command such as <code>kubectl describe pod</code> returns high level description of the pod such as its name, parent node, etc. Many problems in production pods can be solved by looking at the system log, this can be accomplished by invoking the <code>logs</code> command:</p>
<pre><code class="lang-bash">$ kubectl logs -f &lt;pod&gt;
[2022-11-29 04:12:17,262] INFO <span class="hljs-built_in">log</span> data
...
</code></pre>
<p>Most typical large scale applications logs are ingested by tools such as Elastic, Loki etc. As such, the logs command isn't as useful in production except for debugging edge cases.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>This introduction to Kubernetes has set the stage for deeper exploration into specific debugging and troubleshooting techniques, which we will cover in the upcoming posts. The complexity of Kubernetes makes is much harder to debug, but there are facilities in place to workaround some of that complexity.</p>
<p>While this article (and its followups) focus on Kubernetes, future posts will delve into observability and related tools, which are crucial for effective debugging in production environments.</p>
]]></content:encoded></item><item><title><![CDATA[Failure is Required: Understanding Fail-Safe and Fail-Fast Strategies]]></title><description><![CDATA[Failures in software systems are inevitable. How these failures are handled can significantly impact system performance, reliability, and the business’s bottom line. In this post I want to discuss the upside of failure. Why you should seek failure, w...]]></description><link>https://debugagent.com/failure-is-required-understanding-fail-safe-and-fail-fast-strategies</link><guid isPermaLink="true">https://debugagent.com/failure-is-required-understanding-fail-safe-and-fail-fast-strategies</guid><category><![CDATA[debugging]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Java]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 30 Apr 2024 11:22:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1714444446630/2c51814b-fb1f-45b8-866f-cc1dd0027a6d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Failures in software systems are inevitable. How these failures are handled can significantly impact system performance, reliability, and the business’s bottom line. In this post I want to discuss the upside of failure. Why you should seek failure, why failure is good and why avoiding failure can reduce the reliability of your application. We will start with the discussion of fail-fast vs. fail-safe, this will take us to the second discussion about failures in general.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/9Yv1Jj3yn2c">https://youtu.be/9Yv1Jj3yn2c</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-fail-fast">Fail-Fast</h2>
<p>Fail-fast systems are designed to immediately stop functioning upon encountering an unexpected condition. This immediate failure helps to catch errors early, making debugging more straightforward.</p>
<p>The fail-fast approach ensures that errors are caught immediately. For example, in the world of programming languages, Java embodies this approach by producing a <code>NullPointerException</code> instantly when encountering a <code>null</code> value, stopping the system and making the error clear. This immediate response helps developers identify and address issues quickly, preventing them from becoming more serious.</p>
<p>By catching and stopping errors early, fail-fast systems reduce the risk of cascading failures, where one error leads to others. This makes it easier to contain and resolve issues before they spread through the system, preserving overall stability.</p>
<p>It is easy to write unit and integration tests for fail-fast systems. This advantage is even more pronounced when we need to understand the test failure. Fail-fast systems usually point directly at the problem in the error stack trace.</p>
<p>However, fail-fast systems carry their own risks, particularly in production environments:</p>
<ul>
<li><p><strong>Production Disruptions:</strong> If a bug reaches production, it can cause immediate and significant disruptions, potentially impacting both system performance and the business’s operations.</p>
</li>
<li><p><strong>Risk Appetite:</strong> Fail-fast systems require a level of risk tolerance from both engineers and executives. They need to be prepared to handle and address failures quickly, often balancing this with potential business impacts.</p>
</li>
</ul>
<h2 id="heading-fail-safe">Fail-Safe</h2>
<p>Fail-safe systems take a different approach, aiming to recover and continue even in the face of unexpected conditions. This makes them particularly suited for uncertain or volatile environments.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714385208378/bc080fa5-c7cf-4b7f-88c9-c44e39dd9a31.png" alt class="image--center mx-auto" /></p>
<p>Microservices are a prime example of fail-safe systems, embracing resiliency through their architecture. Circuit breakers, both physical and software-based, disconnect failing functionality to prevent cascading failures, helping the system continue operating.</p>
<p>Fail-safe systems ensure that systems can survive even harsh production environments, reducing the risk of catastrophic failure. This makes them particularly suited for mission-critical applications, such as in hardware devices or aerospace systems, where smooth recovery from errors is crucial.</p>
<p>However, fail-safe systems have downsides:</p>
<ul>
<li><p><strong>Hidden Errors:</strong> By attempting to recover from errors, fail-safe systems can delay the detection of issues, making them harder to trace and potentially leading to more severe cascading failures.</p>
</li>
<li><p><strong>Debugging Challenges:</strong> This delayed nature of errors can complicate debugging, requiring more time and effort to find and resolve issues.</p>
</li>
</ul>
<h2 id="heading-choosing-between-fail-fast-and-fail-safe">Choosing Between Fail-Fast and Fail-Safe</h2>
<p>It's challenging to determine which approach is better, as both have their merits. Fail-fast systems offer immediate debugging, lower risk of cascading failures, and quicker detection and resolution of bugs. This helps catch and fix issues early, preventing them from spreading.</p>
<p>Fail-safe systems handle errors gracefully, making them better suited for mission-critical systems and volatile environments, where catastrophic failures can be devastating.</p>
<h3 id="heading-balancing-both">Balancing Both</h3>
<p>To leverage the strengths of each approach, a balanced strategy can be effective:</p>
<ul>
<li><p><strong>Fail-Fast for Local Services:</strong> When invoking local services like databases, fail-fast can catch errors early, preventing cascading failures.</p>
</li>
<li><p><strong>Fail-Safe for Remote Resources:</strong> When relying on remote resources, such as external web services, fail-safe can prevent disruptions from external failures.</p>
</li>
</ul>
<p>A balanced approach also requires clear and consistent implementation throughout coding, reviews, tooling, and testing processes, ensuring it is integrated seamlessly. Fail-fast can integrate well with orchestration and observability. Effectively, this moves the fail-safe aspect to a different layer of OPS instead of into the developer layer.</p>
<h3 id="heading-consistent-layer-behavior">Consistent Layer Behavior</h3>
<p>This is where things get interesting. It isn't about choosing between fail-safe and fail-fast. It's about choosing the right layer for them. E.g. if an error is handled in a deep layer using a fail-safe approach, it won't be noticed. This might be OK, but if that error has adverse impact (performance, garbage data, corruption, security, etc.) then we will have a problem later on and won't have a clue.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714386047124/abdf8ace-6a95-4867-9b15-8f2dc1bf7cd6.png" alt class="image--center mx-auto" /></p>
<p>The right solution is to handle all errors in a single layer, in modern systems the top layer is the OPS layer and it makes the most sense. It can report the error to the engineers who are most qualified to deal with the error. But they can also provide immediate mitigation such as restarting a service, allocating additional resources or reverting a version.</p>
<h3 id="heading-retrys-are-not-fail-safe">Retry's Are not Fail-Safe</h3>
<p>Recently I was at a lecture where the speakers listed their updated cloud architecture. They chose to take a shortcut to microservices by using a framework that allows them to retry in the case of failure. Unfortunately, failure doesn't behave the way we would like. You can't eliminate it completely through testing alone. Retry, isn't fail-safe. In-fact: it can mean catastrophe.</p>
<p>They tested their system and "it works", even in production. But lets assume that a catastrophic situation does occur, their retry mechanism can operate as a denial of service attack against their own servers. The number of ways in which ad-hoc architectures such as this can fail are mind-boggling.</p>
<p>This is especially important once we redefine failures.</p>
<h2 id="heading-redefining-failure">Redefining Failure</h2>
<p>Failures in software systems aren't just about crashes. A crash can be seen as a simple and immediate failure, but there are more complex issues to consider. In fact, crashes in the age of containers are probably the best failures. A system restarts seamlessly with barely an interruption.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/N4OFIiJV22I">https://youtu.be/N4OFIiJV22I</a></div>
<p> </p>
<h3 id="heading-data-corruption">Data Corruption</h3>
<p>Data corruption is far more severe and insidious than a crash. It carries with it long-term consequences. Corrupted data can lead to security and reliability problems that are challenging to fix, requiring extensive reworking and potentially unrecoverable data.</p>
<p>Cloud computing has led to defensive programming techniques, like circuit breakers and retries, emphasizing comprehensive testing and logging to catch and handle failures gracefully. In a way, this environment sent us back in terms of quality.</p>
<p>A fail-fast system at the data level could stop this from happening. Addressing a bug goes beyond a simple fix. It requires understanding its root cause and preventing reoccurrence, extending into comprehensive logging, testing, and process improvements. This ensures that the bug is fully addressed, reducing the chances of it reoccurring.</p>
<h3 id="heading-dont-fix-the-bug">Don't Fix the Bug</h3>
<p>If it's a bug in production you should probably revert, if you can't instantly revert production. This should always be possible and if it isn't this is something you should work on.</p>
<p>Failures must be fully understood before a fix is undertaken. In my own companies I often skipped that step due to pressure, in a small startup that is forgivable. In larger companies we need to understand the root cause. A culture of debrief for bugs and production issues is essential. The fix should also include process mitigation that prevents similar issues from reaching production.</p>
<h2 id="heading-debugging-failure">Debugging Failure</h2>
<p>Fail-fast systems are much easier to debug. They have inherently simpler architecture and it is easier to pinpoint an issue to a specific area. It is crucial to throw exceptions even for minor violations (e.g. validations). This prevents cascading types of bugs that prevail in loose systems.</p>
<p>This should be further enforced by unit tests that verify the limits we define and verify proper exceptions are thrown. Retries should be avoided in the code as they make debugging exceptionally difficult and their proper place is in the OPS layer. To facilitate that further, timeouts should be short by default.</p>
<h3 id="heading-avoiding-cascading-failure">Avoiding Cascading Failure</h3>
<p>Failure isn't something we can avoid, predict or fully test against. The only thing we can do is soften the blow when a failure occurs. Often this "softening" is achieved by using long running tests meant to replicate extreme conditions as much as possible with the goal of finding our applications weak spots. This is rarely enough, robust systems need to revise these tests often based on real production failures.</p>
<p>A great example of fail-safe would be a cache of REST responses that lets us keep working even when a service is down. Unfortunately, this can lead to complex niche issues such as cache poisoning or a situation in which a banned user still had access due to cache.</p>
<h2 id="heading-hybrid-in-production">Hybrid in Production</h2>
<p>Fail-safe is best applied only in production/staging and in the OPS layer. This reduces the amount of changes between production and dev, we want them to be as similar as possible, yet it's still a change which can negatively impact production. But the benefits are tremendous as observability can get a clear picture of system failures.</p>
<p>The discussion here is a bit colored by my more recent experience of building observable cloud architectures. However, the same principle applies to any type of software whether embedded or in the cloud. In such cases we often choose to implement fail-safe in the code, in this case I would suggest implementing it consistently and consciously in a specific layer.</p>
<p>There's also a special case of libraries/frameworks that often provide inconsistent and badly documented behaviors in these situations. I myself am guilty of such inconsistency in some of my work. It's an easy mistake to make.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>This is my last post on the theory of debugging series that's part of my book/course on debugging. We often think of debugging as the action we take when something fails, it isn't. Debugging starts the moment we write the first line of code. We make decisions that will impact the debugging process as we code, often we're just unaware of these decisions until we get a failure.</p>
<p>I hope this post and series will help you write code that is prepared for the unknown. Debugging, by its nature, deals with the unexpected. Tests can't help. But as I illustrated in my previous posts, there are many simple practices we can undertake that would make it easier to prepare. This isn't a one time process, it's an iterative process that requires re-evaluation of decisions made as we encounter failure.</p>
]]></content:encoded></item><item><title><![CDATA[Software Testing as a Debugging Tool]]></title><description><![CDATA[Debugging is not just about identifying errors—it's about instituting a reliable process for ensuring software health and longevity. In this post we discuss the role of software testing in debugging, including foundational concepts and how they conve...]]></description><link>https://debugagent.com/software-testing-as-a-debugging-tool</link><guid isPermaLink="true">https://debugagent.com/software-testing-as-a-debugging-tool</guid><category><![CDATA[Testing]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Java]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 16 Apr 2024 12:13:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713183819632/76015125-1468-4377-92b3-aec77fa9a00d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Debugging is not just about identifying errors—it's about instituting a reliable process for ensuring software health and longevity. In this post we discuss the role of software testing in debugging, including foundational concepts and how they converge to improve software quality.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/yap509UZz6M">https://youtu.be/yap509UZz6M</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-the-intersection-of-debugging-and-testing">The Intersection of Debugging and Testing</h2>
<p>Debugging and testing play distinct roles in software development. Debugging is the targeted process of identifying and fixing known bugs. Testing, on the other hand, encompasses a adjacent scope, identifying unknown issues by validating expected software behavior across a variety of scenarios.</p>
<p>Both are a part of the debug fix cycle which is a core concept in debugging. Before we cover the cycle we should first make sure we're aligned on the basic terminology.</p>
<h3 id="heading-unit-tests">Unit Tests</h3>
<p>Unit tests are tightly linked to debugging efforts, focusing on isolated parts of the application—typically individual functions or methods. Their purpose is to validate that each unit operates correctly in isolation, making them a swift and efficient tool in the debugging arsenal. These tests are characterized by their speed and consistency, enabling developers to run them frequently, sometimes even automatically as code is written within the IDE.</p>
<p>Since software is so tightly bound it is nearly impossible to compose unit tests without extensive mocking. Mocking involves substituting a genuine component with a stand-in that returns predefined results, thus a test method can simulate scenarios without relying on the actual object. This is a powerful yet controversial tool. By using mocking we're in-effect creating a synthetic environment that might misrepresent the real world. We're reducing the scope of the test and might perpetuate some bugs.</p>
<h3 id="heading-integration-tests">Integration Tests</h3>
<p>Opposite to unit tests, integration tests examine the interactions between multiple units, providing a more comprehensive picture of the system's health. While they cover broader scenarios, their setup can be more complex due to the interactions involved. However, they are crucial in catching bugs that arise from the interplay between different software components.</p>
<p>In general mocking can be used in integration tests but it is discouraged. They take longer to run and are sometimes harder to set up. However, many developers (myself included) would argue that they are the only benchmark for quality. Most bugs express themselves in the seams between the modules and integration tests are better at detecting that.</p>
<p>Since they are far more important some developers would argue that unit tests are unnecessary. This isn't true, unit test failures are much easier to read and understand. Since they are faster we can run them during development, even while typing. In that sense the balance between the two approaches is the important part.</p>
<h3 id="heading-coverage">Coverage</h3>
<p>Coverage is a metric that helps quantify the effectiveness of testing by indicating the proportion of code exercised by tests. It helps identify potential areas of the code that have not been tested, which could harbor undetected bugs. However, striving for 100% coverage can be a case of diminishing returns; the focus should remain on the quality and relevance of the tests rather than the metric itself. In my experience, chasing high coverage numbers often results in bad test practices that persist problems.</p>
<p>It is my opinion that unit tests should be excluded from coverage metrics due to the importance of integration tests to overall quality. To get a sense of quality coverage should focus on integration and end to end tests.</p>
<h2 id="heading-the-debug-fix-cycle">The Debug-Fix Cycle</h2>
<p>The debug-fix cycle is a structured approach that integrates testing into the debugging process. The stages include identifying the bug, creating a test that reproduces the bug, fixing the bug, verifying the fix with the test, and finally, running the application to ensure the fix works in the live environment. This cycle emphasizes the importance of testing in not only identifying but also in preventing the recurrence of bugs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713176625855/20ea89b6-b0e4-4216-a6f4-ffa6a7383c9a.png" alt class="image--center mx-auto" /></p>
<p>Notice that this is a simplified version of the cycle with a focus on the testing aspect only. The full cycle includes discussion of the issue tracking and versioning as part of the whole process. I discuss this more in-depth in other posts in the series and my book.</p>
<h2 id="heading-composing-tests-with-debuggers">Composing Tests with Debuggers</h2>
<p>A powerful feature of using debuggers in test composition is their ability to "<a target="_blank" href="https://debugagent.com/debugging-program-control-flow">jump to line</a>" or "<a target="_blank" href="https://debugagent.com/watch-and-evaluate">set value</a>." Developers can effectively reset the execution to a point before the test and rerun it with different conditions, without recompiling or rerunning the entire suite. This iterative process is invaluable for achieving desired test constraints and improves the quality of unit tests by refining the input parameters and expected outcomes.</p>
<p>Increasing test coverage is about more than hitting a percentage; it's about ensuring that tests are meaningful and that they contribute to software quality. A debugger can significantly assist in this by identifying untested paths. When a test coverage tool highlights lines or conditions not reached by current tests, the debugger can be used to force execution down those paths. This helps in crafting additional tests that cover missed scenarios, ensuring that the coverage metric is not just a number but a true reflection of the software's tested state.</p>
<p>In this case you will notice that the next line in the body is a rejectValue call which will throw an exception. I don’t want an exception thrown as I still want to test all the permutations of the method. I can drag the execution pointer (arrow on the left) and place it back at the start of the method.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713181221057/ab4a641a-aca4-4185-a942-cc95774551c8.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-test-driven-development">Test-Driven Development</h2>
<p>How does all of this fit with disciplines like Test-Driven Development (TDD)?</p>
<p>It doesn't fit well. Before we get into that let's revisit the basics of TDD. Weak TDD typically means just writing tests before writing the code. Strong TDD involves a red-green-refactor cycle:</p>
<ol>
<li><p><strong>Red</strong>: Write a test that fails because the feature it tests isn't implemented yet.</p>
</li>
<li><p><strong>Green</strong>: Write the minimum amount of code necessary to make the test pass.</p>
</li>
<li><p><strong>Refactor</strong>: Clean up the code while ensuring that tests continue to pass.</p>
</li>
</ol>
<p>This rigorous cycle guarantees that new code is continually tested and refactored, reducing the likelihood of complex bugs. It also means that when bugs do appear, they are often easier to isolate and fix due to the modular and well-tested nature of the codebase. At least, that's the theory.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/yImkjlm08Cw">https://youtu.be/yImkjlm08Cw</a></div>
<p> </p>
<p>TDD can be especially advantageous for scripting and loosely typed languages. In environments lacking the rigid structure of compilers and linters, TDD steps in to provide the necessary checks that would otherwise be performed during compilation in statically typed languages. It becomes a crucial substitute for compiler/linter checks, ensuring that type and logic errors are caught early.</p>
<p>In real-world application development, TDD's utility is nuanced. While it encourages thorough testing and upfront design, it can sometimes hinder the natural flow of development, especially in complex systems that evolve through numerous iterations. The requirement for 100% test coverage can lead to an unnecessary focus on fulfilling metrics rather than writing meaningful tests.</p>
<p>The biggest problem in TDD is its focus on unit testing. TDD is impractical with integration tests as the process would take too long. But as we determined in the start of this post, integration tests are the true benchmark for quality. In that test TDD is a methodology that provides great quality for arbitrary tests, but not necessarily great quality for the final product. You might have the best cog in the world, but if doesn't fit well into the machine then it isn't great.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>Debugging is a tool that not only fixes bugs but also actively aids in crafting tests that bolster software quality. By utilizing debuggers in test composition and increasing coverage, developers can create a suite of tests that not only identifies existing issues but also guards against future ones, thus ensuring the delivery of reliable, high-quality software.</p>
<p>Debugging lets us increase coverage and verify edge cases effectively. It's part of a standardized process for issue resolution that's critical for reliability and prevents regressions.</p>
]]></content:encoded></item><item><title><![CDATA[Wireshark & tcpdump: A Debugging Power Couple]]></title><description><![CDATA[Wireshark, the free open-source packet sniffer and network protocol analyzer, has cemented itself as an indispensable tool in network troubleshooting, analysis, and security (on both sides). This blog post delves into the features, uses, and practica...]]></description><link>https://debugagent.com/wireshark-tcpdump-a-debugging-power-couple</link><guid isPermaLink="true">https://debugagent.com/wireshark-tcpdump-a-debugging-power-couple</guid><category><![CDATA[debugging]]></category><category><![CDATA[troubleshooting]]></category><category><![CDATA[networking]]></category><category><![CDATA[Wireshark]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 02 Apr 2024 13:25:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711877209323/4f4e9c21-f20e-4123-ab02-92e1dd6b236d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Wireshark, the free open-source packet sniffer and network protocol analyzer, has cemented itself as an indispensable tool in network troubleshooting, analysis, and security (on both sides). This blog post delves into the features, uses, and practical tips for harnessing the full potential of Wireshark, expanding on aspects that may have been glossed over in discussions or demonstrations. Whether you're a developer, security expert, or just curious about network operations, this guide will enhance your understanding of Wireshark and its applications.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/QVWRomT2Ppo">https://youtu.be/QVWRomT2Ppo</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-introduction-to-wireshark">Introduction to Wireshark</h2>
<p>Wireshark was initially developed by Eric Rescorla and Gerald Combs, designed to capture and analyze network packets in real-time. Its capabilities extend across various network interfaces and protocols, making it a versatile tool for anyone involved in networking. Unlike its command-line counterpart, tcpdump, Wireshark's graphical interface simplifies the analysis process, presenting data in a user-friendly "proto view" that organizes packets in a hierarchical structure. This facilitates quick identification of protocols, ports, and data flows.</p>
<p>The key features of Wireshark are:</p>
<ul>
<li><p><strong>Graphical User Interface (GUI):</strong> Eases the analysis of network packets compared to command-line tools.</p>
</li>
<li><p><strong>Proto View:</strong> Displays packet data in a tree structure, simplifying protocol and port identification.</p>
</li>
<li><p><strong>Compatibility:</strong> Supports a wide range of network interfaces and protocols.</p>
</li>
</ul>
<h3 id="heading-browser-network-monitors">Browser Network Monitors</h3>
<p>FireFox and Chrome contain a far superior network monitor tool built into them. It is superior because it is simpler to use and works with secure websites out of the box. If you can use the browser to debug the network traffic you should do that.</p>
<p>In the cases where your traffic requires low level protocol information or is outside of the browser, Wireshark is the next best thing.</p>
<h2 id="heading-installation-and-getting-started">Installation and Getting Started</h2>
<p>To begin with Wireshark, visit their <a target="_blank" href="https://www.wireshark.org/">official website</a> for the download. The installation process is straightforward, but attention should be paid to the installation of command-line tools, which may require separate steps. Upon launching Wireshark, users are greeted with a selection of network interfaces as seen below. Choosing the correct interface, such as the loopback for local server debugging, is crucial for capturing relevant data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711875337637/c9cee93d-59d6-4239-aa4c-2f68accee98b.png" alt class="image--center mx-auto" /></p>
<p>When debugging a Local Server (localhost) use the loopback interface. Remote servers will probably fit with the en0 network adapter. You can use the activity graph next to the network adapter to identify active interfaces for capture.</p>
<h2 id="heading-navigating-through-noise-with-filters">Navigating Through Noise with Filters</h2>
<p>One of the challenges of using Wireshark is the overwhelming amount of data captured, including irrelevant "background noise" as seen in the following image.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711875529541/aa044341-182d-43c8-93fc-2509a8da7be4.png" alt class="image--center mx-auto" /></p>
<p>Wireshark addresses this with powerful display filters, allowing users to hone in on specific ports, protocols, or data types. For instance, filtering TCP traffic on port 8080 can significantly reduce unrelated data, making it easier to debug specific issues.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711875546167/c94b2d03-a5c8-4ad3-b2e7-2727b2551c3b.png" alt class="image--center mx-auto" /></p>
<p>Notice that the there is a completion widget on top of the Wireshark UI that lets you find out the values more easily.</p>
<p>In this case we filter by port <code>tcp.port == 8080</code> which is the port used typically in Java servers (e.g. Spring Boot/tomcat).</p>
<p>But this isn't enough as HTTP is more concise. We can filter by protocol by adding <code>http</code> to the filter which narrows the view to HTTP requests and responses as shown in the following image.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711875720879/660d24d6-82fd-4ce3-98eb-243145749902.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-deep-dive-into-data-analysis">Deep Dive into Data Analysis</h2>
<p>Wireshark excels in its ability to dissect and present network data in an accessible manner. For example, HTTP responses carrying JSON data are automatically parsed and displayed in a readable tree structure as seen below. This feature is invaluable for developers and analysts, providing insights into the data exchanged between clients and servers without manual decoding.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711875752465/3b9eb5fc-3d55-4969-8002-ede85b14efed.png" alt class="image--center mx-auto" /></p>
<p>Wireshark parses and displays JSON data within the packet analysis pane. It offers both hexadecimal and ASCII views for raw packet data.</p>
<h2 id="heading-beyond-basic-usage">Beyond Basic Usage</h2>
<p>While Wireshark's basic functionalities cater to a wide range of networking tasks, its true strength lies in advanced features such as ethernet network analysis, HTTPS decryption, and debugging across devices. These tasks, however, may involve complex configuration steps and a deeper understanding of network protocols and security measures.</p>
<p>There are two big challenges when working with Wireshark:</p>
<ul>
<li><p><strong>HTTPS Decryption:</strong> Decrypting HTTPS traffic requires additional configuration but offers visibility into secure communications.</p>
</li>
<li><p><strong>Device Debugging:</strong> Wireshark can be used to troubleshoot network issues on various devices, requiring specific knowledge of network configurations.</p>
</li>
</ul>
<h3 id="heading-the-basics-of-https-encryption">The Basics of HTTPS Encryption</h3>
<p>HTTPS uses the Transport Layer Security (TLS) or its predecessor, Secure Sockets Layer (SSL), to encrypt data. This encryption mechanism ensures that any data transferred between the web server and the browser remains confidential and untouched. The process involves a series of steps including handshake, data encryption, and data integrity checks.</p>
<p>Decrypting HTTPS traffic is often necessary for developers and network administrators to troubleshoot communication errors, analyze application performance, or ensure that sensitive data is correctly encrypted before transmission. It's a powerful capability in diagnosing complex issues that cannot be resolved by simply inspecting unencrypted traffic or server logs.</p>
<h3 id="heading-methods-for-decrypting-https-in-wireshark">Methods for Decrypting HTTPS in Wireshark</h3>
<p><strong>Important:</strong> Decrypting HTTPS traffic should only be done on networks and systems you own or have explicit permission to analyze. Unauthorized decryption of network traffic can violate privacy laws and ethical standards.</p>
<h4 id="heading-pre-master-secret-key-logging"><strong>Pre-Master Secret Key Logging</strong></h4>
<p>One common method involves using the pre-master secret key to decrypt HTTPS traffic. Browsers like Firefox and Chrome can log the pre-master secret keys to a file when configured to do so. Wireshark can then use this file to decrypt the traffic:</p>
<ol>
<li><p><strong>Configure the Browser:</strong> Set an environment variable (<code>SSLKEYLOGFILE</code>) to specify a file where the browser will save the encryption keys.</p>
</li>
<li><p><strong>Capture Traffic:</strong> Use Wireshark to capture the traffic as usual.</p>
</li>
<li><p><strong>Decrypt the Traffic:</strong> Point Wireshark to the file with the pre-master secret keys (through Wireshark's preferences) to decrypt the captured HTTPS traffic.</p>
</li>
</ol>
<h4 id="heading-using-a-proxy"><strong>Using a Proxy</strong></h4>
<p>Another approach involves routing traffic through a proxy server that decrypts HTTPS traffic and then re-encrypts it before sending it to the destination. This method might require setting up a dedicated decryption proxy that can handle the TLS encryption/decryption:</p>
<ol>
<li><p><strong>Set Up a Decryption Proxy:</strong> Tools like Mitmproxy or Burp Suite can act as an intermediary that decrypts and logs HTTPS traffic.</p>
</li>
<li><p><strong>Configure Network to Route Through Proxy:</strong> Ensure the client's network settings route traffic through the proxy.</p>
</li>
<li><p><strong>Inspect Traffic:</strong> Use the proxy's tools to inspect the decrypted traffic directly.</p>
</li>
</ol>
<h2 id="heading-integrating-tcpdump-with-wireshark-for-enhanced-network-analysis">Integrating tcpdump with Wireshark for Enhanced Network Analysis</h2>
<p>While Wireshark offers a graphical interface for analyzing network packets, there are scenarios where using it directly may not be feasible due to security policies or operational constraints. tcpdump, a powerful command-line packet analyzer, becomes invaluable in these situations, providing a flexible and less intrusive means of capturing network traffic.</p>
<h2 id="heading-the-role-of-tcpdump-in-network-troubleshooting">The Role of tcpdump in Network Troubleshooting</h2>
<p>tcpdump allows for the capture of network packets without a graphical user interface, making it ideal for use in environments with strict security requirements or limited resources. It operates under the principle of capturing network traffic to a file, which can then be analyzed at a later time or on a different machine using Wireshark.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=nLXu3_fzHhQ">https://www.youtube.com/watch?v=nLXu3_fzHhQ</a></div>
<p> </p>
<h4 id="heading-key-scenarios-for-tcpdump-usage">Key Scenarios for tcpdump Usage:</h4>
<ul>
<li><p><strong>High-security Environments:</strong> In places like banks or government institutions where running network sniffers might pose a security risk, tcpdump offers a less intrusive alternative.</p>
</li>
<li><p><strong>Remote Servers:</strong> Debugging issues on a cloud server can be challenging with Wireshark due to the graphical interface; tcpdump captures can be transferred and analyzed locally.</p>
</li>
<li><p><strong>Security-conscious Customers:</strong> Customers may be hesitant to allow third-party tools to run on their systems; tcpdump's command-line operation is often more palatable.</p>
</li>
</ul>
<h3 id="heading-using-tcpdump-effectively">Using tcpdump Effectively</h3>
<p>Capturing traffic with tcpdump involves specifying the network interface and an output file for the capture. This process is straightforward but powerful, allowing for detailed analysis of network interactions:</p>
<ol>
<li><p><strong>Command Syntax:</strong> The basic command structure for initiating a capture involves specifying the network interface (e.g., <code>en0</code> for wireless connections) and the output file name.</p>
</li>
<li><p><strong>Execution:</strong> Once the command is run, tcpdump silently captures network packets. The capture continues until it's manually stopped, at which point the captured data can be saved to the specified file.</p>
</li>
<li><p><strong>Opening Captures in Wireshark:</strong> The file generated by tcpdump can be opened in Wireshark for detailed analysis, utilizing Wireshark's advanced features for dissecting and understanding network traffic.</p>
</li>
</ol>
<p>The following shows the tcpdump command and its output:</p>
<pre><code class="lang-bash">$ sudo tcpdump -i en0 -w output
Password:
tcpdump: listening on en, link-type EN10MB (Ethernet), capture size 262144 bytes
^C3845 packets captured
4189 packets received by filter
0 packets dropped by kernel
</code></pre>
<h3 id="heading-challenges-and-considerations">Challenges and Considerations</h3>
<p>Identifying the correct network interface for capture on remote systems might require additional steps, such as using the <code>ifconfig</code> command to list available interfaces. This step is crucial for ensuring that relevant traffic is captured for analysis.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>Wireshark stands out as a powerful tool for network analysis, offering deep insights into network traffic and protocols. Whether it's for low-level networking work, security analysis, or application development, Wireshark's features and capabilities make it an essential tool in the tech arsenal. With practice and exploration, users can leverage Wireshark to uncover detailed information about their networks, troubleshoot complex issues, and secure their environments more effectively.</p>
<p>Wireshark's blend of ease of use with profound analytical depth ensures it remains a go-to solution for networking professionals across the spectrum. Its continuous development and wide-ranging applicability underscore its position as a cornerstone in the field of network analysis.</p>
<p>Combining tcpdump's capabilities for capturing network traffic with Wireshark's analytical prowess offers a comprehensive solution for network troubleshooting and analysis. This combination is particularly useful in environments where direct use of Wireshark is not possible or ideal. While both tools possess a steep learning curve due to their powerful and complex features, they collectively form an indispensable toolkit for network administrators, security professionals, and developers alike.</p>
<p>This integrated approach not only addresses the challenges of capturing and analyzing network traffic in various operational contexts but also highlights the versatility and depth of tools available for understanding and securing modern networks.</p>
]]></content:encoded></item><item><title><![CDATA[Mastering jhsdb: The Hidden Gem for Debugging JVM Issues]]></title><description><![CDATA[jhsdb is a relatively underexplored yet incredibly powerful tool for debugging JVM issues. Whether you're tackling native code that crashes the JVM or delving into complex performance analysis, understanding how to use jhsdb effectively can be a game...]]></description><link>https://debugagent.com/mastering-jhsdb-the-hidden-gem-for-debugging-jvm-issues</link><guid isPermaLink="true">https://debugagent.com/mastering-jhsdb-the-hidden-gem-for-debugging-jvm-issues</guid><category><![CDATA[debugging]]></category><category><![CDATA[Java]]></category><category><![CDATA[tools]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 26 Mar 2024 11:53:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711355429852/77673558-8f13-4e7b-a6e7-ea4d81bca033.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><code>jhsdb</code> is a relatively underexplored yet incredibly powerful tool for debugging JVM issues. Whether you're tackling native code that crashes the JVM or delving into complex performance analysis, understanding how to use <code>jhsdb</code> effectively can be a game-changer in your debugging arsenal.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=UelhmnOR0lI">https://www.youtube.com/watch?v=UelhmnOR0lI</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-introduction">Introduction</h2>
<p>Java 9 introduced many changes, with modules as the highlight. However, among these significant shifts, <code>jhsdb</code> didn’t get the attention it deserved. Officially, Oracle describes <code>jhsdb</code> as a Serviceability Agent tool, part of the JDK aimed at snapshot debugging, performance analysis, and offering deep insights into the Hotspot JVM and Java applications running on it. Simply put, <code>jhsdb</code> is your go-to for delving into JVM internals, understanding core dumps, and diagnosing JVM or native library failures.</p>
<h2 id="heading-getting-started-with-jhsdb">Getting Started with jhsdb</h2>
<p>To begin we can invoke:</p>
<pre><code class="lang-bash">$ jhsdb --<span class="hljs-built_in">help</span>
clhsdb           <span class="hljs-built_in">command</span> line debugger
hsdb             ui debugger
debugd --<span class="hljs-built_in">help</span>    to get more information
jstack --<span class="hljs-built_in">help</span>    to get more information
jmap   --<span class="hljs-built_in">help</span>    to get more information
jinfo  --<span class="hljs-built_in">help</span>    to get more information
jsnap  --<span class="hljs-built_in">help</span>    to get more information
</code></pre>
<p>This command in reveals that <code>jhsdb</code> includes six distinct tools:</p>
<ol>
<li><p><strong>debugd</strong>: A remote debug server for connecting and diagnosing remotely.</p>
</li>
<li><p><strong>jstack</strong>: Provides detailed stack and lock information.</p>
</li>
<li><p><strong>jmap</strong>: Offers insights into heap memory.</p>
</li>
<li><p><strong>jinfo</strong>: Displays basic JVM information.</p>
</li>
<li><p><strong>jsnap</strong>: Assists with performance data.</p>
</li>
<li><p><strong>Command Line Debugger</strong>: Although there's a preference for the GUI, we'll focus on GUI Debugging for a more visual approach.</p>
</li>
</ol>
<p>Let's dive into these tools and explore how they can aid in diagnosing and resolving JVM issues.</p>
<h3 id="heading-understanding-and-using-debugd">Understanding and Using debugd</h3>
<p><code>debugd</code> might not be your first choice for production environments due to its remote debugging nature. Yet, it could be valuable for local container debugging. To use it we first need to detect the JVM process ID (PID) which we can accomplish using the <code>jps</code> command. Unfortunately, because of a bug in the UI you can’t currently connect to a remote server via the GUI debugger. I could only use this with command-line tools such as <code>jstack</code> (discussed below).</p>
<p>With the command:</p>
<pre><code class="lang-bash">jhsdb debugd --pid 1234
</code></pre>
<p>We can connect to the process 1234. We can then use a tool like <code>jstack</code> to get additional information:</p>
<pre><code class="lang-bash">jhsdb jstack --connect localhost
</code></pre>
<p>Notice that the <code>--connect</code> argument applies globally and should work for all commands.</p>
<h3 id="heading-leveraging-jstack-for-thread-dumps">Leveraging jstack for Thread Dumps</h3>
<p><code>jstack</code> is instrumental in generating thread dumps, crucial for analyzing stack processes in user machines or production environments. This command can reveal detailed JVM running states, including deadlock detection, thread statuses, and compilation insights.</p>
<p>Typically we would use <code>jstack</code> locally which removes the need for <code>debugd</code>:</p>
<pre><code class="lang-bash">$ jhsdb jstack --pid 1234
Attaching to process ID 1234, please <span class="hljs-built_in">wait</span>...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.13+8-LTS
Deadlock Detection:

No deadlocks found.

<span class="hljs-string">"Keep-Alive-Timer"</span> <span class="hljs-comment">#189 daemon prio=8 tid=0x000000011d81f000 nid=0x881f waiting on condition [0x0000000172442000]</span>
   java.lang.Thread.State: TIMED_WAITING (sleeping)
   JavaThread state: _thread_blocked
 - java.lang.Thread.sleep(long) @bci=0 (Interpreted frame)
 - sun.net.www.http.KeepAliveCache.run() @bci=3, line=168 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=829 (Interpreted frame)
 - jdk.internal.misc.InnocuousThread.run() @bci=20, line=134 (Interpreted frame)


<span class="hljs-string">"DestroyJavaVM"</span> <span class="hljs-comment">#171 prio=5 tid=0x000000011f809000 nid=0x2703 waiting on condition [0x0000000000000000]</span>
   java.lang.Thread.State: RUNNABLE
   JavaThread state: _thread_blocked
</code></pre>
<p>This snapshot can help us infer many details about how the application acts locally and in production.</p>
<p>Is our code compiled?</p>
<p>Is it waiting on a monitor?</p>
<p>What other threads are running and what are they doing?</p>
<h3 id="heading-heap-memory-analysis-with-jmap">Heap Memory Analysis with jmap</h3>
<p>For a deep dive into RAM and heap memory, <code>jmap</code> is unmatched. It displays comprehensive heap memory details, aiding in GC tuning and performance optimization. Particularly useful is the <code>histo</code> flag for identifying potential memory leaks through a histogram of RAM usage.</p>
<p>Typical usage of jmap is very similar to <code>jstack</code> and other tools mentioned in this post:</p>
<pre><code class="lang-bash">$ jhsdb jmap --pid 1234 --heap
Attaching to process ID 1234, please <span class="hljs-built_in">wait</span>...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.13+8-LTS

using thread-local object allocation.
Garbage-First (G1) GC with 9 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 17179869184 (16384.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 10305404928 (9828.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 4194304 (4.0MB)

Heap Usage:
G1 Heap:
   regions  = 4096
   capacity = 17179869184 (16384.0MB)
   used     = 323663048 (308.6691360473633MB)
   free     = 16856206136 (16075.330863952637MB)
   1.8839668948203325% used
G1 Young Generation:
Eden Space:
   regions  = 66
   capacity = 780140544 (744.0MB)
   used     = 276824064 (264.0MB)
   free     = 503316480 (480.0MB)
   35.483870967741936% used
Survivor Space:
   regions  = 8
   capacity = 33554432 (32.0MB)
   used     = 33554432 (32.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 4
   capacity = 478150656 (456.0MB)
   used     = 13284552 (12.669136047363281MB)
   free     = 464866104 (443.3308639526367MB)
   2.7783193086322986% used
</code></pre>
<p>In most cases this might seem like gibberish but when we experience GC thrashing this might be a secret weapon in your arsenal. You can use this to fine tune GC settings and determine the right parameters to set. Since this can easily run in production you can base this on real world observations.</p>
<p>If you could reproduce a memory leak but you don’t have a debugger attached, you can use this to generate a memory histogram:</p>
<pre><code class="lang-bash">$ jhsdb jmap --pid 1234 --histo
Attaching to process ID 72640, please <span class="hljs-built_in">wait</span>...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.13+8-LTS
Iterating over heap. This may take a <span class="hljs-keyword">while</span>...
Object Histogram:

num       <span class="hljs-comment">#instances    #bytes    Class description</span>
--------------------------------------------------------------------------
1:        225689    204096416    int[]
2:        485992    59393024    byte[]
3:        17221    23558328    sun.security.ssl.CipherSuite[]
4:        341376    10924032    java.util.HashMap<span class="hljs-variable">$Node</span>
5:        117706    9549752    java.util.HashMap<span class="hljs-variable">$Node</span>[]
6:        306720    7361280    java.lang.String
7:        12718    6713944    char[]
8:        113884    5466432    java.util.HashMap
9:        64683    4657176    java.util.regex.Matcher
10:        95612    4615720    java.lang.Object[]
11:        106233    4249320    java.util.HashMap<span class="hljs-variable">$KeyIterator</span>
12:        16166    4090488    long[]
13:        126977    4063264    java.util.concurrent.ConcurrentHashMap<span class="hljs-variable">$Node</span>
14:        150789    3618936    java.util.ArrayList
15:        130167    3546016    java.lang.String[]
16:        156237    3227152    java.lang.Class[]
17:        33145    2916760    java.lang.reflect.Method
18:        32193    2575440    nonapi.io.github.classgraph.fastzipfilereader.FastZipEntry
19:        17314    2051672    java.lang.Class
20:        32043    1794408    io.github.classgraph.ClasspathElementZip<span class="hljs-variable">$1</span>
21:        107918    1726688    java.util.HashSet
22:        105970    1695520    java.util.HashMap<span class="hljs-variable">$KeySet</span>
</code></pre>
<p>This can help narrow down the source of the issue. There are better tools for that in the IDE and during development. But if you're running a server even locally, it can instantly give you a snapshot of RAM.</p>
<h3 id="heading-basic-jvm-insights-with-jinfo">Basic JVM Insights with jinfo</h3>
<p>Though not as detailed as other commands, <code>jinfo</code> is useful for a quick glance at system properties and JVM flags, especially on unfamiliar machines. It's a straightforward tool that requires just a PID to function.</p>
<pre><code class="lang-bash">jhsdb jinfo --pid 1234
</code></pre>
<h3 id="heading-performance-metrics-with-jsnap">Performance Metrics with jsnap</h3>
<p><code>jsnap</code> offers a wealth of internal metrics and statistics, such as thread counts and peak numbers. This data is vital for fine-tuning aspects like thread pool sizes, directly impacting production overhead.</p>
<pre><code class="lang-bash">$ jhsdb jsnap --pid 72640
Attaching to process ID 72640, please <span class="hljs-built_in">wait</span>...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.13+8-LTS
java.threads.started=418 event(s)
java.threads.live=12
java.threads.livePeak=30
java.threads.daemon=8
java.cls.loadedClasses=16108 event(s)
java.cls.unloadedClasses=0 event(s)
java.cls.sharedLoadedClasses=0 event(s)
java.cls.sharedUnloadedClasses=0 event(s)
java.ci.totalTime=23090159603 tick(s)
java.property.java.vm.specification.version=11
java.property.java.vm.specification.name=Java Virtual Machine Specification
java.property.java.vm.specification.vendor=Oracle Corporation
java.property.java.vm.version=11.0.13+8-LTS
java.property.java.vm.name=OpenJDK 64-Bit Server VM
java.property.java.vm.vendor=Azul Systems, Inc.
java.property.java.vm.info=mixed mode
java.property.jdk.debug=release
</code></pre>
<h3 id="heading-gui-debugging-a-visual-approach">GUI Debugging: A Visual Approach</h3>
<p>We'll skip over the CLI debugger, the GUI debugger deserves a mention for its user-friendly interface, allowing connections to core files, servers, or PIDs with ease. This visual tool opens up a new dimension in debugging, especially when working with JNI native code.</p>
<p>The GUI debugger can be launched just like any other of the <code>jhsdb</code> tools:</p>
<pre><code class="lang-bash">jhsdb hsdb --pid 1234
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658836335333/IVFA84YIV.png?auto=compress,format&amp;format=webp" alt="image02-hsdb-gui.png" /></p>
<p>The GUI layout is designed for ease of navigation, offering a comprehensive view of JVM internals at a glance. Here are some key features and how to use them:</p>
<ul>
<li><p><strong>File Menu</strong>: This is your starting point for connecting to debugging targets. You can load core files for post-mortem analysis, attach to running processes to diagnose live issues, or connect to remote debug servers if you’re dealing with distributed systems.</p>
</li>
<li><p><strong>Threads and Monitors</strong>: The GUI provides a real-time view of thread states, making it easier to identify deadlocks, thread contention, and monitor locks. This visual representation simplifies the process of pinpointing concurrency issues that could be affecting application performance.</p>
</li>
<li><p><strong>Heap Summary</strong>: For memory analysis, the GUI debugger gives a graphical overview of heap usage, including generations (for GC analysis), object counts, and memory footprints. This makes identifying memory leaks and optimizing garbage collection strategies more intuitive.</p>
</li>
<li><p><strong>Method and Stack Inspection</strong>: Delving into method executions and stack frames is seamless, allowing you to trace the execution path, inspect local variables, and evaluate the state of the application at different points in time.</p>
</li>
</ul>
<h2 id="heading-final-word">Final Word</h2>
<p><code>jhsdb</code> stands out as an essential tool in the debugging toolkit, especially for those dealing with JVM and native code issues. Its range of capabilities, from deep memory analysis to performance metrics, makes it a versatile choice for developers and system administrators alike.</p>
<p>The biggest benefit is in debugging the interaction between Java code and native code. Such code often fails in odd ways and on end user machines. In such situations a typical debugger might not be the best tool and might not expose the whole picture. This is especially true if you get a JVM core dump which is the main use case for <code>jhsdb</code>.</p>
]]></content:encoded></item><item><title><![CDATA[Debugging Streams with Peek]]></title><description><![CDATA[I blogged about Java Stream debugging in the past but I skipped an important method that's worthy of a post of its own: peek. This blog post delves into the practicalities of using peek() to debug Java streams, complete with code samples and common p...]]></description><link>https://debugagent.com/debugging-streams-with-peek</link><guid isPermaLink="true">https://debugagent.com/debugging-streams-with-peek</guid><category><![CDATA[Java]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Functional Programming]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 12 Mar 2024 10:21:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710068517434/ec220899-afb2-42bb-b972-e9fca775c74c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I blogged about <a target="_blank" href="https://debugagent.com/debugging-streams-and-collections">Java Stream debugging</a> in the past but I skipped an important method that's worthy of a post of its own: peek. This blog post delves into the practicalities of using <code>peek()</code> to debug Java streams, complete with code samples and common pitfalls.</p>
<h2 id="heading-understanding-java-streams">Understanding Java Streams</h2>
<p>Java Streams represent a significant shift in how Java developers work with collections and data processing, introducing a functional approach to handling sequences of elements. Streams facilitate declarative processing of collections, enabling operations such as filter, map, reduce, and more in a fluent style. This not only makes the code more readable but also more concise compared to traditional iterative approaches.</p>
<h3 id="heading-a-simple-stream-example">A Simple Stream Example</h3>
<p>To illustrate, consider the task of filtering a list of names to only include those that start with the letter "J" and then transforming each name into uppercase. Using the traditional approach, this might involve a loop and some if statements. However, with streams, this can be accomplished in a few lines:</p>
<pre><code class="lang-java">List&lt;String&gt; names = Arrays.asList(<span class="hljs-string">"John"</span>, <span class="hljs-string">"Jacob"</span>, <span class="hljs-string">"Edward"</span>, <span class="hljs-string">"Emily"</span>);
<span class="hljs-comment">// Convert list to stream</span>
List&lt;String&gt; filteredNames = names.stream()       
                  <span class="hljs-comment">// Filter names that start with "J"</span>
                  .filter(name -&gt; name.startsWith(<span class="hljs-string">"J"</span>))  
                  <span class="hljs-comment">// Convert each name to uppercase</span>
                  .map(String::toUpperCase)              
                  <span class="hljs-comment">// Collect results into a new list</span>
                  .collect(Collectors.toList());         
System.out.println(filteredNames);
</code></pre>
<p>Output:</p>
<pre><code class="lang-java">[JOHN, JACOB]
</code></pre>
<p>This example demonstrates the power of Java streams: by chaining operations together, we can achieve complex data transformations and filtering with minimal, readable code. It showcases the declarative nature of streams, where we describe what we want to achieve rather than detailing the steps to get there.</p>
<h2 id="heading-what-is-the-peek-method">What is the <code>peek()</code> Method?</h2>
<p>At its core, <code>peek()</code> is a method provided by the <code>Stream</code> interface, allowing developers a glance into the elements of a stream without disrupting the flow of its operations. The signature of <code>peek()</code> is as follows:</p>
<pre><code class="lang-java"><span class="hljs-function">Stream&lt;T&gt; <span class="hljs-title">peek</span><span class="hljs-params">(Consumer&lt;? <span class="hljs-keyword">super</span> T&gt; action)</span></span>
</code></pre>
<p>It accepts a <code>Consumer</code> functional interface, which means it performs an action on each element of the stream without altering them. The most common use case for <code>peek()</code> is logging the elements of a stream to understand the state of data at various points in the stream pipeline. To understand peek lets look at a sample similar to the previous one:</p>
<pre><code class="lang-java">List&lt;String&gt; collected = Stream.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"banana"</span>, <span class="hljs-string">"cherry"</span>)
                               .filter(s -&gt; s.startsWith(<span class="hljs-string">"a"</span>))
                               .collect(Collectors.toList());
System.out.println(collected);
</code></pre>
<p>This code filters a list of strings, keeping only the ones that start with "a". While it's straightforward, understanding what happens during the filter operation is not visible.</p>
<h3 id="heading-debugging-with-peek">Debugging with <code>peek()</code></h3>
<p>Now, let's incorporate <code>peek()</code> to gain visibility into the stream:</p>
<pre><code class="lang-java">List&lt;String&gt; collected = Stream.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"banana"</span>, <span class="hljs-string">"cherry"</span>)
                               .peek(System.out::println) <span class="hljs-comment">// Logs all elements</span>
                               .filter(s -&gt; s.startsWith(<span class="hljs-string">"a"</span>))
                               .peek(System.out::println) <span class="hljs-comment">// Logs filtered elements</span>
                               .collect(Collectors.toList());
System.out.println(collected);
</code></pre>
<p>By adding <code>peek()</code> both before and after the <code>filter</code> operation, we can see which elements are processed and how the filter impacts the stream. This visibility is invaluable for debugging, especially when the logic within the stream operations becomes complex.</p>
<p>We can't step over stream operations with the debugger, but <code>peek()</code> provides a glance into the code that is normally obscured from us.</p>
<h2 id="heading-uncovering-common-bugs-with-peek">Uncovering Common Bugs with <code>peek()</code></h2>
<h3 id="heading-filtering-issues">Filtering Issues</h3>
<p>Consider a scenario where a filter condition is not working as expected:</p>
<pre><code class="lang-java">List&lt;String&gt; collected = Stream.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"banana"</span>, <span class="hljs-string">"cherry"</span>, <span class="hljs-string">"Avocado"</span>)
                               .filter(s -&gt; s.startsWith(<span class="hljs-string">"a"</span>))
                               .collect(Collectors.toList());
System.out.println(collected);
</code></pre>
<p>Expected output might be <code>["apple"]</code>, but let's say we also wanted "Avocado" due to a misunderstanding of the <code>startsWith</code> method's behavior. Since "Avocado" is spelled with an upper case "A" this code will return false: <code>Avocado".startsWith("a")</code>. Using <code>peek()</code>, we can observe the elements that pass the filter:</p>
<pre><code class="lang-java">List&lt;String&gt; debugged = Stream.of(<span class="hljs-string">"apple"</span>, <span class="hljs-string">"banana"</span>, <span class="hljs-string">"cherry"</span>, <span class="hljs-string">"Avocado"</span>)
                              .peek(System.out::println)
                              .filter(s -&gt; s.startsWith(<span class="hljs-string">"a"</span>))
                              .peek(System.out::println)
                              .collect(Collectors.toList());
System.out.println(debugged);
</code></pre>
<h3 id="heading-large-data-sets">Large Data Sets</h3>
<p>In scenarios involving large datasets, directly printing every element in the stream to the console for debugging can quickly become impractical. It can clutter the console and make it hard to spot the relevant information. Instead, we can use <code>peek()</code> in a more sophisticated way to selectively collect and analyze data without causing side effects that could alter the behavior of the stream.</p>
<p>Consider a scenario where we're processing a large dataset of transactions, and we want to debug issues related to transactions exceeding a certain threshold:</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Transaction</span> </span>{
    <span class="hljs-keyword">private</span> String id;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">double</span> amount;

    <span class="hljs-comment">// Constructor, getters, and setters omitted for brevity</span>
}

List&lt;Transaction&gt; transactions = <span class="hljs-comment">// Imagine a large list of transactions</span>

<span class="hljs-comment">// A placeholder for debugging information</span>
List&lt;Transaction&gt; highValueTransactions = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();

List&lt;Transaction&gt; processedTransactions = transactions.stream()
    <span class="hljs-comment">// Filter transactions above a threshold</span>
    .filter(t -&gt; t.getAmount() &gt; <span class="hljs-number">5000</span>) 
    .peek(t -&gt; {
        <span class="hljs-keyword">if</span> (t.getAmount() &gt; <span class="hljs-number">10000</span>) {
            <span class="hljs-comment">// Collect only high-value transactions for debugging</span>
            highValueTransactions.add(t);
        }
     })
     .collect(Collectors.toList());

<span class="hljs-comment">// Now, we can analyze high-value transactions separately, without overloading the console</span>
System.out.println(<span class="hljs-string">"High-value transactions count: "</span> + 
       highValueTransactions.size());
</code></pre>
<p>In this approach, <code>peek()</code> is used to inspect elements within the stream conditionally. High-value transactions that meet a specific criterion (e.g., amount &gt; 10,000) are collected into a separate list for further analysis. This technique allows for targeted debugging without printing every element to the console, thereby avoiding performance degradation and clutter.</p>
<h4 id="heading-addressing-side-effects"><strong>Addressing Side Effects</strong></h4>
<p>While streams shouldn't have side effects. In fact, such side effects would break the stream debugger in IntelliJ which I discussed in the past. It's crucial to note that while collecting data for debugging within <code>peek()</code> avoids cluttering the console, it does introduce a side effect to the stream operation, which goes against the recommended use of streams. Streams are designed to be side-effect-free to ensure predictability and reliability, especially in parallel operations.</p>
<p>Therefore, while the above example demonstrates a practical use of <code>peek()</code> for debugging, it's important to use such techniques judiciously. Ideally, this debugging strategy should be temporary and removed once the debugging session is completed to maintain the integrity of the stream's functional paradigm.</p>
<h2 id="heading-limitations-and-pitfalls">Limitations and Pitfalls</h2>
<p>While <code>peek()</code> is undeniably a useful tool for debugging Java streams, it comes with its own set of limitations and pitfalls that developers should be aware of. Understanding these can help avoid common traps and ensure that <code>peek()</code> is used effectively and appropriately.</p>
<h3 id="heading-potential-for-misuse-in-production-code">Potential for Misuse in Production Code</h3>
<p>One of the primary risks associated with <code>peek()</code> is its potential for misuse in production code. Because <code>peek()</code> is intended for debugging purposes, using it to alter state or perform operations that affect the outcome of the stream can lead to unpredictable behavior. This is especially true in parallel stream operations, where the order of element processing is not guaranteed. Misusing <code>peek()</code> in such contexts can introduce hard-to-find bugs and undermine the declarative nature of stream processing.</p>
<h3 id="heading-performance-overhead">Performance Overhead</h3>
<p>Another consideration is the performance impact of using <code>peek()</code>. While it might seem innocuous, <code>peek()</code> can introduce a significant overhead, particularly in large or complex streams. This is because every action within <code>peek()</code> is executed for each element in the stream, potentially slowing down the entire pipeline. When used excessively or with complex operations, <code>peek()</code> can degrade performance, making it crucial to use this method judiciously and remove any <code>peek()</code> calls from production code after debugging is complete.</p>
<h3 id="heading-side-effects-and-functional-purity">Side Effects and Functional Purity</h3>
<p>As highlighted in the enhanced debugging example, <code>peek()</code> can be used to collect data for debugging purposes, but this introduces side effects to what should ideally be a side-effect-free operation. The functional programming paradigm, which streams are a part of, emphasizes purity and immutability. Operations should not alter state outside their scope. By using <code>peek()</code> to modify external state (even for debugging), you're temporarily stepping away from these principles. While this can be acceptable for short-term debugging, it's important to ensure that such uses of <code>peek()</code> do not find their way into production code, as they can compromise the predictability and reliability of your application.</p>
<h3 id="heading-the-right-tool-for-the-job">The Right Tool for the Job</h3>
<p>Finally, it's essential to recognize that <code>peek()</code> is not always the right tool for every debugging scenario. In some cases, other techniques such as logging within the operations themselves, using breakpoints and inspecting variables in an IDE, or writing unit tests to assert the behavior of stream operations might be more appropriate and effective. Developers should consider <code>peek()</code> as one tool in a broader debugging toolkit, employing it when it makes sense and opting for other strategies when they offer a clearer or more efficient path to identifying and resolving issues.</p>
<h3 id="heading-navigating-the-pitfalls">Navigating the Pitfalls</h3>
<p>To navigate these pitfalls effectively:</p>
<ul>
<li><p>Reserve <code>peek()</code> strictly for temporary debugging purposes. If you have a linter as part of your CI tools it might make sense to add a rule that block code from invoking <code>peek()</code>.</p>
</li>
<li><p>Always remove <code>peek()</code> calls from your code before committing it to your codebase, especially for production deployments.</p>
</li>
<li><p>Be mindful of performance implications and the potential introduction of side effects.</p>
</li>
<li><p>Consider alternative debugging techniques that might be more suited to your specific needs or the particular issue you're investigating.</p>
</li>
</ul>
<p>By understanding and respecting these limitations and pitfalls, developers can leverage <code>peek()</code> to enhance their debugging practices without falling into common traps or inadvertently introducing problems into their codebases.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>The <code>peek()</code> method offers a simple yet effective way to gain insights into Java stream operations, making it a valuable tool for debugging complex stream pipelines. By understanding how to use <code>peek()</code> effectively, developers can avoid common pitfalls and ensure their stream operations perform as intended. As with any powerful tool, the key is to use it wisely and in moderation.</p>
<p>The true value of <code>peek()</code> is in debugging massive data sets, these elements are very hard to analyze even with dedicated tools. By using <code>peek()</code> we can dig into said data set and understand the source of the issue programmatically.</p>
]]></content:encoded></item><item><title><![CDATA[Debugging Using JMX Revisited]]></title><description><![CDATA[Debugging effectively requires a nuanced approach, similar to using tongs that tightly grip the problem from both sides. While low-level tools have their place in system-level service debugging, today's focus shifts towards a more sophisticated segme...]]></description><link>https://debugagent.com/debugging-using-jmx-revisited</link><guid isPermaLink="true">https://debugagent.com/debugging-using-jmx-revisited</guid><category><![CDATA[observability]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Java]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 27 Feb 2024 08:45:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709009763667/d51369a1-c454-4b60-a3db-df8096b97d2d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Debugging effectively requires a nuanced approach, similar to using tongs that tightly grip the problem from both sides. While low-level tools have their place in system-level service debugging, today's focus shifts towards a more sophisticated segment of the development stack: advanced management tools. Understanding these tools is crucial for developers, as it bridges the gap between code creation and operational deployment, enhancing both efficiency and effectiveness in managing applications across extensive infrastructures.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/rQjHAMM3XfY">https://youtu.be/rQjHAMM3XfY</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-the-need-for-advanced-management-tools-in-development">The Need for Advanced Management Tools in Development</h2>
<p>Development and DevOps teams utilize an array of tools, often perceived as complex or alien by developers. These tools, designed for scalability, enable the management of thousands of servers simultaneously. Such capabilities, although not always necessary for smaller scales, offer significant advantages in application management. Advanced management tools facilitate the navigation and control over multiple machines, making them indispensable for developers seeking to optimize application performance and reliability.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708951384969/ed96c39a-0801-4aa2-8a2a-d6216fc68bff.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-introduction-to-jmx-java-management-extensions">Introduction to JMX (Java Management Extensions)</h3>
<p>One of the pivotal standards in application management is Java Management Extensions (JMX), which Java introduced to simplify the interaction with and management of applications. JMX allows both applications and the Java Development Kit (JDK) itself to expose critical information and functionalities, enabling external tools to manipulate these elements dynamically. Although activating JMX falls outside this discussion, its significance cannot be overstated, with ample resources available for those interested in its implementation.</p>
<p><strong>Setting up JMX</strong></p>
<p>JMX isn't enabled by default, to enable it we need the following steps:</p>
<ol>
<li><p><strong>Modify the JVM Startup Parameters</strong>: To enable JMX on a Java application, you must adjust the Java Virtual Machine (JVM) startup parameters. This involves adding specific flags to your application's startup command. The essential flags for enabling JMX are:</p>
<ul>
<li><p><code>-Dcom.sun.management.jmxremote</code>: This flag activates the JMX remote management and monitoring.</p>
</li>
<li><p><code>-Dcom.sun.management.jmxremote.port=&lt;PORT&gt;</code>: Replace <code>&lt;PORT&gt;</code> with a specific port number where the JMX remote connection will listen.</p>
</li>
<li><p><code>-Dcom.sun.management.jmxremote.ssl=false</code>: This flag disables SSL for JMX connections. For development environments, SSL might be disabled for simplicity, but for production environments, consider enabling SSL for security.</p>
</li>
<li><p><code>-Dcom.sun.management.jmxremote.authenticate=false</code>: This flag disables authentication. Similar to SSL, authentication may be disabled in development but should be enabled in production to ensure secure access.</p>
</li>
</ul>
</li>
<li><p><strong>Restart Your Application</strong>: With the JVM parameters set, restart your application. This will apply the new startup parameters, activating JMX.</p>
</li>
<li><p><strong>Verify JMX Connectivity</strong>: After restarting your application, you can verify that JMX is enabled by connecting to it using a JMX client such as JConsole, VisualVM, or a custom management application. Use the port number specified in the startup parameters to establish the connection.</p>
</li>
</ol>
<p><strong>JMX Security Considerations</strong></p>
<p>While enabling JMX provides powerful management capabilities, it's crucial to consider security implications, especially when JMX is exposed over a network. When deploying applications in production, always enable SSL and authentication to protect against unauthorized access. Additionally, consider firewall rules and network policies to restrict JMX access to trusted clients.</p>
<h4 id="heading-understanding-mbeans"><strong>Understanding MBeans</strong></h4>
<p>Central to JMX are Management Beans (MBeans), which serve as the control points within an application. These beans enable developers to publish specific functionalities for runtime monitoring and configuration. The ability to export application metrics to dashboards through MBeans is particularly valuable, facilitating real-time decision-making based on accurate, up-to-date information. Furthermore, operations such as user management can be exposed through MBeans, enhancing administrative capabilities.</p>
<h3 id="heading-spring-and-management-beans">Spring and Management Beans</h3>
<p>Spring Framework's Actuator module exemplifies the integration of management capabilities within development, offering extensive metrics and operational details. This integration propels applications to "production-ready" status, allowing developers to monitor and manage applications with unprecedented depth and efficiency.</p>
<h2 id="heading-tooling-for-jmx-management">Tooling for JMX Management</h2>
<p>While JMX can be accessed through various web interfaces and administrative tools, command-line tooling offers a direct, efficient method for interacting with JMX-enabled applications on production servers. Tools like JMXTerm complement visual tools by providing a streamlined interface for rapid insights, especially in environments unfamiliar to the developer.</p>
<h3 id="heading-getting-started-with-jmxterm">Getting Started with JMXTerm</h3>
<p>JMXTerm is a powerful utility for managing JMX without the need for graphical visualization, ideal for quick diagnostics or high-level server insights. After enabling JMX on the JVM and setting up the necessary configurations, developers can connect to servers, explore different JMX domains, and manipulate MBeans directly from the command line.</p>
<p>We can accomplish all of the following via visual tools and sometimes using a web interface. Normally, that's the approach I use. However, as a learning tool I think JMXTerm is fantastic since it exposes things in a way that's consistent and verbose. If we can understand JMXTerm the GUI version would be a walk in the park...</p>
<p>We can launch JMXTerm using the command line, in my case I used the following command:</p>
<pre><code class="lang-bash">java -jar ~/Downloads/jmxterm-1.0.2-uber.jar --url localhost:30002
</code></pre>
<p>Once the connection is made we can issue commands to JMX and retrieve information about the JVM or the application e.g. I can list the domains which you can think of as similar to "packages" or "modules" a way to organize the various beans:</p>
<pre><code class="lang-bash">$&gt;domains
<span class="hljs-comment">#following domains are available</span>
JMImplementation
com.sun.management
java.lang
java.nio
java.util.logging
javax.cache
jdk.management.jfr
</code></pre>
<p>I can select a specific domain and thus perform future operations within said domain:</p>
<pre><code class="lang-bash">$&gt;domain java.util.logging
<span class="hljs-comment">#domain is set to java.util.logging</span>
</code></pre>
<p>Once inside the domain I can select a specific bean and perform operations on it. For this I need to first list the beans in the domain, in this case there's only the logging bean. I can then select that bean using the <code>bean</code> command:</p>
<pre><code class="lang-bash">$&gt;beans
<span class="hljs-comment">#domain = java.util.logging:</span>
java.util.logging:<span class="hljs-built_in">type</span>=Logging
$&gt;bean java.util.logging:<span class="hljs-built_in">type</span>=Logging
<span class="hljs-comment">#bean is set to java.util.logging:type=Logging</span>
</code></pre>
<p>I can perform many operations on beans, perhaps the most useful is the <code>info</code> command which lets me query a bean. Notice that a bean can have attributes, think of them like object fields and operations which you can think of as methods. There are also notifications which you can think of as events:</p>
<pre><code class="lang-bash">$&gt;info
<span class="hljs-comment">#mbean = java.util.logging:type=Logging</span>
<span class="hljs-comment">#class name = sun.management.ManagementFactoryHelper$PlatformLoggingImpl</span>
<span class="hljs-comment"># attributes</span>
  %0   - LoggerNames ([Ljava.lang.String;, r)
  %1   - ObjectName (javax.management.ObjectName, r)
<span class="hljs-comment"># operations</span>
  %0   - java.lang.String getLoggerLevel(java.lang.String p0)
  %1   - java.lang.String getParentLoggerName(java.lang.String p0)
  %2   - void setLoggerLevel(java.lang.String p0,java.lang.String p1)
<span class="hljs-comment">#there's no notifications</span>
</code></pre>
<p>I can run operations and pass various commands e.g. I can get the logger level, set it and then check that the logger level was indeed updated:</p>
<pre><code class="lang-bash">$&gt;run getLoggerLevel <span class="hljs-string">"org.apache.tomcat.websocket.WsWebSocketContainer"</span>
<span class="hljs-comment">#calling operation getLoggerLevel of mbean java.util.logging:type=Logging with params [org.apache.tomcat.websocket.WsWebSocketContainer]</span>
<span class="hljs-comment">#operation returns:</span>
$&gt;run setLoggerLevel org.apache.tomcat.websocket.WsWebSocketContainer INFO
<span class="hljs-comment">#calling operation setLoggerLevel of mbean java.util.logging:type=Logging with params [org.apache.tomcat.websocket.WsWebSocketContainer, INFO]</span>
<span class="hljs-comment">#operation returns: </span>
null
$&gt;run getLoggerLevel <span class="hljs-string">"org.apache.tomcat.websocket.WsWebSocketContainer"</span>
<span class="hljs-comment">#calling operation getLoggerLevel of mbean java.util.logging:type=Logging with params [org.apache.tomcat.websocket.WsWebSocketContainer]</span>
<span class="hljs-comment">#operation returns: </span>
INFO
</code></pre>
<p>This is just the tip of the iceberg. We can get many things such as spring settings, internal VM information, etc. In this example I can query VM information directly from the console:</p>
<pre><code class="lang-bash">$&gt;domain com.sun.management
<span class="hljs-comment">#domain is set to com.sun.management</span>
$&gt;beans
<span class="hljs-comment">#domain = com.sun.management:</span>
com.sun.management:<span class="hljs-built_in">type</span>=DiagnosticCommand
com.sun.management:<span class="hljs-built_in">type</span>=HotSpotDiagnostic
$&gt;bean com.sun.management:<span class="hljs-built_in">type</span>=HotSpotDiagnostic
<span class="hljs-comment">#bean is set to com.sun.management:type=HotSpotDiagnostic</span>
$&gt;info
<span class="hljs-comment">#mbean = com.sun.management:type=HotSpotDiagnostic</span>
<span class="hljs-comment">#class name = com.sun.management.internal.HotSpotDiagnostic</span>
<span class="hljs-comment"># attributes</span>
  %0   - DiagnosticOptions ([Ljavax.management.openmbean.CompositeData;, r)
  %1   - ObjectName (javax.management.ObjectName, r)
<span class="hljs-comment"># operations</span>
  %0   - void dumpHeap(java.lang.String p0,boolean p1)
  %1   - javax.management.openmbean.CompositeData getVMOption(java.lang.String p0)
  %2   - void setVMOption(java.lang.String p0,java.lang.String p1)
<span class="hljs-comment">#there's no notifications</span>
</code></pre>
<h2 id="heading-leveraging-jmx-in-debugging-and-management">Leveraging JMX in Debugging and Management</h2>
<p>JMX stands out as a robust tool for wiring management consoles, allowing developers to expose critical settings and metrics for their projects. Beyond its conventional use, JMX can be leveraged as part of the debugging process, serving as a pseudo-interface for triggering debugging scenarios or observing debugging sessions within the management UI. This approach not only simplifies the management of server applications but also enhances the developer's ability to diagnose and resolve issues efficiently.</p>
<h2 id="heading-exposing-mbeans-in-spring-boot">Exposing MBeans in Spring Boot</h2>
<p>Up until now we discussed the process of working with beans that are a part of the JVM or Spring. But what about our own application logic?</p>
<p>We can expose our own applications internal state so we (and our SREs) can review these in production and staging. Instead of building a custom control panel or logging <strong>everything</strong>, we can just expose the data. If a flag is problematic we can change it in production, if you want to query a specific state it too can be exposed.</p>
<p>Spring Boot simplifies the management and monitoring of applications through its comprehensive support for JMX. By leveraging Spring's infrastructure, we can easily expose their application's beans as JMX Managed Beans (MBeans), making them accessible for monitoring and management via JMX clients.</p>
<h3 id="heading-understanding-spring-boot-jmx-support">Understanding Spring Boot JMX Support</h3>
<p>Spring Boot automatically configures JMX for you and exposes any beans annotated with <code>@ManagedResource</code> as JMX MBeans. This feature, combined with Spring Boot’s Actuator, provides a rich set of management endpoints, covering various aspects of the application, from metrics to thread dumps.</p>
<h3 id="heading-expose-an-mbean-in-spring-boot">Expose an MBean in Spring Boot</h3>
<p>To expose a bean we need to take the following steps:</p>
<ol>
<li><p><strong>Define a Management Interface</strong>: Create an interface that defines the operations and attributes you wish to expose via JMX. This interface should be annotated with JMX annotations such as <code>@ManagedOperation</code> for methods and <code>@ManagedAttribute</code> for fields or getter/setter methods.</p>
</li>
<li><p><strong>Implement the MBean</strong>: Implement the interface in a class that performs the actual logic for the operations and attributes defined. This class represents your MBean and can be a regular Spring-managed bean.</p>
</li>
<li><p><strong>Annotate the Bean with</strong><code>@ManagedResource</code>: Annotate your MBean implementation class with <code>@ManagedResource</code> to indicate that it should be exposed as an MBean. You can specify the object name for the MBean in this annotation, which is how it will be identified in JMX clients.</p>
</li>
<li><p><strong>Enable JMX in Spring Boot</strong>: Ensure that JMX is enabled in your Spring Boot application. This is usually the default behavior, but you can explicitly enable it by setting <code>spring.jmx.enabled=true</code> in your <code>application.properties</code> or <code>application.yml</code> file.</p>
</li>
<li><p><strong>Access the MBean via a JMX Client</strong>: Once your application is running, you can access the exposed MBean through any standard JMX client, such as JConsole, VisualVM, or a custom client. Connect to the Spring Boot application's JMX domain, and you'll find the MBean you exposed, ready for interaction.</p>
</li>
</ol>
<h3 id="heading-example-exposing-a-simple-configuration-mbean">Example: Exposing a Simple Configuration MBean</h3>
<pre><code class="lang-java"><span class="hljs-comment">// Define a management interface</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ConfigurationMBean</span> </span>{
    <span class="hljs-meta">@ManagedAttribute</span>
    <span class="hljs-function">String <span class="hljs-title">getApplicationName</span><span class="hljs-params">()</span></span>;

    <span class="hljs-meta">@ManagedOperation</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">updateApplicationName</span><span class="hljs-params">(String name)</span></span>;
}

<span class="hljs-comment">// Implement the MBean</span>
<span class="hljs-meta">@Component</span>
<span class="hljs-meta">@ManagedResource(objectName = "com.example:type=Configuration")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Configuration</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ConfigurationMBean</span> </span>{
    <span class="hljs-keyword">private</span> String applicationName = <span class="hljs-string">"MyApp"</span>;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getApplicationName</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> applicationName;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">updateApplicationName</span><span class="hljs-params">(String name)</span> </span>{
        <span class="hljs-keyword">this</span>.applicationName = name;
    }
}
</code></pre>
<p>In this example, the <code>Configuration</code> class is annotated with <code>@ManagedResource</code>, making it available as an MBean with operations and attributes accessible via JMX clients.</p>
<p>Exposing MBeans in Spring Boot is a powerful feature that enhances the management and monitoring capabilities of applications. By following the steps outlined above, developers can provide external tools with dynamic access to application internals, offering a window into the runtime behavior and allowing for adjustments on the fly. This not only aids in debugging and performance tuning but also aligns with best practices for building manageable, robust applications.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>Advanced management tools, particularly JMX and its integration with frameworks like Spring, offer developers powerful capabilities for application monitoring, configuration, and debugging. By understanding and utilizing these tools, developers can achieve a deeper level of control over their applications, enhancing both performance and reliability. Whether through graphical interfaces or command-line utilities like JMXTerm, the dynamic manipulation and monitoring of applications in runtime environments open new avenues for effective software development and management. As the bridge between development and operations continues to narrow, mastering these advanced tools becomes essential for any developer looking to excel in today's fast-paced technological landscape.</p>
]]></content:encoded></item><item><title><![CDATA[Unleashing the Power of Git Bisect]]></title><description><![CDATA[We don't usually think of Git as a debugging tool. Surprisingly, Git shines not just as a version control system but also as a potent debugging ally when dealing with the tricky matter of regressions.
https://youtu.be/yZuPHEBbjYI
 
As a side note, if...]]></description><link>https://debugagent.com/unleashing-the-power-of-git-bisect</link><guid isPermaLink="true">https://debugagent.com/unleashing-the-power-of-git-bisect</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Git]]></category><category><![CDATA[debugging]]></category><category><![CDATA[git-bisect]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 13 Feb 2024 12:31:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707815973881/4794a0a8-9cfa-4340-b7d0-72f420e37d16.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We don't usually think of Git as a debugging tool. Surprisingly, Git shines not just as a version control system but also as a potent debugging ally when dealing with the tricky matter of regressions.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/yZuPHEBbjYI">https://youtu.be/yZuPHEBbjYI</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-the-essence-of-debugging-with-git">The Essence of Debugging with Git</h2>
<p>Before we tap into the advanced aspects of <code>git bisect</code>, it's essential to understand its foundational premise. Git is known for tracking changes and managing code history, but the <code>git bisect</code> tool is a hidden gem for regression detection. Regressions are distinct from generic bugs, they signify a backward step in functionality—where something that once worked flawlessly now fails. Pinpointing the exact change causing a regression can be akin to finding a needle in a haystack, particularly in extensive codebases with long commit histories.</p>
<p>Traditionally, developers would employ a manual, binary search strategy—checking out different versions, testing them, and narrowing down the search scope. This method, while effective, is painstakingly slow and error-prone. <code>Git bisect</code> automates this search, transforming what used to be a marathon into a swift sprint.</p>
<h2 id="heading-setting-the-stage-for-debugging">Setting the Stage for Debugging</h2>
<p>Imagine you're working on a project, and recent reports indicate a newly introduced bug affecting the functionality of a feature that previously worked flawlessly. You suspect a regression but are unsure which commit introduced the issue among the hundreds made since the last stable version.</p>
<h3 id="heading-initiating-bisect-mode">Initiating Bisect Mode</h3>
<p>To start, you'll enter bisect mode in your terminal within the project's Git repository:</p>
<pre><code class="lang-bash">git bisect start
</code></pre>
<p>This command signals Git to prepare for the bisect process.</p>
<h3 id="heading-marking-the-known-good-revision">Marking the Known Good Revision</h3>
<p>Next, you identify a commit where the feature functioned correctly, often a commit tagged with a release number or dated before the issue was reported. Mark this commit as "good":</p>
<pre><code class="lang-bash">git bisect good a1b2c3d
</code></pre>
<p>Here, <code>a1b2c3d</code> represents the hash of the known good commit.</p>
<h3 id="heading-marking-the-known-bad-revision">Marking the Known Bad Revision</h3>
<p>Similarly, you mark the current version or a specific commit where the bug is present as "bad":</p>
<pre><code class="lang-bash">git bisect bad z9y8x7w
</code></pre>
<p><code>z9y8x7w</code> is the hash of the bad commit, typically the latest commit in the repository where the issue is observed.</p>
<h3 id="heading-bisecting-to-find-the-culprit">Bisecting to Find the Culprit</h3>
<p>Upon marking the good and bad commits, Git automatically jumps to a commit roughly in the middle of the two and waits for you to test this revision. After testing (manually or with a script), you inform Git of the result:</p>
<ul>
<li><p>If the issue is present: <code>git bisect bad</code></p>
</li>
<li><p>If the issue is not present: <code>git bisect good</code></p>
</li>
</ul>
<p>Git then continues to narrow down the range, selecting a new commit to test based on your feedback.</p>
<h3 id="heading-expected-output">Expected Output</h3>
<p>After several iterations, Git will isolate the problematic commit, displaying a message similar to:</p>
<pre><code class="lang-plaintext">Bisecting: 0 revisions left to test after this (roughly 3 steps)
[abcdef1234567890] Commit message of the problematic commit
</code></pre>
<h3 id="heading-reset-and-analysis">Reset and Analysis</h3>
<p>Once the offending commit is identified, you conclude the bisect session to return your repository to its initial state:</p>
<pre><code class="lang-bash">git bisect reset
</code></pre>
<p>Notice that bisect isn't linear. Bisect doesn't scan through the revisions in a sequential manner. Based on the good and bad markers, Git automatically selects a commit approximately in the middle of the range for testing (e.g., commit #6 in the following diagram). This is where the non-linear, binary search pattern starts, as Git divides the search space in half instead of examining each commit sequentially. This means fewer revisions get scanned and the process is faster.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707815719831/959868c2-9c3b-4fd8-87e6-fb838d259e08.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-advanced-usage-and-tips">Advanced Usage and Tips</h2>
<p>The magic of <code>git bisect</code> lies in its ability to automate the binary search algorithm within your repository, systematically halving the search space until the rogue commit is identified.</p>
<p><code>Git bisect</code> offers a powerful avenue for debugging, especially for identifying regressions in a complex codebase. To elevate your use of this tool, consider delving into more advanced techniques and strategies. These tips not only enhance your debugging efficiency but also provide practical solutions to common challenges encountered during the bisecting process.</p>
<h3 id="heading-script-automation-for-precision-and-efficiency">Script Automation for Precision and Efficiency</h3>
<p>Automating the bisect process with a script is a game-changer, significantly reducing manual effort and minimizing the risk of human error. This script should ideally perform a quick test that directly targets the regression, returning an exit code based on the test's outcome.</p>
<p><strong>Example</strong>: Imagine you're debugging a regression where a web application's login feature breaks. You could write a script that attempts to log in using a test account and checks if the login succeeds. The script might look something like this in a simplified form:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># Attempt to log in and check for success</span>
<span class="hljs-keyword">if</span> curl -s http://yourapplication/login -d <span class="hljs-string">"username=test&amp;password=test"</span> | grep -q <span class="hljs-string">"Welcome"</span>; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">exit</span> 0 <span class="hljs-comment"># Login succeeded, mark this commit as good</span>
<span class="hljs-keyword">else</span>
  <span class="hljs-built_in">exit</span> 1 <span class="hljs-comment"># Login failed, mark this commit as bad</span>
<span class="hljs-keyword">fi</span>
</code></pre>
<p>By passing this script to <code>git bisect run</code>, Git automatically executes it at each step of the bisect process, effectively automating the regression hunt.</p>
<h3 id="heading-handling-flaky-tests-with-strategy">Handling Flaky Tests with Strategy</h3>
<p>Flaky tests, which sometimes pass and sometimes fail under the same conditions, can complicate the bisecting process. To mitigate this, your automation script can include logic to rerun tests a certain number of times or to apply more sophisticated checks to differentiate between a true regression and a flaky failure.</p>
<p><strong>Example</strong>: Suppose you have a test that's known to be flaky. You could adjust your script to run the test multiple times, considering the commit "bad" only if the test fails consistently:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># Run the flaky test three times</span>
success_count=0
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> {1..3}; <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">if</span> ./run_flaky_test.sh; <span class="hljs-keyword">then</span>
    ((success_count++))
  <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">done</span>

<span class="hljs-comment"># If the test succeeds twice or more, consider it a pass</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$success_count</span>"</span> -ge 2 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">exit</span> 0
<span class="hljs-keyword">else</span>
  <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>
</code></pre>
<p>This approach reduces the chances that a flaky test will lead to incorrect bisect results.</p>
<h3 id="heading-skipping-commits-with-care">Skipping Commits with Care</h3>
<p>Sometimes, you'll encounter commits that cannot be tested due to reasons like broken builds or incomplete features. <code>git bisect skip</code> is invaluable here, allowing you to bypass these commits. However, use this command judiciously to ensure it doesn't obscure the true source of the regression.</p>
<p><strong>Example</strong>: If you know that commits related to database migrations temporarily break the application, you can skip testing those commits. During the bisect session, when Git lands on a commit you wish to skip, you would manually issue:</p>
<pre><code class="lang-bash">git bisect skip
</code></pre>
<p>This tells Git to exclude the current commit from the search and adjust its calculations accordingly. It's essential to only skip commits when absolutely necessary, as skipping too many can interfere with the accuracy of the bisect process.</p>
<p>These advanced strategies enhance the utility of <code>git bisect</code> in your debugging toolkit. By automating the regression testing process, handling flaky tests intelligently, and knowing when to skip untestable commits, you can make the most out of <code>git bisect</code> for efficient and accurate debugging. Remember, the goal is not just to find the commit where the regression was introduced but to do so in the most time-efficient manner possible. With these tips and examples, you're well-equipped to tackle even the most elusive regressions in your projects.</p>
<h2 id="heading-unraveling-a-regression-mystery">Unraveling a Regression Mystery</h2>
<p>In the past we got to use <code>git bisect</code>, when working on a large-scale web application. After a routine update, users began reporting a critical feature failure: the application's payment gateway stopped processing transactions correctly, leading to a significant business impact.</p>
<p>We knew the feature worked in the last release but had no idea which of the hundreds of recent commits introduced the bug. Manually testing each commit was out of the question due to time constraints and the complexity of the setup required for each test.</p>
<p>Enter <code>git bisect</code>. The team started by identifying a "good" commit where the payment gateway functioned correctly and a "bad" commit where the issue was observed. We then crafted a simple test script that would simulate a transaction and check if it succeeded.</p>
<p>By running <code>git bisect start</code>, followed by marking the known good and bad commits, and executing the script with <code>git bisect run</code>, we set off on an automated process that identified the faulty commit. Git efficiently navigated through the commits, automatically running the test script on each step. In a matter of minutes, <code>git bisect</code> pinpointed the culprit: a seemingly innocuous change to the transaction logging mechanism that inadvertently broke the payment processing logic.</p>
<p>Armed with this knowledge we reverted the problematic change, restoring the payment gateway's functionality and averting further business disruption. This experience not only resolved the immediate issue but also transformed our approach to debugging, making <code>git bisect</code> a go-to tool in our arsenal.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>The story of the payment gateway regression is just one example of how <code>git bisect</code> can be a lifesaver in the complex world of software development. By automating the tedious process of regression hunting, <code>git bisect</code> not only saves precious time but also brings a high degree of precision to the debugging process.</p>
<p>As developers continue to navigate the challenges of maintaining and improving complex codebases, tools like <code>git bisect</code> underscore the importance of leveraging technology to work smarter, not harder. Whether you're dealing with a mysterious regression or simply want to refine your debugging strategies, <code>git bisect</code> offers a powerful, yet underappreciated, solution to swiftly and accurately identify the source of regressions. Remember, the next time you're faced with a regression, <code>git bisect</code> might just be the debugging partner you need to uncover the truth hidden within your commit history.</p>
]]></content:encoded></item><item><title><![CDATA[The Best Way to Diagnose a Patient is to Cut Him Open]]></title><description><![CDATA["The most effective debugging tool is still careful thought, coupled with judiciously placed print statements."  -- Brian Kernighan.

Cutting a patient open and using print for debugging used to be the best ways to diagnose problems. If you still adv...]]></description><link>https://debugagent.com/the-best-way-to-diagnose-a-patient-is-to-cut-him-open</link><guid isPermaLink="true">https://debugagent.com/the-best-way-to-diagnose-a-patient-is-to-cut-him-open</guid><category><![CDATA[debugging]]></category><category><![CDATA[Java]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 06 Feb 2024 14:19:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707217979664/a30983ed-14fe-4eac-8420-ca6bd048af16.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>"The most effective debugging tool is still careful thought, coupled with judiciously placed print statements."  -- Brian Kernighan.</p>
</blockquote>
<p>Cutting a patient open and using print for debugging used to be the best ways to diagnose problems. If you still advocate either one of those as the superior approach to troubleshooting then you're either facing a very niche problem or need to update your knowledge. This is a frequent occurrence, e.g this recent tweet:</p>
<p><img src="https://lh7-us.googleusercontent.com/wXVTk9QYwo40Y1vjx63lFyDiKbrCZikj1yt-1dgyeyMTvNlVE8v3CgMUmXQgrdprH-YCxw1Z2xParVh55Oo3whYbH0MlFGRMVe9FpAGPwYsdNiKpBMymQldCu2YBDP-JEAH0nK1LpbaKvF6wFbQbvio" alt /></p>
<p>This specific tweet got to the HN front page and people chimed in with that usual repetitive nonsense. No, it’s not the best way for the vast majority of developers. It should be discouraged just as surgery should be avoided when possible.</p>
<p>Fixating on print debugging is a form of a mental block, debugging isn’t just stepping over code. It requires a completely new way of thinking about issue resolution. A way that is far superior to merely printing a few lines.</p>
<p>Before I continue, my bias is obvious. I <a target="_blank" href="https://www.amazon.com/Practical-Debugging-Scale-Kubernetes-Production/dp/1484290410/">wrote a book about debugging</a> and I <a target="_blank" href="https://debugagent.com/series/debugging-course">blog about it a lot</a>. This is a pet peeve of mine.</p>
<p>I want to start with the exception to the rule though, when do we need to print something...</p>
<h2 id="heading-logging-is-not-print-debugging">Logging is NOT Print Debugging!</h2>
<p>One of the most important debugging tools in our arsenal is a logger, but it is not the same as print debugging in any way:</p>
<table><tbody><tr><td><p></p></td><td><p><strong>Logger</strong></p></td><td><p><strong>Print</strong></p></td></tr><tr><td><p>Permanence of output</p></td><td><p>Permanent</p></td><td><p>Ephemeral</p></td></tr><tr><td><p>Permanence in code</p></td><td><p>Permanent</p></td><td><p>Should be removed</p></td></tr><tr><td><p>Globally Toggleable</p></td><td><p>Yes</p></td><td><p>No</p></td></tr><tr><td><p>Intention</p></td><td><p>Added as part of design</p></td><td><p>Added ad-hoc</p></td></tr></tbody></table>

<p>A log is something we add with forethought, we want to keep the log for future bugs and might even want to expose it to the users. We can control its verbosity often at the module level and can usually disable it entirely. It’s permanent in code and usually writes to a permanent file we can review at our leisure. </p>
<p>Print debugging is code we add to locate a temporary problem. If such a problem has the potential of recurring then a log would typically make more sense in the long run. This is true for almost every type of system, we see developers adding print statements and removing them constantly instead of creating a simple log to track frequent problems.</p>
<p>There are special cases where print debuggings makes some sense, in mission critical embedded systems a log might be impractical in terms of device constraints. Debuggers are awful in those environments and print debugging is a simple hack. Debugging system level tools like a kernel, compiler, debugger,or JIT can be difficult with a debugger. Logging might not make sense in all of these cases e.g. I don’t want my JIT to print every bytecode it’s processing and the metadata involved. </p>
<p>Those are the exceptions, not the rules. Very few of us write such tools. I do, and even then it’s a fraction of my work. E.g. When working at Lightrun I was working on a production debugger. Debugging the agent code that’s connected to the executable was one of the hardest things to do. A mix of C++ and JVM code that’s connected to a completely separate binary... Print debugging of that portion was simpler, and even then we tried to aim towards logging. But the visual aspects of the debugger within the server backend and the IDE were perfect targets for the debugger.</p>
<h2 id="heading-why-debug">Why Debug?</h2>
<p>There are three reasons to use a debugger instead of printouts or even logs:</p>
<ul>
<li><p>Features - modern debuggers can provide spectacular capabilities that are unfamiliar to many developers. Sadly, there are very few debugging courses in academia since it’s a subject that’s hard to test.</p>
</li>
<li><p>Low overhead - in the past running with the debugger meant slow execution and a lot of overhead. This is no longer true. Many of us use the debug action when launching an application instead of running and there’s no noticeable overhead for most applications. When there is overhead, some debuggers provide means to improve performance by disabling some features.</p>
</li>
<li><p>Library code - a debugger can step into a library or framework and track the bug there. Doing this with print debugging will require compiling code that you might not want to deal with. </p>
</li>
</ul>
<p>I dug into the features I mentioned in my book and series on debugging (linked above) but let’s pick a few fantastic capabilities of the debugger that I wrote about in the past.</p>
<p>For the sake of positive dialog, here are some of my top features of modern debuggers.</p>
<h3 id="heading-tracepoints">Tracepoints</h3>
<p>Whenever someone opens the print debugging discussion all I hear is “<a target="_blank" href="https://debugagent.com/the-massive-hidden-power-of-breakpoints">I don’t know about tracepoints</a>”. They aren’t a new feature in debuggers, yet so few are aware of them. A tracepoint is a breakpoint that doesn’t stop, it just keeps running. Instead of stopping you can do other things at that point, such as print to the console. This is similar to print debugging only it doesn’t suffer from many of the drawbacks: no runtime overhead, no accidental commit to the code base, no need to restart the application when changing it etc.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=eXRqKqSp7x0">https://www.youtube.com/watch?v=eXRqKqSp7x0</a></div>
<p> </p>
<h3 id="heading-grouping-and-naming">Grouping and Naming</h3>
<p>The previous video/post included a discussion of grouping and naming. This lets us group tracepoints together, disable them as a group etc. This might seem like a minor feature until you start thinking about the process of print debugging. We slowly go through the code adding a print and restarting. Then suddenly we need to go back, or if a call comes in and we need to debug something else...</p>
<p>When we package the tracepoints and breakpoints into a group we can set aside a debugging session much like we set aside a branch in version control. It makes it much easier to preserve our train of thought and jump right back to the applicable lines of code.</p>
<h3 id="heading-object-marking">Object Marking</h3>
<p>When asked about my favorite debugging feature I’m always conflicted, <a target="_blank" href="https://debugagent.com/watch-and-evaluate">Object Marking is one of my top two features</a>... It seems like a simple thing, we can mark an object and it gets saved with a specific name.</p>
<p>However, this is a powerful and important feature. I used to write down the pointers to objects or memory areas while debugging. This is valuable as sometimes an area of memory would look the same but would have a different address or it might be hard to track objects with everything going on. Object Marking allows us to save a global reference to an object, use it in conditional breakpoints or for visual comparison. </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=DGjVVKCNosM">https://www.youtube.com/watch?v=DGjVVKCNosM</a></div>
<p> </p>
<h3 id="heading-renderers">Renderers</h3>
<p>My other favorite feature is <a target="_blank" href="https://debugagent.com/watch-area-and-renderers">the renderer</a>, it lets us define how elements look in the debugger watch area. Imagine you have a sophisticated object hierarchy but rarely need that information... A renderer lets you customize the way IntelliJ/IDEA presents the object to you.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=oaUf8KXHsd0&amp;t">https://www.youtube.com/watch?v=oaUf8KXHsd0&amp;t</a></div>
<p> </p>
<h3 id="heading-tracking-new-instances">Tracking New Instances</h3>
<p>One of the often overlooked capabilities of the debugger is <a target="_blank" href="https://debugagent.com/memory-debugging-a-deep-level-of-insight">memory tracking</a>. A Java debugger can show you a searchable set of all object instances in heap, that is a fantastic capability that can expose unintuitive behavior But it can go further, it can track new allocations of an object and provide you with the stack to the applicable object allocation.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=dFOFOEg2W4k">https://www.youtube.com/watch?v=dFOFOEg2W4k</a></div>
<p> </p>
<h2 id="heading-tip-of-the-iceberg">Tip of the Iceberg</h2>
<p>I wrote a lot about debugging, there’s no point in repeating all of that in this post. If you’re a person who feels more comfortable using print debugging then ask yourself this: why?</p>
<p>Don’t hide behind an out of date Brian Kernighan quote. Things change. Are you working in one of the edge cases where print debugging is the only option? </p>
<p>Are you treating logging as print debugging or vice versa?</p>
<p>Or is it just that print debugging was how your team always worked and it stuck in place. If it’s one of those then it might be time to re-evaluate the current state of debuggers.</p>
]]></content:encoded></item><item><title><![CDATA[Regenerate Immediately and RSS]]></title><description><![CDATA[Note: this post was originally published on the gdocweb blog.
gdocweb has its first few users and one of the big complaints is about the tediousness of going through that wizard every time you just want to test a change to a document. I don’t want gd...]]></description><link>https://debugagent.com/regenerate-immediately-and-rss</link><guid isPermaLink="true">https://debugagent.com/regenerate-immediately-and-rss</guid><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[GitHubPages]]></category><category><![CDATA[Google]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Sun, 04 Feb 2024 11:48:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707046720542/9d15c5cf-cc4c-4d08-ae0d-26febd87a15a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Note: this post was originally published on the <a target="_blank" href="https://blog.gdocweb.com/regenerate-immediately-and-rss.html">gdocweb blog</a>.</p>
<p><a target="_blank" href="https://gdocweb.com/">gdocweb</a> has its first few users and one of the big complaints is about the tediousness of going through that wizard every time you just want to test a change to a document. I don’t want gdocweb working in the background reading my documents and publishing, that would be an invasion of privacy. But I would like it to work instantly.</p>
<p>With that we now have a link to regenerate the site quickly. Not with a single click, you still need to go through a Google login for security. Once that’s done you will reach the final stage of the wizard directly and the website will be updated. You can do that by visiting <a target="_blank" href="https://gdocweb.com/regenerateSite">https://gdocweb.com/regenerateSite</a>.</p>
<h2 id="heading-rss-really-simple-syndication-and-sitemap">RSS (Really Simple Syndication) and Sitemap</h2>
<p>Many of us go through life without knowing the RSS exists, it’s a workhorse that powers a great deal of functionality on the internet. Yet, we remain oblivious to it. RSS lets a website broadcast about the changes it went through, e.g. this blog now features an RSS feed that can notify you about every new post to the site.</p>
<p>Typically one would read an RSS feed using a dedicated application (e.g. Feedly), but browsers also have some basic support for RSS. The biggest benefit of RSS is in syndication, it means that other sites can publish a “feed” from this site detailing the latest bit of news. It’s great for search engine optimization and a wonderful feature for users of your website. You can see the feed.xml RSS file <a target="_blank" href="https://blog.gdocweb.com/feed.xml">here</a>.</p>
<p>Sitemaps are even more important. They let search engines know about the pages you have in the website and the dates in which they were last updated. This helps search engines keep track of everything and makes your site easier to find. The sitemap for this site can be found <a target="_blank" href="https://blog.gdocweb.com/sitemap.xml">here</a>. Normally you wouldn’t care about it, but search engines care about it...</p>
<p>Both files require a full URL to the generated pages in order to function. Unfortunately, this isn’t trivial. The gdocweb blog can be reached both on: <a target="_blank" href="https://shai-almog.github.io/GdocwebBlog/">https://shai-almog.github.io/GdocwebBlog/</a> and on <a target="_blank" href="https://blog.gdocweb.com/">https://blog.gdocweb.com/</a>. The latter is the correct link and the former correctly redirects to it, however if we have links to the former this will reduce the search engine ranking. We need to link to the correct blog URL but gdocweb can’t guess it from the project name.</p>
<p>That’s why all of this good stuff will only work if you set the value of the “Base URL” entry in the GitHub repository selection stage of the wizard. Once that is set as shown in the following image, this will all work as expected.</p>
<p><img src="https://blog.gdocweb.com/img/b3d6577f6c0de04d3d431ee705757c5e2fe04930.jpg" alt /></p>
<p>As a bonus, setting this value will also set the canonical URL for each page. This is an important attribute of an HTML file that helps search engines find your website.</p>
<h2 id="heading-target-directory-and-automatic-merge">Target Directory and Automatic Merge</h2>
<p>In the previous image we could see two additional features that are also quite important but mostly geared towards the technical crowd. The first is the target directory. By default, gdocweb generates everything into the GitHub Projects root directory. This is great for a site, however if you’re building documentation for a pre-existing project then generating the project to a docs directory might be a better approach. The “Target Directory” option is a great tool for developers building documentation and websites for their projects.</p>
<p>gdocweb generates a pull request for the project and merges that pull request for you automatically. This default behavior might not be right for all projects and also includes a risk. You might want an additional review for changes. In that case you can uncheck the “Merge Automatically” flag and disable the default behavior. This means a run of the gdocweb wizard will result with a new pull request for you to merge manually. For me that has been a valuable debugging tool as I could experiment with changes to the blog without merging them in.</p>
<p>You can see the document that generated this post <a target="_blank" href="https://docs.google.com/document/d/17ek6RcufHhNb9S8NMGMNPQQW0vMmDNPwtvJKDvHQoRE/edit?usp=sharing">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[strace Revisited: Simple is Beautiful]]></title><description><![CDATA[In the realm of system debugging, particularly on Linux platforms, strace stands out as a powerful and indispensable tool. Its simplicity and efficacy make it the go-to solution for diagnosing and understanding system-level operations, especially whe...]]></description><link>https://debugagent.com/strace-revisited-simple-is-beautiful</link><guid isPermaLink="true">https://debugagent.com/strace-revisited-simple-is-beautiful</guid><category><![CDATA[debugging]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Java]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[video]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 30 Jan 2024 12:46:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706596770089/10e9e903-ee95-4a15-be68-73229223c09d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the realm of system debugging, particularly on Linux platforms, strace stands out as a powerful and indispensable tool. Its simplicity and efficacy make it the go-to solution for diagnosing and understanding system-level operations, especially when working with servers and containers. In this blog post, we'll delve into the nuances of strace, from its history and technical functioning to practical applications and advanced features. Whether you're a seasoned developer or just starting out, this exploration will enhance your diagnostic toolkit and provide deeper insights into the workings of Linux systems.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/bgi7PJXtEzc">https://youtu.be/bgi7PJXtEzc</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/">Debugging book</a> that covers <strong>t</strong>his subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/">Java Basics book.</a> If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">Java 8 to 21 book</a><strong>.</strong></p>
<h2 id="heading-understandinghttpswwwamazoncomjava-21-explore-cutting-edge-featuresdp9355513925-strace-and-its-origins">Understandin<a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/">g</a> strace and its Origins</h2>
<h3 id="heading-a-look-back-strace-and-dtrace"><strong>A Look Back: strace and dtrace</strong></h3>
<p>We discussed dtrace <a target="_blank" href="https://www.amazon.com/dp/1484290410/">the last</a><a target="_blank" href="https://debugagent.com/dtrace-revisited-advanced-debugging-techniques">time</a> around. However, dtrace's availability is limited, particularly on Linux systems where most server and container debugging takes place. This is where strace comes into the picture, offering a simpler yet effective alternative.</p>
<h3 id="heading-originating-from-sun-microsystems"><strong>Originating from Sun Microsystems</strong></h3>
<p>strace, like dtrace, traces its roots back to Sun Microsystems, emerging in the 90s (a decade before dtrace). This isn't surprising given the impressive array of technologies that originated from Sun. However, strace differentiates itself by its straightforwardness in both usage and capabilities. Unlike DTrace, which demands deep operating system support and thus remained absent as an official feature in common Linux distributions, strace thrives in the Linux environment. Its simplicity and ease of implementation make it a popular choice for Linux users, offering a distinct approach to system diagnostics.</p>
<h2 id="heading-technical-functioning-of-strace">Technical Functioning of strace</h2>
<h3 id="heading-the-role-of-ptrace-in-strace"><strong>The Role of ptrace in strace</strong></h3>
<p>The cornerstone of strace's functionality is the ptrace kernel feature. ptrace, pre-existing in Linux, spares users from the need to add additional kernel code or modules, a requirement often associated with DTrace. This fundamental difference not only simplifies the use of strace but also broadens its accessibility.</p>
<h3 id="heading-comparing-with-dtrace"><strong>Comparing with DTrace</strong></h3>
<p>While DTrace offers a more in-depth analysis through deeper kernel support, strace operates on a more surface level. This simplicity, however, does not undermine its effectiveness. strace works essentially by logging every kernel call made by a process, providing verbose but incredibly detailed insights into the system's operation. This method allows users to trace the inner workings of a process, understanding each interaction with the kernel.</p>
<h2 id="heading-practical-usage-and-advantages">Practical Usage and Advantages</h2>
<h3 id="heading-ease-of-use-and-accessibility"><strong>Ease of Use and Accessibility</strong></h3>
<p>One of the most appealing aspects of strace is its user-friendly nature. It doesn't require special privileges or complex setup procedures. This ease of use is particularly beneficial for developers and system administrators who need to quickly diagnose and address issues in a Linux environment. Unlike DTrace, strace is readily available and doesn’t demand advanced configurations or permissions.</p>
<h3 id="heading-favored-in-linux-environments"><strong>Favored in Linux Environments</strong></h3>
<p>strace's popularity in Linux circles is not only due to its accessibility but also its practicality. Being able to run without special privileges makes it a go-to tool for diagnosing various system-related issues. However, it's important to note that strace should be used cautiously in production environments. Its extensive logging can create a significant performance overhead, potentially impacting the efficiency of a live system. This is why strace is generally recommended for use in development or isolated testing environments rather than in production.</p>
<h2 id="heading-strace-in-action-a-closer-look-at-system-calls">strace in Action: A Closer Look at System Calls</h2>
<h3 id="heading-basic-usage-and-output-analysis"><strong>Basic Usage and Output Analysis</strong></h3>
<p>Using strace is straightforward: you simply pass the command line to it.</p>
<pre><code class="lang-bash">strace java -classpath . PrimeMain
</code></pre>
<p>This simplicity belies its power, as the output offers a wealth of information. Each line in the strace output corresponds to a system call made by the process as you can see below:</p>
<pre><code class="lang-plaintext">execve("/home/ec2-user/jdk1.8.0_45/bin/java", ["java", "-classpath.", "PrimeMain"], 0x7fffd689ec20 /* 23 vars */) = 0
brk(NULL)                               = 0xb85000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0294272000
readlink("/proc/self/exe", "/home/ec2-user/jdk1.8.0_45/bin/j"..., 4096) = 35
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/x86_64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/x86_64", 0x7fff37af09a0) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls", 0x7fff37af09a0) = -1 ENOENT (No such file or directory)
</code></pre>
<p>By analyzing these calls, users can gain insights into the intricate operations of their applications. For instance, if a Java process attempts to load a library and fails, strace can reveal the underlying system call and its exit code, providing clues about potential issues like missing files or directories. E.g. in this line:</p>
<pre><code class="lang-plaintext">open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/x86_64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
</code></pre>
<p>Java tries to load the <code>pthread</code> library from the <code>tls</code> directory using a system call open to load the file. The exit code of the system call is <code>-1,</code> which means that the file isn't there. Under normal circumstances, we should get back a file descriptor value from this API (positive non-zero integer). Looking in the directory, it seems the <code>tls</code> directory is missing. I'm guessing that this is because of a missing <code>JCE</code> (Java Cryptography Extensions) installation. This is probably OK but might have been interesting in some cases.</p>
<h3 id="heading-interpreting-system-calls-for-debugging"><strong>Interpreting System Calls for Debugging</strong></h3>
<p>The output of strace, while verbose, is a goldmine for troubleshooting. For example, a negative exit code in a system call indicates an error, such as a missing file, which could be crucial for diagnosing issues in an application. This level of detail, although overwhelming at times, is invaluable for understanding the interactions between your application and the Linux system.</p>
<h2 id="heading-advanced-features-and-tips">Advanced Features and Tips</h2>
<h3 id="heading-filtering-system-calls-for-efficiency"><strong>Filtering System Calls for Efficiency</strong></h3>
<p>A common challenge with strace is managing its voluminous output. Fortunately, strace offers options to filter system calls, significantly enhancing its usability. By using the <code>-e</code> argument, you can instruct strace to log only specific types of system calls, such as <code>open</code> or <code>connect</code> e.g.:</p>
<pre><code class="lang-bash">strace -e open java -classpath . PrimeMain
</code></pre>
<p>This selective logging not only makes the output more manageable but also allows for focused troubleshooting, speeding up the debugging process.</p>
<h3 id="heading-exploring-a-variety-of-system-calls"><strong>Exploring a Variety of System Calls</strong></h3>
<p>strace's utility extends beyond just tracking file access or network interactions. It can be used to monitor a range of system calls, offering insights into various aspects of application behavior. By understanding and utilizing different system calls, users can gain a comprehensive view of their application's interaction with the operating system, leading to more effective debugging and optimization.</p>
<h2 id="heading-strace-and-java-a-special-case">strace and Java: A Special Case</h2>
<h3 id="heading-strace-with-the-jvm"><strong>strace with the JVM</strong></h3>
<p>While strace predates Java and operates at a low level with no specific awareness of the Java Virtual Machine (JVM), it remains highly effective for debugging Java applications. The JVM, like most platforms, relies on system calls for its operations, which strace can monitor and report. However, certain aspects of the JVM's behavior may be less visible to strace due to its unique approach to problem-solving.</p>
<h3 id="heading-allocations-and-threading-in-java"><strong>Allocations and Threading in Java</strong></h3>
<p>For instance, Java's memory management differs significantly from standard system tools. While typical applications use malloc, which directly maps to kernel allocation logic, Java manages its own memory. This approach, aimed at efficiency and streamlined garbage collection, means that some memory allocation activities are obscured from strace's view.</p>
<p>Similarly, Java threading is currently well-represented in strace output, but this is changing with Java 21 and Project Loom. Java 21 added support for Virtual Threads which are only partially visible to the operating system hence 1,000 threads can seem like 16 threads. These changes could affect the clarity of strace outputs in complex, heavily threaded Java applications.</p>
<h2 id="heading-final-word">Final Word</h2>
<p>strace stands out as an exceptionally versatile and powerful tool in the Linux debugging arsenal. Its ability to provide detailed insights into system calls makes it invaluable for diagnosing and understanding the inner workings of applications. Despite its simplicity, strace is capable of handling complex debugging scenarios, especially when used with its advanced filtering options.</p>
<p>For developers and system administrators working in Linux environments, strace is more than just a diagnostic tool; it's a lens through which the intricate interactions between applications and the operating system can be viewed and understood. As technologies evolve, tools like strace adapt, continuing to offer relevant and critical insights into system behaviors.</p>
<p>Whether you are troubleshooting a stubborn issue or simply curious about how your applications interact with the Linux kernel, strace is a tool that you will likely find yourself returning to time and again.</p>
]]></content:encoded></item><item><title><![CDATA[Styling and Dark Mode]]></title><description><![CDATA[Note: this post was originally published on the gdocweb blog.
Styling a Document isn’t as Trivial as you Might Assume
When using Google Docs it’s often tempting to reach out to the font or color toolbar to design your document as you see fit. This wo...]]></description><link>https://debugagent.com/styling-and-dark-mode</link><guid isPermaLink="true">https://debugagent.com/styling-and-dark-mode</guid><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Google]]></category><category><![CDATA[dark mode]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Fri, 26 Jan 2024 13:13:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706274453656/010e9a0e-f8c3-440e-a439-daa87c7aae86.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Note: this post was originally published on the <a target="_blank" href="https://blog.gdocweb.com/styling-and-dark-mode.html"><strong>gdocweb blog</strong></a>.</p>
<h2 id="heading-styling-a-document-isnt-as-trivial-as-you-might-assume">Styling a Document isn’t as Trivial as you Might Assume</h2>
<p>When using Google Docs it’s often tempting to reach out to the font or color toolbar to design your document as you see fit. This would work great for simple sites but as gdocweb evolves you might find it produces a result that isn’t as attractive as you might want. The reason for that is due to two important features we just added to gdocweb: custom colors and dark mode.</p>
<h2 id="heading-dark-mode-support">Dark Mode Support</h2>
<p>We recently updated gdocweb with a new theme: Adaptive.</p>
<p>The Adaptive theme is identical to the default “Basic” theme but when it’s running on a device set to dark mode it will render the website with a dark version of the theme. As you can see below, this blog automatically adapts to light or dark mode. However, the transition wasn’t seamless.</p>
<p><img src="https://blog.gdocweb.com/img/c9ba8a38aaae8f1621f14c6edc5d41059426e3cb.png" alt /></p>
<p><img src="https://blog.gdocweb.com/img/f0c215de23bc6dd99f0d3cd29409daa06d603df7.png" alt /></p>
<h2 id="heading-title-styling">Title Styling</h2>
<p>The initial design of the blog was based on a template from Google Docs. In this template <em>“Introducing gdocweb”</em> was defined as a “Heading 1” style, but it was later customized to appear a bit differently from the actual “Heading 1” definition.</p>
<p>Headings are an important part of the web, in general your page should have only one “Heading 1” and multiple “Heading 2” elements (and possibly 3, 4, 5 etc.). If you break these rules you might suffer a search engine penalty. But it goes deeper than that, once you customize the style of the heading we can’t override it. That results in dark blue text on black background. Not very readable for the most important line within a post...</p>
<p>The solution is simple and it includes two parts. The first is to use styles in the document by placing the cursor on the heading and invoking “Update ‘Heading 1’ to match (or the appropriate style you want to customize). We then need to select “Apply ‘Heading 1’” to re-apply it to the current line. Besides the obvious advantages with dark mode, this also helps keep the document consistent. E.g. if we want to change the font or color for all “Heading 2” entries we can do that in one place and they will all update.</p>
<p><img src="https://blog.gdocweb.com/img/e9eb08e31bfb484a6b7cddc44f9ba661c7044298.png" alt /></p>
<p>But the real advantage is in gdocweb. We can now customize specific colors within the theme. This is useful for dark mode but also useful for applying brand colors on top of the theme. We can pick replacement default colors for background and foreground of various elements and make the replacements apply only for light or dark mode. This is all a part of the themes section as seen in the image below.</p>
<p><img src="https://blog.gdocweb.com/img/b5525e50033ca8265e3d26745f97a6bb8f69733f.png" alt /></p>
<h2 id="heading-future-improvements">Future Improvements</h2>
<p>Ideally the themes should include the right colors out of the box but the ability to customize these colors is a crucial one. Let us know in the comments if you need deeper styling and what your technical level is. One of our planned features is a style override that will let you inject a custom style file to the page. However, this would require technical understanding and control of CSS, I’m not sure if this is something that would fit with our main demographic.</p>
<p>You can see the document that generated this post <a target="_blank" href="https://docs.google.com/document/d/1mZLhYedYKY3MOizIWPt_J4APlzOaNCbOoEiER0LxuyY/edit?usp=sharing">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[DTrace Revisited: Advanced Debugging Techniques]]></title><description><![CDATA[When we think of debugging we think of breakpoints in IDEs, stepping over, inspecting variables, etc. However, there are instances where stepping outside the conventional confines of an IDE becomes essential to track and resolve complex issues. This ...]]></description><link>https://debugagent.com/dtrace-revisited-advanced-debugging-techniques</link><guid isPermaLink="true">https://debugagent.com/dtrace-revisited-advanced-debugging-techniques</guid><category><![CDATA[debugging]]></category><category><![CDATA[Java]]></category><category><![CDATA[macOS]]></category><category><![CDATA[sysadmin]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Shai Almog]]></dc:creator><pubDate>Tue, 23 Jan 2024 15:12:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706007174513/c8022165-6e89-45a1-9488-cf914ff0b192.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When we think of debugging we think of breakpoints in IDEs, stepping over, inspecting variables, etc. However, there are instances where stepping outside the conventional confines of an IDE becomes essential to track and resolve complex issues. This is where tools like DTrace come into play, offering a more nuanced and powerful approach to debugging than traditional methods. This blog post delves into the intricacies of DTrace, an innovative tool that has reshaped the landscape of debugging and system analysis.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/3M0AhZnVoUk">https://youtu.be/3M0AhZnVoUk</a></div>
<p> </p>
<p>As a side note, if you like the content of this and the other posts in this series check out my <a target="_blank" href="https://www.amazon.com/dp/1484290410/"><strong>Debugging book</strong></a> that covers this subject. If you have friends that are learning to code I'd appreciate a reference to my <a target="_blank" href="https://www.amazon.com/Java-Basics-Practical-Introduction-Full-Stack-ebook/dp/B0CCPGZ8W1/"><strong>Java Basics book</strong></a>. If you want to get back to Java after a while check out my <a target="_blank" href="https://www.amazon.com/Java-21-Explore-cutting-edge-features/dp/9355513925/"><strong>Java 8 to 21 book</strong>.</a></p>
<h2 id="heading-dtrace-overview">DTrace Overview</h2>
<p>DTrace was first introduced by Sun Microsystems in 2004, DTrace quickly garnered attention for its groundbreaking approach to dynamic system tracing. Originally developed for Solaris, it has since been ported to various platforms, including MacOS, Windows, and Linux. DTrace stands out as a dynamic tracing framework that enables deep inspection of live systems – from operating systems to running applications. Its capacity to provide real-time insights into system and application behavior without significant performance degradation marks it as a revolutionary tool in the domain of system diagnostics and debugging.</p>
<h2 id="heading-understanding-dtraces-capabilities">Understanding DTrace’s Capabilities</h2>
<p>DTrace, short for Dynamic Tracing, is a comprehensive toolkit for real-time system monitoring and debugging, offering an array of capabilities that span across different levels of system operation. Its versatility lies in its ability to provide insights into both high-level system performance and detailed process-level activities.</p>
<h3 id="heading-system-monitoring-and-analysis">System Monitoring and Analysis</h3>
<p>At its core, DTrace excels in monitoring various system-level operations. It can trace system calls, file system activities, and network operations. This enables developers and system administrators to observe the interactions between the operating system and the applications running on it. For instance, DTrace can identify which files a process accesses, monitor network requests, and even trace system calls to provide a detailed view of what's happening within the system.</p>
<h3 id="heading-process-and-performance-analysis">Process and Performance Analysis</h3>
<p>Beyond system-level monitoring, DTrace is particularly adept at dissecting individual processes. It can provide detailed information about process execution, including CPU and memory usage, helping to pinpoint performance bottlenecks or memory leaks. This granular level of detail is invaluable for performance tuning and debugging complex software issues.</p>
<h3 id="heading-customizability-and-flexibility">Customizability and Flexibility</h3>
<p>One of the most powerful aspects of DTrace is its customizability. With a scripting language based on C syntax, DTrace allows the creation of customized scripts to probe specific aspects of system behavior. This flexibility means that it can be adapted to a wide range of debugging scenarios, making it a versatile tool in a developer’s arsenal.</p>
<h3 id="heading-real-world-applications">Real-World Applications</h3>
<p>In practical terms, DTrace can be used to diagnose elusive performance issues, track down resource leaks, or understand complex interactions between different system components. For example, it can be used to determine the cause of a slow file operation, analyze the reasons behind a process crash, or understand the system impact of a new software deployment.</p>
<h2 id="heading-performance-and-compatibility-of-dtrace">Performance and Compatibility of DTrace</h2>
<p>A standout feature of DTrace is its ability to operate with remarkable efficiency. Despite its deep system integration, DTrace is designed to have minimal impact on overall system performance. This efficiency makes it a feasible tool for use in live production environments, where maintaining system stability and performance is crucial. Its non-intrusive nature allows developers and system administrators to conduct thorough debugging and performance analysis without the worry of significantly slowing down or disrupting the normal operation of the system.</p>
<h3 id="heading-cross-platform-compatibility"><strong>Cross-Platform Compatibility</strong></h3>
<p>Originally developed for Solaris, DTrace has evolved into a cross-platform tool, with adaptations available for MacOS, Windows, and various Linux distributions. Each platform presents its own set of features and limitations. For instance, while DTrace is a native component in Solaris and MacOS, its implementation in Linux often requires a specialized build due to kernel support and licensing considerations.</p>
<h3 id="heading-compatibility-challenges-on-macos"><strong>Compatibility Challenges on MacOS</strong></h3>
<p>On MacOS, DTrace's functionality intersects with System Integrity Protection (SIP), a security feature designed to prevent potentially harmful actions. To utilize DTrace effectively, users may need to disable SIP, which should be done with caution. This process involves booting into recovery mode and executing specific commands, a step that highlights the need for a careful approach when working with such powerful system-level tools.</p>
<p>We can disable SIP using the command:</p>
<pre><code class="lang-bash">csrutil <span class="hljs-built_in">disable</span>
</code></pre>
<p>We can optionally use a more refined approach of enabling SIP without dtrace using the following command:</p>
<pre><code class="lang-bash">csrutil <span class="hljs-built_in">enable</span> --without dtrace
</code></pre>
<p>Be extra careful when issuing these commands and when working on machines where dtrace is enabled. Back up your data properly!</p>
<h2 id="heading-customizability-and-flexibility-of-dtrace">Customizability and Flexibility of DTrace</h2>
<p>A key feature that sets DTrace apart in the realm of system monitoring tools is its highly customizable nature. DTrace employs a scripting language that bears similarity to C syntax, offering users the ability to craft detailed and specific diagnostic scripts. This scripting capability allows for the creation of custom probes that can be fine-tuned to target particular aspects of system behavior, providing precise and relevant data.</p>
<h3 id="heading-adaptability-to-various-scenarios"><strong>Adaptability to Various Scenarios</strong></h3>
<p>The flexibility of DTrace's scripting language means it can adapt to a multitude of debugging scenarios. Whether it's tracking down memory leaks, analyzing CPU usage, or monitoring I/O operations, DTrace can be configured to provide insights tailored to the specific needs of the task. This adaptability makes it an invaluable tool for both developers and system administrators who require a dynamic approach to problem-solving.</p>
<h3 id="heading-examples-of-customizable-probes"><strong>Examples of Customizable Probes</strong></h3>
<p>Users can define probes to monitor specific system events, track the behavior of certain processes, or gather data on system resource usage. This level of customization ensures that DTrace can be an effective tool in a variety of contexts, from routine maintenance to complex troubleshooting tasks. Following in a simple hello world dtrace probe:</p>
<pre><code class="lang-bash">sudo dtrace -qn <span class="hljs-string">'syscall::write:entry, syscall::sendto:entry /pid == $target/ { printf("(%d) %s %s", pid, probefunc, copyinstr(arg1)); }'</span> -p 9999
</code></pre>
<p>The kernel is instrumented with hooks that match various callbacks. dtrace connects to these hooks and can perform interesting tasks when these hooks are triggered. They have a naming convention, specially: <code>provider:module:function:name</code>. In this case the provider is a system call in both cases. We have no module so we can leave that part blank between the colon (<code>:</code>) symbols. We grab a write operation and <code>sendto</code> entries. When an application will write or tries to send a packet, the following code event will trigger.</p>
<p>These things happen frequently which is why we restrict the process ID to the specific target with <code>pid == $target</code>. This means the code will only trigger for the PID passed to us in the command line. The rest of the code should be simple for anyone with basic C experience, it's a printf that would list the processes and the data passed.</p>
<h2 id="heading-real-world-applications-of-dtrace">Real-World Applications of DTrace</h2>
<p>DTrace's diverse capabilities extend far beyond theoretical use, playing a pivotal role in resolving real-world system complexities. Its ability to provide deep insights into system operations makes it an indispensable tool in a variety of practical applications.</p>
<p>To get a sense of how dtrace can be used we can use the <code>man -k dtrace</code> command whose output on my mac is below:</p>
<pre><code class="lang-bash">bitesize.d(1m)           - analyse disk I/O size by process. Uses DTrace
cpuwalk.d(1m)            - Measure <span class="hljs-built_in">which</span> CPUs a process runs on. Uses DTrace
creatbyproc.d(1m)        - snoop creat()s by process name. Uses DTrace
dappprof(1m)             - profile user and lib <span class="hljs-keyword">function</span> usage. Uses DTrace
dapptrace(1m)            - trace user and library <span class="hljs-keyword">function</span> usage. Uses DTrace
dispqlen.d(1m)           - dispatcher queue length by CPU. Uses DTrace
dtrace(1)                - dynamic tracing compiler and tracing utility
dtruss(1m)               - process syscall details. Uses DTrace
errinfo(1m)              - <span class="hljs-built_in">print</span> errno <span class="hljs-keyword">for</span> syscall fails. Uses DTrace
execsnoop(1m)            - snoop new process execution. Uses DTrace
fddist(1m)               - file descriptor usage distributions. Uses DTrace
filebyproc.d(1m)         - snoop opens by process name. Uses DTrace
hotspot.d(1m)            - <span class="hljs-built_in">print</span> disk event by location. Uses DTrace
iofile.d(1m)             - I/O <span class="hljs-built_in">wait</span> time by file and process. Uses DTrace
iofileb.d(1m)            - I/O bytes by file and process. Uses DTrace
iopattern(1m)            - <span class="hljs-built_in">print</span> disk I/O pattern. Uses DTrace
iopending(1m)            - plot number of pending disk events. Uses DTrace
iosnoop(1m)              - snoop I/O events as they occur. Uses DTrace
iotop(1m)                - display top disk I/O events by process. Uses DTrace
kill.d(1m)               - snoop process signals as they occur. Uses DTrace
lastwords(1m)            - <span class="hljs-built_in">print</span> syscalls before <span class="hljs-built_in">exit</span>. Uses DTrace
loads.d(1m)              - <span class="hljs-built_in">print</span> load averages. Uses DTrace
newproc.d(1m)            - snoop new processes. Uses DTrace
opensnoop(1m)            - snoop file opens as they occur. Uses DTrace
pathopens.d(1m)          - full pathnames opened ok count. Uses DTrace
perldtrace(1)            - Perl<span class="hljs-string">'s support for DTrace
pidpersec.d(1m)          - print new PIDs per sec. Uses DTrace
plockstat(1)             - front-end to DTrace to print statistics about POSIX mutexes and read/write locks
priclass.d(1m)           - priority distribution by scheduling class. Uses DTrace
pridist.d(1m)            - process priority distribution. Uses DTrace
procsystime(1m)          - analyse system call times. Uses DTrace
rwbypid.d(1m)            - read/write calls by PID. Uses DTrace
rwbytype.d(1m)           - read/write bytes by vnode type. Uses DTrace
rwsnoop(1m)              - snoop read/write events. Uses DTrace
sampleproc(1m)           - sample processes on the CPUs. Uses DTrace
seeksize.d(1m)           - print disk event seek report. Uses DTrace
setuids.d(1m)            - snoop setuid calls as they occur. Uses DTrace
sigdist.d(1m)            - signal distribution by process. Uses DTrace
syscallbypid.d(1m)       - syscalls by process ID. Uses DTrace
syscallbyproc.d(1m)      - syscalls by process name. Uses DTrace
syscallbysysc.d(1m)      - syscalls by syscall. Uses DTrace
topsyscall(1m)           - top syscalls by syscall name. Uses DTrace
topsysproc(1m)           - top syscalls by process name. Uses DTrace
Tcl_CommandTraceInfo(3tcl), Tcl_TraceCommand(3tcl), Tcl_UntraceCommand(3tcl) - monitor renames and deletes of a command
bitesize.d(1m)           - analyse disk I/O size by process. Uses DTrace
cpuwalk.d(1m)            - Measure which CPUs a process runs on. Uses DTrace
creatbyproc.d(1m)        - snoop creat()s by process name. Uses DTrace
dappprof(1m)             - profile user and lib function usage. Uses DTrace
dapptrace(1m)            - trace user and library function usage. Uses DTrace
dispqlen.d(1m)           - dispatcher queue length by CPU. Uses DTrace
dtrace(1)                - dynamic tracing compiler and tracing utility
dtruss(1m)               - process syscall details. Uses DTrace
errinfo(1m)              - print errno for syscall fails. Uses DTrace
execsnoop(1m)            - snoop new process execution. Uses DTrace
fddist(1m)               - file descriptor usage distributions. Uses DTrace
filebyproc.d(1m)         - snoop opens by process name. Uses DTrace
hotspot.d(1m)            - print disk event by location. Uses DTrace
iofile.d(1m)             - I/O wait time by file and process. Uses DTrace
iofileb.d(1m)            - I/O bytes by file and process. Uses DTrace
iopattern(1m)            - print disk I/O pattern. Uses DTrace
iopending(1m)            - plot number of pending disk events. Uses DTrace
iosnoop(1m)              - snoop I/O events as they occur. Uses DTrace
iotop(1m)                - display top disk I/O events by process. Uses DTrace
kill.d(1m)               - snoop process signals as they occur. Uses DTrace
lastwords(1m)            - print syscalls before exit. Uses DTrace
loads.d(1m)              - print load averages. Uses DTrace
newproc.d(1m)            - snoop new processes. Uses DTrace
opensnoop(1m)            - snoop file opens as they occur. Uses DTrace
pathopens.d(1m)          - full pathnames opened ok count. Uses DTrace
perldtrace(1)            - Perl'</span>s support <span class="hljs-keyword">for</span> DTrace
pidpersec.d(1m)          - <span class="hljs-built_in">print</span> new PIDs per sec. Uses DTrace
plockstat(1)             - front-end to DTrace to <span class="hljs-built_in">print</span> statistics about POSIX mutexes and <span class="hljs-built_in">read</span>/write locks
priclass.d(1m)           - priority distribution by scheduling class. Uses DTrace
pridist.d(1m)            - process priority distribution. Uses DTrace
procsystime(1m)          - analyse system call <span class="hljs-built_in">times</span>. Uses DTrace
rwbypid.d(1m)            - <span class="hljs-built_in">read</span>/write calls by PID. Uses DTrace
rwbytype.d(1m)           - <span class="hljs-built_in">read</span>/write bytes by vnode <span class="hljs-built_in">type</span>. Uses DTrace
rwsnoop(1m)              - snoop <span class="hljs-built_in">read</span>/write events. Uses DTrace
sampleproc(1m)           - sample processes on the CPUs. Uses DTrace
seeksize.d(1m)           - <span class="hljs-built_in">print</span> disk event seek report. Uses DTrace
setuids.d(1m)            - snoop setuid calls as they occur. Uses DTrace
sigdist.d(1m)            - signal distribution by process. Uses DTrace
syscallbypid.d(1m)       - syscalls by process ID. Uses DTrace
syscallbyproc.d(1m)      - syscalls by process name. Uses DTrace
syscallbysysc.d(1m)      - syscalls by syscall. Uses DTrace
topsyscall(1m)           - top syscalls by syscall name. Uses DTrace
topsysproc(1m)           - top syscalls by process name. Uses DTrace
</code></pre>
<p>There's a lot here, we don't need to read everything. The point is that when you run into a problem you can just search through this list and find a tool dedicated to debugging that problem.</p>
<p>Let’s say you're facing elevated disk write issues that are causing the performance of your application to degrade... But is it your app at fault or some other app?</p>
<p>rwbypid.d can help you with that, it can generate a list of processes and the number of calls they have for read/write based on the process id as seen in the following screenshot:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706011615948/ece6dd1f-1898-4a0c-8e91-a21730f7d819.png" alt class="image--center mx-auto" /></p>
<p>We can use this information to better understand IO issues in our code or even in 3rd party applications/libraries. <code>iosnoop</code> is another tool that helps us track IO operations but with more details:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706011771909/2bfd5871-3ac2-499a-a28a-3eceefbebee4.png" alt class="image--center mx-auto" /></p>
<p>In diagnosing elusive system issues, DTrace shines by enabling detailed observation of system calls, file operations, and network activities. For instance, it can be used to uncover the root cause of unexpected system behaviors or to trace the origin of security breaches, offering a level of detail that is often unattainable with other debugging tools.</p>
<p>Performance optimization is main area where DTrace demonstrates its strengths. It allows administrators and developers to pinpoint performance bottlenecks, whether they lie in application code, system calls, or hardware interactions. By providing real-time data on resource usage, DTrace helps in fine-tuning systems for optimal performance.</p>
<h3 id="heading-final-words">Final Words</h3>
<p>In conclusion, DTrace stands as a powerful and versatile tool in the realm of system monitoring and debugging. We've explored its broad capabilities, from in-depth system analysis to individual process tracing, and its remarkable performance efficiency that allows for its use in live environments. Its cross-platform compatibility, coupled with the challenges and solutions specific to MacOS, highlights its widespread applicability. The customizability through scripting provides unmatched flexibility, adapting to a myriad of diagnostic needs. Real-world applications of DTrace in diagnosing system issues and optimizing performance underscore its practical value.</p>
<p>DTrace's comprehensive toolkit offers an unparalleled window into the inner workings of systems, making it an invaluable asset for system administrators and developers alike. Whether it's for routine troubleshooting or complex performance tuning, DTrace provides insights and solutions that are essential in the modern computing landscape.</p>
]]></content:encoded></item></channel></rss>