case studies

jvq Flags an Update Check That Made a Go Command-Line Tool Nearly Four Times Bigger

Some tools are judged by their size. A small binary that gets copied onto every build agent, baked into container images, or downloaded at the start of each CI job pays for every megabyte many times a day. Nobody looks at the size in review, because the diff doesn’t show it.

jvq can gate the size of files your build makes, next to the timings. This worked example is a small Go command-line tool, lvl, that counts log levels in JSON Lines files. Two real commits were run through jvq 0.5.0 on a two-core cloud VM.

Two jvq runs. The update check makes the binary grow from 2.284 MB to 8.727 MB, +282%, and the command 20% slower; both fail. A -version flag instead adds 0.2% and passes.

Both runs compare the branch with main. Progress lines left out.

The Setup

build   = "go build -o lvl ."
command = "./lvl testdata/sample.jsonl"
adapter = "time"
size    = ["lvl"]   # ships to every build agent; 1% budget by default

jvq builds each side once, measures the size of lvl, then times the whole command on a 20,000-line sample, taking turns between base and head. Size has a 1% budget by default and time 5%.

The Change: Tell Users About New Releases

The pull request adds lvl -check-update. It asks the GitHub API for the latest release and prints a note when it isn’t the version you’re running. About 25 lines, using net/http and encoding/json from the standard library. No new dependencies, nothing in go.mod.

jvq measured the binary at 2.284 MB before and 8.727 MB after, +282.0% against a 1% budget. Importing net/http brings in the HTTP client, TLS, certificate handling and the crypto behind them, and Go links all of it into the binary whether or not anyone ever passes -check-update.

Time failed too, which we didn’t expect. The command went from 11.6 ms to 13.5 ms, +20.2%, interval +7.5% to +37.2%. We didn’t measure why. The likely cause is start-up: the bigger binary has more packages to initialize, and in an 11 ms run start-up is a large share. jvq’s reference says the time adapter suits commands of a tenth of a second or more, and this one is well under, so take the time result as a hint. jvq needed 24 rounds per side to settle it. The size result is the solid one.

The Alternative

The team still wanted users to find new releases. The second commit drops the network call and adds lvl -version, which prints the version and a link to the releases page. jvq measured 2.288 MB, +0.2%, and time -4.3% with an interval of -15.0% to +4.6%. Both pass.

A team could just as well decide the update check is worth 6 MB. Then the honest move is to raise the size budget in the same pull request, where reviewers can see it:

[budget]
size = "300%"

The point is that the size stops changing by accident.

Size Budgets in Practice

size takes file names and patterns, such as ["target/release/app", "dist/*.js"], so the same gate works for Rust binaries and JavaScript bundles. A percentage suits big files. For small ones, where 1% is a few bytes, give an amount in bytes instead. The reference has the rules.