Merge pull request #1292 from poanetwork/speed-up-homepage
feat: store averge block time in a genserverpull/1289/head
commit
b48a53efec
@ -0,0 +1,106 @@ |
|||||||
|
defmodule Explorer.Counters.AverageBlockTime do |
||||||
|
use GenServer |
||||||
|
|
||||||
|
@moduledoc """ |
||||||
|
Caches the number of token holders of a token. |
||||||
|
""" |
||||||
|
|
||||||
|
import Ecto.Query, only: [from: 2] |
||||||
|
|
||||||
|
alias Explorer.Chain.Block |
||||||
|
alias Explorer.Repo |
||||||
|
alias Timex.Duration |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Starts a process to periodically update the counter of the token holders. |
||||||
|
""" |
||||||
|
@spec start_link(term()) :: GenServer.on_start() |
||||||
|
def start_link(_) do |
||||||
|
GenServer.start_link(__MODULE__, :ok, name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
def average_block_time(block \\ nil) do |
||||||
|
enabled? = |
||||||
|
:explorer |
||||||
|
|> Application.fetch_env!(__MODULE__) |
||||||
|
|> Keyword.fetch!(:enabled) |
||||||
|
|
||||||
|
if enabled? do |
||||||
|
block = if block, do: {block.number, DateTime.to_unix(block.timestamp)} |
||||||
|
GenServer.call(__MODULE__, {:average_block_time, block}) |
||||||
|
else |
||||||
|
{:error, :disabled} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
## Server |
||||||
|
@impl true |
||||||
|
def init(_) do |
||||||
|
timestamps_query = |
||||||
|
from(block in Block, |
||||||
|
limit: 100, |
||||||
|
offset: 1, |
||||||
|
order_by: [desc: block.number], |
||||||
|
select: {block.number, block.timestamp} |
||||||
|
) |
||||||
|
|
||||||
|
timestamps = |
||||||
|
timestamps_query |
||||||
|
|> Repo.all() |
||||||
|
|> Enum.map(fn {number, timestamp} -> |
||||||
|
{number, DateTime.to_unix(timestamp)} |
||||||
|
end) |
||||||
|
|
||||||
|
{:ok, %{timestamps: timestamps, average: average_distance(timestamps)}} |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def handle_call({:average_block_time, nil}, _from, %{average: average} = state), do: {:reply, average, state} |
||||||
|
|
||||||
|
def handle_call({:average_block_time, block}, _from, state) do |
||||||
|
state = add_block(state, block) |
||||||
|
{:reply, state.average, state} |
||||||
|
end |
||||||
|
|
||||||
|
# This is pretty naive, but we'll only ever be sorting 100 dates so I don't think |
||||||
|
# complex logic is really necessary here. |
||||||
|
defp add_block(%{timestamps: timestamps} = state, block) do |
||||||
|
timestamps = |
||||||
|
[block | timestamps] |
||||||
|
|> Enum.sort_by(fn {number, _} -> number end, &Kernel.>/2) |
||||||
|
|> Enum.take(100) |
||||||
|
|
||||||
|
%{state | timestamps: timestamps, average: average_distance(timestamps)} |
||||||
|
end |
||||||
|
|
||||||
|
defp average_distance([]), do: Duration.from_seconds(0) |
||||||
|
defp average_distance([_]), do: Duration.from_seconds(0) |
||||||
|
|
||||||
|
defp average_distance(timestamps) do |
||||||
|
durations = durations(timestamps) |
||||||
|
|
||||||
|
{sum, count} = |
||||||
|
Enum.reduce(durations, {0, 0}, fn duration, {sum, count} -> |
||||||
|
{sum + duration, count + 1} |
||||||
|
end) |
||||||
|
|
||||||
|
average = sum / count |
||||||
|
|
||||||
|
average |
||||||
|
|> round() |
||||||
|
|> Duration.from_seconds() |
||||||
|
end |
||||||
|
|
||||||
|
defp durations(timestamps) do |
||||||
|
timestamps |
||||||
|
|> Enum.reduce({[], nil}, fn {_, timestamp}, {durations, last_timestamp} -> |
||||||
|
if last_timestamp do |
||||||
|
duration = last_timestamp - timestamp |
||||||
|
{[duration | durations], timestamp} |
||||||
|
else |
||||||
|
{durations, timestamp} |
||||||
|
end |
||||||
|
end) |
||||||
|
|> elem(0) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,64 @@ |
|||||||
|
defmodule Explorer.Counters.AverageBlockTimeTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
alias Explorer.Counters.AverageBlockTime |
||||||
|
|
||||||
|
defp block(number, last, duration), do: %{number: number, timestamp: Timex.shift(last, seconds: duration)} |
||||||
|
|
||||||
|
setup do |
||||||
|
start_supervised!(AverageBlockTime) |
||||||
|
Application.put_env(:explorer, AverageBlockTime, enabled: true) |
||||||
|
|
||||||
|
on_exit(fn -> |
||||||
|
Application.put_env(:explorer, AverageBlockTime, enabled: false) |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
describe "average_block_time/1" do |
||||||
|
test "when disabled, it returns an error" do |
||||||
|
Application.put_env(:explorer, AverageBlockTime, enabled: false) |
||||||
|
|
||||||
|
assert AverageBlockTime.average_block_time() == {:error, :disabled} |
||||||
|
end |
||||||
|
|
||||||
|
test "without blocks duration is 0" do |
||||||
|
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT0S") |
||||||
|
end |
||||||
|
|
||||||
|
test "with only one block, the duration is 0" do |
||||||
|
now = Timex.now() |
||||||
|
block = block(0, now, 0) |
||||||
|
|
||||||
|
assert AverageBlockTime.average_block_time(block) == Timex.Duration.parse!("PT0S") |
||||||
|
end |
||||||
|
|
||||||
|
test "once there are two blocks, the duration is the average distance between them all" do |
||||||
|
now = Timex.now() |
||||||
|
|
||||||
|
block0 = block(0, now, 0) |
||||||
|
block1 = block(1, now, 2) |
||||||
|
block2 = block(2, now, 6) |
||||||
|
|
||||||
|
AverageBlockTime.average_block_time(block0) |
||||||
|
assert AverageBlockTime.average_block_time(block1) == Timex.Duration.parse!("PT2S") |
||||||
|
assert AverageBlockTime.average_block_time(block2) == Timex.Duration.parse!("PT3S") |
||||||
|
end |
||||||
|
|
||||||
|
test "only the last 100 blocks are considered" do |
||||||
|
now = Timex.now() |
||||||
|
|
||||||
|
block0 = block(0, now, 0) |
||||||
|
block1 = block(1, now, 2000) |
||||||
|
|
||||||
|
AverageBlockTime.average_block_time(block0) |
||||||
|
AverageBlockTime.average_block_time(block1) |
||||||
|
|
||||||
|
for i <- 1..100 do |
||||||
|
block = block(i + 1, now, 2000 + i) |
||||||
|
AverageBlockTime.average_block_time(block) |
||||||
|
end |
||||||
|
|
||||||
|
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT1S") |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue