From e58d33243abbdb0caca7f1f2e77d52623ca33a56 Mon Sep 17 00:00:00 2001 From: Connor Rigby Date: Mon, 21 Oct 2019 13:46:07 -0700 Subject: [PATCH] Simplify telemetry systems --- farmbot_telemetry/lib/farmbot_telemetry.ex | 200 ++++++++++++------ .../lib/farmbot_telemetry/application.ex | 19 +- .../lib/farmbot_telemetry/event_class.ex | 14 -- .../event_classes/amqp_event_class.ex | 28 --- .../event_classes/dns_event_class.ex | 22 -- .../event_classes/http_event_class.ex | 36 ---- .../event_classes/network_event_class.ex | 18 -- .../lib/farmbot_telemetry/log_handler.ex | 9 - .../test/farmbot_telemetry_test.exs | 50 ----- 9 files changed, 131 insertions(+), 265 deletions(-) delete mode 100644 farmbot_telemetry/lib/farmbot_telemetry/event_class.ex delete mode 100644 farmbot_telemetry/lib/farmbot_telemetry/event_classes/amqp_event_class.ex delete mode 100644 farmbot_telemetry/lib/farmbot_telemetry/event_classes/dns_event_class.ex delete mode 100644 farmbot_telemetry/lib/farmbot_telemetry/event_classes/http_event_class.ex delete mode 100644 farmbot_telemetry/lib/farmbot_telemetry/event_classes/network_event_class.ex delete mode 100644 farmbot_telemetry/lib/farmbot_telemetry/log_handler.ex diff --git a/farmbot_telemetry/lib/farmbot_telemetry.ex b/farmbot_telemetry/lib/farmbot_telemetry.ex index 29e19ed9..eb51ab2b 100644 --- a/farmbot_telemetry/lib/farmbot_telemetry.ex +++ b/farmbot_telemetry/lib/farmbot_telemetry.ex @@ -1,94 +1,154 @@ defmodule FarmbotTelemetry do @moduledoc """ - Interface for farmbot system introspection + Interface for farmbot system introspection and metrics """ - alias FarmbotTelemetry.EventClass - @typedoc "Module that implements the FarmbotTelemetry.EventClass behaviour" - @type class() :: module() + @typedoc "Classification of telemetry event" + @type class() :: event_class() | metric_class() - @typedoc "First argument to the handler" - @type event() :: nonempty_maybe_improper_list(class(), EventClass.type()) + @typedoc "Event classes are events that have no measurable value" + @type event_class() :: atom() - @typedoc "Second argument to the handler" - @type data() :: %{ - required(:action) => EventClass.action(), + @typedoc "Metric classes are events that have a measurable value" + @type metric_class() :: atom() + + @typedoc "Type within an event" + @type event_type() :: atom() + + @typedoc "Action withing a type" + @type event_action() :: atom() + + @typedoc "Value of a metric event" + @type metric_value() :: term() + + @typedoc """ + 1st arg passed to a `handler` if the type was an event + [:farmbot_telemetry, :event, t/event_class()] + """ + @type event_class_path() :: [atom()] + + @typedoc "2nd arg passed to a `handler` if the type was an event" + @type event_class_data() :: %{ + required(:type) => event_type(), + required(:action) => event_action(), required(:timestamp) => DateTime.t() } - @type meta() :: map() + @typedoc """ + 1st arg passed to a `handler` if the type was a metric + [:farmbot_telemetry, :metric, t/metric_class()] + """ + @type metric_class_path() :: [atom()] - @typedoc "Term that will be passed to the handler." - @type config() :: any() + @typedoc "2nd arg passed to a `handler` if the type was a metric" + @type metric_class_data() :: %{ + required(:value) => metric_value(), + required(:timestamp) => DateTime.t() + } - @typedoc "Module that implements the " - @type handler() :: (event(), data(), meta(), config() -> any()) + @typedoc "3rd arg passed to a `handler`" + @type meta() :: %{ + required(:module) => module() | nil, + required(:file) => Path.t() | nil, + required(:line) => pos_integer() | nil, + required(:function) => {atom, 0 | pos_integer()} | nil + } - @typedoc "required options for FarmbotTelemetry" - @type opt() :: - {:class, class()} - | {:handler, handler()} - | {:config, config()} + @typedoc "4th arg passed to a `handler`" + @type config() :: term() - @typedoc false - @type opts :: [opts] + @typedoc "Function that handles telemetry data" + @type handler() :: + (event_class_path(), event_class_data(), meta(), config() -> any()) + | (metric_class_path(), metric_class_data(), meta(), config() -> any()) - @doc false - @spec child_spec(opts) :: Supervisor.child_spec() - def child_spec(opts) do - %{ - id: opts[:class], - start: {__MODULE__, :attach, [opts]} - } - end + @doc "Execute a telemetry event" + defmacro event(class, type, action, meta \\ %{}) do + meta = + Map.merge(meta, %{ + module: __ENV__.module, + file: __ENV__.file, + line: __ENV__.line, + function: __ENV__.function + }) - @doc false - @spec attach(opts) :: GenServer.on_start() - def attach(opts) do - class = opts[:class] - handler_id = opts[:handler_id] - handler = opts[:handler] - config = opts[:config] - - for {type, _actions} <- class.matrix() do - _ = :telemetry.attach(handler_id, [class, type], handler, config) - end - - :ignore - end - - @doc false - defmacro __using__(_opts) do - quote do - alias FarmbotTelemetry.{ - AMQPEventClass, - DNSEventClass, - HTTPEventClass, - NetworkEventClass - }, - warn: false - - require FarmbotTelemetry + quote location: :keep do + :telemetry.execute( + [:farmbot_telemetry, :event, unquote(class)], + %{type: unquote(type), action: unquote(action), timestamp: DateTime.utc_now()}, + unquote(Macro.escape(meta)) + ) end end - @doc "Execute a telemetry event. `type` and `action` will be validated" - def execute(class, type, action, meta \\ %{}) do - :telemetry.execute([class, type], %{action: action, timestamp: DateTime.utc_now()}, meta) - # quote location: :keep, - # bind_quoted: [class: class, type: type, action: action, meta: Macro.escape(meta)] do - # # unless {type, action} in class.matrix() do - # # raise """ - # # #{type}.#{action} is unknown for #{class} - # # """ - # # end + @doc "Execute a telemetry metric" + defmacro metric(class, value, meta \\ %{}) do + meta = + Map.merge(meta, %{ + module: __ENV__.module, + file: __ENV__.file, + line: __ENV__.line, + function: __ENV__.function + }) - # _ = FarmbotTelemetry.unsafe_execute(class, type, action, meta) - # end + quote location: :keep do + :telemetry.execute( + [:farmbot_telemetry, :metric, unquote(class)], + %{value: unquote(value), timestamp: DateTime.utc_now()}, + unquote(Macro.escape(meta)) + ) + end + end + + @doc "Attach a handler to an event" + @spec attach(String.t(), event_class_path() | metric_class_path(), handler(), config()) :: any() + def attach(handler_id, event, handler, config \\ []) do + :telemetry.attach(handler_id, event, handler, config) + end + + @doc "Helper to attach the log handler to an event" + @spec attach_logger(event_class_path() | metric_class_path(), config()) :: any() + def attach_logger(event, config \\ [level: :info]) do + attach( + "logger.#{:erlang.phash2({node(), :erlang.now()})}", + event, + &FarmbotTelemetry.log_handler/4, + config + ) + end + + @doc "Helper to send a message to the current processes when a matching event is dispatched" + @spec attach_recv(event_class_path() | metric_class_path(), config()) :: any() + def attach_recv(event, config \\ [pid: self()]) do + attach( + "recv.#{:erlang.phash2({node(), :erlang.now()})}", + event, + &Kernel.send(&4[:pid], {&1, &2, &3, &4}), + config + ) end @doc false - def unsafe_execute(class, type, action, meta \\ %{}) do - :telemetry.execute([class, type], %{action: action, timestamp: DateTime.utc_now()}, meta) + def log_handler(event, data, meta, config) do + msg = + case event do + [:farmbot_telemetry, :event, class] -> + %{type: type, action: action} = data + "#{class}.#{type}.#{action}" + + [:farmbot_telemetry, :metric, class] -> + %{value: value} = data + "#{class}.#{value}=#{value}" + end + + Logger.bare_log(config[:level] || :debug, msg, Map.to_list(meta)) end + + @doc "Helper to generate a path for event names" + @spec event_class(event_class()) :: event_class_path() + def event_class(class), do: [:farmbot_telemetry, :event, class] + + @doc "Helper to generate a path for metric names" + @spec metric_class(metric_class()) :: metric_class_path() + def metric_class(class), do: [:farmbot_telemetry, :metric, class] end diff --git a/farmbot_telemetry/lib/farmbot_telemetry/application.ex b/farmbot_telemetry/lib/farmbot_telemetry/application.ex index 0af78b83..60eed28b 100644 --- a/farmbot_telemetry/lib/farmbot_telemetry/application.ex +++ b/farmbot_telemetry/lib/farmbot_telemetry/application.ex @@ -2,28 +2,11 @@ defmodule FarmbotTelemetry.Application do # See https://hexdocs.pm/elixir/Application.html # for more information on OTP Applications @moduledoc false - alias FarmbotTelemetry.LogHandler - - alias FarmbotTelemetry.{ - AMQPEventClass, - DNSEventClass, - HTTPEventClass, - NetworkEventClass - } use Application def start(_type, _args) do - children = - for class <- [AMQPEventClass, DNSEventClass, HTTPEventClass, NetworkEventClass] do - {FarmbotTelemetry, - [ - class: class, - handler_id: "#{class}-LogHandler", - handler: &LogHandler.handle_event/4, - config: [level: :info] - ]} - end + children = [] # See https://hexdocs.pm/elixir/Supervisor.html # for other strategies and supported options diff --git a/farmbot_telemetry/lib/farmbot_telemetry/event_class.ex b/farmbot_telemetry/lib/farmbot_telemetry/event_class.ex deleted file mode 100644 index 83f9fbe9..00000000 --- a/farmbot_telemetry/lib/farmbot_telemetry/event_class.ex +++ /dev/null @@ -1,14 +0,0 @@ -defprotocol FarmbotTelemetry.EventClass do - @moduledoc """ - Classificaiton of a telemetry event - """ - - @typedoc "Type of event in relation to the class" - @type type() :: atom() - - @typedoc "Action in relation to the type" - @type action() :: atom() - - @doc "mapping of `type` to `action`" - @callback matrix() :: [{type(), action()}] -end diff --git a/farmbot_telemetry/lib/farmbot_telemetry/event_classes/amqp_event_class.ex b/farmbot_telemetry/lib/farmbot_telemetry/event_classes/amqp_event_class.ex deleted file mode 100644 index f2ca9af0..00000000 --- a/farmbot_telemetry/lib/farmbot_telemetry/event_classes/amqp_event_class.ex +++ /dev/null @@ -1,28 +0,0 @@ -defmodule FarmbotTelemetry.AMQPEventClass do - @moduledoc """ - Classification of events pertaining to amqp channels including: - - * channel connections - * channel disconnects - * channel errors - """ - - @behaviour FarmbotTelemetry.EventClass - - @impl FarmbotTelemetry.EventClass - def matrix() do - channel_list = [ - :auto_sync, - :celery_script, - :bot_state, - :logs, - :nerves_hub - ] - - [ - channel_connect: channel_list, - channel_disconnect: channel_list, - channel_error: channel_list - ] - end -end diff --git a/farmbot_telemetry/lib/farmbot_telemetry/event_classes/dns_event_class.ex b/farmbot_telemetry/lib/farmbot_telemetry/event_classes/dns_event_class.ex deleted file mode 100644 index 463c4358..00000000 --- a/farmbot_telemetry/lib/farmbot_telemetry/event_classes/dns_event_class.ex +++ /dev/null @@ -1,22 +0,0 @@ -defmodule FarmbotTelemetry.DNSEventClass do - @moduledoc """ - Classification of events pertaining to dns resolution accross the various - networked systems in the application including: - - * ntp - * Farmbot http token fetching - * Farmbot http rest interface - * Farmbot AMQP interface - """ - - @behaviour FarmbotTelemetry.EventClass - - @impl FarmbotTelemetry.EventClass - def matrix(), - do: [ - ntp: [:nxdomain], - http: [:nxdomain], - token: [:nxdomain], - amqp: [:nxdomain] - ] -end diff --git a/farmbot_telemetry/lib/farmbot_telemetry/event_classes/http_event_class.ex b/farmbot_telemetry/lib/farmbot_telemetry/event_classes/http_event_class.ex deleted file mode 100644 index c5ba3439..00000000 --- a/farmbot_telemetry/lib/farmbot_telemetry/event_classes/http_event_class.ex +++ /dev/null @@ -1,36 +0,0 @@ -defmodule FarmbotTelemetry.HTTPEventClass do - @moduledoc """ - Classification of events pertaining to the Farmbot REST interface including: - - * sequences - * farm events - * regimens - * etc - """ - - @behaviour FarmbotTelemetry.EventClass - - @impl FarmbotTelemetry.EventClass - def matrix() do - [ - farm_events: [:http_error, :http_timeout], - farmware_installations: [:http_error, :http_timeout], - regimens: [:http_error, :http_timeout], - device: [:http_error, :http_timeout], - diagnostic_dumps: [:http_error, :http_timeout], - farm_events: [:http_error, :http_timeout], - farmware_envs: [:http_error, :http_timeout], - fbos_config: [:http_error, :http_timeout], - firmware_config: [:http_error, :http_timeout], - peripherals: [:http_error, :http_timeout], - pin_bindings: [:http_error, :http_timeout], - points: [:http_error, :http_timeout], - public_keys: [:http_error, :http_timeout], - sensor_readings: [:http_error, :http_timeout], - sensors: [:http_error, :http_timeout], - sequences: [:http_error, :http_timeout], - sync: [:http_error, :http_timeout], - tools: [:http_error, :http_timeout] - ] - end -end diff --git a/farmbot_telemetry/lib/farmbot_telemetry/event_classes/network_event_class.ex b/farmbot_telemetry/lib/farmbot_telemetry/event_classes/network_event_class.ex deleted file mode 100644 index bd70e184..00000000 --- a/farmbot_telemetry/lib/farmbot_telemetry/event_classes/network_event_class.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule FarmbotTelemetry.NetworkEventClass do - @moduledoc """ - Classification of events pertaining to a network interface, not network - software errors. This includes: - - * WiFi errors - * ip address configuration errors - """ - - @behaviour FarmbotTelemetry.EventClass - - @impl FarmbotTelemetry.EventClass - def matrix(), - do: [ - access_point: [:disconnect, :connect, :eap_error, :assosiate_error, :assosiate_timeout], - ip_address: [:dhcp_lease, :dhcp_renew, :dhcp_lease_fail, :dhcp_renew_fail] - ] -end diff --git a/farmbot_telemetry/lib/farmbot_telemetry/log_handler.ex b/farmbot_telemetry/lib/farmbot_telemetry/log_handler.ex deleted file mode 100644 index e1e3c907..00000000 --- a/farmbot_telemetry/lib/farmbot_telemetry/log_handler.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule FarmbotTelemetry.LogHandler do - @moduledoc false - require Logger - - def handle_event([class, type], %{action: action, timestamp: _timestamp}, meta, config) do - message = "#{class}.#{type}.#{action}" - Logger.bare_log(config[:level], message, Map.to_list(meta)) - end -end diff --git a/farmbot_telemetry/test/farmbot_telemetry_test.exs b/farmbot_telemetry/test/farmbot_telemetry_test.exs index e0da2af8..1e87cd83 100644 --- a/farmbot_telemetry/test/farmbot_telemetry_test.exs +++ b/farmbot_telemetry/test/farmbot_telemetry_test.exs @@ -1,54 +1,4 @@ defmodule FarmbotTelemetryTest do use ExUnit.Case doctest FarmbotTelemetry - use FarmbotTelemetry - - defmodule TestHandler do - def handle_event([class, type], %{action: action}, meta, config) do - send(config[:test_pid], {class, type, action, meta, config}) - end - end - - describe "network" do - setup do - opts = [ - class: NetworkEventClass, - handler_id: "#{inspect(self())}", - handler: &TestHandler.handle_event/4, - config: [test_pid: self()] - ] - - :ignore = FarmbotTelemetry.attach(opts) - :ok - end - - test "access_point.disconnect" do - FarmbotTelemetry.execute(NetworkEventClass, :access_point, :disconnect, %{ssid: "test"}) - assert_receive {NetworkEventClass, :access_point, :disconnect, %{ssid: "test"}, _} - end - - test "access_point.connect" do - FarmbotTelemetry.execute(NetworkEventClass, :access_point, :connect, %{ssid: "test"}) - assert_receive {NetworkEventClass, :access_point, :connect, %{ssid: "test"}, _} - end - - test "access_point.eap_error" do - FarmbotTelemetry.execute(NetworkEventClass, :access_point, :eap_error, %{ssid: "test"}) - assert_receive {NetworkEventClass, :access_point, :eap_error, %{ssid: "test"}, _} - end - - test "access_point.assosiate_error" do - FarmbotTelemetry.execute(NetworkEventClass, :access_point, :assosiate_error, %{ssid: "test"}) - - assert_receive {NetworkEventClass, :access_point, :assosiate_error, %{ssid: "test"}, _} - end - - test "access_point.assosiate_timeout" do - FarmbotTelemetry.execute(NetworkEventClass, :access_point, :assosiate_timeout, %{ - ssid: "test" - }) - - assert_receive {NetworkEventClass, :access_point, :assosiate_timeout, %{ssid: "test"}, _} - end - end end