|
|
|
@ -26,12 +26,14 @@ defmodule EthereumJSONRPC.RollingWindow do |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def init(opts) do |
|
|
|
|
table_name = Keyword.fetch!(opts, :bucket) |
|
|
|
|
table_name = Keyword.fetch!(opts, :table) |
|
|
|
|
window_length = Keyword.fetch!(opts, :window_length) |
|
|
|
|
window_count = Keyword.fetch!(opts, :window_count) |
|
|
|
|
|
|
|
|
|
table = :ets.new(table_name, [:named_table, :set, :public, read_concurrency: true, write_concurrency: true]) |
|
|
|
|
|
|
|
|
|
# TODO: Calculate the match spec for the given window count here, and store it in state |
|
|
|
|
|
|
|
|
|
state = %{ |
|
|
|
|
table: table, |
|
|
|
|
window_length: window_length, |
|
|
|
@ -43,41 +45,9 @@ defmodule EthereumJSONRPC.RollingWindow do |
|
|
|
|
{:ok, state} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def handle_call({:count, key}, _from, %{table: table} = state) do |
|
|
|
|
count = |
|
|
|
|
case :ets.lookup(table, key) do |
|
|
|
|
[windows] -> windows |> Tuple.to_list() |> tl() |> Enum.sum() |
|
|
|
|
_ -> 0 |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
{:reply, count, state} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def handle_call({:inspect, key}, _from, %{table: table, window_count: window_count} = state) do |
|
|
|
|
windows = |
|
|
|
|
case :ets.lookup(table, key) do |
|
|
|
|
[windows] -> |
|
|
|
|
windows |> Tuple.to_list() |> tl() |
|
|
|
|
|
|
|
|
|
_ -> |
|
|
|
|
List.duplicate(0, window_count) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
{:reply, windows, state} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def handle_cast({:inc, key}, %{table: table, window_count: window_count} = state) do |
|
|
|
|
windows = List.duplicate(0, window_count) |
|
|
|
|
default = List.to_tuple([key | windows]) |
|
|
|
|
|
|
|
|
|
:ets.update_counter(table, key, {2, 1}, default) |
|
|
|
|
# TODO consider broadcasting to indexers than some threshold has been met with result of updating the counter |
|
|
|
|
|
|
|
|
|
{:noreply, state} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def handle_info(:sweep, %{window_count: window_count, table: table, window_length: window_length} = state) do |
|
|
|
|
Logger.debug(fn -> "Sweeping windows" end) |
|
|
|
|
# TODO consider broadcasting to indexers than some threshold has been met with result of updating the counter |
|
|
|
|
|
|
|
|
|
# Delete any rows wheree all windows empty |
|
|
|
|
delete_match_spec = delete_match_spec(window_count) |
|
|
|
@ -94,39 +64,70 @@ defmodule EthereumJSONRPC.RollingWindow do |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp match_spec(window_count) do |
|
|
|
|
# This match spec represents this function: |
|
|
|
|
# |
|
|
|
|
# :ets.fun2ms(fn |
|
|
|
|
# {key, n, [a, b, _]} -> |
|
|
|
|
# {key, 0, [n, a, b]} |
|
|
|
|
# |
|
|
|
|
# {key, n, windows} -> |
|
|
|
|
# {key, 0, [n | windows]} |
|
|
|
|
# end) |
|
|
|
|
# |
|
|
|
|
# This function is an example for when window size is 3. The match spec |
|
|
|
|
# matches on all but the last element of the list |
|
|
|
|
|
|
|
|
|
[ |
|
|
|
|
{ |
|
|
|
|
match_spec_matcher(window_count), |
|
|
|
|
full_windows_match_spec_matcher(window_count), |
|
|
|
|
[], |
|
|
|
|
match_spec_mapper(window_count) |
|
|
|
|
full_windows_match_spec_mapper(window_count) |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
partial_windows_match_spec_matcher(), |
|
|
|
|
[], |
|
|
|
|
partial_windows_match_spec_mapper() |
|
|
|
|
} |
|
|
|
|
] |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp match_spec_matcher(window_count) do |
|
|
|
|
range = Range.new(1, window_count + 1) |
|
|
|
|
defp full_windows_match_spec_matcher(1) do |
|
|
|
|
{:"$1", :"$2", []} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
range |
|
|
|
|
defp full_windows_match_spec_matcher(window_count) do |
|
|
|
|
windows = |
|
|
|
|
3 |
|
|
|
|
|> Range.new(window_count) |
|
|
|
|
|> Enum.map(&:"$#{&1}") |
|
|
|
|
|> List.to_tuple() |
|
|
|
|
end |
|
|
|
|
|> Kernel.++([:_]) |
|
|
|
|
|
|
|
|
|
defp delete_match_spec(window_count) do |
|
|
|
|
List.to_tuple([:"$1" | List.duplicate(0, window_count)]) |
|
|
|
|
{:"$1", :"$2", windows} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp match_spec_mapper(1) do |
|
|
|
|
[{{:"$1", 0}}] |
|
|
|
|
defp full_windows_match_spec_mapper(1) do |
|
|
|
|
[{{:"$1", 0, []}}] |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp match_spec_mapper(window_count) do |
|
|
|
|
inner_tuple = |
|
|
|
|
1..window_count |
|
|
|
|
defp full_windows_match_spec_mapper(window_count) do |
|
|
|
|
windows = |
|
|
|
|
3 |
|
|
|
|
|> Range.new(window_count) |
|
|
|
|
|> Enum.map(&:"$#{&1}") |
|
|
|
|
|> List.to_tuple() |
|
|
|
|
|> Tuple.insert_at(1, 0) |
|
|
|
|
|
|
|
|
|
[{inner_tuple}] |
|
|
|
|
[{{:"$1", 0, [:"$2" | windows]}}] |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp partial_windows_match_spec_matcher do |
|
|
|
|
{:"$1", :"$2", :"$3"} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp partial_windows_match_spec_mapper do |
|
|
|
|
[{{:"$1", 0, [:"$2" | :"$3"]}}] |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp delete_match_spec(window_count) do |
|
|
|
|
{:"$1", 0, List.duplicate(0, window_count - 1)} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp schedule_sweep(window_length) do |
|
|
|
@ -136,26 +137,37 @@ defmodule EthereumJSONRPC.RollingWindow do |
|
|
|
|
@doc """ |
|
|
|
|
Increment the count of events in the current window |
|
|
|
|
""" |
|
|
|
|
@spec inc(GenServer.server(), key :: term()) :: :ok |
|
|
|
|
def inc(server, key) do |
|
|
|
|
# Consider requiring the bucket and key to be passed in here |
|
|
|
|
# so that this and count/2 do not need to call to the server |
|
|
|
|
GenServer.cast(server, {:inc, key}) |
|
|
|
|
@spec inc(table :: atom, key :: term()) :: :ok |
|
|
|
|
def inc(table, key) do |
|
|
|
|
default = {key, 0, []} |
|
|
|
|
|
|
|
|
|
:ets.update_counter(table, key, {2, 1}, default) |
|
|
|
|
|
|
|
|
|
:ok |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
@doc """ |
|
|
|
|
Count all events in all windows |
|
|
|
|
""" |
|
|
|
|
@spec count(GenServer.server(), key :: term()) :: non_neg_integer() |
|
|
|
|
def count(server, key) do |
|
|
|
|
GenServer.call(server, {:count, key}) |
|
|
|
|
@spec count(table :: atom, key :: term()) :: non_neg_integer() |
|
|
|
|
def count(table, key) do |
|
|
|
|
case :ets.lookup(table, key) do |
|
|
|
|
[{_, current_window, windows}] -> current_window + Enum.sum(windows) |
|
|
|
|
_ -> 0 |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
@doc """ |
|
|
|
|
Display the raw contents of all windows for a given key |
|
|
|
|
""" |
|
|
|
|
@spec inspect(GenServer.server(), key :: term()) :: nonempty_list(non_neg_integer) |
|
|
|
|
def inspect(server, key) do |
|
|
|
|
GenServer.call(server, {:inspect, key}) |
|
|
|
|
@spec inspect(table :: atom, key :: term()) :: nonempty_list(non_neg_integer) |
|
|
|
|
def inspect(table, key) do |
|
|
|
|
case :ets.lookup(table, key) do |
|
|
|
|
[{_, current_window, windows}] -> |
|
|
|
|
[current_window | windows] |
|
|
|
|
|
|
|
|
|
_ -> |
|
|
|
|
[] |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|