defmodule Mix.Tasks.Ttycast.Bench do
@moduledoc """
Run a small local benchmark for recording size, open latency, and snapshot latency.
mix ttycast.bench --events 1000
"""
use Mix.Task
@shortdoc "Benchmark TTYCast recording and seek performance"
@impl true
def run(argv) do
{opts, _args, invalid} = OptionParser.parse(argv, strict: [events: :integer])
invalid == [] || Mix.raise("usage: mix ttycast.bench [--events N]")
events = Keyword.get(opts, :events, 1_000)
dir = Path.join(System.tmp_dir!(), "ttycast-bench-#{System.unique_integer([:positive])}")
File.mkdir_p!(dir)
ttycast_path = Path.join(dir, "bench.ttycast")
asciinema_path = Path.join(dir, "bench.cast")
asciinema_gz_path = asciinema_path <> ".gz"
{record_us, :ok} = :timer.tc(fn -> write_ttycast(ttycast_path, events) end)
{open_us, cast} = :timer.tc(fn -> TTYCast.open!(ttycast_path) end)
midpoint_ms = div(cast.index.duration_us, 2_000)
{mid_seek_us, _mid_snapshot} =
:timer.tc(fn -> TTYCast.snapshot!(cast, time_ms: midpoint_ms, format: :plain) end)
{snapshot_us, snapshot} = :timer.tc(fn -> TTYCast.snapshot!(cast, format: :plain) end)
:ok = TTYCast.export(cast, :asciinema, asciinema_path)
File.write!(asciinema_gz_path, :zlib.gzip(File.read!(asciinema_path)))
report = %{
events: events,
terminal_contains_last?: String.contains?(snapshot, "line #{events}"),
ttycast_bytes: file_size(ttycast_path),
asciinema_bytes: file_size(asciinema_path),
asciinema_gzip_bytes: file_size(asciinema_gz_path),
record_ms: div(record_us, 1_000),
open_us: open_us,
mid_seek_us: mid_seek_us,
snapshot_us: snapshot_us,
path: ttycast_path
}
Mix.shell().info(inspect(report, pretty: true))
end
defp write_ttycast(path, events) do
{:ok, writer} = TTYCast.start_writer(path: path, width: 80, height: 24, chunk_bytes: 64_000)
Enum.each(1..events, fn index ->
TTYCast.Writer.write(writer, "line #{index}\r\n")
end)
TTYCast.Writer.close(writer)
end
defp file_size(path), do: path |> File.stat!() |> Map.fetch!(:size)
end