defmodule SimpleStatEx.Server.SimpleStatSet do @moduledoc """ A service that holds statistic counters mapped by time period in memory. """ use GenServer alias SimpleStatEx.{SimpleStat, SimpleStatQuery} @doc """ Start with a name that was generated by simple stat protocol """ def start_link(name) do GenServer.start_link(__MODULE__, :ok, [name: name]) end def init(:ok) do {:ok, %{}} end # Client ########################## @doc """ Add a statistic to the state """ def add_stat(category_bucket, %SimpleStat{} = simple_stat) do GenServer.cast(category_bucket, {:add, simple_stat}) category_bucket end @doc """ Select stats from state based on query parameters """ def query_stats(category_bucket, %SimpleStat{} = simple_stat, %SimpleStatQuery{} = simple_stat_query) do result = GenServer.call(category_bucket, {:query, simple_stat, simple_stat_query}) {:ok, result} end def query_stats!(category_bucket, %SimpleStat{} = simple_stat, %SimpleStatQuery{} = simple_stat_query) do {:ok, result} = query_stats(category_bucket, simple_stat, simple_stat_query) result end @doc """ Reset the stat bucket """ def reset(category_bucket) do GenServer.cast(category_bucket, :reset) end def get_stats(category_bucket, period \\ :all) do result = GenServer.call(category_bucket, {:retrieve_all, period}) {:ok, result} end def get_stats!(category_bucket) do {:ok, result} = get_stats(category_bucket) result end # Server ########################### def handle_cast({:add, %SimpleStat{period: period} = simple_stat}, category_state) do # Get the list of stats in the current period {status, stat_period_list} = get_stat_period(category_state, period) updated_category_state = case status do :new_period -> Map.put(category_state, period, []) :ok -> category_state end new_stat_period_list = update_stat_period_list(stat_period_list, simple_stat) {:noreply, %{updated_category_state | period => new_stat_period_list}} end def handle_cast(:reset, _) do {:noreply, %{}} end def handle_call({:retrieve_all, period}, category_state) do stats = case period do :all -> Enum.flat_map(category_state, fn {_, period_stats} -> period_stats end) period_queried -> Map.get(category_state, period_queried) end {:reply, stats, category_state} end def handle_call({:query, %SimpleStat{period: period}, %SimpleStatQuery{limit: limit, offset: offset}}, _from, category_state) do simple_stat_set = if Map.has_key?(category_state, period) do Enum.slice(Map.get(category_state, period), offset, limit) else [] end {:reply, simple_stat_set, category_state} end def handle_info(_, state), do: {:noreply, state} # Helper defp get_stat_period(category_state, period) do if Map.has_key?(category_state, period) do {:ok, Map.get(category_state, period)} else {:new_period, []} end end defp update_stat_period_list(stat_period_list, %SimpleStat{count: count, time: time} = simple_stat) do case stat_period_list do [] -> [simple_stat] [%SimpleStat{time: latest_time, count: period_count} = latest_simple_stat|tail] -> cond do latest_time == time -> [%{latest_simple_stat | :count => period_count + count, :updated_at => Timex.now()}|tail] true -> [%{simple_stat | :updated_at => Timex.now()}, latest_simple_stat|tail] end end end end