Skip to content

Instantly share code, notes, and snippets.

@rrichardsonv
Created August 11, 2020 21:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rrichardsonv/a3f6816bc063f160725d71f096f7180d to your computer and use it in GitHub Desktop.
Save rrichardsonv/a3f6816bc063f160725d71f096f7180d to your computer and use it in GitHub Desktop.
Compilation tracer generating compilation times, number of aliases, imports, requires, and more!
defmodule CompileSpeedTest do
use Mix.Task
@recursive
@ets_table :compile_test
@header_row "FILE,START,IMPORTS,ALIASES,REQUIRES,USES,DURATION\n"
@impl Mix.Task
def run(args \\ []) do
output_dir =
case OptionParser.parse(args) do
{[output: output_dir], _, _} ->
output_dir
{[o: output_dir], _, _} ->
output_dir
_ ->
Path.expand(".")
end
unless File.exists?(output_dir) do
File.mkdir_p!(output_dir)
end
start = utc_meow()
:ets.new(@ets_table, [:named_table, :public])
:ets.insert(@ets_table, {:start_time, utc_meow()})
current_dir = File.cwd!()
if File.dir?(Path.join([current_dir, "apps"])) do
:ets.insert(@ets_table, {:umbrella?, true})
:ets.insert(@ets_table, {:path_prefix, Path.join([current_dir, "apps"])})
else
:ets.insert(@ets_table, {:umbrella?, false})
:ets.insert(@ets_table, {:path_prefix, Path.expand("..")})
:ets.insert(@ets_table, {:output_file, Path.basename(current_dir)})
end
Mix.Task.clear()
Mix.Task.run("compile", ["--force", "--tracer", __MODULE__])
Enum.each(
[
:umbrella?,
:path_prefix,
:output_file,
:start_time
],
fn k -> :ets.delete(@ets_table, k) end
)
@ets_table
|> :ets.select([{{:"$1", :"$2"}, [], [{{:"$1", :"$2"}}]}])
|> Enum.each(fn {app_name, rows} ->
output_dir
|> Path.join(to_string(app_name) <> ".csv")
|> save_log(rows)
end)
end
@spec trace(tuple, Macro.Env.t()) :: :ok
def trace(:start, env) do
:ets.insert(
@ets_table,
{:"#{env.file}", [start: utc_meow(), imps: 0, alis: 0, reqs: 0, uses: 0]}
)
:ok
end
def trace({:import, _meta, _module, _opts}, env) do
update_metrics(env, fn [{_f, kw}] ->
Keyword.update!(kw, :imps, &(&1 + 1))
end)
:ok
end
def trace({:alias, _meta, _alias, _as, _opts}, env) do
update_metrics(env, fn [{_f, kw}] ->
Keyword.update!(kw, :alis, &(&1 + 1))
end)
:ok
end
def trace({:require, _meta, _module, _opts}, env) do
update_metrics(env, fn [{_f, kw}] ->
Keyword.update!(kw, :reqs, &(&1 + 1))
end)
:ok
end
def trace({:remote_macro, _meta, _module, :__using__, _}, env) do
update_metrics(env, fn [{_f, kw}] ->
Keyword.update!(kw, :uses, &(&1 + 1))
end)
:ok
end
def trace(:stop, env) do
prefix = get!(:path_prefix)
umbrella? = get!(:umbrella?)
app_name =
if umbrella? do
File.cwd!()
|> Path.relative_to(prefix)
|> Path.split()
|> List.first()
else
get!(:output_file)
end
:"#{env.file}"
|> get!()
|> put_row(app_name, Path.relative_to(env.file, prefix))
_ = :ets.delete(@ets_table, :"#{env.file}")
:ok
end
def trace(_, _), do: :ok
defp get!(k) do
case :ets.lookup(@ets_table, k) do
[] ->
raise "No #{k}???"
[{_, v}] ->
v
end
end
defp put_row(kw, app_name, file_name) do
row = Map.new(kw)
duration = (utc_meow() - row[:start]) / 1000
from_start = (row[:start] - get!(:start_time)) / 1000
%{
start: s,
imps: i,
alis: a,
reqs: r,
uses: u,
dur: d
} =
row
|> Map.put(:start, from_start)
|> Map.put(:dur, duration)
row =
[file_name, s, i, a, r, u, d]
|> Enum.map_join(",", &to_string/1)
|> Kernel.<>("\n")
case :ets.lookup(@ets_table, :"#{app_name}") do
[] ->
:ets.insert(@ets_table, {:"#{app_name}", [row]})
[{_, rows}] ->
:ets.insert(@ets_table, {:"#{app_name}", [row | rows]})
end
end
defp update_metrics(env, updator_fn) when is_map(env),
do: update_metrics(:"#{env.file}", updator_fn)
defp update_metrics(key, updator_fn) when is_atom(key) do
case :ets.lookup(@ets_table, key) do
[] ->
IO.warn("#{inspect(key)} tried to update without reference????", [])
[_ | _] = metrics ->
:ets.insert(@ets_table, {key, updator_fn.(metrics)})
end
end
defp utc_meow,
do: DateTime.utc_now() |> DateTime.to_unix(:millisecond)
defp save_log(path, data) do
{:ok, file} = File.open(path, [:append, {:delayed_write, 100, 20}])
[@header_row | List.wrap(data)]
|> Enum.each(&IO.binwrite(file, &1))
File.close(file)
end
end
CompileSpeedTest.run(System.argv())
@rrichardsonv
Copy link
Author

To use

mkdir -p script
curl https://gist.githubusercontent.com/rrichardsonv/a3f6816bc063f160725d71f096f7180d/raw/e88c43d13c023e5a07d296a3c38b23b3fe320156/compile_speed_test.exs > script/compile_speed_test.exs
mix run compile_speed_test.exs --output speed_test_results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment