diff --git a/config/config.exs b/config/config.exs index 1c441ed..05c4e32 100644 --- a/config/config.exs +++ b/config/config.exs @@ -17,8 +17,8 @@ config :nostrum, config :plantid_discord_bot, PlantIdDiscordBot.Scheduler, jobs: [ {"@daily", {PlantIdDiscordBot.RateLimiter, :reset_counters, []}}, - {"@hourly", {PlantIdDiscordBot.Metrics, :write, []}}, - {"0 8 * * *", {PlantIdDiscordBot.Metrics.Message, :start, []}} + {"@hourly", {PlantIdDiscordBot.Metrics, :backup, []}}, + {"0 8 * * *", {PlantIdDiscordBot.Metrics.Message, :send, []}} ] import_config "#{config_env()}.exs" diff --git a/lib/application.ex b/lib/application.ex index 061f431..b4e68f9 100644 --- a/lib/application.ex +++ b/lib/application.ex @@ -7,7 +7,7 @@ defmodule PlantIdDiscordBot.Application do PlantIdDiscordBot.ProcessRegistry, PlantIdDiscordBot.Consumer, PlantIdDiscordBot.RateLimiter, - {PlantIdDiscordBot.Metrics, []}, + {PlantIdDiscordBot.Metrics.Requests, []}, PlantIdDiscordBot.Scheduler, {Plug.Cowboy, scheme: :http, diff --git a/lib/metrics.ex b/lib/metrics.ex index 34b88fb..948566f 100644 --- a/lib/metrics.ex +++ b/lib/metrics.ex @@ -1,101 +1,14 @@ defmodule PlantIdDiscordBot.Metrics do - use Agent - alias PlantIdDiscordBot.Metrics + alias PlantIdDiscordBot.Metrics.Requests - @file_path "priv/metrics" - - defstruct [ - :guild_id, - :guild_name, - :first_request_at, - :last_request_at, - :total_requests - ] - - def start_link(_opts) do - initial_state = - case File.exists?(Path.join(@file_path, "requests")) do - true -> read() - false -> %{} - end - - Agent.start_link(fn -> initial_state end, name: via_tuple(:metrics)) + def backup() do + Requests.write() end - defp via_tuple(key) do - PlantIdDiscordBot.ProcessRegistry.via_tuple({__MODULE__, key}) + def increase_request_count(guild_id, guild_name) do + Requests.put(guild_id, guild_name) end - defp get_pid do - [{pid, _value}] = Registry.lookup(PlantIdDiscordBot.ProcessRegistry, {__MODULE__, :metrics}) - pid - end - - def get(key) do - get_pid() - |> Agent.get(&Map.get(&1, key)) - end - - def get_all() do - get_pid() - |> Agent.get(& &1) - end - - def put(guild_id, guild_name) do - get_pid() - |> Agent.update(fn state -> - case Map.get(state, guild_id) do - nil -> - Map.put(state, guild_id, %Metrics{ - guild_id: guild_id, - guild_name: guild_name, - first_request_at: DateTime.utc_now(), - last_request_at: DateTime.utc_now(), - total_requests: 1 - }) - - existing -> - Map.put(state, guild_id, %Metrics{ - existing - | last_request_at: DateTime.utc_now(), - total_requests: existing.total_requests + 1 - }) - end - end) - end - - @doc """ - Reset metrics state and remove the saved file from disk. - """ - def reset() do - get_pid() - |> Agent.update(fn _ -> %{} end) - - file = Path.join(@file_path, "requests") - - if File.exists?(file) do - File.rm!(Path.join(@file_path, "requests")) - end - end - - @doc """ - Write the current state to disk. To be consumed by a CRON job. - """ - def write() do - File.mkdir_p!("priv/metrics") - - data = - get_all() - |> :erlang.term_to_binary() - - File.write!(Path.join(@file_path, "requests"), data) - end - - @doc """ - Read the state from disk. To be loaded into state on process start. - """ - def read() do - File.read!(Path.join(@file_path, "requests")) - |> :erlang.binary_to_term() - end + def requests(guild_id), do: Requests.get(guild_id) + def requests(), do: Requests.get_all() end diff --git a/lib/metrics/message.ex b/lib/metrics/message.ex index ddeb9e1..a46502c 100644 --- a/lib/metrics/message.ex +++ b/lib/metrics/message.ex @@ -1,11 +1,11 @@ defmodule PlantIdDiscordBot.Metrics.Message do - def start() do - PlantIdDiscordBot.Metrics.get_all() + def send() do + PlantIdDiscordBot.Metrics.requests() |> format_message() - |> send_message() + |> do_send_message() end - def format_message(data) do + defp format_message(data) do embeds = Enum.map(data, fn {guild_id, metrics} -> first_request_at = @@ -30,15 +30,10 @@ defmodule PlantIdDiscordBot.Metrics.Message do %{embeds: embeds} end - def send_message(data) do + defp do_send_message(data) do webhook_url = Application.get_env(:plantid_discord_bot, :metrics_webhook_url) - body = Jason.encode!(data) - - headers = [ - {"Content-Type", "application/json"} - ] - + headers = [{"Content-Type", "application/json"}] HTTPoison.post!(webhook_url, body, headers) end end diff --git a/lib/metrics/requests.ex b/lib/metrics/requests.ex new file mode 100644 index 0000000..a4789ee --- /dev/null +++ b/lib/metrics/requests.ex @@ -0,0 +1,104 @@ +defmodule PlantIdDiscordBot.Metrics.Requests do + use Agent + alias PlantIdDiscordBot.Metrics.Requests + alias PlantIdDiscordBot.ProcessRegistry + + @file_path "priv/metrics" + + defstruct [ + :guild_id, + :guild_name, + :first_request_at, + :last_request_at, + :total_requests + ] + + def start_link(_opts) do + initial_state = + case File.exists?(Path.join(@file_path, "requests")) do + true -> read() + false -> %{} + end + + Agent.start_link(fn -> initial_state end, name: via_tuple(:requests)) + end + + defp via_tuple(key) do + ProcessRegistry.via_tuple({__MODULE__, key}) + end + + defp get_pid do + [{pid, _value}] = Registry.lookup(ProcessRegistry, {__MODULE__, :requests}) + pid + end + + def get(key) do + get_pid() + |> Agent.get(&Map.get(&1, key)) + end + + def get_all() do + get_pid() + |> Agent.get(& &1) + end + + def put(guild_id, guild_name) do + get_pid() + |> Agent.update(fn state -> + case Map.get(state, guild_id) do + nil -> + request_time = DateTime.utc_now() + + Map.put(state, guild_id, %Requests{ + guild_id: guild_id, + guild_name: guild_name, + first_request_at: request_time, + last_request_at: request_time, + total_requests: 1 + }) + + existing -> + Map.put(state, guild_id, %Requests{ + existing + | last_request_at: DateTime.utc_now(), + total_requests: existing.total_requests + 1 + }) + end + end) + end + + @doc """ + Reset metrics state and remove the saved file from disk. + """ + def reset() do + get_pid() + |> Agent.update(fn _ -> %{} end) + + file = Path.join(@file_path, "requests") + + if File.exists?(file) do + File.rm!(Path.join(@file_path, "requests")) + end + end + + @doc """ + Write the current state to disk. To be consumed by a CRON job. + """ + def write() do + File.mkdir_p!("priv/metrics") + + data = + get_all() + |> :erlang.term_to_binary() + + File.write!(Path.join(@file_path, "requests"), data) + end + + @doc """ + Read the state from disk. To be loaded into state on process start. + """ + def read() do + File.read!(Path.join(@file_path, "requests")) + |> :erlang.binary_to_term() + end +end diff --git a/lib/plantid_discord_bot/cogs/plantnet.ex b/lib/plantid_discord_bot/cogs/plantnet.ex index 410ed7f..a8f0d72 100644 --- a/lib/plantid_discord_bot/cogs/plantnet.ex +++ b/lib/plantid_discord_bot/cogs/plantnet.ex @@ -117,7 +117,7 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do {:ok, %{name: guild_name}} = GuildCache.get(guild_id) RateLimiter.increase_counter(guild_id) - Metrics.put(guild_id, guild_name) + Metrics.increase_request_count(guild_id, guild_name) Api.create_followup_message(interaction.application_id, interaction.token, %{ content: response_message <> "\n#{original_images}" @@ -140,7 +140,7 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do {:ok, %{name: guild_name}} = GuildCache.get(guild_id) RateLimiter.increase_counter(guild_id) - Metrics.put(guild_id, guild_name) + Metrics.increase_request_count(guild_id, guild_name) Api.create_followup_message(interaction.application_id, interaction.token, %{ content: "Species Not Found" diff --git a/lib/plantid_discord_bot/cogs/plantnet_message.ex b/lib/plantid_discord_bot/cogs/plantnet_message.ex index f88b574..9dfbe6d 100644 --- a/lib/plantid_discord_bot/cogs/plantnet_message.ex +++ b/lib/plantid_discord_bot/cogs/plantnet_message.ex @@ -80,7 +80,7 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do response_message = Parser.parse(body) RateLimiter.increase_counter(guild_id) - Metrics.put(guild_id, guild_name) + Metrics.increase_request_count(guild_id, guild_name) # Nostrum.Api.create_message/2 is deprecated but the new function is not available in v0.10 of the library # Nostrum.Api.message/2 will be the new function @@ -99,7 +99,7 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do {:ok, %HTTPoison.Response{status_code: 404}} -> RateLimiter.increase_counter(guild_id) - Metrics.put(guild_id, guild_name) + Metrics.increase_request_count(guild_id, guild_name) Api.create_message(message.channel_id, content: "Species Not Found",