Cure v0.30.0 :: John
by Aleksei Matiushkin
v0.29.0 was the release where documentation caught up with the
language it describes. cure doc grew a proper two-pane site, every
stdlib module learnt to talk about itself, the REPL's :help stopped
butchering its own Markdown, and the parser stopped choking on doc
comments separated by blank lines. With the reader experience in
order, v0.30.0 turns to the operator.
The headline feature is tiny on the surface and a little obsessive
underneath. It is called john, it prints everything, and it is
named in tribute to John Carbajal, one of the most intelligent
and friendly persons I was honored to work with.
Why John
John is a former colleague of mine. He had one talent that I spent a long time trying to acquire: on a screen full of numbers, he would put his finger on the one number that mattered, usually while the rest of us were still trying to get a header to stop wrapping.
You did not have to explain the dashboard to him. You had to give him the dashboard. He would then tell you what the dashboard was about.
Over the years I came to an uncomfortable conclusion. The reason this
felt so hard to replicate was that most of our dashboards did not show
enough. We curated them. We hid the uncomfortable numbers. We left
Z3 out, left the scheduler count out, left the log tail out. We did
not trust each other with the full picture, so we served half-pictures
back and forth.
The john command is the admission that we were wrong. It shows
everything it can -- version, scheduler topology, memory breakdown,
process and atom counts, dependency versions, project manifest,
lockfile, runtime actor / supervisor / FSM counts, doctor summary,
the last few log lines -- and it trusts the person in front of the
terminal to ignore the rest. That is what John did, and that is what
john is.
Three names, one answer
You can ask for the report anywhere you have Cure at hand:
$ mix cure.john
$ cure john
cure(1)> :john
All three call sites funnel into a single collector and a single
renderer. They accept the same switches: --width, --ansi /
--no-ansi, --banner / --no-banner, --root PATH. The REPL form
inherits the session's theme and colour state, so :john looks like
the rest of your session, not like an escaped visitor from another
one.
What is in the report
The report is divided into deliberately unapologetic sections:
Cure. Version, escript entry point, whether :cure is loaded and
started, the count of Cure.Std.* modules that are currently in
memory, the pipeline event-bus size, the protocol registry size, and
a UTC timestamp.
BEAM / OTP. Elixir / OTP / ERTS versions; the full system banner with the jit / smp / ds flags left in; scheduler topology split into online, total, dirty CPU, and dirty I/O; logical processor count; process, port, and atom counts each paired with their limit; ETS table count; a seven-way memory breakdown (total, processes, binary, code, ETS, atom, system); reductions; runtime; wall-clock uptime; and internal / external wordsize.
System. OS family and version, architecture, hostname, user,
home, cwd, and selected environment variables (LANG, TERM,
SHELL, PATH entry count).
Tooling. Resolved paths of z3, git, make, and cc, plus
loaded versions of every declared dependency. If you have ever sent
somebody into a debugging session only to learn three minutes later
that they did not actually have Z3 installed, this section is for
you.
Project. When Cure.toml is present in the chosen root: name,
version, source paths, lockfile status, and the full dependency
table. When it is absent, the section just says so; there is no
tantrum.
Runtime. A condensed Cure.Observe.Top snapshot. If you are
running an actor system, cure top already gives you the full view;
john gives you the counters and the first five supervisors at a
glance.
Doctor. The severity counters from Cure.Doctor.run/1. The full
report still lives behind cure doctor; this section just tells you
how loud it would be if you ran it.
Recent logs. Tails of up to five log files under
.cure/logs/*.log, _build/cure/logs/*, or an erl_crash.dump at
the project root, sorted by mtime. If something went sideways two
minutes ago, the evidence is right there.
CommonMark all the way down
Cure.John.render/2 produces CommonMark. Cure.John.run/1 then
pushes it through one of two renderers depending on what the runtime
can actually load:
defp render_markdown(md, opts) do
case Keyword.get(opts, :ansi, :auto) do
false ->
md
_ ->
try_marcli(md, opts) || Markdown.render(md, fallback_theme(opts))
end
end
Marcli.render/2 gives us the rich Markdown-to-ANSI pipeline we
already use for cure doc. It depends on MDEx, a Rust NIF, which
cannot be loaded from inside the bundled cure escript archive. Every
call site that might run from the escript therefore needs to fall
back, so run/1 automatically drops to Cure.REPL.Markdown.render/2
-- the pure-Elixir block-aware renderer introduced in v0.29.0 -- when
Marcli cannot load. The output looks slightly less polished in the
fallback path, but it stays readable, and it keeps cure john
identical to mix cure.john in terms of information, which is the
point.
Passing --no-ansi short-circuits both renderers and emits the raw
Markdown. That is what you want to feed into CI logs, jq, or any
parser that prefers a document to a screenshot.
A map that is safe to serialise
Cure.John.collect/1 is the boring part of the feature and therefore
the part I am most fond of. It returns a plain Elixir map: no PIDs, no
references, no anonymous functions. Every value in the tree is either
a primitive, a list of primitives, or a nested map of the same. That
lets tests assert on it, lets you drop it into a structured logger,
and lets you serialise it to JSON if the operational context calls
for it.
snapshot = Cure.John.collect(root: "/path/to/project")
snapshot.vm.schedulers_online
# => 16
snapshot.project.lockfile
# => "missing"
Cure.John.render/2 takes that map and returns a Markdown string.
Cure.John.run/1 does both and writes the result; it also returns
the rendered string for callers that want to keep a copy.
Name tributes
I know that naming a feature after a colleague is mostly a vanity. I am doing it anyway, and I would do it again.
John, if you ever read this: thank you. The terminal knows where it is now.
What's next
The long-horizon items carry over unchanged:
- Monomorphisation. Specialise polymorphic functions whose call sites all use concrete types. Deferred from v0.24.0.
- Profile-guided optimisation. Feed
Cure.Profilerdata into the inliner and pattern-aware SMT encoder so hot paths get specialisation and cold paths stay small. - Time-travel for actors. v0.28.0 added
@record+cure replayfor FSMs; the actor surface still needs journal hooks and a deterministic scheduler. - First-class Cure notebook format (
.cnb). Literate programming in Cure with type-checked code cells. The doc pipeline from v0.29.0 already covers most of what it would need. - WASM target. Compile the Cure compiler via AtomVM so the Playground's type checker and sandbox can run entirely in the browser without a WebSocket round-trip.
The repository lives at
github.com/am-kantox/cure-lang.
The new on-disk reference is
docs/JOHN.md.
The full
CHANGELOG
lists every touch point.