Current section

Files

Jump to
prometheus src prometheus_time.erl
Raw

src/prometheus_time.erl

-module(prometheus_time).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC("""
Measuring time intervals with Prometheus.erl.
Measuring time intervals is trivial - you just have to be sure you are using monotonic time source.
Basically interval is a difference between start time and end time.
Erlang has the standard `erlang:monotonic_time/0` function that returns so called native time units.
Native time units are meaningless and have to be converted to seconds (or other units) using
`erlang:convert_time_unit/3`.
However as `erlang:convert_time_unit/3` documentation
[warns](http://erlang.org/doc/man/erlang.html#convert_time_unit-3):
> You may lose accuracy and precision when converting between time units.
> In order to minimize such loss, collect all data at native time unit and
> do the conversion on the end result.
and because Prometheus mandates support for floats, `set_duration/observe_duration` functions
always work with native time units and conversion is delayed until scraping/retrieving value.
To implement this, metric needs to know desired time unit.
Users can specify time unit explicitly via `duration_unit` or implicitly via metric name
(preferred, since prometheus best practices guide insists on `<name>_duration_<unit>`
metric name format).
Possible units:
- microseconds;
- milliseconds;
- seconds;
- minutes;
- hours;
- days;
Histogram also converts buckets bounds to native units if duration_unit is provided.
It converts it back when scraping or retrieving value.
If values already converted to a 'real' unit,
conversion can be disabled by setting `duration_unit` to `false`.
## Examples
Example where duration unit derived from name:
```erlang
prometheus_histogram:new([{name, fun_duration_seconds},
{buckets, [0.5, 1.1]}, %% in seconds
{help, ""}]),
prometheus_histogram:observe_duration(fun_duration_seconds,
fun () ->
timer:sleep(1000)
end),
prometheus_histogram:value(fun_duration_seconds).
{[0,1,0],1.001030886}
```
Example where duration unit set explicitly:
```erlang
prometheus_histogram:new([{name, fun_duration_histogram},
{buckets, [500, 1100]}, %% in milliseconds
{help, ""},
{duration_unit, milliseconds}]),
prometheus_histogram:observe_duration(fun_duration_histogram,
fun () ->
timer:sleep(1000)
end),
prometheus_histogram:value(fun_duration_histogram).
{[0,1,0],1001.885302}
```
Example where value is in seconds already:
```erlang
prometheus_histogram:new([{name, duration_seconds},
{buckets, [0.5, 1.1]}, %% in seconds
{help, ""},
{duration_unit, false}]),
prometheus_histogram:dobserve(duration_seconds, 1.2),
prometheus_histogram:value(duration_seconds).
{[0,0,1],1.2}
```
""").
-export([
duration_unit_from_string/1,
validate_duration_unit/1,
maybe_convert_to_native/2,
maybe_convert_to_du/2
]).
-ifdef(TEST).
-export([from_native/2, to_native/2]).
-endif.
-define(DURATION_UNITS, [
{"microseconds", microseconds},
{"milliseconds", milliseconds},
{"seconds", seconds},
{"minutes", minutes},
{"hours", hours},
{"days", days}
]).
-type duration_unit() ::
microseconds
| milliseconds
| seconds
| minutes
| hours
| days.
-type maybe_duration_unit() :: false | undefined | duration_unit().
-export_type([duration_unit/0, maybe_duration_unit/0]).
-spec duration_unit_from_string(string()) -> duration_unit() | undefined.
duration_unit_from_string(Str) ->
duration_unit_from_string(Str, ?DURATION_UNITS).
-spec validate_duration_unit(maybe_duration_unit()) -> maybe_duration_unit().
validate_duration_unit(false) ->
false;
validate_duration_unit(undefined) ->
undefined;
validate_duration_unit(SDU) ->
case
lists:any(
fun({_, DU}) ->
DU == SDU
end,
?DURATION_UNITS
)
of
true ->
SDU;
_ ->
erlang:error({invalid_value, SDU, "unknown duration unit"})
end.
-spec maybe_convert_to_native(duration_unit(), infinity | number()) -> infinity | number().
maybe_convert_to_native(_, infinity) ->
infinity;
maybe_convert_to_native(DU, Value) ->
case DU of
undefined -> Value;
_ -> to_native(Value, DU)
end.
-spec maybe_convert_to_du(duration_unit(), infinity | number()) -> infinity | number().
maybe_convert_to_du(_, infinity) ->
infinity;
maybe_convert_to_du(DU, Value) ->
case DU of
undefined -> Value;
_ -> from_native(Value, DU)
end.
%%====================================================================
%% Private Parts
%%====================================================================
duration_unit_from_string(Str, [{SDU, DU} | Rest]) ->
case string:rstr(Str, SDU) of
0 -> duration_unit_from_string(Str, Rest);
_ -> DU
end;
duration_unit_from_string(_, []) ->
undefined.
from_native(Value) ->
erlang:convert_time_unit(trunc(Value), native, nanosecond).
-spec from_native(number(), duration_unit()) -> number().
from_native(Value, microseconds) ->
Nanoseconds = from_native(Value),
Nanoseconds / 1000;
from_native(Value, milliseconds) ->
Nanoseconds = from_native(Value),
Nanoseconds / 1000000;
from_native(Value, seconds) ->
Nanoseconds = from_native(Value),
Nanoseconds / 1000000000;
from_native(Value, minutes) ->
Nanoseconds = from_native(Value),
Nanoseconds / 60000000000;
from_native(Value, hours) ->
Nanoseconds = from_native(Value),
Nanoseconds / 3600000000000;
from_native(Value, days) ->
Nanoseconds = from_native(Value),
Nanoseconds / 86400000000000.
to_native(Value) ->
erlang:convert_time_unit(trunc(Value), nanosecond, native).
-spec to_native(number(), duration_unit()) -> number().
to_native(Value, microseconds) ->
to_native(Value * 1000);
to_native(Value, milliseconds) ->
to_native(Value * 1000000);
to_native(Value, seconds) ->
to_native(Value * 1000000000);
to_native(Value, minutes) ->
to_native(Value * 60000000000);
to_native(Value, hours) ->
to_native(Value * 3600000000000);
to_native(Value, days) ->
to_native(Value * 86400000000000).