diff --git a/.formatter.exs b/.formatter.exs index 2d2aa0955358..69ae0d25d2c6 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -3,7 +3,7 @@ ".credo.exs", ".formatter.exs", "apps/*/mix.exs", - "apps/*/{config,lib,priv,test}/**/*.{ex,exs}", + "apps/*/{benchmarks,config,lib,priv,test}/**/*.{ex,exs}", "mix.exs", "{config}/**/*.{ex,exs}" ], diff --git a/apps/explorer/README.md b/apps/explorer/README.md index 46297c5d9a16..3ad4d4d8ad9a 100644 --- a/apps/explorer/README.md +++ b/apps/explorer/README.md @@ -33,3 +33,10 @@ To get POA Explorer up and running locally: * Lint the Elixir code: `$ mix credo --strict` * Run the dialyzer: `mix dialyzer --halt-exit-status` * Check the Elixir code for vulnerabilities: `$ mix sobelow --config` + +### Benchmarking + +#### `Explorer.Chain.recent_collated_transactions/0` + +* Reset the test database: `MIX_ENV=test mix do ecto.drop, ecto.create, ecto.migrate` +* Run the benchmark: `MIX_ENV=test mix run benchmarks/explorer/chain/recent_collated_transactions.exs` diff --git a/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.benchee b/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.benchee new file mode 100644 index 000000000000..fe46e025efbd Binary files /dev/null and b/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.benchee differ diff --git a/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs b/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs new file mode 100644 index 000000000000..8e582580a2fb --- /dev/null +++ b/apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs @@ -0,0 +1,60 @@ +path = "benchmarks/explorer/chain/recent_collated_transactions.benchee" + +import Explorer.Factory + +alias Explorer.{Chain, Repo} +alias Explorer.Chain.Block + +Benchee.run( + %{ + "Explorer.Chain.recent_collated_transactions" => fn _ -> + Chain.recent_collated_transactions() + end + }, + inputs: %{ + " 0 blocks 0 transactions per block" => %{block_count: 0, transaction_count_per_block: 0}, + " 10 blocks 0 transactions per block" => %{block_count: 10, transaction_count_per_block: 0}, + " 10 blocks 1 transaction per block" => %{block_count: 10, transaction_count_per_block: 1}, + " 10 blocks 10 transactions per blocks" => %{block_count: 10, transaction_count_per_block: 10}, + " 10 blocks 100 transactions per blocks" => %{block_count: 10, transaction_count_per_block: 100}, + " 10 blocks 250 transactions per blocks" => %{block_count: 10, transaction_count_per_block: 250}, + " 100 blocks 0 transactions per block" => %{block_count: 100, transaction_count_per_block: 0}, + " 100 blocks 1 transaction per block" => %{block_count: 100, transaction_count_per_block: 1}, + " 100 blocks 10 transactions per blocks" => %{block_count: 100, transaction_count_per_block: 10}, + " 100 blocks 100 transactions per blocks" => %{block_count: 100, transaction_count_per_block: 100}, + " 100 blocks 250 transactions per blocks" => %{block_count: 100, transaction_count_per_block: 250}, + "1000 blocks 0 transactions per block" => %{block_count: 1000, transaction_count_per_block: 0}, + "1000 blocks 1 transaction per block" => %{block_count: 1000, transaction_count_per_block: 1}, + "1000 blocks 10 transactions per blocks" => %{block_count: 1000, transaction_count_per_block: 10}, + "1000 blocks 100 transactions per blocks" => %{block_count: 1000, transaction_count_per_block: 100} + }, + before_scenario: fn %{block_count: block_count, transaction_count_per_block: transaction_count_per_block} = input -> + :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo, ownership_timeout: :infinity) + + # ensure database is clean from failed runs + 0 = Repo.aggregate(Block, :count, :hash) + + block_count + |> insert_list(:block) + |> Enum.each(fn block -> + transaction_count_per_block + |> insert_list(:transaction) + |> Enum.each(fn transaction -> + transaction + |> with_block(block) + |> with_receipt() + end) + end) + + input + end, + formatter_options: %{ + console: %{extended_statistics: true} + }, + load: path, + save: [ + path: path, + tag: "separate-receipts" + ], + time: 10 +) diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 798df45bf5ae..b5f75ef64a95 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -62,6 +62,10 @@ defmodule Explorer.Mixfile do # Type `mix help deps` for examples and options. defp deps do [ + # benchmark optimizations + {:benchee, "~> 0.13.1", only: :test}, + # CSV output for benchee + {:benchee_csv, "~> 0.8.0", only: :test}, {:bypass, "~> 0.8", only: :test}, {:credo, "0.9.2", only: [:dev, :test], runtime: false}, {:crontab, "~> 1.1"}, diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 7d62e9db0aa7..2bfb4a26228e 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -1,6 +1,11 @@ defmodule Explorer.Factory do use ExMachina.Ecto, repo: Explorer.Repo + require Ecto.Query + + import Ecto.Query + import Kernel, except: [+: 2] + alias Explorer.Chain.Block.{Range, Reward} alias Explorer.Chain.{ @@ -126,8 +131,8 @@ defmodule Explorer.Factory do def block_reward_factory do # Generate ranges like 1 - 10,000; 10,001 - 20,000, 20,001 - 30,000; etc x = sequence("block_range", & &1) - lower = x * 10_000 + 1 - upper = lower + 9_999 + lower = x * Kernel.+(10_000, 1) + upper = Kernel.+(lower, 9_999) wei_per_ether = Decimal.new(1_000_000_000_000_000_000) @@ -205,6 +210,45 @@ defmodule Explorer.Factory do Repo.preload(block_transaction, [:block, :receipt]) end + def with_block(%Transaction{index: nil} = transaction, %Block{hash: block_hash}) do + next_transaction_index = block_hash_to_next_transaction_index(block_hash) + + transaction + |> Transaction.changeset(%{block_hash: block_hash, index: next_transaction_index}) + |> Repo.update!() + |> Repo.preload(:block) + end + + def with_receipt(%Transaction{hash: hash, index: index} = transaction) do + insert(:receipt, transaction_hash: hash, transaction_index: index) + + Repo.preload(transaction, :receipt) + end + + defmacrop left + right do + quote do + fragment("? + ?", unquote(left), unquote(right)) + end + end + + defmacrop coalesce(left, right) do + quote do + fragment("coalesce(?, ?)", unquote(left), unquote(right)) + end + end + + defp block_hash_to_next_transaction_index(block_hash) do + import Kernel, except: [+: 2] + + Repo.one!( + from( + transaction in Transaction, + select: coalesce(max(transaction.index), -1) + 1, + where: transaction.block_hash == ^block_hash + ) + ) + end + defp internal_transaction_factory(:call = type) do gas = Enum.random(21_000..100_000) gas_used = Enum.random(0..gas) diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs index 669332f81689..19ec2c16e9f9 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs @@ -2,7 +2,6 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do use ExplorerWeb.ConnCase import ExplorerWeb.Router.Helpers, only: [address_transaction_path: 4] - import ExplorerWeb.Factory alias Explorer.ExchangeRates.Token diff --git a/apps/explorer_web/test/support/conn_case.ex b/apps/explorer_web/test/support/conn_case.ex index f2c54889af06..c69dcea3a8b9 100644 --- a/apps/explorer_web/test/support/conn_case.ex +++ b/apps/explorer_web/test/support/conn_case.ex @@ -25,7 +25,6 @@ defmodule ExplorerWeb.ConnCase do @endpoint ExplorerWeb.Endpoint import Explorer.Factory - import ExplorerWeb.Factory end end diff --git a/apps/explorer_web/test/support/factory.ex b/apps/explorer_web/test/support/factory.ex deleted file mode 100644 index ebb4baa422d3..000000000000 --- a/apps/explorer_web/test/support/factory.ex +++ /dev/null @@ -1,36 +0,0 @@ -defmodule ExplorerWeb.Factory do - import Ecto.Query - import Explorer.Factory - - alias Explorer.Chain.{Block, Transaction} - alias Explorer.Repo - - def with_block(%Transaction{index: nil} = transaction, %Block{hash: block_hash}) do - next_transaction_index = block_hash_to_next_transaction_index(block_hash) - - transaction - |> Transaction.changeset(%{block_hash: block_hash, index: next_transaction_index}) - |> Repo.update!() - |> Repo.preload(:block) - end - - def with_receipt(%Transaction{hash: hash, index: index} = transaction) do - insert(:receipt, transaction_hash: hash, transaction_index: index) - - Repo.preload(transaction, :receipt) - end - - defp block_hash_to_next_transaction_index(block_hash) do - query = - from( - transaction in Transaction, - select: transaction.index, - where: transaction.block_hash == ^block_hash - ) - - case Repo.one(query) do - nil -> 0 - index -> index + 1 - end - end -end diff --git a/apps/explorer_web/test/support/feature_case.ex b/apps/explorer_web/test/support/feature_case.ex index 4c5b91a0b203..5f0053418514 100644 --- a/apps/explorer_web/test/support/feature_case.ex +++ b/apps/explorer_web/test/support/feature_case.ex @@ -12,7 +12,6 @@ defmodule ExplorerWeb.FeatureCase do import Ecto.Changeset import Ecto.Query import Explorer.Factory - import ExplorerWeb.Factory import ExplorerWeb.Router.Helpers alias Explorer.Repo diff --git a/coveralls.json b/coveralls.json index fc9c72678a5e..674a30d3c106 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 91.9 + "minimum_coverage": 92.3 }, "terminal_options": { "file_column_width": 120 diff --git a/mix.lock b/mix.lock index 5870e02311e9..737ebe6f3932 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{ "abnf2": {:hex, :abnf2, "0.1.2", "6f8792b8ac3288dba5fc889c2bceae9fe78f74e1a7b36bea9726ffaa9d7bef95", [:mix], [], "hexpm"}, + "benchee": {:hex, :benchee, "0.13.1", "bd93ca05be78bcb6159c7176230efeda2f724f7ffd485515175ca411dff4893e", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, + "benchee_csv": {:hex, :benchee_csv, "0.8.0", "0ca094677d6e2b2f601b7ee7864b754789ef9d24d079432e5e3d6f4fb83a4d80", [:mix], [{:benchee, "~> 0.12", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "bypass": {:hex, :bypass, "0.8.1", "16d409e05530ece4a72fabcf021a3e5c7e15dcc77f911423196a0c551f2a15ca", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, @@ -9,8 +11,10 @@ "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, "credo": {:hex, :credo, "0.9.2", "841d316612f568beb22ba310d816353dddf31c2d94aa488ae5a27bb53760d0bf", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "crontab": {:hex, :crontab, "1.1.2", "4784a50987b4a19af07a908f98e8a308b00f9c93efc5a7892155dc10cd8fc7d9", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, + "csv": {:hex, :csv, "2.1.1", "a4c1a7c30d2151b6e4976cb2f52c0a1d49ec965afb737ed84a684bc4284d1627", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm"}, "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, + "deep_merge": {:hex, :deep_merge, "0.1.1", "c27866a7524a337b6a039eeb8dd4f17d458fd40fbbcb8c54661b71a22fffe846", [:mix], [], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.2.8", "a4463c0928b970f2cee722cd29aaac154e866a15882c5737e0038bbfcf03ec2c", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, @@ -43,6 +47,7 @@ "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "mox": {:hex, :mox, "0.3.2", "3b9b8364fd4f28628139de701d97c636b27a8f925f57a8d5a1b85fbd620dad3a", [:mix], [], "hexpm"}, + "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},