defmodule Tim do @readme "README.md" @external_resource @readme @moduledoc_readme @readme |> File.read!() |> String.split("") |> Enum.fetch!(1) |> String.trim() @moduledoc """ #{@moduledoc_readme} """ alias Tim.Stats @type unit :: :microsecond | :millisecond | :second | :minute | :hour @default_opts %{n: 1, unit: :microsecond} @doc """ Takes an Elixir expression and returns a map of timing stats and the evaluated result. `time` has the following optional keyword arguments: * `:n` - number of times the expression is evaluated to build statistics; defaults to 1 * `:unit` - unit of time for the timing statistics; `:microsecond` (default) | `:millisecond` | `:second` | `:minute` | `:hour` """ defmacro time(expr, opts \\ []) do %{n: n, unit: unit} = merge_opts(@default_opts, opts) scale = unit_to_scale(unit) expr_string = Macro.to_string(expr) quote do {times, [result | _]} = Enum.map(1..unquote(n), fn _ -> :timer.tc(fn -> unquote(expr) end) end) |> Enum.unzip() Map.merge( %{expr: unquote(expr_string), n: unquote(n), result: result, unit: unquote(unit)}, Stats.collect(times, unquote(scale)) ) end end @doc """ Takes an Elixir expression and returns the evaluated result while also applying `IO.inspect` to the map of timing stats. `inspect` has the following optional keyword arguments: * `:n` - number of times the expression is evaluated to build statistics (defaults to 1) * `:unit` - unit of time for the timing statistics: `:microsecond` (default) | `:millisecond` | `:second` | `:minute` | `:hour` * `:label` - a string that decorates the inspected output (passed to `IO.inspect`) """ defmacro inspect(expr, opts \\ []) do quote do unquote(expr) |> Tim.time(unquote(opts)) |> then(fn %{result: result} = data -> data |> Map.delete(:result) |> IO.inspect(label: unquote(Keyword.get(opts, :label))) result end) end end defp unit_to_scale(:microsecond), do: 1 defp unit_to_scale(:millisecond), do: 1.0e-3 defp unit_to_scale(:second), do: 1.0e-6 defp unit_to_scale(:minute), do: 1.6667e-8 defp unit_to_scale(:hour), do: 2.7778e-10 defp merge_opts(defaults, opts) do Map.merge(defaults, Enum.into(opts, %{})) end end