Skip to content

Commit

Permalink
feat: add txs per second stat on /stats (#834)
Browse files Browse the repository at this point in the history
  • Loading branch information
sborrazas committed Aug 12, 2022
1 parent f93d72b commit 1e010de
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 19 deletions.
9 changes: 8 additions & 1 deletion lib/ae_mdw/db/model.ex
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,11 @@ defmodule AeMdw.Db.Model do
burned_in_auctions: non_neg_integer()
)

@stat_defaults [:index, :payload]
defrecord :stat, @stat_defaults

@type stat() :: record(:stat, index: atom(), payload: term())

################################################################################

# starts with only chain_tables and add them progressively by groups
Expand Down Expand Up @@ -684,7 +689,8 @@ defmodule AeMdw.Db.Model do
defp stat_tables() do
[
AeMdw.Db.Model.DeltaStat,
AeMdw.Db.Model.TotalStat
AeMdw.Db.Model.TotalStat,
AeMdw.Db.Model.Stat
]
end

Expand Down Expand Up @@ -754,4 +760,5 @@ defmodule AeMdw.Db.Model do
def record(AeMdw.Db.Model.TargetKindIntTransferTx), do: :target_kind_int_transfer_tx
def record(AeMdw.Db.Model.DeltaStat), do: :delta_stat
def record(AeMdw.Db.Model.TotalStat), do: :total_stat
def record(AeMdw.Db.Model.Stat), do: :stat
end
27 changes: 23 additions & 4 deletions lib/ae_mdw/db/mutations/stats_mutation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,54 @@ defmodule AeMdw.Db.StatsMutation do
alias AeMdw.Db.Oracle
alias AeMdw.Db.Origin
alias AeMdw.Db.State
alias AeMdw.Stats
alias AeMdw.Util

require Model

@derive AeMdw.Db.Mutation
defstruct [:height, :all_cached?]
defstruct [:height, :key_hash, :tps, :all_cached?]

@type t() :: %__MODULE__{
height: Blocks.height(),
key_hash: Blocks.block_hash(),
tps: Stats.tps(),
all_cached?: boolean()
}

@spec new(Blocks.height(), boolean()) :: t()
def new(height, all_cached?) do
@spec new(Blocks.height(), Blocks.block_hash(), Stats.tps(), boolean()) :: t()
def new(height, key_hash, tps, all_cached?) do
%__MODULE__{
height: height,
key_hash: key_hash,
tps: tps,
all_cached?: all_cached?
}
end

@spec execute(t(), State.t()) :: State.t()
def execute(%__MODULE__{height: height, all_cached?: all_cached?}, state) do
def execute(
%__MODULE__{height: height, key_hash: key_hash, tps: tps, all_cached?: all_cached?},
state
) do
m_delta_stat = make_delta_stat(state, height, all_cached?)
# delta/transitions are only reflected on total stats at height + 1
m_total_stat = make_total_stat(state, height + 1, m_delta_stat)

state
|> State.put(Model.DeltaStat, m_delta_stat)
|> State.put(Model.TotalStat, m_total_stat)
|> State.update(Model.Stat, Stats.max_tps_key(), fn
Model.stat(payload: {max_tps, _tps_block_hash}) = stat ->
if tps >= max_tps do
Model.stat(stat, payload: {tps, key_hash})
else
stat
end

nil ->
Model.stat(index: Stats.max_tps_key(), payload: {tps, key_hash})
end)
end

#
Expand Down
8 changes: 8 additions & 0 deletions lib/ae_mdw/db/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ defmodule AeMdw.Db.State do

def next(state, table, :forward, cursor), do: next(state, table, cursor)

@spec update(t(), table(), key(), (record() -> record()), term() | nil) :: t()
def update(state, table, key, update_fn, default \\ nil) do
case get(state, table, key) do
{:ok, record} -> put(state, table, update_fn.(record))
:not_found -> put(state, table, update_fn.(default))
end
end

@spec inc_stat(t(), stat_name(), integer()) :: t()
def inc_stat(state, name, delta \\ 1)

Expand Down
4 changes: 2 additions & 2 deletions lib/ae_mdw/db/sync/block.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ defmodule AeMdw.Db.Sync.Block do
alias AeMdw.Db.NamesExpirationMutation
alias AeMdw.Db.OraclesExpirationMutation
alias AeMdw.Db.State
alias AeMdw.Db.StatsMutation
alias AeMdw.Db.Sync.Transaction
alias AeMdw.Db.WriteMutation
alias AeMdw.Db.Mutation
alias AeMdw.Log
alias AeMdw.Node, as: AE
alias AeMdw.Node.Db
alias AeMdw.Stats
alias AeMdw.Txs

