From f7b9d077e670135de0a7cb0bee56e18b48ade128 Mon Sep 17 00:00:00 2001 From: Felipe Ripoll Date: Mon, 7 May 2018 17:13:41 -0600 Subject: [PATCH 1/4] [#6] designing the plugin system --- config/config.exs | 15 +++++ lib/poa_agent.ex | 9 --- lib/poa_agent/application.ex | 17 +++++ lib/poa_agent/plugins/collector.ex | 66 +++++++++++++++++++ .../plugins/collectors/my_collector.ex | 18 +++++ .../plugins/collectors/supervisor.ex | 22 +++++++ lib/poa_agent/plugins/transfer.ex | 53 +++++++++++++++ .../plugins/transfers/my_transfer.ex | 18 +++++ lib/poa_agent/plugins/transfers/supervisor.ex | 22 +++++++ mix.exs | 3 +- test/poa_agent_test.exs | 2 +- 11 files changed, 234 insertions(+), 11 deletions(-) delete mode 100644 lib/poa_agent.ex create mode 100644 lib/poa_agent/application.ex create mode 100644 lib/poa_agent/plugins/collector.ex create mode 100644 lib/poa_agent/plugins/collectors/my_collector.ex create mode 100644 lib/poa_agent/plugins/collectors/supervisor.ex create mode 100644 lib/poa_agent/plugins/transfer.ex create mode 100644 lib/poa_agent/plugins/transfers/my_transfer.ex create mode 100644 lib/poa_agent/plugins/transfers/supervisor.ex diff --git a/config/config.exs b/config/config.exs index d2d855e..d854609 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1 +1,16 @@ use Mix.Config + + +# configuration for collectors. The format for each collector is {collector_process_id, module, [target_transfers], label, args} +config :poa_agent, + :collectors, + [ + {:my_collector, POAAgent.Plugins.Collectors.MyCollector, [:my_transfer], :my_metrics, [host: "localhost", port: 1234]} + ] + +# configuration for transfers. The format for each collector is {collector_process_id, module, args} +config :poa_agent, + :transfers, + [ + {:my_transfer, POAAgent.Plugins.Transfers.MyTransfer, [ws_key: "mykey", other_stuff: "hello"]} + ] \ No newline at end of file diff --git a/lib/poa_agent.ex b/lib/poa_agent.ex deleted file mode 100644 index 464f9b4..0000000 --- a/lib/poa_agent.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule POAAgent do - @moduledoc """ - Documentation for PoaAgent. - """ - - def hello do - :world - end -end diff --git a/lib/poa_agent/application.ex b/lib/poa_agent/application.ex new file mode 100644 index 0000000..714f09f --- /dev/null +++ b/lib/poa_agent/application.ex @@ -0,0 +1,17 @@ +defmodule POAAgent.Application do + @moduledoc false + + use Application + + def start(_type, _args) do + import Supervisor.Spec + + children = [ + supervisor(POAAgent.Plugins.Transfers.Supervisor, []), + supervisor(POAAgent.Plugins.Collectors.Supervisor, []) + ] + + opts = [strategy: :one_for_one, name: POAAgent.Supervisor] + Supervisor.start_link(children, opts) + end +end \ No newline at end of file diff --git a/lib/poa_agent/plugins/collector.ex b/lib/poa_agent/plugins/collector.ex new file mode 100644 index 0000000..0048656 --- /dev/null +++ b/lib/poa_agent/plugins/collector.ex @@ -0,0 +1,66 @@ +defmodule POAAgent.Plugins.Collector do + + @callback init_collector(args :: term()) :: + {:ok, any()} + + @callback collect(state :: any()) :: {:ok, data :: any(), state :: any()} + + @callback terminate(reason, state :: term()) :: term() + when reason: :normal | :shutdown | {:shutdown, term()} + + defmacro __using__(_opt) do + quote do + @behaviour POAAgent.Plugins.Collector + + @doc false + def start_link(%{name: name} = state) do + GenServer.start_link(__MODULE__, state, name: name) + end + + @doc false + def init(state) do + {:ok, internal_state} = init_collector(state[:args]) + set_collector_timer() + {:ok, Map.put(state, :internal_state, internal_state)} + end + + @doc false + def handle_call(_msg, _from, state) do + {:noreply, state} + end + + @doc false + def handle_info(:collect, state) do + {:ok, data, internal_state} = collect(state.internal_state) + transfer(data, state.label, state.transfers) + set_collector_timer() + {:noreply, %{state | internal_state: internal_state}} + end + def handle_info(_msg, state) do + {:noreply, state} + end + + @doc false + def handle_cast(msg, state) do + {:noreply, state} + end + + @doc false + def code_change(_old, state, _extra) do + {:ok, state} + end + + @doc false + def transfer(data, label, transfers) do + Enum.each(transfers, &GenServer.cast(&1, %{label: label, data: data})) + :ok + end + + defp set_collector_timer() do + Process.send_after(self(), :collect, 5000) # TODO timeout must be configurable + end + + end + end + +end \ No newline at end of file diff --git a/lib/poa_agent/plugins/collectors/my_collector.ex b/lib/poa_agent/plugins/collectors/my_collector.ex new file mode 100644 index 0000000..91eb571 --- /dev/null +++ b/lib/poa_agent/plugins/collectors/my_collector.ex @@ -0,0 +1,18 @@ +defmodule POAAgent.Plugins.Collectors.MyCollector do + use POAAgent.Plugins.Collector + + def init_collector(args) do + IO.puts "init_collector args = #{inspect args}" + {:ok, :no_state} + end + + def collect(:no_state) do + IO.puts "I am collecting data!" + {:ok, "data retrieved", :no_state} + end + + def terminate(_reason, _state) do + :ok + end + +end \ No newline at end of file diff --git a/lib/poa_agent/plugins/collectors/supervisor.ex b/lib/poa_agent/plugins/collectors/supervisor.ex new file mode 100644 index 0000000..0bca8d5 --- /dev/null +++ b/lib/poa_agent/plugins/collectors/supervisor.ex @@ -0,0 +1,22 @@ +defmodule POAAgent.Plugins.Collectors.Supervisor do + @moduledoc false + + def start_link do + Supervisor.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def init(:ok) do + import Supervisor.Spec + + # create the children from the config file + collectors = Application.get_env(:poa_agent, :collectors) + + children = for {name, module, transfers, label, args} <- collectors do + worker(module, [%{name: name, transfers: transfers, label: label, args: args}]) + end + + opts = [strategy: :one_for_one] + supervise(children, opts) + end + +end \ No newline at end of file diff --git a/lib/poa_agent/plugins/transfer.ex b/lib/poa_agent/plugins/transfer.ex new file mode 100644 index 0000000..c263abb --- /dev/null +++ b/lib/poa_agent/plugins/transfer.ex @@ -0,0 +1,53 @@ +defmodule POAAgent.Plugins.Transfer do + + @callback init_transfer(args :: term()) :: + {:ok, any()} + + @callback data_received(label :: atom(), data :: any(), state :: any()) :: {:ok, any()} + + @callback terminate(reason, state :: term()) :: term() + when reason: :normal | :shutdown | {:shutdown, term()} + + defmacro __using__(_opt) do + quote do + @behaviour POAAgent.Plugins.Transfer + + @doc false + def start_link(%{name: name} = state) do + GenServer.start_link(__MODULE__, state, name: name) + end + + @doc false + def init(state) do + {:ok, internal_state} = init_transfer(state[:args]) + {:ok, Map.put(state, :internal_state, internal_state)} + end + + @doc false + def handle_call(_msg, _from, state) do + {:noreply, state} + end + + @doc false + def handle_info(_msg, state) do + {:noreply, state} + end + + @doc false + def handle_cast(%{label: label, data: data}, state) do + {:ok, internal_state} = data_received(label, data, state.internal_state) + {:noreply, %{state | internal_state: internal_state}} + end + def handle_cast(msg, state) do + {:noreply, state} + end + + @doc false + def code_change(_old, state, _extra) do + {:ok, state} + end + + end + end + +end \ No newline at end of file diff --git a/lib/poa_agent/plugins/transfers/my_transfer.ex b/lib/poa_agent/plugins/transfers/my_transfer.ex new file mode 100644 index 0000000..900eae6 --- /dev/null +++ b/lib/poa_agent/plugins/transfers/my_transfer.ex @@ -0,0 +1,18 @@ +defmodule POAAgent.Plugins.Transfers.MyTransfer do + use POAAgent.Plugins.Transfer + + def init_transfer(args) do + IO.puts "init_transfer args = #{inspect args}" + {:ok, :no_state} + end + + def data_received(label, data, state) do + IO.puts "Received data with label #{inspect label}, data #{inspect data} and internal_state #{inspect state}" + {:ok, :no_state} + end + + def terminate(_reason, _state) do + :ok + end + +end \ No newline at end of file diff --git a/lib/poa_agent/plugins/transfers/supervisor.ex b/lib/poa_agent/plugins/transfers/supervisor.ex new file mode 100644 index 0000000..b385520 --- /dev/null +++ b/lib/poa_agent/plugins/transfers/supervisor.ex @@ -0,0 +1,22 @@ +defmodule POAAgent.Plugins.Transfers.Supervisor do + @moduledoc false + + def start_link do + Supervisor.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def init(:ok) do + import Supervisor.Spec + + # create the children from the config file + transfers = Application.get_env(:poa_agent, :transfers) + + children = for {name, module, args} <- transfers do + worker(module, [%{name: name, args: args}]) + end + + opts = [strategy: :one_for_one] + supervise(children, opts) + end + +end \ No newline at end of file diff --git a/mix.exs b/mix.exs index aa2d0e2..e667f06 100644 --- a/mix.exs +++ b/mix.exs @@ -13,7 +13,8 @@ defmodule POAAgent.MixProject do def application do [ - extra_applications: [:logger] + extra_applications: [:logger], + mod: {POAAgent.Application, []} ] end diff --git a/test/poa_agent_test.exs b/test/poa_agent_test.exs index ebd07af..4cceab3 100644 --- a/test/poa_agent_test.exs +++ b/test/poa_agent_test.exs @@ -3,6 +3,6 @@ defmodule POAAgentTest do doctest POAAgent test "greets the world" do - assert POAAgent.hello() == :world + # assert POAAgent.hello() == :world end end From 64d1d89a2f0e50ae26baa7a9446409b1947656f5 Mon Sep 17 00:00:00 2001 From: Felipe Ripoll Date: Tue, 8 May 2018 05:45:30 -0600 Subject: [PATCH 2/4] [#6] removing reason in terminate and changing configuration --- config/config.exs | 9 ++++++++- lib/poa_agent/plugins/collector.ex | 8 ++++++-- lib/poa_agent/plugins/collectors/my_collector.ex | 2 +- lib/poa_agent/plugins/collectors/supervisor.ex | 5 +++-- lib/poa_agent/plugins/transfer.ex | 8 ++++++-- lib/poa_agent/plugins/transfers/my_transfer.ex | 2 +- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/config/config.exs b/config/config.exs index d854609..7fcf871 100644 --- a/config/config.exs +++ b/config/config.exs @@ -5,7 +5,7 @@ use Mix.Config config :poa_agent, :collectors, [ - {:my_collector, POAAgent.Plugins.Collectors.MyCollector, [:my_transfer], :my_metrics, [host: "localhost", port: 1234]} + {:my_collector, POAAgent.Plugins.Collectors.MyCollector, :my_metrics, [host: "localhost", port: 1234]} ] # configuration for transfers. The format for each collector is {collector_process_id, module, args} @@ -13,4 +13,11 @@ config :poa_agent, :transfers, [ {:my_transfer, POAAgent.Plugins.Transfers.MyTransfer, [ws_key: "mykey", other_stuff: "hello"]} + ] + +# configuration for mappings. This relates one collector with a list of transfers which the data will be sent +config :poa_agent, + :mappings, + [ + {:my_collector, [:my_transfer]} ] \ No newline at end of file diff --git a/lib/poa_agent/plugins/collector.ex b/lib/poa_agent/plugins/collector.ex index 0048656..64a9316 100644 --- a/lib/poa_agent/plugins/collector.ex +++ b/lib/poa_agent/plugins/collector.ex @@ -5,8 +5,7 @@ defmodule POAAgent.Plugins.Collector do @callback collect(state :: any()) :: {:ok, data :: any(), state :: any()} - @callback terminate(reason, state :: term()) :: term() - when reason: :normal | :shutdown | {:shutdown, term()} + @callback terminate(state :: term()) :: term() defmacro __using__(_opt) do quote do @@ -50,6 +49,11 @@ defmodule POAAgent.Plugins.Collector do {:ok, state} end + @doc false + def terminate(_reason, state) do + terminate(state) + end + @doc false def transfer(data, label, transfers) do Enum.each(transfers, &GenServer.cast(&1, %{label: label, data: data})) diff --git a/lib/poa_agent/plugins/collectors/my_collector.ex b/lib/poa_agent/plugins/collectors/my_collector.ex index 91eb571..1b3990a 100644 --- a/lib/poa_agent/plugins/collectors/my_collector.ex +++ b/lib/poa_agent/plugins/collectors/my_collector.ex @@ -11,7 +11,7 @@ defmodule POAAgent.Plugins.Collectors.MyCollector do {:ok, "data retrieved", :no_state} end - def terminate(_reason, _state) do + def terminate(_state) do :ok end diff --git a/lib/poa_agent/plugins/collectors/supervisor.ex b/lib/poa_agent/plugins/collectors/supervisor.ex index 0bca8d5..ce929b4 100644 --- a/lib/poa_agent/plugins/collectors/supervisor.ex +++ b/lib/poa_agent/plugins/collectors/supervisor.ex @@ -10,9 +10,10 @@ defmodule POAAgent.Plugins.Collectors.Supervisor do # create the children from the config file collectors = Application.get_env(:poa_agent, :collectors) + mappings = Application.get_env(:poa_agent, :mappings) - children = for {name, module, transfers, label, args} <- collectors do - worker(module, [%{name: name, transfers: transfers, label: label, args: args}]) + children = for {name, module, label, args} <- collectors do + worker(module, [%{name: name, transfers: mappings[name], label: label, args: args}]) end opts = [strategy: :one_for_one] diff --git a/lib/poa_agent/plugins/transfer.ex b/lib/poa_agent/plugins/transfer.ex index c263abb..c4231f9 100644 --- a/lib/poa_agent/plugins/transfer.ex +++ b/lib/poa_agent/plugins/transfer.ex @@ -5,8 +5,7 @@ defmodule POAAgent.Plugins.Transfer do @callback data_received(label :: atom(), data :: any(), state :: any()) :: {:ok, any()} - @callback terminate(reason, state :: term()) :: term() - when reason: :normal | :shutdown | {:shutdown, term()} + @callback terminate(state :: term()) :: term() defmacro __using__(_opt) do quote do @@ -47,6 +46,11 @@ defmodule POAAgent.Plugins.Transfer do {:ok, state} end + @doc false + def terminate(_reason, state) do + terminate(state) + end + end end diff --git a/lib/poa_agent/plugins/transfers/my_transfer.ex b/lib/poa_agent/plugins/transfers/my_transfer.ex index 900eae6..6a57d36 100644 --- a/lib/poa_agent/plugins/transfers/my_transfer.ex +++ b/lib/poa_agent/plugins/transfers/my_transfer.ex @@ -11,7 +11,7 @@ defmodule POAAgent.Plugins.Transfers.MyTransfer do {:ok, :no_state} end - def terminate(_reason, _state) do + def terminate(_state) do :ok end From edc2e949cfb16c103218e1d02f7cebcd2b768700 Mon Sep 17 00:00:00 2001 From: Felipe Ripoll Date: Tue, 8 May 2018 09:20:58 -0600 Subject: [PATCH 3/4] [#6] adding tests for Plugins --- config/config.exs | 8 +- lib/poa_agent/plugins/collector.ex | 3 +- .../plugins/collectors/my_collector.ex | 18 ---- .../plugins/transfers/my_transfer.ex | 18 ---- test/plugins_test.exs | 101 ++++++++++++++++++ test/poa_agent_test.exs | 8 -- 6 files changed, 107 insertions(+), 49 deletions(-) delete mode 100644 lib/poa_agent/plugins/collectors/my_collector.ex delete mode 100644 lib/poa_agent/plugins/transfers/my_transfer.ex create mode 100644 test/plugins_test.exs delete mode 100644 test/poa_agent_test.exs diff --git a/config/config.exs b/config/config.exs index 7fcf871..5b431fd 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,23 +1,23 @@ use Mix.Config -# configuration for collectors. The format for each collector is {collector_process_id, module, [target_transfers], label, args} +# configuration for collectors. The format for each collector is {collector_process_id, module, label, args} config :poa_agent, :collectors, [ - {:my_collector, POAAgent.Plugins.Collectors.MyCollector, :my_metrics, [host: "localhost", port: 1234]} + # {:my_collector, POAAgent.Plugins.Collectors.MyCollector, :my_metrics, [host: "localhost", port: 1234]} ] # configuration for transfers. The format for each collector is {collector_process_id, module, args} config :poa_agent, :transfers, [ - {:my_transfer, POAAgent.Plugins.Transfers.MyTransfer, [ws_key: "mykey", other_stuff: "hello"]} + # {:my_transfer, POAAgent.Plugins.Transfers.MyTransfer, [ws_key: "mykey", other_stuff: "hello"]} ] # configuration for mappings. This relates one collector with a list of transfers which the data will be sent config :poa_agent, :mappings, [ - {:my_collector, [:my_transfer]} + # {:my_collector, [:my_transfer]} ] \ No newline at end of file diff --git a/lib/poa_agent/plugins/collector.ex b/lib/poa_agent/plugins/collector.ex index 64a9316..8053419 100644 --- a/lib/poa_agent/plugins/collector.ex +++ b/lib/poa_agent/plugins/collector.ex @@ -55,11 +55,12 @@ defmodule POAAgent.Plugins.Collector do end @doc false - def transfer(data, label, transfers) do + defp transfer(data, label, transfers) do Enum.each(transfers, &GenServer.cast(&1, %{label: label, data: data})) :ok end + @doc false defp set_collector_timer() do Process.send_after(self(), :collect, 5000) # TODO timeout must be configurable end diff --git a/lib/poa_agent/plugins/collectors/my_collector.ex b/lib/poa_agent/plugins/collectors/my_collector.ex deleted file mode 100644 index 1b3990a..0000000 --- a/lib/poa_agent/plugins/collectors/my_collector.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule POAAgent.Plugins.Collectors.MyCollector do - use POAAgent.Plugins.Collector - - def init_collector(args) do - IO.puts "init_collector args = #{inspect args}" - {:ok, :no_state} - end - - def collect(:no_state) do - IO.puts "I am collecting data!" - {:ok, "data retrieved", :no_state} - end - - def terminate(_state) do - :ok - end - -end \ No newline at end of file diff --git a/lib/poa_agent/plugins/transfers/my_transfer.ex b/lib/poa_agent/plugins/transfers/my_transfer.ex deleted file mode 100644 index 6a57d36..0000000 --- a/lib/poa_agent/plugins/transfers/my_transfer.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule POAAgent.Plugins.Transfers.MyTransfer do - use POAAgent.Plugins.Transfer - - def init_transfer(args) do - IO.puts "init_transfer args = #{inspect args}" - {:ok, :no_state} - end - - def data_received(label, data, state) do - IO.puts "Received data with label #{inspect label}, data #{inspect data} and internal_state #{inspect state}" - {:ok, :no_state} - end - - def terminate(_state) do - :ok - end - -end \ No newline at end of file diff --git a/test/plugins_test.exs b/test/plugins_test.exs new file mode 100644 index 0000000..2017f47 --- /dev/null +++ b/test/plugins_test.exs @@ -0,0 +1,101 @@ +defmodule POAAgent.PluginsTest do + use ExUnit.Case + + test "__using__ Collector" do + defmodule Collector1 do + use POAAgent.Plugins.Collector + + def init_collector(_args) do + {:ok, :no_state} + end + + def collect(:no_state) do + {:ok, "data retrieved", :no_state} + end + + def terminate(_state) do + :ok + end + end + + assert Collector1.init(%{}) == {:ok, %{internal_state: :no_state}} + assert Collector1.handle_call(:msg, :from, :state) == {:noreply, :state} + assert Collector1.handle_info(:msg, :state) == {:noreply, :state} + assert Collector1.handle_cast(:msg, :state) == {:noreply, :state} + assert Collector1.code_change(:old, :state, :extra) == {:ok, :state} + assert Collector1.terminate(:reason, :state) == :ok + + end + + test "__using__ Transfer" do + defmodule Transfer1 do + use POAAgent.Plugins.Transfer + + def init_transfer(_args) do + {:ok, :no_state} + end + + def data_received(_label, _data, _state) do + {:ok, :no_state} + end + + def terminate(_state) do + :ok + end + end + + assert Transfer1.init(%{args: :args}) == {:ok, %{internal_state: :no_state, args: :args}} + assert Transfer1.handle_call(:msg, :from, :state) == {:noreply, :state} + assert Transfer1.handle_info(:msg, :state) == {:noreply, :state} + assert Transfer1.handle_cast(:msg, :state) == {:noreply, :state} + assert Transfer1.code_change(:old, :state, :extra) == {:ok, :state} + assert Transfer1.terminate(:reason, :state) == :ok + + end + + test "Collector - Transfer integration" do + defmodule Collector2 do + use POAAgent.Plugins.Collector + + def init_collector(test_pid) do + {:ok, test_pid} + end + + def collect(test_pid) do + data = "data retrieved" + send test_pid, {:sent, self(), data} + {:ok, data, test_pid} + end + + def terminate(_state) do + :ok + end + end + + defmodule Transfer2 do + use POAAgent.Plugins.Transfer + + def init_transfer(test_pid) do + {:ok, test_pid} + end + + def data_received(label, data, test_pid) do + send test_pid, {:received, self(), label, data} + {:ok, test_pid} + end + + def terminate(_state) do + :ok + end + end + + transfer1 = :transfer2 + + {:ok, tpid} = Transfer2.start_link(%{name: transfer1, args: self()}) + {:ok, cpid} = Collector2.start_link(%{name: :collector2, transfers: [transfer1], label: :label, args: self()}) + + assert_receive {:sent, ^cpid, "data retrieved"}, 20_000 + assert_receive {:received, ^tpid, :label, "data retrieved"}, 20_000 + + end +end diff --git a/test/poa_agent_test.exs b/test/poa_agent_test.exs deleted file mode 100644 index 4cceab3..0000000 --- a/test/poa_agent_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule POAAgentTest do - use ExUnit.Case - doctest POAAgent - - test "greets the world" do - # assert POAAgent.hello() == :world - end -end From 41311bc8570e028aade11b0c7e7dc133a3add905 Mon Sep 17 00:00:00 2001 From: Felipe Ripoll Date: Tue, 8 May 2018 12:58:27 -0600 Subject: [PATCH 4/4] [#6] adding some documentation in the modules --- .gitignore | 1 + README.md | 8 ++ config/config.exs | 4 +- lib/poa_agent/application.ex | 8 +- lib/poa_agent/plugins/collector.ex | 100 +++++++++++++++++- .../plugins/collectors/supervisor.ex | 4 +- lib/poa_agent/plugins/transfer.ex | 75 +++++++++++++ mix.exs | 27 ++++- mix.lock | 2 + test/plugins_test.exs | 4 +- 10 files changed, 218 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index ac67aaf..01b6022 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /_build /cover /deps +/doc erl_crash.dump *.ez *.beam diff --git a/README.md b/README.md index 7dd3634..e23185f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ **TODO: Add description** +## Documentation + +In order to create the documentation + +``` +mix docs +``` + ## Run POAAgent is an Elixir application, in order to run it first we need to fetch the dependencies and compile it. diff --git a/config/config.exs b/config/config.exs index 5b431fd..1ea9830 100644 --- a/config/config.exs +++ b/config/config.exs @@ -5,7 +5,7 @@ use Mix.Config config :poa_agent, :collectors, [ - # {:my_collector, POAAgent.Plugins.Collectors.MyCollector, :my_metrics, [host: "localhost", port: 1234]} + # {:my_collector, POAAgent.Plugins.Collectors.MyCollector, 1000, :my_metrics, [host: "localhost", port: 1234]} ] # configuration for transfers. The format for each collector is {collector_process_id, module, args} @@ -20,4 +20,4 @@ config :poa_agent, :mappings, [ # {:my_collector, [:my_transfer]} - ] \ No newline at end of file + ] diff --git a/lib/poa_agent/application.ex b/lib/poa_agent/application.ex index 714f09f..2643fa6 100644 --- a/lib/poa_agent/application.ex +++ b/lib/poa_agent/application.ex @@ -1,5 +1,9 @@ defmodule POAAgent.Application do - @moduledoc false + @moduledoc """ + + This module implements the Application behaviour + + """ use Application @@ -14,4 +18,4 @@ defmodule POAAgent.Application do opts = [strategy: :one_for_one, name: POAAgent.Supervisor] Supervisor.start_link(children, opts) end -end \ No newline at end of file +end diff --git a/lib/poa_agent/plugins/collector.ex b/lib/poa_agent/plugins/collector.ex index 8053419..c63a66d 100644 --- a/lib/poa_agent/plugins/collector.ex +++ b/lib/poa_agent/plugins/collector.ex @@ -1,12 +1,104 @@ defmodule POAAgent.Plugins.Collector do + @moduledoc """ + Defines a Collector Plugin. + + A Collector plugin will run in an independent process and will run the `collect/1` + function in a given `frequency`. + + `POAAgent` app reads the Collectors configuration from the `config.exs` file when bootstrap and will create a + process per each one of them. That configuration is referenced by :collectors key. + + config :poa_agent, + :collectors, + [ + {name, module, frequency, label, args} + ] + + for example + + config :poa_agent, + :collectors, + [ + {:my_collector, POAAgent.Plugins.Collectors.MyCollector, 5000, :my_metrics, [host: "localhost", port: 1234]} + ] + + `name`, `module`, `frequency`, `label` and `args` must be defined in the configuration file. + + - `name`: Name for the new created process. Must be unique + - `module`: Module which implements the Collector behaviour + - `frequency`: time in milliseconds after which the function `collect/1` will be called + - `label`: The data collected will be prefixed with this label. ie `{:eth_metrics, "data"}` + - `args`: Initial args which will be passed to the `init_collector/1` function + + In order to work properly we have to define in the configuration file also the mapping between the Collector + and the Transfers related with it. A `Transfer` is a Plugin process which transfers the data to outside the agent node + (external Database, Dashboard server...). + + config :poa_agent, + :mappings, + [ + {collector_name, [transfer_name1, transfer_name2]} + ] + + for example + + config :poa_agent, + :mappings, + [ + {:my_collector, [:my_transfer]} + ] + + In order to implement your Collector Plugin you must implement 3 functions. + + - `init_collector/1`: Called only once when the process starts + - `collect/1`: This function is called periodically after `frequency` milliseconds. It is responsible + of retrieving the metrics + - `terminate/1`: Called just before stopping the process + + This is a simple example of custom Collector Plugin + + defmodule POAAgent.Plugins.Collectors.MyCollector do + use POAAgent.Plugins.Collector + + def init_collector(args) do + {:ok, :no_state} + end + + def collect(:no_state) do + IO.puts "I am collecting data!" + {:ok, "data retrieved", :no_state} + end + + def terminate(_reason, _state) do + :ok + end + + end + + """ + + @doc """ + A callback executed when the Collector Plugin starts. + The argument is retrieved from the configuration file when the Collector is defined + It must return `{:ok, state}`, that `state` will be keept as in `GenServer` and can be + retrieved in the `collect/1` function. + """ @callback init_collector(args :: term()) :: {:ok, any()} + @doc """ + In this callback is where the metrics collection logic must be placed. + It must return `{:ok, data, state}`. `data` is the retrieved metrics. + """ @callback collect(state :: any()) :: {:ok, data :: any(), state :: any()} + @doc """ + This callback is called just before the Process goes down. This is a good place for closing connections. + """ @callback terminate(state :: term()) :: term() + @doc false defmacro __using__(_opt) do quote do @behaviour POAAgent.Plugins.Collector @@ -19,7 +111,7 @@ defmodule POAAgent.Plugins.Collector do @doc false def init(state) do {:ok, internal_state} = init_collector(state[:args]) - set_collector_timer() + set_collector_timer(state.frequency) {:ok, Map.put(state, :internal_state, internal_state)} end @@ -32,7 +124,7 @@ defmodule POAAgent.Plugins.Collector do def handle_info(:collect, state) do {:ok, data, internal_state} = collect(state.internal_state) transfer(data, state.label, state.transfers) - set_collector_timer() + set_collector_timer(state.frequency) {:noreply, %{state | internal_state: internal_state}} end def handle_info(_msg, state) do @@ -61,8 +153,8 @@ defmodule POAAgent.Plugins.Collector do end @doc false - defp set_collector_timer() do - Process.send_after(self(), :collect, 5000) # TODO timeout must be configurable + defp set_collector_timer(frequency) do + Process.send_after(self(), :collect, frequency) end end diff --git a/lib/poa_agent/plugins/collectors/supervisor.ex b/lib/poa_agent/plugins/collectors/supervisor.ex index ce929b4..be0e381 100644 --- a/lib/poa_agent/plugins/collectors/supervisor.ex +++ b/lib/poa_agent/plugins/collectors/supervisor.ex @@ -12,8 +12,8 @@ defmodule POAAgent.Plugins.Collectors.Supervisor do collectors = Application.get_env(:poa_agent, :collectors) mappings = Application.get_env(:poa_agent, :mappings) - children = for {name, module, label, args} <- collectors do - worker(module, [%{name: name, transfers: mappings[name], label: label, args: args}]) + children = for {name, module, frequency, label, args} <- collectors do + worker(module, [%{name: name, transfers: mappings[name], frequency: frequency, label: label, args: args}]) end opts = [strategy: :one_for_one] diff --git a/lib/poa_agent/plugins/transfer.ex b/lib/poa_agent/plugins/transfer.ex index c4231f9..5b296bb 100644 --- a/lib/poa_agent/plugins/transfer.ex +++ b/lib/poa_agent/plugins/transfer.ex @@ -1,12 +1,87 @@ defmodule POAAgent.Plugins.Transfer do + @moduledoc """ + Defines a Transfer Plugin. + + A Transfer plugin receives data from Collectors. It uses the Collector's `label` in order to + differenciate from multiple Collectors. + + `POAAgent` app reads the Transfers configuration from the `config.exs` file when bootstrap and will create a + process per each one of them. That configuration is referenced by :transfers key. + + config :poa_agent, + :transfers, + [ + {name, module, args} + ] + + for example + + config :poa_agent, + :transfers, + [ + {:my_transfer, POAAgent.Plugins.Transfers.MyTransfer, [ws_key: "mykey", other_stuff: "hello"]} + ] + + `name`, `module` and `args` must be defined in the configuration file. + + - `name`: Name for the new created process. Must be unique + - `module`: Module which implements the Transfer behaviour + - `args`: Initial args which will be passed to the `init_transfer/1` function + + In order to implement your Transfer Plugin you must implement 3 functions. + + - `init_transfer/1`: Called only once when the process starts + - `data_received/2`: This function is called every time a Collector sends metrics to the Transfer + - `terminate/1`: Called just before stopping the process + + This is a simple example of custom Transfer Plugin + + defmodule POAAgent.Plugins.Transfers.MyTransfer do + use POAAgent.Plugins.Transfer + + def init_transfer(args) do + {:ok, :no_state} + end + + def data_received(label, data, state) do + IO.puts "Received data from the collector referenced by label" + {:ok, :no_state} + end + + def terminate(_reason, _state) do + :ok + end + + end + + """ + + @doc """ + A callback executed when the Transfer Plugin starts. + The argument is retrieved from the configuration file when the Transfer is defined + It must return `{:ok, state}`, that `state` will be keept as in `GenServer` and can be + retrieved in the `data_received/2` function. + """ @callback init_transfer(args :: term()) :: {:ok, any()} + @doc """ + In this callback is called when a Collector sends data to this Transfer. + + The data received is in `{label, data}` format where `label` identifies the Collector and the + data is the real data received. + + It must return `{:ok, state}`. + """ @callback data_received(label :: atom(), data :: any(), state :: any()) :: {:ok, any()} + @doc """ + This callback is called just before the Process goes down. This is a good place for closing connections. + """ @callback terminate(state :: term()) :: term() + @doc false defmacro __using__(_opt) do quote do @behaviour POAAgent.Plugins.Transfer diff --git a/mix.exs b/mix.exs index e667f06..e2e772b 100644 --- a/mix.exs +++ b/mix.exs @@ -1,13 +1,16 @@ defmodule POAAgent.MixProject do use Mix.Project + @version "0.1.0" + def project do [ app: :poa_agent, - version: "0.1.0", + version: @version, elixir: "~> 1.6", start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + docs: docs() ] end @@ -21,7 +24,25 @@ defmodule POAAgent.MixProject do defp deps do [ {:credo, "~> 0.9", only: [:dev, :test], runtime: false}, - {:dialyxir, "~> 0.5", only: [:dev], runtime: false} + {:dialyxir, "~> 0.5", only: [:dev], runtime: false}, + + # Docs + {:ex_doc, "~> 0.18", only: :dev, runtime: false} ] end + + defp docs do + [ + source_ref: "v#{@version}", + main: "POAAgent.Application", + source_url: "https://github.com/poanetwork/poa-netstats-agent", + groups_for_modules: [ + "Plugins": [ + POAAgent.Plugins.Collector, + POAAgent.Plugins.Transfer, + ] + ] + ] + end + end diff --git a/mix.lock b/mix.lock index 31fa6e1..64ca07f 100644 --- a/mix.lock +++ b/mix.lock @@ -2,5 +2,7 @@ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"}, "credo": {:hex, :credo, "0.9.2", "841d316612f568beb22ba310d816353dddf31c2d94aa488ae5a27bb53760d0bf", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"}, } diff --git a/test/plugins_test.exs b/test/plugins_test.exs index 2017f47..0985a3f 100644 --- a/test/plugins_test.exs +++ b/test/plugins_test.exs @@ -18,7 +18,7 @@ defmodule POAAgent.PluginsTest do end end - assert Collector1.init(%{}) == {:ok, %{internal_state: :no_state}} + assert Collector1.init(%{frequency: 5_000}) == {:ok, %{internal_state: :no_state, frequency: 5_000}} assert Collector1.handle_call(:msg, :from, :state) == {:noreply, :state} assert Collector1.handle_info(:msg, :state) == {:noreply, :state} assert Collector1.handle_cast(:msg, :state) == {:noreply, :state} @@ -92,7 +92,7 @@ defmodule POAAgent.PluginsTest do transfer1 = :transfer2 {:ok, tpid} = Transfer2.start_link(%{name: transfer1, args: self()}) - {:ok, cpid} = Collector2.start_link(%{name: :collector2, transfers: [transfer1], label: :label, args: self()}) + {:ok, cpid} = Collector2.start_link(%{name: :collector2, transfers: [transfer1], label: :label, args: self(), frequency: 2_000}) assert_receive {:sent, ^cpid, "data retrieved"}, 20_000 assert_receive {:received, ^tpid, :label, "data retrieved"}, 20_000