diff --git a/lib/trinity/scheduler.ex b/lib/trinity/scheduler.ex index dffec81..f6cfc05 100644 --- a/lib/trinity/scheduler.ex +++ b/lib/trinity/scheduler.ex @@ -15,6 +15,9 @@ defmodule Trinity.Scheduler do file_paths: :ets.table, file_data: :ets.table, + log: :ets.table, + log_atomic: :atomics.atomics_ref, + now: :atomics.atomics_ref, supervisor_pid: pid, } @@ -30,6 +33,9 @@ defmodule Trinity.Scheduler do :file_paths, :file_data, + :log, + :log_atomic, + :now, :supervisor_pid, ] diff --git a/lib/trinity/scheduler/simulation_supervisor.ex b/lib/trinity/scheduler/simulation_supervisor.ex index f36f39a..ebb0f1c 100644 --- a/lib/trinity/scheduler/simulation_supervisor.ex +++ b/lib/trinity/scheduler/simulation_supervisor.ex @@ -52,6 +52,9 @@ defmodule Trinity.Scheduler.SimulationSupervisor do file_paths: :ets.new(__MODULE__, [:ordered_set, :public]), file_data: :ets.new(__MODULE__, [:ordered_set, :public]), + log: :ets.new(__MODULE__, [:ordered_set, :public]), + log_atomic: :atomics.new(2, signed: false), + now: :atomics.new(1, signed: false), supervisor_pid: self(), } @@ -86,7 +89,6 @@ defmodule Trinity.Scheduler.SimulationSupervisor do sim: sim, root_pid: root_pid, parent_pid: parent_pid, - parent_ref: parent_ref, } = state case from do @@ -96,7 +98,7 @@ defmodule Trinity.Scheduler.SimulationSupervisor do ^root_pid -> # The root has died, end the simulation - send parent_pid, {parent_ref, reason} + end_simulation(state, reason) {:noreply, state} _ -> @@ -110,7 +112,7 @@ defmodule Trinity.Scheduler.SimulationSupervisor do # If the root was killed, end the simulation case pid == root_pid do - true -> send parent_pid, {parent_ref, reason} + true -> end_simulation(state, reason) _ -> :noop end end) @@ -119,6 +121,38 @@ defmodule Trinity.Scheduler.SimulationSupervisor do end end + defp end_simulation(%State{} = state, reason) do + %{sim: sim, parent_pid: parent_pid, parent_ref: parent_ref} = state + + %Simulation{log: log, log_atomic: log_atomic} = sim + hash = :atomics.get(log_atomic, 1) + + require Logger + Logger.info """ + Simulation complete + Hash: #{print_hash(hash)} + Log: + + #{print_log(log)} + """ + + send parent_pid, {parent_ref, reason} + end + + defp print_hash(hash) do + :crypto.hash(:sha256, <>) + |> Base.encode32() + |> String.slice(0, 8) + end + + defp print_log(log) do + :ets.tab2list(log) + |> Enum.reduce("", fn {i, msg}, acc -> + i_pad = String.pad_leading(Integer.to_string(i), 2) + acc <> i_pad <> ": " <> msg <> "\n" + end) + end + defp spawn_sim_child(%Simulation{} = sim, fun) do spawn_link(fn -> Process.put(simulation_key(), sim) diff --git a/lib/trinity/sim_file.ex b/lib/trinity/sim_file.ex index 3efc48d..bd76be2 100644 --- a/lib/trinity/sim_file.ex +++ b/lib/trinity/sim_file.ex @@ -231,7 +231,6 @@ defmodule Trinity.SimFile do defp put_path(file_paths, node, path, value) when value == :directory or is_integer(value) do - dbg {{node, path}, value} :ets.insert(file_paths, {{node, path}, value}) end end diff --git a/lib/trinity/sim_logger.ex b/lib/trinity/sim_logger.ex new file mode 100644 index 0000000..596562e --- /dev/null +++ b/lib/trinity/sim_logger.ex @@ -0,0 +1,50 @@ +defmodule Trinity.SimLogger do + import Trinity.Scheduler, only: [simulation_key: 0] + + @spec get_sim :: Simulation.t | nil + defp get_sim, do: Process.get(simulation_key()) + + defmacro debug(message) do + key = simulation_key() + + {:<<>>, _, elements} = message + values = + Enum.reduce(elements, [], fn + {:"::", _, [{{:., _, [Kernel, :to_string]}, _, [value]}, {:binary, _, _}]}, acc -> + [value | acc] + _, acc -> + acc + end) + |> Enum.reverse() + + %{module: module, line: line} = __CALLER__ + values_tuple = {:{}, [], [module, line | values]} + + quote do + require Logger + case Process.get(unquote(key)) do + nil -> Logger.debug(unquote(message)) + _sim -> Trinity.SimLogger.sim_hash_and_log(unquote(values_tuple), unquote(message)) + end + end + end + + @doc false + def sim_hash_and_log(values_tuple, message) do + %{log: log, log_atomic: log_atomic} = get_sim() + + sim_hash_values(log_atomic, values_tuple) + sim_log_message(log, log_atomic, message) + end + + defp sim_hash_values(log_atomic, values_tuple) do + prev_hash = :atomics.get(log_atomic, 1) + new_hash = :erlang.phash2([prev_hash | values_tuple]) + :atomics.put(log_atomic, 1, new_hash) + end + + defp sim_log_message(log, log_atomic, message) do + i = :atomics.add_get(log_atomic, 2, 1) + :ets.insert(log, {i, message}) + end +end diff --git a/test/trinity_test.exs b/test/trinity_test.exs index 9a7c1a4..2c9a0e5 100644 --- a/test/trinity_test.exs +++ b/test/trinity_test.exs @@ -1,8 +1,9 @@ defmodule TrinityTest do use ExUnit.Case - alias Trinity.{SimProcess, Scheduler} + alias Trinity.{SimProcess, SimLogger, Scheduler} import Trinity.Scheduler, only: [receive_yield: 1] + require SimLogger defmodule Counter do use GenServer @@ -20,6 +21,8 @@ defmodule TrinityTest do :ok = SimFile.mkdir_p("/counters/#{id}/") {:ok, fd} = SimFile.open("/counters/#{id}/#{id}.count", [:read, :write]) + SimLogger.debug "Init (id=#{fd}, fd=#{fd}, initial_count=#{initial_count})" + state = %{ fd: fd, size: nil, @@ -62,7 +65,7 @@ defmodule TrinityTest do SimProcess.register(self(), name) receive_yield do - :begin -> dbg {"began", name} + :begin -> :noop end pids = Enum.map(1..10, fn i -> @@ -81,7 +84,7 @@ defmodule TrinityTest do end) |> Enum.concat() - dbg pids + #dbg pids Enum.each(pids, fn {pid, id} -> result = Counter.add(pid, 10) @@ -90,7 +93,7 @@ defmodule TrinityTest do SimProcess.send_after self(), :finish, 1000 receive_yield do - :finish -> dbg "finished" + :finish -> :noop end Scheduler.yield(1000)