Concurrent and simple. No blocking calls or bottlenecks. Increments are atomic.
Throughput.start_link()
Throughput.increment()
Throughput.get_throughput_per_second()
Throughput.get_max_throughput_per_second()
Concurrent and simple. No blocking calls or bottlenecks. Increments are atomic.
Throughput.start_link()
Throughput.increment()
Throughput.get_throughput_per_second()
Throughput.get_max_throughput_per_second()
defmodule Throughput do | |
@moduledoc """ | |
Simple ETS-based store for keeping track of throughput for stuff | |
""" | |
use GenServer | |
require Logger | |
@table_name :throughput_stats | |
@throughput_calc_every 5_000 # calc throughput every 5 seconds | |
@doc """ | |
Start up the process. | |
""" | |
def start_link(_), do: start_link() | |
def start_link, do: GenServer.start_link(__MODULE__, :ok, name: __MODULE__) | |
@doc """ | |
Increment the number of things processed | |
""" | |
def increment, do: :ets.update_counter(@table_name, :processed, {2, 1}, {:processed, 0}) | |
@doc """ | |
Returns the latest calculated throughput per second | |
""" | |
def get_throughput_per_second, do: get_value(:throughput_per_second) | |
@doc """ | |
Returns the max calculated throughput per second | |
""" | |
def get_max_throughput_per_second, do: get_value(:max_throughput_per_second) | |
@doc false | |
def init(_) do | |
# create new ets table for handling counts | |
:ets.new(@table_name, [:set, :named_table, :public, write_concurrency: true]) | |
Process.send_after(self(), :update_throughput, @throughput_calc_every) | |
{:ok, %{}} | |
end | |
@doc false | |
def handle_info(:update_throughput, state) do | |
# get the processed count and reset to zero | |
count = get_value(:processed) | |
:ets.insert(@table_name, {:processed, 0}) | |
(count / 5.0) | |
|> Float.round(2) | |
|> set_throughput_per_second() | |
Process.send_after(self(), :update_throughput, @throughput_calc_every) | |
{:noreply, state} | |
end | |
# generic get for ets | |
defp get_value(key_name) do | |
case :ets.lookup(@table_name, key_name) do | |
[{_, count}|_] -> count | |
_ -> 0 | |
end | |
end | |
# set the throughput per second | |
defp set_throughput_per_second(throughput) do | |
:ets.insert(@table_name, {:throughput_per_second, throughput}) | |
set_max_throughput_per_second(throughput) | |
end | |
# only bother if the throughput was greater than zero | |
defp set_max_throughput_per_second(current_throughput) when current_throughput > 0 do | |
persist_max_throughput_per_second(current_throughput, get_max_throughput_per_second()) | |
end | |
defp set_max_throughput_per_second(_current_throughput), do: nil | |
# only persist max throughput if we exceeded the existing max value | |
defp persist_max_throughput_per_second(current_throughput, max_throughput) when current_throughput > max_throughput do | |
:ets.insert(@table_name, {:max_throughput_per_second, current_throughput}) | |
end | |
defp persist_max_throughput_per_second(_current_throughput, _max_throughput), do: nil | |
end |