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