Current section
Files
Jump to
Current section
Files
lib/benchee/formatters/html.ex
defmodule Benchee.Formatters.HTML do
@moduledoc """
Functionality for converting Benchee benchmarking results to an HTML page
with plotly.js generated graphs and friends.
## Examples
list = Enum.to_list(1..10_000)
map_fun = fn(i) -> [i, i * i] end
Benchee.run(
%{
"flat_map" => fn -> Enum.flat_map(list, map_fun) end,
"map.flatten" => fn -> list |> Enum.map(map_fun) |> List.flatten() end
},
formatters: [
Benchee.Formatters.Console,
{Benchee.Formatters.HTML, file: "samples_output/flat_map.html"}
]
)
"""
@behaviour Benchee.Formatter
alias Benchee.{
Configuration,
Conversion,
Formatters.HTML.Render,
Formatters.JSON,
Suite,
Utility.FileCreation
}
@doc """
Transforms the statistical results from benchmarking to html reports.
Returns a map from file name/path to file content.
"""
@spec format(Suite.t(), map) :: %{Suite.key() => String.t()}
def format(
%Suite{
scenarios: scenarios,
system: system,
configuration: %Configuration{unit_scaling: unit_scaling}
},
opts
) do
ensure_applications_loaded()
%{file: filename, inline_assets: inline_assets} = default_configuration(opts)
scenarios
|> Enum.group_by(fn scenario -> scenario.input_name end)
|> Enum.map(fn tagged_scenarios ->
reports_for_input(tagged_scenarios, system, filename, unit_scaling, inline_assets)
end)
|> add_index(filename, system, inline_assets)
|> List.flatten()
|> Map.new()
end
@doc """
Writes the output of `Benchee.Formatters.HTML.format/2` to disk.
Generates the following files:
* index file (exactly like `file` is named)
* a comparison of all the benchmarked jobs (one per benchmarked input)
* for each job a detail page with more detailed run time graphs for that
particular job (one per benchmark input)
"""
@spec write(%{Suite.key() => String.t()}, map) :: :ok
def write(data, opts) do
%{
file: filename,
auto_open: auto_open?,
inline_assets: inline_assets?
} = default_configuration(opts)
prepare_folder_structure(filename, inline_assets?)
FileCreation.each(data, filename)
if auto_open?, do: open_report(filename)
:ok
end
defp ensure_applications_loaded do
_ = Application.load(:benchee)
_ = Application.load(:benchee_html)
end
@default_filename "benchmarks/output/results.html"
@default_auto_open true
@default_inline_assets false
defp default_configuration(opts) do
opts
|> Map.put_new(:file, @default_filename)
|> Map.put_new(:auto_open, @default_auto_open)
|> Map.put_new(:inline_assets, @default_inline_assets)
end
defp prepare_folder_structure(filename, inline_assets?) do
base_directory = create_base_directory(filename)
unless inline_assets?, do: copy_asset_files(base_directory)
base_directory
end
defp create_base_directory(filename) do
base_directory = Path.dirname(filename)
File.mkdir_p!(base_directory)
base_directory
end
@asset_directory "assets"
defp copy_asset_files(base_directory) do
asset_target_directory = Path.join(base_directory, @asset_directory)
asset_source_directory = Application.app_dir(:benchee_html, "priv/assets/")
File.cp_r!(asset_source_directory, asset_target_directory)
end
defp reports_for_input({input_name, scenarios}, system, filename, unit_scaling, inline_assets) do
units = Conversion.units(scenarios, unit_scaling)
scenario_reports = scenario_reports(input_name, scenarios, system, units, inline_assets)
comparison = comparison_report(input_name, scenarios, system, filename, units, inline_assets)
[comparison | scenario_reports]
end
defp scenario_reports(input_name, scenarios, system, units, inline_assets) do
Enum.map(scenarios, fn scenario ->
scenario_json = JSON.encode!(scenario)
{
[input_name, scenario.name],
Render.scenario_detail(
scenario,
scenario_json,
system,
units,
inline_assets
)
}
end)
end
defp comparison_report(input_name, scenarios, system, filename, units, inline_assets) do
scenarios_json = JSON.encode!(scenarios)
input_suite = %{
system: system,
job_count: length(scenarios),
filename: filename,
scenarios: scenarios
}
{[input_name, "comparison"],
Render.comparison(input_name, input_suite, units, scenarios_json, inline_assets)}
end
defp add_index(grouped_main_contents, filename, system, inline_assets) do
index_structure = inputs_to_paths(grouped_main_contents, filename)
index_entry = {[], Render.index(index_structure, system, inline_assets)}
[index_entry | grouped_main_contents]
end
defp inputs_to_paths(grouped_main_contents, filename) do
grouped_main_contents
|> Enum.map(fn reports -> input_to_paths(reports, filename) end)
|> Map.new()
end
defp input_to_paths(input_reports, filename) do
[{[input_name | _], _} | _] = input_reports
paths =
Enum.map(input_reports, fn {tags, _content} ->
Render.relative_file_path(filename, tags)
end)
{input_name, paths}
end
defp open_report(filename) do
browser = get_browser()
{_, exit_code} = System.cmd(browser, [filename])
unless exit_code > 0, do: IO.puts("Opened report using #{browser}")
end
defp get_browser do
case :os.type() do
{:unix, :darwin} -> "open"
{:unix, _} -> "xdg-open"
{:win32, _} -> "explorer"
end
end
end