defmodule Benchee.Conversion.Duration do
@moduledoc """
Unit scaling for duration converting from microseconds to minutes and others.
Only Benchee plugins should use this code.
"""
alias Benchee.Conversion.{Format, Scale, Unit}
@behaviour Scale
@behaviour Format
@nanoseconds_per_microsecond 1000
@microseconds_per_millisecond 1000
@milliseconds_per_second 1000
@seconds_per_minute 60
@minutes_per_hour 60
@nanoseconds_per_millisecond @nanoseconds_per_microsecond * @microseconds_per_millisecond
@nanoseconds_per_second @nanoseconds_per_millisecond * @milliseconds_per_second
@nanoseconds_per_minute @nanoseconds_per_second * @seconds_per_minute
@nanoseconds_per_hour @nanoseconds_per_minute * @minutes_per_hour
@units_map %{
hour: %Unit{
name: :hour,
magnitude: @nanoseconds_per_hour,
label: "h",
long: "Hours"
},
minute: %Unit{
name: :minute,
magnitude: @nanoseconds_per_minute,
label: "min",
long: "Minutes"
},
second: %Unit{
name: :second,
magnitude: @nanoseconds_per_second,
label: "s",
long: "Seconds"
},
millisecond: %Unit{
name: :millisecond,
magnitude: @nanoseconds_per_millisecond,
label: "ms",
long: "Milliseconds"
},
microsecond: %Unit{
name: :microsecond,
magnitude: @nanoseconds_per_microsecond,
label: "μs",
long: "Microseconds"
},
nanosecond: %Unit{
name: :nanosecond,
magnitude: 1,
label: "ns",
long: "Nanoseconds"
}
}
@units Map.values(@units_map)
@doc """
Scales a duration value in nanoseconds into a larger unit if appropriate
## Examples
iex> {value, unit} = scale(1)
iex> value
1.0
iex> unit.name
:nanosecond
iex> {value, unit} = scale(1_234)
iex> value
1.234
iex> unit.name
:microsecond
iex> {value, unit} = scale(11_234_567_890_123)
iex> value
3.1207133028119443
iex> unit.name
:hour
"""
def scale(duration) when duration >= @nanoseconds_per_hour do
scale_with_unit(duration, :hour)
end
def scale(duration) when duration >= @nanoseconds_per_minute do
scale_with_unit(duration, :minute)
end
def scale(duration) when duration >= @nanoseconds_per_second do
scale_with_unit(duration, :second)
end
def scale(duration) when duration >= @nanoseconds_per_millisecond do
scale_with_unit(duration, :millisecond)
end
def scale(duration) when duration >= @nanoseconds_per_microsecond do
scale_with_unit(duration, :microsecond)
end
def scale(duration) do
scale_with_unit(duration, :nanosecond)
end
# Helper function for returning a tuple of {value, unit}
defp scale_with_unit(duration, unit) do
{scale(duration, unit), unit_for(unit)}
end
@doc """
Get a unit by its atom representation. If handed already a %Unit{} struct it
just returns it.
## Examples
iex> unit_for :hour
%Benchee.Conversion.Unit{
name: :hour,
magnitude: 3_600_000_000_000,
label: "h",
long: "Hours"
}
iex> unit_for(%Benchee.Conversion.Unit{
...> name: :hour,
...> magnitude: 3_600_000_000_000,
...> label: "h",
...> long: "Hours"
...>})
%Benchee.Conversion.Unit{
name: :hour,
magnitude: 3_600_000_000_000,
label: "h",
long: "Hours"
}
"""
def unit_for(unit) do
Scale.unit_for(@units_map, unit)
end
def units do
@units
end
@doc """
Scales a duration value in nanoseconds into a value in the specified unit
## Examples
iex> scale(12345, :nanosecond)
12345.0
iex> scale(12345, :microsecond)
12.345
iex> scale(12345, :minute)
2.0575e-7
"""
def scale(count, unit) do
Scale.scale(count, unit, __MODULE__)
end
@doc """
Converts a value for a specified %Unit or unit atom and converts it to the equivalent of another unit of measure.
## Examples
iex> {value, unit} = convert({90, :minute}, :hour)
iex> value
1.5
iex> unit.name
:hour
"""
def convert(number_and_unit, desired_unit) do
Scale.convert(number_and_unit, desired_unit, __MODULE__)
end
@doc """
Converts a value of the given unit into the desired unit, returning only the value not the unit.
## Examples
iex> convert_value({1.234, :second}, :microsecond)
1_234_000.0
iex> convert_value({1.234, :minute}, :microsecond)
7.404e7
iex> microseconds = convert_value({1.234, :minute}, :microsecond)
iex> {value, _} = convert({microseconds, :microsecond}, :minute)
iex> value
1.234
"""
def convert_value({duration, unit}, desired_unit) do
{value, _} = convert({duration, unit}, desired_unit)
value
end
@doc """
Finds the best unit for a list of durations. By default, chooses the most common
unit. In case of tie, chooses the largest of the most common units.
Pass `[strategy: :smallest]` to always return the smallest unit in the list.
Pass `[strategy: :largest]` to always return the largest unit in the list.
## Examples
iex> best([23, 23_000, 34_000, 2_340_000]).name
:microsecond
iex> best([23, 23_000, 34_000_000, 2_340_000_000, 3_450_000_000]).name
:second
iex> best([23, 23_000, 34_000, 2_340_000], strategy: :smallest).name
:nanosecond
iex> best([23, 23_000, 34_000, 2_340_000_000], strategy: :largest).name
:second
"""
def best(list, opts \\ [strategy: :best])
def best(list, opts) do
Scale.best_unit(list, __MODULE__, opts)
end
@doc """
The most basic unit in which measurements occur.
## Examples
iex> base_unit().name
:nanosecond
"""
def base_unit, do: unit_for(:nanosecond)
@doc """
Formats a number as a string, with a unit label. To specify the unit, pass
a tuple of `{value, unit_atom}` like `{1_234, :second}`.
## Examples
iex> format(45_678.9)
"45.68 μs"
iex> format(45.6789)
"45.68 ns"
iex> format({45.6789, :millisecond})
"45.68 ms"
iex> format {45.6789,
...> %Benchee.Conversion.Unit{
...> long: "Milliseconds", magnitude: 1000, label: "ms"}
...> }
"45.68 ms"
"""
def format(duration) do
Format.format(duration, __MODULE__)
end
@doc """
Formats in a more "human" way - 1h 30min instead of 1.5h.
## Examples
iex> format_human(5_400_000_000_000)
"1 h 30 min"
iex> format_human(12.5)
"12.50 ns"
iex> format_human(1000.555)
"1 μs 0.55 ns"
iex> format_human(3_660_001_001_000)
"1 h 1 min 1 ms 1 μs"
iex> format_human(0)
"0 ns"
iex> format_human(2 * 1000 * 1000 * 1000)
"2 s"
"""
def format_human(duration) do
Format.format_human(duration, __MODULE__)
end
end