<?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[Keep It Simple, Stupid!]]></title><description><![CDATA[Software Engineer × Architect × Problem Solver - building scalable, data-driven systems that turn complexity into clarity.]]></description><link>https://blog.skopow.ski</link><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 00:32:11 GMT</lastBuildDate><atom:link href="https://blog.skopow.ski/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Stop Writing Bigger Prompts. Start Designing Agent Skills.]]></title><description><![CDATA[When an AI coding agent does something wrong, the natural reaction is to add more instructions.
Another rule. Another example. Another edge case paragraph.
The prompt grows from a few hundred tokens t]]></description><link>https://blog.skopow.ski/stop-writing-bigger-prompts-start-designing-agent-skills</link><guid isPermaLink="true">https://blog.skopow.ski/stop-writing-bigger-prompts-start-designing-agent-skills</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[large language models]]></category><category><![CDATA[context-window]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sat, 11 Apr 2026 13:06:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65d1f8889a5bb78adbf014e7/bc193869-1486-47e9-a34a-4852d56b6f2f.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When an AI coding agent does something wrong, the natural reaction is to add more instructions.</p>
<p>Another rule. Another example. Another edge case paragraph.</p>
<p>The prompt grows from a few hundred tokens to thousands, then tens of thousands. And somehow the agent gets worse - it forgets rules from the top, applies the wrong convention, or just ignores half of what you wrote.</p>
<p>The problem isn't missing instructions. The problem is architectural.</p>
<h1>The Monolithic Prompt Trap</h1>
<p>Most people start with a single system prompt that tries to cover everything: code style, git conventions, testing standards, ticket management, deployment rules, error handling patterns.</p>
<p>It works fine at first. Then the project grows, the team adds more conventions, and suddenly you're maintaining a 10-page document that the model has to reason over for every single request.</p>
<p>This is the equivalent of putting your entire application logic in one file. It works until it doesn't.</p>
<p>LLMs have finite attention. The more context you load, the less reliably the model attends to any specific part of it. Research on this is clear - performance degrades as context grows, especially for instructions in the middle of long prompts.</p>
<p>You're not giving the agent more knowledge.</p>
<p>You're giving it more noise.</p>
<h1>Skills as Modular Units</h1>
<p>Instead of one massive prompt, break your instructions into self-contained modules - call them skills, rules, playbooks, whatever fits your setup.</p>
<p>Each one covers exactly one domain:</p>
<ul>
<li><p>git workflow conventions</p>
</li>
<li><p>code style for your language</p>
</li>
<li><p>how to interact with your ticket system</p>
</li>
<li><p>testing standards</p>
</li>
</ul>
<p>Each skill has two properties:</p>
<ul>
<li><p>A trigger condition: when should this knowledge be active? A git workflow skill is irrelevant when the user asks about database migrations. A Java code style skill is irrelevant when the task is creating a Jira ticket.</p>
</li>
<li><p>A focused scope: it contains everything the agent needs for that domain, and nothing else.</p>
</li>
</ul>
<p>The key insight: skills are loaded on demand, not all at once. When someone asks the agent to create a merge request, it loads the git workflow skill. When someone asks to implement a feature, it loads the code quality and language-specific skills.</p>
<p>The active context stays small and relevant.</p>
<p>THis isn't a new idea. It's the same principle behind modular software design - high cohesion, low coupling. Each module knows its domain deeply and doesn't leak into others.</p>
<h1>The Router, Not the Brain</h1>
<p>If skills are modules, you need something to route between them. This is a lightweight orchestrator - a small set of instructions that knows what skills exist and when to load them. It doesn't contain domain knowledge itself. It's a lookup table.</p>
<p>Think of it as an API gateway for your agent's knowledge. The gateway doesn't process business logic. It takes at the incoming request and forwards it to the right service.</p>
<p>In practice, this means your base prompt is tiny. It contains:</p>
<ul>
<li><p>A list of available skills with their trigger conditions</p>
</li>
<li><p>Rules for when to load which skill</p>
</li>
<li><p>Delegation logic for which agent handles which domain</p>
</li>
</ul>
<p>Everything else lives in the skills themselves, loaded only when needed.</p>
<h1>Split Agents by Domain</h1>
<p>The same principle applies to agents themselves. A single agent with every tool and every piece of context is the prompt equivalent of a god object. It can do everything, which means it does nothing reliably.</p>
<p>Instead, split by domain. One agent handles ticket management. Another handles git operation. Another handles code implementation. Each gets only the tools and context relevant to its job.</p>
<p>This has a practical benefit beyond context size: agents can chain. The code agent writes the implementation, then delegates the commit to the git agent. The git agent doesn't need to know about the code style. The code agent doesn't need to know about commit message convention. Each operates within its domain.</p>
<p>Delegation rules should be explicit. URL patterns, keyword triggers, tool categories - whatever majkes the routing unambiguous. When the boundaries are clear, the agent doesn't have to guess which hat to wear.</p>
<h1>What You Actually Get</h1>
<p><strong>Smaller active context</strong>. Instead of 50k tokens always loaded, you load 2-5k per skillm only when relevant. The model reasons over focused information instead of scanning a wall of text.</p>
<p><strong>Predictability</strong>. Each skill is testable in isolation. You can verify that your git workflow skill produces correct branch names without woryying about Go convention interfering. When something breaks, you know which skill to look at.</p>
<p><strong>Composability</strong>. Skills can be shared across projects. A new repository gets the same quality standards by importing the same skill files. Teams standardize without facing everyone into one monolithic prompt.</p>
<p><strong>Maintainability</strong>. Adding a new conventions means editing a skill file, not hunting through a 10-page document hoping you don't break something else. Skills can have clear ownership - the backend team maintains language-specific skills, the platform team maintains git and deployments skills.</p>
<h1>How to Start</h1>
<p><strong>Audit your current prompt</strong>. Read through it and highlight where topics change. Every topic boundary is a candidate for extraction into a separate skill/</p>
<p><strong>Extract each domain into its own file</strong>. One file per domain. Give each a clear trigger condition - a simple sentence describing when this skill should be active.</p>
<p><strong>Build a minimal router</strong>. Your base prompt becomes a table: "when the task involves X, load skill Y". Nothing more.</p>
<p><strong>Add delegation rules if you use multiple agents</strong>. Map tool categories to agents. Make the boundaries explicit.</p>
<p><strong>Iterate by diagnosing, not appending</strong>. When the agent misbehaves, dob't add another paragraph. Ask: which skill should have handled this? Was it loaded? Was the trigger condition wrong? Was the skill content unclear? The fix is almost alsways in one specific skill, not in "more instructions".</p>
<h1>The Mental Shift</h1>
<p>The instinct to write bigger prompts comes from treating the AI agent like a junior developer who needs exhaustive instructions upfront. But that mental model breaks at scale. A better model: treat the agent like a system of services, each with a clear API contract and bounded context.</p>
<p>You wouldn't build a microservices architecture by putting all the logic in the API gateway. Don't build an agent architecture by putting all the knowledge in the system prompt.</p>
<p>The next time your agent does something wrong, resist the urge to add more text. Instead, ask: is this a missing skill, a wrong trigger, or a rouitng problem? The answer will be more useful than another paragraph in an already-too-long prompt.</p>
<p>Hope this helps,</p>
<p>Cheers!</p>
]]></content:encoded></item><item><title><![CDATA[Small Pull Requests Win Every Time]]></title><description><![CDATA[One of the simplest engineering habits that dramatically improves team productivity is to

Keep the pull requests small.

It sounds obvious. Yet in many teams, pull (merge) requests regularly contain ]]></description><link>https://blog.skopow.ski/small-pull-requests-win-every-time</link><guid isPermaLink="true">https://blog.skopow.ski/small-pull-requests-win-every-time</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Pull Requests]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 08 Mar 2026 12:55:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/65d1f8889a5bb78adbf014e7/50404d81-85d9-4a37-8b06-b7122ab04e6c.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the simplest engineering habits that dramatically improves team productivity is to</p>
<blockquote>
<p>Keep the pull requests small.</p>
</blockquote>
<p>It sounds obvious. Yet in many teams, pull (merge) requests regularly contain hundreds or even thousands of changed lines.</p>
<p>Large PRs slow down reviews, introduce more bugs, and create unnecessary friction in the development process.</p>
<p>Small pull requests solve most of these problems.</p>
<p>Let's look at why.</p>
<h1>The Problem With Large Pull Requests</h1>
<p>A typical large PR looks like this:</p>
<pre><code class="language-plaintext">Changes:
- 2k lines modified
- 25 files changed
- feature implementation
- refactoring
- small bug fixes
</code></pre>
<p>When reviewers open such a PR, several things happen immediately:</p>
<ul>
<li><p>it takes a long time to understand the context (even if there's a comprehensive description)</p>
</li>
<li><p>the reviewer gets tired halfway through</p>
</li>
<li><p>parts of the change are reviewed <strong>superficially</strong></p>
</li>
<li><p>bugs slip through</p>
</li>
</ul>
<p>Even worse, large PRs often mix <strong>multiple types of changes</strong>:</p>
<ul>
<li><p>feature implementation</p>
</li>
<li><p>refactoring</p>
</li>
<li><p>formatting</p>
</li>
<li><p>dependency updates</p>
</li>
</ul>
<p>This makes the review provess significantly harder.</p>
<h1>What a Good Pull Request Looks Like</h1>
<p>For example:</p>
<pre><code class="language-plaintext">Changes:
- 120 lines modified
- 3 files changed
- one clearly defined change
</code></pre>
<p>The intention should be obvious from the title:</p>
<p><code>Add retry mechanism for payment API client</code></p>
<p>Or</p>
<p><code>Fix race condition in inventory cache refresh</code></p>
<p>A reviewer should be able to quickly answer:</p>
<ul>
<li><p><strong>What problem does this solve?</strong></p>
</li>
<li><p><strong>What changed in the code?</strong></p>
</li>
<li><p><strong>Could this introduce side effects?</strong></p>
</li>
</ul>
<p>If the <strong>answer</strong> to these questions is <strong>clear</strong>, the PR is <strong>well srtructured</strong>.</p>
<h1>Why Small PRs Are Better</h1>
<p>Small pull requests bring several benefits.</p>
<h2>Faster Reviews</h2>
<p>Small PRs are easier to revierw, which means they get merged faster! (Time to market!)</p>
<p>A useful rule of thumb:</p>
<blockquote>
<p>A pull request should not take more than 10-15 minutes to review.</p>
</blockquote>
<p>If it does, it's probably too large ¯\_(ツ)_/¯</p>
<h2>Better Code Quality</h2>
<p>When changes are small:</p>
<ul>
<li><p>reviewers pay more attention</p>
</li>
<li><p>discussions are more focused</p>
</li>
<li><p>issues are caught earlier</p>
</li>
</ul>
<p>Large PRs often lead to "rubber-stamp approvals" ("<em>LGTM</em>").</p>
<img src="https://cdn.hashnode.com/uploads/covers/65d1f8889a5bb78adbf014e7/d50c2646-22cc-4c2b-9929-a91fea1b1019.png" alt="" style="display:block;margin:0 auto" />

<h2>Easier Debugging</h2>
<p>When a bug appears after deployment, smaller PRs make it mych easier to locate the cause.</p>
<p>Instead of searching through a massive change set, you can quickly narrow it down.</p>
<h2>Lower Risk Deployments</h2>
<p>Small changes mean smaller risk.</p>
<p>If something goes wrong:</p>
<ul>
<li><p>the rollback is easier</p>
</li>
<li><p>the impact is limited</p>
</li>
<li><p>the root cause is easier to identify</p>
</li>
</ul>
<p>This is one the reasons high-performing teams deploy frequently.</p>
<h1>Practical Tips for Keeping PRs Small</h1>
<p>Here are a few simple habits that help.</p>
<h2>Separate Refactoring From Features</h2>
<p>Do not mix refactoring and features developmnet in the same PR.</p>
<p>Bad:</p>
<pre><code class="language-plaintext">Changes:
- Add new API endpoint
- Refactor service layer
</code></pre>
<p>Better:</p>
<pre><code class="language-plaintext">PR #1
Changes:
- Refactor service layer

PR #2
Changes:
- Add new API endpoint
</code></pre>
<h2>Commit Early and Often</h2>
<p>Instead of building a huge branch over several days, open PRs earlier.</p>
<p>Even partial work can be reviewed it it's structured well.</p>
<p>Read more on <a href="https://blog.skopow.ski/how-to-use-rero-to-improve-developer-velocity-and-feedback-loops">Release Early, Release Often in my previous article</a>.</p>
<h2>Split Large Changes</h2>
<p>If a change becomes too big, split it into logical steps.</p>
<p>Example:</p>
<pre><code class="language-plaintext">PR #1
Changes: 
- Introduce new repository interface

PR #2
Changes:
- Implement PGSQL adapter

PR #3
Changes:
- Update service to use new repository
</code></pre>
<p>Each step is easier to understand and review. It's atomic. The discussions are stritly focused on a subject.</p>
<h1>A Simple Rule I Follow</h1>
<p>If a pull request feels difficult to review, it's probably too big.</p>
<p>In practice, I try to keep PRs:</p>
<ul>
<li><p>up to 500 lines (under <strong>200-300</strong> lines is a sweet spot; the &lt;100 is a perfect PR)</p>
</li>
<li><p>focused on <strong>one logical change</strong></p>
</li>
<li><p>reviewable in under <strong>15 minutes</strong></p>
</li>
<li><p>clearly <strong>described</strong></p>
</li>
</ul>
<p>This keeps the development flow smooth and predictable.</p>
<h1>Article Takeaway</h1>
<p>Small pull requests are one of the easiest improvements a team can make.</p>
<p>They lead to:</p>
<ul>
<li><p>faster reviews</p>
</li>
<li><p>better feedback</p>
</li>
<li><p>fewer bugs</p>
</li>
<li><p>smoother deployments</p>
</li>
</ul>
<p>And the best part is that this habit requires no new tools or processes.</p>
<p>Just small changes.</p>
<h1>Sources:</h1>
<ul>
<li><p>LGTM memes: <a href="https://programmerhumor.io/memes/lgtm">https://programmerhumor.io/memes/lgtm</a></p>
</li>
<li><p>Code review paradox meme: <a href="https://programmerhumor.io/git-memes/looks-good-to-me-2w1a">https://programmerhumor.io/git-memes/looks-good-to-me-2w1a</a></p>
</li>
</ul>
<p>Cheers!</p>
]]></content:encoded></item><item><title><![CDATA[Allocation Rate in Go and Java]]></title><description><![CDATA[Garbage Collection discussions often drift into theory: algorithms, generations, colors, phases. In practise , most real GC problems come from one simple thing:

Allocation rate is too high

Today we’re going to discuss how high allocation rate affec...]]></description><link>https://blog.skopow.ski/allocation-rate-in-go-and-java</link><guid isPermaLink="true">https://blog.skopow.ski/allocation-rate-in-go-and-java</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[memory-management]]></category><category><![CDATA[Java]]></category><category><![CDATA[Go Language]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 08 Feb 2026 12:43:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/RkIsyD_AVvc/upload/24599a7597ca6078973025c5758e8cc9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Garbage Collection discussions often drift into theory: algorithms, generations, colors, phases. In practise , most real GC problems come from one simple thing:</p>
<blockquote>
<p>Allocation rate is too high</p>
</blockquote>
<p>Today we’re going to discuss how high allocation rate affects GC in Go and Java - and why it bites you differently in each language.</p>
<p>One problem. Two ecosystems.</p>
<h1 id="heading-the-problem-small-short-lived-objects-everywhere">The Problem: “small, short-lived objects everywhere”</h1>
<p>Typical scenario:</p>
<ul>
<li><p>HTTP request handling</p>
</li>
<li><p>JSON (de)serialization</p>
</li>
<li><p>DTO → domain → response mapping</p>
</li>
<li><p>Logging, metrics, tracing</p>
</li>
</ul>
<p>Nothing fancy. Just a lot of:</p>
<ul>
<li><p>short-lived objects</p>
</li>
<li><p>created fast</p>
</li>
<li><p>discarded almost immediately</p>
</li>
</ul>
<p>From a business PoV: <em>normal backend</em> code. From the GC PoV: <strong>constant pressure</strong>.</p>
<h1 id="heading-go-allocation-rate-directly-drives-gc-frequency">Go: Allocation Rate Directly Drives GC Frequency</h1>
<p>In Go there’s no generational heap and the GC doesn’t assume “most objects die young”. Allocation rate is one of the primary triggers for GC work.</p>
<p>What this means in practice?</p>
<ul>
<li><p>More allocations → GC runs <strong>more often</strong></p>
</li>
<li><p>GC runs more often → more CPU stolen from your goroutines</p>
</li>
<li><p>Even if pauses are short, total GC cost grows linearly</p>
</li>
</ul>
<p>Important detail: Go’s GC is optimized for low latency, not for absorbing insance allocations rates.</p>
<h1 id="heading-typical-go-failure-mode">Typical Go Failure Mode</h1>
<p>You don’t see long pauses, instead you see:</p>
<ul>
<li><p>higher CPU usage</p>
</li>
<li><p>lower throughput</p>
</li>
<li><p>unexplained slowdown under load</p>
</li>
</ul>
<p>Common assumption is: GC is concurrent, so it shouldn’t hurt. It is concurrent - but still <strong>does work</strong>, and that work scales with allocation rate.</p>
<p>In Go, the fix is almost always:</p>
<ul>
<li><p>reduce allocations</p>
</li>
<li><p>reuse objects</p>
</li>
<li><p>avoid unnecessary heap escapes</p>
</li>
</ul>
<h1 id="heading-java-allocation-rate-fills-the-young-generation">Java: Allocation Rate Fills the Young Generation</h1>
<p>Java assumes something Go doesn’t:</p>
<blockquote>
<p>Most objects die young.</p>
</blockquote>
<p>That’s why Java uses a <strong>generational heap.</strong></p>
<p>What happens with high allocation rate:</p>
<ul>
<li><p>Eden space fills up quickly</p>
</li>
<li><p>Minor GCs happen frequently</p>
</li>
<li><p>Most objects are reclaimed cheaply</p>
</li>
</ul>
<p>As long as objects die young and there’re only a few survivors. Java handles high allocation rates surprisingly well.</p>
<h1 id="heading-where-it-goes-wrong">Where it Goes Wrong</h1>
<p>The problem starts when objects <em>almost</em> die young but survive just long enough to be promoted.</p>
<p>Then old generation fills up and a major GC cycles appear and pause times jump from miliseconds to seconds.</p>
<p>The allocation rate itself isn’t the killer. <strong>Promotion rate is.</strong></p>
<p>This is why Java systems often look fine… until they suddenly don’t.</p>
<h1 id="heading-same-problem-different-pain">Same Problem, Different Pain</h1>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>Go</td><td>Java</td></tr>
</thead>
<tbody>
<tr>
<td>High allocation rate</td><td>Increases GC CPU cost</td><td>Usually absorbed by young gen</td></tr>
<tr>
<td>Typical symptom</td><td>Throughput drop</td><td>Sudden latency spikes</td></tr>
<tr>
<td>Failure mode</td><td>“System is slower"</td><td>“System freezes sometimes”</td></tr>
<tr>
<td>Developer trap</td><td>“GC is concurrent”</td><td>“Young GC is cheap”</td></tr>
</tbody>
</table>
</div><p>Both languages suffer - just in different ways.</p>
<h1 id="heading-why-this-matters-architecturally">Why This Matters Architecturally</h1>
<p>This is not a micro-optimization issue. Application patterns come from:</p>
<ul>
<li><p>API design</p>
</li>
<li><p>Data modeling</p>
</li>
<li><p>Serialization choices</p>
</li>
<li><p>Abstraction layers</p>
</li>
</ul>
<p>In Go sloppy allocation patterns show up <strong>early and constantly</strong>.</p>
<p>In Java sloppy allocation patterns show up <strong>late and catastrophically</strong>.</p>
<p>Different runtime philosophy, same root cause.</p>
<h1 id="heading-one-takeaway">One Takeaway</h1>
<blockquote>
<p>GC performance is mostly decided by allocation behavior, not GC algorithms</p>
</blockquote>
<p>If you want predictable systems:</p>
<ul>
<li><p>measure allocation</p>
</li>
<li><p>understand object lifetime</p>
</li>
<li><p>treat memory like a first-class design concern</p>
</li>
</ul>
<p>The rest - GC flags, collectors, tuning - comes after that.</p>
<p>Cheers!</p>
]]></content:encoded></item><item><title><![CDATA[HTTP Server in Go vs Java: The Stuff That Actually Hurts in Production]]></title><description><![CDATA[Building an HTTP server is easy.
Building one that survives real traffic, slow dependencies, deploys, and partial failures is where Go and Java start to diverge in interesting - and sometimes painful - ways.
This article is not about frameworks, anno...]]></description><link>https://blog.skopow.ski/http-server-in-go-vs-java-the-stuff-that-actually-hurts-in-production</link><guid isPermaLink="true">https://blog.skopow.ski/http-server-in-go-vs-java-the-stuff-that-actually-hurts-in-production</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Java]]></category><category><![CDATA[http]]></category><category><![CDATA[server]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 18 Jan 2026 13:18:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/GYZ9F3U1gBk/upload/97af3e0dbf84339f1db46cbc9f2c1521.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Building an HTTP server is easy.</p>
<p>Building one that survives real traffic, slow dependencies, deploys, and partial failures is where Go and Java start to diverge in interesting - and sometimes painful - ways.</p>
<p>This article is not about frameworks, annotations, or syntactic sugar. It’s about defaults, failure modes, and things that break at 2 in the morning.</p>
<h1 id="heading-same-endpoint-same-expectations">Same Endpoint, Same Expectations</h1>
<p>We’ll expose two ednpoints:</p>
<ul>
<li><p><code>GET</code> <code>/health</code></p>
</li>
<li><p><code>GET</code> <code>/api/v1/items/{id}</code></p>
</li>
</ul>
<p>Requirements (non-negotiable in production):</p>
<ul>
<li><p><code>JSON</code> response</p>
</li>
<li><p>request timeouts</p>
</li>
<li><p>graceful shutdown</p>
</li>
<li><p>bounded resource usage</p>
</li>
<li><p>request ID propagation</p>
</li>
</ul>
<h1 id="heading-minimal-http-server">Minimal HTTP Server</h1>
<p>For Go we’re going to use the <code>net/http</code> package (ignore unhandled errors, it’s for the simplicity of the article):</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    mux := http.NewServeMux()

    mux.HandleFunc(<span class="hljs-string">"/health"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, _ *http.Request)</span></span> {
        w.WriteHeader(http.StatusOK)
        w.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">"ok\n"</span>))
    })

    mux.HandleFunc(<span class="hljs-string">"/api/v1/items/{id}"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
        id := r.PathValue(<span class="hljs-string">"id"</span>)
        json.NewEncoder(w).Encode(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>{
            <span class="hljs-string">"id"</span>: id,
        })
    })

    log.Fatal(http.ListenAndServe(<span class="hljs-string">":8080"</span>, mux))
}
</code></pre>
<p>FOr Java we go with the Spring Boot:</p>
<pre><code class="lang-go">@RestController
@RequestMapping(<span class="hljs-string">"/api/v1/items"</span>)
class ItemController {

