mirror of
https://github.com/ash-project/reactor.git
synced 2024-09-19 12:53:19 +12:00
feat: Add telemetry middleware. (#93)
* improvement: don't incur compile-time dependencies on middleware. * feat: Add a middleware which emits telemetry events about Reactor.
This commit is contained in:
parent
ee72c64af9
commit
37b9eda48e
7 changed files with 744 additions and 1 deletions
|
@ -699,7 +699,7 @@ Call functions before and after a group of steps.
|
||||||
| Name | Type | Default | Docs |
|
| Name | Type | Default | Docs |
|
||||||
|------|------|---------|------|
|
|------|------|---------|------|
|
||||||
| [`before_all`](#reactor-group-before_all){: #reactor-group-before_all .spark-required} | `(any, any, any -> any) \| mfa` | | The before function. See `Reactor.Step.Group` for more information. |
|
| [`before_all`](#reactor-group-before_all){: #reactor-group-before_all .spark-required} | `(any, any, any -> any) \| mfa` | | The before function. See `Reactor.Step.Group` for more information. |
|
||||||
| [`after_all`](#reactor-group-after_all){: #reactor-group-after_all .spark-required} | `(any, any, any -> any) \| mfa` | | The after function. See `Reactor.Step.Group` for more information. |
|
| [`after_all`](#reactor-group-after_all){: #reactor-group-after_all .spark-required} | `(any -> any) \| mfa` | | The after function. See `Reactor.Step.Group` for more information. |
|
||||||
| [`allow_async?`](#reactor-group-allow_async?){: #reactor-group-allow_async? } | `boolean` | `true` | Whether the emitted steps should be allowed to run asynchronously. |
|
| [`allow_async?`](#reactor-group-allow_async?){: #reactor-group-allow_async? } | `boolean` | `true` | Whether the emitted steps should be allowed to run asynchronously. |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ defmodule Reactor.Dsl.Middleware do
|
||||||
target: __MODULE__,
|
target: __MODULE__,
|
||||||
args: [:module],
|
args: [:module],
|
||||||
identifier: :module,
|
identifier: :module,
|
||||||
|
modules: [:module],
|
||||||
schema: [
|
schema: [
|
||||||
module: [
|
module: [
|
||||||
type: {:behaviour, Middleware},
|
type: {:behaviour, Middleware},
|
||||||
|
|
|
@ -8,6 +8,15 @@ defmodule Reactor.Executor.Hooks do
|
||||||
@doc "Run the init hooks collecting the new context as it goes"
|
@doc "Run the init hooks collecting the new context as it goes"
|
||||||
@spec init(Reactor.t(), Reactor.context()) :: {:ok, Reactor.context()} | {:error, any}
|
@spec init(Reactor.t(), Reactor.context()) :: {:ok, Reactor.context()} | {:error, any}
|
||||||
def init(reactor, context) do
|
def init(reactor, context) do
|
||||||
|
context =
|
||||||
|
Map.put(context, :__reactor__, %{
|
||||||
|
id: reactor.id,
|
||||||
|
inputs: reactor.inputs,
|
||||||
|
middleware: reactor.middleware,
|
||||||
|
step_count: step_count(reactor),
|
||||||
|
initial_state: reactor.state
|
||||||
|
})
|
||||||
|
|
||||||
Utils.reduce_while_ok(reactor.middleware, context, fn middleware, context ->
|
Utils.reduce_while_ok(reactor.middleware, context, fn middleware, context ->
|
||||||
if function_exported?(middleware, :init, 1) do
|
if function_exported?(middleware, :init, 1) do
|
||||||
middleware.init(context)
|
middleware.init(context)
|
||||||
|
@ -94,4 +103,11 @@ defmodule Reactor.Executor.Hooks do
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp step_count(reactor) when is_nil(reactor.plan), do: length(reactor.steps)
|
||||||
|
|
||||||
|
defp step_count(reactor) do
|
||||||
|
vertices = Graph.num_vertices(reactor.plan)
|
||||||
|
length(reactor.steps) + vertices
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
537
lib/reactor/middleware/telemetry.ex
Normal file
537
lib/reactor/middleware/telemetry.ex
Normal file
|
@ -0,0 +1,537 @@
|
||||||
|
defmodule Reactor.Middleware.Telemetry do
|
||||||
|
@moduledoc """
|
||||||
|
A Reactor middleware that emits telemetry events.
|
||||||
|
|
||||||
|
The following events are emitted:
|
||||||
|
|
||||||
|
* `[:reactor, :run, :start]`
|
||||||
|
* `[:reactor, :run, :stop]`
|
||||||
|
* `[:reactor, :step, :run, :start]`
|
||||||
|
* `[:reactor, :step, :run, :stop]`
|
||||||
|
* `[:reactor, :step, :process, :start]`
|
||||||
|
* `[:reactor, :step, :process, :stop]`
|
||||||
|
* `[:reactor, :step, :compensate, :start]`
|
||||||
|
* `[:reactor, :step, :compensate, :stop]`
|
||||||
|
* `[:reactor, :step, :undo, :start]`
|
||||||
|
* `[:reactor, :step, :undo, :stop]`
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Reactor.Middleware
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@impl true
|
||||||
|
def init(context) do
|
||||||
|
start = System.monotonic_time()
|
||||||
|
|
||||||
|
metadata = context.__reactor__
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :run, :start],
|
||||||
|
%{system_time: System.system_time()},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
context =
|
||||||
|
context
|
||||||
|
|> Map.put(__MODULE__, %{
|
||||||
|
start_time: start,
|
||||||
|
metadata: metadata
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, context}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@impl true
|
||||||
|
def complete(result, %{__MODULE__ => %{start_time: start_time, metadata: metadata}}) do
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: :ok,
|
||||||
|
result: result
|
||||||
|
})
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :run, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, result}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@impl true
|
||||||
|
def error(error_or_errors, %{__MODULE__ => %{start_time: start_time, metadata: metadata}}) do
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: :error,
|
||||||
|
errors: error_or_errors
|
||||||
|
})
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :run, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@impl true
|
||||||
|
def halt(%{__MODULE__ => %{start_time: start_time, metadata: metadata}} = context) do
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: :halt
|
||||||
|
})
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :run, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, context}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@impl true
|
||||||
|
def event({:process_start, pid}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
pid: pid
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = System.monotonic_time()
|
||||||
|
Process.put({__MODULE__, :process_start_time}, start_time)
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :process, :start],
|
||||||
|
%{system_time: System.system_time()},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:process_terminate, pid}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
pid: pid
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :process_start_time})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :process, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:run_start, arguments}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
arguments: arguments
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = System.monotonic_time()
|
||||||
|
Process.put({__MODULE__, :step_start_time, step.name}, start_time)
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :run, :start],
|
||||||
|
%{system_time: System.system_time()},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:run_complete, result}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: result,
|
||||||
|
status: :ok
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :step_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:run_error, errors}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: errors,
|
||||||
|
status: :error
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :step_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:run_halt, value}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: value,
|
||||||
|
status: :halt
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :step_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:run_retry, value}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: value,
|
||||||
|
status: :retry
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :step_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event(:run_retry, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: nil,
|
||||||
|
status: :retry
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :step_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:compensate_start, reason}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: reason,
|
||||||
|
status: :compensate
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = System.monotonic_time()
|
||||||
|
Process.put({__MODULE__, :compensate_start_time, step.name}, start_time)
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :compensate, :start],
|
||||||
|
%{system_time: System.system_time()},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event(:compensate_retry, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: nil,
|
||||||
|
status: :retry
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :compensate_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :compensate, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event(:compensate_complete, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: nil,
|
||||||
|
status: :ok
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :compensate_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :compensate, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:compensate_retry, value}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: value,
|
||||||
|
status: :retry
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :compensate_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :compensate, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:compensate_error, errors}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: errors,
|
||||||
|
status: :error
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :compensate_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :compensate, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:compensate_continue, result}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: result,
|
||||||
|
status: :ok
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :compensate_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :compensate, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event(:undo_start, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: nil,
|
||||||
|
status: :undo
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = System.monotonic_time()
|
||||||
|
Process.put({__MODULE__, :undo_start_time, step.name}, start_time)
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :undo, :start],
|
||||||
|
%{system_time: System.system_time()},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:undo_error, errors}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: errors,
|
||||||
|
status: :error
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :undo_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :undo, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event({:undo_retry, errors}, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: errors,
|
||||||
|
status: :retry
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :undo_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :undo, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event(:undo_retry, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: nil,
|
||||||
|
status: :retry
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :undo_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :undo, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def event(:undo_complete, step, %{__MODULE__ => %{metadata: metadata}}) do
|
||||||
|
metadata =
|
||||||
|
metadata
|
||||||
|
|> Map.merge(%{
|
||||||
|
step: step,
|
||||||
|
result: nil,
|
||||||
|
status: :ok
|
||||||
|
})
|
||||||
|
|
||||||
|
start_time = Process.delete({__MODULE__, :undo_start_time, step.name})
|
||||||
|
end_time = System.monotonic_time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:reactor, :step, :undo, :stop],
|
||||||
|
%{
|
||||||
|
system_time: System.system_time(),
|
||||||
|
duration: duration
|
||||||
|
},
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
3
mix.exs
3
mix.exs
|
@ -40,6 +40,8 @@ defmodule Reactor.MixProject do
|
||||||
groups_for_modules: [
|
groups_for_modules: [
|
||||||
DSL: ~r/^Reactor\.Dsl$/,
|
DSL: ~r/^Reactor\.Dsl$/,
|
||||||
Steps: ~r/^Reactor\.Step.*/,
|
Steps: ~r/^Reactor\.Step.*/,
|
||||||
|
Middleware: ~r/^Reactor\.Middleware.*/,
|
||||||
|
Builder: ~r/^Reactor\.Builder.*/,
|
||||||
Internals: ~r/^Reactor\..*/
|
Internals: ~r/^Reactor\..*/
|
||||||
],
|
],
|
||||||
extra_section: "GUIDES",
|
extra_section: "GUIDES",
|
||||||
|
@ -89,6 +91,7 @@ defmodule Reactor.MixProject do
|
||||||
[
|
[
|
||||||
{:spark, "~> 1.0"},
|
{:spark, "~> 1.0"},
|
||||||
{:libgraph, "~> 0.16"},
|
{:libgraph, "~> 0.16"},
|
||||||
|
{:telemetry, "~> 1.2"},
|
||||||
|
|
||||||
# Dev/Test dependencies
|
# Dev/Test dependencies
|
||||||
{:credo, ">= 0.0.0", only: [:dev, :test], runtime: false},
|
{:credo, ">= 0.0.0", only: [:dev, :test], runtime: false},
|
||||||
|
|
1
mix.lock
1
mix.lock
|
@ -24,6 +24,7 @@
|
||||||
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
|
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
|
||||||
"sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"},
|
"sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"},
|
||||||
"spark": {:hex, :spark, "1.1.54", "54dac39403a2960f738ba5d60678d20b30de7381fb51b787b6bcb6aeabb73d9d", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "abc9a67cfb60a97d2f3c7e270fa968a2ace94f389e2741d406239d237ec6dbb1"},
|
"spark": {:hex, :spark, "1.1.54", "54dac39403a2960f738ba5d60678d20b30de7381fb51b787b6bcb6aeabb73d9d", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "abc9a67cfb60a97d2f3c7e270fa968a2ace94f389e2741d406239d237ec6dbb1"},
|
||||||
|
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||||
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
|
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
|
||||||
"yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"},
|
"yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"},
|
||||||
}
|
}
|
||||||
|
|
185
test/reactor/middleware/telemetry_test.exs
Normal file
185
test/reactor/middleware/telemetry_test.exs
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
defmodule Reactor.Middleware.TelemetryTest do
|
||||||
|
@moduledoc false
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
setup(context) do
|
||||||
|
table =
|
||||||
|
:ets.new(context.test, [:public, :ordered_set])
|
||||||
|
|
||||||
|
:telemetry.attach_many(
|
||||||
|
to_string(context.test),
|
||||||
|
[
|
||||||
|
[:reactor, :run, :start],
|
||||||
|
[:reactor, :run, :stop],
|
||||||
|
[:reactor, :step, :process, :start],
|
||||||
|
[:reactor, :step, :process, :stop],
|
||||||
|
[:reactor, :step, :run, :start],
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
[:reactor, :step, :compensate, :start],
|
||||||
|
[:reactor, :step, :compensate, :stop],
|
||||||
|
[:reactor, :step, :undo, :start],
|
||||||
|
[:reactor, :step, :undo, :stop]
|
||||||
|
],
|
||||||
|
&__MODULE__.handler/4,
|
||||||
|
table
|
||||||
|
)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
:telemetry.detach(to_string(context.test))
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, table: table}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handler(event, measurements, metadata, table) do
|
||||||
|
:ets.insert(
|
||||||
|
table,
|
||||||
|
{measurements.system_time, %{event: event, measurements: measurements, metadata: metadata}}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_events(table) do
|
||||||
|
Process.sleep(200)
|
||||||
|
|
||||||
|
table
|
||||||
|
|> :ets.tab2list()
|
||||||
|
|> Enum.map(&elem(&1, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "step run events", %{table: table} do
|
||||||
|
defmodule SuccessfulStepReactor do
|
||||||
|
@moduledoc false
|
||||||
|
use Reactor
|
||||||
|
|
||||||
|
middlewares do
|
||||||
|
middleware Reactor.Middleware.Telemetry
|
||||||
|
end
|
||||||
|
|
||||||
|
step :noop do
|
||||||
|
argument :marty, value(:mcfly)
|
||||||
|
run fn _, _ -> {:ok, :noop} end
|
||||||
|
end
|
||||||
|
|
||||||
|
return :noop
|
||||||
|
end
|
||||||
|
|
||||||
|
start_time = System.monotonic_time()
|
||||||
|
|
||||||
|
{:ok, :noop} = Reactor.run(SuccessfulStepReactor)
|
||||||
|
|
||||||
|
expected_duration =
|
||||||
|
System.convert_time_unit(System.monotonic_time() - start_time, :native, :millisecond)
|
||||||
|
|
||||||
|
events = get_events(table)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
[:reactor, :run, :start],
|
||||||
|
[:reactor, :step, :process, :start],
|
||||||
|
[:reactor, :step, :run, :start],
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
[:reactor, :step, :process, :stop],
|
||||||
|
[:reactor, :run, :stop]
|
||||||
|
] = Enum.map(events, & &1.event)
|
||||||
|
|
||||||
|
[run_start, _, _, step_stop, _, run_stop] = events
|
||||||
|
|
||||||
|
assert run_start.metadata.id == SuccessfulStepReactor
|
||||||
|
assert run_start.metadata.inputs == []
|
||||||
|
assert run_start.metadata.middleware == [Reactor.Middleware.Telemetry]
|
||||||
|
assert run_start.metadata.step_count == 1
|
||||||
|
|
||||||
|
assert run_stop.metadata.status == :ok
|
||||||
|
assert run_stop.metadata.result == :noop
|
||||||
|
|
||||||
|
run_duration_in_millis =
|
||||||
|
System.convert_time_unit(run_stop.measurements.duration, :native, :millisecond)
|
||||||
|
|
||||||
|
assert_in_delta run_duration_in_millis, expected_duration, 100
|
||||||
|
assert run_duration_in_millis <= expected_duration
|
||||||
|
|
||||||
|
step_duration_in_millis =
|
||||||
|
System.convert_time_unit(step_stop.measurements.duration, :native, :millisecond)
|
||||||
|
|
||||||
|
assert step_duration_in_millis <= run_duration_in_millis
|
||||||
|
assert_in_delta step_duration_in_millis, expected_duration, 100
|
||||||
|
end
|
||||||
|
|
||||||
|
test "step compensation events", %{table: table} do
|
||||||
|
defmodule CompensationReactor do
|
||||||
|
@moduledoc false
|
||||||
|
use Reactor
|
||||||
|
|
||||||
|
middlewares do
|
||||||
|
middleware Reactor.Middleware.Telemetry
|
||||||
|
end
|
||||||
|
|
||||||
|
step :fail do
|
||||||
|
run fn _, _ -> raise "hell" end
|
||||||
|
compensate fn _, _ -> :ok end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, _} = Reactor.run(CompensationReactor)
|
||||||
|
|
||||||
|
events = get_events(table)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
[:reactor, :run, :start],
|
||||||
|
[:reactor, :step, :process, :start],
|
||||||
|
[:reactor, :step, :run, :start],
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
[:reactor, :step, :compensate, :start],
|
||||||
|
[:reactor, :step, :compensate, :stop],
|
||||||
|
[:reactor, :step, :process, :stop],
|
||||||
|
[:reactor, :run, :stop]
|
||||||
|
] = Enum.map(events, & &1.event)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "step undo events", %{table: table} do
|
||||||
|
defmodule UndoReactor do
|
||||||
|
@moduledoc false
|
||||||
|
use Reactor
|
||||||
|
|
||||||
|
middlewares do
|
||||||
|
middleware Reactor.Middleware.Telemetry
|
||||||
|
end
|
||||||
|
|
||||||
|
step :noop do
|
||||||
|
run fn _, _ ->
|
||||||
|
{:ok, :noop}
|
||||||
|
end
|
||||||
|
|
||||||
|
undo fn _ ->
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
step :fail do
|
||||||
|
wait_for :noop
|
||||||
|
|
||||||
|
run fn _, _ ->
|
||||||
|
raise "hell"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, _} = Reactor.run(UndoReactor)
|
||||||
|
|
||||||
|
events = get_events(table)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
[:reactor, :run, :start],
|
||||||
|
[:reactor, :step, :process, :start],
|
||||||
|
[:reactor, :step, :run, :start],
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
[:reactor, :step, :process, :stop],
|
||||||
|
[:reactor, :step, :process, :start],
|
||||||
|
[:reactor, :step, :run, :start],
|
||||||
|
[:reactor, :step, :run, :stop],
|
||||||
|
[:reactor, :step, :process, :stop],
|
||||||
|
[:reactor, :step, :undo, :start],
|
||||||
|
[:reactor, :step, :undo, :stop],
|
||||||
|
[:reactor, :run, :stop]
|
||||||
|
] = Enum.map(events, & &1.event)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue