Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions lib/mix/lib/mix/tasks/profile.cprof.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -166,12 +168,15 @@ defmodule Mix.Tasks.Profile.Cprof do
* `:module` - filters out any results not pertaining to the given module

"""
@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
Expand All @@ -186,7 +191,7 @@ defmodule Mix.Tasks.Profile.Cprof do
:error -> :cprof.start()
end

apply(fun, [])
return_value = apply(fun, [])

:cprof.pause()

Expand All @@ -209,19 +214,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
Expand Down
15 changes: 10 additions & 5 deletions lib/mix/lib/mix/tasks/profile.eprof.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -181,10 +183,13 @@ defmodule Mix.Tasks.Profile.Eprof do
* `:sort` - sort the results by `:time` or `:calls` (default: `:time`)

"""
@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
Expand All @@ -194,7 +199,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, {:_, :_, :_}))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit is potentially dangerous, as it might return {:error, Reason} https://www.erlang.org/doc/man/eprof.html#profile-1

If tracing could be enabled for P and all processes in Rootset, the function returns {ok,Value} when Fun()/apply returns with the value Value, or {error,Reason} if Fun()/apply fails with exit reason Reason. Otherwise it returns {error, Reason} immediately.

It would be a rather hard failure here, on the other hand I'm not sure a possible error was handled gracefully before. Also not sure how to best "gracefully" handle it here.

Feedback welcome!


results =
Enum.map(:eprof.dump(), fn {pid, call_results} ->
Expand All @@ -209,7 +214,7 @@ defmodule Mix.Tasks.Profile.Eprof do

:eprof.stop()

results
{return_value, results}
end

defp filter_results(call_results, opts) do
Expand Down
15 changes: 10 additions & 5 deletions lib/mix/lib/mix/tasks/profile.fprof.ex
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,22 @@ 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
* `:details` - includes profile data for each profiled process
* `:sort` - sorts the output by given key: `:acc` (default) or `:own`

"""
@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
Expand All @@ -186,7 +191,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("")

Expand All @@ -201,7 +206,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
Expand Down
8 changes: 8 additions & 0 deletions lib/mix/test/mix/tasks/profile.cprof_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions lib/mix/test/mix/tasks/profile.eprof_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions lib/mix/test/mix/tasks/profile.fprof_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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