    @GetMapping(<span class="hljs-string">"/{id}"</span>)
    Map&lt;String, String&gt; getItem(@PathVariable String id) {
        <span class="hljs-keyword">return</span> Map.of(<span class="hljs-string">"id"</span>, id);
    }
}

@RestController
class HealthController {

    @GetMapping(<span class="hljs-string">"/health"</span>)
    String health() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"ok"</span>;
    }
}
</code></pre>
<p>Both work.</p>
<p>Both are dangerous.</p>
<h1 id="heading-the-first-production-lie-defaults-are-fine">The First Production Lie: “defaults are fine”</h1>
<p>They’re not xD</p>
<p>Handle timeouts - the silent killers - first.</p>
<p>Go - explicit and unavoidable:</p>
<pre><code class="lang-go">server := &amp;http.Server{
    Addr:              <span class="hljs-string">":8080"</span>,
    Handler:           mux,
    ReadTimeout:       <span class="hljs-number">5</span> * time.Second,
    ReadHeaderTimeout: <span class="hljs-number">2</span> * time.Second,
    WriteTimeout:      <span class="hljs-number">10</span> * time.Second,
    IdleTimeout:       <span class="hljs-number">60</span> * time.Second,
    MaxHeaderBytes:    <span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">20</span>,
}
</code></pre>
<p>If you don’t set these:</p>
<ul>
<li><p>slow clients can eat your goroutines</p>
</li>
<li><p>you’re vulnerable to trivial DoS attacks</p>
</li>
</ul>
<p>Java - hidden behind layers. In Spring Boot, timeouts live in configuration, not code:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">server:</span>
  <span class="hljs-attr">tomcat:</span>
    <span class="hljs-attr">connection-timeout:</span> <span class="hljs-string">5s</span>
    <span class="hljs-attr">max-connections:</span> <span class="hljs-number">200</span>
    <span class="hljs-attr">threads:</span>
      <span class="hljs-attr">max:</span> <span class="hljs-number">200</span>
</code></pre>
<p>What hurts:</p>
<ul>
<li><p>default vary by container</p>
</li>
<li><p>engineers assume “the framework handles it” (!)</p>
</li>
<li><p>thread pools silently saturate</p>
</li>
</ul>
<h1 id="heading-concurrency-model-goroutines-vs-threads-where-pain-differs">Concurrency Model: Goroutines vs Threads (Where Pain Differs)</h1>
<p>Go:</p>
<ul>
<li><p>one request → one goroutine</p>
</li>
<li><p>cheap, but not free</p>
</li>
<li><p>unlimited concurrency unless limit it</p>
</li>
</ul>
<p>Java:</p>
<ul>
<li><p>one request → one thread (or virtual thread)</p>
</li>
<li><p>thread pools introduce implicit backpressure</p>
</li>
<li><p>starvation is a real failure mode</p>
</li>
</ul>
<p>Worth remembering:</p>
<ul>
<li><p>Go fails by overcommitment</p>
</li>
<li><p>Java fails by starvation</p>
</li>
</ul>
<p>More about the concurrency comparison between both technologies you can find in the previous article - <a target="_blank" href="https://blog.skopow.ski/concurrency-in-go-vs-java">https://blog.skopow.ski/concurrency-in-go-vs-java</a></p>
<h1 id="heading-backpressure-what-happens-when-downstream-is-slow">Backpressure: What Happens When Downstream is Slow?</h1>
<p>This is where production systems die 💀</p>
<p>Go: you must build it yourself</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> sem = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">struct</span>{}, <span class="hljs-number">100</span>) <span class="hljs-comment">// max 100 in-flight requests</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">limit</span><span class="hljs-params">(next http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> {
    <span class="hljs-keyword">return</span> http.HandlerFunc(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
        <span class="hljs-keyword">select</span> {
        <span class="hljs-keyword">case</span> sem &lt;- <span class="hljs-keyword">struct</span>{}{}:
            <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> { &lt;-sem }()
            next.ServeHTTP(w, r)
        <span class="hljs-keyword">default</span>:
            http.Error(w, <span class="hljs-string">"too many requests"</span>, http.StatusTooManyRequests)
        }
    })
}
</code></pre>
<p>What’s good in it: it’s explicit and predictable.</p>
<p>What’ bad: it’s easy to forget and every team reinvents it all over.</p>
<p>In Java it’s more like a backpressure by accident:</p>
<ul>
<li><p>servlet thread pool fills up</p>
</li>
<li><p>requests queue</p>
</li>
<li><p>latency explodes</p>
</li>
<li><p>health checks start timing out</p>
</li>
<li><p>Kubernetes kills the pod 💀</p>
</li>
</ul>
<p>Virtual threads help, but do not fix slow downstreams.</p>
<h1 id="heading-context-propagation-and-cancellation">Context Propagation and Cancellation</h1>
<p>Go: cancellation is contagious (in a good way):</p>
<pre><code class="lang-go">req, err := http.NewRequestWithContext(ctx, <span class="hljs-string">"GET"</span>, url, <span class="hljs-literal">nil</span>)
</code></pre>
<ul>
<li><p>client disconnects → <code>context</code> cancelled</p>
</li>
<li><p>DB calls, HTTP calls, Kafka producers can stop</p>
</li>
<li><p>If you propagate the context correclty</p>
</li>
</ul>
<p>Context are your powerful friend. Use them!</p>
<p>Java: cancellation exists, but is optional:</p>
<ul>
<li><p>request cancellation rarely reaches DB drivers</p>
</li>
<li><p>blocking calls ignore interrupts</p>
</li>
<li><p>vierual threads improve ergonomics, not semantics</p>
</li>
</ul>
<p>Reality:</p>
<ul>
<li><p>Go makes cancellation hard to avoid</p>
</li>
<li><p>Java makes it easy to ignore</p>
</li>
</ul>
<h1 id="heading-graceful-shutdown-where-many-apps-lie-to-kube">Graceful Shutdown (Where Many Apps Lie to Kube)</h1>
<p>Go: explicit and correct by default:</p>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> err := server.ListenAndServe(); err != <span class="hljs-literal">nil</span> &amp;&amp; err != http.ErrServerClosed {
        log.Fatal(err)
    }
}()

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
&lt;-ctx.Done()

shutdownCtx, cancel := context.WithTimeout(context.Background(), <span class="hljs-number">10</span>*time.Second)
<span class="hljs-keyword">defer</span> cancel()
server.Shutdown(shutdownCtx)
</code></pre>
<ul>
<li><p>stop accepting requests</p>
</li>
<li><p>wait for in-flight ones</p>
</li>
<li><p>deterministic</p>
</li>
</ul>
<p>In Java it’s more like a framework magic (until it isn’t).</p>
<p>Spring does graceful shutdown if:</p>
<ul>
<li><p>probes are configured correctly</p>
</li>
<li><p>timeouts match Kubernetes settings</p>
</li>
<li><p>no custom executors block shutdown</p>
</li>
</ul>
<p>When it fails, debugging is painful.</p>
<h1 id="heading-middleware-filters-order-matters-more-than-you-think">Middleware / Filters: Order Matters More Than You Think</h1>
<p>In Go:</p>
<ul>
<li><p>the middleware is explicit</p>
</li>
<li><p>order is visible in code</p>
</li>
<li><p>ugly, but honest</p>
</li>
</ul>
<p>In Java:</p>
<ul>
<li><p>filters, interceptors, aspects</p>
</li>
<li><p>execution order spread across annotations and configs</p>
</li>
<li><p>someone will eventually log <em>before</em> request ID is set</p>
</li>
</ul>
<p>Rule of thumb:<br />If you can’t draw the request flow from memory, it’s already too complex.</p>
<h1 id="heading-observability-what-you-need-on-day-one">Observability: What You Need on Day One</h1>
<p>Both ecosystems need the same signals:</p>
<ul>
<li><p>request count by status</p>
</li>
<li><p>latency percentiles</p>
</li>
<li><p>in-flight requests</p>
</li>
<li><p>downstream dependency latency</p>
</li>
<li><p>structured logs with request ID</p>
</li>
</ul>
<p>Difference:</p>
<ul>
<li><p>Go forces you to add this consciously</p>
</li>
<li><p>Java gives you 80% for free - and hides the remaining 20%</p>
</li>
</ul>
<h1 id="heading-failure-patterns-you-will-see-in-real-life">Failure Patterns You Will See In Real Life</h1>
<p>In Go those are:</p>
<ul>
<li><p>mem spikes due to unbounded concurrency</p>
</li>
<li><p>goroutine leaks from missing context cancellation</p>
</li>
<li><p>accidental DoS due to missing limits</p>
</li>
</ul>
<p>In Java:</p>
<ul>
<li><p>thread pool exhaustion</p>
</li>
<li><p>cascading timeouts</p>
</li>
<li><p>“CPU is low, but latency is insane”</p>
</li>
</ul>
<p>Neither is “better”. They fail differently.</p>
<h1 id="heading-if-you-do-only-one-thing">If You Do Only One Thing</h1>
<p>Those should be your checklists:</p>
<p>Go checklist:</p>
<ul>
<li><p>see all HTTP timeouts</p>
</li>
<li><p>limit in-flight requests</p>
</li>
<li><p>propagate context everywhere</p>
</li>
<li><p>implement graceful shutdown</p>
</li>
<li><p>expose basic metrics</p>
</li>
</ul>
<p>Java checklist:</p>
<ul>
<li><p>cap thread pools intentionally</p>
</li>
<li><p>align server + Kubernetes timeouts</p>
</li>
<li><p>monitor queue length, not just CPU</p>
</li>
<li><p>test shutdown under load</p>
</li>
<li><p>don’t trust defaults blindly</p>
</li>
</ul>
<h1 id="heading-final-thoughts">Final Thoughts</h1>
<p>Go makes danger visible.<br />Java makes danger comfortable.</p>
<p>Both can build excellent HTTP servers - but only if you understand how they fail, not how they start.</p>
<h1 id="heading-sources">Sources</h1>
<p>The complete Go server with all the good practices described today you can find - as always - in the K.I.S.S. repository:</p>
<ul>
<li><a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/http-server-go-v-java">https://github.com/flashlabs/kiss-samples/tree/main/http-server-go-v-java</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Concurrency in Go vs Java]]></title><description><![CDATA[Concurrency is one of those topics that feels solved, until it breaks in production.
Go gives us goroutines and channels. Java gives us executors, futures and virtual threads.
Different tools, different syntax… and the same mistakes.
This article is ...]]></description><link>https://blog.skopow.ski/concurrency-in-go-vs-java</link><guid isPermaLink="true">https://blog.skopow.ski/concurrency-in-go-vs-java</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Java]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sat, 13 Dec 2025 15:23:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/5E5N49RWtbA/upload/69b1247516814f46be57f6223d6ea7b8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Concurrency is one of those topics that feels solved, until it breaks in production.</p>
<p>Go gives us goroutines and channels. Java gives us executors, futures and <a target="_blank" href="https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html">virtual threads</a>.</p>
<p>Different tools, different syntax… and <strong>the same mistakes</strong>.</p>
<p>This article is not about benchmarks or language wars ;-) It’s about the <em>real concurrency</em> bugs in production systems - both in Go and in Java.</p>
<h1 id="heading-concurrency-is-not-parallelism-still">Concurrency is Not Parallelism (Still)</h1>
<p>This mistake never goes away.</p>
<ul>
<li><p>Concurrency is about structuring work.</p>
</li>
<li><p>Parallelism is about executing work at the same time.</p>
</li>
</ul>
<p>We’ve discussed the <a target="_blank" href="https://blog.skopow.ski/concurrency-is-not-parallelism">Concurrency is not Parallelism</a> recently, but - as a reminder - you can write highly concurrent code that:</p>
<ul>
<li><p>runs on a single core</p>
</li>
<li><p>blocks on I/O</p>
</li>
<li><p>performs worse than a sequential version</p>
</li>
</ul>
<p>How this shows up?</p>
<ul>
<li><p>Go: “Goroutines are cheap, so I’ll just spawn one per request”</p>
</li>
<li><p>Java: “Virtual threads are lightweight, so I don’t need to think about limits anymore”</p>
</li>
</ul>
<p>Both are <strong>wrong</strong> for the same reason: <strong>you didn’t analyze the workload.</strong></p>
<p>CPU-bound and I/O-bound workloads behave very differently under concurrency.</p>
<h1 id="heading-fire-and-forget-is-a-production-bug">“Fire and Forget” is a Production Bug</h1>
<p>This is probably the most common concurrency bug</p>
<p>Go:</p>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> processOrder(order)
</code></pre>
<p>Java:</p>
<pre><code class="lang-java">executor.submit(() -&gt; processOrder(order));
</code></pre>
<p>At firsst glance, this looks harmless.</p>
<p>In reality, you just lost:</p>
<ul>
<li><p>lifecycle control</p>
</li>
<li><p>error handling</p>
</li>
<li><p>cancellation</p>
</li>
<li><p>ebservability</p>
</li>
</ul>
<p>What happens in production:</p>
<ul>
<li><p>goroutines / threads keep running afrter the request is gone</p>
</li>
<li><p>errors dissapear into logs (or nowhere)</p>
</li>
<li><p>resource usage slowly climbs until something collapses</p>
</li>
</ul>
<p>Rule of thumb: “If you start concurrent work, you must also define who owns it and who stops it”.</p>
<h1 id="heading-no-backpressure-self-inflicted-dos">No Backpressure = Self-Inflicted DoS</h1>
<p>Go:</p>
<pre><code class="lang-go"><span class="hljs-keyword">for</span> req := <span class="hljs-keyword">range</span> requests {
    <span class="hljs-keyword">go</span> handle(req)
}
</code></pre>
<p>Java:</p>
<pre><code class="lang-java">requests.forEach(req -&gt;
    executor.submit(() -&gt; handle(req))
);
</code></pre>
<p>The bug:</p>
<ul>
<li><p>no limits</p>
</li>
<li><p>no queue size</p>
</li>
<li><p>no load scheduling</p>
</li>
</ul>
<p>The result:</p>
<ul>
<li><p>traffic spike → thread explosion</p>
</li>
<li><p>memory pressure</p>
</li>
<li><p>GC storms (especially in Java)</p>
</li>
<li><p>latency spikes everywhere</p>
</li>
</ul>
<p>Backpressure is not an optimization. It’s a survival mechanism.</p>
<p>Backpressure in Go (conceptually):</p>
<pre><code class="lang-go">workers := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">struct</span>{}, <span class="hljs-number">10</span>)   <span class="hljs-comment">// limit concurrency</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">handle</span><span class="hljs-params">(req Request)</span></span> {
    workers &lt;- <span class="hljs-keyword">struct</span>{}{}            <span class="hljs-comment">// accuire slot</span>
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> { &lt;-workers } ()    <span class="hljs-comment">// release slot</span>

    process(req)
}
</code></pre>
<p>If all slots are taken:</p>
<ul>
<li><p>the caller blocks</p>
</li>
<li><p>work slows down naturally</p>
</li>
<li><p>the system stays stable</p>
</li>
</ul>
<p>The blocking is <strong>backpressure.</strong></p>
<p>Backpressure in Java (conceptually):</p>
<pre><code class="lang-java">ExecutorService executor = 
    <span class="hljs-keyword">new</span> ThreadPoolExecutor(
        <span class="hljs-number">10</span>, <span class="hljs-number">10</span>,
        <span class="hljs-number">0L</span>, TimeUnit.MILLISECONDS,
        <span class="hljs-keyword">new</span> ArrayBlockingQueue&lt;&gt;(<span class="hljs-number">100</span>)
    );
</code></pre>
<p>Here:</p>
<ul>
<li><p>max 10 workers</p>
</li>
<li><p>queue limited to 100 tasks</p>
</li>
<li><p>when full → rejection policy kicks in</p>
</li>
</ul>
<p>The rejection is <strong>backpressure</strong>.</p>
<h1 id="heading-cancellation-is-treated-as-optional-its-not">Cancellation Is Treated as Optional (It’s Not)</h1>
<p>Cancellation is one of those features everyone “supports” and almost no one uses correctly.</p>
<p>Go:</p>
<ul>
<li><p><code>context.Context</code> passed around “just in case”</p>
</li>
<li><p>nobody checks <code>ctx.Done()</code></p>
</li>
</ul>
<p>Java:</p>
<ul>
<li><p><code>InterruptedException</code> ignored</p>
</li>
<li><p>virtual threads assumed to auto-cancel everything</p>
</li>
</ul>
<p>Why this hurts:</p>
<ul>
<li><p>request times out</p>
</li>
<li><p>work continues anyway</p>
</li>
<li><p>side effects happen after the client is gone</p>
</li>
</ul>
<p>Cancellation must be:</p>
<ul>
<li><p>explicit</p>
</li>
<li><p>propagated</p>
</li>
<li><p>actively checked</p>
</li>
</ul>
<p>If cancellation is an afterthought, your system will behave unpredictable under load.</p>
<h1 id="heading-shared-state-different-tools-same-pain">Shared State: Different Tools, Same Pain</h1>
<p>Go and Java take different approach here, but developers still manage to get it wrong.</p>
<p>Go:</p>
<ul>
<li><p>channels used as a magic solution</p>
</li>
<li><p>mutexes added later, without clear ownership</p>
</li>
</ul>
<p>Java:</p>
<ul>
<li><p><code>synchronized</code> everywhere</p>
</li>
<li><p>mutable shared objects crossing thread boundaries</p>
</li>
</ul>
<p>The real problem: Not synchronization. <strong>Ownership</strong>.</p>
<p>If it’s unclear:</p>
<ul>
<li><p>who owns the data</p>
</li>
<li><p>who is allowed to mutate it</p>
</li>
<li><p>when it can be accessed</p>
</li>
</ul>
<p>…then concurrency bugs are inevitable.</p>
<p>Prefer:</p>
<ul>
<li><p>immutable data</p>
</li>
<li><p>clear boundaries</p>
</li>
<li><p>single-writer patterns</p>
</li>
</ul>
<h1 id="heading-blocking-where-it-hurst-most">Blocking Where It Hurst Most</h1>
<p>Blocking is not evil. Blocking <em>in the wrong place</em> is.</p>
<p>Go:</p>
<ul>
<li><p>blocking I/O in unlimited goroutines</p>
</li>
<li><p><code>time.Sleep</code> used for coordination</p>
</li>
</ul>
<p>Java:</p>
<ul>
<li><p>blocking calls inside virtual threads</p>
</li>
<li><p>mixing async and blocking APIs blindly</p>
</li>
</ul>
<p>Symptoms:</p>
<ul>
<li><p>thread pools stuck</p>
</li>
<li><p>request queues growing</p>
</li>
<li><p>sudden latency cliffs</p>
</li>
</ul>
<p>Virtual threads reduce the cost of blocking, but they do not eliminate it.</p>
<h1 id="heading-debugging-concurrency-by-logs">Debugging Concurrency “By Logs”</h1>
<p>Logs don’t explain concurrency issues. They only confirm that something already went wrong.</p>
<p>Common mistakes:</p>
<ul>
<li><p>no metrics</p>
</li>
<li><p>no visibility into queues or workers</p>
</li>
<li><p>debugging via stack traces only</p>
</li>
</ul>
<p>What actually helps:</p>
<ul>
<li><p>Go: <code>pprof</code>, goroutine dumps</p>
</li>
<li><p>Java: JFR, thread dumps</p>
</li>
<li><p>metrics like:</p>
<ul>
<li><p>queue depth</p>
</li>
<li><p>active workers</p>
</li>
<li><p>execution time distribution</p>
</li>
</ul>
</li>
</ul>
<p>If you can’t see concurrency behavior, you can’t fix it.</p>
<h1 id="heading-the-biggest-shared-mistake-thinking-too-low-level">The Biggest Shared Mistake: Thinking Too Low-Level</h1>
<p>Most concurrency bugs don’t come from goroutines or threads.</p>
<p>They come from thinking in terms of <em>how</em> work runs, instead of <em>how work flows.</em></p>
<p>Stop designing systems around:</p>
<ul>
<li><p>goroutines</p>
</li>
<li><p>threads</p>
</li>
<li><p>and executors.</p>
</li>
</ul>
<p>Start designing around:</p>
<ul>
<li><p>data flow</p>
</li>
<li><p>limits</p>
</li>
<li><p>ownership</p>
</li>
<li><p>failure models</p>
</li>
</ul>
<h1 id="heading-design-over-language">Design Over Language</h1>
<p>Go and Java look very different on the surface.</p>
<p>But in production, concurrency failures usually come from the same place: <strong>design, not language.</strong></p>
<ul>
<li><p>Go makes it easy to start concurrent work</p>
</li>
<li><p>Java makes you think harder before you do</p>
</li>
<li><p>neither will save you from bad assumptions</p>
</li>
</ul>
<p>Concurrency is not a feature.</p>
<p>It’s a responsibility.</p>
<p>Cheers!</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>Virtual Threads: <a target="_blank" href="https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html">https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html</a></p>
</li>
<li><p>Concurrency: <a target="_blank" href="https://docs.oracle.com/en/java/javase/21/core/concurrency.html">https://docs.oracle.com/en/java/javase/21/core/concurrency.html</a></p>
</li>
<li><p>No K.I.S.S.! samples for today ¯\_(ツ)_/¯</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Use RERO to Improve Developer Velocity and Feedback Loops]]></title><description><![CDATA[Problem: Slow Releases Kill Momentum
There’s a lot of teams that thinks about themselves as “agile”, they do have JIRA, they do the standups, but if we look closely at their release cycle it says otherwise.
They can plan sprints, push to main and the...]]></description><link>https://blog.skopow.ski/how-to-use-rero-to-improve-developer-velocity-and-feedback-loops</link><guid isPermaLink="true">https://blog.skopow.ski/how-to-use-rero-to-improve-developer-velocity-and-feedback-loops</guid><category><![CDATA[deployment]]></category><category><![CDATA[development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sat, 08 Nov 2025 12:11:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/OHOU-5UVIYQ/upload/f92453adeaa0023bc550609e4e86d45f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-problem-slow-releases-kill-momentum">Problem: Slow Releases Kill Momentum</h1>
<p>There’s a lot of teams that thinks about themselves as “agile”, they do have JIRA, they do the standups, but if we look closely at their release cycle it says otherwise.</p>
<p>They can plan sprints, push to <code>main</code> and then they… wait. They wait for the QA - a week. Then for the UAT testing phase - another week. Then for the Release Window.</p>
<p>By the time the feature hits production, everyone forgot <em>why</em> it was built.</p>
<p>Slow releases kill developer momentum and delay the only thing that matters - feedback from real users.</p>
<h1 id="heading-what-rero-really-means">What RERO Really Means</h1>
<p>RERO - Realease Early, Release Often - isn’t about deploying half-baked code. It’s abt shortening the feedback loop between code and reality.</p>
<ul>
<li><p>Release Early → push features to production as soon as they’re functional, not perfect</p>
</li>
<li><p>Realease Often → make deployment a non-event; something you do daily, not quarterly (or monthly)</p>
</li>
</ul>
<p>The goal isn’t speed for the sake of speed.</p>
<p>The goal is <strong>learning faster</strong> than your competition.</p>
<h1 id="heading-why-it-works">Why It Works</h1>
<p>Every release is a feedback opportunity. EVery deployment tests your delivery pipeline, your observability, and your assumptions.</p>
<p>The more often you release:</p>
<ul>
<li><p>the smaller your changes are → they’re easier to debug (this is uber important part!)</p>
</li>
<li><p>the faster you catch the regression (if any) → you lower the risk</p>
</li>
<li><p>the quicker you learn what users actually want → profit (better ROI)</p>
</li>
</ul>
<p>RERO shifts focus from “perfect features” to conrtinuos improvement.</p>
<h1 id="heading-how-to-apply-rero-in-practice">How to Apply RERO in Practice</h1>
<ol>
<li><p>Define What “Release Ready” Means:</p>
<ul>
<li><p>Agree as a team what “good enough for release” means</p>
</li>
<li><p>Use your <a target="_blank" href="https://blog.skopow.ski/definition-of-ready-vs-definition-of-done">Definition of Done</a> as the gate - not “it feels ready” (lol):</p>
<ul>
<li><p>code reviewed</p>
</li>
<li><p>tests passing (CI)</p>
</li>
<li><p>feature flag enabled</p>
</li>
<li><p>rollback plan ready</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Use Feature Flags:</p>
<ul>
<li><p>Don’t wait until a feature is “fully complete”</p>
</li>
<li><p>Wrap it in a flag and ship it dark:</p>
<ul>
<li><pre><code class="lang-java">  <span class="hljs-keyword">if</span> feature.IsEnabled(<span class="hljs-string">"new_flow"</span>) {
      newFlow();
  } <span class="hljs-keyword">else</span> {
      oldFlow();
  }
