zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

execution_timing.ex (4431B)


      1 defmodule Credo.Execution.ExecutionTiming do
      2   @moduledoc """
      3   The `ExecutionTiming` module can help in timing the execution of code parts and
      4   storing those timing inside the `Credo.Execution` struct.
      5   """
      6 
      7   use GenServer
      8 
      9   alias Credo.Execution
     10 
     11   @doc """
     12   Runs the given `fun` and prints the time it took with the given `label`.
     13 
     14       iex> Credo.Execution.ExecutionTiming.inspect("foo", fn -> some_complicated_stuff() end)
     15       foo: 51284
     16 
     17   """
     18   def inspect(label, fun) do
     19     {duration, result} = :timer.tc(fun)
     20 
     21     # credo:disable-for-lines:3 Credo.Check.Warning.IoInspect
     22     duration
     23     |> format_time()
     24     |> IO.inspect(label: label)
     25 
     26     result
     27   end
     28 
     29   @doc """
     30   Returns the current timestamp in the same format (microseconds) as the returned starting times of `run/1`.
     31   """
     32   def now, do: :os.system_time(:microsecond)
     33 
     34   @doc """
     35   Runs the given `fun` and returns a tuple of `{started_at, duration, result}`.
     36 
     37       iex> Credo.Execution.ExecutionTiming.run(fn -> some_complicated_stuff() end)
     38       {1540540119448181, 51284, [:whatever, :fun, :returned]}
     39 
     40   """
     41   def run(fun) do
     42     started_at = now()
     43     {duration, result} = :timer.tc(fun)
     44 
     45     {started_at, duration, result}
     46   end
     47 
     48   @doc "Same as `run/1` but takes `fun` and `args` separately."
     49   def run(fun, args) do
     50     started_at = now()
     51     {duration, result} = :timer.tc(fun, args)
     52 
     53     {started_at, duration, result}
     54   end
     55 
     56   @doc """
     57   Adds a timing to the given `exec` using the given values of `tags`, `started_at` and `duration`.
     58   """
     59   def append(%Execution{timing_pid: pid}, tags, started_at, duration) do
     60     spawn(fn ->
     61       GenServer.call(pid, {:append, tags, started_at, duration})
     62     end)
     63   end
     64 
     65   @doc """
     66   Adds a timing piped from `run/2` to the given `exec` (using the given values of `tags`, `started_at` and `duration`).
     67   """
     68   def append({started_at, duration, _result}, %Execution{timing_pid: pid}, tags) do
     69     spawn(fn ->
     70       GenServer.call(pid, {:append, tags, started_at, duration})
     71     end)
     72   end
     73 
     74   @doc """
     75   Returns all timings for the given `exec`.
     76   """
     77   def all(%Execution{timing_pid: pid}) do
     78     GenServer.call(pid, :all)
     79   end
     80 
     81   @doc """
     82   Groups all timings for the given `exec` and `tag_name`.
     83   """
     84   def grouped_by_tag(exec, tag_name) do
     85     map =
     86       exec
     87       |> all()
     88       |> Enum.filter(fn {tags, _started_at, _time} -> tags[tag_name] end)
     89       |> Enum.group_by(fn {tags, _started_at, _time} -> tags[tag_name] end)
     90 
     91     map
     92     |> Map.keys()
     93     |> Enum.map(fn map_key ->
     94       sum = Enum.reduce(map[map_key], 0, fn {_tags, _, time}, acc -> time + acc end)
     95 
     96       {[{tag_name, map_key}, {:accumulated, true}], nil, sum}
     97     end)
     98   end
     99 
    100   @doc """
    101   Returns all timings for the given `exec` and `tag_name`.
    102   """
    103   def by_tag(exec, tag_name) do
    104     exec
    105     |> all()
    106     |> Enum.filter(fn {tags, _started_at, _time} -> tags[tag_name] end)
    107   end
    108 
    109   @doc """
    110   Returns all timings for the given `exec` and `tag_name` where the tag's value also matches the given `regex`.
    111   """
    112   def by_tag(exec, tag_name, regex) do
    113     exec
    114     |> all()
    115     |> Enum.filter(fn {tags, _started_at, _time} ->
    116       tags[tag_name] && to_string(tags[tag_name]) =~ regex
    117     end)
    118   end
    119 
    120   @doc """
    121   Returns the earliest timestamp for the given `exec`.
    122   """
    123   def started_at(exec) do
    124     {_, started_at, _} =
    125       exec
    126       |> all()
    127       |> List.first()
    128 
    129     started_at
    130   end
    131 
    132   @doc """
    133   Returns the latest timestamp plus its duration for the given `exec`.
    134   """
    135   def ended_at(exec) do
    136     {_, started_at, duration} =
    137       exec
    138       |> all()
    139       |> List.last()
    140 
    141     started_at + duration
    142   end
    143 
    144   defp format_time(time) do
    145     cond do
    146       time > 1_000_000 ->
    147         "#{div(time, 1_000_000)}s"
    148 
    149       time > 1_000 ->
    150         "#{div(time, 1_000)}ms"
    151 
    152       true ->
    153         "#{time}μs"
    154     end
    155   end
    156 
    157   # callbacks
    158 
    159   @doc false
    160   def start_server(exec) do
    161     {:ok, pid} = GenServer.start_link(__MODULE__, [])
    162 
    163     %Execution{exec | timing_pid: pid}
    164   end
    165 
    166   @doc false
    167   def init(_) do
    168     {:ok, []}
    169   end
    170 
    171   @doc false
    172   def handle_call({:append, tags, started_at, time}, _from, current_state) do
    173     new_current_state = [{tags, started_at, time} | current_state]
    174 
    175     {:reply, :ok, new_current_state}
    176   end
    177 
    178   @doc false
    179   def handle_call(:all, _from, current_state) do
    180     list = Enum.sort_by(current_state, fn {_, started_at, _} -> started_at end)
    181 
    182     {:reply, list, current_state}
    183   end
    184 end