Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F115725
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
35 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/.gitignore b/.gitignore
index 6674b25..187d407 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,19 +1,20 @@
# The directory Mix will write compiled artifacts to.
/_build
# If you run "mix test --cover", coverage assets end up here.
/cover
# The directory Mix downloads your dependencies sources to.
/deps
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
.\#*
+.rebar3
diff --git a/lib/prometheus/injector.ex b/lib/prometheus/injector.ex
new file mode 100644
index 0000000..d7d4ed8
--- /dev/null
+++ b/lib/prometheus/injector.ex
@@ -0,0 +1,83 @@
+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)]}]}
+ 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})}]
+ 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
+ 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
+ {:def, def_meta, [head, [do: callback.(
+ quote do
+ try unquote(all)
+ end)]]}
+ end
+end
diff --git a/lib/prometheus/metric.ex b/lib/prometheus/metric.ex
index efcf8bb..bbf3dc6 100644
--- a/lib/prometheus/metric.ex
+++ b/lib/prometheus/metric.ex
@@ -1,150 +1,151 @@
defmodule Prometheus.Metric do
@moduledoc """
Prometheus metrics shortcuts.
Aliases and requires respective metric modules so they are
accessible without `Prometheus.Metric` prefix.
Allows to automatically setup metrics with
`@<type`> attributes. Metrics will be declared in
the `@on_load` callback. If the module already
has `@on_laod` callback, metrics will be declared
iff the callback returns `:ok`.
iex(1)> defmodule MyCoolModule do
...(1)> use Prometheus.Metric
...(1)>
...(1)> @counter name: :test_counter3, labels: [], help: "qwe"
...(1)> end
iex(2)> require Prometheus.Metric.Counter
Prometheus.Metric.Counter
iex(3)> Prometheus.Metric.Counter.value(:test_counter3)
0
"""
@metrics [:counter, :gauge, :boolean, :summary, :histogram]
defmacro __using__(_opts) do
module_name = __CALLER__.module
quote do
alias Prometheus.Metric.{Counter,Gauge,Histogram,Summary,Boolean}
require Prometheus.Metric.{Counter,Gauge,Histogram,Summary,Boolean}
+ require Prometheus.Error
unquote_splicing(
for metric <- @metrics do
quote do
Module.register_attribute unquote(module_name), unquote(metric), accumulate: true
end
end)
@before_compile unquote(__MODULE__)
end
end
defmacro __before_compile__(env) do
quote do
def __declare_prometheus_metrics__() do
if List.keymember?(Application.started_applications(), :prometheus, 0) do
unquote_splicing(
for metric <- @metrics do
declarations = Module.get_attribute(env.module, metric)
Module.delete_attribute(env.module, metric)
quote do
unquote_splicing(
for params <- declarations do
emit_create_metric(metric, params)
end)
:ok
end
end)
else
:ok
end
end
unquote(
case get_on_load_attribute(env.module) do
nil ->
quote do
@on_load :__declare_prometheus_metrics__
end
on_load ->
Module.delete_attribute(env.module, :on_load)
Module.put_attribute(env.module, :on_load, :__prometheus_on_load_override__)
quote do
def __prometheus_on_load_override__() do
case unquote(on_load)() do
:ok -> __declare_prometheus_metrics__()
result -> result
end
end
end
end)
end
end
defp get_on_load_attribute(module) do
case Module.get_attribute(module, :on_load) do
[] ->
nil
nil ->
nil
atom when is_atom(atom) ->
atom
{atom, 0} when is_atom(atom) ->
atom
[{atom, 0}] when is_atom(atom) ->
atom
other ->
raise ArgumentError,
"expected the @on_load attribute to be an atom or a " <>
"{atom, 0} tuple, got: #{inspect(other)}"
end
end
defp emit_create_metric(:counter, params) do
quote do
Prometheus.Metric.Counter.declare(unquote(params))
end
end
defp emit_create_metric(:gauge, params) do
quote do
Prometheus.Metric.Gauge.declare(unquote(params))
end
end
defp emit_create_metric(:boolean, params) do
quote do
Prometheus.Metric.Boolean.declare(unquote(params))
end
end
defp emit_create_metric(:summary, params) do
quote do
Prometheus.Metric.Summary.declare(unquote(params))
end
end
defp emit_create_metric(:histogram, params) do
quote do
Prometheus.Metric.Histogram.declare(unquote(params))
end
end
defmacro ct_parsable_spec?(spec) do
quote do
is_list(unquote(spec)) or is_atom(unquote(spec))
end
end
def parse_spec(spec) when is_list(spec) do
registry = Keyword.get(spec, :registry, :default)
name = Keyword.fetch!(spec, :name)
labels = Keyword.get(spec, :labels, [])
{registry, name, labels}
end
def parse_spec(spec) when is_atom(spec) do
{:default, spec, []}
end
end
diff --git a/lib/prometheus/metric/counter.ex b/lib/prometheus/metric/counter.ex
index e13141f..9723232 100644
--- a/lib/prometheus/metric/counter.ex
+++ b/lib/prometheus/metric/counter.ex
@@ -1,147 +1,221 @@
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
+ 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 b059715..b078f70 100644
--- a/lib/prometheus/metric/gauge.ex
+++ b/lib/prometheus/metric/gauge.ex
@@ -1,220 +1,226 @@
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.
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
+ end
+
@doc """
Tracks the amount of time spent executing `fun`.
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)])
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/test/metric/counter_test.exs b/test/metric/counter_test.exs
index c763151..4d318f0 100644
--- a/test/metric/counter_test.exs
+++ b/test/metric/counter_test.exs
@@ -1,216 +1,312 @@
defmodule Prometheus.CounterTest do
use Prometheus.Case
test "registration" do
spec = [name: :name,
help: "",
registry: :qwe]
assert true == Counter.declare(spec)
assert false == Counter.declare(spec)
assert_raise Prometheus.MFAlreadyExistsError,
"Metric qwe:name already exists.",
fn ->
Counter.new(spec)
end
end
test "spec errors" do
assert_raise Prometheus.MissingMetricSpecKeyError,
"Required key name is missing from metric spec.",
fn ->
Counter.new([help: ""])
end
assert_raise Prometheus.InvalidMetricNameError,
"Invalid metric name: 12.",
fn ->
Counter.new([name: 12, help: ""])
end
assert_raise Prometheus.InvalidMetricLabelsError,
"Invalid metric labels: 12.",
fn ->
Counter.new([name: "qwe", labels: 12, help: ""])
end
assert_raise Prometheus.InvalidMetricHelpError,
"Invalid metric help: 12.",
fn ->
Counter.new([name: "qwe", help: 12])
end
end
test "counter specific errors" do
spec = [name: :http_requests_total,
help: ""]
## inc
assert_raise Prometheus.InvalidValueError,
"Invalid value: -1 (inc accepts only non-negative integers).",
fn ->
Counter.inc(spec, -1)
end
assert_raise Prometheus.InvalidValueError,
"Invalid value: 1.5 (inc accepts only non-negative integers).",
fn ->
Counter.inc(spec, 1.5)
end
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (inc accepts only non-negative integers).",
fn ->
Counter.inc(spec, "qwe")
end
## dinc
assert_raise Prometheus.InvalidValueError,
"Invalid value: -1 (dinc accepts only non-negative numbers).",
fn ->
Counter.dinc(spec, -1)
end
assert_raise Prometheus.InvalidValueError,
"Invalid value: \"qwe\" (dinc accepts only non-negative numbers).",
fn ->
Counter.dinc(spec, "qwe")
end
end
test "mf/arity errors" do
spec = [name: :metric_with_label,
labels: [:label],
help: ""]
Counter.declare(spec)
## inc
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Counter.inc(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Counter.inc([name: :metric_with_label, labels: [:l1, :l2]])
end
## dinc
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Counter.dinc(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Counter.dinc([name: :metric_with_label, labels: [:l1, :l2]])
end
## remove
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Counter.remove(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Counter.remove([name: :metric_with_label, labels: [:l1, :l2]])
end
## reset
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Counter.reset(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Counter.reset([name: :metric_with_label, labels: [:l1, :l2]])
end
## value
assert_raise Prometheus.UnknownMetricError,
"Unknown metric {registry: default, name: unknown_metric}.",
fn ->
Counter.value(:unknown_metric)
end
assert_raise Prometheus.InvalidMetricArityError,
"Invalid metric arity: got 2, expected 1.",
fn ->
Counter.value([name: :metric_with_label, labels: [:l1, :l2]])
end
end
test "inc" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
Counter.new(spec)
Counter.inc(spec)
Counter.inc(spec, 3)
assert 4 == Counter.value(spec)
Counter.reset(spec)
assert 0 == Counter.value(spec)
end
test "dinc" do
spec = [name: :http_requests_total,
help: ""]
Counter.new(spec)
Counter.dinc(spec)
Counter.dinc(spec, 3.5)
## dinc is async. let's make sure gen_server processed our request
Process.sleep(10)
assert 4.5 == Counter.value(spec)
Counter.reset(spec)
assert 0 == Counter.value(spec)
end
test "remove" do
spec = [name: :http_requests_total,
labels: [:method],
help: ""]
wl_spec = [name: :simple_counter,
help: ""]
Counter.new(spec)
Counter.new(wl_spec)
Counter.inc(spec)
Counter.inc(wl_spec)
assert 1 == Counter.value(spec)
assert 1 == Counter.value(wl_spec)
assert true == Counter.remove(spec)
assert true == Counter.remove(wl_spec)
assert :undefined == Counter.value(spec)
assert :undefined == Counter.value(wl_spec)
assert false == Counter.remove(spec)
assert false == Counter.remove(wl_spec)
end
test "default value" do
lspec = [name: :http_requests_total,
labels: [:method],
help: ""]
Counter.new(lspec)
assert :undefined == Counter.value(lspec)
spec = [name: :something_total,
labels: [],
help: ""]
Counter.new(spec)
assert 0 == Counter.value(spec)
end
+ defmodule CounterInjectorsTest do
+
+ use Prometheus.Metric
+
+ @counter name: :calls_total, help: ""
+ @counter name: :sometimes_total, help: ""
+ @counter name: :exceptions_total, help: ""
+ @counter name: :no_exceptions_total, help: ""
+
+ Counter.count name: :calls_total do
+ def decorated_fun() do
+ IO.puts("I'm decorated fun")
+ end
+
+ def decorated_fun1() do
+ IO.puts("I'm decorated fun1")
+ IO.puts("I'm decorated fun1")
+ end
+ end
+
+ def sometimes_count(arg) do
+ if arg do
+ Counter.count [name: :sometimes_total], do: IO.puts "Called indeed!"
+ else
+ IO.puts("Not this time")
+ end
+ end
+
+ Counter.count_no_exceptions [name: :no_exceptions_total] do
+ Counter.count_exceptions [name: :exceptions_total], ArithmeticError do
+ def sometimes_raise(arg) do
+ 5/arg
+ end
+ end
+
+ def sometimes_raise1(arg) when is_list(arg) do
+ 5/arg
+ end
+ end
+
+ def qwe () do
+ Counter.count_no_exceptions [name: :no_exceptions_total], fn () ->
+ IO.puts 1
+ IO.puts 2
+ end
+ end
+
+ Counter.count_exceptions [name: :exceptions_total] do
+ def sometimes_raise_any(arg) do
+ 5/arg
+ end
+ end
+ end
+
+ test "decorators test" do
+
+ CounterInjectorsTest.__declare_prometheus_metrics__()
+
+ assert 0 == Counter.value name: :calls_total
+ assert capture_io(fn -> CounterInjectorsTest.decorated_fun() end) ==
+ "I'm decorated fun\n"
+ assert 1 == Counter.value name: :calls_total
+ assert capture_io(fn -> CounterInjectorsTest.decorated_fun1() end) ==
+ "I'm decorated fun1\nI'm decorated fun1\n"
+ assert 2 == Counter.value name: :calls_total
+
+ assert 0 == Counter.value name: :sometimes_total
+ assert capture_io(fn -> CounterInjectorsTest.sometimes_count(true) end) ==
+ "Called indeed!\n"
+ assert capture_io(fn -> CounterInjectorsTest.sometimes_count(false) end) ==
+ "Not this time\n"
+ assert 1 == Counter.value name: :sometimes_total
+
+ assert 0 == Counter.value name: :exceptions_total
+ assert 0 == Counter.value name: :no_exceptions_total
+ assert 1 == CounterInjectorsTest.sometimes_raise(5)
+ assert 0 == Counter.value name: :exceptions_total
+ assert 1 == Counter.value name: :no_exceptions_total
+ assert_raise ArithmeticError,
+ fn ->
+ CounterInjectorsTest.sometimes_raise(0)
+ end
+ assert 1 == Counter.value name: :exceptions_total
+ assert 1 == Counter.value name: :no_exceptions_total
+
+ assert 1 == Counter.value name: :exceptions_total
+ assert 1 == CounterInjectorsTest.sometimes_raise(5)
+ assert 1 == Counter.value name: :exceptions_total
+ assert_raise ArithmeticError,
+ fn ->
+ CounterInjectorsTest.sometimes_raise(0)
+ end
+ assert 2 == Counter.value name: :exceptions_total
+
+ end
+
end
diff --git a/test/metric/injector_test.exs b/test/metric/injector_test.exs
new file mode 100644
index 0000000..d27276c
--- /dev/null
+++ b/test/metric/injector_test.exs
@@ -0,0 +1,157 @@
+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)) ==
+ "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/test_helper.exs b/test/test_helper.exs
index 09bacf3..d8b7989 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -1,25 +1,25 @@
ExUnit.start()
defmodule Prometheus.Case do
defmacro __using__(_opts) do
quote do
use ExUnit.Case
-
use Prometheus
+ import ExUnit.CaptureIO
setup do
collectors = Prometheus.Registry.collectors()
Prometheus.Registry.clear()
Prometheus.Registry.clear(:qwe)
on_exit fn ->
Prometheus.Registry.clear()
Prometheus.Registry.clear(:qwe)
Prometheus.Registry.register_collectors(collectors)
end
end
end
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Nov 28, 6:25 PM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41082
Default Alt Text
(35 KB)
Attached To
Mode
R26 prometheus.ex
Attached
Detach File
Event Timeline
Log In to Comment