</code></pre>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Automate Everything:</p>
<ul>
<li><p>if your deployment requires a 10-step manual guid, you’ll never release often</p>
</li>
<li><p>Automate GH Actions, GL CI or wahtever you’re using - the key is zero (or minimal) friction deploys</p>
</li>
</ul>
</li>
<li><p>Release Small, Release Confident:</p>
<ul>
<li><p><a target="_blank" href="https://trunkbaseddevelopment.com/">Trunk based</a> development helps</p>
</li>
<li><p>No more long-lived branches that rot for weeks - merge daily</p>
</li>
<li><p>Small commits = fast reviews = fewer conflicts = faster learning</p>
</li>
</ul>
</li>
<li><p>Monitor and Rollback</p>
<ul>
<li><p>RERO without monitoring is chaos</p>
</li>
<li><p>Make sure you can:</p>
<ul>
<li><p>detect failures quickly (metrics, logs, alerts)</p>
</li>
<li><p>rollback instantly (<a target="_blank" href="https://en.wikipedia.org/wiki/Blue%E2%80%93green_deployment">blue/green</a> or versioned deploys)</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1 id="heading-common-pitfalls-and-how-to-avoid-them">Common Pitfalls and How To Avoid Them</h1>
<ol>
<li><p>“We’ll release when QA approves” → Integrate QA into the pipeline. Use automated regression tests.</p>
</li>
<li><p>“Our users can’t handle frequent updates” → Use progressive rollouts: 5%, 25%, 100% (<a target="_blank" href="https://martinfowler.com/bliki/CanaryRelease.html">canary releases</a>)</p>
</li>
<li><p>“We’re afraid of breaking production” → Build trust in rollback and observability.</p>
</li>
<li><p>“We don’t have time for this” → You don’t have time <em>not to</em>. Every delay costs learning.</p>
</li>
</ol>
<p>It’s not just about speed - it’s about <strong>smoother flow</strong>. Velocity isn’t measured by lines of code - it’s measured by how fast you learn and adapt.</p>
<h1 id="heading-final-thoughts">Final Thoughts</h1>
<p>“Release Early, Release Often” is more than a slogan - it’s a mindset shift. It replaces fear with feedback and perfectionism with progress.</p>
<p>The faster you ship, the faster you learn. The faster you learn, the better you build.</p>
<p>Cheers!</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>RERO: <a target="_blank" href="https://en.wikipedia.org/wiki/Release_early,_release_often">https://en.wikipedia.org/wiki/Release_early,_release_often</a></p>
</li>
<li><p>Trunk Based Development: <a target="_blank" href="https://trunkbaseddevelopment.com/">https://trunkbaseddevelopment.com/</a></p>
</li>
<li><p>B/G deployment: <a target="_blank" href="https://en.wikipedia.org/wiki/Blue%E2%80%93green_deployment">https://en.wikipedia.org/wiki/Blue%E2%80%93green_deployment</a></p>
</li>
<li><p>Canary Release: <a target="_blank" href="https://martinfowler.com/bliki/CanaryRelease.html">https://martinfowler.com/bliki/CanaryRelease.html</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Concurrency is not Parallelism]]></title><description><![CDATA[We - as a developers - love to talk about “running things at once” - but not everyone means the same thing. Some chase speed, others chase structure, and that’s where the confusion starts.
Parallelism makes it faster. Concurrency makest it work.

Par...]]></description><link>https://blog.skopow.ski/concurrency-is-not-parallelism</link><guid isPermaLink="true">https://blog.skopow.ski/concurrency-is-not-parallelism</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[parallelism]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 26 Oct 2025 12:28:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/HKW_d22rBuc/upload/449a3bc92a433fc207990e1542affb81.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We - as a developers - love to talk about “running things at once” - but not everyone means the same thing. Some chase speed, others chase structure, and that’s where the confusion starts.</p>
<p>Parallelism makes it faster. Concurrency makest it work.</p>
<ul>
<li><p><strong>Parallelism</strong> is about using more cores to finish tasks sooner.</p>
</li>
<li><p><strong>Concurrency</strong> is about organizing your code so multiple things <em>can</em> happen - even if they don’t all run at the same time.</p>
</li>
</ul>
<p>If you’ve ever built an API that juggles thousands of requests or a background worker that consumes messages non-stop, you’ve touched the <strong>concurrency</strong>. And if you’ve tried to squeeze evey ms out of CPU-bound code - that’s <strong>parallelism</strong>.</p>
<h1 id="heading-quick-defs-practical-not-academic">Quick Defs (Practical, Not Academic)</h1>
<p>This always confuses a lot of engineers.</p>
<p>The simplest way to explain the difference is to imagine yourself writing both hands at once on a different pages - this is <strong>parallelism.</strong></p>
<p>When you write only one hand and swap pages - this is a <strong>concurrency</strong>.</p>
<p>Easy, right?</p>
<ul>
<li><p><strong>Parallelism</strong> = true simultaneity / executing multiple things physically at once</p>
</li>
<li><p><strong>Concurrency</strong> = multitasking / managing multiple things</p>
</li>
</ul>
<h1 id="heading-what-actually-limits-us">What Actually Limits Us</h1>
<ul>
<li><p>CPU cores → true parallel speedup</p>
</li>
<li><p>Blocking I/O → here’s where the concurrency shines</p>
</li>
<li><p>Context switching cost → too many “workers” means overhead</p>
</li>
<li><p>Coordination cost → locks, queues, channels can bottleneck</p>
</li>
</ul>
<h1 id="heading-use-cases">Use Cases</h1>
<p>Based on the typical workload usages:</p>
<ul>
<li><p>Thousands outbound HTTP calls:</p>
<ul>
<li><p>We want high <strong>throughput</strong>, acceptable <strong>latency</strong></p>
</li>
<li><p>Concurrency overlaps the waiting time on sockets</p>
</li>
</ul>
</li>
<li><p>Message consumers (Kafka, Rabbit):</p>
<ul>
<li><p>We want steady <strong>throughput</strong></p>
</li>
<li><p>Concurrency scale workers while controlling commit/ack operations</p>
</li>
</ul>
</li>
<li><p>CPU-bound (image resize, JSON schema validation or huge payloads):</p>
<ul>
<li><p>We want raw <strong>parallel</strong> speedup</p>
</li>
<li><p>Concurrency alone won’t help, we need more cores</p>
</li>
</ul>
</li>
</ul>
<h1 id="heading-misconceptions">Misconceptions</h1>
<blockquote>
<p>Nine women won’t give a birth to a child in a month</p>
</blockquote>
<ul>
<li><p>“More threads/goroutines → faster” - not if you’re CPU-bound or thrashing the scheduler</p>
</li>
<li><p>“Async always beats sync” - async done poorly adds latency and complexity</p>
</li>
<li><p>“Locks are bad, channels are good” - both are tools, use them wisely otherwise you end up with deadlock or stall</p>
</li>
</ul>
<h1 id="heading-a-tiny-thought-experiment">A Tiny Thought Experiment</h1>
<p>We must call 1000 slow 3rd party APIs (avg 200 ms).</p>
<ul>
<li><p>Serial is about 200 s wall-clock</p>
</li>
<li><p>Concurrent (well-tuned pool) is close to the <em>slowest</em> batch (~200 - 400 ms), plus overhead and rate-limits</p>
</li>
</ul>
<p>The win comes from <strong>overlapping waits</strong>, not raw CPU.</p>
<h1 id="heading-write-for-concurrency-optimize-for-parallelism">Write for Concurrency, Optimize for Parallelism</h1>
<p>It’s easy to mix’em up, but there’s the practical takeway:</p>
<ul>
<li><p><strong>Concurrency</strong> is how you structure your program to handle multiple things at once</p>
</li>
<li><p><strong>Parallelism</strong> is how the hardware executes them faster</p>
</li>
<li><p>You can have one w/o the other - and most of the time, you start with concurrency and <em>earn</em> parallelism later</p>
</li>
</ul>
<p>When you write Go code, every go <code>func()</code> you spawn is a bet that your runtime will handle the juggling well.</p>
<p>When you write Java code, every thread pool or async executor is your manual way of telling the system how much to juggle.</p>
<p>But neither concurrency nor parallelism is a silver bullet. They won’t fix slow algo or bad I/O patterns - they’ll just help you manage <em>how</em> the slowness happens.</p>
<p>So, before adding more threads, goroutines, or async magic, ask one simple question:</p>
<blockquote>
<p><em>Am I trying to make this faster, or just make it work better?</em></p>
</blockquote>
<p>Knowing that difference is the real skill.</p>
<p>Cheers!</p>
]]></content:encoded></item><item><title><![CDATA[gofmt & goimports - Small Tools, Big Difference]]></title><description><![CDATA[If you’re in Go more than hour, you’ve probably noticed the mention about the gofmt. It’s one of those tools that cleans up your mess - without a redundant style discussion.
Next to it there’s a little bit more clever sibling - goimports. At first gl...]]></description><link>https://blog.skopow.ski/gofmt-and-goimports-small-tools-big-difference</link><guid isPermaLink="true">https://blog.skopow.ski/gofmt-and-goimports-small-tools-big-difference</guid><category><![CDATA[golang]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Fri, 26 Sep 2025 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/nvzvOPQW0gc/upload/ebcd128d6311cda3d6a9c0d9d9641445.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you’re in Go more than hour, you’ve probably noticed the mention about the <code>gofmt</code>. It’s one of those tools that cleans up your mess - without a redundant style discussion.</p>
<p>Next to it there’s a little bit more clever sibling - <code>goimports</code>. At first glance it’s doing the same, but in practice it’s saving you a lot of time and frustration.</p>
<h1 id="heading-gofmt-the-classic-that-keeps-things-tidy">gofmt - The Classic That Keeps Things Tidy</h1>
<p><code>gofmt</code> it’s an official Go tool for the code formatting according to the language’s standards.</p>
<p>There’s no configuration, there’s no <code>Google</code> or <code>Uber</code> style, there’s just a Go <em>style.</em></p>
<pre><code class="lang-bash"><span class="hljs-comment"># One file formatting</span>
gofmt -w main.go

<span class="hljs-comment"># Directory formatting (recursively)</span>
gofmt -w .
</code></pre>
<p>The <code>-w</code> flag means that the file will be overwritten with the <code>gofmt</code>'s version if its styling is different.</p>
<p>To see the list of files that do not follow fhe <code>gofmt</code>'s formatting you use the <code>-l</code> flag:</p>
<pre><code class="lang-bash">gofmt -l .
</code></pre>
<h1 id="heading-goimports-formatting-imports-management">goimports - Formatting + Imports Management</h1>
<p><code>goimports</code> is a tool from the <code>golang.org/x/tools/cmd/goimports</code> package that can do everything what <code>gofmt</code> does and as a bonus <strong>automatically manages package imports</strong>.</p>
<blockquote>
<p>Command goimports updates your Go import lines, adding missing ones and removing unreferenced ones.</p>
<p>Source: <a target="_blank" href="https://pkg.go.dev/golang.org/x/tools/cmd/goimports">https://pkg.go.dev/golang.org/x/tools/cmd/goimports</a></p>
</blockquote>
<p>Installation:</p>
<pre><code class="lang-bash">go install golang.org/x/tools/cmd/goimports@latest
</code></pre>
<p>Usage:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Same as in the gofmt -&gt; overwrite files with formatted version</span>
goimports -w .

<span class="hljs-comment"># List files whose formatting differs from goimport's</span>
goimports -l .
</code></pre>
<p>What’s the difference from <code>gofmt</code>?</p>
<ul>
<li><p>Will add the missing imports, f.e. if you used the <code>fmt.Println</code> but forgot to import the <code>fmt</code> package</p>
</li>
<li><p>Will remove the unused imports automatically</p>
</li>
</ul>
<p>Example:</p>
<p>Before:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"os"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    fmt.Println(<span class="hljs-string">"Keep Is Simple, Stupid!"</span>)
}
</code></pre>
<p>After:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    fmt.Println(<span class="hljs-string">"Keep Is Simple, Stupid!"</span>)
}
</code></pre>
<h1 id="heading-how-to-use-them-on-a-daily-basis">How to Use Them on a Daily Basis</h1>
<p>The best tool is a one that you don’t have to remember about.</p>
<p>You can set up them in the IDE or CI, so they’re executed automatically.</p>
<p>My JetBrains GoLand’s code style is as follows:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759575884159/8e925a87-20e6-4ae0-80a0-8407b2320776.png" alt class="image--center mx-auto" /></p>
<p>For the VS Code it should look like this:</p>
<p>The <code>settings.json</code> file:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"go.formatTool"</span>: <span class="hljs-string">"goimports"</span>,
  <span class="hljs-attr">"editor.formatOnSave"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<p>For the GitHub’s CI / GitHub Actions:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">goimports</span>
  <span class="hljs-attr">run:</span> <span class="hljs-string">go</span> <span class="hljs-string">install</span> <span class="hljs-string">golang.org/x/tools/cmd/goimports@latest</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Verify</span> <span class="hljs-string">formatting</span> <span class="hljs-string">&amp;</span> <span class="hljs-string">imports</span>
  <span class="hljs-attr">run:</span> <span class="hljs-string">test</span> <span class="hljs-string">-z</span> <span class="hljs-string">"$(goimports -l .)"</span>
</code></pre>
<p>If you have to choose one, use <code>goimports</code>. It's like <code>gofmt</code> on steroids, and it also saves you a few <em>seconds</em> per file.</p>
<p>Finally, <em>Keep It Simple, Stupid!</em></p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>gofmt: <a target="_blank" href="https://pkg.go.dev/cmd/gofmt">https://pkg.go.dev/cmd/gofmt</a></p>
</li>
<li><p>goimports: <a target="_blank" href="https://pkg.go.dev/golang.org/x/tools/cmd/goimports">https://pkg.go.dev/golang.org/x/tools/cmd/goimports</a></p>
</li>
<li><p>Go tools: <a target="_blank" href="https://cs.opensource.google/go/x/tools">https://cs.opensource.google/go/x/tools</a></p>
</li>
<li><p>go fmt your code: <a target="_blank" href="https://go.dev/blog/gofmt">https://go.dev/blog/gofmt</a></p>
</li>
<li><p>The Go Programming Language: <a target="_blank" href="https://cs.opensource.google/go/go">https://cs.opensource.google/go/go</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[MCP Server in Golang Providing Data for Amazon Q in JetBrains IDE]]></title><description><![CDATA[Today we’re going to build an MCP server in Golang that will provide a Wikipedia Search-like tool, that can be then used as an MCP server in the Amazon Q plugin that resides in the JetBrains IDE.
The MCP tool will be working in the STDIO mode and can...]]></description><link>https://blog.skopow.ski/mcp-server-in-golang-providing-data-for-amazon-q-in-jetbrains-ide</link><guid isPermaLink="true">https://blog.skopow.ski/mcp-server-in-golang-providing-data-for-amazon-q-in-jetbrains-ide</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[mcp]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[server]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 24 Aug 2025 15:39:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/RTBGLx0K1Ns/upload/56ef8be27839fe060f79c588e3c54947.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today we’re going to build an MCP server in Golang that will provide a Wikipedia Search-like tool, that can be then used as an MCP server in the Amazon Q plugin that resides in the JetBrains IDE.</p>
<p>The MCP tool will be working in the STDIO mode and can be added to any other LLM tool that is supporting the MCP standard. We’re going to use the Goland as an example.</p>
<h1 id="heading-what-is-the-model-context-protocol-mcp">What is the Model Context Protocol (MCP)</h1>
<blockquote>
<p>The <strong>Model Context Protocol</strong> (<strong>MCP</strong>) is an <a target="_blank" href="https://en.wikipedia.org/wiki/Open_standard">open standard</a>, <a target="_blank" href="https://en.wikipedia.org/wiki/Open-source">open-source</a> <a target="_blank" href="https://en.wikipedia.org/wiki/Software_framework">framework</a> introduced by <a target="_blank" href="https://en.wikipedia.org/wiki/Anthropic">Anthropic</a> in November 2024 to standardize the way <a target="_blank" href="https://en.wikipedia.org/wiki/Artificial_intelligence">artificial intelligence</a> (AI) systems like <a target="_blank" href="https://en.wikipedia.org/wiki/Large_language_model">large language models</a> (LLMs) integrate and share data with external tools, systems, and data sources.<a target="_blank" href="https://en.wikipedia.org/wiki/Model_Context_Protocol#cite_note-venturebeat-2024-11-25-1"><sup>[1]</sup></a> MCP provides a universal interface for reading files, executing functions, and handling contextual prompts.<a target="_blank" href="https://en.wikipedia.org/wiki/Model_Context_Protocol#cite_note-venturebeat_2025-03-27-2"><sup>[2]</sup></a> Following its announcement, the protocol was adopted by major AI providers, including <a target="_blank" href="https://en.wikipedia.org/wiki/OpenAI">OpenAI</a> and <a target="_blank" href="https://en.wikipedia.org/wiki/Google_DeepMind">Google DeepMind</a>.<a target="_blank" href="https://en.wikipedia.org/wiki/Model_Context_Protocol#cite_note-TechCrunch_2025-03-25-3"><sup>[3]</sup></a><a target="_blank" href="https://en.wikipedia.org/wiki/Model_Context_Protocol#cite_note-TechCrunch_2025-04-09-4"><sup>[4]</sup></a></p>
</blockquote>
<p>Source: <a target="_blank" href="https://en.wikipedia.org/wiki/Model_Context_Protocol">https://en.wikipedia.org/wiki/Model_Context_Protocol</a></p>
<p>In other other words, it’s a “common language” that let’s AI models talk to tools, data and apps.</p>
<h1 id="heading-building-the-mcp-server">Building the MCP Server</h1>
<p>For this article I’m going to use the community library <a target="_blank" href="https://github.com/mark3labs/mcp-go">mark3labs/mcp-go</a> that fully supports the <a target="_blank" href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP standard</a>.</p>
<p>You have to be aware there’s also an official <a target="_blank" href="https://github.com/modelcontextprotocol/go-sdk">Go SDK for the MCP</a> which is less stable, but provides more strict approach, however requires more bolierplate to kickstart the project.</p>
<p>Okay, straight to the point.</p>
<p>Let’s install the package:</p>
<pre><code class="lang-bash">go get github.com/mark3labs/mcp-go
</code></pre>
<p>And head straight to setting up the MCP server:</p>
<pre><code class="lang-go">s := server.NewMCPServer(
        <span class="hljs-string">"Wikipedia Search"</span>,
        <span class="hljs-string">"v1.0.0"</span>,
        server.WithToolCapabilities(<span class="hljs-literal">false</span>),
        server.WithRecovery(),
    )
</code></pre>
<p>Then the tool definition. This is the part that is being exposed as an “interface” to the “model” client:</p>
<pre><code class="lang-go">wikiTool := mcp.NewTool(
        <span class="hljs-string">"wikipedia_search"</span>,
        mcp.WithDescription(<span class="hljs-string">"Search Wikipedia for a query and return the first paragraph"</span>),
        mcp.WithString(<span class="hljs-string">"query"</span>, mcp.Required(), mcp.Description(<span class="hljs-string">"Search term"</span>)),
    )
