Releases: Timdebruijn/smokeng
Release list
smokeng v0.20.0
What's Changed
- Give each instance its own group, so its settings stay its own by @Timdebruijn in #15
- Let the role grant the receive buffer the prober asks for by @Timdebruijn in #16
- Dependency round, September 2026 by @Timdebruijn in #17
Full Changelog: v0.19.0...v0.20.0
smokeng v0.19.0
v0.19.0 — a SmokePing import arrives as text
SmokePing renders title, menu and remark as HTML, and its configs use that:
entities for punctuation, for emphasis,
for a break. smokeng renders
them as text. The importer copied the markup through, so a migrated tree
displayed "—" where a dash belonged and "" around a word — sixteen and
ten of them on a real migration, corrected by hand one title at a time before
anyone worked out what they were.
Tags are removed before entities are decoded, so a config that escaped a
literal "" keeps it as text rather than losing it on a second pass. A bare
"<" is left alone: a tag has to start with a letter, and "latency < 5ms" is
ordinary prose in a remark.
The continuation rule was wrong in a way that could join lines that had nothing
to do with each other. SmokePing continues a value when it ends with a
backslash — its own parser reads while (/\\$/) { s/\\$//; ... $_ .= ' ' . $n }
— and this importer keyed on indentation instead, which both missed genuine
continuations and glued unrelated indented lines onto the value above. It also
kept the backslash, so every wrapped sentence carried a stray one.
Two narrower cases of the same thing came out of review. A continuation takes
the very next line, whatever it is: SmokePing reads that one straight from the
file handle, so a blank line ends the value rather than being stepped over, and
a dangling backslash no longer swallows the key that follows it. And a comment
is removed before anything decides whether a line continues, so a backslash
inside one does not continue the value.
None of this reaches a tree that has already been imported; that has to be
repaired where it stands, with config export, an edit, and config import.
Also: the Ansible role restarts the prober when the binary changes. It notified
only the master, so replacing the file left the prober running the release it
started with — four behind, on one host.
smokeng v0.18.0
v0.18.0 — probes that were never sent are no longer loss
An irtt target reported 19 of 20 about a quarter of the time, with a
send-failure flag and five percent loss. Nothing was lost and nothing failed:
the twentieth packet was never scheduled.
irtt's sender does not count packets. It runs until a duration elapses and
keeps to an interval grid, so a packet sent past the halfway point of its slot
makes the client aim at the slot after next — costing a whole interval. The
margin was half a step, so one late send anywhere in the train ended the
session a packet short.
The window is now a full interval past the last slot, clamped to what the
bucket has left. Both halves matter: unclamped, spread mode asks for 315
seconds of a 300-second interval and every session is cancelled, which reads as
total loss for ever.
But the window cannot be the fix, because no duration yields exactly the
requested count both with and without a skipped slot. So a session that ends
early is no longer treated as a session that broke. The tail of a broken
session was owed and never went out: it counts, and says why — for a receive
failure as well as a send failure, since either stops the client. The tail of a
short session was never owed, and is left uncounted, so the interval reads 19
of 19: a narrower distribution than was configured, which the sent count
records and the graph shows, and no loss, because none occurred.
Reporting loss that did not happen is the same defect as reporting a value that
was not measured, on the one probe type where loss is the whole point.
SmokePing does not solve this. Its IRTT probe uses pings*interval, which is the
same arithmetic and fails the same way — and its graphs for the same target
show sixteen-of-twenty losses at moments this prober measures twenty of twenty
on the same host, port and second. Its three IRTT targets fail identically to
the packet, which is its own contention rather than the path, and it pads the
jitter metrics with an invented median so the loss count comes out right.
Three tests on this release passed with the code they guarded removed. Each was
found by mutating the code rather than by reading it, which is the only reason
to run a test suite.
smokeng v0.17.0
v0.17.0 — a review of v0.16.0, and what it found
Seven changes, all from reviewing the release before it. Two of them are the
kind of defect this project exists to prevent, and one is a monitor that
quietly stops monitoring.
Two irtt targets probing at once could wedge one of them indefinitely. Every
irtt client shared one package-level timer and averager whose fields are
written without synchronisation, and each target is probed on its own
goroutine. One session completes, the other never returns, and the probe's own
deadline does not free it because what is stuck is not waiting on that context.
The target stops delivering measurements with nothing in the log. Two irtt
targets is what a SmokePing import produces, since its IRTT probe graphs one
figure per target.
server_processing recorded fabricated zeros against a server started with
--tstamp=single. At a midpoint stamp the two server timestamps hold the same
value, so the difference between them is not unavailable but exactly zero — a
distribution of zeros kept forever under a heading that says how long the far
end held each packet. The same stamp also leaks half the server's hold time
into both jitter figures, so a loaded peer graphs its own scheduling as network
jitter; measured, not argued. Every extra series now requires a stamp at both
ends, and a wall-clock fallback silences them too, because inter-packet delay
variation only cancels the clock offset while it is a monotonic difference.
A signed agent could exhaust the master with a sub-megabyte request: Arrow IPC
allocates whatever uncompressed size a message header claims, with no ceiling.
629 KB grew the heap by 5.6 GiB. Unvalidated list offsets were used as a slice
capacity, so 408 bytes reserved 8 GiB. Both are bounded now, and an unsorted
distribution is normalised rather than failing a write that would have wedged
that agent's outbox forever.
Measurements record why a send failed, not only that it did. An irtt target
losing a probe an interval could not say whether the far end refused the
traffic or this prober never got it out — a network fault and a bug here,
stored identically, which cost three wrong theories and an hour of a
production SmokePing.
The rest: a corrupt optional series no longer takes the whole window with it,
the outbox query no longer scans the table (or overruns SQLite's expression
depth, which the first fix did), and the crosshair reads the interval it is
over rather than the nearest one — wrong for half of every bucket.
Schema v21, additive. Both wire formats gained optional columns, so master and
agents may be upgraded in either order.
The tests are half of this release. Mutating the code found nine checks that
could be deleted with the suite still green, including the orphan invariant the
storage design rests on and the version byte of the samples blob. go test -race
did not work for the probe package at all.
Every finding here came from review, and most were not in what the code did but
in what its comments claimed it did.
smokeng v0.16.0
v0.16.0 — the directional measurements irtt already makes
An irtt session measures more per packet than the round trip, and smokeng read
all of it off the wire and kept only the round trip. A SmokePing install that
graphed send and receive jitter therefore came across the migration with one
graph where it had three.
Skipping the other two on import was right: SmokePing graphs one figure per
target, so the same host appears three times, and as three smokeng targets they
opened three sessions to the same server and collided. But the migration guide
said nothing was lost, and that was not true — the measurements were being made
and dropped. All three are now kept from the one session and drawn under the
round trip on the same time cursor.
What is kept is inter-packet delay variation, not the absolute one-way delay
sitting beside it in the same result. One-way delay subtracts one machine's wall
clock from another's and is wrong by however far apart they have drifted, so it
measures NTP as much as the network. IPDV compares consecutive packets on the
monotonic clock, so the offset cancels and the number holds without the two ends
being synchronised.
graph_series chooses what is drawn, per target and inherited: "all" (the
default), a list, or "" for none. Display only — everything measured is stored
whatever it says, so switching a graph on later shows the history it already has
rather than starting it from that moment. It is writable from TOML as well as
the API, unlike the shape rules in v0.14.2: a setting the file format does not
know gets reverted by the next declarative import.
Two fixes travel with it. The submission decoder matched on column count, so
these three columns would have made an older agent's whole batch fail — every
measurement lost to gain three optional distributions, and a master upgrade
would have been a flag day for every remote agent. It resolves columns by name
now. And the linear density clipped only at the top; on a signed scale it clips
at both ends, so an excursion below zero is left out of the density rather than
pinned to the axis edge where it would read as a plateau.
Schema migrations v19 and v20, both additive.
smokeng v0.15.0
v0.15.0 — the client secret leaves the command line, and grants can be made to matter
Three things that were only visible from a deployment, not from the code.
The OIDC client secret was passed as --oidc-client-secret. A command line is
world-readable in /proc, so that hands the secret to every local user on the
host, and the systemd unit carrying it is mode 0644, which hands it out again.
It worked perfectly the whole time, which is the worst property a disclosure
can have. --oidc-client-secret-file reads it from a file instead, and warns
when that file is readable by anyone but its owner — a secret moved out of the
command line into a 0644 file has gained nothing, and nothing would have said
so. The two flags are mutually exclusive rather than one silently winning. The
irtt keyfile goes through the same reader and gains the same warning.
If you have ever passed --oidc-client-secret, treat it as disclosed and rotate
it. Moving it to a file afterwards does not un-publish it.
The Ansible role could not set --default-role at all, so every deployment ran
the viewer default: every authenticated user reads the whole tree, and a grant
can only add to that. You could write grants, see them listed, and have them
restrict nothing, with no way to change it short of editing the unit by hand.
smokeng_default_role fixes that, and the docs now state the corollary where it
is read rather than leaving it as a footnote — while default-role is viewer, a
grant is inert, and an installation in that state looks exactly like one that
is working.
A grant's role could be changed all along: the store upserts on (group,
target), so re-adding re-roles. But the grant list offered only Remove, so the
discoverable way to demote an editor was to delete the grant and rebuild it
from memory, through a state where the group has no access and with the path to
retype correctly. The role is now editable on the row. Group and path stay
fixed: they are what identifies a grant, so changing either is a different
grant, not an edit.
smokeng v0.14.3
Full Changelog: v0.14.2...v0.14.3
smokeng v0.14.2
Full Changelog: v0.14.1...v0.14.2
smokeng v0.14.1
Full Changelog: v0.14.0...v0.14.1
smokeng v0.14.0
Full Changelog: v0.13.0...v0.14.0