<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ryan Armstrong's technical blog</title><description>Systems software, storage and reliability at scale.</description><link>https://hotsrc.dev/</link><atom:link href="https://hotsrc.dev/feed.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 11 Jun 2018 00:00:00 +0000</pubDate><lastBuildDate>Mon, 11 Jun 2018 00:00:00 +0000</lastBuildDate><generator>Hugo 0.165.0</generator><item><title>Dynamic stats in Go's expvar</title><description>&lt;p&gt;Go&amp;rsquo;s expvar package enables a standardized way to publish runtime statistics&#10;for your application. Out of the box, by importing &lt;code&gt;expvar&lt;/code&gt;, you will have a&#10;HTTP handler registered at &amp;ldquo;/debug/vars&amp;rdquo; that will print a whole mess of GC,&#10;memory and commandline stats as a JSON document.&lt;/p&gt;&#10;&lt;p&gt;Expvar includes many primitives for safely setting or incrementing various&#10;static metric types. This works great under normal circumstances, but there are&#10;occasions when you might need to dynamically compute the desired metric value on&#10;demand.&lt;/p&gt;&#10;&lt;p&gt;A key example is &amp;ldquo;uptime&amp;rdquo;. How long has my service been up? Using the&#10;traditional static approach, you would need to update this value periodically&#10;(wasteful) and you risk retrieving an outdated value on request.&lt;/p&gt;&#10;&lt;p&gt;The following code is the simplest way to export an &amp;ldquo;uptime&amp;rdquo; variable that is&#10;computed on demand, using &lt;code&gt;expvar.Func&lt;/code&gt; and a closure function.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;expvar&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;expvar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;uptime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expvar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;:8080&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefaultServeMux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you had more complex needs - say, additional fields to consider - you could&#10;instead implement the &lt;code&gt;expvar.Var&lt;/code&gt; interface&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;expvar&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;strconv&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uptimeVar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// other important fields...&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;uptimeVar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// other things to compute...&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strconv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FormatFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;g&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;uptimeVar&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;expvar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;uptime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;:8080&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefaultServeMux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A possible tradeoff in using dynamic metrics is that you risk introducing&#10;latency to your monitoring tools or health checks if your dynamic metrics are&#10;too compute-heavy or if they depend on I/O.&lt;/p&gt;&#10;</description><pubDate>Mon, 11 Jun 2018 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/dynamic-stats-in-gos-expvar.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/dynamic-stats-in-gos-expvar.html</guid></item><item><title>Optimized abs() for int64 in Go</title><description>&lt;p&gt;The Go programming language has no built in &lt;code&gt;abs&lt;/code&gt; function for computing the&#10;&lt;a href="https://en.wikipedia.org/wiki/Absolute_value"&gt;absolute value of an integer&lt;/a&gt;.&#10;That is, the non-negative representation of a negative or positive number.&lt;/p&gt;&#10;&lt;p&gt;I recently needed an implementation of the &lt;code&gt;abs&lt;/code&gt; function to solve the&#10;&lt;a href="http://adventofcode.com/2017/day/20"&gt;Day 20&lt;/a&gt; challenge in&#10;&lt;a href="http://adventofcode.com/2017/about"&gt;Advent of Code 2017&lt;/a&gt;. If you want to learn&#10;something new and get a kick out of testing yourself, I strongly recommend you&#10;check it out!&lt;/p&gt;&#10;&lt;p&gt;Go does include an &lt;code&gt;abs&lt;/code&gt; function in the &lt;code&gt;math&lt;/code&gt; package:&#10;&lt;a href="https://golang.org/pkg/math/#Abs"&gt;math.Abs&lt;/a&gt;. Unfortunately, this doesn&amp;rsquo;t fit my&#10;use case, as it only accepts &lt;code&gt;float64&lt;/code&gt; as input and output. I need &lt;code&gt;int64&lt;/code&gt;. It&#10;is possible to use &lt;code&gt;math.Abs&lt;/code&gt; via type conversion, but this introduces some&#10;overhead for the conversion to &lt;code&gt;float64&lt;/code&gt; and back to &lt;code&gt;int64&lt;/code&gt;, as well as&#10;introducing truncation for larger numbers.&lt;/p&gt;&#10;&lt;p&gt;There is a &lt;a href="https://groups.google.com/forum/#!topic/golang-dev/nP5mWvwAXZo"&gt;great discussion&lt;/a&gt; on optimizing&#10;&lt;code&gt;math.Abs&lt;/code&gt; for floating point integers, but unfortunately these optimizations&#10;don&amp;rsquo;t directly apply to integers, due to their underlying encoding.&lt;/p&gt;&#10;&lt;p&gt;There must be other options. So begins my adventure into optimizing a simple&#10;function in Go!&lt;/p&gt;&#10;&lt;p&gt;You can find the source code and tests for this article on&#10;&lt;a href="https://github.com/cavaliercoder/go-abs"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="type-conversion-vs-branching"&gt;Type conversion vs. branching&lt;/h2&gt;&#10;&lt;p&gt;The most obvious solution to me for this problem was a very basic function which&#10;returns &lt;code&gt;n&lt;/code&gt; if &lt;code&gt;n&lt;/code&gt; is zero or greater, and returns negative &lt;code&gt;n&lt;/code&gt; if &lt;code&gt;n&lt;/code&gt; is less&#10;than zero. The double negative is, of course, always positive.&lt;/p&gt;&#10;&lt;p&gt;Since it relies on a branching control structure to calculate an absolute value,&#10;let&amp;rsquo;s call this function &lt;code&gt;abs.WithBranch&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WithBranch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It works! Curiously, this is currently (Go v1.9.x) how &lt;code&gt;math.Abs&lt;/code&gt; is&#10;&lt;a href="https://github.com/golang/go/blob/release-branch.go1.9/src/math/abs.go"&gt;implemented&lt;/a&gt;&#10;for &lt;code&gt;float64&lt;/code&gt; numbers.&lt;/p&gt;&#10;&lt;p&gt;Great! Branching works. But, does it improve on a call to &lt;code&gt;math.Abs&lt;/code&gt; using type&#10;conversions? Let&amp;rsquo;s implement that too.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WithStdLib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we are converting &lt;code&gt;n&lt;/code&gt; to a &lt;code&gt;float64&lt;/code&gt;, passing it to &lt;code&gt;math.Abs&lt;/code&gt; and then&#10;converting the result from &lt;code&gt;float64&lt;/code&gt; to &lt;code&gt;int64&lt;/code&gt;. Surely, this must introduce&#10;some performance overhead?&lt;/p&gt;&#10;&lt;p&gt;A couple of simple &lt;a href="https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go"&gt;benchmark tests&lt;/a&gt; yield clear&#10;results:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ go test -bench=.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goos: darwin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goarch: amd64&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg: github.com/cavaliercoder/abs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithBranch-8 2000000000 0.30 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithStdLib-8 2000000000 0.79 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PASS&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ok github.com/cavaliercoder/abs 2.320s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At 0.30 ns/op (nanoseconds per operation), &lt;code&gt;WithBranch&lt;/code&gt; is more than twice as&#10;fast! But our benchmark doesn&amp;rsquo;t represent the real world&amp;hellip;&lt;/p&gt;&#10;&lt;h2 id="benchmarking"&gt;Benchmarking&lt;/h2&gt;&#10;&lt;p&gt;We know that branching code breaks the sequential flow of a&#10;program, meaning that pipelining processors must predict what will happen next&#10;to maintain similar performance to a sequential program.&lt;/p&gt;&#10;&lt;p&gt;In the above benchmark, we used a constant value as the input to each call to&#10;&lt;code&gt;abs&lt;/code&gt;. Because of this, the processor is able to predict which branch to take&#10;every time, because it&amp;rsquo;s the same every time.&lt;/p&gt;&#10;&lt;p&gt;We can trip the processor&amp;rsquo;s ability to predict the flow of execution, and better&#10;represent real world use-cases by introducing a Pseudo Random-Number Generator.&#10;If the input is random, the output will be random and the processor&amp;rsquo;s&#10;predictions should be wrong more often.&lt;/p&gt;&#10;&lt;p&gt;Given there are only two possible futures, I assume a pipelining processor can&#10;compute both outcomes at once, without a performance penalty. Let&amp;rsquo;s test by&#10;experiment.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ go test -bench=.&#10;goos: darwin&#10;goarch: amd64&#10;pkg: github.com/cavaliercoder/go-abs&#10;BenchmarkRand-8 1000000000 2.59 ns/op&#10;BenchmarkWithBranch-8 200000000 6.36 ns/op&#10;BenchmarkWithStdLib-8 500000000 3.13 ns/op&#10;PASS&#10;ok github.com/cavaliercoder/go-abs 6.692s&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;ve introduced a new benchmark, &lt;code&gt;BenchmarkRand&lt;/code&gt;, to measure the overhead of the&#10;RNG. We can substract the resulting overhead from the other benchmarks for an&#10;approximation of their actual runtime.&lt;/p&gt;&#10;&lt;p&gt;It turns out, &lt;code&gt;abs.WithBranch&lt;/code&gt; doesn&amp;rsquo;t perform very well with random input. In&#10;fact, &lt;code&gt;abs.WithStdLib&lt;/code&gt; is nearly seven times faster.&lt;/p&gt;&#10;&lt;h2 id="truncation"&gt;Truncation&lt;/h2&gt;&#10;&lt;p&gt;Before we throw out the branching approach, it should be noted that it has the&#10;advantage that it won&amp;rsquo;t truncate any large numbers by converting from a signed&#10;&lt;code&gt;int64&lt;/code&gt; to an IEEE-754 &lt;code&gt;float64&lt;/code&gt;, losing bits to represent the decimal point.&lt;/p&gt;&#10;&lt;p&gt;By example, &lt;code&gt;abs.WithBranch(-9223372036854775807)&lt;/code&gt; correctly returns&#10;&lt;code&gt;9223372036854775807&lt;/code&gt;, while &lt;code&gt;abs.WithStdLib(-9223372036854775807)&lt;/code&gt; experiences&#10;an overflow during the type conversion and incorrectly returns&#10;&lt;code&gt;-9223372036854775808&lt;/code&gt;. It even returns the same incorrect, negative result for&#10;a high positive input, &lt;code&gt;abs.WithStdLib(9223372036854775807)&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;The type conversion approach is faster with random input, but the branching&#10;approach is more correct. Can we do better?&lt;/p&gt;&#10;&lt;h2 id="a-non-branching-solution"&gt;A non-branching solution&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://books.google.com.au/books?id=VicPJYM0I5QC&amp;amp;lpg=PA18&amp;amp;ots=2o-SROAuXq&amp;amp;dq=hackers%20delight%20absolute&amp;amp;pg=PA18#v=onepage&amp;amp;q=hackers%20delight%20absolute&amp;amp;f=false"&gt;Chapter 2 of Hacker&amp;rsquo;s Delight by Henry S. Warren&lt;/a&gt;&#10;introduces us to a branch-free way of computing the absolute value of a signed&#10;integer using a little &lt;a href="https://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html"&gt;Two&amp;rsquo;s Complement&lt;/a&gt; arithmetic.&lt;/p&gt;&#10;&lt;p&gt;To compute the absolute value of &lt;code&gt;x&lt;/code&gt;, first, we compute the value &lt;code&gt;y&lt;/code&gt; which is&#10;equal to &lt;code&gt;x ⟫ 63&lt;/code&gt;. That is, &lt;code&gt;x&lt;/code&gt; right shifted by 63 bits on a 64 bit&#10;architecture. Finally, compute &lt;code&gt;(x ⨁ y) - y&lt;/code&gt;. That is, &lt;code&gt;x&lt;/code&gt; exclusive-or &lt;code&gt;y&lt;/code&gt;,&#10;take &lt;code&gt;y&lt;/code&gt;. This yields the absolute value of &lt;code&gt;x&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s implement this in assembly!&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// abs.go&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WithASM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-asm" data-lang="asm"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// abs_amd64.s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;TEXT&lt;/span&gt; &lt;span class="err"&gt;·&lt;/span&gt;&lt;span class="no"&gt;WithASM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SB&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="no"&gt;$0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;n&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FP&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;AX&lt;/span&gt; &lt;span class="c1"&gt;// copy input to AX&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;AX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CX&lt;/span&gt; &lt;span class="c1"&gt;// y ← x&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;SARQ&lt;/span&gt; &lt;span class="no"&gt;$63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CX&lt;/span&gt; &lt;span class="c1"&gt;// y ← y ⟫ 63&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;XORQ&lt;/span&gt; &lt;span class="no"&gt;CX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AX&lt;/span&gt; &lt;span class="c1"&gt;// x ← x ⨁ y&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;SUBQ&lt;/span&gt; &lt;span class="no"&gt;CX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AX&lt;/span&gt; &lt;span class="c1"&gt;// x ← x - y&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MOVQ&lt;/span&gt; &lt;span class="no"&gt;AX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ret&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// copy result to return value&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;RET&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We start above with a Go function declaration for &lt;code&gt;WithASM&lt;/code&gt; that has no&#10;immediate implementation. In a separate ASM code file, we declare the body in&#10;&lt;a href="https://golang.org/doc/asm"&gt;Go&amp;rsquo;s Assembler&lt;/a&gt; language. Note that this&#10;implementation is only valid on &lt;code&gt;amd64&lt;/code&gt; architecture systems, so we advise the&#10;compiler by giving the file name the &lt;code&gt;_amd64.s&lt;/code&gt; suffix.&lt;/p&gt;&#10;&lt;p&gt;And the benchmarks results:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ go test -bench=.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goos: darwin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goarch: amd64&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg: github.com/cavaliercoder/go-abs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkRand-8 1000000000 2.52 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithBranch-8 200000000 5.91 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithStdLib-8 500000000 2.88 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithASM-8 1000000000 2.78 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PASS&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ok github.com/cavaliercoder/go-abs 9.454s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All right! We observe a minor improvement of 0.10 ns/op. That&amp;rsquo;s actually a ~38%&#10;speed up. Still, there could be more! It might help to understand how Go&#10;treats ASM code.&lt;/p&gt;&#10;&lt;h2 id="compiler-optimizations"&gt;Compiler optimizations&lt;/h2&gt;&#10;&lt;p&gt;We need visibility of the optimizations the compiler is applying to the Go&#10;functions. The compiler accepts the &lt;code&gt;-m&lt;/code&gt; flag to &amp;ldquo;print optimization decisions&amp;rdquo;.&#10;This can also be enabled in &lt;code&gt;go build&lt;/code&gt; or &lt;code&gt;go test&lt;/code&gt; with the &lt;code&gt;-gcflags=-m&lt;/code&gt;&#10;argument.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s what we see:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ go tool compile -m abs.go&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# github.com/cavaliercoder/abs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./abs.go:11:6: can inline WithBranch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./abs.go:21:6: can inline WithStdLib&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./abs.go:22:23: inlining call to math.Abs&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For simple functions like ours, the Go compiler supports &lt;a href="https://github.com/golang/go/wiki/CompilerOptimizations#function-inlining"&gt;function inlining&lt;/a&gt;.&#10;Function inlining means that calls to our function are replaced inline with the&#10;actual body of our function.&lt;/p&gt;&#10;&lt;p&gt;By example,&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/cavaliercoder/abs&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithBranch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip; might actually be compiled more similar to:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;According to the compiler output above, &lt;code&gt;WithBranch&lt;/code&gt; and &lt;code&gt;WithStdLib&lt;/code&gt; are able&#10;to be inlined, but &lt;code&gt;WithASM&lt;/code&gt; is not. Even the underlying call to &lt;code&gt;math.Abs&lt;/code&gt; is&#10;inlined into &lt;code&gt;WithStdLib&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Since our &lt;code&gt;WithASM&lt;/code&gt; function cannot be inlined, each caller incurs the overhead&#10;of a function call. This means writing a stack frame, copying in arguments,&#10;branching the program pointer, etc.&lt;/p&gt;&#10;&lt;p&gt;What if we even the playing field and disable inlining on our other Go&#10;functions? We can do this with a simple pragma comment before the function&#10;declaration: &lt;code&gt;//go:noinline&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;For example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;//go:noinline&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WithBranch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the compiler again, we see many fewer optimizations:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ go tool compile -m abs.go&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;abs.go:22:23: inlining call to math.Abs&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here are the benchmark results:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$go test -bench=.&#10;goos: darwin&#10;goarch: amd64&#10;pkg: github.com/cavaliercoder/go-abs&#10;BenchmarkRand-8 1000000000 2.40 ns/op&#10;BenchmarkWithBranch-8 200000000 7.06 ns/op&#10;BenchmarkWithStdLib-8 500000000 3.46 ns/op&#10;BenchmarkWithASM-8 1000000000 2.86 ns/op&#10;PASS&#10;ok github.com/cavaliercoder/go-abs 10.032s&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The performance of &lt;code&gt;WithASM&lt;/code&gt; remains unchanged, while &lt;code&gt;WithStdLib&lt;/code&gt; is three&#10;times slower thanks to the overhead of the non-inline function call.&lt;/p&gt;&#10;&lt;p&gt;The lesson learned for me here, is that the performance gained by implementing&#10;ASM by hand needs to outweigh the benefits of compiler type safety, garbage&#10;collection, function inlining, etc. In most cases, this won&amp;rsquo;t be the true,&#10;though there are exceptions; like taking advantage of &lt;a href="https://goroutines.com/asm"&gt;SIMD&lt;/a&gt; instructions for cryptography or media encoding.&lt;/p&gt;&#10;&lt;p&gt;Running the benchmarks a few times it becomes clear that there is a reasonable&#10;performance gain to be had via ASM. But, can we somehow retain the benefits of&#10;inlining?&lt;/p&gt;&#10;&lt;h2 id="one-inline-function-please"&gt;One inline function, please&lt;/h2&gt;&#10;&lt;p&gt;The Go compiler cannot inline functions that are implemented in assembler, but&#10;implementing our Two&amp;rsquo;s Complement arithmetic in Go is easy:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WithTwosComplement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// y ← x ⟫ 63&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// (x ⨁ y) - y&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The compiler says our new function can be inlined:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ go tool compile -m abs.go&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;abs.go:26:6: can inline WithTwosComplement&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How does it perform? It turns out, when we re-enable inlining, it performs even&#10;better! A whole 0.18 ns better, or roughly 60%.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ go test -bench=.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goos: darwin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goarch: amd64&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg: github.com/cavaliercoder/go-abs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkRand-8 1000000000 2.39 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithBranch-8 300000000 5.71 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithStdLib-8 1000000000 3.01 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithASM-8 1000000000 2.88 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BenchmarkWithTwosComplement-8 1000000000 2.70 ns/op&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PASS&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ok github.com/cavaliercoder/go-abs 14.417s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now that the overhead of a function call is gone, the Two&amp;rsquo;s Complement&#10;implementation in Go out performs the ASM implementation. Curiosity might lead&#10;us next to wonder how similar the compiler&amp;rsquo;s output is to our hand-crafted ASM?&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s an app for that.&lt;/p&gt;&#10;&lt;p&gt;Passing &lt;code&gt;-S&lt;/code&gt; to the Go compiler causes it to &amp;ldquo;print assembly listing&amp;rdquo;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ go tool compile -S abs.go&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;&amp;#34;.WithTwosComplement STEXT nosplit size=24 args=0x10 locals=0x0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x0000 00000 (abs.go:26) TEXT &amp;#34;&amp;#34;.WithTwosComplement(SB), NOSPLIT, $0-16&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x0000 00000 (abs.go:26) FUNCDATA $0, gclocals·f207267fbf96a0178e8758c6e3e0ce28(SB)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x0000 00000 (abs.go:26) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x0000 00000 (abs.go:26) MOVQ &amp;#34;&amp;#34;.n+8(SP), AX&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x0005 00005 (abs.go:26) MOVQ AX, CX&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x0008 00008 (abs.go:27) SARQ $63, AX&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x000c 00012 (abs.go:28) XORQ AX, CX&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x000f 00015 (abs.go:28) SUBQ AX, CX&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x0012 00018 (abs.go:28) MOVQ CX, &amp;#34;&amp;#34;.~r1+16(SP)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0x0017 00023 (abs.go:28) RET&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The compiler&amp;rsquo;s implementation is exactly the same. Only this time, it&#10;has the advantage of being correctly configured and being cross-platform&#10;portable! Running the compiler again with &lt;code&gt;GOARCH=386&lt;/code&gt; produces a more&#10;complicated program that handles the 64-bit arithmetic on 32-bit machines.&lt;/p&gt;&#10;&lt;p&gt;In this case I could be more trusting of the compiler.&lt;/p&gt;&#10;&lt;p&gt;One last note about memory allocations; all of our implementations exhibit the&#10;same ideal behavior. When I run &lt;code&gt;go test -bench=. -benchmem&lt;/code&gt;, I observe that&#10;each of the functions result in zero allocation operations and zero bytes&#10;allocated within the function body.&lt;/p&gt;&#10;&lt;h2 id="homework"&gt;Homework&lt;/h2&gt;&#10;&lt;p&gt;A few exercises for the reader:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;What if we implemented the assembly for &lt;code&gt;abs&lt;/code&gt; as a compiler intrinsic, rather&#10;than a runtime function call?&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;What if we used the &lt;code&gt;PABSQ&lt;/code&gt; instruction included in SSSE3? This is not&#10;currently supported in Go&amp;rsquo;s ASM.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p&gt;The Two&amp;rsquo;s Complement implementation in Go offers portability, function inlining,&#10;predictable performance, zero allocations and no integer truncation due to type&#10;conversions. The benchmarks showed a significant speed advantage over the other&#10;approaches.&lt;/p&gt;&#10;&lt;p&gt;The end result:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><pubDate>Sat, 13 Jan 2018 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/optimized-abs-for-int64-in-go.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/optimized-abs-for-int64-in-go.html</guid></item><item><title>Inline vs. discrete rules for AWS Security Groups in Terraform</title><description>&lt;p&gt;There are two ways to configure AWS Security Groups in Terraform. You may define&#10;rules &lt;em&gt;inline&lt;/em&gt; with a &lt;code&gt;aws_security_group&lt;/code&gt; resource or you may define additional&#10;discrete &lt;code&gt;aws_security_group_rule&lt;/code&gt; resources.&lt;/p&gt;&#10;&lt;p&gt;My first instinct was to define a &amp;ldquo;base&amp;rdquo; Security Group using inline rules and&#10;then extend on it using external rules. Bad idea. More on that later.&lt;/p&gt;&#10;&lt;p&gt;For the two valid options though, there are important implications and I found&#10;these were not clear at the time of writing (circa Terraform v0.9.11). After a&#10;little research and experimentation I have a much clearer understanding and hope&#10;to save you all the bother.&lt;/p&gt;&#10;&lt;p&gt;This article focuses on managing AWS Security Groups in Terraform but you will&#10;find that all of the principles explored here apply equally to Network ACLs and&#10;Route Tables - both of which allow inline or external rule management.&lt;/p&gt;&#10;&lt;h2 id="two-approaches"&gt;Two approaches&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s how an &lt;em&gt;inline&lt;/em&gt; Security Group definition looks:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_security_group&amp;#34; &amp;#34;allow_all&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;allow_all&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow all inbound traffic&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;ingress&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;egress&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Above there are two rules, an &lt;code&gt;ingress&lt;/code&gt; and &lt;code&gt;egress&lt;/code&gt; rule defined inside or&#10;&lt;em&gt;inline&lt;/em&gt; with the &lt;code&gt;aws_security_group&lt;/code&gt; resource block.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s how the same idea can be expressed using external rules via the&#10;&lt;code&gt;aws_security_group_rule&lt;/code&gt; resource:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_security_group&amp;#34; &amp;#34;allow_all&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;allow_all&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow all inbound traffic&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;ingress&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; security_group_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${aws_security_group.allow_all.id}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;egress&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;egress&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; security_group_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${aws_security_group.allow_all.id}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Security Group and each of its rules are defined as discrete resources,&#10;intimately linked together in loving union by the &lt;code&gt;security_group_id&lt;/code&gt; attribute.&lt;/p&gt;&#10;&lt;p&gt;A reasonable person might posit that the outcome of both configurations would&#10;be the same, but they are different in subtle ways - ways that might hurt a bit&#10;if not clearly understood.&lt;/p&gt;&#10;&lt;h2 id="option-1-external-rules"&gt;Option 1: External rules&lt;/h2&gt;&#10;&lt;p&gt;I had hoped that external rules would function similar to Puppet&amp;rsquo;s &lt;code&gt;concat&lt;/code&gt;&#10;module - gathering partial resources defined anywhere in the graph and then&#10;enforcing the sum state. The reality however, which does make sense, is that the&#10;desired state is managed non-destructively.&lt;/p&gt;&#10;&lt;p&gt;You can test this, by manually adding a rule to the Security Group created by&#10;the Terraform code above (the snippet with only external rules). If you run&#10;&lt;code&gt;terraform apply&lt;/code&gt;, it will ignore this manually created rule.&lt;/p&gt;&#10;&lt;p&gt;What this means, is that you can add and enforce rules on a Security Group that&#10;was created elsewhere. Just be cautious of conflicts with existing rules,&#10;precedence and collisions in your rule numbers.&lt;/p&gt;&#10;&lt;h3 id="pros"&gt;Pros&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Rules can be added non-destructively to unmanaged Security Groups. This might&#10;be useful for Security Groups associated with shared services, such as a&#10;Bastion host. Terraform configurations from all over the kingdom can create&#10;rules on a common Security Group to enable the access they require.&lt;/p&gt;&#10;&lt;p&gt;Ideally, one would create and associate distinct, well-tagged Security Groups&#10;for each use-case but we are limited to only &lt;a href="http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Appendix_Limits.html#vpc-limits-security-groups"&gt;five Security Groups per network&#10;interface&lt;/a&gt;.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Security Groups can be &amp;ldquo;modularized&amp;rdquo; and extended upon. You might write a&#10;Terraform module that creates a Security Group with standardized naming,&#10;tagging, lifecycle and maybe even some base access rules. Additional rules&#10;could then be added to the Security Group returned by the module using&#10;&lt;code&gt;aws_security_group_rule&lt;/code&gt; resources.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="cons"&gt;Cons&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Security Group rules that are added by accident or by nefarious means will not&#10;be nuked by Terraform. It won&amp;rsquo;t even report that they exist.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Infrastructure code should succinctly describe and quasi-document your actual&#10;infrastructure. Defining rules for a single Security Group in disparate files&#10;in your code-base makes it difficult to see at a glance what the state of a&#10;Security group should be.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;It&amp;rsquo;s difficult to know or manage for how externally created rules will&#10;effect the rules you are configuring in Terraform. This is particularly&#10;important for Network ACLs, where a rule defined elsewhere could have higher&#10;precedence than yours and might explicitly deny a port you intend to open.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="option-2-inline-rules"&gt;Option 2: Inline rules&lt;/h2&gt;&#10;&lt;p&gt;When rules are defined inline, a Security Group is managed destructively. That&#10;is, any rule not defined inline, including rules defined elsewhere in Terraform&#10;and rules added manually or via other tools, will be unapologetically destroyed&#10;whenever Terraform next runs.&lt;/p&gt;&#10;&lt;h3 id="pros-1"&gt;Pros&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Security Groups are for security. They need to be clear, understood and well&#10;configured. Using inline rules means your resource definition is complete,&#10;definitive and deterministically provisioned. You can have confidence that&#10;there are no rules being described elsewhere in the code-base or added outside&#10;of Terraform. If there are, they get destroyed.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="cons-1"&gt;Cons&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;There&amp;rsquo;s no way to share code between inline-style Security Groups. This might&#10;mean lots of repetition for Security Groups that are &lt;em&gt;mostly&lt;/em&gt; the same;&#10;having a common set of rules with minor exceptions between them. Make use of&#10;the five available Security Group slots per network interface to allow re-use&#10;of shared base Security Groups - though this is difficult if you&amp;rsquo;re using&#10;least-privilege, point-to-point access rules.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;You can&amp;rsquo;t use the &lt;code&gt;count&lt;/code&gt; meta-parameter to described rules in a loop.&lt;/p&gt;&#10;&lt;p&gt;It works with external rules:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http_ports&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;80&amp;#34;, &amp;#34;443&amp;#34;, &amp;#34;8080&amp;#34;, &amp;#34;8443&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;ingress_http&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${length(var.http_ports)}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${element(var.http_ports, count.index)}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${element(var.http_ports, count.index)}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; security_group_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${aws_security_group.allow_all.id}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip; but not with inline rules:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http_ports&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;80&amp;#34;, &amp;#34;443&amp;#34;, &amp;#34;8080&amp;#34;, &amp;#34;8443&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_security_group&amp;#34; &amp;#34;allow_all&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;allow_all&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow all inbound traffic&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;ingress&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${length(var.http_ports)}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${element(var.http_ports, count.index)}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${element(var.http_ports, count.index)}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1 error(s) occurred:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# * aws_security_group.allow_all: ingress.0: invalid or unknown key: count&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;re going to need to code up an inline rule for each port.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="why-not-both"&gt;Why not both?&lt;/h2&gt;&#10;&lt;p&gt;My naive first approach was to blend both approaches. I hoped to create a&#10;configurable &lt;code&gt;aws_security_group&lt;/code&gt; &amp;ldquo;module&amp;rdquo; that contained some mandatory rules,&#10;like allowing ingress SSH, monitoring, etc. This module could then be extended&#10;with additional rules using &lt;code&gt;aws_security_group_rules&lt;/code&gt; resources. A kinda&#10;pseudo-OOP-abstract-class approach.&lt;/p&gt;&#10;&lt;p&gt;Fortunately, the &lt;a href="https://www.terraform.io/docs/providers/aws/r/security_group.html"&gt;Terraform&#10;documentation&lt;/a&gt;&#10;contained a well-lit warning sign:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;At this time you cannot use a Security Group with in-line rules in conjunction&#10;with any Security Group Rule resources. Doing so will cause a conflict of rule&#10;settings and will overwrite rules.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;What happens when you combine both methods? Here&amp;rsquo;s a cool high-school science&#10;lab experiment for you!&lt;/p&gt;&#10;&lt;p&gt;The following Terraform code defines both inline rules, and an external&#10;&lt;code&gt;ingress_http&lt;/code&gt; rule.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_security_group&amp;#34; &amp;#34;allow_all&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;allow_all&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow all inbound traffic&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;ingress&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;egress&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;ingress_http&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; from_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; to_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cidr_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.0.0.0/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; security_group_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${aws_security_group.allow_all.id}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="instructions"&gt;Instructions&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Apply this code with &lt;code&gt;terraform apply&lt;/code&gt; - it should create the &lt;code&gt;ingress_http&lt;/code&gt;&#10;rule&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Apply the same code again with &lt;code&gt;terraform apply&lt;/code&gt; - it should remove the newly&#10;created &lt;code&gt;ingress_http&lt;/code&gt; rule&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Go to step 1&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Terraform will create and then destroy the external rule on each alternating&#10;invocation - like that scene with Dormammu in Marvel&amp;rsquo;s Dr Strange.&lt;/p&gt;&#10;&lt;p&gt;Bug? No. It actually kinda makes sense. Jake Champlin from HashiCorp explains it&#10;on a &lt;a href="https://github.com/hashicorp/terraform/issues/11011#issuecomment-283076580"&gt;related GitHub&#10;issue&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;&#10;&lt;p&gt;I&amp;rsquo;ve come to prefer using inline rules where possible. It means our Security&#10;Groups match the code and phantom &amp;lsquo;allow all&amp;rsquo; rules can&amp;rsquo;t be introduced that&#10;would break our security model and the integrity of our tests.&lt;/p&gt;&#10;&lt;p&gt;It does mean we have to duplicate some rules in a few Security Groups, and keep&#10;these definitions in sync, but I share the opinion that &lt;a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;amp;t=9m28s"&gt;a little copying is&#10;better than a little&#10;dependency&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;For more information about AWS Security Groups in Terraform, please see:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;a href="http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_SecurityGroups.html"&gt;AWS: Security Groups for your VPC&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;a href="https://www.terraform.io/docs/providers/aws/r/security_group.html"&gt;Terraform: aws_security_group&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;a href="https://www.terraform.io/docs/providers/aws/r/security_group_rule.html"&gt;Terraform: aws_security_group_rule&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description><pubDate>Sat, 15 Jul 2017 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/inline-vs-discrete-security-groups-in-terraform.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/inline-vs-discrete-security-groups-in-terraform.html</guid></item><item><title>Postmortem: a socket leak in the AWS SDK for Node.js</title><description>&lt;p&gt;On May 18, 2017, &lt;a href="https://thewest.com.au"&gt;thewest.com.au&lt;/a&gt; started serving&#10;&lt;a href="https://tools.ietf.org/html/rfc7231#section-6.6.5"&gt;504 Gateway Timeout&lt;/a&gt;&#10;responses for images. The root cause was a socket leak in a months-old release&#10;of the AWS SDK for Node.js that had been pinned by accident. Connections to S3&#10;hung after the response headers arrived, and once 50 of them had leaked the&#10;SDK&amp;rsquo;s connection pool was exhausted and every request for a static asset&#10;stalled. This is how we found it.&lt;/p&gt;&#10;&lt;p&gt;I led the Production Engineering team at Seven West Media in Western Australia.&#10;Our internal postmortem differs from this one only in formality, not in the&#10;facts.&lt;/p&gt;&#10;&lt;p&gt;While reports came in from colleagues and customers that images were failing&#10;to display, a team of developers and sysadmins invoked our incident response&#10;process. It borrows heavily from ITIL Incident Management and &lt;a href="https://sre.google/sre-book/table-of-contents/"&gt;Google&amp;rsquo;s Site&#10;Reliability Engineering&lt;/a&gt;, and&#10;it calls for us to stay calm, objective and to seek out quality information&#10;before making decisions.&lt;/p&gt;&#10;&lt;h2 id="network-io-deviations"&gt;Network I/O deviations&lt;/h2&gt;&#10;&lt;p&gt;Initially we discovered unusual network I/O patterns on our Content API servers.&lt;/p&gt;&#10;&lt;p&gt;The Content API is a Node.js/Express application fronted by a local instance of&#10;nginx. It is responsible for serving article content, listings, curations, etc.&#10;to both our internal web servers (for server-side rendering) and to our&#10;client-side Javascript which uses the Single-Page Application model (see:&#10;&lt;a href="https://medium.com/airbnb-engineering/isomorphic-javascript-the-future-of-web-apps-10882b7a2ebc"&gt;Isomorphic Javascript: The Future of Web&#10;Apps&lt;/a&gt;).&#10;It also serves static assets, such as images, which it pipes directly from&#10;Amazon S3.&lt;/p&gt;&#10;&lt;a class="lightbox" href="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/network-io.png"&gt;&#10; &lt;img src="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/network-io.png" alt="Network I/O on Content API instance" /&gt;&#10;&lt;/a&gt;&#10;&lt;p&gt;The above figure from Zabbix shows network I/O on one of the Content API&#10;instances. Around 7 AM the bursty traffic flattened into a ceiling, which meant&#10;something was being serialised or capped.&lt;/p&gt;&#10;&lt;p&gt;We employed &lt;a href="http://www.brendangregg.com/usemethod.html"&gt;Brendan Gregg&amp;rsquo;s USE&#10;method&lt;/a&gt; to build a clearer picture&#10;of network performance. We observed:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;throughput between each component was otherwise typical, with plenty of&#10;headroom&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;no dropped packets, interface errors or other known TCP/IP issues that would&#10;indicate saturation or congestion&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;no error messages in any of the counters or logs we knew to check&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;no network issues were being reported by AWS&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Health checks on the Content API servers were passing&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;There was one metric we neglected to check for saturation. More on that below.&lt;/p&gt;&#10;&lt;h2 id="http-499-errors"&gt;HTTP 499 errors&lt;/h2&gt;&#10;&lt;p&gt;One fastidious engineer noticed something else amiss: a large number of &lt;a href="http://lxr.nginx.org/source/src/http/ngx_http_request.h#0124"&gt;499&#10;Client Closed&#10;Request&lt;/a&gt; responses&#10;being logged by nginx on the same Content API servers.&lt;/p&gt;&#10;&lt;a class="lightbox" href="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/499-graph.png"&gt;&#10; &lt;img src="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/499-graph.png" alt="HTTP 499 status graph" /&gt;&#10;&lt;/a&gt;&#10;&lt;p&gt;The above screen grab from Sumo Logic of the same instance shows the uptick in&#10;499 status messages at the same time that the network I/O anomaly appeared. The&#10;uptick is obvious in this image, but it was visually masked on our dashboards&#10;by the thousands of successful requests occurring at the same time.&lt;/p&gt;&#10;&lt;p&gt;Reviewing the nginx logs on each server revealed an interesting pattern that&#10;helped us to understand the scope of the issue: all 499 responses were&#10;constrained to static assets only. Requests for dynamic content were unaffected.&lt;/p&gt;&#10;&lt;p&gt;This finding was easy to confirm locally on any of the production servers. A&#10;quick &lt;code&gt;$ curl -iv http://localhost/...&lt;/code&gt; for any static asset would&#10;hang until the client cancelled the request. The same &lt;code&gt;curl&lt;/code&gt; request for any&#10;other resource behaved as expected.&lt;/p&gt;&#10;&lt;p&gt;This test also validated that no error was logged via nginx or the Node.js&#10;application. The application logs did show an incoming request but no outbound&#10;response or error.&lt;/p&gt;&#10;&lt;p&gt;So here&amp;rsquo;s what we knew:&lt;/p&gt;&#10;&lt;a class="lightbox" href="https://hotsrc.dev/images/aws-sdk-socket-leak/what-we-know.png"&gt;&#10; &lt;img src="https://hotsrc.dev/images/aws-sdk-socket-leak/what-we-know.png" alt="Sequence diagram" /&gt;&#10;&lt;/a&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Either our CDN or Load Balancing layer was cancelling some requests and&#10;returning status 504 as no timely response was received from upstream&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Nginx was logging a status 499 when the downstream components cancelled a&#10;request&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Requests to the Content API for static assets were falling into a black hole&#10;which meant that image content on the site was failing to display&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;At this point we decided we had sufficient information about the scope of the&#10;issue to consider a mitigation plan.&lt;/p&gt;&#10;&lt;h2 id="stem-the-bleeding"&gt;Stem the bleeding&lt;/h2&gt;&#10;&lt;p&gt;Our incident response process emphasizes small, non-destructive changes over&#10;wholesale repairs where possible. The mantra I repeat to my team:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Avoid corrective surgery - just stem the bleeding&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Corrective surgery is best performed once the situation has stabilized, we have&#10;better information and everyone is cool, calm and collected.&lt;/p&gt;&#10;&lt;p&gt;We formed a hypothesis that freshly launched instances of the Content API would&#10;behave correctly. They did: the new servers produced no errors once marked in.&lt;/p&gt;&#10;&lt;p&gt;We did have the option to instead simply restart the unhealthy instances, but&#10;our process also encourages the preservation of evidence. Restarting the&#10;instances would have reset their state - and as we learned later - would have&#10;reset the key metric we neglected to observe. We might not have found the root&#10;cause until the issue surfaced again.&lt;/p&gt;&#10;&lt;p&gt;Besides that, servers should be treated &lt;a href="http://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/"&gt;like cattle, not&#10;pets&lt;/a&gt;.&#10;A freshly launched instance represents a known good state.&lt;/p&gt;&#10;&lt;p&gt;With the healthy instances in rotation, everything stabilized. The 499 log&#10;messages disappeared, our &lt;code&gt;curl&lt;/code&gt; test started behaving correctly and users&#10;were confirming a positive outcome.&lt;/p&gt;&#10;&lt;h2 id="root-cause-analysis"&gt;Root cause analysis&lt;/h2&gt;&#10;&lt;p&gt;With clearer heads and happy customers, we had the opportunity to take a&#10;detailed look at the unhealthy servers and compare them to healthy servers.&lt;/p&gt;&#10;&lt;p&gt;A call to &lt;code&gt;$ netstat -t4&lt;/code&gt; shows all TCPv4 sockets on a Linux system. The&#10;following figure shows the results of this call on both an unhealthy (left) and&#10;healthy (right) Content API instance.&lt;/p&gt;&#10;&lt;a class="lightbox" href="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/netstat-bad.png"&gt;&#10; &lt;img class="osx-window" src="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/netstat-bad.png" alt="netstat session" /&gt;&#10;&lt;/a&gt;&#10;&lt;p&gt;Notice that the unhealthy instance has a large number of sockets in a&#10;&lt;code&gt;CLOSE_WAIT&lt;/code&gt; state and also that most sockets have a large quantity of packets&#10;in the Receive Queue (second column). All of these sockets list AWS S3 as the&#10;foreign end point.&lt;/p&gt;&#10;&lt;p&gt;By comparison the healthy server has only one &lt;code&gt;CLOSE_WAIT&lt;/code&gt; socket and the&#10;Receive Queues are mostly empty, except for the lonely &lt;code&gt;CLOSE_WAIT&lt;/code&gt; socket.&#10;Could this be the beginning of a very slow connection leak?&lt;/p&gt;&#10;&lt;p&gt;Remember that key metric that we neglected to observe during our network&#10;performance observations? We needed to measure the &lt;strong&gt;number of &lt;code&gt;CLOSE_WAIT&lt;/code&gt;&#10;sockets on a server&lt;/strong&gt;. Queue length and the other sockets states might also have&#10;been useful under differing circumstances.&lt;/p&gt;&#10;&lt;p&gt;According to &lt;a href="http://www.cs.northwestern.edu/~agupta/cs340/project2/TCPIP_State_Transition_Diagram.pdf"&gt;Gordon McKinney&amp;rsquo;s TCP/IP State Transition&#10;Diagram&lt;/a&gt;,&#10;&lt;code&gt;CLOSE_WAIT&lt;/code&gt; indicates that the socket is:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;hellip; waiting for a connection termination request from the local user&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;In our case the &lt;em&gt;&amp;ldquo;local user&amp;rdquo;&lt;/em&gt; was the Node.js application. Adding &lt;code&gt;-p&lt;/code&gt; to the&#10;netstat call printed the PID that owned the leaked sockets. The PID belonged to&#10;our application.&lt;/p&gt;&#10;&lt;p&gt;Somewhere in our application, sockets were being created for S3 requests, but&#10;never cleaned up. These sockets were exhausting some unknown upper limit,&#10;causing new requests to hang.&lt;/p&gt;&#10;&lt;p&gt;Now we know where to look and our picture of the control path has a little more&#10;detail. Here&amp;rsquo;s how it looked once the leaked sockets reached critical mass:&lt;/p&gt;&#10;&lt;a class="lightbox" href="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/what-we-know-2.png"&gt;&#10; &lt;img src="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/what-we-know-2.png" alt="Sequence diagram" /&gt;&#10;&lt;/a&gt;&#10;&lt;p&gt;We set about tracing the code path for S3 requests in our codebase and found&#10;the following.&lt;/p&gt;&#10;&lt;h2 id="aws-sdk-for-nodejs"&gt;AWS SDK for Node.js&lt;/h2&gt;&#10;&lt;p&gt;All of the connections to AWS S3 were being established via the AWS SDK for&#10;Node.js.&lt;/p&gt;&#10;&lt;p&gt;The SDK uses socket pooling for connections to S3. The default &lt;a href="http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-configuring-maxsockets.html"&gt;maximum size of&#10;the pool is&#10;50&lt;/a&gt;.&#10;If sockets were leaking and there is an upper boundary to the number of active&#10;sockets, it stands to reason that new requests to S3 (in our case, for static&#10;assets) would fail in some way once 50 sockets had leaked. The number 50&#10;anecdotally matched the number of &lt;code&gt;CLOSE_WAIT&lt;/code&gt; sockets we had seen with netstat.&lt;/p&gt;&#10;&lt;p&gt;We also found that our version of the SDK had been inadvertently pinned in our&#10;codebase to&#10;&lt;a href="https://github.com/aws/aws-sdk-js/blob/HEAD/CHANGELOG.md#2614"&gt;v2.6.14&lt;/a&gt;, which&#10;was released shortly before we relaunched thewest.com.au.&lt;/p&gt;&#10;&lt;p&gt;Given the age of our SDK version, it was conceivable that the socket leak was&#10;now a known issue. We trawled through GitHub issues, PRs and the SDK Changelog&#10;and found a suspect note in the&#10;&lt;a href="https://github.com/aws/aws-sdk-js/blob/HEAD/CHANGELOG.md#2500"&gt;2.50.0&lt;/a&gt; release&#10;notes:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;bugfix: Request: Updates node.js request handling to obey socket read timeouts&#10;after response headers have been received. Previously timeouts were being&#10;ignored once headers were received, sometimes causing connections to hang.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Inspecting the code also revealed a significant body of other work had also been&#10;released concerning sockets.&lt;/p&gt;&#10;&lt;p&gt;We formed a hypothesis that updating the AWS SDK would cause all sockets to be&#10;cleaned up correctly or emit errors that could be safely handled. There were&#10;strong coincidences that suggested this was our issue. Patching the SDK was low&#10;risk, easily tested and overdue. First, though, we needed to measure.&lt;/p&gt;&#10;&lt;h2 id="measurements"&gt;Measurements&lt;/h2&gt;&#10;&lt;p&gt;It was important that we started measuring these socket metrics before&#10;implementing a fix. Before-and-after measurements are critical to validate any&#10;fix.&lt;/p&gt;&#10;&lt;p&gt;I wrote a Zabbix module named&#10;&lt;a href="https://github.com/cavaliercoder/zabbix-module-sockets"&gt;zabbix-module-sockets&lt;/a&gt;&#10;to capture the following metrics:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;the number of sockets in each state&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;the sum of the receive queue of all sockets&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;the sum of the send queue of all sockets&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;With a little simulated load we were able to replicate the connection leak in&#10;pre-production environments and measure the results in Zabbix.&lt;/p&gt;&#10;&lt;h2 id="the-fix"&gt;The fix&lt;/h2&gt;&#10;&lt;p&gt;With a reasonable hypothesis, measurements in place and way to validate our plan&#10;in pre-production, we deployed the updated AWS SDK to our pre-production&#10;environments.&lt;/p&gt;&#10;&lt;p&gt;The fix held. The following figure from Zabbix shows the change in behavior&#10;around June 2nd, when the fix was subsequently deployed to production.&lt;/p&gt;&#10;&lt;a class="lightbox" href="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/recvq-fix.png"&gt;&#10; &lt;img src="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/recvq-fix.png" alt="Socket Recv-Q graph" /&gt;&#10;&lt;/a&gt;&#10;&lt;p&gt;The gradual upward trend of the Receive Queue length (green) stops after the&#10;production deployment and the queue length stabilizes. The earlier sawtooth&#10;pattern you may observe is caused by application restarts that were triggered to&#10;remediate the socket pool being exhausted.&lt;/p&gt;&#10;&lt;p&gt;The following figure shows sockets states on the same server. The thin, red&#10;slice of &lt;code&gt;CLOSE_WAIT&lt;/code&gt; sockets discontinues around the time of the fix&#10;deployment.&lt;/p&gt;&#10;&lt;a class="lightbox" href="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/state-fix.png"&gt;&#10; &lt;img src="https://cdn.cavaliercoder.com/blog/2017-05-27-a-webops-post-mortem/state-fix.png" alt="Socket states graph" /&gt;&#10;&lt;/a&gt;&#10;&lt;p&gt;The &lt;em&gt;max&lt;/em&gt; column for &lt;code&gt;CLOSE_WAIT&lt;/code&gt; sockets reads 50: exactly the SDK&amp;rsquo;s default&#10;pool size.&lt;/p&gt;&#10;&lt;h2 id="lessons-learned"&gt;Lessons learned&lt;/h2&gt;&#10;&lt;p&gt;We trade time-to-market against technical debt like everyone else, and we make&#10;the best moves we can with the information and resources we have at the time.&#10;This incident showed where that trade had gone wrong, and a few places it could&#10;go wrong again.&lt;/p&gt;&#10;&lt;h3 id="keep-dependencies-current"&gt;Keep dependencies current&lt;/h3&gt;&#10;&lt;p&gt;The SDK had been pinned by accident at v2.6.14 and nobody had looked at it&#10;since launch. The fix for our bug had been public for months. A dependency that&#10;nobody chose to pin is debt that nobody is tracking; versions need an owner and&#10;a review cadence.&lt;/p&gt;&#10;&lt;h3 id="alert-on-499-responses"&gt;Alert on 499 responses&lt;/h3&gt;&#10;&lt;p&gt;For HTTP servers that support HTTP 499 like nginx, a small number of&#10;&lt;code&gt;499 Client Closed Request&lt;/code&gt; responses should be expected at the edge, where&#10;clients do have the prerogative to disconnect prematurely. But in higher&#10;frequency, or at other points in your signal chain, these responses might&#10;indicate that timeouts are incorrectly configured. So&amp;hellip;&lt;/p&gt;&#10;&lt;h3 id="get-your-timeouts-right"&gt;Get your timeouts right&lt;/h3&gt;&#10;&lt;p&gt;Each component should be configured with a timeout that is longer than all its&#10;upstream dependencies. By example, if you have a 3 second timeout on database&#10;queries, then the application server should not cancel requests any sooner than&#10;this. Otherwise the source of a problem is obscured and all you are presented&#10;with is a timeout error from a component other that the one that misbehaved - as&#10;we experienced. In &lt;a href="https://sre.google/sre-book/addressing-cascading-failures/"&gt;deadline&#10;propagation&lt;/a&gt; terms,&#10;a callee should give up before its caller does, so the error surfaces from the&#10;layer that actually misbehaved.&lt;/p&gt;&#10;&lt;p&gt;This solution is complicated by services that need multiple round-trips or have&#10;asynchronous behaviors, but&amp;hellip;&lt;/p&gt;&#10;&lt;h3 id="everything-should-be-bounded"&gt;Everything should be bounded&lt;/h3&gt;&#10;&lt;p&gt;There is an expected operational behavior of every component in our system. Any&#10;operation that completes too quickly or too slowly should create an error that&#10;is meaningfully propagated, logged and alerted on.&lt;/p&gt;&#10;&lt;p&gt;In this case, our Content API servers should have been configured with a tightly&#10;constrained timeout on all requests to S3. Error handlers in our code would then&#10;have sent a meaningful 5xx error back downstream.&lt;/p&gt;&#10;&lt;p&gt;This principle should also apply to the incoming request to the API. We should&#10;cancel any request that runs too long, &lt;em&gt;for any reason&lt;/em&gt;. This has operational&#10;advantages, but is also good security practice to mitigate denial of service.&lt;/p&gt;&#10;&lt;h3 id="health-checks-must-validate-all-dependencies"&gt;Health checks must validate all dependencies&lt;/h3&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Everything fails, all the time.&lt;/p&gt;&#10;&lt;p&gt;&lt;cite&gt;Werner Vogels, CTO of Amazon&lt;/cite&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Each of our micro-services has a health check route that quickly validates each&#10;of its known dependencies (e.g. it can connect to the database, write to a log&#10;file, etc.). In this case we had neglected to include S3 in the dependency&#10;checks. As a result, each service was advertising itself as healthy.&lt;/p&gt;&#10;&lt;p&gt;If we had included S3 in the health checks, the checks would have failed, these&#10;instances would have been marked out of the load balancer, alerts would have&#10;escalated and new healthy instances would have been rolled in automatically.&lt;/p&gt;&#10;&lt;p&gt;The trade-off is that a deep health check ties every instance&amp;rsquo;s fate to a&#10;shared dependency. Had &lt;a href="https://aws.amazon.com/message/41926/"&gt;S3 itself failed&lt;/a&gt;,&#10;every instance would have failed its check at once and the load balancer would&#10;have had nothing left to route to. The check has to distinguish a sick instance&#10;from a sick dependency, or the balancer needs a floor below which it stops&#10;marking instances out.&lt;/p&gt;&#10;&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;&#10;&lt;p&gt;Under the stress of trying to restore services, we never felt any hunch to check&#10;the battle-hardened AWS SDK for leaked sockets to the notoriously reliable AWS&#10;S3 service. But this was our root cause.&lt;/p&gt;&#10;&lt;p&gt;As unlikely as the issue was, having a response plan in place and sticking to it&#10;gave us the best possible outcomes:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;We followed the facts and found the root cause&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Services were restored quickly, without introducing unnecessary risk or&#10;destroying evidence&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;We uncovered other weaknesses in our systems that could be addressed before&#10;they led to disaster&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;We learned a whole lot about the software and systems we work with&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="appendix-leaks-from-other-buckets"&gt;Appendix: Leaks from other buckets&lt;/h2&gt;&#10;&lt;p&gt;With our new socket monitoring in place in Zabbix, we noticed a much smaller&#10;number of sockets to S3 being leaked on almost all of our application servers.&lt;/p&gt;&#10;&lt;p&gt;We discovered that the AWS CodeDeploy Agent was the culprit. We use CodeDeploy&#10;to deploy our Node.js applications and S3 is used as the storage backend.&lt;/p&gt;&#10;&lt;p&gt;Fortunately, these sockets leak much slower (we&amp;rsquo;ve only seen up to 3 on any one&#10;instance). For now, routine restarts will suffice until AWS are able to address&#10;&lt;a href="https://github.com/aws/aws-codedeploy-agent/issues/115"&gt;the issue&lt;/a&gt;.&lt;/p&gt;&#10;</description><pubDate>Tue, 27 Jun 2017 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/aws-sdk-socket-leak.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/aws-sdk-socket-leak.html</guid></item><item><title>Zabbix tools I've written</title><description>&lt;p&gt;I&amp;rsquo;ve had the pleasure of working with Zabbix for many years now, (since 2.0).&#10;Over that time, I&amp;rsquo;ve built a few tools that have made life easier for myself,&#10;the teams I&amp;rsquo;ve worked and the Zabbix community.&lt;/p&gt;&#10;&lt;p&gt;A couple of these tools are well known, like the popular libzbxpgsql, g2z or&#10;mib2zabbix, but others are obscured in the depths of my GitHub repository&#10;list.&lt;/p&gt;&#10;&lt;p&gt;I hope by listing these tools here, that they are of some use to you, and you&#10;might even consider contributing more rad features to them.&lt;/p&gt;&#10;&lt;h2 id="monitoring"&gt;Monitoring&lt;/h2&gt;&#10;&lt;h3 id="postgresql-module"&gt;&lt;a href="https://github.com/cavaliercoder/libzbxpgsql"&gt;PostgreSQL Module&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A native Zabbix module for high performance monitoring of PostgreSQL servers.&lt;/p&gt;&#10;&lt;h3 id="systemd-module"&gt;&lt;a href="https://github.com/cavaliercoder/zabbix-module-systemd"&gt;Systemd Module&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A native Zabbix module to monitor systemd units via D-Bus.&lt;/p&gt;&#10;&lt;h3 id="sockets-module"&gt;&lt;a href="https://github.com/cavaliercoder/zabbix-module-sockets"&gt;Sockets Module&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A native Zabbix module for monitoring Linux sockets - including TCP, TCP6, UDP,&#10;UDP6 and UNIX.&lt;/p&gt;&#10;&lt;h2 id="configuration"&gt;Configuration&lt;/h2&gt;&#10;&lt;h3 id="zabbops"&gt;&lt;a href="https://github.com/cavaliercoder/zabbops"&gt;Zabbops&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A Python package for automating Zabbix configuration using Amazon Web Services&#10;APIs and services.&lt;/p&gt;&#10;&lt;h3 id="postgresql-partioning-scripts"&gt;&lt;a href="https://github.com/cavaliercoder/zabbix-pgsql-partitioning"&gt;PostgreSQL Partioning scripts&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;PostgreSQL scripts to create and manage partitions for massive Zabbix databases.&lt;/p&gt;&#10;&lt;h2 id="deployment"&gt;Deployment&lt;/h2&gt;&#10;&lt;h3 id="docker-zabbix"&gt;&lt;a href="https://github.com/cavaliercoder/docker-zabbix"&gt;Docker-Zabbix&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Dockerfiles to build vanilla, all-in-one Zabbix containers of common versions.&#10;Mostly useful for testing integrations with vanilla Zabbix instances.&lt;/p&gt;&#10;&lt;h3 id="puppet-zabbix"&gt;&lt;a href="https://github.com/cavaliercoder/puppet-zabbix"&gt;Puppet-Zabbix&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A Puppet module to install, configure and maintain Zabbix on RHEL derivative&#10;operating systems.&lt;/p&gt;&#10;&lt;h3 id="packer-zabbix"&gt;&lt;a href="https://github.com/cavaliercoder/packer-zabbix"&gt;Packer-Zabbix&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A Packer script to build a Vagrant box running Zabbix.&lt;/p&gt;&#10;&lt;h3 id="zabbix-msi-wix-template"&gt;&lt;a href="https://github.com/cavaliercoder/zabbix-msi"&gt;Zabbix MSI WiX template&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A WiX template to build a MSI package of the Zabbix agent and tools for&#10;installation on Windows systems.&lt;/p&gt;&#10;&lt;h2 id="extending-zabbix"&gt;Extending Zabbix&lt;/h2&gt;&#10;&lt;h3 id="go2zabbix"&gt;&lt;a href="https://github.com/cavaliercoder/g2z"&gt;Go2Zabbix&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A Go-lang library for creating loadable Zabbix modules, written in Go.&lt;/p&gt;&#10;&lt;h3 id="embedded-python-module"&gt;&lt;a href="https://github.com/cavaliercoder/libzbxpython"&gt;Embedded Python Module&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A native Zabbix module that embeds a Python interpreter into the agent, to allow&#10;for high performance Python script execution. Also includes a Python library to&#10;simplify the creation of embedded Zabbix modules.&lt;/p&gt;&#10;&lt;h2 id="testing"&gt;Testing&lt;/h2&gt;&#10;&lt;h3 id="zabbix-agent-bench"&gt;&lt;a href="https://github.com/cavaliercoder/zabbix_agent_bench"&gt;Zabbix Agent Bench&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A simple binary for highly parallel stress testing of a Zabbix agent and its&#10;loaded modules or User Parameters. Useful for highlighting issues in code or&#10;for simple integration tests.&lt;/p&gt;&#10;&lt;h2 id="templating"&gt;Templating&lt;/h2&gt;&#10;&lt;h3 id="zabbix-template-convertor"&gt;&lt;a href="https://github.com/cavaliercoder/zabbix-template-convertor"&gt;Zabbix Template Convertor&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A Python script to convert Zabbix templates between versions. For example, you&#10;can convert a 3.2 template to be loadable in Zabbix 2.0.&lt;/p&gt;&#10;&lt;h3 id="mib2zabbix"&gt;&lt;a href="https://github.com/cavaliercoder/mib2zabbix"&gt;MIB2Zabbix&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A Perl script to automatically generate Zabbix templates from SNMP MIB files.&lt;/p&gt;&#10;&lt;h3 id="ps-zabbixtemplates"&gt;&lt;a href="https://github.com/cavaliercoder/ZabbixTemplates"&gt;PS-ZabbixTemplates&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A native PowerShell module with useful extensions to the Zabbix agent, and the&#10;ability to automatically generate templates from Windows PDH Performance&#10;Counters.&lt;/p&gt;&#10;</description><pubDate>Sat, 17 Jun 2017 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/zabbix-tools.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/zabbix-tools.html</guid></item><item><title>Zabbix Conference 2016</title><description>&lt;iframe id="ytplayer" type="text/html" width="740" height="420"&#10; src="https://www.youtube.com/embed/nlk3nMHy188?autoplay=1"&#10; frameborder="0"&gt;&lt;/iframe&gt;&#10;&lt;a href="https://cdn.cavaliercoder.com/blog/2016-09-18-zabbix-conference-2016/slides.pdf" target="_blank"&gt;&#10; &lt;img class="right" src="https://cdn.cavaliercoder.com/blog/2016-09-18-zabbix-conference-2016/slides.png" alt="Download slide deck"&gt;&#10;&lt;/a&gt;&#10;&lt;p&gt;I&amp;rsquo;m finally recovering from jet lag after landing home from Zabbix Conference&#10;2016 in Riga, Latvia. My good mate and colleague, James Cook and I had the&#10;pleasure of attending the conference this year to discover what&amp;rsquo;s new with&#10;Zabbix, how others are making use of it, and most importantly, to meet and greet&#10;with the superb team at Zabbix SIA HQ.&lt;/p&gt;&#10;&lt;p&gt;After my talk at &lt;a href="https://hotsrc.dev/blog/monitoring-6000-hosts-in-zabbix.html"&gt;Perth DevOps&#10;Meetup&lt;/a&gt;, Zabbix&#10;were kind enough to ask me to give a similar presentation at the conference. You&#10;can &lt;a href="https://cdn.cavaliercoder.com/blog/2016-09-18-zabbix-conference-2016/slides.pdf"&gt;download a PDF copy&#10;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;My talk covered a little about Kinetic IT and our client, the WA Department of&#10;Education. I then described some of the automation tools we have written to&#10;turn Zabbix into a fairly autonomous monitoring system. Finally I gave a&#10;demonstration of a selection of the automation tools that I have released or&#10;rewritten for open source.&lt;/p&gt;&#10;&lt;p&gt;Please also, check out the talks for all the other speakers at the&#10;&lt;a href="http://www.zabbix.com/conf2016_agenda.php"&gt;Zabbix Conference 2016 homepage&lt;/a&gt;.&lt;/p&gt;&#10;</description><pubDate>Sun, 18 Sep 2016 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/zabbix-conference-2016.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/zabbix-conference-2016.html</guid></item><item><title>Downloading large files in Go</title><description>&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Note, 2026:&lt;/strong&gt; this article describes grab v1. The package has since moved to&#10;&lt;a href="https://github.com/cavaliergopher/grab"&gt;github.com/cavaliergopher/grab&lt;/a&gt; and&#10;the v3 API differs from the code below — &lt;code&gt;GetAsync&lt;/code&gt; is gone in favour of&#10;&lt;code&gt;Client.Do&lt;/code&gt; and &lt;code&gt;Client.DoChannel&lt;/code&gt;, and &lt;code&gt;IsNoFilename&lt;/code&gt; is now the&#10;&lt;code&gt;ErrNoFilename&lt;/code&gt; error value. The design discussion still holds; for working&#10;code, see the&#10;&lt;a href="https://pkg.go.dev/github.com/cavaliergopher/grab/v3"&gt;current documentation&lt;/a&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The &lt;code&gt;net/http&lt;/code&gt; package bundled with Go provides a really solid HTTP&#10;implementation which excels particularly as a base for HTTP based API clients&#10;and servers.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re writing software which needs to download large files from the&#10;Internet, such as ISO images or software packages, you may need to implement&#10;some client side logic to overcome some more use-case specific challenges such&#10;as:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;multiple concurrent downloads&lt;/li&gt;&#10;&lt;li&gt;naming of downloaded files&lt;/li&gt;&#10;&lt;li&gt;UI feedback with progress indicators&lt;/li&gt;&#10;&lt;li&gt;clean cancellation of running downloads&lt;/li&gt;&#10;&lt;li&gt;resuming of interrupted downloads&lt;/li&gt;&#10;&lt;li&gt;validating downloaded files using checksums&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;This article will step you through using a Go package called&#10;&lt;a href="https://github.com/cavaliergopher/grab"&gt;grab&lt;/a&gt; which abstracts &lt;code&gt;net/http&lt;/code&gt; to&#10;provide these features. We&amp;rsquo;ll build a simple &amp;lsquo;wget&amp;rsquo;-like binary to make use of&#10;all such awesomeness.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;grab&lt;/code&gt; provides convenience methods &lt;code&gt;grab.Get&lt;/code&gt;, &lt;code&gt;grab.GetAsync&lt;/code&gt; and&#10;&lt;code&gt;grab.GetBatch&lt;/code&gt; for simple operations. When you need more control over the&#10;HTTP session, you can use a &lt;code&gt;grab.Client&lt;/code&gt; and configure it to your needs.&lt;/p&gt;&#10;&lt;p&gt;Examples for these functions and using a custom client are included below.&lt;/p&gt;&#10;&lt;p&gt;To get started, install the &lt;code&gt;grab&lt;/code&gt; package with:&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;$ go get github.com/cavaliercoder/grab&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;h2 id="download-a-file"&gt;Download a file&lt;/h2&gt;&#10;&lt;p&gt;The simplest way to download a file is using &lt;code&gt;grab.Get&lt;/code&gt;. It accepts two&#10;parameters; a destination file path and the source URL. &lt;code&gt;grab.Get&lt;/code&gt; uses&#10;&lt;code&gt;grab.DefaultClient&lt;/code&gt; as a HTTP client which has default settings. It will follow&#10;redirect responses from remote servers and use a corporate proxy if configured&#10;on the host system. Essentially, &lt;code&gt;grab.Get&lt;/code&gt; is a wrapper for&#10;&lt;code&gt;grab.DefaultClient.Do&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;You may specify an existing or non-existing file path as the destination or you&#10;may specify an existing directory.&lt;/p&gt;&#10;&lt;p&gt;If a directory is given as the destination, &lt;code&gt;grab&lt;/code&gt; will determine the filename&#10;using &lt;code&gt;Content-Disposition&lt;/code&gt; headers if they are returned by the remote server or&#10;extract a filename from the source URL. If either of these features fails, an&#10;error is returned which can be identified using &lt;code&gt;grab.IsNoFilename&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;If the destination filename exists, &lt;code&gt;grab&lt;/code&gt; assumes it is a complete or partially&#10;complete download and will resume downloading from the end of the file if&#10;supported by the remote server. Otherwise the file will be overwritten.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;grab.Get&lt;/code&gt; and all other download functions return a &lt;code&gt;grab.Response&lt;/code&gt; which&#10;includes context about the downloaded file; including the path where the file&#10;was saved.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;grab.Get&lt;/code&gt; is a blocking, synchronous operation, which means that the function&#10;does not return a response until the download is complete or encounters an&#10;error. This is not terribly useful for lengthy downloads so I&amp;rsquo;ll solve this&#10;problem a little further down.&lt;/p&gt;&#10;&lt;p&gt;First, the following example will create a simple binary which will download a&#10;source file from a URL specified on the command line and save it to the current&#10;working directory (&lt;code&gt;&amp;quot;.&amp;quot;&lt;/code&gt;).&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/cavaliercoder/grab&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// get URL to download from command args&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;usage: %s url\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// download file&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Downloading %s...\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error downloading %s: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Successfully downloaded to %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Build it with:&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;$ go build -o grab-example&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;p&gt;and run it with:&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;$ ./grab-example http://some-url&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;h2 id="add-progress-updates"&gt;Add progress updates&lt;/h2&gt;&#10;&lt;p&gt;The simple example above will download a file but it is not practical for&#10;lengthy downloads which should provide some feedback to the user with the&#10;progress of the download.&lt;/p&gt;&#10;&lt;p&gt;The following example uses &lt;code&gt;grab.GetAsync&lt;/code&gt; which immediately returns a channel&#10;which will receive a &lt;code&gt;*grab.Response&lt;/code&gt; and close as soon as the download has been&#10;negotiated with the remote server, before the file transfer has started.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;grab.GetAsync&lt;/code&gt; is a wrapper for &lt;code&gt;grab.DefaultClient.DoAsync&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Once the response is received, it can be polled periodically to monitor the&#10;progress of the file transfer until it is finished. This example simply prints a&#10;progress update every 200ms.&lt;/p&gt;&#10;&lt;p&gt;All of the &lt;code&gt;grab.Response&lt;/code&gt; methods are thread-safe and atomic.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/cavaliercoder/grab&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// get URL to download from command args&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;usage: %s url\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// start file download&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Downloading %s...\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;respch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error downloading %s: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// block until HTTP/1.1 GET response is received&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Initializing download...\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;respch&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// print progress until transfer is complete&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsComplete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;\033[1AProgress %d / %d bytes (%d%%)\033[K\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BytesTransferred&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// clear progress line&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;\033[1A\033[K&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// check for errors&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error downloading %s: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Successfully downloaded to ./%s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="batch-downloads"&gt;Batch downloads&lt;/h2&gt;&#10;&lt;p&gt;The next example allows multiple URLs to be given on the command line and&#10;downloaded simultaneously using &lt;code&gt;grab.GetBatch()&lt;/code&gt; which is a wrapper for&#10;&lt;code&gt;grab.DefaultClient.DoBatch()&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Files will be transferred three at a time, as &lt;code&gt;3&lt;/code&gt; is given for the worker count&#10;parameter in the call to &lt;code&gt;grab.Getbatch()&lt;/code&gt;. To download all files immediately&#10;(one worker per request), simply give &lt;code&gt;0&lt;/code&gt; as the worker count parameter. Each&#10;download will be saved to the current working directory as &lt;code&gt;&amp;quot;.&amp;quot;&lt;/code&gt; is given as the&#10;destination parameter.&lt;/p&gt;&#10;&lt;p&gt;With a batch operation we don&amp;rsquo;t have immediate access to any &lt;code&gt;grab.Response&lt;/code&gt;.&#10;These will be sent via the channel returned by &lt;code&gt;grab.GetBatch()&lt;/code&gt; each time a&#10;worker starts a requested URL. We don&amp;rsquo;t know when all of these responses will&#10;arrive, but we want to monitor downloads which are already in process so we&#10;create a &lt;code&gt;for&lt;/code&gt; loop and &lt;code&gt;select&lt;/code&gt; between two channels; one to receive responses&#10;and the other (the ticker) to periodically print the status of the responses&#10;which have already been received.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/cavaliercoder/grab&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// get URL to download from command args&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;usage: %s url [url]...\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// start file downloads, 3 at a time&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Downloading %d files...\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;respch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// start a ticker to update progress every 200ms&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// monitor downloads&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;respch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// a new response has been received and has started downloading&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// (nil is received once, when the channel is closed by grab)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// clear lines&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;\033[%dA\033[K&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// update completed downloads&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsComplete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// print final result&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error downloading %s: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Finished %s %d / %d bytes (%d%%)\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BytesTransferred&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// mark completed&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// update downloads in progress&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Downloading %s %d / %d bytes (%d%%)\033[K\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BytesTransferred&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%d files successfully downloaded.\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="customizing-requests"&gt;Customizing requests&lt;/h2&gt;&#10;&lt;p&gt;None of the previous convenience methods offer any control over the HTTP request&#10;or transport. Much like the &lt;code&gt;net/http&lt;/code&gt; package, &lt;code&gt;grab&lt;/code&gt; enable such controls as&#10;well as additional features such as checksum validation via &lt;code&gt;grab.Client&lt;/code&gt;,&#10;&lt;code&gt;grab.Request&lt;/code&gt; and &lt;code&gt;grab.Response&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;In the same way that the &lt;code&gt;grab.Get*&lt;/code&gt; methods work behind the scenes, using&#10;a client requires that you define and configure a &lt;code&gt;grab.Client&lt;/code&gt;, one or more&#10;&lt;code&gt;grab.Requests&lt;/code&gt; and pass them to one of the &lt;code&gt;grab.Client.Do*&lt;/code&gt; methods which&#10;then return a &lt;code&gt;grab.Response&lt;/code&gt; for each request.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s take a look at some of the configuration options available when creating&#10;a download request in the following example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// create a download request&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://some-url/my-file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set destination file path&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;./my-file&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set expected file size if known&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set expected file checksum if known&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;b982505fc48ea2221d163730c1856770dc6579af9eb73c997541c4ac6ecf20a9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetChecksum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// delete the downloaded file if it fails checksum validation&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemoveOnError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// request a notification when the download is completed (successfully or&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// otherwise)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NotifyOnClose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You may also configure the HTTP request itself, including request headers,&#10;cookies, authentication, etc. using the &lt;code&gt;http.Request&lt;/code&gt; nested in the&#10;&lt;code&gt;grab.Request.HTTPRequest&lt;/code&gt; field, as demonstrated in the following code:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set custom HTTP method&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set request headers&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;X-SOME-HEADER&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Custom value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set a cookie&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set basic HTTP authentication headers&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBasicAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;username&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;password&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="using-a-custom-client"&gt;Using a custom Client&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;grab&lt;/code&gt; provides a default client, &lt;code&gt;grab.DefaultClient&lt;/code&gt; which is used by each of&#10;the &lt;code&gt;grab.Get*&lt;/code&gt; methods. If you wish to customize HTTP transport rules such&#10;as connection timeouts, proxy configuration, redirect policies, etc. you may&#10;create a custom client with &lt;code&gt;grab.NewClient&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;The following code includes examples of customizing a client:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// create a custom client&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set the user agent string for all HTTP requests&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UserAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set a custom connection timeout&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// replace the HTTP client with one that bypasses any system proxy settings&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;Proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once you have configured a client and some requests, you pass the requests to&#10;whichever of the &lt;code&gt;grab.Do*&lt;/code&gt; methods best match your use case. These methods&#10;are synonymous with the &lt;code&gt;grab.Get*&lt;/code&gt; methods and behave as follows:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;grab.Client.Do&lt;/code&gt; - blocks and returns a response once the download is&#10;completed or an error occurs&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;grab.Client.DoAsync&lt;/code&gt; - immediately returns a channel which will receive a&#10;single &lt;code&gt;*grab.Response&lt;/code&gt; and close as soon as the download has been negotiated&#10;with the remote server; before the transfer has started&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;grab.Client.DoBatch&lt;/code&gt; - accepts multiple requests and executes them&#10;simultaneously. It accepts a &lt;code&gt;workers&lt;/code&gt; parameter which determines how many&#10;downloads will be in process at any given time, while the remaining requests&#10;are queued until a worker is available. It returns a channel which will&#10;receive a &lt;code&gt;grab.*Response&lt;/code&gt; for each request and close once they are all&#10;sent. The responses are sent through the channel as soon as the download has&#10;been negotiated with the remote server; before the transfer has started&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The following and final example uses a custom client to download a batch of&#10;files with periodic progress updates:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/cavaliercoder/grab&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// get URL to download from command args&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;usage: %s url [url]...\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// create a custom client&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UserAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Grab example&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// create requests from command arguments&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;reqs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;reqs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// start file downloads, 3 at a time&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Downloading %d files...\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;respch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DoBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reqs&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// start a ticker to update progress every 200ms&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// monitor downloads&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;respch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// a new response has been received and has started downloading&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// (nil is received once, when the channel is closed by grab)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// clear lines&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;\033[%dA\033[K&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// update completed downloads&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsComplete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// print final result&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error downloading %s: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Finished %s %d / %d bytes (%d%%)\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BytesTransferred&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// mark completed&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// update downloads in progress&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;inProgress&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Downloading %s %d / %d bytes (%d%%)\033[K\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BytesTransferred&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%d files successfully downloaded.\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reqs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><pubDate>Tue, 19 Jan 2016 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/downloading-large-files-in-go.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/downloading-large-files-in-go.html</guid></item><item><title>Recommended books</title><description>&lt;p&gt;For me, the best measure of a book is whether it changes the way I think. These&#10;are the ones that did, in no particular order. Most of them found me somewhere&#10;between running infrastructure teams and building storage systems, and they&amp;rsquo;ve&#10;shaped how I approach both.&lt;/p&gt;&#10;&lt;h2 id="thinking-in-systems"&gt;Thinking in Systems&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/1603580557" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/thinking-in-systems.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Donella H. Meadows&#10;&lt;p&gt;Everything is connected, across time and space. Donella beautifully illustrates&#10;a number of different systems &amp;ldquo;Animals&amp;rdquo; - common patterns that appear in&#10;air-conditioner thermostats, the art of steering a car on the road and&#10;distributed computing systems. This book taught me to prefer small, calculated&#10;moves over wholesale systems changes and to better interpret and respond to&#10;measurements over time.&lt;/p&gt;&#10;&lt;h2 id="lean-enterprise"&gt;Lean Enterprise&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/1449368425" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/lean-enterprise.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Jez Humble, Joanne Molesky and Barry O'Reilly&#10;&lt;p&gt;When an IT organization gets large enough, the need for predictability and&#10;stability starts to stifle innovation until fast moving, unencumbered startups&#10;disrupt them.&lt;/p&gt;&#10;&lt;p&gt;Lean Enterprise provides a prescriptive process and plenty of follow up reading&#10;to enable large organizations to overcome &amp;ldquo;inertia&amp;rdquo; while minimizing cost and&#10;risk. The business is divided into three horizons where first, exploration and&#10;experimentation take place. Experiments that prove to deliver value are&#10;transitioned into the &amp;lsquo;Exploitation&amp;rsquo; horizon where an MVP is quickly transformed&#10;into a sustainable product. Finally, the &amp;lsquo;Stabilization&amp;rsquo; horizon focuses on&#10;efficiency and stability; keeping the lights on so new experimentation can get&#10;underway.&lt;/p&gt;&#10;&lt;h2 id="the-open-organization"&gt;The Open Organization&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/1625275277" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/the-open-organization.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Jim Whitehurst&#10;&lt;p&gt;It&amp;rsquo;s no secret I&amp;rsquo;m a big believer in the power of Free and Open Source Software&#10;and open source communities. This book offers an inside look at Red Hat&amp;rsquo;s&#10;success as the largest open source vendor in the world, from the perspective of&#10;their C.E.O., Jim Whitehurst.&lt;/p&gt;&#10;&lt;p&gt;Jim explores how Red Hat leverage the power of internal and external communities&#10;to build the world class software products that run the New York Stock Exchange&#10;and countless other high profile enterprises.&lt;/p&gt;&#10;&lt;p&gt;What I found most interesting was Red Hat&amp;rsquo;s adoption of a meritocracy; where&#10;community members and their contributions are evaluated on their merits, not&#10;their title or pay grade. This has changed the way I take on ideas from my&#10;colleagues and has also encouraged me to participate in discussions outside my&#10;remit.&lt;/p&gt;&#10;&lt;h2 id="made-to-stick"&gt;Made to Stick&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/1400064287" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/made-to-stick.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Chip Heath and Dan Heath&#10;&lt;p&gt;Ever wondered why the latest company strategy is not getting any traction? Why&#10;your marketing pitch is falling on deaf ears? Why even clear, concise facts are&#10;not enough to persuade people to your way of thinking?&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Made to Stick&lt;/em&gt; answers these questions in a common-sense manner that has&#10;profoundly changed the way I communicate. It offers a very simple checklist (the&#10;&lt;em&gt;SUCCESs&lt;/em&gt; model) to apply to your own communications to ensure they will be&#10;understood, memorable and influence people&amp;rsquo;s thinking.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;ve found this book particularly useful in presenting my ideas for new&#10;projects, and in writing for my blog.&lt;/p&gt;&#10;&lt;h2 id="the-five-dysfunctions-of-a-team"&gt;The Five Dysfunctions of a Team&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/0787960756" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/the-five-dysfunctions-of-a-team.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Patrick Lencioni&#10;&lt;p&gt;From the teams on the service desk to the executives and the board, nothing is&#10;more critical to the success of a business than effective teamwork. In &lt;em&gt;The Five&#10;Dysfunctions of a Team&lt;/em&gt;, Patrick Lencioni illustrates the reforming of an&#10;executive team using a simple, five tier model which highlights the most common&#10;obstacles to being effective as a team.&lt;/p&gt;&#10;&lt;p&gt;For me it highlighted some common sense cause and effect in interpersonal and&#10;inter-team politics in my own workplace. Where I might have previously strived&#10;to encourage people to focus on business outcomes and results, I learned we&#10;first need to create a culture of accountability, which first needs unanimous&#10;commitment towards common goals, which first needs constructive conflict to take&#10;place, which first requires people to trust one another enough to feel&#10;vulnerable.&lt;/p&gt;&#10;&lt;h2 id="the-phoenix-project"&gt;The Phoenix Project&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/0988262509" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/the-phoenix-project.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Gene Kim, Kevin Behr and George Spafford&#10;&lt;p&gt;This book is the de facto entry point for anyone interested in understanding&#10;DevOps. What I love most, is that it is a management parable, not another&#10;inapplicable implementation of Continuous Delivery or Infrastructure As Code.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;The Phoenix Project&lt;/em&gt; is a very deliberate homage to &lt;a href="#the-goal"&gt;The Goal&lt;/a&gt; and&#10;is written in a very similar style, adapted for modern IT audiences. It&#10;incorporates elements of The Goal&amp;rsquo;s Theory of Constraints, Lean manufacturing,&#10;&lt;a href="#toyota-kata"&gt;Toyota Kata&lt;/a&gt;, Agile and Continuous Delivery into a DevOps&#10;management recipe and way of thinking.&lt;/p&gt;&#10;&lt;p&gt;It focuses primarily on helping the business achieve its goals using IT as a&#10;core competency, rather than focusing on the Dev/Ops relationship and&#10;automation tools. It helped me to focus and realign my day-to-day activities to&#10;those that can be directly linked to successful outcomes for the business as a&#10;whole.&lt;/p&gt;&#10;&lt;h2 id="the-goal"&gt;The Goal&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/0884271951" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/the-goal.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Eliyahu M. Goldratt&#10;&lt;p&gt;For a parable written in 1984 concerning manufacturing plants, the&#10;application of &lt;em&gt;The Goal&lt;/em&gt; to modern IT development and operations is incredibly&#10;potent and is expanded on further in&#10;&lt;a href="#the-phoenix-project"&gt;The Phoenix Project&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;This book has really helped me to identify where to (and where not to) invest my&#10;time and energy, especially concerning IT automation and process improvement.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;ve written a full review in &lt;a href="https://hotsrc.dev/blog/the-goal.html"&gt;The Goal: A challenge to IT&#10;Operations&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="toyota-kata"&gt;Toyota Kata&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/0071635238" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/toyota-kata.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Mike Rother&#10;&lt;p&gt;Almost every &amp;ldquo;improvement&amp;rdquo; initiative I have ever participated in could be&#10;described as an &amp;ldquo;implementation&amp;rdquo; of a known methodology. We implement Scrum or&#10;Lean or war rooms or silo crushing meetings or Continuous Delivery, etc., etc.,&#10;etc.&lt;/p&gt;&#10;&lt;p&gt;Sometimes the implementations are successful (i.e. a well run Scrum team) but&#10;don&amp;rsquo;t actually benefit the business in any tangible or profitable way and even&#10;moderately successful implementations tend to degrade very quickly.&lt;/p&gt;&#10;&lt;p&gt;Mike Rother takes a look inside Toyota&amp;rsquo;s successful Continuous Improvement&#10;culture to show why people who copy Toyota&amp;rsquo;s process &lt;em&gt;implementations&lt;/em&gt; typically&#10;cannot emulate Toyota&amp;rsquo;s success.&lt;/p&gt;&#10;&lt;p&gt;Toyota&amp;rsquo;s Lean processes are actually an evolution, more explicitly, an&#10;adaptation to change, specific to their needs and environment. This book&#10;explores &lt;em&gt;how&lt;/em&gt; Toyota approaches changes and develops improvements. Their way of&#10;thinking and &amp;ldquo;Improvement Kata&amp;rdquo; can be applied almost universally; including in&#10;IT.&lt;/p&gt;&#10;&lt;p&gt;This book has helped me to bake Continuous Improvement into every action I take,&#10;rather than approaching it as sporadic and typically unsuccessful projects.&lt;/p&gt;&#10;&lt;h2 id="who-moved-my-cheese"&gt;Who Moved My Cheese?&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/0399144463" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/who-moved-my-cheese.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Spencer Johnson M.D.&#10;&lt;p&gt;It only took roughly an hour to read this profound little parable from cover to&#10;cover. &lt;em&gt;Who Moved My Cheese&lt;/em&gt; follows four little creatures, Sniff, Scurry, Hem&#10;and Haw, who navigate their way through a maze searching for &lt;em&gt;Cheese&lt;/em&gt;, an&#10;analogy for the things that make us happy. Most importantly, it explores how&#10;each character reacts to changes in the maze and the location of their Cheese.&lt;/p&gt;&#10;&lt;p&gt;Change is inevitable in our lives, and especially in IT. This book offers some&#10;simple ideas, expressed as &lt;em&gt;Handwriting on the wall&lt;/em&gt; of the maze, for how to&#10;identify, cope with and adapt to change effectively.&lt;/p&gt;&#10;&lt;p&gt;I identify most with the character &lt;em&gt;Sniff&lt;/em&gt;. I tend to sniff out change and&#10;typically get excited by it. This book has helped me to better understand that&#10;others approach change differently and that being a &amp;ldquo;forward thinker&amp;rdquo; can&#10;actually be perceived as threatening.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m making a point of reading this regularly to make sure I don&amp;rsquo;t get trapped in&#10;the arrogance of success or miss the writing on the wall when my comfort zones&#10;start to change.&lt;/p&gt;&#10;&lt;h2 id="the-c-programming-language"&gt;The C Programming Language&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/0131103628" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/the-c-programming-language.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Brian W. Kernighan and Dennis M. Ritchie&#10;&lt;p&gt;Not a programmer? Doesn&amp;rsquo;t matter. The concepts in this book and the tutorials&#10;will be a real challenge for a first time programmer and you may even give up.&#10;In fact, I did give up. But I came back a few years later and conquered it&#10;cover to cover.&lt;/p&gt;&#10;&lt;p&gt;As a Sysadmin, it&amp;rsquo;s important to understand the difference between the heap and&#10;the stack, what a pointer is, what shared memory is for, how memory gets&#10;allocated and corrupted, how memory leaks happen, how code patterns affect&#10;performance and capacity, how shared libraries are really used, what stack&#10;traces mean, etc.&lt;/p&gt;&#10;&lt;p&gt;If you can grok this book, all of these concepts become much clearer and you&amp;rsquo;ll&#10;even be able to build small tools of your own, participate better in open source&#10;communities and communicate with developers in a common tongue.&lt;/p&gt;&#10;&lt;h2 id="good-to-great"&gt;Good to Great&lt;/h2&gt;&#10;&lt;a href="http://www.jimcollins.com/books.html" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/good-to-great.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Jim Collins&#10;&lt;p&gt;Good to Great is probably the most thoroughly researched and well presented&#10;work I have ever encountered. Jim Collins dissects the differentiators between&#10;companies which outperform the market consistently over fifteen years to those&#10;that don&amp;rsquo;t. The findings are often counter-intuitive but are very well&#10;established in the presented facts.&lt;/p&gt;&#10;&lt;p&gt;Jim condenses everything down into a tangible framework called &lt;em&gt;The Flywheel&lt;/em&gt;&#10;which offers stepping stones to replicating the success of the Great companies.&lt;/p&gt;&#10;&lt;p&gt;One stage of the flywheel, &lt;em&gt;The Hedgehog Concept&lt;/em&gt; complements the idea presented&#10;in &lt;a href="#made-to-stick"&gt;Made to Stick&lt;/a&gt; that succinct, unidirectional strategies are&#10;most effective at enabling a company to move forward in unison.&lt;/p&gt;&#10;&lt;p&gt;A later stage, &lt;em&gt;First Who, Then What&lt;/em&gt; emphasises that &amp;ldquo;getting the right people&#10;on the bus&amp;rdquo; is the first and most critical part of building a successful&#10;company. This idea is echoed as the &lt;em&gt;First Key&lt;/em&gt; in&#10;&lt;a href="#first-break-all-the-rules"&gt;First, Break All the Rules&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d strongly recommend anyone read this book as it offers a great sense of&#10;context around organisational success. More importantly however, the principles&#10;of a &lt;em&gt;Level Five Leader&lt;/em&gt; should be taken on board by team members at any level&#10;of experience or title.&lt;/p&gt;&#10;&lt;h2 id="first-break-all-the-rules"&gt;First, Break All the Rules&lt;/h2&gt;&#10;&lt;a href="https://www.amazon.com/dp/1595621113" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/first-break-all-the-rules.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;By Marcus Buckingham and Curt Coffman&#10;&lt;p&gt;This book brings together the findings from interviews with more than 80,000&#10;successful managers to find out what makes them so effective.&lt;/p&gt;&#10;&lt;p&gt;The following keys are found to be consistent across most of the managers who&#10;took part in the study:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;They hire for talent and understand the recurring thoughts, feelings and&#10;behaviors that are needed on their team&lt;/li&gt;&#10;&lt;li&gt;They define clear and valuable outcomes and leave the implementation to their&#10;employees&lt;/li&gt;&#10;&lt;li&gt;They focus on developing strengths instead of trying to fix weaknesses&lt;/li&gt;&#10;&lt;li&gt;They work to fit people where they belong and can succeed&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I&amp;rsquo;d recommend this book to anyone looking to move into a management or&#10;leadership role, anyone looking to expand their team, hoping to land on the&#10;right hire, or anyone hoping to understand why their current org-structure is&#10;letting them down.&lt;/p&gt;&#10;&lt;h2 id="site-reliability-engineering"&gt;Site Reliability Engineering&lt;/h2&gt;&#10;&lt;a href="http://www.amazon.in/dp/149192912X" target="_blank"&gt;&#10; &lt;img class="book-cover" src="https://cdn.cavaliercoder.com/blog/2015-12-27-recommended-reading/site-reliability-engineering.jpg" alt="Buy on Amazon"&gt;&#10;&lt;/a&gt;&#10;Edited by Betsy Beyer, Chris Jones, Jennifer Petoff and Niall Richard Murphy.&#10;&lt;p&gt;This book collects essays from key members of Google&amp;rsquo;s SRE teams that build,&#10;deploy, monitor and maintain Google&amp;rsquo;s large scale production systems. The essays&#10;cover the philosophies, processes, architectures, mantras and mistakes that&#10;enable Google to be so successful.&lt;/p&gt;&#10;&lt;p&gt;Many of the lessons are directly applicable to much smaller scale IT service&#10;providers, like their incident response procedures, on-call rostering and&#10;philosophies concerning risk management and eliminating low value &amp;ldquo;toil&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Other lessons were less applicable, but equally interesting, such as the&#10;design challenges of distributed consensus systems, automated deployment and&#10;data processing pipelines.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d recommend this book to engineers and operations managers in any IT org. It&amp;rsquo;s&#10;a real privilege to be able to learn from the refined and battle hardened&#10;practices of some of the world&amp;rsquo;s most talented operators.&lt;/p&gt;&#10;</description><pubDate>Sun, 27 Dec 2015 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/recommended-reading.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/recommended-reading.html</guid></item><item><title>The Goal: A challenge to IT Operations</title><description>&lt;p&gt;This week I took an adventure in 1980&amp;rsquo;s manufacturing plant optimization in&#10;Eliyahu M. Goldratt&amp;rsquo;s &amp;ldquo;&lt;a href="https://www.amazon.com/The-Goal-Process-Ongoing-Improvement/dp/0884270610"&gt;The Goal: A process of ongoing&#10;improvement&lt;/a&gt;&amp;rdquo;.&#10;The book is featured in discussions around DevOps, particularly concerning its&#10;heavy reference in Kim, Behr and Stafford&amp;rsquo;s &lt;a href="http://itrevolution.com/books/phoenix-project-devops-book/"&gt;The Phoenix&#10;Project&lt;/a&gt; and all&#10;things Lean.&lt;/p&gt;&#10;&lt;p&gt;It was an unusual read for me but has certainly changed the way I approach&#10;problems in IT operations and even in getting my daughter to school on time.&lt;/p&gt;&#10;&lt;p&gt;In this post, I&amp;rsquo;ll aim to condense some of the principles taught in the book,&#10;their application in DevOps and finally offer a challenge to IT organizations&#10;where application deployment is &lt;em&gt;not&lt;/em&gt; the primary line of business&lt;/p&gt;&#10;&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;&#10;&lt;p&gt;&lt;em&gt;The Goal&lt;/em&gt; is a fictional business parable that follows Alex Rogo, a&#10;manufacturing plant manager at UniCo manufacturing. His plant (and the business&#10;as a whole) are deep in the red, despite employing some of the best management&#10;wizardry of cost cutting exercises, order expedition, localized optimizations&#10;and even fancy new robots.&lt;/p&gt;&#10;&lt;p&gt;Alex has a chance meeting with a physics professor, Jonah, from his university&#10;days, who challenges some of Alex&amp;rsquo;s (and my own) most basic assumptions about&#10;productivity and the reason the business exists at all. Jonah leads Alex, via&#10;provocative Socratic inquiry, to approach his manufacturing and personal issues&#10;scientifically.&lt;/p&gt;&#10;&lt;p&gt;The result for Alex was saving the business, and a good deal more besides.&lt;/p&gt;&#10;&lt;p&gt;I was surprised how entertaining the book was. For me the subject matter of the&#10;plant was fairly foreign and uninteresting but the story telling is very human&#10;and relatable.&lt;/p&gt;&#10;&lt;p&gt;Manufacturing jargon is kept to a minimum, with only enough detail given to&#10;solidify some of the abstract ideas being taught.&lt;/p&gt;&#10;&lt;p&gt;The book didn&amp;rsquo;t feel dated at all. The only reminder that we are not in the&#10;current millennium is the mention of dot-matrix printers and smoking in the&#10;office.&lt;/p&gt;&#10;&lt;p&gt;Being a work of fiction, I did doubt whether the scale of some of the successes&#10;encountered in the book could be replicated in reality. While the turnaround of&#10;the fictional UniCo is extreme, the common-sense nature of the science described&#10;lends credibility. Fortunately for us, there is now also a long history of well&#10;published successes and failures with Eliyahu&amp;rsquo;s process and similar ideas from&#10;DevOps, the Toyota Production System or Lean manufacturing.&lt;/p&gt;&#10;&lt;h2 id="socratic-inquiry"&gt;Socratic inquiry&lt;/h2&gt;&#10;&lt;p&gt;All of the lessons taught in the book could be condensed into a two or three&#10;page cheat sheet; even with substantial context. Unfortunately, some of the&#10;lessons are so contrary to our modus operandi that in this form they would read&#10;as nonsense; much like the notion of the world being round once did.&lt;/p&gt;&#10;&lt;p&gt;For example, Jonah suggests that &lt;em&gt;most&lt;/em&gt; efficiencies gained in local areas of&#10;production actually serve to cripple the entire system, contrary to my&#10;intuition.&lt;/p&gt;&#10;&lt;p&gt;The beauty of the relationship between Jonah and Alex, is that Jonah never&#10;provides answers to anything. He only provides questions and validation of&#10;Alex&amp;rsquo;s assertions. In doing so, much like the Greek philosopher&#10;&lt;a href="https://en.wikipedia.org/wiki/Socratic_method"&gt;Socrates&lt;/a&gt;, Jonah teaches Alex&#10;how to think. Essentially, Jonah could give Alex a fish and feed him for a day,&#10;but instead teaches Alex how to fish, so Alex can own his own destiny.&lt;/p&gt;&#10;&lt;p&gt;This form of dialog caused me to continually consider my own situation. Clearly&#10;the nuances of manufacturing do not directly apply to IT operations, so some of&#10;the answers Alex discovers have no relevance to me (I don&amp;rsquo;t use heat treat ovens&#10;in the datacenter). Instead the challenges Jonah offers to my assumptions and&#10;way of thinking lead me to answers of my own.&lt;/p&gt;&#10;&lt;h2 id="the-goal"&gt;The goal&lt;/h2&gt;&#10;&lt;p&gt;The first question Jonah poses to Alex is &amp;ldquo;What is the goal&amp;rdquo;? In my position in&#10;IT Operations, I might have answered &amp;ldquo;uptime&amp;rdquo; or &amp;ldquo;minimizing costs&amp;rdquo; or&#10;&amp;ldquo;information security&amp;rdquo; or &amp;ldquo;good customer service&amp;rdquo;. Jonah highlights that these&#10;sorts of answers are actually just a means to an end, not the end itself.&lt;/p&gt;&#10;&lt;p&gt;For UniCo manufacturing, the &lt;em&gt;end&lt;/em&gt;, or the goal, is to make money. I work for an&#10;outsourcing company, who have assigned me to a government body concerned with&#10;education. For me the goal is a little unclear, so I&amp;rsquo;ll give it some more&#10;thought and perhaps conversation with the powers-at-be. Is the goal to make&#10;money for my employer? Or to improve education for my client? Or to make money&#10;for my employer by improving education for my client? Or is the goal to assist&#10;the current government in winning the next election (not educational outcomes)&#10;so we can keep the client and continue to make money?&lt;/p&gt;&#10;&lt;p&gt;The point is, without clarity on the goal, you might unwittingly work to&#10;undermine it. For example, UniCo purchased new welding robots that improved&#10;local efficiency, but actually impacted negatively on their goal; to make money.&lt;/p&gt;&#10;&lt;p&gt;Once the goal is clear, Alex devises a succinct list of critical measures&#10;of progress towards the goal which are used to classify everything in the plant:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Throughput&lt;/strong&gt;: the rate at which the system generates money though completed&#10;sales&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Inventory&lt;/strong&gt;: the money currently invested in assets it intends to sell&#10;(unfinished products, work-in-process, etc.)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Operational expenses&lt;/strong&gt;: money spent to turn inventory into throughput&#10;(wages, power, etc.)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;These measures are specific to UniCo&amp;rsquo;s goal of making money through&#10;manufacturing but could be abstracted to other applications.&lt;/p&gt;&#10;&lt;p&gt;For example in education, throughput might be the number of student who graduate&#10;and proceed into successful work placements, inventory might be the number of&#10;students currently in the system and operational expenses remain; the cost of&#10;employing staff and keeping the lawns.&lt;/p&gt;&#10;&lt;h2 id="dependent-events-statistical-fluctuations"&gt;Dependent events, statistical fluctuations&lt;/h2&gt;&#10;&lt;p&gt;Jonah highlights that a manufacturing plant represents a sequence (or many&#10;sequences) of dependent events. Meaning, some events cannot happen until&#10;preceding events have completed. E.g. assembly cannot begin until all required&#10;parts have arrived. Each step in the process is also subject to statistical&#10;fluctuations; where the duration of each execution of a discrete process will&#10;fluctuate. Welding or painting a part, will always vary in time.&lt;/p&gt;&#10;&lt;p&gt;This is fairly self-evident, and like me, Alex assumes that the &lt;em&gt;average&lt;/em&gt;&#10;execution time of each production line station will determine the throughput of&#10;the system (i.e. time from customer order to dispatch and receipt). In practice&#10;however, the plant is failing to keep up and the issue is compounding over time.&lt;/p&gt;&#10;&lt;p&gt;While hiking a group of scouts in single file through a nature trail, Alex&#10;becomes frustrated that the group of boys cannot seem to stay together and are&#10;running behind time to complete the hike. Alex identifies that the hike is much&#10;like his plant:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;there is a goal: everyone arriving on time, all together&lt;/li&gt;&#10;&lt;li&gt;one boy cannot move forward until the next boy has moved forward&lt;/li&gt;&#10;&lt;li&gt;each step of each boy fluctuates in distance and duration&lt;/li&gt;&#10;&lt;li&gt;the distance covered by the last hiker is the throughput of the system&lt;/li&gt;&#10;&lt;li&gt;the energy expended is the operational expense of the system&lt;/li&gt;&#10;&lt;li&gt;the distance between the first and last hiker, is the inventory or&#10;work-in-process.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Much like the factory, the work-in-process is in continual growth, with the&#10;distance between front and back of the line constantly expanding, increasing&#10;risk and the energy required to compensate for the distance.&lt;/p&gt;&#10;&lt;p&gt;Alex&amp;rsquo;s expectation was that the average pace of the boy scouts would determine&#10;the arrival time. Instead he discovers, that it&amp;rsquo;s actually the maximum&#10;accumulated, negative deviation from the average that determines the throughput&#10;(arrival time) and inventory (distance between front and back) of the group.&lt;/p&gt;&#10;&lt;p&gt;Each step that each boy takes will deviate slightly from the average. Every time&#10;a deviation is in the negative (slightly slower), that deviation accumulates as&#10;the boys behind cannot pass and slow down. Additionally the distance between the&#10;hiker in front increases. Compensating for this phenomena, requires significant&#10;effort and is often outside the capacity of the boys.&lt;/p&gt;&#10;&lt;p&gt;Alex further models the problem by passing matches along a sequence of bowls,&#10;based on the fluctuating rolls of a dice. The throughput of the last bowl is&#10;significantly lower than anticipated.&lt;/p&gt;&#10;&lt;p&gt;This misconception about the capacity of the hikers being based on average pace&#10;is shared at the UniCo plant and in my own estimation.&lt;/p&gt;&#10;&lt;h2 id="the-illusion-of-local-efficiency"&gt;The illusion of local efficiency&lt;/h2&gt;&#10;&lt;p&gt;What Alex learns (and Jonah later validates) is that the whole process is&#10;actually hinged on the bottleneck (or constraint), the slowest boy scout hiker.&#10;Alex forces all of the boys to maintain pace with the slowest boy, young Herbie,&#10;by placing Herbie at the front of the pack. As a result, the boys behind start&#10;operating below capacity which in turn leads to the entire pack walking closely&#10;together (a reduction in inventory or work-in-process). The boys are no longer&#10;required to jog to catch up the gaps between them and so operational expenditure&#10;is reduced.&lt;/p&gt;&#10;&lt;p&gt;Now that Herbie is setting the pace for the group, his capacity gets greater&#10;scrutiny and the group realize Herbie is carrying several kilos of obscure&#10;camping equipment in his ruck sack. The equipment (or load) is distributed&#10;amongst the boys with higher capacity and suddenly the entire group is moving&#10;quicker (throughput)!&lt;/p&gt;&#10;&lt;p&gt;Prior to understanding this metaphor, I might have hinged the success of the&#10;group on the fastest kids (highest capacity resources). Ultimately, any&#10;efficiency gained in the faster boys (strength, speed, etc.) is a complete and&#10;utter waste when considering the goal of arriving on time and together.&lt;/p&gt;&#10;&lt;p&gt;Likewise, the robots purchased by UniCo were a waste. Because of their&#10;impressive capacity, they only served to increase inventory (by piling up&#10;products in front of the next work station) and operational cost (staff working&#10;overtime to catch up). The robots did increase the local efficiency of their&#10;work centers, but undermined the goal of the company, to make money, by&#10;increasing inventory and operational expenditure without increasing throughput&#10;of the entire system.&lt;/p&gt;&#10;&lt;h2 id="the-process-of-ongoing-improvements"&gt;The process of ongoing improvements&lt;/h2&gt;&#10;&lt;p&gt;The author provides a process to solve these problems, which in the parable, is&#10;devised by Alex and his team of colleagues. This is the process Alex used to&#10;rectify the hiking boy scouts, and his manufacturing plant woes:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Identify the system&amp;rsquo;s primary bottleneck/s (Herbie, or the plant&amp;rsquo;s heat treat&#10;machine)&lt;/li&gt;&#10;&lt;li&gt;Decide how to exploit the bottleneck (move Herbie to the front or keep the&#10;heat treat machine running during lunch breaks)&lt;/li&gt;&#10;&lt;li&gt;Subordinate everything else to the above decision (all kids slow down to&#10;Herbie&amp;rsquo;s pace and all other workstations prioritize parts bound for the heat&#10;treat machine and remain idle if there are sufficient parts ready)&lt;/li&gt;&#10;&lt;li&gt;Elevate the system&amp;rsquo;s constraints (distribute the contents of Herbie&amp;rsquo;s&#10;rucksack or install additional heat treat machines)&lt;/li&gt;&#10;&lt;li&gt;If a bottleneck is broken (i.e. no longer the bottleneck) repeat from step 1,&#10;but don&amp;rsquo;t allow inertia to become a constraint (in this case, market demand&#10;became the new bottleneck for Alex&amp;rsquo;s plant, but inertia caused them to&#10;stockpile too many completed products)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="change"&gt;Change&lt;/h2&gt;&#10;&lt;p&gt;Once the manufacturing plant is once again profitable, Alex faces the challenge&#10;of responding to changes in the marketplace and planning ahead to prevent any&#10;further decline. With further prompting from his mentor Jonah, Alex determines&#10;there are three critical challenges for managers in applying his new knowledge:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;What needs to change?&lt;/li&gt;&#10;&lt;li&gt;What does it need to change &lt;strong&gt;to&lt;/strong&gt;?&lt;/li&gt;&#10;&lt;li&gt;How do you execute the change?&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;These steps are particularly critical when approaching changes to the most&#10;difficult of all business resources to manage: people. When trying to change&#10;the culture, values and thinking of people, the book highlights two important&#10;strategies:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Socratic inquiry: leading people to answers by simply invoking curiosity.&#10;Alex did this by sharing Jonah&amp;rsquo;s questions with his colleagues and working&#10;together to find the answers.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;The scientific method: create a hypothesis, A/B test the hypothesis (with a&#10;control) and share the conclusion. Alex did this by comparing the results of&#10;his hypothesis to the results attained in other UniCo plants, or simply with&#10;&amp;ldquo;before and after&amp;rdquo; results.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="contribution-to-devops"&gt;Contribution to DevOps&lt;/h2&gt;&#10;&lt;p&gt;Prior to reading this book and The Phoenix Project, I would have described&#10;DevOps as:&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;Enabling frequent production releases by making developers and sysadmins&#10;play nice together and using some cool new automation tool chains.&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;p&gt;I now have a completely different perspective. In fact, the cooperative culture&#10;and tools have taken a heavy dive in significance for me. They are simply a&#10;means to an end. They were a tailored response to a discrete problem. Truth is,&#10;in my own experience in adopting &amp;ldquo;DevOps&amp;rdquo;, the CI/CD (Continuous&#10;Integration/Continuous Delivery) tools simply became the overpriced, under&#10;performing, state-of- the-art, highly &amp;ldquo;efficient&amp;rdquo; robots in Alex&amp;rsquo;s&#10;manufacturing plant.&lt;/p&gt;&#10;&lt;p&gt;CI/CD tools and DevOps culture were born from a specific problem. That is, that&#10;successful implementations of Agile development methodologies have significantly&#10;increased the production capacity of development teams, typically without&#10;exceeding marketplace demand. Historically, operations teams held the title of&#10;&amp;ldquo;most efficient&amp;rdquo; as software releases were infrequent. Now the tables have&#10;turned and operations has become a bottleneck in businesses achieving their&#10;primary goal; make money first, fast and forever.&lt;/p&gt;&#10;&lt;p&gt;DevOps addresses this problem, using Goldratt&amp;rsquo;s principles in the following&#10;ways:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Production deployment is identified as the bottleneck in achieving the goal&lt;/li&gt;&#10;&lt;li&gt;The bottleneck is &amp;ldquo;exploited&amp;rdquo; in that more frequent releases keep operations&#10;functioning at full capacity. Operational activities that don&amp;rsquo;t directly&#10;progress the goal are deprioritized.&lt;/li&gt;&#10;&lt;li&gt;Work is buffered in front of the bottleneck using &amp;ldquo;feature toggles&amp;rdquo; that&#10;operations can enable via configuration when ready&lt;/li&gt;&#10;&lt;li&gt;Testing and quality assurance are moved before the bottleneck using Continuous&#10;Integration and automated testing tools. This elevates the bottleneck by&#10;preventing it from processing defective goods (i.e. buggy software)&lt;/li&gt;&#10;&lt;li&gt;Batch sizes are reduced, increasing the work of the testing, packaging and&#10;release work centers, but increasing flow through the bottleneck (smaller&#10;changes, lower risk, easier planning, etc.)&lt;/li&gt;&#10;&lt;li&gt;Automation is used to elevate the operations bottleneck. E.g. one touch&#10;deployments, infrastructure as code, containers, etc.&lt;/li&gt;&#10;&lt;li&gt;As bottlenecks are defeated (i.e. ops can keep up with dev), other bottlenecks&#10;(prioritized according to the goal) are identified and attacked such as&#10;operational outages, technical debt, infrastructure projects, compliance&#10;problems, etc.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="application-in-it-operations"&gt;Application in IT Operations&lt;/h2&gt;&#10;&lt;p&gt;Deploying applications into production is actually a very small part of what my&#10;organization does. Our focus is more in service desk, process management and&#10;infrastructure services.&lt;/p&gt;&#10;&lt;p&gt;Essentially, CI/CD is of very little (if any) value to us in achieving our&#10;goals. For this reason, often my colleagues express that DevOps is also of very&#10;little use to us. Eliyahu&amp;rsquo;s parable illustrates that the Theory of Constraints,&#10;a core component of DevOps thinking, is actually more relevant than ever; it&amp;rsquo;s&#10;just that the application will look different to CI/CD.&lt;/p&gt;&#10;&lt;p&gt;Our goal, hypothetically, is to make money. We do this by increasing throughput&#10;(projects delivered, requests fulfilled, etc.) while minimizing operational&#10;expenses and inventory (incomplete works).&lt;/p&gt;&#10;&lt;p&gt;Our bottleneck is not the frequent deployment of new software features. What we&#10;need to determine, is where the primary bottleneck is in achieving our goal.&#10;Hypothetically, it could be:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Service outages&lt;/li&gt;&#10;&lt;li&gt;Change and release management&lt;/li&gt;&#10;&lt;li&gt;Skills shortages&lt;/li&gt;&#10;&lt;li&gt;Quality assurance&lt;/li&gt;&#10;&lt;li&gt;Inter-team politics&lt;/li&gt;&#10;&lt;li&gt;Project delivery&lt;/li&gt;&#10;&lt;li&gt;Etc.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Improving any one of these in isolation could actually serve to undermine our&#10;goal by increasing inventory or operational expenses, without increasing&#10;throughput.&lt;/p&gt;&#10;&lt;p&gt;The next step, the challenge to you and hopefully the subject of a future&#10;article, is the application of Goldratt&amp;rsquo;s process of ongoing improvement to&#10;more diverse IT Operations organizations and overcoming the challenges of&#10;organizational change.&lt;/p&gt;&#10;</description><pubDate>Sat, 12 Dec 2015 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/the-goal.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/the-goal.html</guid></item><item><title>dmidecode for Apple OS X</title><description>&lt;p&gt;&lt;a href="https://github.com/cavaliercoder/dmidecode-osx"&gt;Dmidecode&lt;/a&gt; is a long standing,&#10;effective tool for reading manufacturer info from the SMBIOS tables present on&#10;most modern x86 based systems. It&amp;rsquo;s been available for many years on most Unix&#10;like operating systems and has also been ported to&#10;&lt;a href="http://gnuwin32.sourceforge.net/packages/dmidecode.htm"&gt;Windows&lt;/a&gt;. Until now, it&#10;had never made the leap to OS X.&lt;/p&gt;&#10;&lt;p&gt;From the &lt;a href="http://www.nongnu.org/dmidecode/"&gt;dmidecode website&lt;/a&gt;:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Dmidecode reports information about your system&amp;rsquo;s hardware as described in&#10;your system BIOS according to the SMBIOS/DMI standard. This information&#10;typically includes system manufacturer, model name, serial number, BIOS&#10;version, asset tag as well as a lot of other details of varying level of&#10;interest and reliability depending on the manufacturer. This will often&#10;include usage status for the CPU sockets, expansion slots (e.g. AGP, PCI, ISA)&#10;and memory module slots, and the list of I/O ports (e.g. serial, parallel,&#10;USB).&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="installation-on-os-x"&gt;Installation on OS X&lt;/h2&gt;&#10;&lt;p&gt;The easiest way to install &lt;code&gt;dmidecode&lt;/code&gt; is with the &lt;a href="http://brew.sh/"&gt;homebrew&lt;/a&gt;&#10;package manager:&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;$ brew install cavaliercoder/dmidecode/dmidecode&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;p&gt;To build from source, sources are available from &lt;a href="https://github.com/cavaliercoder/dmidecode-osx/releases"&gt;the project page on&#10;GitHub&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Once cloned or extracted from the release tarball, you may build and install&#10;with:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ make &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make install&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above steps install the &lt;code&gt;dmidecode&lt;/code&gt; binary into &lt;code&gt;/usr/local/sbin&lt;/code&gt;. If this&#10;is not in your &lt;code&gt;$PATH&lt;/code&gt;, you&amp;rsquo;ll need to add it or call the full file path:&lt;/p&gt;&#10;&lt;pre&gt;&lt;code&gt;$ /usr/local/sbin/dmidecode&#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;h2 id="getting-help"&gt;Getting help&lt;/h2&gt;&#10;&lt;p&gt;For all issues related to &lt;code&gt;dmidecode&lt;/code&gt; itself, please see the&#10;&lt;a href="http://www.nongnu.org/dmidecode/"&gt;dmidecode website&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;This port is thoroughly tested on x86_64 iMacs and MacBook Pros running 10.9+.&#10;All API calls have documented compatability since OS X 10.0 but without the&#10;opportunity to test on older machines, I&amp;rsquo;ll be relying on the community for&#10;feedback.&lt;/p&gt;&#10;&lt;p&gt;If you encounter any issues, please raise an issue on the &lt;a href="https://github.com/cavaliercoder/dmidecode-osx/issues"&gt;GitHub page&lt;/a&gt; and include your Apple&#10;device details and OS X version. Please first check for existing issues which&#10;may match your problem.&lt;/p&gt;&#10;&lt;h2 id="the-portability-challenge"&gt;The portability challenge&lt;/h2&gt;&#10;&lt;p&gt;Dmidecode ports very nicely onto any Unix-like platform that exposes raw system&#10;memory (specifically the range where SMBIOS lives) as a file handle (such as&#10;&lt;code&gt;/dev/mem&lt;/code&gt;). Unfortunately, a few years back, Apple disabled its memory file&#10;handle by default. Now the only way to access SMBIOS memory directly (on an&#10;out-of-box OS X machine) is to use Apple&amp;rsquo;s proprietary IOService API.&lt;/p&gt;&#10;&lt;p&gt;This port of dmidecode aims to minimize deviation from the upstream codebase to&#10;ensure that ongoing maintenance is as simple as possible. The challenge has&#10;been achieving this without butchering the code with a bunch of&#10;&lt;code&gt;if apple... else...&lt;/code&gt; blocks but cleanly navigating around code that assumes&#10;SMBIOS data is accessible from a file path.&lt;/p&gt;&#10;&lt;h2 id="sample-output"&gt;Sample output&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s some sample dmidecode output from my MacBook Pro 2010 running&#10;OS X 10.9.5; modified for privacy and brevity:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# dmidecode 3.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Getting SMBIOS data from Apple SMBIOS service.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SMBIOS 2.4 present.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;61 structures occupying 2888 bytes.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Handle 0x001C, DMI type 0, 24 bytes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BIOS Information&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Vendor: Apple Inc.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Version: MBP61.88Z.0057.B0F.1112091028&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Release Date: 12/09/11&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;ROM Size: 4096 kB&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Characteristics:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;PCI is supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;BIOS is upgradeable&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;BIOS shadowing is allowed&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;Boot from CD is supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;Selectable boot is supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;ACPI is supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;IEEE 1394 boot is supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;Smart battery is supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;Function key-initiated network boot is supported&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;BIOS Revision: 0.1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Handle 0x001D, DMI type 1, 27 bytes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;System Information&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Manufacturer: Apple Inc.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Product Name: MacBookPro6,2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Version: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Serial Number: W80223VBDAW&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;UUID: 677C7349-0762-4254-A9A5-6646B1BD4083&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Wake-up Type: Power Switch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;SKU Number: System SKU#&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Family: MacBook Pro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Handle 0x001E, DMI type 2, 16 bytes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Base Board Information&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Manufacturer: Apple Inc.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Product Name: Mac-F22586C8&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Version: MacBookPro6,2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Serial Number: W8022732FVUPA&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Asset Tag: Base Board Asset Tag#&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Features:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;Board is a hosting board&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;Board is replaceable&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Location In Chassis: Part Component&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Chassis Handle: 0x001F&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Type: Motherboard&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Contained Object Handles: 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><pubDate>Tue, 20 Oct 2015 00:00:00 +0000</pubDate><link>https://hotsrc.dev/blog/dmidecode-for-apple-osx.html</link><guid isPermaLink="true">https://hotsrc.dev/blog/dmidecode-for-apple-osx.html</guid></item></channel></rss>