</code></pre>
<p>Now, let’s connect the server and the tool definition:</p>
<pre><code class="lang-go">s.AddTool(wikiTool, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(ctx context.Context, req mcp.CallToolRequest)</span> <span class="hljs-params">(*mcp.CallToolResult, error)</span></span> {
        term, err := req.RequireString(<span class="hljs-string">"query"</span>)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to get query: %w"</span>, err)
        }

        u := fmt.Sprintf(<span class="hljs-string">"https://en.wikipedia.org/api/rest_v1/page/summary/%s"</span>, url.PathEscape(term))

        ctx, cancel := context.WithTimeout(ctx, <span class="hljs-number">10</span>*time.Second)
        <span class="hljs-keyword">defer</span> cancel()

        httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, u, <span class="hljs-literal">nil</span>)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to create request: %w"</span>, err)
        }
        httpReq.Header.Set(<span class="hljs-string">"User-Agent"</span>, <span class="hljs-string">"Wikipedia-MCP-Server/1.0"</span>)

        r, err := http.DefaultClient.Do(httpReq)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to get wikipedia page: %w"</span>, err)
        }
        <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">if</span> closeErr := r.Body.Close(); closeErr != <span class="hljs-literal">nil</span> {
                fmt.Printf(<span class="hljs-string">"Warning: failed to close response body: %v\n"</span>, closeErr)
            }
        }()

        <span class="hljs-keyword">if</span> r.StatusCode != http.StatusOK {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to get wikipedia page: %s"</span>, r.Status)
        }

        body, err := io.ReadAll(io.LimitReader(r.Body, <span class="hljs-number">1024</span>*<span class="hljs-number">1024</span>))
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to read response body: %w"</span>, err)
        }

        <span class="hljs-keyword">var</span> summary <span class="hljs-keyword">struct</span> {
            Extract <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"extract"`</span>
        }
        <span class="hljs-keyword">if</span> err := json.Unmarshal(body, &amp;summary); err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to parse response: %w"</span>, err)
        }

        <span class="hljs-keyword">if</span> summary.Extract == <span class="hljs-string">""</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"no summary found for '%s'"</span>, term)
        }

        <span class="hljs-keyword">return</span> mcp.NewToolResultText(summary.Extract), <span class="hljs-literal">nil</span>
    })
</code></pre>
<p>Our tool is calling the Wiki API and parsing the <code>summary</code> for the given query. It’s limited to process only 1MB of data and has a timeout of 10 seconds, to not get locked if the network issues will occur.</p>
<p>The last thing required is to run the MCP server:</p>
<pre><code class="lang-go"><span class="hljs-keyword">if</span> err := server.ServeStdio(s); err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Server error: %v\n"</span>, err)
    }
</code></pre>
<h1 id="heading-compiling-the-mcp-server">Compiling the MCP Server</h1>
<p>To build a real binary application, that can be run in the background an serve as an MCP server, you need to:</p>
<pre><code class="lang-bash">go build -o mcp-wiki-tool
</code></pre>
<p>And that’s it. When the model will need it it will run it by itself.</p>
<h1 id="heading-connecting-to-amazon-q">Connecting to Amazon Q</h1>
<p>In your JetBrains IDE, open Amazon Q plugin and select the followings:</p>
<ol>
<li>Configure MCP servers:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756046315457/2823e8a4-fc8b-4355-972c-23751f1f79ef.png" alt class="image--center mx-auto" /></p>
<ol start="2">
<li>Add new MCP server:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756046290612/ef1ef712-f377-4084-a47d-f63c7683c3a0.png" alt class="image--center mx-auto" /></p>
<ol start="3">
<li>Provide the details about our newly created MCP server:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756046132649/0b04a8d5-b9e4-447f-840f-5e7ec9dad85d.png" alt class="image--center mx-auto" /></p>
<p>Click “Save” and you’re good to go.</p>
<p>On your MCP servers list you should see “WikipediaSearch”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756048187814/2b1b3ad2-2ce7-4cdd-9a8a-7063297ad942.png" alt class="image--center mx-auto" /></p>
<p>Now, anytime you ask anything related to the Wikipedia or Amazon Q decides it needs a data from the Wikipedia it will use your model.</p>
<p>You can configure the Amazon Q how it should behave when using your new tool:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756048289200/fd6e67e4-c3aa-4681-975d-f05c008dbcc6.png" alt class="image--center mx-auto" /></p>
<p>You can approve the usage of the tool each time the Amazon Q needs it or just allow it to be used in the background seamlessly.</p>
<p>Our MCP server in action:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756048577722/ea68cba8-a26a-4870-a2c0-f37a0eb7f860.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756048612728/3a4e6a78-5765-4998-9817-1c967e5b5465.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-expanding-with-more-useful-tools">Expanding with More Useful Tools</h1>
<p>As you can see, the capabilities of the MCP servers are limitless. You can expand your model’s context in a way you want and access tools that aren’t publicly available on the internet.</p>
<p>The MCP is for the models the same what the HTTP was for the Internet. It standardizes the information exchange format.</p>
<p>Please keep in mind that MCP servers are not only for the read-only mode. You can expose endpoints that will be modyfing state of your application, like <a target="_blank" href="https://support.atlassian.com/rovo/docs/getting-started-with-the-atlassian-remote-mcp-server/">“attlasian” MCP server</a> allows to manage Jira on your behalf, manage tickets, stories and everything related.</p>
<h1 id="heading-sources">Sources</h1>
<p>As always the full source code used for this article you can find on my GitHub: <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/mcp">https://github.com/flashlabs/kiss-samples/tree/main/mcp</a></p>
<ul>
<li><p>MCP GO SDK: <a target="_blank" href="https://github.com/mark3labs/mcp-go">https://github.com/mark3labs/mcp-go</a></p>
</li>
<li><p>Official MCP GO SDK: <a target="_blank" href="https://github.com/modelcontextprotocol/go-sdk">https://github.com/modelcontextprotocol/go-sdk</a></p>
</li>
<li><p>MCP Wiki: <a target="_blank" href="https://en.wikipedia.org/wiki/Model_Context_Protocol">https://en.wikipedia.org/wiki/Model_Context_Protocol</a></p>
</li>
<li><p>MCP Official Website: <a target="_blank" href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io/</a></p>
</li>
<li><p>MCP Official GH: <a target="_blank" href="https://github.com/modelcontextprotocol">https://github.com/modelcontextprotocol</a></p>
</li>
<li><p>Article’s Golang Repository: <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/mcp">https://github.com/flashlabs/kiss-samples/tree/main/mcp</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Run YOLOv8 Inference Directly in Golang (with ONNX)]]></title><description><![CDATA[This is a focused how-to article, I assume you already know what YOLO is and have a basic Golang knowledge.
Notes:

the inference example is based on the yalue examples

the code used in this article is adapted to the MacOS with the ARM architecture,...]]></description><link>https://blog.skopow.ski/how-to-run-yolov8-inference-directly-in-golang-with-onnx</link><guid isPermaLink="true">https://blog.skopow.ski/how-to-run-yolov8-inference-directly-in-golang-with-onnx</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Go Language]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 27 Jul 2025 11:28:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/10eRYKGiSZo/upload/d300ef19495f3b0daf9889491ddaa444.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a focused how-to article, I assume you already know what <code>YOLO</code> is and have a basic Golang knowledge.</p>
<p>Notes:</p>
<ul>
<li><p>the inference example is based on the <a target="_blank" href="https://github.com/yalue/onnxruntime_go_examples/tree/master">yalue examples</a></p>
</li>
<li><p>the code used in this article is adapted to the <code>MacOS</code> with the <code>ARM</code> architecture, for other <code>OS</code>es and architecures, you will need <a target="_blank" href="https://github.com/yalue/onnxruntime_go_examples/tree/master/third_party">libs available here</a> and an example on how to use them <a target="_blank" href="https://github.com/yalue/onnxruntime_go_examples/blob/master/image_object_detect/image_object_detect.go">is located here</a></p>
</li>
</ul>
<h1 id="heading-step-1-convert-yolo-to-onnx">Step 1: Convert YOLO to ONNX</h1>
<p>First we need to convert the <code>YOLOv8</code> model to the <code>ONNX</code> format. To do this we’ll install the <code>ultralytics</code> package with <code>pip</code> and use the <code>yolo export</code> command.</p>
<p>The image size here - <code>640</code> - is important, it has to be the same size that we’ll use later in our code.</p>
<pre><code class="lang-bash">mkdir yolov8 &amp;&amp; <span class="hljs-built_in">cd</span> yolov8

<span class="hljs-comment"># Create and enable virtual env.</span>
python3.10 -m venv env
<span class="hljs-built_in">source</span> env/bin/activate

pip install ultralytics

yolo <span class="hljs-built_in">export</span> model=yolov8n.pt format=onnx imgsz=640

<span class="hljs-comment"># Quit virtual env.</span>
deactivate
</code></pre>
<p>The ouput should be similar to:</p>
<pre><code class="lang-bash">(...)
ONNX: starting <span class="hljs-built_in">export</span> with onnx 1.17.0 opset 17...
ONNX: slimming with onnxslim 0.1.61...
ONNX: <span class="hljs-built_in">export</span> success ✅ 19.8s, saved as <span class="hljs-string">'yolov8n.onnx'</span> (12.2 MB)

Export complete (20.5s)
Results saved to /(...)/yolov8
Predict:         yolo predict task=detect model=yolov8n.onnx imgsz=640  
Validate:        yolo val task=detect model=yolov8n.onnx imgsz=640 data=coco.yaml  
Visualize:       https://netron.app
💡 Learn more at https://docs.ultralytics.com/modes/<span class="hljs-built_in">export</span>
</code></pre>
<p>This will create a <code>yolov8n.onnx</code> file with the <code>onnx</code> <code>YOLOv8</code> model. Please be sure to copy the model to your directory.</p>
<p>I’ve already included a converted model file in the article full code example.</p>
<h1 id="heading-step-2-load-image-file">Step 2: Load Image File</h1>
<pre><code class="lang-go">pic, e := loadImageFile(imagePath)
<span class="hljs-keyword">if</span> e != <span class="hljs-literal">nil</span> {
    fmt.Printf(<span class="hljs-string">"error loading input image: %s\n"</span>, e)

    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
}

(...)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadImageFile</span><span class="hljs-params">(filePath <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(image.Image, error)</span></span> {
    f, e := os.Open(filePath)

    <span class="hljs-keyword">if</span> e != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"error opening %s: %w"</span>, filePath, e)
    }
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(f *os.File)</span></span> {
        err := f.Close()
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            fmt.Printf(<span class="hljs-string">"error closing %s: %v\n"</span>, filePath, err)
        }
    }(f)

    pic, _, e := image.Decode(f)
    <span class="hljs-keyword">if</span> e != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"error decoding %s: %w"</span>, filePath, e)
    }

    <span class="hljs-keyword">return</span> pic, <span class="hljs-literal">nil</span>
}
</code></pre>
<h1 id="heading-step-3-init-onnx-session">Step 3: Init ONNX Session</h1>
<pre><code class="lang-go">modelSession, e := initSession()
<span class="hljs-keyword">if</span> e != <span class="hljs-literal">nil</span> {
    fmt.Printf(<span class="hljs-string">"Error creating session and tensors: %s\n"</span>, e)

    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
}
<span class="hljs-keyword">defer</span> modelSession.Destroy()

(...)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">initSession</span><span class="hljs-params">()</span> <span class="hljs-params">(*ModelSession, error)</span></span> {
    ort.SetSharedLibraryPath(sharedLibPath)

    err := ort.InitializeEnvironment()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"error initializing ORT environment: %w"</span>, err)
    }

    inputShape := ort.NewShape(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">640</span>, <span class="hljs-number">640</span>)

    inputTensor, err := ort.NewEmptyTensor[<span class="hljs-keyword">float32</span>](inputShape)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"error creating input tensor: %w"</span>, err)
    }

    outputShape := ort.NewShape(<span class="hljs-number">1</span>, <span class="hljs-number">84</span>, <span class="hljs-number">8400</span>)

    outputTensor, err := ort.NewEmptyTensor[<span class="hljs-keyword">float32</span>](outputShape)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        inputTensor.Destroy()
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"error creating output tensor: %w"</span>, err)
    }

    options, err := ort.NewSessionOptions()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        inputTensor.Destroy()
        outputTensor.Destroy()
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"error creating ORT session options: %w"</span>, err)
    }
    <span class="hljs-keyword">defer</span> options.Destroy()

    session, err := ort.NewAdvancedSession(modelPath,
        []<span class="hljs-keyword">string</span>{<span class="hljs-string">"images"</span>}, []<span class="hljs-keyword">string</span>{<span class="hljs-string">"output0"</span>},
        []ort.ArbitraryTensor{inputTensor},
        []ort.ArbitraryTensor{outputTensor},
        options)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        inputTensor.Destroy()
        outputTensor.Destroy()
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"error creating ORT session: %w"</span>, err)
    }

    <span class="hljs-keyword">return</span> &amp;ModelSession{
        Session: session,
        Input:   inputTensor,
        Output:  outputTensor,
    }, <span class="hljs-literal">nil</span>
}
</code></pre>
<h1 id="heading-step-4-prepare-input">Step 4: Prepare Input</h1>
<p>This is where we need to use the data from the image and fill the <code>YOLO</code> input tensor with it:</p>
<pre><code class="lang-go">e = prepareInput(pic, modelSession.Input)
<span class="hljs-keyword">if</span> e != <span class="hljs-literal">nil</span> {
    fmt.Printf(<span class="hljs-string">"Error converting image to network input: %s\n"</span>, e)

    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
}

(...)

<span class="hljs-comment">// Populates a YOLOv8n input tensor with the contents of the given image.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">prepareInput</span><span class="hljs-params">(pic image.Image, dst *ort.Tensor[<span class="hljs-keyword">float32</span>])</span> <span class="hljs-title">error</span></span> {
    data := dst.GetData()
    channelSize := <span class="hljs-number">640</span> * <span class="hljs-number">640</span>
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(data) &lt; (channelSize * <span class="hljs-number">3</span>) {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"destination tensor only holds %d floats, needs %d (make sure it's the right shape!)"</span>, <span class="hljs-built_in">len</span>(data), channelSize*<span class="hljs-number">3</span>)
    }
    redChannel := data[<span class="hljs-number">0</span>:channelSize]
    greenChannel := data[channelSize : channelSize*<span class="hljs-number">2</span>]
    blueChannel := data[channelSize*<span class="hljs-number">2</span> : channelSize*<span class="hljs-number">3</span>]

    <span class="hljs-comment">// Resize the image to 640x640 using Lanczos3 algorithm</span>
    pic = resize.Resize(<span class="hljs-number">640</span>, <span class="hljs-number">640</span>, pic, resize.Lanczos3)
    i := <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> y := <span class="hljs-number">0</span>; y &lt; <span class="hljs-number">640</span>; y++ {
        <span class="hljs-keyword">for</span> x := <span class="hljs-number">0</span>; x &lt; <span class="hljs-number">640</span>; x++ {
            r, g, b, _ := pic.At(x, y).RGBA()
            redChannel[i] = <span class="hljs-keyword">float32</span>(r&gt;&gt;<span class="hljs-number">8</span>) / <span class="hljs-number">255.0</span>
            greenChannel[i] = <span class="hljs-keyword">float32</span>(g&gt;&gt;<span class="hljs-number">8</span>) / <span class="hljs-number">255.0</span>
            blueChannel[i] = <span class="hljs-keyword">float32</span>(b&gt;&gt;<span class="hljs-number">8</span>) / <span class="hljs-number">255.0</span>
            i++
        }
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<h1 id="heading-step-5-run-session">Step 5: Run Session</h1>
<p>Run the inference:</p>
<pre><code class="lang-go">e = modelSession.Session.Run()
<span class="hljs-keyword">if</span> e != <span class="hljs-literal">nil</span> {
    fmt.Printf(<span class="hljs-string">"Error running ORT session: %s\n"</span>, e)

    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
}
</code></pre>
<h1 id="heading-step-6-process-output">Step 6: Process Output</h1>
<p>Now we need to process the inference results and prepare an output in a human readable format:</p>
<pre><code class="lang-go">boxes := processOutput(modelSession.Output.GetData(), originalWidth,
        originalHeight)
<span class="hljs-keyword">for</span> i, box := <span class="hljs-keyword">range</span> boxes {
    fmt.Printf(<span class="hljs-string">"Box %d: %s\n"</span>, i, &amp;box)
}

(...)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">processOutput</span><span class="hljs-params">(output []<span class="hljs-keyword">float32</span>, originalWidth,
    originalHeight <span class="hljs-keyword">int</span>)</span> []<span class="hljs-title">boundingBox</span></span> {
    boundingBoxes := <span class="hljs-built_in">make</span>([]boundingBox, <span class="hljs-number">0</span>, <span class="hljs-number">8400</span>)

    <span class="hljs-keyword">var</span> classID <span class="hljs-keyword">int</span>
    <span class="hljs-keyword">var</span> probability <span class="hljs-keyword">float32</span>

    <span class="hljs-comment">// Iterate through the output array, considering 8400 indices</span>
    <span class="hljs-keyword">for</span> idx := <span class="hljs-number">0</span>; idx &lt; <span class="hljs-number">8400</span>; idx++ {
        <span class="hljs-comment">// Iterate through 80 classes and find the class with the highest probability</span>
        probability = <span class="hljs-number">-1e9</span>
        <span class="hljs-keyword">for</span> col := <span class="hljs-number">0</span>; col &lt; <span class="hljs-number">80</span>; col++ {
            currentProb := output[<span class="hljs-number">8400</span>*(col+<span class="hljs-number">4</span>)+idx]
            <span class="hljs-keyword">if</span> currentProb &gt; probability {
                probability = currentProb
                classID = col
            }
        }

        <span class="hljs-comment">// If the probability is less than 0.5, continue to the next index</span>
        <span class="hljs-keyword">if</span> probability &lt; <span class="hljs-number">0.5</span> {
            <span class="hljs-keyword">continue</span>
        }

        <span class="hljs-comment">// Extract the coordinates and dimensions of the bounding box</span>
        xc, yc := output[idx], output[<span class="hljs-number">8400</span>+idx]
        w, h := output[<span class="hljs-number">2</span>*<span class="hljs-number">8400</span>+idx], output[<span class="hljs-number">3</span>*<span class="hljs-number">8400</span>+idx]
        x1 := (xc - w/<span class="hljs-number">2</span>) / <span class="hljs-number">640</span> * <span class="hljs-keyword">float32</span>(originalWidth)
        y1 := (yc - h/<span class="hljs-number">2</span>) / <span class="hljs-number">640</span> * <span class="hljs-keyword">float32</span>(originalHeight)
        x2 := (xc + w/<span class="hljs-number">2</span>) / <span class="hljs-number">640</span> * <span class="hljs-keyword">float32</span>(originalWidth)
        y2 := (yc + h/<span class="hljs-number">2</span>) / <span class="hljs-number">640</span> * <span class="hljs-keyword">float32</span>(originalHeight)

        <span class="hljs-comment">// Append the bounding box to the result</span>
        boundingBoxes = <span class="hljs-built_in">append</span>(boundingBoxes, boundingBox{
            label:      yoloClasses[classID],
            confidence: probability,
            x1:         x1,
            y1:         y1,
            x2:         x2,
            y2:         y2,
        })
    }

    <span class="hljs-comment">// Sort the bounding boxes by probability</span>
    sort.Slice(boundingBoxes, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> {
        <span class="hljs-keyword">return</span> boundingBoxes[i].confidence &lt; boundingBoxes[j].confidence
    })

    <span class="hljs-comment">// Define a slice to hold the final result</span>
    mergedResults := <span class="hljs-built_in">make</span>([]boundingBox, <span class="hljs-number">0</span>, <span class="hljs-built_in">len</span>(boundingBoxes))

    <span class="hljs-comment">// Iterate through sorted bounding boxes, removing overlaps</span>
    <span class="hljs-keyword">for</span> _, candidateBox := <span class="hljs-keyword">range</span> boundingBoxes {
        overlapsExistingBox := <span class="hljs-literal">false</span>
        <span class="hljs-keyword">for</span> _, existingBox := <span class="hljs-keyword">range</span> mergedResults {
            <span class="hljs-keyword">if</span> (&amp;candidateBox).iou(&amp;existingBox) &gt; <span class="hljs-number">0.7</span> {
                overlapsExistingBox = <span class="hljs-literal">true</span>
                <span class="hljs-keyword">break</span>
            }
        }
        <span class="hljs-keyword">if</span> !overlapsExistingBox {
            mergedResults = <span class="hljs-built_in">append</span>(mergedResults, candidateBox)
        }
    }

    <span class="hljs-comment">// This will still be in sorted order by confidence</span>
    <span class="hljs-keyword">return</span> mergedResults
}
</code></pre>
<p>It will produce something similar:</p>
<pre><code class="lang-bash">go run main.go
Box 0: Object laptop (confidence 0.524439): (213.599579, 243.196198), (419.911469, 350.581512)
Box 1: Object cup (confidence 0.563491): (433.477356, 257.403839), (571.929077, 355.463074)
Box 2: Object parking meter (confidence 0.578624): (406.172058, 50.842918), (565.424744, 231.428116)
</code></pre>
<p>The inference on the Apple M1 Pro (2020) takes about 10 seconds.</p>
<h1 id="heading-step-7-draw-output-image-with-boxes">Step 7: Draw Output Image with Boxes</h1>
<p>In this step we create an output image based on the input image, but with the boxes marking detected objects.</p>
<p>Note:</p>
<ul>
<li>for labels you need to provide the correct path to the font you want to use (see <code>fontPath</code> const at the top of the program)</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">const</span> (
    outputImagePath = <span class="hljs-string">"./output.jpg"</span>
(...)
    fontPath        = <span class="hljs-string">"/Library/Fonts/Arial Unicode.ttf"</span>
)

(...)

err := drawBoxes(imagePath, outputImagePath, boxes)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    fmt.Printf(<span class="hljs-string">"error drawing boxes: %s\n"</span>, err)

    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
}

(...)

<span class="hljs-comment">// Draws bounding boxes with labels onto the image and saves the result</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">drawBoxes</span><span class="hljs-params">(inputPath <span class="hljs-keyword">string</span>, outputPath <span class="hljs-keyword">string</span>, boxes []boundingBox)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// Open and decode the image</span>
    f, err := os.Open(inputPath)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"error opening input image: %w"</span>, err)
    }
    <span class="hljs-keyword">defer</span> f.Close()

    img, _, err := image.Decode(f)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"error decoding image: %w"</span>, err)
    }

    dc := gg.NewContextForImage(img)
    dc.SetLineWidth(<span class="hljs-number">1</span>)
    fontLoaded := <span class="hljs-literal">false</span>
    <span class="hljs-keyword">if</span> err := dc.LoadFontFace(fontPath, <span class="hljs-number">14</span>); err == <span class="hljs-literal">nil</span> {
        fontLoaded = <span class="hljs-literal">true</span>
    }

    <span class="hljs-keyword">for</span> _, box := <span class="hljs-keyword">range</span> boxes {
        <span class="hljs-comment">// Draw rectangle</span>
        dc.SetRGB(<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>) <span class="hljs-comment">// red</span>
        dc.DrawRectangle(<span class="hljs-keyword">float64</span>(box.x1), <span class="hljs-keyword">float64</span>(box.y1), <span class="hljs-keyword">float64</span>(box.x2-box.x1), <span class="hljs-keyword">float64</span>(box.y2-box.y1))
        dc.Stroke()

        <span class="hljs-comment">// Draw label</span>
        <span class="hljs-keyword">if</span> fontLoaded {
            label := fmt.Sprintf(<span class="hljs-string">"%s (%.2f)"</span>, box.label, box.confidence)
            dc.SetRGB(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>)
            dc.DrawStringAnchored(label, <span class="hljs-keyword">float64</span>(box.x1)+<span class="hljs-number">4</span>, <span class="hljs-keyword">float64</span>(box.y1)<span class="hljs-number">-4</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>)
        }
    }

    <span class="hljs-comment">// Save the result</span>
    out, err := os.Create(outputPath)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"error creating output file: %w"</span>, err)
    }
    <span class="hljs-keyword">defer</span> out.Close()

    <span class="hljs-keyword">return</span> jpeg.Encode(out, dc.Image(), &amp;jpeg.Options{Quality: <span class="hljs-number">90</span>})
}
</code></pre>
<p>The output should be like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753614634568/1e32e799-a6e1-492d-ad70-fe974d850d6f.jpeg" alt class="image--center mx-auto" /></p>
<p>Hope you like it! Nice hacking!</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>ONNX Runtime: <a target="_blank" href="https://github.com/yalue/onnxruntime_go">https://github.com/yalue/onnxruntime_go</a></p>
</li>
<li><p>Image detection: <a target="_blank" href="https://github.com/yalue/onnxruntime_go_examples/tree/master/image_object_detect">https://github.com/yalue/onnxruntime_go_examples/tree/master/image_object_detect</a></p>
</li>
<li><p>Sample images: <a target="_blank" href="https://www.kaggle.com/datasets/kkhandekar/object-detection-sample-images">https://www.kaggle.com/datasets/kkhandekar/object-detection-sample-images</a></p>
</li>
<li><p>Virtual envs: <a target="_blank" href="https://docs.python.org/3/library/venv.html">https://docs.python.org/3/library/venv.html</a></p>
</li>
<li><p>Article Golang code repository: <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/yolo-in-go-with-onnx">https://github.com/flashlabs/kiss-samples/tree/main/yolo-in-go-with-onnx</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Run YOLOv5 Inference From Golang with Python API]]></title><description><![CDATA[There’s multiple ways of running a YOLO (You Only Look Once) inferences in Golang:

Call a YOLO model via the Python REST API

Communicate via the gRPC with Python service that runs a YOLO model

Use the onnxruntime_go to run the YOLO model in native...]]></description><link>https://blog.skopow.ski/how-to-run-yolov5-inference-from-golang-with-python-api</link><guid isPermaLink="true">https://blog.skopow.ski/how-to-run-yolov5-inference-from-golang-with-python-api</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Python]]></category><category><![CDATA[golang]]></category><category><![CDATA[models]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 29 Jun 2025 14:40:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/He_YXURoPyA/upload/38d0b165628cad85b1cf934bb6fa963c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There’s multiple ways of running a YOLO (You Only Look Once) inferences in Golang:</p>
<ul>
<li><p>Call a YOLO model via the Python REST API</p>
</li>
<li><p>Communicate via the gRPC with Python service that runs a YOLO model</p>
</li>
<li><p>Use the <code>onnxruntime_go</code> to run the YOLO model in native GO environment</p>
</li>
</ul>
<p>Today we’re going to focus on the fastest approach of all three of them, which is calling a YOLO model via the Python REST API.</p>
<h1 id="heading-architecture">Architecture</h1>
<p>We’re going to write a simple Golang application, that will call the Python REST API with the provided image and write the model inference results to the <code>CLI</code>.</p>
<p><strong>Golang ⇄ HTTP ⇄ Python (YOLOv5 inference)</strong></p>
<p>With this approach, we’re getting:</p>
<ul>
<li><p>Minimal setup</p>
</li>
<li><p>Quite nice performance, since it’s the Python that is doing the heavy lifting (YOLO inference)</p>
</li>
<li><p>Easy to containerize (two separate services: Go + Python)</p>
</li>
</ul>
<p>Project structure:</p>
<pre><code class="lang-bash">yolo-in-go-with-python/
├── go-backend/
│   ├── go.mod
│   └── main.go
├── yolo-api/
│   ├── detect.py
│   └── requirements.txt
└── example.jpg
</code></pre>
<p>Looks simple, right? It is!</p>
<h1 id="heading-python-inference-server-yolov5">Python Inference Server (YOLOv5)</h1>
<p>A minimal FastAPI server:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI, File, UploadFile
<span class="hljs-keyword">from</span> fastapi.responses <span class="hljs-keyword">import</span> JSONResponse
<span class="hljs-keyword">import</span> torch
<span class="hljs-keyword">from</span> PIL <span class="hljs-keyword">import</span> Image
<span class="hljs-keyword">import</span> io

<span class="hljs-comment"># Initialize the FastAPI application</span>
app = FastAPI()
<span class="hljs-comment"># Load the pretrained YOLOv5s model from the Ultralytics repository</span>
model = torch.hub.load(<span class="hljs-string">"ultralytics/yolov5"</span>, <span class="hljs-string">"yolov5s"</span>, pretrained=<span class="hljs-literal">True</span>)

<span class="hljs-comment"># Define the endpoint to handle object detection requests</span>
<span class="hljs-meta">@app.post("/detect")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">detect</span>(<span class="hljs-params">file: UploadFile = File(<span class="hljs-params">...</span>)</span>):</span>
    <span class="hljs-comment"># Read the uploaded image file as bytes</span>
    image_bytes = <span class="hljs-keyword">await</span> file.read()
    <span class="hljs-comment"># Convert the byte data to a PIL Image</span>
    image = Image.open(io.BytesIO(image_bytes))
    <span class="hljs-comment"># Run the image through the YOLO model</span>
    results = model(image)
    <span class="hljs-comment"># Convert the detection results to a JSON response</span>
    <span class="hljs-keyword">return</span> JSONResponse(results.pandas().xyxy[<span class="hljs-number">0</span>].to_dict(orient=<span class="hljs-string">"records"</span>))
</code></pre>
<p>Requirements:</p>
<p>The base server reqs are:</p>
<pre><code class="lang-python">torch
fastapi
uvicorn
pillow
</code></pre>
<p>but in the <code>requirements.txt</code> you can find all my deps <code>freeze</code> that was used during this tutorial.</p>
<p><em>I strongly recommend using the</em> <code>venv</code> <em>-</em> <a target="_blank" href="https://docs.python.org/3/library/venv.html"><em>https://docs.python.org/3/library/venv.html</em></a> <em>and not to install the reqs in your local environment.</em></p>
<ol>
<li><p>Install deps: <code>pip install -r requirements.txt</code></p>
</li>
<li><p>Start a server: <code>uvicorn detect:app --host 0.0.0.0 --port 8000</code></p>
</li>
</ol>
<p>You should see something like this:</p>
<pre><code class="lang-bash">uvicorn detect:app --host 0.0.0.0 --port 8000
Using cache found <span class="hljs-keyword">in</span> /.../.cache/torch/hub/ultralytics_yolov5_master
/.../.cache/torch/hub/ultralytics_yolov5_master/utils/general.py:32: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated <span class="hljs-keyword">for</span> removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools&lt;81.
  import pkg_resources as pkg
YOLOv5 🚀 2025-6-29 Python-3.11.6 torch-2.2.2 CPU

Fusing layers... 
[W NNPACK.cpp:64] Could not initialize NNPACK! Reason: Unsupported hardware.
YOLOv5s summary: 213 layers, 7225885 parameters, 0 gradients, 16.4 GFLOPs
Adding AutoShape... 
INFO:     Started server process [28206]
INFO:     Waiting <span class="hljs-keyword">for</span> application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
</code></pre>
<p>Don’t mind the <code>NNPACK</code> warning, it’s related to the optimization that couldn’t be applied. All is working correctly!</p>
<h1 id="heading-go-client-to-call-yolov5">Go Client to Call YOLOv5</h1>
<p>Go Client:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"bytes"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"io"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"mime/multipart"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"os"</span>
    <span class="hljs-string">"path/filepath"</span>
)

<span class="hljs-keyword">const</span> (
    filePath   = <span class="hljs-string">"../example.jpg"</span>
    yoloAPIURL = <span class="hljs-string">"http://localhost:8000/detect"</span>
)

<span class="hljs-comment">// main is the entry point for the application. It prepares the image,</span>
<span class="hljs-comment">// sends it to the YOLO API, and prints the result.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Prepare the image file as a multipart form</span>
    body, contentType, err := prepareMultipartForm(filePath)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"Error preparing multipart form: "</span>, err)
    }

    <span class="hljs-comment">// Send the HTTP POST request to the YOLO API</span>
    respBytes, err := sendYOLORequest(yoloAPIURL, body, contentType)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"Error sending YOLO request: "</span>, err)
    }

    <span class="hljs-comment">// Print the detection results</span>
    fmt.Println(<span class="hljs-keyword">string</span>(respBytes))
}

<span class="hljs-comment">// prepareMultipartForm creates a multipart/form-data body from the given file path.</span>
<span class="hljs-comment">// It returns the form body, content type, and any error encountered.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">prepareMultipartForm</span><span class="hljs-params">(filePath <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(*bytes.Buffer, <span class="hljs-keyword">string</span>, error)</span></span> {
    body := &amp;bytes.Buffer{}
    writer := multipart.NewWriter(body)

    <span class="hljs-comment">// Open the file</span>
    file, err := os.Open(filePath)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, <span class="hljs-string">""</span>, fmt.Errorf(<span class="hljs-string">"failed to open file: %w"</span>, err)
    }
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> e := file.Close(); e != <span class="hljs-literal">nil</span> {
            log.Println(<span class="hljs-string">"Failed to close file"</span>, e)
        }
    }()

    <span class="hljs-comment">// Create a new form file field</span>
    part, err := writer.CreateFormFile(<span class="hljs-string">"file"</span>, filepath.Base(filePath))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, <span class="hljs-string">""</span>, fmt.Errorf(<span class="hljs-string">"failed to create form file: %w"</span>, err)
    }

    <span class="hljs-comment">// Copy the image data into the form</span>
    _, err = io.Copy(part, file)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, <span class="hljs-string">""</span>, fmt.Errorf(<span class="hljs-string">"failed to copy file: %w"</span>, err)
    }

    <span class="hljs-comment">// Close the multipart writer</span>
    <span class="hljs-keyword">if</span> err = writer.Close(); err != <span class="hljs-literal">nil</span> {
        log.Println(<span class="hljs-string">"Failed to close writer"</span>, err)
    }

    <span class="hljs-keyword">return</span> body, writer.FormDataContentType(), <span class="hljs-literal">nil</span>
}

<span class="hljs-comment">// sendYOLORequest sends the image as a multipart POST request to the specified YOLO API.</span>
<span class="hljs-comment">// It returns the response body or an error.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sendYOLORequest</span><span class="hljs-params">(apiURL <span class="hljs-keyword">string</span>, body *bytes.Buffer, contentType <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">([]<span class="hljs-keyword">byte</span>, error)</span></span> {
    <span class="hljs-comment">// Create a new HTTP POST request with the multipart data</span>
    req, err := http.NewRequest(http.MethodPost, apiURL, body)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to create request: %w"</span>, err)
    }
    req.Header.Set(<span class="hljs-string">"Content-Type"</span>, contentType)

    <span class="hljs-comment">// Send the request and get the response</span>
    resp, err := http.DefaultClient.Do(req)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to execute request: %w"</span>, err)
    }
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> e := resp.Body.Close(); e != <span class="hljs-literal">nil</span> {
            log.Println(<span class="hljs-string">"Failed to close body"</span>, e)
        }
    }()

    <span class="hljs-comment">// Read and return the response body</span>
    respBytes, err := io.ReadAll(resp.Body)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to read response body: %w"</span>, err)
    }

    <span class="hljs-keyword">return</span> respBytes, <span class="hljs-literal">nil</span>
}
</code></pre>
<h1 id="heading-run-the-inference">Run the Inference</h1>
<ol>
<li>The Python REST API is up &amp; running, if not, execute:</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> yolo-api &amp;&amp; uvicorn detect:app --host 0.0.0.0 --port 8000
</code></pre>
<ol start="2">
<li>Run the Go app:</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> go-backend &amp;&amp; go run main.go
</code></pre>
<p>You should see the output like this:</p>
<pre><code class="lang-bash">go run main.go
[{<span class="hljs-string">"xmin"</span>:451.77557373046875,<span class="hljs-string">"ymin"</span>:256.8055114746094,<span class="hljs-string">"xmax"</span>:572.8908081054688,<span class="hljs-string">"ymax"</span>:355.9529724121094,<span class="hljs-string">"confidence"</span>:0.8660547733306885,<span class="hljs-string">"class"</span>:41,<span class="hljs-string">"name"</span>:<span class="hljs-string">"cup"</span>},{<span class="hljs-string">"xmin"</span>:216.73318481445312,<span class="hljs-string">"ymin"</span>:242.79660034179688,<span class="hljs-string">"xmax"</span>:417.9637756347656,<span class="hljs-string">"ymax"</span>:352.3187561035156,<span class="hljs-string">"confidence"</span>:0.3558332026004791,<span class="hljs-string">"class"</span>:67,<span class="hljs-string">"name"</span>:<span class="hljs-string">"cell phone"</span>},{<span class="hljs-string">"xmin"</span>:0.4250640869140625,<span class="hljs-string">"ymin"</span>:0.6914291381835938,<span class="hljs-string">"xmax"</span>:276.78839111328125,<span class="hljs-string">"ymax"</span>:174.0032958984375,<span class="hljs-string">"confidence"</span>:0.27563828229904175,<span class="hljs-string">"class"</span>:73,<span class="hljs-string">"name"</span>:<span class="hljs-string">"book"</span>},{<span class="hljs-string">"xmin"</span>:211.1724090576172,<span class="hljs-string">"ymin"</span>:242.36141967773438,<span class="hljs-string">"xmax"</span>:421.87457275390625,<span class="hljs-string">"ymax"</span>:351.2012634277344,<span class="hljs-string">"confidence"</span>:0.26584678888320923,<span class="hljs-string">"class"</span>:63,<span class="hljs-string">"name"</span>:<span class="hljs-string">"laptop"</span>}]
</code></pre>
<p>With the “pretty print” it loos like this:</p>
<pre><code class="lang-bash">go run main.go | jq .
[
  {
    <span class="hljs-string">"xmin"</span>: 451.77557373046875,
    <span class="hljs-string">"ymin"</span>: 256.8055114746094,
    <span class="hljs-string">"xmax"</span>: 572.8908081054688,
    <span class="hljs-string">"ymax"</span>: 355.9529724121094,
    <span class="hljs-string">"confidence"</span>: 0.8660547733306885,
    <span class="hljs-string">"class"</span>: 41,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"cup"</span>
  },
  {
    <span class="hljs-string">"xmin"</span>: 216.73318481445312,
    <span class="hljs-string">"ymin"</span>: 242.79660034179688,
    <span class="hljs-string">"xmax"</span>: 417.9637756347656,
    <span class="hljs-string">"ymax"</span>: 352.3187561035156,
    <span class="hljs-string">"confidence"</span>: 0.3558332026004791,
    <span class="hljs-string">"class"</span>: 67,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"cell phone"</span>
  },
  {
    <span class="hljs-string">"xmin"</span>: 0.4250640869140625,
    <span class="hljs-string">"ymin"</span>: 0.6914291381835938,
    <span class="hljs-string">"xmax"</span>: 276.78839111328125,
    <span class="hljs-string">"ymax"</span>: 174.0032958984375,
    <span class="hljs-string">"confidence"</span>: 0.27563828229904175,
    <span class="hljs-string">"class"</span>: 73,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"book"</span>
  },
  {
    <span class="hljs-string">"xmin"</span>: 211.1724090576172,
    <span class="hljs-string">"ymin"</span>: 242.36141967773438,
    <span class="hljs-string">"xmax"</span>: 421.87457275390625,
    <span class="hljs-string">"ymax"</span>: 351.2012634277344,
    <span class="hljs-string">"confidence"</span>: 0.26584678888320923,
    <span class="hljs-string">"class"</span>: 63,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"laptop"</span>
  }
]
</code></pre>
<p>As you can see this is mostly what we have in our image:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751206586419/013d8358-a0a3-4a7f-a7f5-f8df4cd81978.jpeg" alt class="image--center mx-auto" /></p>
<p>There’s no “laptop”, but the confidence score was really low - 0.26, so we shouldn’t be surprised by that. Also the “cell phone” is probably a tablet.</p>
<p>At the same time, your <code>CLI</code> output for the <code>Python REST API</code> show you incoming requests:</p>
<pre><code class="lang-bash">uvicorn detect:app --host 0.0.0.0 --port 8000
Using cache found <span class="hljs-keyword">in</span> /.../.cache/torch/hub/ultralytics_yolov5_master
/.../.cache/torch/hub/ultralytics_yolov5_master/utils/general.py:32: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated <span class="hljs-keyword">for</span> removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools&lt;81.
  import pkg_resources as pkg
YOLOv5 🚀 2025-6-29 Python-3.11.6 torch-2.2.2 CPU

Fusing layers... 
[W NNPACK.cpp:64] Could not initialize NNPACK! Reason: Unsupported hardware.
YOLOv5s summary: 213 layers, 7225885 parameters, 0 gradients, 16.4 GFLOPs
Adding AutoShape... 
INFO:     Started server process [28206]
INFO:     Waiting <span class="hljs-keyword">for</span> application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:51144 - <span class="hljs-string">"POST /detect HTTP/1.1"</span> 200 OK
INFO:     127.0.0.1:51146 - <span class="hljs-string">"POST /detect HTTP/1.1"</span> 200 OK
INFO:     127.0.0.1:51147 - <span class="hljs-string">"POST /detect HTTP/1.1"</span> 200 OK
INFO:     127.0.0.1:51150 - <span class="hljs-string">"POST /detect HTTP/1.1"</span> 200 OK
</code></pre>
<p>Have fun with detections!</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>Article Golang code repository: <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/yolo-in-go-with-python">https://github.com/flashlabs/kiss-samples/tree/main/yolo-in-go-with-python</a></p>
</li>
<li><p>Sample images: <a target="_blank" href="https://www.kaggle.com/datasets/kkhandekar/object-detection-sample-images"><strong>https://www.kaggle.com/datasets/kkhandekar/object-detection-sample-images</strong></a></p>
</li>
<li><p>Virtual Environment: <a target="_blank" href="https://docs.python.org/3/library/venv.html">https://docs.python.org/3/library/venv.html</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building a Golang Microservice for Machine Learning Inference with TensorFlow]]></title><description><![CDATA[In today’s article we’ll focus on how to create a simple REST API in Go that loads a TensorFlow model and serves a predictions.
As a base we’ll use the code from the previous article “Running TensorFlow Models in Golang” and work on that.
We’ll do a ...]]></description><link>https://blog.skopow.ski/building-a-golang-microservice-for-machine-learning-inference-with-tensorflow</link><guid isPermaLink="true">https://blog.skopow.ski/building-a-golang-microservice-for-machine-learning-inference-with-tensorflow</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[TensorFlow]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 18 May 2025 11:47:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/GESOWH4YLRI/upload/42ccf9122dc53d9d518fceeebd68a312.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today’s article we’ll focus on how to create a simple REST API in Go that loads a TensorFlow model and serves a predictions.</p>
<p>As a base we’ll use the code from the previous article “<a target="_blank" href="https://blog.skopow.ski/running-tensorflow-models-in-golang">Running TensorFlow Models in Golang</a>” and work on that.</p>
<p>We’ll do a little bit of refactoring regarding the architecture and introduce the modular folder structure for the easier maintenance and scalability.</p>
<p>Note: for setting up the environment and preparing the model please take a look at <a target="_blank" href="https://blog.skopow.ski/running-tensorflow-models-in-golang#heading-setting-up-the-environment">Setting Up the Environment</a> section of the previous article.</p>
<h1 id="heading-architecture">Architecture</h1>
<p>We’re going to split the logic that was previously in the <code>main.go</code> file into smaller chunks:</p>
<pre><code class="lang-bash">.
├── main.go                          <span class="hljs-comment"># entry point</span>
├── internal/
│   ├── inference/
│   │   ├── model.go                 <span class="hljs-comment"># model loading + prediction</span>
│   │   └── labels.go                <span class="hljs-comment"># label loading</span>
│   └── handler/
│       └── predict.go               <span class="hljs-comment"># HTTP handler</span>
├── model/
│   └── mobilenet_v2/                <span class="hljs-comment"># saved_model.pb + variables/</span>
├── static/
│   └── example.jpg
├── ImageNetLabels.txt
├── go.mod
└── go.sum
</code></pre>
<h1 id="heading-model">Model</h1>
<p>Loading a model to the memory in <code>model.go</code> file in the <code>inference</code> package and exposing it via the public <code>Model</code> variable:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> inference

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>

    tf <span class="hljs-string">"github.com/wamuir/graft/tensorflow"</span>
)

<span class="hljs-keyword">var</span> Model *tf.SavedModel

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">LoadModel</span><span class="hljs-params">(path <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(err error)</span></span> {
    Model, err = tf.LoadSavedModel(path, []<span class="hljs-keyword">string</span>{<span class="hljs-string">"serve"</span>}, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"LoadSavedModel: %w"</span>, err)
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<h1 id="heading-labels">Labels</h1>
<p>Loading labels into the <code>Labels</code> public variable in the <code>labels.go</code> file in the <code>inference</code> package:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> inference

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"bufio"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"os"</span>
)

<span class="hljs-keyword">var</span> Labels []<span class="hljs-keyword">string</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">LoadLabels</span><span class="hljs-params">(path <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
    file, err := os.Open(path)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"os.Open: %w"</span>, err)
    }

    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(file *os.File)</span></span> {
        <span class="hljs-keyword">if</span> e := file.Close(); e != <span class="hljs-literal">nil</span> {
            log.Println(<span class="hljs-string">"file.Close"</span>, e)
        }
    }(file)

    <span class="hljs-keyword">var</span> labels []<span class="hljs-keyword">string</span>

    scanner := bufio.NewScanner(file)
    <span class="hljs-keyword">for</span> scanner.Scan() {
        labels = <span class="hljs-built_in">append</span>(labels, scanner.Text())
    }

    <span class="hljs-keyword">if</span> err = scanner.Err(); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"bufio.Scanner: %w"</span>, err)
    }

    Labels = labels

    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<p>Note that the contents of the <code>Labels</code> var we’re updating only when the whole process has completed successfully.</p>
<h1 id="heading-handler">Handler</h1>
<p>The main logic from our previous article we need to move to the http server handler - the <code>Predict</code> in this example.</p>
<p>The <code>makeTensorFromImage</code> helper function comes here with us.</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> handler

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"image"</span>
    <span class="hljs-string">"image/jpeg"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"mime/multipart"</span>
    <span class="hljs-string">"net/http"</span>

    <span class="hljs-string">"github.com/nfnt/resize"</span>
    tf <span class="hljs-string">"github.com/wamuir/graft/tensorflow"</span>

    <span class="hljs-string">"github.com/flashlabs/kiss-samples/tensorflowrestapi/internal/inference"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Predict</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    <span class="hljs-keyword">if</span> r.Method != http.MethodPost {
        http.Error(w, <span class="hljs-string">"Method not allowed"</span>, http.StatusMethodNotAllowed)

        <span class="hljs-keyword">return</span>
    }

    file, _, err := r.FormFile(<span class="hljs-string">"image"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to get images"</span>, http.StatusBadRequest)

        <span class="hljs-keyword">return</span>
    }
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(file multipart.File)</span></span> {
        <span class="hljs-keyword">if</span> e := file.Close(); e != <span class="hljs-literal">nil</span> {
            log.Println(<span class="hljs-string">"file.Close"</span>, e)
        }
    }(file)

    img, err := jpeg.Decode(file)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to decode image"</span>, http.StatusBadRequest)

        <span class="hljs-keyword">return</span>
    }

    tensor, err := makeTensorFromImage(img)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to make tensor from image"</span>, http.StatusInternalServerError)

        <span class="hljs-keyword">return</span>
    }

    input := inference.Model.Graph.Operation(<span class="hljs-string">"serving_default_x"</span>)
    output := inference.Model.Graph.Operation(<span class="hljs-string">"StatefulPartitionedCall"</span>)

    outputs, err := inference.Model.Session.Run(
        <span class="hljs-keyword">map</span>[tf.Output]*tf.Tensor{
            input.Output(<span class="hljs-number">0</span>): tensor,
        },
        []tf.Output{
            output.Output(<span class="hljs-number">0</span>),
        },
        <span class="hljs-literal">nil</span>,
    )
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to run inference"</span>, http.StatusInternalServerError)

        <span class="hljs-keyword">return</span>
    }

    predictions := outputs[<span class="hljs-number">0</span>].Value().([][]<span class="hljs-keyword">float32</span>)

    bestIdx, bestScore := <span class="hljs-number">0</span>, <span class="hljs-keyword">float32</span>(<span class="hljs-number">0.0</span>)
    <span class="hljs-keyword">for</span> i, p := <span class="hljs-keyword">range</span> predictions[<span class="hljs-number">0</span>] {
        <span class="hljs-keyword">if</span> p &gt; bestScore {
            bestIdx, bestScore = i, p
        }
    }

    label := inference.Labels[bestIdx]

    _, err = fmt.Fprintf(w, <span class="hljs-string">`{"class_id": %d, "label": "%s", "confidence": %.4f}`</span>+<span class="hljs-string">"\n"</span>, bestIdx, label, bestScore)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Failed to write response"</span>, http.StatusInternalServerError)

        <span class="hljs-keyword">return</span>
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeTensorFromImage</span><span class="hljs-params">(img image.Image)</span> <span class="hljs-params">(*tf.Tensor, error)</span></span> {
    <span class="hljs-comment">// Resize to 224x224</span>
    resized := resize.Resize(<span class="hljs-number">224</span>, <span class="hljs-number">224</span>, img, resize.Bilinear)

    <span class="hljs-comment">// Create a 4D array to hold input</span>
    bounds := resized.Bounds()
    batch := <span class="hljs-built_in">make</span>([][][][]<span class="hljs-keyword">float32</span>, <span class="hljs-number">1</span>) <span class="hljs-comment">// batch size 1</span>
    batch[<span class="hljs-number">0</span>] = <span class="hljs-built_in">make</span>([][][]<span class="hljs-keyword">float32</span>, bounds.Dy())

    <span class="hljs-keyword">for</span> y := bounds.Min.Y; y &lt; bounds.Max.Y; y++ {
        row := <span class="hljs-built_in">make</span>([][]<span class="hljs-keyword">float32</span>, bounds.Dx())
        <span class="hljs-keyword">for</span> x := bounds.Min.X; x &lt; bounds.Max.X; x++ {
            r, g, b, _ := resized.At(x, y).RGBA()
            row[x] = []<span class="hljs-keyword">float32</span>{
                <span class="hljs-keyword">float32</span>(r) / <span class="hljs-number">65535.0</span>, <span class="hljs-comment">// normalize to [0,1]</span>
                <span class="hljs-keyword">float32</span>(g) / <span class="hljs-number">65535.0</span>,
                <span class="hljs-keyword">float32</span>(b) / <span class="hljs-number">65535.0</span>,
            }
        }
        batch[<span class="hljs-number">0</span>][y] = row
    }

    <span class="hljs-keyword">return</span> tf.NewTensor(batch)
}
</code></pre>
<p>Please note that instead of logging fatals, we need to write an output to the <code>http.ResponeWriter</code> and break the processing, so the client side knows what’s wrong with the request processing, f.e. if the request method is not <code>POST</code>, we need to communicate this issue with the proper message and the <code>HTTP</code> status code:</p>
<pre><code class="lang-go"><span class="hljs-keyword">if</span> r.Method != http.MethodPost {
    http.Error(w, <span class="hljs-string">"Method not allowed"</span>, http.StatusMethodNotAllowed)

    <span class="hljs-keyword">return</span>
}
</code></pre>
<h1 id="heading-main-program">Main Program</h1>
<p>Now, having all the logic extracted into the proper packages our main program looks like it should looks like - it’s small and compatc and is responsible for initialization and running the main process:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>

    <span class="hljs-string">"github.com/flashlabs/kiss-samples/tensorflowrestapi/internal/handler"</span>
    <span class="hljs-string">"github.com/flashlabs/kiss-samples/tensorflowrestapi/internal/inference"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    fmt.Println(<span class="hljs-string">"Loading TF model..."</span>)
    <span class="hljs-keyword">if</span> err := inference.LoadModel(<span class="hljs-string">"model/saved_mobilenet_v2"</span>); err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Failed to load SavedModel: %v"</span>, err)
    }

    fmt.Println(<span class="hljs-string">"Loading labels..."</span>)
    <span class="hljs-keyword">if</span> err := inference.LoadLabels(<span class="hljs-string">"ImageNetLabels.txt"</span>); err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Failed to load labels: %v"</span>, err)
    }

    fmt.Println(<span class="hljs-string">"Setting up handlers..."</span>)
    http.HandleFunc(<span class="hljs-string">"/predict"</span>, handler.Predict)

    fmt.Println(<span class="hljs-string">"listening on :8080"</span>)
    log.Fatal(http.ListenAndServe(<span class="hljs-string">":8080"</span>, <span class="hljs-literal">nil</span>))
}
</code></pre>
<p>As you can see all it does is:</p>
<ul>
<li><p>Loading a TF model</p>
</li>
<li><p>Loading labels</p>
</li>
<li><p>Setting up the <code>HTTP</code> handlers</p>
</li>
<li><p>Starting a <code>HTTP</code> server on the local port <code>8080</code></p>
</li>
</ul>
<h2 id="heading-running-a-program">Running a Program</h2>
<p>Just run <code>main.go</code> with the <code>go run main.go</code> and expect the output similar to this:</p>
<pre><code class="lang-bash">go run main.go
Loading TF model...
2025-05-18 13:29:22.879008: I tensorflow/cc/saved_model/reader.cc:83] Reading SavedModel from: model/saved_mobilenet_v2
2025-05-18 13:29:22.886212: I tensorflow/cc/saved_model/reader.cc:52] Reading meta graph with tags { serve }
2025-05-18 13:29:22.886235: I tensorflow/cc/saved_model/reader.cc:147] Reading SavedModel debug info (<span class="hljs-keyword">if</span> present) from: model/saved_mobilenet_v2
WARNING: All <span class="hljs-built_in">log</span> messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1747567762.933428 11558409 mlir_graph_optimization_pass.cc:425] MLIR V1 optimization pass is not enabled
2025-05-18 13:29:22.940719: I tensorflow/cc/saved_model/loader.cc:236] Restoring SavedModel bundle.
2025-05-18 13:29:23.159484: I tensorflow/cc/saved_model/loader.cc:220] Running initialization op on SavedModel bundle at path: model/saved_mobilenet_v2
2025-05-18 13:29:23.217182: I tensorflow/cc/saved_model/loader.cc:471] SavedModel load <span class="hljs-keyword">for</span> tags { serve }; Status: success: OK. Took 338178 microseconds.
Loading labels...
Setting up handlers...
listening on :8080
</code></pre>
<h2 id="heading-making-a-rest-call">Making a REST Call</h2>
<p>Be sure to be in the project directory to be able to read the <code>static/example.jpg</code> file:</p>
<pre><code class="lang-bash">curl -X POST -F image=@static/example.jpg http://localhost:8080/predict
{<span class="hljs-string">"class_id"</span>: 469, <span class="hljs-string">"label"</span>: <span class="hljs-string">"cab"</span>, <span class="hljs-string">"confidence"</span>: 12.6021}
</code></pre>
<p>Looking at our <code>example.jpg</code> file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747568071998/679899fc-102a-40ce-ab5e-599492c0b255.jpeg" alt class="image--center mx-auto" /></p>
<p>We can see it’s working.</p>
<h1 id="heading-next-steps">Next Steps</h1>
<p>You have a fully working example of a REST API that handles a POST requests with image payloads.</p>
<p>You might want to add more endpoints, validation, detect image sizes and so on.</p>
<p>The sky is the limit.</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>Article Golang code repository: <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/tensorflowrestapi">https://github.com/flashlabs/kiss-samples/tree/main/tensorflowrestapi</a></p>
</li>
<li><p>Sample images: <a target="_blank" href="https://www.kaggle.com/datasets/kkhandekar/object-detection-sample-images"><strong>https://www.kaggle.com/datasets/kkhandekar/object-detection-sample-images</strong></a></p>
</li>
<li><p>ImageNet labels: <a target="_blank" href="https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt"><strong>https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Running TensorFlow Models in Golang]]></title><description><![CDATA[In this article we’re going to walk through loading a pre-trained TensorFlow model and running inference with the Go bindings.
Now, because of the

The TensorFlow team is not currently maintaining the Documentation for installing the Go bindings for ...]]></description><link>https://blog.skopow.ski/running-tensorflow-models-in-golang</link><guid isPermaLink="true">https://blog.skopow.ski/running-tensorflow-models-in-golang</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[golang]]></category><category><![CDATA[TensorFlow]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 27 Apr 2025 17:19:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/D3SzBCAeMhQ/upload/e953c700567106c9bd45b89ac0585666.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article we’re going to walk through loading a pre-trained TensorFlow model and running inference with the Go bindings.</p>
<p>Now, because of the</p>
<blockquote>
<p>The TensorFlow team is not currently maintaining the Documentation for installing the Go bindings for TensorFlow.</p>
<p><a target="_blank" href="https://github.com/tensorflow/tensorflow/tree/master/tensorflow/go">https://github.com/tensorflow/tensorflow/tree/master/tensorflow/go</a></p>
</blockquote>
<p>The new “official” contributor for the Go bindings (as recommended by the <a target="_blank" href="https://github.com/tensorflow/build/tree/master/golang_install_guide">TF itself</a>) is William Muir and his <code>graft</code> repo - <a target="_blank" href="https://github.com/wamuir/graft">https://github.com/wamuir/graft</a></p>
<h1 id="heading-setting-up-the-environment">Setting Up the Environment</h1>
<p>Reqs:</p>
<ul>
<li><p>Go <code>1.21</code>+</p>
</li>
<li><p>TensorFlow (TF) installed (Go bindings rely on TF C library)</p>
</li>
</ul>
<p>Installing the TF:</p>
<pre><code class="lang-bash">brew install tensorflow
</code></pre>
<p>Installing the Go TF package:</p>
<pre><code class="lang-bash">go get -u github.com/wamuir/graft/tensorflow/...
</code></pre>
<p>To check if your TF installation works, please follow the “Hello Tensorflow” example from the <code>graft</code> README - <a target="_blank" href="https://github.com/wamuir/graft">https://github.com/wamuir/graft</a>:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    tf <span class="hljs-string">"github.com/wamuir/graft/tensorflow"</span>
    <span class="hljs-string">"github.com/wamuir/graft/tensorflow/op"</span>
    <span class="hljs-string">"fmt"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Construct a graph with an operation that produces a string constant.</span>
    s := op.NewScope()
    c := op.Const(s, <span class="hljs-string">"Hello from TensorFlow version "</span> + tf.Version())
    graph, err := s.Finalize()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    <span class="hljs-comment">// Execute the graph in a session.</span>
    sess, err := tf.NewSession(graph, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
    output, err := sess.Run(<span class="hljs-literal">nil</span>, []tf.Output{c}, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
    fmt.Println(output[<span class="hljs-number">0</span>].Value())
}
</code></pre>
<p>The output when you run the program should be similar to this one:</p>
<pre><code class="lang-bash">go run main.go
WARNING: All <span class="hljs-built_in">log</span> messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1745750228.799498 1712814 mlir_graph_optimization_pass.cc:425] MLIR V1 optimization pass is not enabled
Hello from TF version 2.19.0
</code></pre>
<p>In case of any issues please refer to the section “<em>Common Pitfalls and Troubleshooting</em>” at the end of this article.</p>
<h1 id="heading-preparing-the-model">Preparing the Model</h1>
<p>We’re going to use the pre-trained image classifier model <code>mobilenet_v2</code> - <a target="_blank" href="https://www.kaggle.com/models/google/mobilenet-v2/tensorFlow2">https://www.kaggle.com/models/google/mobilenet-v2/tensorFlow2</a></p>
<p>Unfortunately the model downloaded from the given source had issues with the provided input layer, so in the blog article repository (see “<em>Sources</em>”) you can find a <code>converter.py</code> script, that exported it from source and provided us with the named input layer called <code>serving_default_x</code>.</p>
<p>You don’t have to do it, it’s already done, but you can take a look at the <code>converter.py</code> to see how you can export a model to a <code>SavedModel</code> format.</p>
<h1 id="heading-loading-and-running-the-model-in-go">Loading and Running the Model in Go</h1>
<p>In the attached code you can observe the whole processing operations split into main sections:</p>
<ul>
<li><p>Load the model</p>
</li>
<li><p>Load an image</p>
</li>
<li><p>Create input tensors (preprocess the image)</p>
</li>
<li><p>Run the session (run inference)</p>
</li>
<li><p>Fetch outputs (predictions)</p>
</li>
<li><p>Find the best prediction and disply results</p>
</li>
</ul>
<p>Our goal is to detect what’s on that image, we want to know if that’s a squirrel:</p>
<p><img src="https://github.com/flashlabs/kiss-samples/blob/main/tensorflow/images/1.jpg?raw=true" alt="1.jpg" /></p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"bufio"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"image"</span>
    <span class="hljs-string">"image/jpeg"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/nfnt/resize"</span>
    tf <span class="hljs-string">"github.com/wamuir/graft/tensorflow"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Load the SavedModel</span>
    model, err := tf.LoadSavedModel(<span class="hljs-string">"saved_mobilenet_v2"</span>, []<span class="hljs-keyword">string</span>{<span class="hljs-string">"serve"</span>}, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"LoadSavedModel"</span>, err)
    }
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(Session *tf.Session)</span></span> {
        <span class="hljs-keyword">if</span> e := Session.Close(); e != <span class="hljs-literal">nil</span> {
            log.Fatal(<span class="hljs-string">"Session.Close"</span>, e)
        }
    }(model.Session)

    <span class="hljs-comment">// Load an image</span>
    img, err := loadImage(<span class="hljs-string">"images/1.jpg"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"loadImage"</span>, err)
    }

    <span class="hljs-comment">// Preprocess the image</span>
    tensor, err := makeTensorFromImage(img)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"makeTensorFromImage"</span>, err)
    }

    inputOp := model.Graph.Operation(<span class="hljs-string">"serving_default_x"</span>)
    <span class="hljs-keyword">if</span> inputOp == <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"model.Graph.Operation: serving_default_x not found"</span>)
    }

    outputOp := model.Graph.Operation(<span class="hljs-string">"StatefulPartitionedCall"</span>)
    <span class="hljs-keyword">if</span> outputOp == <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"model.Graph.Operation: StatefulPartitionedCall not found"</span>)
    }

    <span class="hljs-comment">// Run inference</span>
    outputs, err := model.Session.Run(
        <span class="hljs-keyword">map</span>[tf.Output]*tf.Tensor{
            inputOp.Output(<span class="hljs-number">0</span>): tensor,
        },
        []tf.Output{
            outputOp.Output(<span class="hljs-number">0</span>),
        },
        <span class="hljs-literal">nil</span>,
    )
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"Session.Run"</span>, err)
    }

    <span class="hljs-comment">// Predictions</span>
    predictions := outputs[<span class="hljs-number">0</span>].Value().([][]<span class="hljs-keyword">float32</span>)

    <span class="hljs-comment">// Find the top-1 prediction</span>
    bestIdx := <span class="hljs-number">0</span>
    bestScore := <span class="hljs-keyword">float32</span>(<span class="hljs-number">0.0</span>)
    <span class="hljs-keyword">for</span> i, p := <span class="hljs-keyword">range</span> predictions[<span class="hljs-number">0</span>] {
        <span class="hljs-keyword">if</span> p &gt; bestScore {
            bestIdx = i
            bestScore = p
        }
    }

    labels, err := loadLabels(<span class="hljs-string">"ImageNetLabels.txt"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(<span class="hljs-string">"loadLabels"</span>, err)
    }

    fmt.Printf(<span class="hljs-string">"Predicted label: %s (index: %d, confidence: %.4f)\n"</span>, labels[bestIdx], bestIdx, bestScore)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadImage</span><span class="hljs-params">(filename <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(image.Image, error)</span></span> {
    file, err := os.Open(filename)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"os.Open: %w"</span>, err)
    }
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(file *os.File)</span></span> {
        <span class="hljs-keyword">if</span> e := file.Close(); e != <span class="hljs-literal">nil</span> {
            log.Fatal(<span class="hljs-string">"file.Close"</span>, e)
        }
    }(file)

    img, err := jpeg.Decode(file)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"jpeg.Decode: %w"</span>, err)
    }

    <span class="hljs-keyword">return</span> img, <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeTensorFromImage</span><span class="hljs-params">(img image.Image)</span> <span class="hljs-params">(*tf.Tensor, error)</span></span> {
    <span class="hljs-comment">// Resize to 224x224</span>
    resized := resize.Resize(<span class="hljs-number">224</span>, <span class="hljs-number">224</span>, img, resize.Bilinear)

    <span class="hljs-comment">// Create a 4D array to hold input</span>
    bounds := resized.Bounds()
    batch := <span class="hljs-built_in">make</span>([][][][]<span class="hljs-keyword">float32</span>, <span class="hljs-number">1</span>) <span class="hljs-comment">// batch size 1</span>
    batch[<span class="hljs-number">0</span>] = <span class="hljs-built_in">make</span>([][][]<span class="hljs-keyword">float32</span>, bounds.Dy())

    <span class="hljs-keyword">for</span> y := bounds.Min.Y; y &lt; bounds.Max.Y; y++ {
        row := <span class="hljs-built_in">make</span>([][]<span class="hljs-keyword">float32</span>, bounds.Dx())
        <span class="hljs-keyword">for</span> x := bounds.Min.X; x &lt; bounds.Max.X; x++ {
            r, g, b, _ := resized.At(x, y).RGBA()
            row[x] = []<span class="hljs-keyword">float32</span>{
                <span class="hljs-keyword">float32</span>(r) / <span class="hljs-number">65535.0</span>, <span class="hljs-comment">// normalize to [0,1]</span>
                <span class="hljs-keyword">float32</span>(g) / <span class="hljs-number">65535.0</span>,
                <span class="hljs-keyword">float32</span>(b) / <span class="hljs-number">65535.0</span>,
            }
        }
        batch[<span class="hljs-number">0</span>][y] = row
    }

    <span class="hljs-keyword">return</span> tf.NewTensor(batch)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadLabels</span><span class="hljs-params">(filename <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">([]<span class="hljs-keyword">string</span>, error)</span></span> {
    file, err := os.Open(filename)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"os.Open: %w"</span>, err)
    }
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(file *os.File)</span></span> {
        <span class="hljs-keyword">if</span> e := file.Close(); e != <span class="hljs-literal">nil</span> {
            log.Fatal(<span class="hljs-string">"file.Close"</span>, e)
        }
    }(file)

    <span class="hljs-keyword">var</span> labels []<span class="hljs-keyword">string</span>

    scanner := bufio.NewScanner(file)
    <span class="hljs-keyword">for</span> scanner.Scan() {
        labels = <span class="hljs-built_in">append</span>(labels, scanner.Text())
    }

    <span class="hljs-keyword">if</span> err = scanner.Err(); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"bufio.Scanner: %w"</span>, err)
    }

    <span class="hljs-keyword">return</span> labels, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>The output is as follows:</p>
<pre><code class="lang-bash">go run main.go
2025-04-27 18:56:20.238487: I tensorflow/cc/saved_model/reader.cc:83] Reading SavedModel from: saved_mobilenet_v2
2025-04-27 18:56:20.244913: I tensorflow/cc/saved_model/reader.cc:52] Reading meta graph with tags { serve }
2025-04-27 18:56:20.244945: I tensorflow/cc/saved_model/reader.cc:147] Reading SavedModel debug info (<span class="hljs-keyword">if</span> present) from: saved_mobilenet_v2
WARNING: All <span class="hljs-built_in">log</span> messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1745772980.292412 2175877 mlir_graph_optimization_pass.cc:425] MLIR V1 optimization pass is not enabled
2025-04-27 18:56:20.298863: I tensorflow/cc/saved_model/loader.cc:236] Restoring SavedModel bundle.
2025-04-27 18:56:20.505321: I tensorflow/cc/saved_model/loader.cc:220] Running initialization op on SavedModel bundle at path: saved_mobilenet_v2
2025-04-27 18:56:20.562274: I tensorflow/cc/saved_model/loader.cc:471] SavedModel load <span class="hljs-keyword">for</span> tags { serve }; Status: success: OK. Took 323789 microseconds.
Predicted label: fox squirrel (index: 336, confidence: 8.3710)
</code></pre>
<p>As you can see, the most common tag is a “<strong>fox squirrel</strong>”, which is exactly what we wanted to achieve. Personally not sure if this is a <a target="_blank" href="https://en.wikipedia.org/wiki/Fox_squirrel">fox squirel</a> or any other regular squirrel, but for sure it’s a squirrel.</p>
<p>All the resources like models, images and labels you can find in the article repository.</p>
<h1 id="heading-common-pitfalls-and-troubleshooting">Common Pitfalls and Troubleshooting</h1>
<p>Issues with the TensorFlow library:</p>
<pre><code class="lang-bash">go run main.go
<span class="hljs-comment"># github.com/wamuir/graft/tensorflow</span>
../../../go/pkg/mod/github.com/wamuir/graft@v0.10.0/tensorflow/tensor.go:69:26: could not determine what C.TF_FLOAT8_E4M3FN refers to
../../../go/pkg/mod/github.com/wamuir/graft@v0.10.0/tensorflow/tensor.go:68:26: could not determine what C.TF_FLOAT8_E5M2 refers to
../../../go/pkg/mod/github.com/wamuir/graft@v0.10.0/tensorflow/tensor.go:70:26: could not determine what C.TF_INT4 refers to
../../../go/pkg/mod/github.com/wamuir/graft@v0.10.0/tensorflow/tensor.go:71:26: could not determine what C.TF_UINT4 refers to
</code></pre>
<p>Even though TensorFlow is installed via Homebrew, it's not properly configured for pkg-config, which is needed for Go to find and link against the TensorFlow C library.</p>
<p>Run <code>brew link --force libtensorflow</code></p>
<p>Then if needed add also env vars to you bash profile file (I’m using the <code>.zshrc</code>):</p>
<pre><code class="lang-bash"><span class="hljs-comment"># TensorFlow configuration</span>
<span class="hljs-built_in">export</span> LIBRARY_PATH=<span class="hljs-string">"/opt/homebrew/lib:<span class="hljs-variable">$LIBRARY_PATH</span>"</span>
<span class="hljs-built_in">export</span> CPATH=<span class="hljs-string">"/opt/homebrew/include:<span class="hljs-variable">$CPATH</span>"</span>
<span class="hljs-built_in">export</span> PKG_CONFIG_PATH=<span class="hljs-string">"/opt/homebrew/lib/pkgconfig:<span class="hljs-variable">$PKG_CONFIG_PATH</span>"</span>
</code></pre>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>Article Golang code repository: <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/tensorflow">https://github.com/flashlabs/kiss-samples/tree/main/tensorflow</a></p>
</li>
<li><p>Image classification model: <a target="_blank" href="https://www.kaggle.com/models/google/mobilenet-v2/tensorFlow2">https://www.kaggle.com/models/google/mobilenet-v2/tensorFlow2</a></p>
</li>
<li><p>Sample images: <a target="_blank" href="https://www.kaggle.com/datasets/kkhandekar/object-detection-sample-images">https://www.kaggle.com/datasets/kkhandekar/object-detection-sample-images</a></p>
</li>
<li><p>ImageNet labels: <a target="_blank" href="https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt">https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt</a></p>
</li>
<li><p>TensorFlow: <a target="_blank" href="https://www.tensorflow.org/guide/saved_model">https://www.tensorflow.org/</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Fuzzing a Go REST API Handler]]></title><description><![CDATA[Fuzzing

Fuzzing is a type of automated testing which continuously manipulates inputs to a program to find bugs. Go fuzzing uses coverage guidance to intelligently walk through the code being fuzzed to find and report failures to the user. Since it c...]]></description><link>https://blog.skopow.ski/fuzzing-a-go-rest-api-handler</link><guid isPermaLink="true">https://blog.skopow.ski/fuzzing-a-go-rest-api-handler</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Thu, 27 Mar 2025 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/sa3YL5-70x0/upload/2bddab1e691d31f61c53c96aafacf83e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-fuzzing">Fuzzing</h1>
<blockquote>
<p>Fuzzing is a type of automated testing which continuously manipulates inputs to a program to find bugs. Go fuzzing uses coverage guidance to intelligently walk through the code being fuzzed to find and report failures to the user. Since it can reach edge cases which humans often miss, fuzz testing can be particularly valuable for finding security exploits and vulnerabilities.</p>
<p><a target="_blank" href="https://go.dev/doc/security/fuzz/">https://go.dev/doc/security/fuzz/</a></p>
</blockquote>
<p>Mostly fuzzing is being used in parsers, formatters, decoders, network handlers and other security-critical code.</p>
<p>It helps to find:</p>
<ul>
<li><p>panics</p>
</li>
<li><p>nil pointer dereferences</p>
</li>
<li><p>memleaks</p>
</li>
<li><p>unhandled errors</p>
</li>
<li><p>unexpected behaviors</p>
</li>
<li><p>security vulnerabilities (like buffer overflow or infinite loops)</p>
</li>
</ul>
<p>But hey, what about the web API’s? The REST endpoints are full of user input handling and this is the perfect place for fuzzing!</p>
<h1 id="heading-the-handler">The Handler</h1>
<p>Let’s assume we have an endpoint responsible for a user creation. The input payload comes in the <code>JSON</code> format:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">CreateUserHandler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    <span class="hljs-keyword">var</span> req <span class="hljs-keyword">struct</span> {
        Username <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"username"`</span>
        Age      <span class="hljs-keyword">int</span>    <span class="hljs-string">`json:"age"`</span>
    }

    err := json.NewDecoder(r.Body).Decode(&amp;req)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Invalid request"</span>, http.StatusBadRequest)

        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">if</span> req.Age &lt; <span class="hljs-number">0</span> {
        http.Error(w, <span class="hljs-string">"Invalid age"</span>, http.StatusBadRequest)

        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// Create user logic goes here.</span>

    fmt.Fprintf(w, <span class="hljs-string">"User %s created successfully"</span>, req.Username)
}
</code></pre>
<p>The happy path is very simple, we decode the input payload into the <code>struct</code>, run some validation and register a user in the system.</p>
<p>As you can see there’s nothing special, but the <code>JSON</code> encoding part might get tricky. The <code>JSON</code> payload might be malformed, might cause unexpected results in further processing.</p>
<p>Would be nice to have something extra that will cover our handler with extra tests.</p>
<h1 id="heading-the-fuzz-test">The Fuzz Test</h1>
<p>The fuzz test in its basic form is an abstract layer over the regular <code>*testing.T</code> framework - we’re going to use the <code>*testing.F</code> input param.</p>
<p>There are couple of requirements for the fuzz test to work, like the name has to follow the pattern <code>FuzzName</code>, you can read more on it <a target="_blank" href="https://go.dev/doc/security/fuzz/#requirements">here</a>.</p>
<p>Let’s focus on our basic fuzz test:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">FuzzCreateUserHandler</span><span class="hljs-params">(f *testing.F)</span></span> {
    <span class="hljs-comment">// Seed corpus with a valid JSON input</span>
    f.Add(<span class="hljs-string">`{"username":"testuser","age":30}`</span>)

    f.Fuzz(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T, body <span class="hljs-keyword">string</span>)</span></span> {
        req := httptest.NewRequest(http.MethodPost, <span class="hljs-string">"/users"</span>, strings.NewReader(body))
        req.Header.Set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)

        w := httptest.NewRecorder()

        CreateUserHandler(w, req)

        <span class="hljs-keyword">if</span> w.Result().StatusCode == http.StatusInternalServerError {
            t.Errorf(<span class="hljs-string">"Unexpected internal server error for input: %q"</span>, body)
        }
    })
}
</code></pre>
<p>What’s happening in our test:</p>
<ul>
<li><p>We’re feeding our seed corpus with some valid test cases</p>
</li>
<li><p>Go’s fuzzer will mutate the input: remove/replace fields, add random bytes, change numbers, etc.</p>
</li>
<li><p>We’re asserting that if the result’s <code>StatusCode</code> is <code>http.StatusInternalServerError</code> then the test should fail</p>
</li>
</ul>
<p>The more sophisticated fuzz test, that will check for <code>panic</code> would be:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">FuzzCreateUserHandler</span><span class="hljs-params">(f *testing.F)</span></span> {
    <span class="hljs-comment">// Seed corpus with a valid JSON input</span>
    f.Add(<span class="hljs-string">`{"username":"testuser","age":30}`</span>)

    f.Fuzz(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T, body <span class="hljs-keyword">string</span>)</span></span> {
        <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
            <span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r != <span class="hljs-literal">nil</span> {
                t.Fatalf(<span class="hljs-string">"Handler panicked with input %q: %v"</span>, body, r)
            }
        }()

        req := httptest.NewRequest(http.MethodPost, <span class="hljs-string">"/users"</span>, strings.NewReader(body))
        req.Header.Set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)

        w := httptest.NewRecorder()

        CreateUserHandler(w, req)

        resp := w.Result()
        <span class="hljs-keyword">defer</span> resp.Body.Close()

        <span class="hljs-keyword">if</span> resp.StatusCode == http.StatusInternalServerError {
            t.Errorf(<span class="hljs-string">"Unexpected 500 Internal Server Error for input: %q"</span>, body)
        }
    })
}
</code></pre>
<p>As you can see we’re using the <code>recover()</code> function that is checked at the end of the function call to see if there were any unexpected panics.</p>
<p>What you will assert is only up to you, the sky is the limit.</p>
<h1 id="heading-run-fuzz-test">Run Fuzz Test</h1>
<p>Okay, but how to run this test?</p>
<p>It’s simple, the regular tests you run <code>go test ./…</code> - the same way you can run the fuzz test btw (but limited to the regular execution - without fuzzing).</p>
<p>The fuzz test, you run:</p>
<pre><code class="lang-bash">go <span class="hljs-built_in">test</span> -fuzz=Fuzz -fuzztime 10s
</code></pre>
<p>Please note the <code>-fuzztime</code> param. The fuzz tests can run really long, if you want to limit its execution time, this is the way.</p>
<p>The typical fuzz test output is similar to this:</p>
<pre><code class="lang-bash">go <span class="hljs-built_in">test</span> -fuzz=Fuzz -fuzztime 10s
fuzz: elapsed: 0s, gathering baseline coverage: 0/411 completed
fuzz: elapsed: 0s, gathering baseline coverage: 411/411 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 244934 (81632/sec), new interesting: 0 (total: 411)
fuzz: elapsed: 6s, execs: 513074 (89363/sec), new interesting: 3 (total: 414)
fuzz: elapsed: 9s, execs: 785859 (90958/sec), new interesting: 4 (total: 415)
fuzz: elapsed: 10s, execs: 882683 (89005/sec), new interesting: 5 (total: 416)
PASS
ok      github.com/flashlabs/kiss-samples/fuzzing    10.445s
</code></pre>
<p>What does those sections even mean?</p>
<ul>
<li><p><code>gathering baseline coverage</code> - before Go starts throwing random data at your code, it first runs your fuzz target with all the seeded inputs in your corpus.</p>
</li>
<li><p><code>gathering baseline coverage: 0/411 completed</code> - it means that Go has prepared a 411 inputs and run 0 out of it</p>
</li>
<li><p><code>gathering baseline coverage: 411/411 completed</code> - now it’s done. Go knows what your code coverage looks like <em>before</em> fuzzing</p>
</li>
<li><p><code>now fuzzing with 8 workers</code> - Go is launching 8 parallel processes in the background (8 goroutines) that will now work on our <code>input</code>, mutate it and feed it to our test function, looking for</p>
<ul>
<li><p>crashes</p>
</li>
<li><p>panics</p>
</li>
<li><p>failed assertions</p>
</li>
</ul>
</li>
<li><p><code>new interesting: 3 (total: 414)</code> - Go has found 3 new inputs that increase the code coverage, total corpus is now 414 (411 + 3)</p>
<ul>
<li>keep in mind that since those input doesn’t trigger the <code>t.Fatalf()</code> or <code>t.Errorf()</code> and there’s no panic they’re considered safe and a run is considered a successfull one</li>
</ul>
</li>
</ul>
<p>The logic for the new inputs and the code coverage:</p>
<pre><code class="lang-bash">┌────────────────────────────────────────────┐
│  New input found                           │
└────────────────────────────────────────────┘
               │
               ▼
