state machine with requests per guild

This commit is contained in:
Owen
2025-01-09 14:44:22 +01:00
parent 0b656b4d24
commit c7d47ee6ed
5 changed files with 162 additions and 0 deletions
+4
View File
@@ -1,3 +1,7 @@
**0.1.3**
- Typo corrections in README.md
**0.1.2** **0.1.2**
- bug fixes around `Threat Status`, choosing to not display when no IUCU data is available - bug fixes around `Threat Status`, choosing to not display when no IUCU data is available
+2
View File
@@ -4,8 +4,10 @@ defmodule PlantIdDiscordBot.Application do
@impl true @impl true
def start(_type, _args) do def start(_type, _args) do
children = [ children = [
PlantIdDiscordBot.ProcessRegistry,
PlantIdDiscordBot.Consumer, PlantIdDiscordBot.Consumer,
PlantIdDiscordBot.RateLimiter, PlantIdDiscordBot.RateLimiter,
{PlantIdDiscordBot.Metrics, []},
PlantIdDiscordBot.Scheduler, PlantIdDiscordBot.Scheduler,
{Plug.Cowboy, {Plug.Cowboy,
scheme: :http, scheme: :http,
+96
View File
@@ -0,0 +1,96 @@
defmodule PlantIdDiscordBot.Metrics do
use Agent
alias PlantIdDiscordBot.Metrics
@file_path "priv/metrics"
defstruct [
:guild_id,
:guild_name,
: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))
end
defp via_tuple(key) do
PlantIdDiscordBot.ProcessRegistry.via_tuple({__MODULE__, key})
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,
total_requests: 1
})
existing ->
Map.put(state, guild_id, %Metrics{
existing
| 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
@@ -0,0 +1,17 @@
defmodule PlantIdDiscordBot.ProcessRegistry do
def start_link do
Registry.start_link(keys: :unique, name: __MODULE__)
end
def via_tuple(key) do
{:via, Registry, {__MODULE__, key}}
end
def child_spec(_) do
Supervisor.child_spec(
Registry,
id: __MODULE__,
start: {__MODULE__, :start_link, []}
)
end
end
+43
View File
@@ -0,0 +1,43 @@
defmodule PlantIdDiscordBot.MetricsTest do
use ExUnit.Case
doctest PlantIdDiscordBot.Metrics
test "put multiple entries into state" do
PlantIdDiscordBot.Metrics.reset()
PlantIdDiscordBot.Metrics.put(123, "Test Guild 1")
PlantIdDiscordBot.Metrics.put(321, "Test Guild 2")
keys =
PlantIdDiscordBot.Metrics.get_all()
|> Map.keys()
assert length(keys) == 2
assert %PlantIdDiscordBot.Metrics{
guild_id: 123,
guild_name: "Test Guild 1",
total_requests: 1
} ==
PlantIdDiscordBot.Metrics.get(123)
assert %PlantIdDiscordBot.Metrics{
guild_id: 321,
guild_name: "Test Guild 2",
total_requests: 1
} ==
PlantIdDiscordBot.Metrics.get(321)
end
test "update requests for a guild" do
PlantIdDiscordBot.Metrics.reset()
PlantIdDiscordBot.Metrics.put(123, "Test Guild 1")
PlantIdDiscordBot.Metrics.put(123, "Test Guild 1")
assert %PlantIdDiscordBot.Metrics{
guild_id: 123,
guild_name: "Test Guild 1",
total_requests: 2
} ==
PlantIdDiscordBot.Metrics.get(123)
end
end