Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate profiling reports #17

Merged
merged 6 commits into from
Feb 4, 2017
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
4 changes: 4 additions & 0 deletions .dialyzer_ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Unknown functions:
fprof:analyse/1
fprof:apply/3
fprof:profile/1
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ erl_crash.dump

# Emacs backup files
*~

# Profiling output directory
/profile
6 changes: 4 additions & 2 deletions lib/backtrex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ defmodule Backtrex do
quote location: :keep do
@behaviour Backtrex

def solve(problem) do
Backtrex.solve(__MODULE__, problem)
def solve(problem, backtracker \\ :naive_sequential) do
case backtracker do
:naive_sequential -> Backtrex.solve(__MODULE__, problem)
end
end
end
end
Expand Down
91 changes: 91 additions & 0 deletions lib/backtrex/profiler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
defmodule Backtrex.Profiler do
alias Backtrex.Examples.Sudoku

@spec backends() :: [atom()]
def backends do
[
:naive_sequential,
]
end

@spec problems() :: [{module(), [{atom(), any()}]}]
def problems do
[
{Sudoku.Solver,
[
quick_puzzle: example_puzzle()
]},
]
end

def combos do
for backend <- backends(),
{frontend, inputs} <- problems() do
for input <- inputs, do: {backend, frontend, input}
end
|> Enum.concat()
end

def combo_name({b, f, {n, _}}) do
"#{inspect b}-#{inspect f}-#{inspect n}"
|> String.replace(":", "")
end

def profile(dir \\ "./profile") do
if !File.exists?(dir) do
File.mkdir_p!(dir)
end

eflame(dir)
fprof(dir)
end

def eflame(dir) do
combos()
|> Enum.each(fn combo ->
fname = Path.join(dir, "stacks-#{combo_name(combo)}.out")
:eflame.apply(:normal_with_children, fname, __MODULE__, :run_test, [combo])
System.cmd("bash", ["./script/make_flamegraph.sh", fname])
end)
end

def fprof(dir) do
combos()
|> Enum.each(fn combo ->
trace_file = Path.join(dir, "fprof-#{combo_name(combo)}.trace") |> to_charlist
analysis_file = Path.join(dir, "fprof-#{combo_name(combo)}.analysis") |> to_charlist
:fprof.apply(&run_test/1, [combo], [
{:file, trace_file}
])
:fprof.profile(file: trace_file)
:fprof.analyse([
dest: analysis_file,
callers: true,
sort: :own,
totals: true,
details: true])
end)
end

def example_puzzle do
{:ok, puzzle} = Sudoku.Puzzle.from_list([
[5, 3, :_, :_, 7, :_, :_, 1, 2],
[6, 7, :_, 1, 9, 5, :_, 4, :_],
[1, 9, 8, 3, :_, :_, 5, 6, 7],
[8, :_, 9, :_, 6, :_, 4, :_, 3],
[4, 2, :_, 8, :_, 3, 7, :_, 1],
[7, :_, 3, :_, 2, 4, :_, 5, 6],
[:_, 6, :_, 5, :_, 7, 2, 8, :_],
[2, :_, :_, 4, 1, 9, 6, :_, 5],
[:_, 4, 5, :_, 8, 6, :_, 7, 9]])
puzzle
end

def run_test({backend, frontend, {_name, input}}) do
{:ok, :solution, _} = apply(frontend, :solve, [input, backend])
end

def run_test do
example_puzzle() |> Backtrex.Examples.Sudoku.Solver.solve
end
end
10 changes: 10 additions & 0 deletions lib/mix/tasks/backtrex/profile.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Mix.Tasks.Backtrex.Profile do
use Mix.Task

@shortdoc "Run Backtrex profiler."
def run(_) do
output_dir = "profile"
Backtrex.Profiler.profile(output_dir)
IO.puts "Profiler data written to #{output_dir}."
end
end
7 changes: 5 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@ defmodule Backtrex.Mixfile do
elixirc_options: [warnings_as_errors: true],
description: description(),
package: package(),
deps: deps()]
deps: deps(),
dialyzer: [ignore_warnings: ".dialyzer_ignore"],
]
end

def application do
[extra_applications: [:logger],
[extra_applications: [:logger, :eflame, :mix],
applications: [:logger]] # format expected by Elixir 1.3
end

defp deps do
[
{:credo, "~> 0.5", only: [:dev, :test]},
{:dialyxir, "~> 0.4", only: [:dev], runtime: false},
{:eflame, ~r/.*/, git: "https://github.com/proger/eflame.git", compile: "rebar compile"},
{:ex_doc, "~> 0.14", only: :dev},
]
end
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"credo": {:hex, :credo, "0.5.3", "0c405b36e7651245a8ed63c09e2d52c2e2b89b6d02b1570c4d611e0fcbecf4a2", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]},
"dialyxir": {:hex, :dialyxir, "0.4.3", "a4daeebd0107de10d3bbae2ccb6b8905e69544db1ed5fe9148ad27cd4cb2c0cd", [:mix], []},
"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
"eflame": {:git, "https://github.com/proger/eflame.git", "ee3a44b950aeb2434460045274809ad506141a83", []},
"ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"inch_ex": {:hex, :inch_ex, "0.5.5", "b63f57e281467bd3456461525fdbc9e158c8edbe603da6e3e4671befde796a3d", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
"poison": {:hex, :poison, "3.0.0", "625ebd64d33ae2e65201c2c14d6c85c27cc8b68f2d0dd37828fde9c6920dd131", [:mix], []}}
10 changes: 10 additions & 0 deletions script/make_flamegraph.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

# Makes a flame graph from :eflame's output. Could eventually be converted to
# Elixir, but would require I/O redirection.

STACK_TO_FLAME="./deps/eflame/stack_to_flame.sh"
STACK_FILE="$1"
SVG_OUT_FILE="${STACK_FILE/%.out/.svg}"

"$STACK_TO_FLAME" < "$STACK_FILE" > "$SVG_OUT_FILE"