┌────────────────────────────┐
│ Does it increase coverage? │───► YES ──► Corpus grows
└────────────────────────────┘
               │
               ▼
            NO │
               ▼
     Input is discarded
</code></pre>
<p>Not every interesting input is a bug — fuzzers also collect inputs that increase code coverage. These inputs expand the test corpus, helping the fuzzer explore more paths over time.</p>
<h1 id="heading-summary">Summary</h1>
<p>While fuzzing is often associated with lower-level data parsing, it’s a powerful tool for web APIs too — especially since every API endpoint is essentially a parser of user-controlled input.</p>
<p>You need to experiment locally with different assertions and fuzzing times.</p>
<p>The full source code for this example with the <code>http</code> server you can find on the <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/fuzzing">https://github.com/flashlabs/kiss-samples/tree/main/fuzzing</a></p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p><a target="_blank" href="https://go.dev/doc/tutorial/fuzz">https://go.dev/doc/tutorial/fuzz</a></p>
</li>
<li><p><a target="_blank" href="https://go.dev/doc/security/fuzz/">https://go.dev/doc/security/fuzz/</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/fuzzing">https://github.com/flashlabs/kiss-samples/tree/main/fuzzing</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Definition of Ready vs Definition of Done]]></title><description><![CDATA[Problem
Both concepts are strictly related to Scrum, although only one - the DoD - is defined by it.
The confusion in understanding the differences is related to the “Ready” and “Done” being similar in meaning.
To move further we need to introduce th...]]></description><link>https://blog.skopow.ski/definition-of-ready-vs-definition-of-done</link><guid isPermaLink="true">https://blog.skopow.ski/definition-of-ready-vs-definition-of-done</guid><category><![CDATA[Scrum]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sat, 15 Feb 2025 12:03:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/fMbRKk2la0s/upload/988f59b8fbac777d1295f7abd53fef03.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-problem">Problem</h1>
<p>Both concepts are strictly related to Scrum, although only one - the DoD - is defined by it.</p>
<p>The confusion in understanding the differences is related to the “Ready” and “Done” being similar in meaning.</p>
<p>To move further we need to introduce the third term: PBI - the Product Backlog Item, which is a set of business criteria to be developed.</p>
<p>The regular flow is that the PBI waits for its turn to be picked by the development team for the development. When it’s complete, it’s considered as done.</p>
<p>Knowing all of that, we can move forward.</p>
<h1 id="heading-definition-of-ready">Definition of Ready</h1>
<p>The Definition of Ready is a set of criteria for the PBI <strong>to be picked by the developmnet team</strong> for the development.</p>
<p>The criteria are defined by the team and usually come down to:</p>
<ul>
<li><p><strong>Who</strong> is the addressee of the PBI</p>
</li>
<li><p><strong>What</strong> does the owner wants to achieve</p>
</li>
<li><p><strong>Why</strong> this is needed</p>
</li>
<li><p>And what are the <strong>Acceptance Criteria</strong></p>
</li>
</ul>
<h1 id="heading-definition-of-done">Definition of Done</h1>
<p>Usually the DoD is not just “the end of the development”, this should mean that the PBI is <strong>really DONE and is bringing a VALUE</strong>.</p>
<p>The typical DoD criteria might be as follows:</p>
<ul>
<li><p>The <strong>development</strong> is done</p>
</li>
<li><p>The <strong>tests</strong> are covering business use cases</p>
</li>
<li><p>The feature is being <strong>released</strong></p>
</li>
</ul>
<p>This is basically the end of life for the PBI.</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>The Power of Jira Ticket: <a target="_blank" href="https://blog.skopow.ski/the-power-of-jira-ticket">https://blog.skopow.ski/the-power-of-jira-ticket</a></p>
</li>
<li><p>Scrum: <a target="_blank" href="https://www.scrum.org/">https://www.scrum.org/</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to validate a payload schema]]></title><description><![CDATA[Problem
Incoming request payloads in JSON format often requires validation.
Validators might get complex and messy.
We don’t want our codebase to be messy - at least we try not to.
Solution
JSON schema
The best way is to convert our JSON representati...]]></description><link>https://blog.skopow.ski/how-to-validate-a-payload-schema</link><guid isPermaLink="true">https://blog.skopow.ski/how-to-validate-a-payload-schema</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[json]]></category><category><![CDATA[json-schema]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Fri, 24 Jan 2025 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/_XXNjSziZuA/upload/af13a2a5b9478d72d0412b6bc0e83c01.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-problem">Problem</h1>
<p>Incoming request payloads in <code>JSON</code> format often requires validation.</p>
<p>Validators might get complex and messy.</p>
<p>We don’t want our codebase to be messy - at least we try not to.</p>
<h1 id="heading-solution">Solution</h1>
<h2 id="heading-json-schema">JSON schema</h2>
<p>The best way is to convert our <code>JSON</code> representation of the incoming request into the <code>JSON</code> schema with the help of one of the multiple converters online. I was using the <a target="_blank" href="https://www.liquid-technologies.com/online-json-to-schema-converter">Free Online JSON to JSON Schema Converter</a>.</p>
<p>From the input <code>JSON</code> payload:</p>
<pre><code class="lang-bash">{<span class="hljs-string">"id"</span>:<span class="hljs-string">"022eb326-6fa2-4932-8a5f-058a908616a4"</span>}
</code></pre>
<p>We’re getting the <code>JSON</code> shema that looks like this for the mentioned input:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"<span class="hljs-variable">$schema</span>"</span>: <span class="hljs-string">"http://json-schema.org/draft-04/schema#"</span>,
  <span class="hljs-string">"type"</span>: <span class="hljs-string">"object"</span>,
  <span class="hljs-string">"properties"</span>: {
    <span class="hljs-string">"id"</span>: {
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"string"</span>
    }
  },
  <span class="hljs-string">"required"</span>: [
    <span class="hljs-string">"id"</span>
  ]
}
</code></pre>
<h2 id="heading-schema-validation">Schema validation</h2>
<p>Now let’s get to the code.</p>
<p>The <strong>validation</strong> function:</p>
<pre><code class="lang-go"><span class="hljs-comment">// ValidateSchema validates payload against JSON schema located in the schema file.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ValidateSchema</span><span class="hljs-params">(payload <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]any, schema <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// Let's read the schema file</span>
    file, err := os.ReadFile(schema)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"os.ReadFile: %w"</span>, err)
    }

    <span class="hljs-comment">// Prepare validators for schema...</span>
    schemaLoader := gojsonschema.NewStringLoader(<span class="hljs-keyword">string</span>(file))
    <span class="hljs-comment">// ... and payload.</span>
    payloadLoader := gojsonschema.NewGoLoader(payload)

    <span class="hljs-comment">// Validate schema against payload:</span>
    result, err := gojsonschema.Validate(schemaLoader, payloadLoader)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"gojsonschema.Validate: %w"</span>, err)
    }

    <span class="hljs-comment">// If there was something wrong, communicate the errors:</span>
    <span class="hljs-keyword">if</span> !result.Valid() {
        errMsg := <span class="hljs-string">"JSON validation failed:\n"</span>
        <span class="hljs-keyword">for</span> _, desc := <span class="hljs-keyword">range</span> result.Errors() {
            errMsg += fmt.Sprintf(<span class="hljs-string">"- %s\n"</span>, desc)
        }
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"%w: %s"</span>, ErrInvalidPayload, errMsg)
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<h2 id="heading-working-application">Working application</h2>
<p>Let’s put all the piecen together (file <code>main.go</code>):</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"errors"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/google/uuid"</span>
    <span class="hljs-string">"github.com/xeipuuv/gojsonschema"</span>
)

