Page MenuHomePhorge

No OneTemporary

Size
71 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/prometheus/error.ex b/lib/prometheus/error.ex
index a634850..7ec9185 100644
--- a/lib/prometheus/error.ex
+++ b/lib/prometheus/error.ex
@@ -1,195 +1,207 @@
defmodule Prometheus.InvalidValueError do
@moduledoc """
Raised when given `value` is invalid i.e. when you pass a negative number to
`Prometheus.Metric.Counter.inc/2`.
"""
defexception [:value, :orig_message]
def message(%{value: value, orig_message: message}) do
"Invalid value: #{inspect value} (#{message})."
end
end
defmodule Prometheus.InvalidMetricNameError do
@moduledoc """
Raised when given metric `name` is invalid i.e. can't be represented as printable utf-8
string that matches `^[a-zA-Z_:][a-zA-Z0-9_:]*$` regular expression.
"""
defexception [:name]
def message(%{name: name}) do
"Invalid metric name: #{name}."
end
end
defmodule Prometheus.InvalidMetricLabelsError do
@moduledoc """
Raised when `labels` isn't a list.
"""
defexception [:labels]
def message(%{labels: labels}) do
"Invalid metric labels: #{labels}."
end
end
defmodule Prometheus.InvalidMetricHelpError do
@moduledoc """
Raised when given metric `help` is invalid i.e. isn't a printable utf-8 string.
"""
defexception [:help]
def message(%{help: help}) do
"Invalid metric help: #{help}."
end
end
defmodule Prometheus.InvalidMetricArityError do
@moduledoc """
Raised when metric arity is invalid e.g. counter metric was created with two labels but
only one label value is passed to `Prometheus.Metric.Counter.inc/2`.
"""
defexception [:present, :expected]
def message(%{present: present, expected: expected}) do
"Invalid metric arity: got #{present}, expected #{expected}."
end
end
defmodule Prometheus.UnknownMetricError do
defexception [:registry, :name]
def message(%{registry: registry, name: name}) do
"Unknown metric {registry: #{registry}, name: #{name}}."
end
end
defmodule Prometheus.InvalidLabelNameError do
@moduledoc """
Raised when label `name` is invalid i.e. can't be represented as printable utf-8 string
that matches `^[a-zA-Z_][a-zA-Z0-9_]*$` regular expression or starts with `__`.
Metric can impose further restrictions on label names.
"""
defexception [:name, :orig_message]
def message(%{name: name, orig_message: message}) do
"Invalid label name: #{name} (#{message})."
end
end
defmodule Prometheus.MFAlreadyExistsError do
@moduledoc """
Raised when one tries to create metric in `registry` with `name` it already exists.
"""
defexception [:registry, :name]
def message(%{registry: registry, name: name}) do
"Metric #{registry}:#{name} already exists."
end
end
defmodule Prometheus.HistogramNoBucketsError do
@moduledoc """
Raised by histogram constructors when buckets can't be found in spec, or
found value is empty list.
"""
defexception [:buckets]
def message(%{buckets: buckets}) do
"Invalid histogram buckets: #{buckets}."
end
end
defmodule Prometheus.HistogramInvalidBucketsError do
@moduledoc """
Raised by histogram constructors when buckets are invalid i.e. not sorted in increasing
order or generator spec is unknown.
"""
defexception [:buckets, :orig_message]
def message(%{buckets: buckets, orig_message: message}) do
buckets = :io_lib.format("~p", [buckets])
"Invalid histogram buckets: #{buckets} (#{message})."
end
end
defmodule Prometheus.HistogramInvalidBoundError do
@moduledoc """
Raised by histogram constructors when bucket bound isn't a number.
"""
defexception [:bound]
def message(%{bound: bound}) do
"Invalid histogram bound: #{bound}."
end
end
defmodule Prometheus.MissingMetricSpecKeyError do
@moduledoc """
Raised when required metric `spec` `key` is missing. All metrics
require at least `name` and when metric created `help`.
Metrics can have their specific required keys.
"""
defexception [:key, :spec]
def message(%{key: key}) do
"Required key #{key} is missing from metric spec."
end
end
+defmodule Prometheus.InvalidBlockArityError do
+ @moduledoc """
+ Raised when fn passed as block has more then 0 arguments
+ """
+ defexception [:args]
+
+ def message(%{args: args}) do
+ insp = Enum.map_join(args, ", ", &inspect/1)
+ "Fn with arity #{length(args)} (args: #{insp}) passed as block."
+ end
+end
+
defmodule Prometheus.Error do
@moduledoc false
@lint [{Credo.Check.Refactor.ABCSize, false},
{Credo.Check.Refactor.CyclomaticComplexity, false}]
def normalize(erlang_error) do
case erlang_error do
%ErlangError{original: original} ->
case original do
{:invalid_value, value, message} ->
%Prometheus.InvalidValueError{value: value, orig_message: message}
{:invalid_metric_name, name, _message} ->
%Prometheus.InvalidMetricNameError{name: name}
{:invalid_metric_help, help, _message} ->
%Prometheus.InvalidMetricHelpError{help: help}
{:invalid_metric_arity, present, expected} ->
%Prometheus.InvalidMetricArityError{present: present, expected: expected}
{:unknown_metric, registry, name} ->
%Prometheus.UnknownMetricError{registry: registry, name: name}
{:invalid_metric_labels, labels, _message} ->
%Prometheus.InvalidMetricLabelsError{labels: labels}
{:invalid_metric_label_name, name, message} ->
%Prometheus.InvalidLabelNameError{name: name, orig_message: message}
{:mf_already_exists, {registry, name}, _message} ->
%Prometheus.MFAlreadyExistsError{registry: registry, name: name}
{:histogram_no_buckets, buckets} ->
%Prometheus.HistogramNoBucketsError{buckets: buckets}
{:histogram_invalid_buckets, buckets, message} ->
%Prometheus.HistogramInvalidBucketsError{buckets: buckets,
orig_message: message}
{:histogram_invalid_bound, bound} ->
%Prometheus.HistogramInvalidBoundError{bound: bound}
{:missing_metric_spec_key, key, spec} ->
%Prometheus.MissingMetricSpecKeyError{key: key, spec: spec}
_ ->
erlang_error
end
_ ->
erlang_error
end
end
defmacro with_prometheus_error(block) do
quote do
try do
unquote(block)
rescue
e in ErlangError -> reraise Prometheus.Error.normalize(e), System.stacktrace
end
end
end
end
diff --git a/lib/prometheus/injector.ex b/lib/prometheus/injector.ex
index d7d4ed8..c5083fe 100644
--- a/lib/prometheus/injector.ex
+++ b/lib/prometheus/injector.ex
@@ -1,83 +1,94 @@
defmodule Prometheus.Injector do
def inject(callback, env, ast) do
ast
|> Macro.prewalk(fn (thing) ->
case thing do
{:def, _, _} = defun -> defun
{:in, _, _} = arrow -> arrow #otherwise e in RuntimeError will be rewritten
_ -> Macro.expand(thing, env)
end
end)
|> inject_(callback)
end
# lambda
def inject_({:fn, fn_meta, [{:->, arrow_meta, [args, do_block]}]}, callback) do
- {:fn, fn_meta, [{:->, arrow_meta, [args, callback.(do_block)]}]}
+ case args do
+ [] ->
+ callback.({{:., [],
+ [{:fn, fn_meta,
+ [{:->, arrow_meta,
+ [[], do_block]}]}]}, [],
+ []})
+ _ ->
+ names = args
+ |> Enum.map(fn({name, _, _}) -> name end)
+ raise Prometheus.InvalidBlockArityError, args: names
+ end
end
# do_blocks can be simple calls or defs
def inject_([{:do, {:__block__, [], do_blocks}}], callback) do
do_blocks = List.flatten(do_blocks)
if have_defs(do_blocks) do
Enum.map(do_blocks, &inject_to_def(&1, callback))
else
- [{:do, callback.({:__block__, [], do_blocks})}]
+ callback.({:__block__, [], do_blocks})
end
end
# just do
def inject_([{:do, do_block}], callback) do
inject_([{:do, {:__block__, [], [do_block]}}], callback)
end
# implicit try
def inject_([{:do, _do_block} | rest] = all, callback) do
if is_try_unwrapped(rest) do
callback.(
quote do
try unquote(all)
end)
else
raise "Unexpected do block #{inspect rest}"
end
end
- # single dos, or other non-block stuff like function calls
+ # single do, or other non-block stuff like function calls
def inject_(thing, callback) do
inject_([{:do, {:__block__, [], [thing]}}], callback)
end
defp is_try_unwrapped(block) do
Keyword.has_key?(block, :catch)
|| Keyword.has_key?(block, :rescue)
|| Keyword.has_key?(block, :after)
|| Keyword.has_key?(block, :else)
end
defp have_defs(blocks) do
defs_count = Enum.count(blocks, fn
({:def, _, _}) -> true
(_) -> false
end)
blocks_count = Enum.count(blocks)
case defs_count do
0 -> false
^blocks_count -> true
_ -> raise "Mixing defs and other blocks isn't allowed"
end
end
defp inject_to_def({:def, def_meta, [head, [do: body]]}, callback) do
{:def, def_meta, [head, [do: callback.(body)]]}
end
- defp inject_to_def({:def, def_meta, [head, [{:do, _do_block} | rest] = all]}, callback) do
+ defp inject_to_def({:def, def_meta, [head, [{:do, _do_block} | _rest] = all]}, callback) do
{:def, def_meta, [head, [do: callback.(
quote do
try unquote(all)
- end)]]}
+ end)]]}
end
end
diff --git a/lib/prometheus/metric/counter.ex b/lib/prometheus/metric/counter.ex
index 9723232..874c550 100644
--- a/lib/prometheus/metric/counter.ex
+++ b/lib/prometheus/metric/counter.ex
@@ -1,221 +1,222 @@
defmodule Prometheus.Metric.Counter do
@moduledoc """
Counter is a Metric that represents a single numerical value that only ever
goes up. That implies that it cannot be used to count items whose number can
also go down, e.g. the number of currently running processes. Those
"counters" are represented by `Prometheus.Metric.Gauge`.
A Counter is typically used to count requests served, tasks completed, errors
occurred, etc.
Example use cases for Counters:
- Number of requests processed;
- Number of items that were inserted into a queue;
- Total amount of data that a system has processed.
Use the [`rate()`](https://prometheus.io/docs/querying/functions/#rate())/
[`irate()`](https://prometheus.io/docs/querying/functions/#irate())
functions in Prometheus to calculate the rate of increase of a Counter.
By convention, the names of Counters are suffixed by `_total`.
To create a counter use either `new/1` or `declare/1`, the difference is that
`new/` will raise `Prometheus.MFAlreadyExistsError` exception if counter with
the same `registry`, `name` and `labels` combination already exists.
Both accept `spec` `Keyword` with the same set of keys:
- `:registry` - optional, default is `:default`;
- `:name` - required, can be an atom or a string;
- `:help` - required, must be a string;
- `:labels` - optional, default is `[]`.
Example:
```
defmodule MyServiceInstrumenter do
use Prometheus.Metric
## to be called at app/supervisor startup.
## to tolerate restarts use declare.
def setup() do
Counter.declare([name: :my_service_requests_total,
help: "Requests count.",
labels: [:caller]])
end
def inc(caller) do
Counter.inc([name: :my_service_requests_total,
labels: [caller]])
end
end
```
"""
use Prometheus.Erlang, :prometheus_counter
@doc """
Creates a counter using `spec`.
Raises `Prometheus.MissingMetricSpecKeyError` if required `spec` key is missing.<br>
Raises `Prometheus.InvalidMetricNameError` if metric name is invalid.<br>
Raises `Prometheus.InvalidMetricHelpError` if help is invalid.<br>
Raises `Prometheus.InvalidMetricLabelsError` if labels isn't a list.<br>
Raises `Prometheus.InvalidLabelNameError` if label name is invalid.<br>
Raises `Prometheus.MFAlreadyExistsError` if a counter with
the same `spec` already exists.
"""
defmacro new(spec) do
Erlang.call([spec])
end
@doc """
Creates a counter using `spec`.
If a counter with the same `spec` exists returns `false`.
Raises `Prometheus.MissingMetricSpecKeyError` if required `spec` key is missing.<br>
Raises `Prometheus.InvalidMetricNameError` if metric name is invalid.<br>
Raises `Prometheus.InvalidMetricHelpError` if help is invalid.<br>
Raises `Prometheus.InvalidMetricLabelsError` if labels isn't a list.<br>
Raises `Prometheus.InvalidLabelNameError` if label name is invalid.
"""
defmacro declare(spec) do
Erlang.call([spec])
end
@doc """
Increments the counter identified by `spec` by `value`.
Raises `Prometheus.InvalidValueError` exception if `value` isn't a positive integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a counter
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro inc(spec, value \\ 1) do
Erlang.metric_call(:inc, spec, [value])
end
@doc """
Increments the counter identified by `spec` by 1 when `body` executed.
Read more about bodies: `Prometheus.Injector`.
Raises `Prometheus.InvalidValueError` exception if `value` isn't a positive integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a counter
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro count(spec, body) do
env = __CALLER__
Prometheus.Injector.inject(fn(block) ->
quote do
Prometheus.Metric.Counter.inc(unquote(spec), 1)
unquote(block)
end
end, env, body)
end
@doc """
Increments the counter identified by `spec` by 1 when `body` raises `exception`.
Read more about bodies: `Prometheus.Injector`.
Raises `Prometheus.InvalidValueError` exception if `value` isn't a positive integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a counter
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro count_exceptions(spec, exception \\ :_, body) do
env = __CALLER__
Prometheus.Injector.inject(fn(block) ->
quote do
require Prometheus.Error
Prometheus.Error.with_prometheus_error(
try do
unquote(block)
rescue
e in unquote(exception) ->
stacktrace = System.stacktrace()
{registry, name, labels} = Prometheus.Metric.parse_spec(unquote(spec))
:prometheus_counter.inc(registry, name, labels, 1)
reraise(e, stacktrace)
end)
end
end, env, body)
end
@doc """
Increments the counter identified by `spec` by 1 when `body` raises no exceptions.
Read more about bodies: `Prometheus.Injector`.
Raises `Prometheus.InvalidValueError` exception if `value` isn't a positive integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a counter
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro count_no_exceptions(spec, body) do
env = __CALLER__
Prometheus.Injector.inject(fn(block) ->
quote do
+ require Prometheus.Error
try do
unquote(block)
else
value ->
Prometheus.Metric.Counter.inc(unquote(spec), 1)
value
end
end
end, env, body)
end
@doc """
Increments the counter identified by `spec` by `value`.
If `value` happened to be a float number even one time(!) you
shouldn't use `inc/2` after dinc.
Raises `Prometheus.InvalidValueError` exception if `value` isn't a positive number.<br>
Raises `Prometheus.UnknownMetricError` exception if a counter
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro dinc(spec, value \\ 1) do
Erlang.metric_call({:prometheus_counter, :dinc}, spec, [value])
end
@doc """
Removes counter series identified by spec.
Raises `Prometheus.UnknownMetricError` exception if a counter
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro remove(spec) do
Erlang.metric_call(spec)
end
@doc """
Resets the value of the counter identified by `spec`.
Raises `Prometheus.UnknownMetricError` exception if a counter
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro reset(spec) do
Erlang.metric_call(spec)
end
@doc """
Returns the value of the counter identified by `spec`. If there is no counter for
given labels combination, returns `:undefined`.
Raises `Prometheus.UnknownMetricError` exception if a counter
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro value(spec) do
Erlang.metric_call(spec)
end
end
diff --git a/lib/prometheus/metric/gauge.ex b/lib/prometheus/metric/gauge.ex
index b078f70..29c41d4 100644
--- a/lib/prometheus/metric/gauge.ex
+++ b/lib/prometheus/metric/gauge.ex
@@ -1,226 +1,240 @@
defmodule Prometheus.Metric.Gauge do
@moduledoc """
Gauge metric, to report instantaneous values.
Gauge is a metric that represents a single numerical value that can
arbitrarily go up and down.
A Gauge is typically used for measured values like temperatures or current
memory usage, but also "counts" that can go up and down, like the number of
running processes.
Example use cases for Gauges:
- Inprogress requests;
- Number of items in a queue;
- Free memory;
- Total memory;
- Temperature.
Example:
```
defmodule MyPoolInstrumenter do
use Prometheus.Metric
## to be called at app/supervisor startup.
## to tolerate restarts use declare.
def setup() do
Gauge.declare([name: :my_pool_size,
help: "Pool size."])
Gauge.declare([name: :my_pool_checked_out,
help: "Number of sockets checked out from the pool"])
end
def set_size(size) do
Gauge.set([name: :my_pool_size], size)
end
def track_checked_out_sockets(checkout_fun) do
Gauge.track_inprogress([name: :my_pool_checked_out], checkout_fun)
end
end
```
"""
use Prometheus.Erlang, :prometheus_gauge
@doc """
Creates a gauge using `spec`.
Raises `Prometheus.MissingMetricSpecKeyError` if required `spec` key is missing.<br>
Raises `Prometheus.InvalidMetricNameError` if metric name is invalid.<br>
Raises `Prometheus.InvalidMetricHelpError` if help is invalid.<br>
Raises `Prometheus.InvalidMetricLabelsError` if labels isn't a list.<br>
Raises `Prometheus.InvalidMetricNameError` if label name is invalid.<br>
Raises `Prometheus.InvalidValueError` exception if duration_unit is unknown or
doesn't match metric name.<br>
Raises `Prometheus.MFAlreadyExistsError` if a gauge with the same `spec` exists.
"""
defmacro new(spec) do
Erlang.call([spec])
end
@doc """
Creates a gauge using `spec`.
If a gauge with the same `spec` exists returns `false`.
Raises `Prometheus.MissingMetricSpecKeyError` if required `spec` key is missing.<br>
Raises `Prometheus.InvalidMetricNameError` if metric name is invalid.<br>
Raises `Prometheus.InvalidMetricHelpError` if help is invalid.<br>
Raises `Prometheus.InvalidMetricLabelsError` if labels isn't a list.<br>
Raises `Prometheus.InvalidMetricNameError` if label name is invalid.<br>
Raises `Prometheus.InvalidValueError` exception if duration_unit is unknown or
doesn't match metric name.
"""
defmacro declare(spec) do
Erlang.call([spec])
end
@doc """
Sets the gauge identified by `spec` to `value`.
Raises `Prometheus.InvalidValueError` exception if `value` isn't
a number or `:undefined`.<br>
Raises `Prometheus.UnknownMetricError` exception if a gauge for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro set(spec, value) do
Erlang.metric_call(spec, [value])
end
@doc """
Increments the gauge identified by `spec` by `value`.
Raises `Prometheus.InvalidValueError` exception if `value` isn't an integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a gauge for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro inc(spec, value \\ 1) do
Erlang.metric_call(spec, [value])
end
@doc """
Decrements the gauge identified by `spec` by `value`.
Raises `Prometheus.InvalidValueError` exception if `value` isn't an integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a gauge for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro dec(spec, value \\ 1) do
Erlang.metric_call(spec, [value])
end
@doc """
Increments the gauge identified by `spec` by `value`.
If `value` happened to be a float number even one time(!) you shouldn't
use `inc/2` or `dec/2` after dinc.
Raises `Prometheus.InvalidValueError` exception if `value` isn't a number.<br>
Raises `Prometheus.UnknownMetricError` exception if a gauge
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro dinc(spec, value \\ 1) do
Erlang.metric_call(spec, [value])
end
@doc """
Decrements the gauge identified by `spec` by `value`.
If `value` happened to be a float number even one time(!) you shouldn't
use `inc/2` or `dec/2` after ddec.
Raises `Prometheus.InvalidValueError` exception if `value` isn't a number.<br>
Raises `Prometheus.UnknownMetricError` exception if a gauge
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro ddec(spec, value \\ 1) do
Erlang.metric_call(spec, [value])
end
@doc """
Sets the gauge identified by `spec` to the current unixtime.
Raises `Prometheus.UnknownMetricError` exception if a gauge
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro set_to_current_time(spec) do
Erlang.metric_call(spec)
end
@doc """
- Sets the gauge identified by `spec` to the number of currently executing `fun`s.
+ Sets the gauge identified by `spec` to the number of currently executing `body`s.
Raises `Prometheus.UnknownMetricError` exception if a gauge
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
Raises `Prometheus.InvalidValueError` exception if fun isn't a function or block.
"""
- defmacro track_inprogress(spec, fun) do
- Erlang.metric_call(spec, [Erlang.ensure_fn(fun)])
- end
-
- defmacro track_inprogress(spec) do
- quote do
- @instrument {unquote(__MODULE__), :track_inprogress, unquote(spec)}
- end
+ defmacro track_inprogress(spec, body) do
+ env = __CALLER__
+ Prometheus.Injector.inject(fn(block) ->
+ quote do
+ Prometheus.Metric.Gauge.inc(unquote(spec))
+ try do
+ unquote(block)
+ after
+ Prometheus.Metric.Gauge.dec(unquote(spec))
+ end
+ end
+ end, env, body)
end
@doc """
- Tracks the amount of time spent executing `fun`.
+ Tracks the amount of time spent executing `body`.
Raises `Prometheus.UnknownMetricError` exception if a gauge
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
Raises `Prometheus.InvalidValueError` exception if `fun` isn't a function or block.
"""
- defmacro set_duration(spec, fun) do
- Erlang.metric_call(spec, [Erlang.ensure_fn(fun)])
+ defmacro set_duration(spec, body) do
+ env = __CALLER__
+ Prometheus.Injector.inject(fn(block) ->
+ quote do
+ start_time = :erlang.monotonic_time()
+ try do
+ unquote(block)
+ after
+ end_time = :erlang.monotonic_time()
+ Prometheus.Metric.Gauge.set(unquote(spec), end_time - start_time)
+ end
+ end
+ end, env, body)
end
@doc """
Removes gauge series identified by spec.
Raises `Prometheus.UnknownMetricError` exception if a gauge
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro remove(spec) do
Erlang.metric_call(spec)
end
@doc """
Resets the value of the gauge identified by `spec`.
Raises `Prometheus.UnknownMetricError` exception if a gauge
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro reset(spec) do
Erlang.metric_call(spec)
end
@doc """
Returns the value of the gauge identified by `spec`.
If duration unit set, value will be converted to the duration unit.
[Read more here.](time.html)
Raises `Prometheus.UnknownMetricError` exception if a gauge
for `spec` can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro value(spec) do
Erlang.metric_call(spec)
end
end
-
diff --git a/lib/prometheus/metric/histogram.ex b/lib/prometheus/metric/histogram.ex
index f5b8ba0..c3d32b4 100644
--- a/lib/prometheus/metric/histogram.ex
+++ b/lib/prometheus/metric/histogram.ex
@@ -1,169 +1,180 @@
defmodule Prometheus.Metric.Histogram do
@moduledoc """
A Histogram tracks the size and number of events in buckets.
You can use Histograms for aggregatable calculation of quantiles.
Example use cases for Histograms:
- Response latency;
- Request size.
Histogram expects `buckets` key in a metric spec. Buckets can be:
- a list of numbers in increasing order;
- one of the generate specs (shortcuts for `Prometheus.Buckets` macros)
- :default;
- {:linear, start, step, count};
- {:exponential, start, step, count}.
Example:
```
defmodule ExampleInstrumenter do
use Prometheus.Metric
## to be called at app/supervisor startup.
## to tolerate restarts use declare.
def setup do
Histogram.new([name: :http_request_duration_milliseconds,
labels: [:method],
buckets: [100, 300, 500, 750, 1000],
help: "Http Request execution time."])
end
def instrument(%{time: time, method: method}) do
Histogram.observe([name: :http_request_duration_milliseconds, labels: [method]],
time)
end
end
```
"""
use Prometheus.Erlang, :prometheus_histogram
@doc """
Creates a histogram using `spec`.
Histogram cannot have a label named "le".
Raises `Prometheus.MissingMetricSpecKeyError` if required `spec` key is missing.<br>
Raises `Prometheus.InvalidMetricNameError` if metric name is invalid.<br>
Raises `Prometheus.InvalidMetricHelpError` if help is invalid.<br>
Raises `Prometheus.InvalidMetricLabelsError` if labels isn't a list.<br>
Raises `Prometheus.InvalidMetricNameError` if label name is invalid.<br>
Raises `Prometheus.InvalidValueError` exception if duration_unit is unknown or
doesn't match metric name.<br>
Raises `Prometheus.MFAlreadyExistsError` if a histogram with the same `spec` exists.
Histogram-specific exceptions:
Raises `Prometheus.HistogramNoBucketsError` if buckets are missing, not a list,
empty list or not known buckets spec.<br>
Raises `Prometheus.HistogramInvalidBucketsError` if buckets aren't
in increasing order.<br>
Raises `Prometheus.HistogramInvalidBoundError` if bucket bound isn't a number.
"""
defmacro new(spec) do
Erlang.call([spec])
end
@doc """
Creates a histogram using `spec`.
Histogram cannot have a label named "le".
If a histogram with the same `spec` exists returns `false`.
Raises `Prometheus.MissingMetricSpecKeyError` if required `spec` key is missing.<br>
Raises `Prometheus.InvalidMetricNameError` if metric name is invalid.<br>
Raises `Prometheus.InvalidMetricHelpError` if help is invalid.<br>
Raises `Prometheus.InvalidMetricLabelsError` if labels isn't a list.<br>
Raises `Prometheus.InvalidMetricNameError` if label name is invalid.<br>
Raises `Prometheus.InvalidValueError` exception if duration_unit is unknown or
doesn't match metric name.
Histogram-specific exceptions:
Raises `Prometheus.HistogramNoBucketsError` if buckets are missing, not a list,
empty list or not known buckets spec.<br>
Raises `Prometheus.HistogramInvalidBucketsError` if buckets aren't
in increasing order.<br>
Raises `Prometheus.HistogramInvalidBoundError` if bucket bound isn't a number.
"""
defmacro declare(spec) do
Erlang.call([spec])
end
@doc """
Observes the given amount.
Raises `Prometheus.InvalidValueError` exception if `amount` isn't
a positive integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a histogram for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro observe(spec, amount \\ 1) do
Erlang.metric_call(spec, [amount])
end
@doc """
Observes the given amount.
If `amount` happened to be a float number even one time(!) you shouldn't
use `observe/2` after dobserve.
Raises `Prometheus.InvalidValueError` exception if `amount` isn't
a positive integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a histogram for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro dobserve(spec, amount \\ 1) do
Erlang.metric_call(spec, [amount])
end
@doc """
Observes the amount of seconds spent executing `fun`.
Raises `Prometheus.UnknownMetricError` exception if a histogram for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
Raises `Prometheus.InvalidValueError` exception if fun isn't a function or block.
"""
- defmacro observe_duration(spec, fun) do
- Erlang.metric_call(spec, [Erlang.ensure_fn(fun)])
+ defmacro observe_duration(spec, body) do
+ env = __CALLER__
+ Prometheus.Injector.inject(fn(block) ->
+ quote do
+ start_time = :erlang.monotonic_time()
+ try do
+ unquote(block)
+ after
+ end_time = :erlang.monotonic_time()
+ Prometheus.Metric.Histogram.observe(unquote(spec), end_time - start_time)
+ end
+ end
+ end, env, body)
end
@doc """
Removes histogram series identified by spec.
Raises `Prometheus.UnknownMetricError` exception if a histogram for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro remove(spec) do
Erlang.metric_call(spec)
end
@doc """
Resets the value of the histogram identified by `spec`.
Raises `Prometheus.UnknownMetricError` exception if a histogram for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro reset(spec) do
Erlang.metric_call(spec)
end
@doc """
Returns the value of the histogram identified by `spec`. If there is no histogram for
given labels combination, returns `:undefined`.
Raises `Prometheus.UnknownMetricError` exception if a histogram for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro value(spec) do
Erlang.metric_call(spec)
end
end
diff --git a/lib/prometheus/metric/summary.ex b/lib/prometheus/metric/summary.ex
index 6e215a1..a3a8233 100644
--- a/lib/prometheus/metric/summary.ex
+++ b/lib/prometheus/metric/summary.ex
@@ -1,151 +1,162 @@
defmodule Prometheus.Metric.Summary do
@moduledoc """
Summary metric, to track the size of events.
Example use cases for Summaries:
- Response latency;
- Request size;
- Response size.
Example:
```
defmodule MyProxyInstrumenter do
use Prometheus.Metric
## to be called at app/supervisor startup.
## to tolerate restarts use declare.
def setup() do
Summary.declare([name: :request_size_bytes,
help: "Request size in bytes."])
Summary.declare([name: :response_size_bytes,
help: "Response size in bytes."])
end
def observe_request(size) do
Summary.observe([name: :request_size_bytes], size)
end
def observe_response(size) do
Summary.observe([name: :response_size_bytes], size)
end
end
```
"""
use Prometheus.Erlang, :prometheus_summary
@doc """
Creates a summary using `spec`.
Summary cannot have a label named "quantile".
Raises `Prometheus.MissingMetricSpecKeyError` if required `spec` key is missing.<br>
Raises `Prometheus.InvalidMetricNameError` if metric name is invalid.<br>
Raises `Prometheus.InvalidMetricHelpError` if help is invalid.<br>
Raises `Prometheus.InvalidMetricLabelsError` if labels isn't a list.<br>
Raises `Prometheus.InvalidMetricNameError` if label name is invalid.<br>
Raises `Prometheus.InvalidValueError` exception if duration_unit is unknown or
doesn't match metric name.<br>
Raises `Prometheus.MFAlreadyExistsError` if a summary with the same `spec`
already exists.
"""
defmacro new(spec) do
Erlang.call([spec])
end
@doc """
Creates a summary using `spec`.
Summary cannot have a label named "quantile".
If a summary with the same `spec` exists returns `false`.
Raises `Prometheus.MissingMetricSpecKeyError` if required `spec` key is missing.<br>
Raises `Prometheus.InvalidMetricNameError` if metric name is invalid.<br>
Raises `Prometheus.InvalidMetricHelpError` if help is invalid.<br>
Raises `Prometheus.InvalidMetricLabelsError` if labels isn't a list.<br>
Raises `Prometheus.InvalidMetricNameError` if label name is invalid;<br>
Raises `Prometheus.InvalidValueError` exception if duration_unit is unknown or
doesn't match metric name.
"""
defmacro declare(spec) do
Erlang.call([spec])
end
@doc """
Observes the given amount.
Raises `Prometheus.InvalidValueError` exception if `amount` isn't an integer.<br>
Raises `Prometheus.UnknownMetricError` exception if a summary for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro observe(spec, amount \\ 1) do
Erlang.metric_call(spec, [amount])
end
@doc """
Observes the given amount.
If `amount` happened to be a float number even one time(!) you shouldn't use `observe/2`
after dobserve.
Raises `Prometheus.InvalidValueError` exception if `amount` isn't a number.<br>
Raises `Prometheus.UnknownMetricError` exception if a summary for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro dobserve(spec, amount \\ 1) do
Erlang.metric_call(spec, [amount])
end
@doc """
Observes the amount of seconds spent executing `fun`.
Raises `Prometheus.UnknownMetricError` exception if a summary for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
Raises `Prometheus.InvalidValueError` exception if `fun` isn't a function or block.
"""
- defmacro observe_duration(spec, fun) do
- Erlang.metric_call(spec, [Erlang.ensure_fn(fun)])
+ defmacro observe_duration(spec, body) do
+ env = __CALLER__
+ Prometheus.Injector.inject(fn(block) ->
+ quote do
+ start_time = :erlang.monotonic_time()
+ try do
+ unquote(block)
+ after
+ end_time = :erlang.monotonic_time()
+ Prometheus.Metric.Summary.observe(unquote(spec), end_time - start_time)
+ end
+ end
+ end, env, body)
end
@doc """
Removes summary series identified by spec.
Raises `Prometheus.UnknownMetricError` exception if a summary for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro remove(spec) do
Erlang.metric_call(spec)
end
@doc """
Resets the value of the summary identified by `spec`.
Raises `Prometheus.UnknownMetricError` exception if a summary for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro reset(spec) do
Erlang.metric_call(spec)
end
@doc """
Returns the value of the summary identified by `spec`. If there is no summary for
given labels combination, returns `:undefined`.
If duration unit set, sum will be converted to the duration unit.
[Read more here.](time.html)
Raises `Prometheus.UnknownMetricError` exception if a summary for `spec`
can't be found.<br>
Raises `Prometheus.InvalidMetricArityError` exception if labels count mismatch.
"""
defmacro value(spec) do
Erlang.metric_call(spec)
end
end
diff --git a/test/metric/injector_test.exs b/test/injector_test.exs
similarity index 97%
rename from test/metric/injector_test.exs
rename to test/injector_test.exs
index d27276c..7210ceb 100644
--- a/test/metric/injector_test.exs
+++ b/test/injector_test.exs
@@ -1,157 +1,158 @@
defmodule Injector do
defmacro test(ast) do
Prometheus.Injector.inject(fn(block) ->
quote do
try do
IO.puts("before block")
unquote(block)
after
IO.puts("after block")
end
end
end, __CALLER__, ast)
end
end
defmodule Prometheus.InjectorTest do
use Prometheus.Case
require Injector
Injector.test do
def fun1() do
IO.puts("fun1")
end
def fun2() do
IO.puts("fun2")
end
-
Injector.test do
def fun3() do
IO.puts("fun3")
rescue
e in RuntimeError ->
IO.puts(e)
end
end
end
def do_dangerous_work(x) do
Injector.test do
IO.puts("Doing dangerous work #{x}")
rescue
_ -> IO.puts("Died")
after
IO.puts("Done anyway")
end
end
test "fn" do
- assert capture_io(Injector.test(fn () -> IO.puts("qwe") end)) ==
+ assert capture_io(fn ->
+ Injector.test(fn () -> IO.puts("qwe") end)
+ end) ==
"before block\nqwe\nafter block\n"
end
test "blocks" do
assert capture_io(fn () ->
Injector.test IO.puts("qwe")
end) == "before block\nqwe\nafter block\n"
assert capture_io(fn () ->
Injector.test do: IO.puts("qwe")
end) == "before block\nqwe\nafter block\n"
assert capture_io(fn () ->
Injector.test do
IO.puts("qwe")
IO.puts("qwa")
end
end) == "before block\nqwe\nqwa\nafter block\n"
end
test "implicit try" do
assert capture_io(fn () ->
do_dangerous_work(5)
end) == "before block\nDoing dangerous work 5\nDone anyway\nafter block\n"
assert capture_io(fn () ->
do_dangerous_work({})
end) == "before block\nDied\nDone anyway\nafter block\n"
end
test "defs" do
assert capture_io(fn () ->
fun1()
end) == "before block\nfun1\nafter block\n"
assert capture_io(fn () ->
fun2()
end) == "before block\nfun2\nafter block\n"
assert capture_io(fn () ->
fun3()
end) == "before block\nbefore block\nfun3\nafter block\nafter block\n"
end
defmodule QweInjector do
defmacro inject_(body) do
Prometheus.Injector.inject_(body, fn(b) ->
quote do
IO.puts("qwe")
try do
unquote(b)
after
IO.puts("after_qwe")
end
end
end)
end
defmacro inject1(body) do
body
end
end
defmodule UsefulModule do
require QweInjector
def do_work(x) do
QweInjector.inject_ do
IO.puts("Doing work #{inspect x}")
end
end
def do_dangerous_work(x) do
QweInjector.inject_ do
IO.puts("Doing dangerous work #{x}")
rescue
_-> IO.puts("Died")
after
IO.puts("Done anyway")
end
end
QweInjector.inject_ do
def mildly_interesting(what) do
IO.puts("#{what} is mildly interesting")
end
def wtf(what) do
IO.puts("#{what} is wtf")
rescue
_ -> IO.puts("Oh my, #{inspect what} is real wtf")
else
_ -> IO.puts("Saw wtf and still stronk")
end
end
end
test "UsefulModule" do
assert capture_io(fn () ->
UsefulModule.wtf({})
end) ==
"qwe\nOh my, {} is real wtf\nafter_qwe\n"
end
end
diff --git a/test/metric/gauge_test.exs b/test/metric/gauge_test.exs
index 3e99e43..0612d2c 100644
--- a/test/metric/gauge_test.exs
+++ b/test/metric/gauge_test.exs
@@ -1,458 +1,462 @@
defmodule Prometheus.GaugeTest do
use Prometheus.Case
test "registration" do
spec = [name: :name,
help: "",
registry: :qwe]
assert true == Gauge.declare(spec)
assert false == Gauge.declare(spec)
assert_raise Prometheus.MFAlreadyExistsError,
"Metric qwe:name already exists.",
fn ->
Gauge.new(spec)
end
end
test "spec errors" do
assert_raise Prometheus.MissingMetricSpecKeyError,
"Required key name is missing from metric spec.",
fn ->
Gauge.new([help: ""])
end
assert_raise Prometheus.InvalidMetricNameError,
"Invalid metric name: 12.",
fn ->
Gauge.new([name: 12, help: ""])
end
assert_raise Prometheus.InvalidMetricLabelsError,
"Invalid metric labels: 12.",
fn ->
Gauge.new([name: "qwe", labels: 12, help: ""])
end
assert_raise Prometheus.InvalidMetricHelpError,
"Invalid metric help: 12.",
fn ->
Gauge.new([name: "qwe", help: 12])
end
end
test "gauge specific errors" do
spec = [name: :http_requests_total,
help: ""]
## set
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (set accepts only numbers).",
fn ->
Gauge.set(spec, "qwe")
end
## inc
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (inc accepts only integers).",
fn ->
Gauge.inc(spec, "qwe")
end
assert_raise Prometheus.InvalidValueError,
"Invalid value: -1.5 (inc accepts only integers).",
fn ->
Gauge.inc(spec, -1.5)
end
## dec
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (dec accepts only integers).",
fn ->
Gauge.dec(spec, "qwe")
end
assert_raise Prometheus.InvalidValueError,
"Invalid value: -1.5 (dec accepts only integers).",
fn ->
Gauge.dec(spec, -1.5)
end
## dinc
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (dinc accepts only numbers).",
fn ->
Gauge.dinc(spec, "qwe")
end
## ddec
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (ddec accepts only numbers).",
fn ->
Gauge.ddec(spec, "qwe")
end
## track_inprogress
- assert_raise Prometheus.InvalidValueError,
- "Invalid value: \"qwe\" (track_inprogress accepts only functions).",
+ assert_raise Prometheus.InvalidBlockArityError,
+ "Fn with arity 2 (args: :x, :y) passed as block.",
fn ->
- Gauge.track_inprogress(spec, "qwe")
+ Macro.expand(quote do
+ Gauge.track_inprogress(spec, fn(x, y) -> 1 + x + y end)
+ end, __ENV__)
end
## set_duration
- assert_raise Prometheus.InvalidValueError,
- "Invalid value: \"qwe\" (set_duration accepts only functions).",
+ assert_raise Prometheus.InvalidBlockArityError,
+ "Fn with arity 2 (args: :x, :y) passed as block.",
fn ->
- Gauge.set_duration(spec, "qwe")
+ Macro.expand(quote do
+ Gauge.set_duration(spec, fn(x, y) -> 1 + x + y end)
+ end, __ENV__)
end
end
test "mf/arity errors" do
spec = [name: :metric_with_label,
labels: [:label],
help: ""]
Gauge.declare(spec)
## set
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.set(:unknown_metric, 1)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.set([name: :metric_with_label, labels: [:l1, :l2]], 1)
end
## inc
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.inc(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.inc([name: :metric_with_label, labels: [:l1, :l2]])
end
## dinc
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.dinc(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.dinc([name: :metric_with_label, labels: [:l1, :l2]])
end
## dec
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.dec(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.dec([name: :metric_with_label, labels: [:l1, :l2]])
end
## ddec
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.ddec(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.ddec([name: :metric_with_label, labels: [:l1, :l2]])
end
## set_to_current_time
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.set_to_current_time(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.set_to_current_time([name: :metric_with_label, labels: [:l1, :l2]])
end
## track_inprogress
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.track_inprogress(:unknown_metric, fn -> 1 end)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.track_inprogress([name: :metric_with_label, labels: [:l1, :l2]], fn -> 1 end)
end
## set_duration
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.set_duration(:unknown_metric, fn -> 1 end)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.set_duration([name: :metric_with_label, labels: [:l1, :l2]], fn -> 1 end)
end
## remove
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.remove(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.remove([name: :metric_with_label, labels: [:l1, :l2]])
end
## reset
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.reset(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.reset([name: :metric_with_label, labels: [:l1, :l2]])
end
## value
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Gauge.value(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Gauge.value([name: :metric_with_label, labels: [:l1, :l2]])
end
end
test "set" do
spec = [name: :metric_with_label,
labels: [:label],
help: ""]
Gauge.declare(spec)
Gauge.set(spec, 100)
assert 100 == Gauge.value(spec)
Gauge.set(spec, 105)
assert 105 == Gauge.value(spec)
Gauge.reset(spec)
assert 0 == Gauge.value(spec)
end
test "inc" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
Gauge.new(spec)
Gauge.inc(spec)
Gauge.inc(spec, 3)
assert 4 == Gauge.value(spec)
Gauge.reset(spec)
assert 0 == Gauge.value(spec)
end
test "dinc" do
spec = [name: :http_requests_total,
help: ""]
Gauge.new(spec)
Gauge.dinc(spec)
Gauge.dinc(spec, 3.5)
## dinc is async. let's make sure gen_server processed our request
Process.sleep(10)
assert 4.5 == Gauge.value(spec)
Gauge.reset(spec)
assert 0 == Gauge.value(spec)
end
test "dec" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
Gauge.new(spec)
Gauge.dec(spec)
Gauge.dec(spec, 3)
assert -4 == Gauge.value(spec)
Gauge.reset(spec)
assert 0 == Gauge.value(spec)
end
test "ddec" do
spec = [name: :http_requests_total,
help: ""]
Gauge.new(spec)
Gauge.ddec(spec)
Gauge.ddec(spec, 3.5)
## ddec is async. let's make sure gen_server processed our request
Process.sleep(10)
assert -4.5 == Gauge.value(spec)
Gauge.reset(spec)
assert 0 == Gauge.value(spec)
end
test "set_to_current_time" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
Gauge.new(spec)
Gauge.set_to_current_time(spec)
assert :os.system_time(:seconds) == Gauge.value(spec)
end
test "test_track_inprogress fn" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
Gauge.new(spec)
assert 1 == Gauge.track_inprogress(spec, fn ->
Gauge.value(spec)
end)
assert_raise ErlangError, fn ->
Gauge.track_inprogress(spec, fn ->
:erlang.error({:qwe})
end)
end
assert 0 = Gauge.value(spec)
end
test "test_track_inprogress block" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
Gauge.new(spec)
assert 1 == Gauge.track_inprogress(spec, do: Gauge.value(spec))
assert_raise ErlangError, fn ->
Gauge.track_inprogress spec do
:erlang.error({:qwe})
end
end
assert 0 = Gauge.value(spec)
end
test "set_duration fn" do
spec = [name: :http_requests_total,
labels: [:method],
help: "",
duration_unit: :seconds]
Gauge.new(spec)
assert 1 == Gauge.set_duration(spec, fn ->
Process.sleep(1000)
1
end)
assert 1 < Gauge.value(spec) and Gauge.value(spec) < 1.2
assert_raise ErlangError, fn ->
Gauge.set_duration(spec, fn ->
:erlang.error({:qwe})
end)
end
assert 0.0 < Gauge.value(spec) and Gauge.value(spec) < 0.2
end
test "set_duration block" do
spec = [name: :http_requests_total,
labels: [:method],
help: "",
duration_unit: :seconds]
Gauge.new(spec)
assert :ok == Gauge.set_duration(spec, do: Process.sleep(1000))
assert 1 < Gauge.value(spec) and Gauge.value(spec) < 1.2
assert_raise ErlangError, fn ->
Gauge.set_duration spec do
:erlang.error({:qwe})
end
end
assert 0.0 < Gauge.value(spec) and Gauge.value(spec) < 0.2
end
test "remove" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
wl_spec = [name: :simple_gauge,
help: ""]
Gauge.new(spec)
Gauge.new(wl_spec)
Gauge.inc(spec)
Gauge.inc(wl_spec)
assert 1 == Gauge.value(spec)
assert 1 == Gauge.value(wl_spec)
assert true == Gauge.remove(spec)
assert true == Gauge.remove(wl_spec)
assert :undefined == Gauge.value(spec)
assert :undefined == Gauge.value(wl_spec)
assert false == Gauge.remove(spec)
assert false == Gauge.remove(wl_spec)
end
test "default value" do
lspec = [name: :http_requests_gauge,
labels: [:method],
help: ""]
Gauge.new(lspec)
assert :undefined == Gauge.value(lspec)
spec = [name: :something_gauge,
labels: [],
help: ""]
Gauge.new(spec)
assert 0 == Gauge.value(spec)
end
end
diff --git a/test/metric/histogram_test.exs b/test/metric/histogram_test.exs
index b53cb0b..bf861ac 100644
--- a/test/metric/histogram_test.exs
+++ b/test/metric/histogram_test.exs
@@ -1,316 +1,318 @@
defmodule Prometheus.HistogramTest do
use Prometheus.Case
test "registration" do
spec = [name: :name,
help: "",
registry: :qwe]
assert true == Histogram.declare(spec)
assert false == Histogram.declare(spec)
assert_raise Prometheus.MFAlreadyExistsError,
"Metric qwe:name already exists.",
fn ->
Histogram.new(spec)
end
end
test "spec errors" do
assert_raise Prometheus.MissingMetricSpecKeyError,
"Required key name is missing from metric spec.",
fn ->
Histogram.new([help: ""])
end
assert_raise Prometheus.InvalidMetricNameError,
"Invalid metric name: 12.",
fn ->
Histogram.new([name: 12, help: ""])
end
assert_raise Prometheus.InvalidMetricLabelsError,
"Invalid metric labels: 12.",
fn ->
Histogram.new([name: "qwe", labels: 12, help: ""])
end
assert_raise Prometheus.InvalidMetricHelpError,
"Invalid metric help: 12.",
fn ->
Histogram.new([name: "qwe", help: 12])
end
assert_raise Prometheus.InvalidLabelNameError,
"Invalid label name: le (histogram cannot have a label named \"le\").",
fn ->
Histogram.new([name: "qwe", help: "", labels: ["le"]])
end
## buckets
assert_raise Prometheus.HistogramNoBucketsError,
"Invalid histogram buckets: .",
fn ->
Histogram.new([name: "qwe", help: "", buckets: []])
end
assert_raise Prometheus.HistogramNoBucketsError,
"Invalid histogram buckets: undefined.",
fn ->
Histogram.new([name: "qwe", help: "", buckets: :undefined])
end
assert_raise Prometheus.HistogramInvalidBucketsError,
"Invalid histogram buckets: 1 (not a list).",
fn ->
Histogram.new([name: "qwe", help: "", buckets: 1])
end
assert_raise Prometheus.HistogramInvalidBucketsError,
"Invalid histogram buckets: [1,3,2] (buckets not sorted).",
fn ->
Histogram.new([name: "qwe", help: "", buckets: [1, 3, 2]])
end
assert_raise Prometheus.HistogramInvalidBoundError,
"Invalid histogram bound: qwe.",
fn ->
Histogram.new([name: "qwe", help: "", buckets: ["qwe"]])
end
end
test "histogram specific errors" do
spec = [name: :http_requests_total,
help: ""]
## observe
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (observe accepts only integers).",
fn ->
Histogram.observe(spec, "qwe")
end
assert_raise Prometheus.InvalidValueError,
"Invalid value: 1.5 (observe accepts only integers).",
fn ->
Histogram.observe(spec, 1.5)
end
## dobserve
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (dobserve accepts only numbers).",
fn ->
Histogram.dobserve(spec, "qwe")
end
-
- ## observe_duration
- assert_raise Prometheus.InvalidValueError,
- "Invalid value: \"qwe\" (observe_duration accepts only functions).",
- fn ->
- Histogram.observe_duration(spec, "qwe")
- end
end
test "mf/arity errors" do
spec = [name: :metric_with_label,
labels: [:label],
help: ""]
Histogram.declare(spec)
## observe
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Histogram.observe(:unknown_metric, 1)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Histogram.observe([name: :metric_with_label, labels: [:l1, :l2]], 1)
end
## dobserve
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Histogram.dobserve(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Histogram.dobserve([name: :metric_with_label, labels: [:l1, :l2]])
end
## observe_duration
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Histogram.observe_duration(:unknown_metric, fn -> 1 end)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Histogram.observe_duration(
[name: :metric_with_label, labels: [:l1, :l2]], fn -> 1 end)
end
## remove
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Histogram.remove(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Histogram.remove([name: :metric_with_label, labels: [:l1, :l2]])
end
## reset
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Histogram.reset(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Histogram.reset([name: :metric_with_label, labels: [:l1, :l2]])
end
## value
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Histogram.value(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Histogram.value([name: :metric_with_label, labels: [:l1, :l2]])
end
+
+ ## observe_duration
+ assert_raise Prometheus.InvalidBlockArityError,
+ "Fn with arity 2 (args: :x, :y) passed as block.",
+ fn ->
+ Macro.expand(quote do
+ Histogram.observe_duration(spec, fn(x, y) -> 1 + x + y end)
+ end, __ENV__)
+ end
end
test "observe" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
Histogram.new(spec)
Histogram.observe(spec)
Histogram.observe(spec, 3)
assert {[0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0], 4} == Histogram.value(spec)
Histogram.reset(spec)
assert {[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0} == Histogram.value(spec)
end
test "dobserve" do
spec = [name: :http_requests_total,
help: ""]
Histogram.new(spec)
Histogram.dobserve(spec)
Histogram.dobserve(spec, 3.5)
## dobserve is async. let's make sure gen_server processed our request
Process.sleep(10)
assert {[0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0], 4.5} == Histogram.value(spec)
Histogram.reset(spec)
assert {[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0} == Histogram.value(spec)
end
test "observe_duration fn" do
spec = [name: :duration_seconds,
labels: [:method],
help: ""]
Histogram.new(spec)
assert 1 == Histogram.observe_duration(spec, fn ->
Process.sleep(1000)
1
end)
## observe_duration is async. let's make sure gen_server processed our request
Process.sleep(10)
{buckets, sum} = Histogram.value(spec)
assert [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0] == buckets
assert 1 < sum and sum < 1.2
assert_raise ErlangError, fn ->
Histogram.observe_duration(spec, fn ->
:erlang.error({:qwe})
end)
end
## observe_duration is async. let's make sure gen_server processed our request
Process.sleep(10)
{buckets, sum} = Histogram.value(spec)
assert [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0] == buckets
assert 1 < sum and sum < 1.2
end
test "observe_duration block" do
spec = [name: :duration_seconds,
labels: [:method],
help: ""]
Histogram.new(spec)
assert :ok == Histogram.observe_duration(spec, do: Process.sleep(1000))
## observe_duration is async. let's make sure gen_server processed our request
Process.sleep(10)
{buckets, sum} = Histogram.value(spec)
assert [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0] == buckets
assert 1 < sum and sum < 1.2
assert_raise ErlangError, fn ->
Histogram.observe_duration spec do
:erlang.error({:qwe})
end
end
## observe_duration is async. let's make sure gen_server processed our request
Process.sleep(10)
{buckets, sum} = Histogram.value(spec)
assert [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0] == buckets
assert 1 < sum and sum < 1.2
end
test "remove" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
wl_spec = [name: :simple_histogram,
help: ""]
Histogram.new(spec)
Histogram.new(wl_spec)
Histogram.observe(spec)
Histogram.observe(wl_spec)
assert {[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], 1} == Histogram.value(spec)
assert {[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], 1} == Histogram.value(wl_spec)
assert true == Histogram.remove(spec)
assert true == Histogram.remove(wl_spec)
assert :undefined == Histogram.value(spec)
assert :undefined == Histogram.value(wl_spec)
assert false == Histogram.remove(spec)
assert false == Histogram.remove(wl_spec)
end
test "undefined value" do
lspec = [name: :duraiton_histogram,
labels: [:method],
buckets: [5, 10],
help: ""]
Histogram.new(lspec)
assert :undefined == Histogram.value(lspec)
spec = [name: :something_histogram,
labels: [],
buckets: [5, 10],
help: ""]
Histogram.new(spec)
assert {[0, 0, 0], 0} == Histogram.value(spec)
end
end
diff --git a/test/metric/summary_test.exs b/test/metric/summary_test.exs
index 7f87dfb..3b03aea 100644
--- a/test/metric/summary_test.exs
+++ b/test/metric/summary_test.exs
@@ -1,288 +1,290 @@
defmodule Prometheus.SummaryTest do
use Prometheus.Case
test "registration" do
spec = [name: :name,
help: "",
registry: :qwe]
assert true == Summary.declare(spec)
assert false == Summary.declare(spec)
assert_raise Prometheus.MFAlreadyExistsError,
"Metric qwe:name already exists.",
fn ->
Summary.new(spec)
end
end
test "spec errors" do
assert_raise Prometheus.MissingMetricSpecKeyError,
"Required key name is missing from metric spec.",
fn ->
Summary.new([help: ""])
end
assert_raise Prometheus.InvalidMetricNameError,
"Invalid metric name: 12.",
fn ->
Summary.new([name: 12, help: ""])
end
assert_raise Prometheus.InvalidMetricLabelsError,
"Invalid metric labels: 12.",
fn ->
Summary.new([name: "qwe", labels: 12, help: ""])
end
assert_raise Prometheus.InvalidMetricHelpError,
"Invalid metric help: 12.",
fn ->
Summary.new([name: "qwe", help: 12])
end
assert_raise Prometheus.InvalidLabelNameError,
"Invalid label name: quantile (summary cannot have a label named \"quantile\").",
fn ->
Summary.new([name: "qwe", help: "", labels: ["quantile"]])
end
end
test "summary specific errors" do
spec = [name: :http_requests_total,
help: ""]
## observe
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (observe accepts only integers).",
fn ->
Summary.observe(spec, "qwe")
end
assert_raise Prometheus.InvalidValueError,
"Invalid value: 1.5 (observe accepts only integers).",
fn ->
Summary.observe(spec, 1.5)
end
## dobserve
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (dobserve accepts only numbers).",
fn ->
Summary.dobserve(spec, "qwe")
end
## observe_duration
- assert_raise Prometheus.InvalidValueError,
- "Invalid value: \"qwe\" (observe_duration accepts only functions).",
+ assert_raise Prometheus.InvalidBlockArityError,
+ "Fn with arity 2 (args: :x, :y) passed as block.",
fn ->
- Summary.observe_duration(spec, "qwe")
+ Macro.expand(quote do
+ Summary.observe_duration(spec, fn(x, y) -> 1 + x + y end)
+ end, __ENV__)
end
end
test "mf/arity errors" do
spec = [name: :metric_with_label,
labels: [:label],
help: ""]
Summary.declare(spec)
## observe
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Summary.observe(:unknown_metric, 1)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Summary.observe([name: :metric_with_label, labels: [:l1, :l2]], 1)
end
## dobserve
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Summary.dobserve(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Summary.dobserve([name: :metric_with_label, labels: [:l1, :l2]])
end
## observe_duration
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Summary.observe_duration(:unknown_metric, fn -> 1 end)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Summary.observe_duration(
[name: :metric_with_label, labels: [:l1, :l2]], fn -> 1 end)
end
## remove
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Summary.remove(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Summary.remove([name: :metric_with_label, labels: [:l1, :l2]])
end
## reset
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Summary.reset(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Summary.reset([name: :metric_with_label, labels: [:l1, :l2]])
end
## value
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Summary.value(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Summary.value([name: :metric_with_label, labels: [:l1, :l2]])
end
end
test "observe" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
Summary.new(spec)
Summary.observe(spec)
Summary.observe(spec, 3)
assert {2, 4} == Summary.value(spec)
Summary.reset(spec)
assert {0, 0} == Summary.value(spec)
end
test "dobserve" do
spec = [name: :http_requests_total,
help: ""]
Summary.new(spec)
Summary.dobserve(spec)
Summary.dobserve(spec, 3.5)
## dobserve is async. let's make sure gen_server processed our increment request
Process.sleep(10)
assert {2, 4.5} == Summary.value(spec)
Summary.reset(spec)
assert {0, 0} == Summary.value(spec)
end
test "observe_duration fn" do
spec = [name: :duration_seconds,
labels: [:method],
help: ""]
Summary.new(spec)
assert 1 == Summary.observe_duration(spec, fn ->
Process.sleep(1000)
1
end)
## observe_duration is async. let's make sure gen_server processed our request
Process.sleep(10)
{count, sum} = Summary.value(spec)
assert 1 == count
assert 1 < sum and sum < 1.2
assert_raise ErlangError, fn ->
Summary.observe_duration(spec, fn ->
:erlang.error({:qwe})
end)
end
## observe_duration is async. let's make sure gen_server processed our request
Process.sleep(10)
{count, sum} = Summary.value(spec)
assert 2 == count
assert 1 < sum and sum < 1.2
end
test "observe_duration block" do
spec = [name: :duration_seconds,
labels: [:method],
help: ""]
Summary.new(spec)
assert :ok == Summary.observe_duration(spec, do: Process.sleep(1000))
## observe_duration is async. let's make sure gen_server processed our request
Process.sleep(10)
{count, sum} = Summary.value(spec)
assert 1 == count
assert 1 < sum and sum < 1.2
assert_raise ErlangError, fn ->
Summary.observe_duration spec do
:erlang.error({:qwe})
end
end
## observe_duration is async. let's make sure gen_server processed our request
Process.sleep(10)
{count, sum} = Summary.value(spec)
assert 2 == count
assert 1 < sum and sum < 1.2
end
test "remove" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
wl_spec = [name: :simple_summary,
help: ""]
Summary.new(spec)
Summary.new(wl_spec)
Summary.observe(spec)
Summary.observe(wl_spec)
assert {1, 1} == Summary.value(spec)
assert {1, 1} == Summary.value(wl_spec)
assert true == Summary.remove(spec)
assert true == Summary.remove(wl_spec)
assert :undefined == Summary.value(spec)
assert :undefined == Summary.value(wl_spec)
assert false == Summary.remove(spec)
assert false == Summary.remove(wl_spec)
end
test "undefined value" do
lspec = [name: :orders_summary,
labels: [:department],
help: ""]
Summary.new(lspec)
assert :undefined == Summary.value(lspec)
spec = [name: :something_summary,
labels: [],
help: ""]
Summary.new(spec)
assert {0, 0} == Summary.value(spec)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 28, 4:27 PM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41065
Default Alt Text
(71 KB)

Event Timeline