From 8a95ff0de320004a5e296e90bf2265d153482d1c Mon Sep 17 00:00:00 2001 From: Tobias Pfeiffer Date: Sun, 20 Feb 2022 21:21:19 +0100 Subject: [PATCH 1/2] Profiler return the return value of the function As discussed in: https://groups.google.com/g/elixir-lang-core/c/f0gP0It-yuU But basically: * right now no return value is documented/discussed so should not be relied upon * the original profiler themselves all return the return value, so it should be possible * I need it in Benchee :angel: --- lib/mix/lib/mix/tasks/profile.cprof.ex | 20 ++++++++++++------- lib/mix/lib/mix/tasks/profile.eprof.ex | 16 ++++++++++----- lib/mix/lib/mix/tasks/profile.fprof.ex | 16 ++++++++++----- lib/mix/test/mix/tasks/profile.cprof_test.exs | 8 ++++++++ lib/mix/test/mix/tasks/profile.eprof_test.exs | 8 ++++++++ lib/mix/test/mix/tasks/profile.fprof_test.exs | 8 ++++++++ 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/lib/mix/lib/mix/tasks/profile.cprof.ex b/lib/mix/lib/mix/tasks/profile.cprof.ex index b04eae85d56..6b87d91578a 100644 --- a/lib/mix/lib/mix/tasks/profile.cprof.ex +++ b/lib/mix/lib/mix/tasks/profile.cprof.ex @@ -157,6 +157,8 @@ defmodule Mix.Tasks.Profile.Cprof do @doc """ Allows to programmatically run the `cprof` profiler on expression in `fun`. + Returns the return value of `fun`. + ## Options * `:matching` - only profile calls matching the given pattern in form of @@ -166,12 +168,16 @@ defmodule Mix.Tasks.Profile.Cprof do * `:module` - filters out any results not pertaining to the given module """ + @spec profile((() -> any())) :: any() + @spec profile((() -> any()), keyword()) :: any() def profile(fun, opts \\ []) when is_function(fun, 0) do - fun - |> profile_and_analyse(opts) - |> print_output + {return_value, num_matched_functions, analysis_result} = profile_and_analyse(fun, opts) + + print_output(num_matched_functions, analysis_result) :cprof.stop() + + return_value end defp profile_and_analyse(fun, opts) do @@ -186,7 +192,7 @@ defmodule Mix.Tasks.Profile.Cprof do :error -> :cprof.start() end - apply(fun, []) + return_value = apply(fun, []) :cprof.pause() @@ -209,19 +215,19 @@ defmodule Mix.Tasks.Profile.Cprof do end end - {num_matched_functions, analysis_result} + {return_value, num_matched_functions, analysis_result} end defp string_to_existing_module(":" <> module), do: String.to_existing_atom(module) defp string_to_existing_module(module), do: Module.concat([module]) - defp print_output({num_matched_functions, {all_call_count, mod_analysis_list}}) do + defp print_output(num_matched_functions, {all_call_count, mod_analysis_list}) do print_total_row(all_call_count) Enum.each(mod_analysis_list, &print_analysis_result/1) print_number_of_matched_functions(num_matched_functions) end - defp print_output({num_matched_functions, {_mod, _call_count, _mod_fun_list} = mod_analysis}) do + defp print_output(num_matched_functions, {_mod, _call_count, _mod_fun_list} = mod_analysis) do print_analysis_result(mod_analysis) print_number_of_matched_functions(num_matched_functions) end diff --git a/lib/mix/lib/mix/tasks/profile.eprof.ex b/lib/mix/lib/mix/tasks/profile.eprof.ex index 31ec9475f2c..b8f40720ea9 100644 --- a/lib/mix/lib/mix/tasks/profile.eprof.ex +++ b/lib/mix/lib/mix/tasks/profile.eprof.ex @@ -171,6 +171,8 @@ defmodule Mix.Tasks.Profile.Eprof do @doc """ Allows to programmatically run the `eprof` profiler on expression in `fun`. + Returns the return value of `fun`. + ## Options * `:matching` - only profile calls matching the given pattern in form of @@ -181,10 +183,14 @@ defmodule Mix.Tasks.Profile.Eprof do * `:sort` - sort the results by `:time` or `:calls` (default: `:time`) """ + @spec profile((() -> any())) :: any() + @spec profile((() -> any()), keyword()) :: any() def profile(fun, opts \\ []) when is_function(fun, 0) do - fun - |> profile_and_analyse(opts) - |> print_output() + {return_value, results} = profile_and_analyse(fun, opts) + + print_output(results) + + return_value end defp profile_and_analyse(fun, opts) do @@ -194,7 +200,7 @@ defmodule Mix.Tasks.Profile.Eprof do end :eprof.start() - :eprof.profile([], fun, Keyword.get(opts, :matching, {:_, :_, :_})) + {:ok, return_value} = :eprof.profile([], fun, Keyword.get(opts, :matching, {:_, :_, :_})) results = Enum.map(:eprof.dump(), fn {pid, call_results} -> @@ -209,7 +215,7 @@ defmodule Mix.Tasks.Profile.Eprof do :eprof.stop() - results + {return_value, results} end defp filter_results(call_results, opts) do diff --git a/lib/mix/lib/mix/tasks/profile.fprof.ex b/lib/mix/lib/mix/tasks/profile.fprof.ex index 38dd32531b3..90d2eff1c3d 100644 --- a/lib/mix/lib/mix/tasks/profile.fprof.ex +++ b/lib/mix/lib/mix/tasks/profile.fprof.ex @@ -166,6 +166,8 @@ defmodule Mix.Tasks.Profile.Fprof do @doc """ Allows to programmatically run the `fprof` profiler on expression in `fun`. + Returns the return value of `fun`. + ## Options * `:callers` - prints detailed information about immediate callers and called functions @@ -173,10 +175,14 @@ defmodule Mix.Tasks.Profile.Fprof do * `:sort` - sorts the output by given key: `:acc` (default) or `:own` """ + @spec profile((() -> any())) :: any() + @spec profile((() -> any()), keyword()) :: any() def profile(fun, opts \\ []) when is_function(fun, 0) do - fun - |> profile_and_analyse(opts) - |> print_output + {return_value, analysis_output} = profile_and_analyse(fun, opts) + + print_output(analysis_output) + + return_value end defp profile_and_analyse(fun, opts) do @@ -186,7 +192,7 @@ defmodule Mix.Tasks.Profile.Fprof do end {:ok, tracer} = :fprof.profile(:start) - :fprof.apply(fun, [], tracer: tracer) + return_value = :fprof.apply(fun, [], tracer: tracer) {:ok, analyse_dest} = StringIO.open("") @@ -201,7 +207,7 @@ defmodule Mix.Tasks.Profile.Fprof do else :ok -> {_in, analysis_output} = StringIO.contents(analyse_dest) - String.to_charlist(analysis_output) + {return_value, String.to_charlist(analysis_output)} after StringIO.close(analyse_dest) end diff --git a/lib/mix/test/mix/tasks/profile.cprof_test.exs b/lib/mix/test/mix/tasks/profile.cprof_test.exs index 1a16c7b78a0..bc51dc2f120 100644 --- a/lib/mix/test/mix/tasks/profile.cprof_test.exs +++ b/lib/mix/test/mix/tasks/profile.cprof_test.exs @@ -103,4 +103,12 @@ defmodule Mix.Tasks.Profile.CprofTest do end) =~ "Warmup..." end) end + + describe ".profile/2" do + test "returns the return value of the function call" do + capture_io(fn -> + assert 42 == Cprof.profile(fn -> 42 end) + end) + end + end end diff --git a/lib/mix/test/mix/tasks/profile.eprof_test.exs b/lib/mix/test/mix/tasks/profile.eprof_test.exs index 12bfc11b629..216b772f1e2 100644 --- a/lib/mix/test/mix/tasks/profile.eprof_test.exs +++ b/lib/mix/test/mix/tasks/profile.eprof_test.exs @@ -112,4 +112,12 @@ defmodule Mix.Tasks.Profile.EprofTest do end) =~ "Warmup..." end) end + + describe ".profile/2" do + test "returns the return value of the function call" do + capture_io(fn -> + assert 42 == Eprof.profile(fn -> 42 end) + end) + end + end end diff --git a/lib/mix/test/mix/tasks/profile.fprof_test.exs b/lib/mix/test/mix/tasks/profile.fprof_test.exs index 1a7ad249e5d..a40adc21146 100644 --- a/lib/mix/test/mix/tasks/profile.fprof_test.exs +++ b/lib/mix/test/mix/tasks/profile.fprof_test.exs @@ -98,4 +98,12 @@ defmodule Mix.Tasks.Profile.FprofTest do end) =~ "Warmup..." end) end + + describe ".profile/2" do + test "returns the return value of the function call" do + capture_io(fn -> + assert 42 == Fprof.profile(fn -> 42 end) + end) + end + end end From 692243c5a3ba1684b789c75d550e1a1268e3fdda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 20 Feb 2022 22:33:45 +0100 Subject: [PATCH 2/2] Apply suggestions from code review --- lib/mix/lib/mix/tasks/profile.cprof.ex | 1 - lib/mix/lib/mix/tasks/profile.eprof.ex | 1 - lib/mix/lib/mix/tasks/profile.fprof.ex | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/mix/lib/mix/tasks/profile.cprof.ex b/lib/mix/lib/mix/tasks/profile.cprof.ex index 6b87d91578a..a154f3f5bbc 100644 --- a/lib/mix/lib/mix/tasks/profile.cprof.ex +++ b/lib/mix/lib/mix/tasks/profile.cprof.ex @@ -168,7 +168,6 @@ defmodule Mix.Tasks.Profile.Cprof do * `:module` - filters out any results not pertaining to the given module """ - @spec profile((() -> any())) :: any() @spec profile((() -> any()), keyword()) :: any() def profile(fun, opts \\ []) when is_function(fun, 0) do {return_value, num_matched_functions, analysis_result} = profile_and_analyse(fun, opts) diff --git a/lib/mix/lib/mix/tasks/profile.eprof.ex b/lib/mix/lib/mix/tasks/profile.eprof.ex index b8f40720ea9..5a0b27bb017 100644 --- a/lib/mix/lib/mix/tasks/profile.eprof.ex +++ b/lib/mix/lib/mix/tasks/profile.eprof.ex @@ -183,7 +183,6 @@ defmodule Mix.Tasks.Profile.Eprof do * `:sort` - sort the results by `:time` or `:calls` (default: `:time`) """ - @spec profile((() -> any())) :: any() @spec profile((() -> any()), keyword()) :: any() def profile(fun, opts \\ []) when is_function(fun, 0) do {return_value, results} = profile_and_analyse(fun, opts) diff --git a/lib/mix/lib/mix/tasks/profile.fprof.ex b/lib/mix/lib/mix/tasks/profile.fprof.ex index 90d2eff1c3d..2c8e9d59744 100644 --- a/lib/mix/lib/mix/tasks/profile.fprof.ex +++ b/lib/mix/lib/mix/tasks/profile.fprof.ex @@ -175,7 +175,6 @@ defmodule Mix.Tasks.Profile.Fprof do * `:sort` - sorts the output by given key: `:acc` (default) or `:own` """ - @spec profile((() -> any())) :: any() @spec profile((() -> any()), keyword()) :: any() def profile(fun, opts \\ []) when is_function(fun, 0) do {return_value, analysis_output} = profile_and_analyse(fun, opts)