<span class="hljs-keyword">var</span> ErrInvalidPayload = errors.New(<span class="hljs-string">"invalid payload"</span>)

<span class="hljs-keyword">type</span> RequestPayload <span class="hljs-keyword">struct</span> {
    ID uuid.UUID <span class="hljs-string">`json:"id"`</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Request payload.</span>
    p, err := payload()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    }

    <span class="hljs-comment">// Validate incoming payload.</span>
    <span class="hljs-keyword">if</span> err = ValidateSchema(p, <span class="hljs-string">"schema/payload.json"</span>); err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    }

    <span class="hljs-comment">// We're good!</span>
    fmt.Println(<span class="hljs-string">"Schema validated"</span>)
}

<span class="hljs-comment">// ValidateSchema validates payload against JSON schema located in the schema file.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ValidateSchema</span><span class="hljs-params">(payload <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]any, schema <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-comment">// Let's read the schema file</span>
    file, err := os.ReadFile(schema)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"os.ReadFile: %w"</span>, err)
    }

    <span class="hljs-comment">// Prepare validators for schema...</span>
    schemaLoader := gojsonschema.NewStringLoader(<span class="hljs-keyword">string</span>(file))
    <span class="hljs-comment">// ... and payload.</span>
    payloadLoader := gojsonschema.NewGoLoader(payload)

    <span class="hljs-comment">// Validate schema against payload:</span>
    result, err := gojsonschema.Validate(schemaLoader, payloadLoader)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"gojsonschema.Validate: %w"</span>, err)
    }

    <span class="hljs-comment">// If there was something wrong, communicate the errors:</span>
    <span class="hljs-keyword">if</span> !result.Valid() {
        errMsg := <span class="hljs-string">"JSON validation failed:\n"</span>
        <span class="hljs-keyword">for</span> _, desc := <span class="hljs-keyword">range</span> result.Errors() {
            errMsg += fmt.Sprintf(<span class="hljs-string">"- %s\n"</span>, desc)
        }
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"%w: %s"</span>, ErrInvalidPayload, errMsg)
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-comment">// payload returns sample request payload in map[string]any format.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">payload</span><span class="hljs-params">()</span> <span class="hljs-params">(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]any, error)</span></span> {
    r := &amp;RequestPayload{ID: uuid.New()}

    payload, err := json.Marshal(r)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"json.Marshal: %w"</span>, err)
    }

    <span class="hljs-keyword">var</span> data <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]any
    <span class="hljs-keyword">if</span> err := json.Unmarshal(payload, &amp;data); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"json.Unmarshal: %w"</span>, err)
    }

    <span class="hljs-keyword">return</span> data, <span class="hljs-literal">nil</span>
}
</code></pre>
<pre><code class="lang-bash">➜  payloadschema git:(main) ✗ go run main.go
Schema validated
</code></pre>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p><a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/payloadschema">Go code used in this article - Flashlabs’ KISS samples</a></p>
</li>
<li><p><a target="_blank" href="https://www.liquid-technologies.com/online-json-to-schema-converter">Free Online JSON to JSON Schema Converter</a></p>
</li>
<li><p><a target="_blank" href="https://www.uuidgenerator.net/">UUID Generator</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Post Mortem]]></title><description><![CDATA[The Blame Culture

You’re being afraid of making a production deployment because when something goes wrong it will be your fault?

You’re feeling stressed before joining a meeting because you might get accused for a project/product/initiative failure...]]></description><link>https://blog.skopow.ski/post-mortem</link><guid isPermaLink="true">https://blog.skopow.ski/post-mortem</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 15 Dec 2024 13:22:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/TamMbr4okv4/upload/18d920af6c807802569fe877b9cc302d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-the-blame-culture">The Blame Culture</h1>
<ul>
<li><p>You’re being afraid of making a production deployment because when something goes wrong it will be your fault?</p>
</li>
<li><p>You’re feeling stressed before joining a meeting because you might get accused for a project/product/initiative failure?</p>
</li>
<li><p>Someone can abuse you directly because of any work-related activity?</p>
</li>
</ul>
<p>If your answer to any of the questions above is “yes”, then “congratulations”, you’re working in a Blame Culture environment that indicates your company’s failure.</p>
<h1 id="heading-what-is-post-portem">What is Post Portem</h1>
<p>The Post Mortem:</p>
<blockquote>
<p>(…) is a process used to identify the causes of a (…) failure (…), and how to prevent them in the future. [1]</p>
</blockquote>
<p>It’s that simple! This is a process where we can focus on <strong>what</strong> has gone wrong and <strong>why</strong> and this will lead us to the conclusion - <strong>how</strong> we can avoid similar situations in the future.</p>
<p>In most cases, Post Mortem analysis is a process based on a simple document format in which we provide:</p>
<ul>
<li><p>a short description of the event</p>
</li>
<li><p>how it happened</p>
</li>
<li><p>and what steps we intend to take in the future to prevent a similar situation.</p>
</li>
</ul>
<p>Often, there is also a place in such a document to list the members of the team that the incident concerns. Important - we include members of the entire team working on the initiative, not the unit directly related to the incident. This is mainly done so that more details about the situation/incident can be obtained later. In the future, when similar challenges are taken on, these members will usually be more experienced and will know better how to effectively lead/support such initiatives.</p>
<h1 id="heading-modern-software-development">Modern Software Development</h1>
<p>In a Modern Software Development which often uses agile and lean methodologies that are focused on the outcome, iteration, product requirements and collaboration, there’s no place for the Blame Culture.</p>
<p>Good software is being created by the people who can trust and respect each other. All team members work for the success of the product. Everyone is working to the best of their ability and we must assume they are acting in good faith. No one introduces bugs intentionally and no one sabotages our product.</p>
<p>If something isn't working, if errors occur, it means we need to focus on improving our processes that allowed this to happen.</p>
<p>Keep your work environment clean - get rid of the Blame Culture!</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>Blame in organizations - <a target="_blank" href="https://en.wikipedia.org/wiki/Blame_in_organizations">https://en.wikipedia.org/wiki/Blame_in_organizations</a> [1]</p>
</li>
<li><p>Postmortem documentation - <a target="_blank" href="https://en.wikipedia.org/wiki/Postmortem_documentation">https://en.wikipedia.org/wiki/Postmortem_documentation</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to send a PDF file to Claude.ai with Go SDK]]></title><description><![CDATA[PDF Support
Couple days ago Alex Albert from Anthropic announced that they’ve rolled out a PDF support directly in prompt’s message. Until now it was possible to upload a PDF documents only in the Claude’s interface, but wasn’t from the API’s PoV - y...]]></description><link>https://blog.skopow.ski/how-to-send-a-pdf-file-to-claudeai-with-go-sdk</link><guid isPermaLink="true">https://blog.skopow.ski/how-to-send-a-pdf-file-to-claudeai-with-go-sdk</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[llm]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[sdk]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Mon, 11 Nov 2024 14:25:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XN4T2PVUUgk/upload/a4c451ad2e3cce8873f025df47bb257b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-pdf-support">PDF Support</h1>
<p>Couple days ago <a target="_blank" href="https://x.com/alexalbert__">Alex Albert</a> from <a target="_blank" href="https://x.com/AnthropicAI">Anthropic</a> <a target="_blank" href="https://x.com/alexalbert__/status/1852393994892042561">announced</a> that they’ve rolled out a <code>PDF</code> support directly in prompt’s message. Until now it was possible to upload a <code>PDF</code> documents only in the Claude’s interface, but wasn’t from the <code>API</code>’s PoV - you had to extract data manually and provide it in the prompt’s payload (the same way as the Anthropic was doing when accepting documents from the UI requests).</p>
<p>As always you can find a code snippets on how to manage this use case <a target="_blank" href="https://docs.anthropic.com/en/docs/build-with-claude/pdf-support">directly on the Anthropic website</a>. Unfortunatelly there’re only examples for <code>Shell</code>, <code>Python</code> and <code>TypeScript</code>. If you’re using <code>anthropic-sdk-go</code> <a target="_blank" href="https://github.com/anthropics/anthropic-sdk-go">for Golang</a>, there’s lack of such examples. That’s the reason for this article.</p>
<h1 id="heading-pdf-in-go-sdk">PDF in Go SDK</h1>
<p>Install Anthropic <code>SDK</code>:</p>
<pre><code class="lang-bash">go get -u <span class="hljs-string">'github.com/anthropics/anthropic-sdk-go@v0.2.0-alpha.4'</span>
</code></pre>
<p>Initialize Anthropic client with the <code>anthropic-beta</code> header:</p>
<pre><code class="lang-go">client := anthropic.NewClient(
    option.WithAPIKey(<span class="hljs-string">"API_KEY"</span>),
    option.WithHeader(<span class="hljs-string">"anthropic-beta"</span>, <span class="hljs-string">"pdfs-2024-09-25"</span>),
)
</code></pre>
<p>The <code>API_KEY</code> you can obtain in the <a target="_blank" href="https://console.anthropic.com/login?returnTo=%2F%3F">Antrhopic Console</a>.</p>
<p>Convert a <code>PDF</code> document into the <code>base64</code> format string:</p>
<pre><code class="lang-go">file, err := os.Open(<span class="hljs-string">"./dogs.pdf"</span>)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    <span class="hljs-built_in">panic</span>(fmt.Errorf(<span class="hljs-string">"failed to open file: %w"</span>, err))
}