require Model
Expand Down Expand Up @@ -99,7 +99,7 @@ defmodule AeMdw.Db.Sync.Block do
block_rewards_mutation,
NamesExpirationMutation.new(height),
OraclesExpirationMutation.new(height),
StatsMutation.new(height, starting_from_mb0?),
Stats.mutation(height, key_block, micro_blocks, starting_from_mb0?),
next_kb_mutation
]

Expand Down
46 changes: 46 additions & 0 deletions lib/ae_mdw/stats.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ defmodule AeMdw.Stats do
Context module for dealing with Stats.
"""

alias :aeser_api_encoder, as: Enc
alias AeMdw.Blocks
alias AeMdw.Db.Model
alias AeMdw.Db.State
alias AeMdw.Db.StatsMutation
alias AeMdw.Database
alias AeMdw.Error
alias AeMdw.Error.Input, as: ErrInput
alias AeMdw.Node.Db
alias AeMdw.Util

require Model
Expand All @@ -15,12 +20,38 @@ defmodule AeMdw.Stats do
@type delta_stat() :: map()
@type total_stat() :: map()
@type cursor() :: binary() | nil
@type tps() :: non_neg_integer()

@typep height() :: Blocks.height()
@typep direction() :: Database.direction()
@typep limit() :: Database.limit()
@typep range() :: {:gen, Range.t()} | nil

@tps_stat_key :max_tps

@spec mutation(height(), Db.key_block(), [Db.micro_block()], boolean()) :: StatsMutation.t()
def mutation(height, key_block, micro_blocks, starting_from_mb0?) do
header = :aec_blocks.to_header(key_block)
time = :aec_headers.time_in_msecs(header)
{:ok, key_hash} = :aec_headers.hash_header(header)

{_last_time, total_time, total_txs} =
Enum.reduce(micro_blocks, {time, 0, 0}, fn micro_block, {last_time, time_acc, count_acc} ->
header = :aec_blocks.to_header(micro_block)
time = :aec_headers.time_in_msecs(header)
count = length(:aec_blocks.txs(micro_block))

{time, time_acc + time - last_time, count_acc + count}
end)

tps = if total_time > 0, do: round(total_txs * 100_000 / total_time) / 100, else: 0

StatsMutation.new(height, key_hash, tps, starting_from_mb0?)
end

@spec max_tps_key() :: atom()
def max_tps_key, do: @tps_stat_key

# Legacy v1 is a blending between /totalstats and /deltastats.
# The active and inactive object counters are totals while the rewards are delta.
@spec fetch_stats_v1(State.t(), direction(), range(), cursor(), limit()) ::
Expand Down Expand Up @@ -91,6 +122,21 @@ defmodule AeMdw.Stats do
end
end

@spec fetch_stats(State.t()) :: {:ok, map()} | {:error, Error.t()}
def fetch_stats(state) do
case State.get(state, Model.Stat, @tps_stat_key) do
{:ok, Model.stat(payload: {tps, tps_block_hash})} ->
{:ok,
%{
max_transactions_per_second: tps,
max_transactions_per_second_block_hash: Enc.encode(:key_block_hash, tps_block_hash)
}}

:not_found ->
{:error, ErrInput.NotFound.exception(value: "no stats")}
end
end

@spec fetch_delta_stat!(State.t(), height()) :: delta_stat()
def fetch_delta_stat!(state, height),
do: render_delta_stat(State.fetch!(state, Model.DeltaStat, height))
Expand Down
14 changes: 12 additions & 2 deletions lib/ae_mdw_web/controllers/stats_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ defmodule AeMdwWeb.StatsController do

alias AeMdw.Stats
alias AeMdwWeb.Plugs.PaginatedPlug
alias AeMdwWeb.FallbackController
alias AeMdwWeb.Util
alias Plug.Conn

plug(PaginatedPlug)
action_fallback(FallbackController)

@spec stats(Conn.t(), map()) :: Conn.t()
def stats(%Conn{assigns: assigns} = conn, _params) do
@spec stats_v1(Conn.t(), map()) :: Conn.t()
def stats_v1(%Conn{assigns: assigns} = conn, _params) do
%{
state: state,
pagination: {direction, _is_reversed?, limit, _has_cursor?},
Expand Down Expand Up @@ -52,4 +54,12 @@ defmodule AeMdwWeb.StatsController do

Util.paginate(conn, prev_cursor, stats, next_cursor)
end

@spec stats(Conn.t(), map()) :: Conn.t()
def stats(%Conn{assigns: %{state: state}} = conn, _params) do
case Stats.fetch_stats(state) do
{:ok, stats} -> json(conn, stats)
{:error, reason} -> {:error, reason}
end
end
end
3 changes: 2 additions & 1 deletion lib/ae_mdw_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ defmodule AeMdwWeb.Router do
get "/oracles", OracleController, :oracles

get "/deltastats", StatsController, :delta_stats
get "/stats", StatsController, :stats

scope "/swagger" do
forward "/", SwaggerForwardV2,
Expand Down Expand Up @@ -155,7 +156,7 @@ defmodule AeMdwWeb.Router do
get "/aex9/balances/hash/:blockhash/:contract_id", Aex9Controller, :balances_for_hash
get "/aex9/balances/:contract_id", Aex9Controller, :balances

get "/stats", StatsController, :stats
get "/stats", StatsController, :stats_v1
get "/stats/:direction", StatsController, :stats
get "/stats/:scope_type/:range", StatsController, :stats
get "/totalstats/:direction", StatsController, :total_stats
Expand Down
25 changes: 16 additions & 9 deletions test/ae_mdw/db/mutations/stats_mutation_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule AeMdw.Db.StatsMutationTest do
Database.dirty_write(Model.DeltaStat, prev_delta_stat)
Database.dirty_write(Model.TotalStat, prev_total_stat)

mutation = StatsMutation.new(height, false)
mutation = StatsMutation.new(height, nil, nil, false)

State.commit(State.new(), [mutation])

Expand Down Expand Up @@ -91,7 +91,7 @@ defmodule AeMdw.Db.StatsMutationTest do
State.new()
|> State.inc_stat(:block_reward, @first_block_reward)

mutation = StatsMutation.new(height, true)
mutation = StatsMutation.new(height, nil, nil, true)

State.commit(state, [mutation])

Expand Down Expand Up @@ -156,7 +156,7 @@ defmodule AeMdw.Db.StatsMutationTest do
|> State.inc_stat(:dev_reward, increased_dev_reward)
|> State.inc_stat(:names_activated, 1)

mutation = StatsMutation.new(height, true)
mutation = StatsMutation.new(height, nil, nil, true)

State.commit(state, [mutation])

Expand Down Expand Up @@ -189,7 +189,7 @@ defmodule AeMdw.Db.StatsMutationTest do
describe "execute/2" do
test "with all_cached? = false, on 1st block reward it stores the stat using database counts" do
height = 21
mutation = StatsMutation.new(height, false)
mutation = StatsMutation.new(height, nil, nil, false)

expected_delta =
Model.delta_stat(
Expand Down Expand Up @@ -225,13 +225,15 @@ defmodule AeMdw.Db.StatsMutationTest do
get: fn
Model.TotalStat, ^height -> {:ok, Model.total_stat(active_auctions: 1)}
Model.InactiveName, _plain_name -> {:ok, Model.name()}
Model.Stat, _key -> :not_found
end,
count: fn
Model.ActiveName -> 3
Model.ActiveOracle -> 4
Model.AuctionExpiration -> 5
end,
next_key: fn _tab, _init_key -> :none end
next_key: fn _tab, _init_key -> :none end,
dirty_write: fn _txs, _record -> :ok end
]},
{State, [:passthrough], put: fn state, _tab, _record -> state end},
{Collection, [],
Expand Down Expand Up @@ -265,7 +267,7 @@ defmodule AeMdw.Db.StatsMutationTest do
State.new()
|> State.inc_stat(:block_reward, 5)

mutation = StatsMutation.new(height, true)
mutation = StatsMutation.new(height, nil, nil, true)

expected_delta =
Model.delta_stat(
Expand Down Expand Up @@ -298,9 +300,14 @@ defmodule AeMdw.Db.StatsMutationTest do
with_mocks [
{Database, [],
[
get: fn Model.TotalStat, ^height ->
{:ok, Model.total_stat(active_auctions: 1)}
end
get: fn
Model.TotalStat, ^height ->
{:ok, Model.total_stat(active_auctions: 1)}

Model.Stat, _key ->
:not_found
end,
dirty_write: fn _txs, _record -> :ok end
]},
{State, [:passthrough], put: fn state, _tab, _record -> state end}
] do
Expand Down

0 comments on commit 1e010de

Please sign in to comment.