fileBytes, err := io.ReadAll(file)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    <span class="hljs-built_in">panic</span>(err)
}

fileEncoded := base64.StdEncoding.EncodeToString(fileBytes)
</code></pre>
<p>Send a document altogether with a prompt via <code>SDK</code>:</p>
<pre><code class="lang-go">message, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
    MaxTokens: anthropic.Int(<span class="hljs-number">1024</span>),
    Messages: anthropic.F([]anthropic.BetaMessageParam{{
        Role: anthropic.F(anthropic.BetaMessageParamRoleUser),
        Content: anthropic.F(
            []anthropic.BetaContentBlockParamUnion{
                anthropic.BetaTextBlockParam{
                    Text: anthropic.F(content),
                    Type: anthropic.F(anthropic.BetaTextBlockParamTypeText),
                },
                anthropic.BetaBase64PDFBlockParam{
                    Source: anthropic.F(anthropic.BetaBase64PDFSourceParam{
                        Data:      anthropic.F(fileEncoded),
                        MediaType: anthropic.F[anthropic.BetaBase64PDFSourceMediaType](anthropic.BetaBase64PDFSourceMediaTypeApplicationPDF),
                        Type:      anthropic.F[anthropic.BetaBase64PDFSourceType](<span class="hljs-string">"base64"</span>),
                    }),
                    Type: anthropic.F(anthropic.BetaBase64PDFBlockTypeDocument),
                },
            },
        ),
    }}),
    Model: anthropic.F(anthropic.ModelClaude3_5Sonnet20241022),
})
</code></pre>
<p>Be sure to use the model <code>anthropic.ModelClaude3_5Sonnet20241022</code>!</p>
<p>And that’s it!</p>
<h1 id="heading-working-example">Working example</h1>
<p>Here’s the full working example:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"encoding/base64"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"io"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/anthropics/anthropic-sdk-go"</span>
    <span class="hljs-string">"github.com/anthropics/anthropic-sdk-go/option"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    client := anthropic.NewClient(
        option.WithAPIKey(<span class="hljs-string">"API_KEY"</span>),
        option.WithHeader(<span class="hljs-string">"anthropic-beta"</span>, <span class="hljs-string">"pdfs-2024-09-25"</span>),
    )

    content := <span class="hljs-string">"How many dogs are in the attached document?"</span>

    <span class="hljs-built_in">println</span>(<span class="hljs-string">"[user]: "</span> + content)

    file, err := os.Open(<span class="hljs-string">"./dogs.pdf"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(fmt.Errorf(<span class="hljs-string">"failed to open file: %w"</span>, err))
    }

    fileBytes, err := io.ReadAll(file)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    fileEncoded := base64.StdEncoding.EncodeToString(fileBytes)

    message, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
        MaxTokens: anthropic.Int(<span class="hljs-number">1024</span>),
        Messages: anthropic.F([]anthropic.BetaMessageParam{{
            Role: anthropic.F(anthropic.BetaMessageParamRoleUser),
            Content: anthropic.F(
                []anthropic.BetaContentBlockParamUnion{
                    anthropic.BetaTextBlockParam{
                        Text: anthropic.F(content),
                        Type: anthropic.F(anthropic.BetaTextBlockParamTypeText),
                    },
                    anthropic.BetaBase64PDFBlockParam{
                        Source: anthropic.F(anthropic.BetaBase64PDFSourceParam{
                            Data:      anthropic.F(fileEncoded),
                            MediaType: anthropic.F[anthropic.BetaBase64PDFSourceMediaType](anthropic.BetaBase64PDFSourceMediaTypeApplicationPDF),
                            Type:      anthropic.F[anthropic.BetaBase64PDFSourceType](<span class="hljs-string">"base64"</span>),
                        }),
                        Type: anthropic.F(anthropic.BetaBase64PDFBlockTypeDocument),
                    },
                },
            ),
        }}),
        Model: anthropic.F(anthropic.ModelClaude3_5Sonnet20241022),
    })
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    <span class="hljs-built_in">println</span>(<span class="hljs-string">"[assistant]: "</span> + message.Content[<span class="hljs-number">0</span>].Text + message.StopSequence)
}
</code></pre>
<p>The output should looks like this:</p>
<pre><code class="lang-bash">➜  pdf2claude git:(main) ✗ go run main.go
[user]: How many dogs are <span class="hljs-keyword">in</span> the attached document?
[assistant]: In this document, there are 5 dogs shown <span class="hljs-keyword">in</span> black and white silhouette/portrait style. The breeds appear to be:

1. What appears to be a Jack Russell Terrier or similar mixed breed (top left)
2. An Australian Shepherd or similar long-haired herding dog (top right)
3. A French Bulldog (bottom left)
4. What looks like a Jack Russell Terrier or similar terrier breed (bottom middle)
5. A Chihuahua (bottom right)

The images are stylized <span class="hljs-keyword">in</span> a high contrast black and white design, showing distinctive features of each breed.
</code></pre>
<p>The complete example you can find <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/pdf2claude">here on the GitHub</a>.</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li><p>Twitter: <a target="_blank" href="https://x.com/alexalbert__/status/1852393994892042561">https://x.com/alexalbert__/status/1852393994892042561</a></p>
</li>
<li><p>Anthropic: <a target="_blank" href="https://docs.anthropic.com/en/docs/build-with-claude/pdf-support">https://docs.anthropic.com/en/docs/build-with-claude/pdf-support</a></p>
</li>
<li><p>SDK: <a target="_blank" href="https://github.com/anthropics/anthropic-sdk-go">https://github.com/anthropics/anthropic-sdk-go</a></p>
</li>
<li><p>Code: <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/pdf2claude">https://github.com/flashlabs/kiss-samples/tree/main/pdf2claude</a></p>
</li>
<li><p>PDF document: <a target="_blank" href="https://www.templatesarea.com/free-dog-vector-illustrations-svg-png-pdf-design-diy/">https://www.templatesarea.com/free-dog-vector-illustrations-svg-png-pdf-design-diy/</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Interface compliance at compile-time in Go]]></title><description><![CDATA[Interfaces
Imagine having an interface called Runner that looks like this:
type Runner interface {
    Run() error
}

And a struct called Worker that you want to implement the mentioned interface. What you need to do? Ofc the only thing required is t...]]></description><link>https://blog.skopow.ski/interface-compliance-at-compile-time-in-go</link><guid isPermaLink="true">https://blog.skopow.ski/interface-compliance-at-compile-time-in-go</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Go Language]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sat, 19 Oct 2024 12:44:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/-6GinD-1NwE/upload/fc0fbbfff2cb574e0f850bc511cedde9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-interfaces">Interfaces</h1>
<p>Imagine having an <code>interface</code> called <code>Runner</code> that looks like this:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Runner <span class="hljs-keyword">interface</span> {
    Run() error
}
</code></pre>
<p>And a <code>struct</code> called <code>Worker</code> that you want to implement the mentioned interface. What you need to do? Ofc the only thing required is to add a <code>Run</code> function according to the interface:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Worker <span class="hljs-keyword">struct</span> {
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(w Worker)</span> <span class="hljs-title">Run</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<p>Voilà! Now your <code>Worker</code> struct implements the <code>Runner</code> interface.</p>
<p>The whole program now looks like this:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-keyword">type</span> Runner <span class="hljs-keyword">interface</span> {
    Run() error
}

<span class="hljs-keyword">type</span> Worker <span class="hljs-keyword">struct</span> {
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(w Worker)</span> <span class="hljs-title">Run</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    w := Worker{}

    fmt.Println(<span class="hljs-string">"worker"</span>, w)
}
</code></pre>
<p>You can run your program and everything is fine:</p>
<pre><code class="lang-bash">➜  interfaces git:(main) ✗ go run main.go
worker {}
</code></pre>
<h1 id="heading-interface-mutation-problem">Interface Mutation → Problem</h1>
<p>Okay, but what will happen if you change the <code>Runner</code> <code>interface</code>?</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Runner <span class="hljs-keyword">interface</span> {
    Run() error
    Stop() error
}
</code></pre>
<p>Well, you run the program and… nothing happens. Program runs successfully, but <code>Worker</code> is no longer compliant with the <code>Runner</code> interface, bc it’s missing the <code>Stop</code> function:</p>
<pre><code class="lang-bash">➜  interfaces git:(main) ✗ go run main.go
worker {}
</code></pre>
<h1 id="heading-compile-time-compliance">Compile-time Compliance</h1>
<p>How to be sure that your struct implements the interface at a complie-time? That’s easy! You just need to add this line:</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> _ Runner = (*Worker)(<span class="hljs-literal">nil</span>)
</code></pre>
<p>What it does, you ask?</p>
<ul>
<li><p>The underscore <code>_</code> is used only as a blank identifier, means to ignore the value.</p>
</li>
<li><p>The <code>Runner</code> is the <code>interface</code> that we want to check.</p>
</li>
<li><p>And the right side of the assignement is a type conversion of <code>nil</code> to a <code>pointer</code> of the <code>Worker</code> struct.</p>
</li>
</ul>
<p>Thanks to this we enforce at the compile-time that the given type (<code>Worker</code>) implements the <code>interface</code> (<code>Runner</code>).</p>
<p>Having the <code>main</code> packages like this (with the missing <code>Stop</code> function in the <code>Worker</code>):</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-keyword">type</span> Runner <span class="hljs-keyword">interface</span> {
    Run() error
    Stop() error
}

<span class="hljs-keyword">var</span> _ Runner = (*Worker)(<span class="hljs-literal">nil</span>)

<span class="hljs-keyword">type</span> Worker <span class="hljs-keyword">struct</span> {
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(w Worker)</span> <span class="hljs-title">Run</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    w := Worker{}

    fmt.Println(<span class="hljs-string">"worker"</span>, w)
}
</code></pre>
<p>When we try to run this program:</p>
<pre><code class="lang-bash">➜  interfaces git:(main) ✗ go run main.go
<span class="hljs-comment"># command-line-arguments</span>
./main.go:10:16: cannot use (*Worker)(nil) (value of <span class="hljs-built_in">type</span> *Worker) as Runner value <span class="hljs-keyword">in</span> variable declaration: *Worker does not implement Runner (missing method Stop)
</code></pre>
<p>We got a compile-time error.</p>
<p>Let’s fix this quickly by adding a missing <code>Stop</code> function:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(w Worker)</span> <span class="hljs-title">Stop</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<p>And we’re good:</p>
<pre><code class="lang-bash">➜  kiss-samples git:(main) ✗ go run main.go
worker {}
</code></pre>
<p>That’s it! Hope you like it and use it in your own projects!</p>
<h1 id="heading-sources">Sources</h1>
<ul>
<li>Code samples: <a target="_blank" href="https://github.com/flashlabs/kiss-samples/tree/main/interfaces">https://github.com/flashlabs/kiss-samples/tree/main/interfaces</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Start Profiling a Golang Application]]></title><description><![CDATA[What is an app profiling?
In general, the application profiling is the process of analyzing the behavior and performance of an app to identify its bottlenecks, inefficiencies, and areas for optimization. This usually involves collecting detailed info...]]></description><link>https://blog.skopow.ski/how-to-start-profiling-a-golang-application</link><guid isPermaLink="true">https://blog.skopow.ski/how-to-start-profiling-a-golang-application</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[software development]]></category><category><![CDATA[profiling]]></category><dc:creator><![CDATA[Marek Skopowski]]></dc:creator><pubDate>Sun, 22 Sep 2024 11:38:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/4D9PHPZ5U_c/upload/3317370633644f323ab493bfc9297f26.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-what-is-an-app-profiling">What is an app profiling?</h1>
<p>In general, the application profiling is the process of analyzing the behavior and performance of an app to identify its bottlenecks, inefficiencies, and areas for optimization. This usually involves collecting detailed information about various aspects of the application, such as:</p>
<ul>
<li><p>CPU and memory usage</p>
</li>
<li><p>Execution time</p>
</li>
<li><p>I/O operations</p>
</li>
<li><p>Threading and concurrency</p>
</li>
<li><p>Resource utilization</p>
</li>
</ul>
<p>Types of collected data might differ between profilers and programming languages.</p>
<h1 id="heading-how-to-profile-your-app">How to profile your app?</h1>
<p>Step 1: Blank import the <code>pprof</code> package for the side effects - it will start collecting the profiling data</p>
<pre><code class="lang-go"><span class="hljs-keyword">import</span> (
    _ <span class="hljs-string">"net/http/pprof"</span>
)
</code></pre>
<p>Step 2: Expose the <code>pprof</code> <code>debug</code> <code>HTTP</code> endpoint:</p>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
    log.Println(http.ListenAndServe(<span class="hljs-string">"localhost:6060"</span>, <span class="hljs-literal">nil</span>))
}()
</code></pre>
<p>And that’s it! You’re good to good! You can access now your profiler at the <a target="_blank" href="http://localhost:6060/debug/pprof/">http://localhost:6060/debug/pprof/</a></p>
<p>Note: Please remember, that your program needs to be running in order to access the profiler at the mentioned link. If it’s terminating after you run it - it doesn’t have a <code>worker</code> nature, that is constantly running until you kill it, just add the following snippet at the end of your <code>main</code> function:</p>
<pre><code class="lang-go">&lt;-<span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> any)

<span class="hljs-comment">// The same as:</span>
<span class="hljs-comment">// ch :=  make(chan any)</span>
<span class="hljs-comment">// &lt;-ch</span>
</code></pre>
<p>This will keep your program running. It creates an empty channel of <code>any</code> type and waits for a data to pull it from. Since no one is sending there anything, the program will keep waiting and running.</p>
<p>Your program already exposes an <code>HTTP</code> server? That’s not a problem! All you need to do is just blank import the <code>pprof</code> package. The <code>pprof</code> handlers will be automatically registered under the <code>/debug/pprof/</code>. Let’s say you have exposed an <code>HTTP</code> server at the port <code>8080</code>, then your <code>pprof</code> debug session will be available at the <a target="_blank" href="http://localhost:6060/debug/pprof/">http://localhost:8080/debug/pprof/</a>.</p>
<p>You don’t have to also block the program termination in such case, because the <code>HTTP</code> server blocks the main goroutine, preventing the program from exiting.</p>
<p>To recap, the whole code looks like this:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>
    _ <span class="hljs-string">"net/http/pprof"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        log.Println(http.ListenAndServe(<span class="hljs-string">"localhost:6060"</span>, <span class="hljs-literal">nil</span>))
    }()

    <span class="hljs-comment">// Your code goes here</span>

    &lt;-<span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> any)
}
</code></pre>
<h1 id="heading-what-does-the-profiler-says">What does the profiler says?</h1>
<p>My profiler is exposed at the <code>6060</code> port, so visiting the URL <a target="_blank" href="http://localhost:6060/debug/pprof/">http://localhost:6060/debug/pprof/</a> I see:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727001188243/768c99de-d07f-44e2-a29a-17647763e09e.png" alt class="image--center mx-auto" /></p>
<p>The profiler data are split into the following sections:</p>
<ul>
<li><p><code>allocs</code>: A sampling of all past memory allocations</p>
</li>
<li><p><code>block</code>: Stack traces that led to blocking on synchronization primitives</p>
</li>
<li><p><code>cmdline</code>: The command line invocation of the current program</p>
</li>
<li><p><code>goroutine</code>: Stack traces of all current goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic.</p>
</li>
<li><p><code>heap</code>: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.</p>
</li>
<li><p><code>mutex</code>: Stack traces of holders of contended mutexes</p>
</li>
<li><p><code>profile</code>: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.</p>
</li>
<li><p><code>threadcreate</code>: Stack traces that led to the creation of new OS threads</p>
</li>
<li><p><code>trace</code>: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.</p>
</li>
</ul>
<p>Feel free to explore the available sections to get to know your app better.</p>
<h1 id="heading-visualizing-profiler-data">Visualizing profiler data</h1>
<p>If you prefer the visual way of analyzing the data, the <code>pprof</code> tool comes for the rescue.</p>
<p>Step 1: Install the <code>pprof</code> tool:</p>
<pre><code class="lang-bash">go install github.com/google/pprof@latest
</code></pre>
<p>Step 2: Install the <code>Graphviz</code> to generate the graphic visualizations of profiles:</p>
<pre><code class="lang-bash">brew install graphviz
</code></pre>
<p>Make sure that the <code>graphviz/bin</code> directory is in your <code>$PATH</code>, otherwise the <code>pprof</code> won’t be able to generate visualizations.</p>
<p>Now, the full example on how to visualize the profile data with the <code>pprof</code> and the <code>graphviz</code>.</p>
<p>I have the following code in the <code>worker</code> package (directory):</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> worker

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"time"</span>
)

<span class="hljs-keyword">const</span> (
    interval = time.Second * <span class="hljs-number">3</span>
)

<span class="hljs-keyword">type</span> worker <span class="hljs-keyword">struct</span> {
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(w worker)</span> <span class="hljs-title">run</span><span class="hljs-params">(ctx context.Context)</span></span> {
    t := time.NewTicker(interval)
    <span class="hljs-keyword">defer</span> t.Stop()

    <span class="hljs-keyword">for</span> {
        <span class="hljs-keyword">select</span> {
        <span class="hljs-keyword">case</span> &lt;-ctx.Done():
        <span class="hljs-comment">// cleanup</span>
        <span class="hljs-keyword">case</span> &lt;-t.C:
            log.Println(<span class="hljs-string">"Worker cycle"</span>)
        }
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Start</span><span class="hljs-params">(ctx context.Context)</span></span> {
    log.Println(<span class="hljs-string">"Starting worker"</span>)

    w := worker{}
    w.run(ctx)
}
</code></pre>
<p>And that’s how my <code>main</code> program looks like now:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>
    _ <span class="hljs-string">"net/http/pprof"</span>

    <span class="hljs-string">"github.com/flashlabs/tools/worker"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        log.Println(http.ListenAndServe(<span class="hljs-string">"localhost:6060"</span>, <span class="hljs-literal">nil</span>))
    }()

    ctx := context.Background()
    worker.Start(ctx)
}
</code></pre>
<p>Now I just start my program:</p>
<pre><code class="lang-bash">go run main.go
2024/09/22 13:29:06 Starting worker
2024/09/22 13:29:09 Worker cycle
2024/09/22 13:29:12 Worker cycle
2024/09/22 13:29:15 Worker cycle
...
</code></pre>
<p>And to visualize the goroutines:</p>
<p>Step 1: Download the goroutine profile and save it in the <code>goroutine_profile.prof</code> file</p>
<pre><code class="lang-bash">curl http://localhost:6060/debug/pprof/goroutine -o goroutine_profile.prof
</code></pre>
<p>Step 2: Run the <code>pprof</code> to analyze and visualize the data in saved file:</p>
<pre><code class="lang-bash">go tool pprof goroutine_profile.prof
</code></pre>
<p>Now you are in the the <code>pprof</code> interactive mode, you can f.e. display the functions and how many goroutines are in each state with the <code>top</code> command:</p>
<pre><code class="lang-bash">go tool pprof goroutine_profile.prof
File: main
Type: goroutine
Time: Sep 22, 2024 at 1:09pm (CEST)
Entering interactive mode (<span class="hljs-built_in">type</span> <span class="hljs-string">"help"</span> <span class="hljs-keyword">for</span> commands, <span class="hljs-string">"o"</span> <span class="hljs-keyword">for</span> options)
(pprof) top
Showing nodes accounting <span class="hljs-keyword">for</span> 5, 100% of 5 total
Showing top 10 nodes out of 36
      flat  flat%   sum%        cum   cum%
         4 80.00% 80.00%          4 80.00%  runtime.gopark
         1 20.00%   100%          1 20.00%  runtime.goroutineProfileWithLabels
         0     0%   100%          1 20.00%  bufio.(*Reader).Peek
         0     0%   100%          1 20.00%  bufio.(*Reader).fill
         0     0%   100%          1 20.00%  github.com/flashlabs/tools/worker.Start
         0     0%   100%          1 20.00%  github.com/flashlabs/tools/worker.worker.run
         0     0%   100%          1 20.00%  internal/poll.(*FD).Accept
         0     0%   100%          2 40.00%  internal/poll.(*FD).Read
         0     0%   100%          3 60.00%  internal/poll.(*pollDesc).<span class="hljs-built_in">wait</span>
         0     0%   100%          3 60.00%  internal/poll.(*pollDesc).waitRead (inline)
</code></pre>
<p>To visualize the same data, just type the <code>web</code> in the <code>pprof</code> interactive mode:</p>
<pre><code class="lang-bash">(pprof) web
</code></pre>
<p>This will generate the visualized call graph of the goroutines and opens it in your browser immediately. The graph will looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727003768056/cc50a341-2b16-4dbf-9234-d27e20e8c8bf.png" alt class="image--center mx-auto" /></p>
<p>For more information on how to read the visualization graphs and how to work with the <code>pprof</code> I strongly recommend to visit the <code>pprof</code> <a target="_blank" href="https://github.com/google/pprof/blob/main/doc/README.md">documentation page</a>.</p>
<h1 id="heading-sources">Sources</h1>
<p>Pprof official page - <a target="_blank" href="https://github.com/google/pprof">https://github.com/google/pprof</a></p>
<p>Profiling Go Programs - <a target="_blank" href="https://go.dev/blog/pprof">https://go.dev/blog/pprof</a></p>
<p>Graphviz home page - <a target="_blank" href="https://www.graphviz.org/">https://www.graphviz.org/</a></p>
]]></content:encoded></item></channel></rss>